diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java index 38879041e..c8436a4df 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java +++ b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java @@ -36,6 +36,7 @@ import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty; import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.nodes.NodeId; import org.yaml.snakeyaml.nodes.NodeTuple; import org.yaml.snakeyaml.nodes.ScalarNode; import org.yaml.snakeyaml.nodes.SequenceNode; @@ -67,7 +68,7 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler { if (type!=null) { DynamicSchemaContext schemaContext = new ASTDynamicSchemaContext(doc, path, node); type = typeUtil.inferMoreSpecificType(type, schemaContext); - switch (node.getNodeId()) { + switch (getNodeId(node)) { case mapping: MappingNode map = (MappingNode) node; checkForDuplicateKeys(map); @@ -113,7 +114,10 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler { ValueParser parser = typeUtil.getValueParser(type, schemaContext); if (parser!=null) { try { - parser.parse(NodeUtil.asScalar(node)); + String value = NodeUtil.asScalar(node); + if (value!=null) { + parser.parse(value); + } } catch (Exception e) { String msg = ExceptionUtil.getMessage(e); valueParseError(type, node, msg); @@ -129,6 +133,36 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler { } } + protected NodeId getNodeId(Node node) { + NodeId id = node.getNodeId(); + if (id==NodeId.mapping && isMoustacheVar((MappingNode)node)) { + return NodeId.scalar; + } + return id; + } + + /** + * 'Moustache' variables look like `{{name}}` and unfortuately when + * parsed these will parse as a kind of map. But since these vars are meant to be replaced + * with some kind of string we should treat them as scalar instead. + *

+ * This function recognizes a mapping node that actually is moustache var pattern. + */ + private boolean isMoustacheVar(Node node) { + return NodeUtil.asScalar(debrace(debrace(node))) != null; + } + + private Node debrace(Node _node) { + MappingNode node = NodeUtil.asMapping(_node); + if (node!=null && node.getFlowStyle() && node.getValue().size()==1) { + NodeTuple entry = node.getValue().get(0); + if ("".equals(NodeUtil.asScalar(entry.getValueNode()))) { + return entry.getKeyNode(); + } + } + return null; + } + private YamlPath keyAt(YamlPath path, String key) { if (path!=null && key!=null) { return path.append(YamlPathSegment.keyAt(key)); 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 e7e8f74d6..4ea3f21de 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 @@ -607,8 +607,9 @@ public class YTypeFactory { return description; } - public void setDescriptionProvider(Renderable description) { + public YTypedPropertyImpl setDescriptionProvider(Renderable description) { this.description = description; + return this; } } 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 76822796d..2af8e147f 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,8 +10,6 @@ *******************************************************************************/ package org.springframework.ide.vscode.concourse; -import java.util.Set; - import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.Renderables; import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil; @@ -25,7 +23,9 @@ import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.AbstractT import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YAtomicType; 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 org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil; +import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty; import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema; /** @@ -36,7 +36,15 @@ public class PipelineYmlSchema implements YamlSchema { private final YBeanType TOPLEVEL_TYPE; private final YTypeUtil TYPE_UTIL; - private final YTypeFactory f = new YTypeFactory(); + public final YTypeFactory f = new YTypeFactory(); + public final YType t_string = f.yatomic("String"); + public final YType t_strings = f.yseq(t_string); + public final YAtomicType t_boolean = f.yenum("boolean", "true", "false"); + public final YType t_any = f.yany("Object"); + public final YType t_params = f.ymap(t_string, t_any); + public final YType t_string_params = f.ymap(t_string, t_string); + + private final ResourceTypeRegistry resourceTypes = new ResourceTypeRegistry(); public PipelineYmlSchema(ConcourseModel models) { TYPE_UTIL = f.TYPE_UTIL; @@ -44,20 +52,13 @@ public class PipelineYmlSchema implements YamlSchema { // define schema types TOPLEVEL_TYPE = f.ybean("Pipeline"); - YType t_string = f.yatomic("String"); YAtomicType t_ne_string = f.yatomic("String"); t_ne_string.parseWith(ValueParsers.NE_STRING); - YType t_strings = f.yseq(t_string); - YAtomicType t_boolean = f.yenum("boolean", "true", "false"); YAtomicType t_pos_integer = f.yatomic("Positive Integer"); t_pos_integer.parseWith(ValueParsers.POS_INTEGER); YAtomicType t_strictly_pos_integer = f.yatomic("Strictly Positive Integer"); t_strictly_pos_integer.parseWith(ValueParsers.integerAtLeast(1)); - YType t_any = f.yany("Object"); - YType t_params = f.ymap(t_string, t_any); - YType t_string_params = f.ymap(t_string, t_string); - YAtomicType t_duration = f.yatomic("Duration"); t_duration.parseWith(ValueParsers.DURATION); @@ -119,28 +120,28 @@ public class PipelineYmlSchema implements YamlSchema { jobNameDef.parseWith(ValueParsers.jobNameDef(models)); YBeanType getStep = f.ybean("GetStep"); - prop(getStep, "get", resourceName); - prop(getStep, "resource", t_string); - prop(getStep, "version", t_version); - prop(getStep, "passed", f.yseq(jobName)); - prop(getStep, "params", t_params); - prop(getStep, "trigger", t_boolean); + addProp(getStep, "get", resourceName); + addProp(getStep, "resource", t_string); + addProp(getStep, "version", t_version); + addProp(getStep, "passed", f.yseq(jobName)); + addProp(getStep, "params", t_params); + addProp(getStep, "trigger", t_boolean); YBeanType putStep = f.ybean("PutStep"); - prop(putStep, "put", resourceName); - prop(putStep, "resource", jobName); - prop(putStep, "params", t_params); - prop(putStep, "get_params", t_params); + addProp(putStep, "put", resourceName); + addProp(putStep, "resource", jobName); + addProp(putStep, "params", t_params); + addProp(putStep, "get_params", t_params); YBeanType taskStep = f.ybean("TaskStep"); - prop(taskStep, "task", t_ne_string); - prop(taskStep, "file", t_string); - prop(taskStep, "config", t_any); - prop(taskStep, "privileged", t_boolean); - prop(taskStep, "params", t_params); - prop(taskStep, "image", t_ne_string); - prop(taskStep, "input_mapping", f.ymap(t_ne_string, resourceName)); - prop(taskStep, "output_mapping", t_string_params); + addProp(taskStep, "task", t_ne_string); + addProp(taskStep, "file", t_string); + addProp(taskStep, "config", t_any); + addProp(taskStep, "privileged", t_boolean); + addProp(taskStep, "params", t_params); + addProp(taskStep, "image", t_ne_string); + addProp(taskStep, "input_mapping", f.ymap(t_ne_string, resourceName)); + addProp(taskStep, "output_mapping", t_string_params); YBeanType aggregateStep = f.ybean("AggregateStep"); YBeanType doStep = f.ybean("DoStep"); @@ -155,83 +156,111 @@ public class PipelineYmlSchema implements YamlSchema { tryStep }; YBeanUnionType step = f.yunion("Step", stepTypes); - prop(aggregateStep, "aggregate", f.yseq(step)); - prop(doStep, "do", f.yseq(step)); - prop(tryStep, "try", step); + addProp(aggregateStep, "aggregate", f.yseq(step)); + addProp(doStep, "do", f.yseq(step)); + addProp(tryStep, "try", step); // shared properties applicable for any subtype of Step: for (YBeanType subStep : stepTypes) { - prop(step, subStep, "on_success", step); - prop(step, subStep, "on_failure", step); - prop(step, subStep, "ensure", step); - prop(step, subStep, "attempts", t_strictly_pos_integer); - prop(step, subStep, "tags", t_strings); - prop(step, subStep, "timeout", t_duration); + addProp(step, subStep, "on_success", step); + addProp(step, subStep, "on_failure", step); + addProp(step, subStep, "ensure", step); + addProp(step, subStep, "attempts", t_strictly_pos_integer); + addProp(step, subStep, "tags", t_strings); + addProp(step, subStep, "timeout", t_duration); } - YType gitResourceSource = t_any; - YType resourceSource = f.contextAware("ResourceSource", (dc) -> { - YamlPath path = dc.getPath(); - if (path!=null) { - YamlFileAST root = models.getSafeAst(dc.getDocument()); - if (root!=null) { - String typeTag = NodeUtil.asScalar(path.dropLast().append(YamlPathSegment.valueAt("type")).traverseToNode(root)); - switch (typeTag) { - case "git": - return gitResourceSource; - default: - break; - } - } + String typeTag = getResourceTypeTag(models, dc); + if (typeTag!=null) { + return resourceTypes.getSourceType(typeTag); } return t_any; }); YBeanType resource = f.ybean("Resource"); - prop(resource, "name", resourceNameDef); - prop(resource, "type", t_resource_type_name); - prop(resource, "source", resourceSource); - prop(resource, "check_every", t_duration); + addProp(resource, "name", resourceNameDef); + addProp(resource, "type", t_resource_type_name); + addProp(resource, "source", resourceSource); + addProp(resource, "check_every", t_duration); YBeanType job = f.ybean("Job"); - prop(job, "name", jobNameDef); - prop(job, "serial", t_boolean); - prop(job, "build_logs_to_retain", t_pos_integer); - prop(job, "serial_groups", t_strings); - prop(job, "max_in_flight", t_pos_integer); - prop(job, "public", t_boolean); - prop(job, "disable_manual_trigger", t_boolean); - prop(job, "plan", f.yseq(step)); + addProp(job, "name", jobNameDef); + addProp(job, "serial", t_boolean); + addProp(job, "build_logs_to_retain", t_pos_integer); + addProp(job, "serial_groups", t_strings); + addProp(job, "max_in_flight", t_pos_integer); + addProp(job, "public", t_boolean); + addProp(job, "disable_manual_trigger", t_boolean); + addProp(job, "plan", f.yseq(step)); YBeanType resourceType = f.ybean("ResourceType"); - prop(resourceType, "name", t_ne_string); - prop(resourceType, "type", t_image_type); - prop(resourceType, "source", t_any); + addProp(resourceType, "name", t_ne_string); + addProp(resourceType, "type", t_image_type); + addProp(resourceType, "source", t_any); YBeanType group = f.ybean("Group"); - prop(group, "name", t_ne_string); - prop(group, "resources", f.yseq(resourceName)); - prop(group, "jobs", f.yseq(jobName)); - - prop(TOPLEVEL_TYPE, "resources", f.yseq(resource)); - prop(TOPLEVEL_TYPE, "jobs", f.yseq(job)); - prop(TOPLEVEL_TYPE, "resource_types", f.yseq(resourceType)); - prop(TOPLEVEL_TYPE, "groups", f.yseq(group)); + addProp(group, "name", t_ne_string); + addProp(group, "resources", f.yseq(resourceName)); + addProp(group, "jobs", f.yseq(jobName)); + addProp(TOPLEVEL_TYPE, "resources", f.yseq(resource)); + addProp(TOPLEVEL_TYPE, "jobs", f.yseq(job)); + addProp(TOPLEVEL_TYPE, "resource_types", f.yseq(resourceType)); + addProp(TOPLEVEL_TYPE, "groups", f.yseq(group)); + + initializeDefaultResourceTypes(); } - private void prop(AbstractType superType, AbstractType bean, String name, YType type) { - bean.addProperty(name, type, descriptionFor(superType, name)); + private void initializeDefaultResourceTypes() { + YBeanType gitSource = f.ybean("GitResourceSource"); + addProp(gitSource, "uri", t_string); + addProp(gitSource, "branch", t_string); + addProp(gitSource, "private_key", t_string); + addProp(gitSource, "username", t_string); + addProp(gitSource, "password", t_string); + addProp(gitSource, "paths", t_strings); + addProp(gitSource, "ignore_paths", t_strings); + addProp(gitSource, "skip_ssl_verification", t_boolean); + addProp(gitSource, "tag_filter", t_string); + addProp(gitSource, "git_config", t_string); + addProp(gitSource, "disable_ci_skip", t_boolean); + addProp(gitSource, "commit_verification_keys", t_strings); + addProp(gitSource, "commit_verification_key_ids", t_strings); + addProp(gitSource, "gpg_keyserver", t_strings); + + resourceTypes.def("git", gitSource); } - private void prop(AbstractType bean, String name, YType type) { - prop(bean, bean, name, type); + private String getResourceTypeTag(ConcourseModel models, DynamicSchemaContext dc) { + YamlPath path = dc.getPath(); + if (path!=null) { + YamlFileAST root = models.getSafeAst(dc.getDocument()); + if (root!=null) { + return NodeUtil.asScalar(path.dropLast().append(YamlPathSegment.valueAt("type")).traverseToNode(root)); + } + } + return null; } - private Renderable descriptionFor(YType owner, String propName) { + private YTypedProperty prop(AbstractType beanType, String name, YType type) { + YTypedPropertyImpl prop = f.yprop(name, type); + prop.setDescriptionProvider(descriptionFor(beanType, name)); + return prop; + } + + + private void addProp(AbstractType superType, AbstractType bean, String name, YType type) { + bean.addProperty(prop(superType, name, type)); + } + + private void addProp(AbstractType bean, String name, YType type) { + addProp(bean, bean, name, type); + } + + public static Renderable descriptionFor(YType owner, String propName) { String typeName = owner.toString(); - return Renderables.fromClasspath(this.getClass(), "/desc/"+typeName+"/"+propName); + return Renderables.fromClasspath(PipelineYmlSchema.class, "/desc/"+typeName+"/"+propName); } @Override diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ResourceTypeRegistry.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ResourceTypeRegistry.java new file mode 100644 index 000000000..f5bf907e3 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ResourceTypeRegistry.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.concourse; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.ide.vscode.commons.util.Assert; +import org.springframework.ide.vscode.commons.yaml.schema.YType; +import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanType; + +/** + * Keeps track of known resource types. For now this only keeps track of the resource-types that + * are built-in to concourse. + * + * @author Kris De Volder + */ +public class ResourceTypeRegistry { + + private Map sourceTypes = new HashMap<>(); + + public ResourceTypeRegistry() { + } + + public void def(String resourceTypeName, YBeanType sourceType) { + Assert.isLegal(!sourceTypes.containsKey(resourceTypeName), "Multiple definitions for '"+resourceTypeName+"'"); + sourceTypes.put(resourceTypeName, sourceType); + } + + public YType getSourceType(String typeTag) { + return sourceTypes.get(typeTag); + } + +} 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 8c5a24b6a..c94e9e3d0 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 @@ -470,7 +470,7 @@ public class PipelineYamlEditorTest { "- name: sts4\n" + " type: git\n" + " source:\n" + - " repository: https://github.com/kdvolder/somestuff\n" + + " uri: https://github.com/kdvolder/somestuff\n" + "jobs:\n" + "- name: job1\n" + " plan:\n" + @@ -502,15 +502,15 @@ public class PipelineYamlEditorTest { "- name: sts4\n" + " type: git\n" + " source:\n" + - " repository: https://github.com/kdvolder/somestuff\n" + + " uri: https://github.com/kdvolder/somestuff\n" + "- name: utils\n" + " type: git\n" + " source:\n" + - " repository: https://github.com/kdvolder/someutils\n" + + " uri: https://github.com/kdvolder/someutils\n" + "- name: sts4\n" + " type: git\n" + " source:\n" + - " repository: https://github.com/kdvolder/extras\n" + " uri: https://github.com/kdvolder/extras\n" ); editor.assertProblems( "sts4|Duplicate resource name", @@ -572,13 +572,13 @@ public class PipelineYamlEditorTest { "- name: my-repo\n" + " type: git\n" + " source:\n" + - " repository: https://github.com/kdvolder/my-repo\n" + + " uri: https://github.com/kdvolder/my-repo\n" + "resources:\n" + "- name: your-repo\n" + " type: git\n" + " type: git\n" + " source:\n" + - " repository: https://github.com/kdvolder/forked-repo\n" + " uri: https://github.com/kdvolder/forked-repo\n" ); editor.assertProblems(