Add support for flag parameters in descriptions
Fixes #55 Change one of standard command samples, to see effect on it
This commit is contained in:
@@ -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<String> defaultValue = Optional.empty();
|
||||
|
||||
/**
|
||||
* A string representation of the default value for this parameter, if it can be used as a mere flag (<em>e.g.</em>
|
||||
* {@literal --force} without a value, being an equivalent to {@literal --force true}).
|
||||
* <p>{@literal Optional.empty()} (the default) means that this parameter cannot be used as a flag.</p>
|
||||
*/
|
||||
private Optional<String> 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<String> 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<String> defaultValueWhenFlag() {
|
||||
return defaultValueWhenFlag;
|
||||
}
|
||||
|
||||
public ParameterDescription formal(String formal) {
|
||||
this.formal = formal;
|
||||
return this;
|
||||
|
||||
@@ -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<String> 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<String> 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<String> keys) {
|
||||
return keys.iterator().next();
|
||||
}
|
||||
|
||||
private CharSequence listCommands() {
|
||||
Map<String, Set<String>> 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<Map.Entry<String, Set<String>>> 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) {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -100,51 +100,17 @@ public class LegacyParameterResolver implements ParameterResolver {
|
||||
.filter(key -> !key.isEmpty())
|
||||
.map(key -> CLI_PREFIX + key)
|
||||
.collect(Collectors.toList()));
|
||||
Optional<String> 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<String> defaultValueFor(CliOption option, List<String> 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<CompletionProposal> 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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user