Skip to content

Commit

Permalink
Add --show to select command
Browse files Browse the repository at this point in the history
--show is used to include additional data in select command output,
including the shape type, file where the shape is defined, and/or
variables captured when the shape was matched.

For example:

```
smithy select --selector 'structure $neighbors(>)' --show-traits documentation --show type,file,vars --aut s3.json
```

Outputs:

```
[
    {
        "shape": "com.amazonaws.s3#CSVInput",
        "type": "structure",
        "file": "/path/to/s3.json:16224:34",
        "vars": {
            "neighbors": [
                "com.amazonaws.s3#CSVInput$AllowQuotedRecordDelimiter",
                "com.amazonaws.s3#CSVInput$Comments",
                "com.amazonaws.s3#CSVInput$FieldDelimiter",
                "com.amazonaws.s3#CSVInput$FileHeaderInfo",
                "com.amazonaws.s3#CSVInput$QuoteCharacter",
                "com.amazonaws.s3#CSVInput$QuoteEscapeCharacter",
                "com.amazonaws.s3#CSVInput$RecordDelimiter"
            ]
        },
        "traits": {
            "smithy.api#documentation": "<p>Describes how an uncompressed comma-separated values (CSV)-formatted input object is formatted.</p>"
        }
    }
]
```

This now deprecates the `--show-vars` option, as `--show vars` should
be used instead.
  • Loading branch information
