From c7da2984782d3ef0b39a7de54cb871d7df168c20 Mon Sep 17 00:00:00 2001 From: Eric Bottard Date: Mon, 22 May 2017 14:56:53 +0200 Subject: [PATCH] Add support for flag parameters in descriptions Fixes #55 Change one of standard command samples, to see effect on it --- .../shell2/ParameterDescription.java | 19 ++++++- .../springframework/shell2/commands/Help.java | 56 ++++++++++++++----- .../shell2/samples/standard/Commands.java | 6 +- .../legacy/LegacyParameterResolver.java | 46 ++------------- .../shell2/legacy/LegacyCommands.java | 7 ++- .../legacy/LegacyParameterResolverTest.java | 6 +- 6 files changed, 78 insertions(+), 62 deletions(-) diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/ParameterDescription.java b/spring-shell2-core/src/main/java/org/springframework/shell2/ParameterDescription.java index e79113d4..60851d5b 100644 --- a/spring-shell2-core/src/main/java/org/springframework/shell2/ParameterDescription.java +++ b/spring-shell2-core/src/main/java/org/springframework/shell2/ParameterDescription.java @@ -47,10 +47,17 @@ public class ParameterDescription { private String formal; /** - * A string representation of the default value for the parameter, if any. + * A string representation of the default value (if the option is left out entirely) for the parameter, if any. */ private Optional defaultValue = Optional.empty(); + /** + * A string representation of the default value for this parameter, if it can be used as a mere flag (e.g. + * {@literal --force} without a value, being an equivalent to {@literal --force true}). + *

{@literal Optional.empty()} (the default) means that this parameter cannot be used as a flag.

