GitResource 'get' params support
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user