GitResource 'get' params support

This commit is contained in:
Kris De Volder
2017-01-21 10:49:03 -08:00
parent 1fb5c6ddeb
commit 0eabef9787
6 changed files with 189 additions and 24 deletions

View File

@@ -98,4 +98,19 @@ public class NodeUtil {
return Collections.emptySet();
}
public static String getScalarProperty(Node node, String propName) {
if (node instanceof MappingNode) {
for (NodeTuple entry : ((MappingNode)node).getValue()) {
String key = NodeUtil.asScalar(entry.getKeyNode());
if (propName.equals(key)) {
String value = asScalar(entry.getValueNode());
if (value!=null) {
return value;
}
}
}
}
return null;
}
}

View File

@@ -46,7 +46,7 @@ public class YTypeFactory {
return new YContextSensitive(name, guessType);
}
public YType yany(String name) {
public AbstractType yany(String name) {
return new YAny(name);
}
@@ -246,7 +246,7 @@ public class YTypeFactory {
public void addProperty(String name, YType type) {
addProperty(new YTypedPropertyImpl(name, type));
}
public void addHints(String... strings) {
public AbstractType addHints(String... strings) {
if (strings != null) {
for (String value : strings) {
BasicYValueHint hint = new BasicYValueHint(value);
@@ -255,6 +255,7 @@ public class YTypeFactory {
}
}
}
return this;
}
public void addHints(YValueHint... extraHints) {
@@ -269,8 +270,9 @@ public class YTypeFactory {
this.parser = parser;
}
public void parseWith(ValueParser parser) {
public AbstractType parseWith(ValueParser parser) {
parseWith((DynamicSchemaContext dc) -> parser);
return this;
}
private ValueParser getParser(DynamicSchemaContext dc) {
return parser == null ? null : parser.withContext(dc);

View File

@@ -23,7 +23,10 @@ import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider;
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
import org.springframework.ide.vscode.commons.yaml.ast.YamlParser;
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.concourse.util.CollectorUtil;
import org.springframework.ide.vscode.concourse.util.StaleFallbackCache;
import org.yaml.snakeyaml.Yaml;
@@ -33,12 +36,12 @@ import org.yaml.snakeyaml.nodes.Node;
import com.google.common.collect.Multiset;
/**
* ConcourseModels is responsible for extracting various bits of information
* ConcourseModels is responsible for extracting various bits of information
* out of .yml documents and caching them for use by various tools (reconcile engine
* and completion engine).
*/
public class ConcourseModel {
private static final YamlPath RESOURCE_NAMES_PATH = new YamlPath(
valueAt("resources"),
anyChild(),
@@ -50,7 +53,13 @@ public class ConcourseModel {
anyChild(),
valueAt("name")
);
private static final YamlPath RESOURCES_FROM_ROOT_PATH = new YamlPath(
anyChild(), // skip over the root node which contains multiple doces
valueAt("resources"),
anyChild()
);
private final YamlParser parser;
private final StaleFallbackCache<String, YamlFileAST> asts = new StaleFallbackCache<>();
@@ -66,7 +75,7 @@ public class ConcourseModel {
asts.invalidate(uri);
}
}
/**
* Returns the resource names that are defined by given IDocument. If the contents
* of IDocument is not currently parseable then this may return stale information
@@ -80,7 +89,29 @@ public class ConcourseModel {
return getStringsFromAst(doc, RESOURCE_NAMES_PATH);
}
/**
* Get the resource type tag associated with a given resourceName in the given document.
* <p>
* If the content of IDocument is not currently parseable then this may return stale information
* retained from a previous successful parse.
* <p>
* It may also return null if its not currently possible to obtain type of the resource. E.g
* because there is no such resource, the resource has no valid type tag, or the document
* was never successfully parsed.
*/
public String getResourceType(IDocument doc, String resourceName) {
return getFromAst(doc, (ast) -> {
Node resource = RESOURCES_FROM_ROOT_PATH.traverseAmbiguously(new ASTRootCursor(ast))
.map((cursor) -> ((NodeCursor)cursor).getNode())
.filter((resourceNode) -> resourceName.equals(NodeUtil.getScalarProperty(resourceNode, "name")))
.findFirst().orElse(null);
if (resource!=null) {
return NodeUtil.getScalarProperty(resource, "type");
}
return null;
});
}
/**
* Returns the job names that are defined by given IDocument. If the contents
* of IDocument is not currently parseable then this may return stale information
@@ -104,14 +135,14 @@ public class ConcourseModel {
.collect(CollectorUtil.toMultiset());
});
}
private <T> T getFromAst(IDocument doc, Function<YamlFileAST, T> getResourceNames) {
private <T> T getFromAst(IDocument doc, Function<YamlFileAST, T> astFunction) {
try {
if (doc!=null) {
String uri = doc.getUri();
if (uri!=null) {
YamlFileAST ast = getAst(doc);
return getResourceNames.apply(ast);
return astFunction.apply(ast);
}
}
} catch (YAMLException e) {
@@ -146,5 +177,4 @@ public class ConcourseModel {
};
}
}

View File

@@ -12,6 +12,7 @@ package org.springframework.ide.vscode.concourse;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.util.StringUtil;
import org.springframework.ide.vscode.commons.util.ValueParsers;
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
@@ -50,6 +51,10 @@ public class PipelineYmlSchema implements YamlSchema {
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);
public final YType t_pos_integer = f.yatomic("Positive Integer")
.parseWith(ValueParsers.POS_INTEGER);
public final YType t_strictly_pos_integer = f.yatomic("Strictly Positive Integer")
.parseWith(ValueParsers.integerAtLeast(1));
private final ResourceTypeRegistry resourceTypes = new ResourceTypeRegistry();
@@ -61,10 +66,6 @@ public class PipelineYmlSchema implements YamlSchema {
YAtomicType t_ne_string = f.yatomic("String");
t_ne_string.parseWith(ValueParsers.NE_STRING);
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));
YAtomicType t_duration = f.yatomic("Duration");
t_duration.parseWith(ConcourseValueParsers.DURATION);
@@ -131,7 +132,9 @@ public class PipelineYmlSchema implements YamlSchema {
addProp(getStep, "resource", t_string);
addProp(getStep, "version", t_version);
addProp(getStep, "passed", f.yseq(jobName));
addProp(getStep, "params", t_params);
addProp(getStep, "params", f.contextAware("GetParams", (dc) ->
resourceTypes.getInParamsType(getResourceType("get", models, dc))
));
addProp(getStep, "trigger", t_boolean);
YBeanType putStep = f.ybean("PutStep");
@@ -216,6 +219,7 @@ public class PipelineYmlSchema implements YamlSchema {
}
private void initializeDefaultResourceTypes() {
// git resource
YBeanType gitSource = f.ybean("GitResourceSource");
addProp(gitSource, "uri", t_string).isRequired(true);
addProp(gitSource, "branch", t_string).isRequired(true);
@@ -232,15 +236,34 @@ public class PipelineYmlSchema implements YamlSchema {
addProp(gitSource, "commit_verification_key_ids", t_strings);
addProp(gitSource, "gpg_keyserver", t_string);
resourceTypes.def("git", gitSource);
YBeanType gitGetParams = f.ybean("GitGetParams");
addProp(gitGetParams, "depth", t_pos_integer);
addProp(gitGetParams, "submodules", f.yany("GitSubmodules").addHints("all", "none"));
addProp(gitGetParams, "disable_git_lfs", t_boolean);
YBeanType gitPutParams = f.ybean("GitPutParams");
resourceTypes.def("git", gitSource, gitGetParams, gitPutParams);
}
private String getResourceType(String resourceNameProp, ConcourseModel models, DynamicSchemaContext dc) {
String resourceName = getParentPropertyValue(resourceNameProp, models, dc);
if (resourceName!=null) {
return models.getResourceType(dc.getDocument(), resourceName);
}
return null;
}
private String getResourceTypeTag(ConcourseModel models, DynamicSchemaContext dc) {
return getParentPropertyValue("type", models, dc);
}
private String getParentPropertyValue(String propName, 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 NodeUtil.asScalar(path.dropLast().append(YamlPathSegment.valueAt(propName)).traverseToNode(root));
}
}
return null;

View File

@@ -25,19 +25,58 @@ import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanType
*/
public class ResourceTypeRegistry {
private Map<String, YType> sourceTypes = new HashMap<>();
private static class ResourceTypeInfo {
private final YBeanType source;
private final YBeanType in;
private final YBeanType out;
public ResourceTypeInfo(YBeanType source, YBeanType in, YBeanType out) {
super();
this.source = source;
this.in = in;
this.out = out;
}
public YBeanType getSource() {
return source;
}
public YBeanType getIn() {
return in;
}
public YBeanType getOut() {
return out;
}
}
private Map<String, ResourceTypeInfo> resourceTypes = 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 void def(String resourceTypeName, YBeanType source, YBeanType in, YBeanType out) {
Assert.isLegal(!resourceTypes.containsKey(resourceTypeName), "Multiple definitions for '"+resourceTypeName+"'");
resourceTypes.put(resourceTypeName, new ResourceTypeInfo(source, in, out));
}
public YType getSourceType(String typeTag) {
if (typeTag!=null) {
return sourceTypes.get(typeTag);
ResourceTypeInfo v = resourceTypes.get(typeTag);
if (v!=null) {
return v.getSource();
}
}
return null;
}
public YType getInParamsType(String typeTag) {
if (typeTag!=null) {
ResourceTypeInfo v = resourceTypes.get(typeTag);
if (v!=null) {
return v.getIn();
}
}
return null;
}

View File

@@ -778,6 +778,62 @@ public class PipelineYamlEditorTest {
editor.assertHoverContains("commit_verification_key_ids", "Array of GPG public key ids");
}
@Test public void gitResourceGetParamsCompletions() throws Exception {
String context =
"resources:\n" +
"- name: my-git\n" +
" type: git\n" +
"jobs:\n" +
"- name: do-stuff\n" +
" plan:\n" +
" - get: my-git\n" +
" params:\n" +
" <*>";
assertContextualCompletions(context,
"<*>"
, // ===>
"depth: <*>"
,
"disable_git_lfs: <*>"
,
"submodules:\n"+
" <*>"
);
assertContextualCompletions(context,
"disable_git_lfs: <*>"
, // ===>
"disable_git_lfs: false<*>",
"disable_git_lfs: true<*>"
);
assertContextualCompletions(context,
"submodules: <*>"
, // ===>
"submodules: all<*>",
"submodules: none<*>"
);
}
@Test public void gitResourceGetParamsReconcile() throws Exception {
Editor editor = harness.newEditor(
"resources:\n" +
"- name: my-git\n" +
" type: git\n" +
"jobs:\n" +
"- name: do-stuff\n" +
" plan:\n" +
" - get: my-git\n" +
" params:\n" +
" depth: -1\n" +
" disable_git_lfs: not-bool\n"
);
editor.assertProblems(
"-1|must be positive",
"not-bool|'boolean'"
);
}
@Test
public void contentAssistJobNames() throws Exception {
assertContextualCompletions(