+ */ + private Optional defaultValueWhenFlag = Optional.empty(); + /** * The list of 'keys' that can be used to specify this parameter, if any. */ @@ -99,6 +106,11 @@ public class ParameterDescription { return this; } + public ParameterDescription whenFlag(String defaultValue) { + this.defaultValueWhenFlag = Optional.of(defaultValue); + return this; + } + public ParameterDescription keys(List keys) { this.keys = keys; return this; @@ -109,6 +121,7 @@ public class ParameterDescription { return this; } + public String type() { return type; } @@ -121,6 +134,10 @@ public class ParameterDescription { return help; } + public Optional defaultValueWhenFlag() { + return defaultValueWhenFlag; + } + public ParameterDescription formal(String formal) { this.formal = formal; return this; diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/commands/Help.java b/spring-shell2-core/src/main/java/org/springframework/shell2/commands/Help.java index 341d9275..6a670cbe 100644 --- a/spring-shell2-core/src/main/java/org/springframework/shell2/commands/Help.java +++ b/spring-shell2-core/src/main/java/org/springframework/shell2/commands/Help.java @@ -105,25 +105,32 @@ public class Help { for (ParameterDescription description : parameterDescriptions) { if (description.defaultValue().isPresent()) { - result.append("["); + result.append("["); // Whole parameter is optional, as there is a default value (1) } - if(!description.keys().isEmpty()) { + List keys = description.keys(); + if(!keys.isEmpty()) { if (!description.mandatoryKey()) { - result.append("["); + result.append("["); // Specifying a key is optional (ie positional params). (2) } - result.append(description.keys().iterator().next(), AttributedStyle.BOLD); + result.append(first(keys), AttributedStyle.BOLD); if (!description.mandatoryKey()) { - result.append("]"); + result.append("]"); // (close 2) } if (!description.formal().isEmpty()) { result.append(" "); } } - appendUnderlinedFormal(result, description); - if (description.defaultValue().isPresent()) { - result.append("]"); + if (description.defaultValueWhenFlag().isPresent()) { + result.append("["); // Parameter can be used as a toggle flag (3) } - result.append(" "); + appendUnderlinedFormal(result, description); + if (description.defaultValueWhenFlag().isPresent()) { + result.append("]"); // (close 3) + } + if (description.defaultValue().isPresent()) { + result.append("]"); // (close 1) + } + result.append(" "); // two spaces between each param for better legibility } result.append("\n\n"); @@ -137,7 +144,9 @@ public class Help { if (!description.keys().isEmpty()) { result.append(" "); } + description.defaultValueWhenFlag().ifPresent(f -> result.append('[')); appendUnderlinedFormal(result, description); + description.defaultValueWhenFlag().ifPresent(f -> result.append(']')); result.append("\n\t"); } else if (description.keys().size() > 1) { @@ -145,12 +154,27 @@ public class Help { } result.append("\t"); result.append(description.help()); + // Optional parameter if (description.defaultValue().isPresent()) { result .append(" [Optional, default = ", AttributedStyle.BOLD) - .append(description.defaultValue().get(), AttributedStyle.BOLD.italic()) - .append("]", AttributedStyle.BOLD); - } else { + .append(description.defaultValue().get(), AttributedStyle.BOLD.italic()); + description.defaultValueWhenFlag().ifPresent( + s -> result.append(", or ", AttributedStyle.BOLD) + .append(s, AttributedStyle.BOLD.italic()) + .append(" if used as a flag", AttributedStyle.BOLD) + ); + + result.append("]", AttributedStyle.BOLD); + } // Mandatory parameter, but with a default when used as a flag + else if (description.defaultValueWhenFlag().isPresent()) { + result + .append(" [Mandatory, default = ", AttributedStyle.BOLD) + .append(description.defaultValueWhenFlag().get(), AttributedStyle.BOLD.italic()) + .append(" when used as a flag]", AttributedStyle.BOLD) + ; + } // true mandatory parameter + else { result.append(" [Mandatory]", AttributedStyle.BOLD); } result.append("\n\n"); @@ -159,7 +183,7 @@ public class Help { // ALSO KNOWN AS Set aliases = shell.listCommands().entrySet().stream() .filter(e -> e.getValue().equals(methodTarget)) - .map(e -> e.getKey()) + .map(Map.Entry::getKey) .filter(c -> !command.equals(c)) .collect(toCollection(TreeSet::new)); @@ -174,6 +198,10 @@ public class Help { return result; } + private String first(List keys) { + return keys.iterator().next(); + } + private CharSequence listCommands() { Map> groupedByMethodTarget = shell.listCommands().entrySet().stream() .collect(Collectors.groupingBy(e -> e.getValue().getHelp(), // Use help() as the grouping key @@ -195,7 +223,7 @@ public class Help { } private Comparator>> sortByFirstElement() { - return (e1, e2) -> e1.getValue().iterator().next().compareTo(e2.getValue().iterator().next()); + return Comparator.comparing(e -> e.getValue().iterator().next()); } private void appendUnderlinedFormal(AttributedStringBuilder result, ParameterDescription description) { diff --git a/spring-shell2-samples/src/main/java/org/springframework/shell2/samples/standard/Commands.java b/spring-shell2-samples/src/main/java/org/springframework/shell2/samples/standard/Commands.java index ee596fd3..45442ba4 100644 --- a/spring-shell2-samples/src/main/java/org/springframework/shell2/samples/standard/Commands.java +++ b/spring-shell2-samples/src/main/java/org/springframework/shell2/samples/standard/Commands.java @@ -36,9 +36,9 @@ public class Commands { } - @ShellMethod(help = "it's better") - public void foobar() { - + @ShellMethod(help = "Shows support for boolean parameters, with arity=0") + public void shutdown(@ShellOption(arity = 0) boolean force) { + System.out.println("You passed " + force); } @ShellMethod(help = "something else") diff --git a/spring-shell2-shell1-adapter/src/main/java/org/springframework/shell2/legacy/LegacyParameterResolver.java b/spring-shell2-shell1-adapter/src/main/java/org/springframework/shell2/legacy/LegacyParameterResolver.java index dde1977d..17a1ac6e 100644 --- a/spring-shell2-shell1-adapter/src/main/java/org/springframework/shell2/legacy/LegacyParameterResolver.java +++ b/spring-shell2-shell1-adapter/src/main/java/org/springframework/shell2/legacy/LegacyParameterResolver.java @@ -100,51 +100,17 @@ public class LegacyParameterResolver implements ParameterResolver { .filter(key -> !key.isEmpty()) .map(key -> CLI_PREFIX + key) .collect(Collectors.toList())); - Optional defaultValue = defaultValueFor(option, result.keys()); - if (defaultValue.isPresent()) { - result.defaultValue(defaultValue.get()); + if (!option.mandatory()) { + result.defaultValue(CLI_OPTION_NULL.equals(option.unspecifiedDefaultValue()) ? "null" : option.unspecifiedDefaultValue()); + } + if(!CLI_OPTION_NULL.equals(option.specifiedDefaultValue())) { + result.whenFlag(option.specifiedDefaultValue()); } boolean containsEmptyKey = keys.contains(""); result.mandatoryKey(!containsEmptyKey); return result; } - private Optional defaultValueFor(CliOption option, List keys) { - // CliOption annotations have two default values, one for when the key is specified without a value, - // and one when the key isn't specified (e.g. "command --key" vs "command") - - final boolean unspecifiedDefaultDeclared = !CLI_OPTION_NULL.equals(option.unspecifiedDefaultValue()); - final boolean specifiedDefaultDeclared = !CLI_OPTION_NULL.equals(option.specifiedDefaultValue()); - - if (!unspecifiedDefaultDeclared && !specifiedDefaultDeclared) { - if (option.mandatory()) { - return Optional.empty(); - } else { - // according to CliOption, is no default is declared, then null will be presented to non-primitive - // arguments - return Optional.of("null"); - } - } - - final StringBuilder defaultValue = new StringBuilder(); - - if (unspecifiedDefaultDeclared) { - defaultValue.append(option.unspecifiedDefaultValue()); - } - - if (specifiedDefaultDeclared) { - if (unspecifiedDefaultDeclared) { - defaultValue.append(", or "); - } - - defaultValue.append(option.specifiedDefaultValue()); - defaultValue.append(" if used as "); - defaultValue.append(keys.stream().collect(Collectors.joining(" or "))); - } - - return Optional.of(defaultValue.toString()); - } - @Override public List complete(MethodParameter parameter, CompletionContext context) { return null; @@ -175,7 +141,7 @@ public class LegacyParameterResolver implements ParameterResolver { for (String key : option.key()) { if (values.containsKey(key)) { String value = values.get(key); - if (value == null && !"__NULL__".equals(option.specifiedDefaultValue())) { + if (value == null && !CLI_OPTION_NULL.equals(option.specifiedDefaultValue())) { value = option.specifiedDefaultValue(); } Class parameterType = methodParameter.getParameterType(); diff --git a/spring-shell2-shell1-adapter/src/test/java/org/springframework/shell2/legacy/LegacyCommands.java b/spring-shell2-shell1-adapter/src/test/java/org/springframework/shell2/legacy/LegacyCommands.java index 46362daf..66ede284 100644 --- a/spring-shell2-shell1-adapter/src/test/java/org/springframework/shell2/legacy/LegacyCommands.java +++ b/spring-shell2-shell1-adapter/src/test/java/org/springframework/shell2/legacy/LegacyCommands.java @@ -56,7 +56,10 @@ public class LegacyCommands implements CommandMarker { } @CliCommand(value = "sum", help = "adds two numbers") - public int sum(@CliOption(key = "v1", unspecifiedDefaultValue = "38") int a, @CliOption(key = "v2", specifiedDefaultValue = "42") int b) { + public int sum( + @CliOption(key = "v1", unspecifiedDefaultValue = "38") int a, + @CliOption(key = "v2", specifiedDefaultValue = "42") int b + ) { return a + b; } @@ -68,7 +71,7 @@ public class LegacyCommands implements CommandMarker { @CliCommand(value = "someMethod", help = "Method used for testing purposes") public String someMethod( @CliOption(key = "key", mandatory = false, help = "The optional parameter") String parameter, - @CliOption(key = "option", help = "an option", specifiedDefaultValue = "true", unspecifiedDefaultValue = "false", mandatory = true) boolean option) { + @CliOption(key = "option", help = "an option", specifiedDefaultValue = "true", unspecifiedDefaultValue = "false") boolean option) { return parameter + ", " + option; } diff --git a/spring-shell2-shell1-adapter/src/test/java/org/springframework/shell2/legacy/LegacyParameterResolverTest.java b/spring-shell2-shell1-adapter/src/test/java/org/springframework/shell2/legacy/LegacyParameterResolverTest.java index c90fefba..50a83ea3 100644 --- a/spring-shell2-shell1-adapter/src/test/java/org/springframework/shell2/legacy/LegacyParameterResolverTest.java +++ b/spring-shell2-shell1-adapter/src/test/java/org/springframework/shell2/legacy/LegacyParameterResolverTest.java @@ -179,7 +179,8 @@ public class LegacyParameterResolverTest { assertThat(description.keys()).containsExactly("--option"); assertThat(description.formal()).isEqualTo(boolean.class.getName()); - assertThat(description.defaultValue().get()).isEqualTo("false, or true if used as --option"); + assertThat(description.defaultValue().get()).isEqualTo("false"); + assertThat(description.defaultValueWhenFlag().get()).isEqualTo("true"); assertThat(description.mandatoryKey()).isTrue(); String expectedHelp = methodParameter.getParameterAnnotation(CliOption.class).help(); @@ -194,7 +195,8 @@ public class LegacyParameterResolverTest { assertThat(description.keys()).containsExactly("--v2"); assertThat(description.formal()).isEqualTo(int.class.getName()); - assertThat(description.defaultValue().get()).isEqualTo("42 if used as --v2"); + assertThat(description.defaultValue().get()).isEqualTo("null"); + assertThat(description.defaultValueWhenFlag().get()).isEqualTo("42"); assertThat(description.mandatoryKey()).isTrue(); String expectedHelp = methodParameter.getParameterAnnotation(CliOption.class).help();