mtdowling committed Aug 25, 2023
1 parent 5927fea commit afc523d
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public void doesNotShowWarnings() {

@Test
public void selectsVariables() {
List<String> args = Arrays.asList("select", "--show-vars", "--selector", "list $list(*) > member > string");
List<String> args = Arrays.asList("select", "--show", "vars", "--selector", "list $list(*) > member > string");
IntegUtils.run("simple-config-sources", args, result -> {
assertThat(result.getExitCode(), equalTo(0));
String content = result.getOutput().trim();
Expand Down Expand Up @@ -79,4 +79,80 @@ public void showTraitsCannotHaveEmptyValues() {
assertThat(result.getExitCode(), not(equalTo(0)));
});
}

@Test
public void showCannotBeEmpty() {
List<String> args = Arrays.asList("select", "--show", "", "--selector", "string");
IntegUtils.run("simple-config-sources", args, result -> {
assertThat(result.getExitCode(), equalTo(1));
});
}

@Test
public void showCannotContainInvalidValues() {
List<String> args = Arrays.asList("select", "--show", "foo", "--selector", "string");
IntegUtils.run("simple-config-sources", args, result -> {
assertThat(result.getExitCode(), equalTo(1));
});
}

@Test
public void showCannotContainInvalidValuesInCsv() {
List<String> args = Arrays.asList("select", "--show", "vars,foo", "--selector", "string");
IntegUtils.run("simple-config-sources", args, result -> {
assertThat(result.getExitCode(), equalTo(1));
});
}

@Test
public void includesType() {
List<String> args = Arrays.asList("select", "--show", "type", "--selector", "string");
IntegUtils.run("simple-config-sources", args, result -> {
assertThat(result.getExitCode(), equalTo(0));
String content = result.getOutput().trim();
// Ensure it's valid JSON
Node.parse(content);
assertThat(content, containsString("\"type\": \"string\""));
});
}

@Test
public void includesFile() {
List<String> args = Arrays.asList("select", "--show", "file", "--selector", "string");
IntegUtils.run("simple-config-sources", args, result -> {
assertThat(result.getExitCode(), equalTo(0));
String content = result.getOutput().trim();
// Ensure it's valid JSON
Node.parse(content);
assertThat(content, containsString("\"file\": "));
});
}

@Test
public void includesFileAndType() {
List<String> args = Arrays.asList("select", "--show", "file, type", "--selector", "string");
IntegUtils.run("simple-config-sources", args, result -> {
assertThat(result.getExitCode(), equalTo(0));
String content = result.getOutput().trim();
// Ensure it's valid JSON
Node.parse(content);
assertThat(content, containsString("\"type\": \"string\""));
assertThat(content, containsString("\"file\": "));
});
}

@Test
public void includesFileAndTypeAndVars() {
List<String> args = Arrays.asList("select", "--show", "file, type,vars", "--selector", "string $hi(*)");
IntegUtils.run("simple-config-sources", args, result -> {
assertThat(result.getExitCode(), equalTo(0));
String content = result.getOutput().trim();
// Ensure it's valid JSON
Node.parse(content);
assertThat(content, containsString("\"type\": \"string\""));
assertThat(content, containsString("\"file\": "));
assertThat(content, containsString("\"vars\": "));
assertThat(content, containsString("\"hi\": "));
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand Down Expand Up @@ -86,30 +87,88 @@ public int execute(Arguments arguments, Env env) {
}

private String getDocumentation(ColorFormatter colors) {
return "By default, each matching shape ID is printed to stdout on a new line. Pass --show-vars to print out "
+ "a JSON array that contains a 'shape' and 'vars' property, where the 'vars' property is a map of "
+ "each variable that was captured when the shape was matched.";
return "By default, each matching shape ID is printed to stdout on a new line. Pass --show or --show-traits "
+ "to get JSON array output.";
}

private static final class Options implements ArgumentReceiver {
private boolean showVars;
private Selector selector;
private final List<ShapeId> showTraits = new ArrayList<>();
private final Set<Show> show = new TreeSet<>();

private enum Show {
TYPE("type") {
@Override
protected void inject(Selector.ShapeMatch match, ObjectNode.Builder builder) {
builder.withMember("type", match.getShape().getType().toString());
}
},

FILE("file") {
@Override
protected void inject(Selector.ShapeMatch match, ObjectNode.Builder builder) {
SourceLocation source = match.getShape().getSourceLocation();
// Only shapes with a real source location add a file.
if (!source.getFilename().equals(SourceLocation.NONE.getFilename())) {
builder.withMember("file", source.getFilename()
+ ':' + source.getLine()
+ ':' + source.getColumn());
}
}
},

VARS("vars") {
@Override
protected void inject(Selector.ShapeMatch match, ObjectNode.Builder builder) {
if (!match.isEmpty()) {
ObjectNode.Builder varBuilder = Node.objectNodeBuilder();
for (Map.Entry<String, Set<Shape>> varEntry : match.entrySet()) {
varBuilder.withMember(
varEntry.getKey(),
sortShapeIds(varEntry.getValue()).map(Node::from).collect(ArrayNode.collect())
);
}
ObjectNode collectedVars = varBuilder.build();
builder.withMember("vars", collectedVars);
}
}
};

private final String value;

Show(String value) {
this.value = value;
}

protected abstract void inject(Selector.ShapeMatch match, ObjectNode.Builder builder);

private static Show from(String value) {
for (Show variant : values()) {
if (variant.value.equals(value)) {
return variant;
}
}
throw new CliError("Invalid value given to --show: `" + value + "`");
}
}

@Override
public boolean testOption(String name) {
switch (name) {
case "--vars":
LOGGER.warning("--vars is deprecated. Use --show-vars instead.");
// fall-through
case "--show-vars":
showVars = true;
return true;
return deprecatedVars(name);
default:
return false;
}
}

private boolean deprecatedVars(String name) {
LOGGER.warning(name + " is deprecated. Use `--show vars` instead.");
show.add(Show.VARS);
return true;
}

@Override
public Consumer<String> testParameter(String name) {
switch (name) {
Expand All @@ -128,6 +187,13 @@ public Consumer<String> testParameter(String name) {
throw new CliError("--show-traits must contain traits");
}
};
case "--show":
return value -> {
String[] parts = value.split("\\s*,\\s*");
for (String part : parts) {
show.add(Show.from(part));
}
};
default:
return null;
}
Expand All @@ -137,16 +203,17 @@ public Consumer<String> testParameter(String name) {
public void registerHelp(HelpPrinter printer) {
printer.param("--selector", null, "SELECTOR",
"The Smithy selector to execute. Reads from STDIN when not provided.");
printer.param("--show", null, "DATA",
"Displays additional top-level members in each match and forces JSON output. This parameter "
+ "accepts a comma-separated list of values, including 'type', 'file', and 'vars'. 'type' "
+ "adds a string member containing the shape type of each match. 'file' adds a string "
+ "member containing the absolute path to where the shape is defined followed by the line "
+ "number then column (e.g., '/path/example.smithy:10:1'). 'vars' adds an object containing "
+ "the variables that were captured when a shape was matched.");
printer.param("--show-traits", null, "TRAITS",
"Returns JSON output that includes the values of specific traits applied to matched shapes, "
+ "stored in a 'traits' property. Provide a comma-separated list of trait shape IDs. "
+ "Prelude traits may omit a namespace (e.g., 'required' or 'smithy.api#required').");
printer.option("--show-vars", null, "Returns JSON output that includes the variables that were captured "
+ "when a shape was matched, stored in a 'vars' property.");
}

public boolean showVars() {
return showVars;
}

public Selector selector() {
Expand Down Expand Up @@ -199,8 +266,8 @@ void dumpResults(Selector selector, Model model, Options options, CliPrinter std
ObjectNode.Builder builder = Node.objectNodeBuilder()
.withMember("shape", Node.from(match.getShape().getId().toString()));

if (!match.isEmpty()) {
builder.withMember("vars", collectVars(match));
for (Options.Show showData : options.show) {
showData.inject(match, builder);
}

if (!options.showTraits.isEmpty()) {
Expand All @@ -223,21 +290,11 @@ void dumpResults(Selector selector, Model model, Options options, CliPrinter std
abstract void dumpResults(Selector selector, Model model, Options options, CliPrinter stdout);

static OutputFormat determineFormat(Options options) {
// If --show-vars isn't provided and --show-traits is empty, then use the SHAPE_ID_LINES output.
return !options.showVars() && options.showTraits.isEmpty() ? SHAPE_ID_LINES : JSON;
}

private static Stream<String> sortShapeIds(Collection<Shape> shapes) {
return shapes.stream().map(Shape::getId).map(ShapeId::toString).sorted();
return options.showTraits.isEmpty() && options.show.isEmpty() ? SHAPE_ID_LINES : JSON;
}
}

private static ObjectNode collectVars(Map<String, Set<Shape>> vars) {
ObjectNode.Builder varBuilder = Node.objectNodeBuilder();
for (Map.Entry<String, Set<Shape>> varEntry : vars.entrySet()) {
ArrayNode value = sortShapeIds(varEntry.getValue()).map(Node::from).collect(ArrayNode.collect());
varBuilder.withMember(varEntry.getKey(), value);
}
return varBuilder.build();
}
private static Stream<String> sortShapeIds(Collection<Shape> shapes) {
return shapes.stream().map(Shape::getId).map(ShapeId::toString).sorted();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public void printsSuccessfulMatchesToStdout() throws Exception {
public void printsJsonVarsToStdout() throws Exception {
String model = Paths.get(getClass().getResource("valid-model.smithy").toURI()).toString();
CliUtils.Result result = CliUtils.runSmithy("select", "--selector", "string $referenceMe(<)",
"--show-vars", model);
"--show", "vars", model);

assertThat(result.code(), equalTo(0));
validateSelectorOutput(result.stdout());
Expand All @@ -81,7 +81,7 @@ public void readsSelectorFromStdinToo() throws Exception {
try {
// Send the selector through input stream.
System.setIn(new ByteArrayInputStream("string $referenceMe(<)".getBytes()));
CliUtils.Result result = CliUtils.runSmithy("select", "--show-vars", model);
CliUtils.Result result = CliUtils.runSmithy("select", "--show", "vars", model);

assertThat(result.code(), equalTo(0));
validateSelectorOutput(result.stdout());
Expand Down

0 comments on commit afc523d

Please sign in to comment.