From f70d4b584d15c421135b291c6b049cc8969262d4 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Tue, 24 Jan 2017 17:48:58 -0800 Subject: [PATCH] resource-type name reconciling --- .../reconcile/ReconcileException.java | 3 - .../commons/yaml/schema/YTypeFactory.java | 53 +++++++++++- .../ide/vscode/concourse/ConcourseModel.java | 39 +++++++++ .../concourse/ConcourseValueParsers.java | 19 ++++- .../vscode/concourse/PipelineYmlSchema.java | 73 ++++++++++------- .../concourse/PipelineYamlEditorTest.java | 81 +++++++++++++++---- .../manifest/yaml/ManifestYmlSchema.java | 2 +- .../yaml/ManifestYmlValueParsers.java | 23 ------ 8 files changed, 221 insertions(+), 72 deletions(-) diff --git a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReconcileException.java b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReconcileException.java index 147b2facf..8dcc495df 100644 --- a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReconcileException.java +++ b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/reconcile/ReconcileException.java @@ -18,9 +18,6 @@ package org.springframework.ide.vscode.commons.languageserver.reconcile; */ public class ReconcileException extends Exception implements ProblemTypeProvider { - /** - * - */ private static final long serialVersionUID = 1L; private final ProblemType problemType; diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java index f9e582825..95b09212b 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java +++ b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java @@ -23,15 +23,20 @@ import java.util.concurrent.Callable; import java.util.function.BiFunction; import java.util.stream.Collectors; +import javax.inject.Provider; + import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.EnumValueParser; import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.Renderables; import org.springframework.ide.vscode.commons.util.ValueParser; +import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YAtomicType; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multiset; /** * Static utility method for creating YType objects representing either @@ -607,6 +612,21 @@ public class YTypeFactory { return new YTypedPropertyImpl(name, type); } + public YAtomicType yenumFromHints(String name, BiFunction, String> errorMessageFormatter, SchemaContextAware> values) { + YAtomicType t = yatomic(name); + t.addHintProvider((dc) -> () -> values.withContext(dc)); + t.parseWith((DynamicSchemaContext dc) -> { + Collection strings = YTypeFactory.values(values.withContext(dc)); + return new EnumValueParser("blah", strings) { + @Override + protected String createErrorMessage(String parseString, Collection values) { + return errorMessageFormatter.apply(parseString, values); + } + }; + }); + return t; + } + public YAtomicType yenum(String name, BiFunction, String> errorMessageFormatter, SchemaContextAware> values) { YAtomicType t = yatomic(name); t.addHintProvider((dc) -> { @@ -629,6 +649,10 @@ public class YTypeFactory { return t; } + public static Collection values(Collection hints) { + return hints.stream().map(YValueHint::getValue).collect(Collectors.toList()); + } + public YAtomicType yenum(String name, String... values) { YAtomicType t = yatomic(name); t.addHints(values); @@ -636,8 +660,35 @@ public class YTypeFactory { return t; } - public YValueHint hint(String value, String label) { + public static Callable> valuesFromHintProvider(Callable> hintProvider) { + Callable> values = () -> { + Collection hints = hintProvider.call(); + if (hints != null) { + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (YValueHint hint : hints ) { + builder.add(hint.getValue()); + } + return builder.build(); + } + return null; + }; + return values; + } + + public static YValueHint hint(String value, String label) { return new BasicYValueHint(value, label); } + public static YValueHint hint(String value) { + return new BasicYValueHint(value); + } + + public static Collection hints(Collection values) { + if (values!=null) { + return values.stream().map(YTypeFactory::hint).collect(Collectors.toList()); + } + return null; + } + + } diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java index 6df4fd28f..108ab541d 100644 --- a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java @@ -13,7 +13,12 @@ package org.springframework.ide.vscode.concourse; import static org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment.anyChild; import static org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment.valueAt; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; import java.util.function.Function; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; @@ -28,14 +33,22 @@ import org.springframework.ide.vscode.commons.yaml.path.ASTRootCursor; import org.springframework.ide.vscode.commons.yaml.path.NodeCursor; import org.springframework.ide.vscode.commons.yaml.path.YamlPath; import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment; +import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory; +import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; import org.springframework.ide.vscode.concourse.util.CollectorUtil; import org.springframework.ide.vscode.concourse.util.StaleFallbackCache; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.error.YAMLException; import org.yaml.snakeyaml.nodes.Node; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMultiset; +import com.google.common.collect.ImmutableMultiset.Builder; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multiset; +import reactor.core.publisher.Flux; + /** * ConcourseModels is responsible for extracting various bits of information * out of .yml documents and caching them for use by various tools (reconcile engine @@ -55,6 +68,12 @@ public class ConcourseModel { valueAt("name") ); + private static final YamlPath RESOURCE_TYPE_NAMES_PATH = new YamlPath( + valueAt("resource_types"), + anyChild(), + valueAt("name") + ); + private static final YamlPath RESOURCES_PATH = new YamlPath( anyChild(), // skip over the root node which contains multiple doces valueAt("resources"), @@ -140,6 +159,26 @@ public class ConcourseModel { }); } + public Multiset getResourceTypeNames(IDocument doc) { + Collection hints = getResourceTypeNameHints(doc); + if (hints!=null) { + return ImmutableMultiset.copyOf(YTypeFactory.values(hints)); + } + return null; + } + + public Collection getResourceTypeNameHints(IDocument doc) { + Multiset userDefined = getStringsFromAst(doc, RESOURCE_TYPE_NAMES_PATH); + if (userDefined!=null) { + Builder builder = ImmutableMultiset.builder(); + builder.addAll(YTypeFactory.hints(userDefined)); + builder.addAll(Arrays.asList(PipelineYmlSchema.BUILT_IN_RESOURCE_TYPES)); + return builder.build(); + } + return null; + } + + private T getFromAst(IDocument doc, Function astFunction) { try { if (doc!=null) { diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseValueParsers.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseValueParsers.java index 770782c8f..b6b590690 100644 --- a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseValueParsers.java +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseValueParsers.java @@ -10,14 +10,20 @@ *******************************************************************************/ package org.springframework.ide.vscode.concourse; +import java.util.Collection; +import java.util.concurrent.Callable; import java.util.function.Function; +import org.springframework.ide.vscode.commons.util.EnumValueParser; import org.springframework.ide.vscode.commons.util.RegexpParser; import org.springframework.ide.vscode.commons.util.ValueParser; import org.springframework.ide.vscode.commons.util.text.IDocument; import org.springframework.ide.vscode.commons.yaml.schema.SchemaContextAware; +import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multiset; +import com.google.common.collect.ImmutableSet.Builder; /** * Methods and constants to create/get parsers for some atomic types @@ -27,6 +33,17 @@ import com.google.common.collect.Multiset; */ public class ConcourseValueParsers { +// public static final SchemaContextAware resourceTypeName(ConcourseModel models) { +// return (dc) -> { +// return new EnumValueParser("ResourceType Name", models.getResourceTypeNames(dc.getDocument())) { +// @Override +// protected String createErrorMessage(String value, Collection validValues) { +// return "The '"+value+"' Resource Type does not exist. Existing resource types: "+validValues; +// } +// }; +// }; +// }; + public static final SchemaContextAware resourceNameDef(ConcourseModel models) { return acceptOnlyUniqueNames(models::getResourceNames, "resource name"); } @@ -61,6 +78,4 @@ public class ConcourseValueParsers { ); - - } diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java index 5babe0b9c..9c3542d5b 100644 --- a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java @@ -10,6 +10,9 @@ *******************************************************************************/ package org.springframework.ide.vscode.concourse; +import java.util.Set; +import java.util.stream.Collectors; + import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.Renderables; import org.springframework.ide.vscode.commons.util.ValueParsers; @@ -25,7 +28,11 @@ import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YAtomicTy import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanType; import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanUnionType; import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YTypedPropertyImpl; + +import reactor.core.publisher.Flux; + import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil; +import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema; /** @@ -33,6 +40,33 @@ import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema; */ public class PipelineYmlSchema implements YamlSchema { + //TODO: the infos for composing this should probably be integrated somehow in the ResourceTypeRegistry so + // we only have a list of built-in resource types in a single place. + public static final YValueHint[] BUILT_IN_RESOURCE_TYPES = { + hint("git", "The 'git' resource can pull and push to git repositories."), + hint("hg", "The 'hg' resource can pull and push to Mercurial repositories."), + hint("time", "The 'time' resource can start jobs on a schedule or timestamp outputs."), + hint("s3", "The 's3' resource can fetch from and upload to S3 buckets."), + hint("archive", "The 'archive' resource can fetch and extract .tar.gz archives."), + hint("semver", "The 'semver' resource can set or bump version numbers."), + hint("github-release", "The 'github-release' resource can fetch and publish versioned GitHub resources."), + hint("docker-image", "The 'docker-image' resource can fetch, build, and push Docker images."), + hint("tracker", "The 'tracker' resource can deliver stories and bugs on Pivotal Tracker."), + hint("pool", "The 'pool' resource allows you to configure how to serialize use of an external system. " + + "This lets you prevent test interference or overwork on shared systems."), + hint("cf", "The cf resource can deploy an application to Cloud Foundry."), + hint("bosh-io-release", "The bosh-io-release resource can track and fetch new BOSH releases from bosh.io."), + hint("bosh-io-stemcell", "The bosh-io-stemcell resource can track and fetch new BOSH stemcells from bosh.io."), + hint("bosh-deployment", "The bosh-deployment resource can deploy BOSH stemcells and releases."), + hint("vagrant-cloud", "The vagrant-cloud resource can fetch and publish Vagrant boxes to Atlas.") + }; + + public static final Set BUILT_IN_RESOURCE_TYPE_NAMES = Flux.fromArray(PipelineYmlSchema.BUILT_IN_RESOURCE_TYPES) + .map(YValueHint::getValue) + .collect(Collectors.toSet()) + .block(); + + private final YBeanType TOPLEVEL_TYPE; private final YTypeUtil TYPE_UTIL; @@ -76,32 +110,13 @@ public class PipelineYmlSchema implements YamlSchema { YAtomicType t_image_type = f.yatomic("ImageType"); t_image_type.addHints("docker_image"); - YAtomicType t_resource_type_name = f.yatomic("ResourceType Name"); - t_resource_type_name.addHints( - f.hint("archive", "archive - The 'archive' resource can fetch and extract .tar.gz archives."), - f.hint("git", "git - The 'git' resource can pull and push to git repositories"), - f.hint("s3", "s3 - The 's3' resource can fetch from and upload to S3 buckets."), - f.hint("semver", "semver - The 'semver' resource can set or bump version numbers."), - f.hint("time", "time - The 'time' resource can start jobs on a schedule or timestamp outputs."), - f.hint("docker-image", "docker-image - The 'docker-image' resource can fetch, build, and push Docker images") - //TODO: add more resource types and descriptions. -// -// The github-release resource can fetch and publish versioned GitHub resources. -// -// -// The tracker resource can deliver stories and bugs on Pivotal Tracker -// -// The pool resource allows you to configure how to serialize use of an external system. This lets you prevent test interference or overwork on shared systems. -// -// The cf resource can deploy an application to Cloud Foundry. -// -// The bosh-io-release resource can track and fetch new BOSH releases from bosh.io. -// -// The bosh-io-stemcell resource can track and fetch new BOSH stemcells from bosh.io. -// -// The bosh-deployment resource can deploy BOSH stemcells and releases. -// -// The vagrant-cloud r + YAtomicType t_resource_type_name = f.yenumFromHints("ResourceType Name", + (parseString, validValues) -> { + return "The '"+parseString+"' Resource Type does not exist. Existing types: "+validValues; + }, + (DynamicSchemaContext dc) -> { + return models.getResourceTypeNameHints(dc.getDocument()); + } ); this.t_resource_name = f.yenum("Resource Name", @@ -109,7 +124,7 @@ public class PipelineYmlSchema implements YamlSchema { return "The '"+parseString+"' resource does not exist. Existing resources: "+validValues; }, (DynamicSchemaContext dc) -> { - return models.getResourceNames(dc.getDocument()); + return (models.getResourceNames(dc.getDocument())); } ); @@ -221,6 +236,10 @@ public class PipelineYmlSchema implements YamlSchema { initializeDefaultResourceTypes(); } + private static YValueHint hint(String value, String description) { + return YTypeFactory.hint(value, value + " - " + description); + } + private void initializeDefaultResourceTypes() { // git : { diff --git a/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java index b5473734b..75ffea671 100644 --- a/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java +++ b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java @@ -17,7 +17,6 @@ import java.io.InputStream; import java.util.Arrays; import java.util.stream.Collectors; -import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.DiagnosticSeverity; import org.junit.Before; import org.junit.Test; @@ -396,22 +395,26 @@ public class PipelineYamlEditorTest { @Test public void valueCompletions() throws Exception { + String [] builtInResourceTypes = { + "git", "hg", "time", "s3", + "archive", "semver", "github-release", + "docker-image", "tracker", "pool", "cf", "bosh-io-release", + "bosh-io-stemcell", "bosh-deployment", "vagrant-cloud" + }; + Arrays.sort(builtInResourceTypes); + + String[] expectedCompletions = new String[builtInResourceTypes.length]; + for (int i = 0; i < expectedCompletions.length; i++) { + expectedCompletions[i] = + "resources:\n" + + "- type: "+builtInResourceTypes[i]+"<*>"; + } + assertCompletions( "resources:\n" + "- type: <*>" , //=> - "resources:\n" + - "- type: archive<*>", - "resources:\n" + - "- type: docker-image<*>", - "resources:\n" + - "- type: git<*>", - "resources:\n" + - "- type: s3<*>", - "resources:\n" + - "- type: semver<*>", - "resources:\n" + - "- type: time<*>" + expectedCompletions ); assertCompletions( "jobs:\n" + @@ -1051,9 +1054,9 @@ public class PipelineYamlEditorTest { //addProp(resource, "name", resourceNameDef).isRequired(true); editor = harness.newEditor( "resources:\n" + - "- type: foo" + "- type: git" ); - editor.assertProblems("type: foo|'name' is required"); + editor.assertProblems("type: git|'name' is required"); //addProp(resource, "type", t_resource_type_name).isRequired(true); editor = harness.newEditor( @@ -1312,6 +1315,54 @@ public class PipelineYamlEditorTest { ); } + @Test public void reconcileResourceTypeNames() throws Exception { + String userDefinedResourceTypesSnippet = + "resource_types:\n" + + "- name: s3-multi\n" + + " type: docker-image\n" + + " source:\n" + + " repository: kdvolder/s3-resource-simple\n" + + "- name: slack-notification\n" + + " type: docker-image\n" + + " source:\n" + + " repository: cfcommunity/slack-notification-resource\n" + + " tag: latest\n"; + String[] goodNames = { + //user-defined: + "s3-multi", "slack-notification", + //built-in: + "git", "hg", "time", "s3", + "archive", "semver", "github-release", + "docker-image", "tracker", "pool", "cf", "bosh-io-release", + "bosh-io-stemcell", "bosh-deployment", "vagrant-cloud" + }; + String[] badNames = { + "bogus", "wrong", "not-defined-resource-type" + }; + + //All the bad names are detected and flagged: + for (String badName : badNames) { + Editor editor = harness.newEditor( + userDefinedResourceTypesSnippet + + "resources:\n" + + "- name: the-resource\n" + + " type: "+badName + ); + editor.assertProblems(badName+"|Resource Type does not exist"); + } + + //All the good names are accepted: + for (String goodName : goodNames) { + Editor editor = harness.newEditor( + userDefinedResourceTypesSnippet + + "resources:\n" + + "- name: the-resource\n" + + " type: "+goodName + ); + editor.assertProblems(/*None*/); + } + } + ////////////////////////////////////////////////////////////////////////////// private void assertContextualCompletions(String conText, String textBefore, String... textAfter) throws Exception { diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java index 74af343e2..d1848cfc9 100644 --- a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java +++ b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java @@ -68,7 +68,7 @@ public class ManifestYmlSchema implements YamlSchema { if (servicesProvider != null) { t_service_string.addHintProvider(servicesProvider); t_service_string.parseWith(new CFServicesValueParser(t_service_string.toString(), - ManifestYmlValueParsers.getValuesFromHints(servicesProvider))); + YTypeFactory.valuesFromHintProvider(servicesProvider))); } YType t_services = f.yseq(t_service_string); diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlValueParsers.java b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlValueParsers.java index 897c04138..612f5aa70 100644 --- a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlValueParsers.java +++ b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlValueParsers.java @@ -92,27 +92,4 @@ public class ManifestYmlValueParsers { }; } - public static ValueParser fromHints(String typeName, Callable> hintProvider) { - Callable> values = getValuesFromHints(hintProvider); - - return new EnumValueParser(typeName, values); - } - - public static Callable> getValuesFromHints(Callable> hintProvider) { - Callable> values= () -> { - Collection hints = hintProvider.call(); - if (hints != null) { - Builder builder = ImmutableSet.builder(); - - for (YValueHint hint : hints ) { - builder.add(hint.getValue()); - } - return builder.build(); - } - - return null; - }; - return values; - } - }