1. Options
Command line arguments can be separated into options and positional parameters. Following sections describes features how options are defined and used.
1.1. Definition
Options can be defined within a target method as annotations in a method arguments
or with programmatically with CommandRegistration.
Having a target method with argument is automatically registered with a matching argument name.
public String example(String arg1) {
return "Hello " + arg1;
}
@ShellOption annotation can be used to define an option name if you
don’t want it to be same as argument name.
public String example(@ShellOption(value = { "--argx" }) String arg1) {
return "Hello " + arg1;
}
If option name is defined without prefix, either - or --, it is discovered
from ShellMethod#prefix.
public String example(@ShellOption(value = { "argx" }) String arg1) {
return "Hello " + arg1;
}
Programmatic way with CommandRegistration is to use method adding a long name.
CommandRegistration.builder()
.withOption()
.longNames("arg1")
.and()
.build();
1.2. Short Format
Short style POSIX option in most is just a synonym to long format but
adds additional feature to combine those options together. Having short
options a, b, c can be used as -abc.
Programmatically short option is defined by using short name function.
CommandRegistration.builder()
.withOption()
.shortNames('a')
.and()
.withOption()
.shortNames('b')
.and()
.withOption()
.shortNames('c')
.and()
.build();
Short option with combined format is powerful if type is defined as a flag
which means type is a boolean. That way you can define a presense of a flags
as -abc, -abc true or -abc false.
CommandRegistration.builder()
.withOption()
.shortNames('a')
.type(boolean.class)
.and()
.withOption()
.shortNames('b')
.type(boolean.class)
.and()
.withOption()
.shortNames('c')
.type(boolean.class)
.and()
.build();
With annotation model you can define short argument directly.
public String example(
@ShellOption(value = { "-a" }) String arg1,
@ShellOption(value = { "-b" }) String arg2,
@ShellOption(value = { "-c" }) String arg3
) {
return "Hello " + arg1;
}
1.3. Arity
Sometimes, you want to have more fine control of how many parameters with an option are processed when parsing operations happen. Arity is defined as min and max values, where min must be a positive integer and max has to be more or equal to min.
CommandRegistration.builder()
.withOption()
.longNames("arg1")
.arity(0, 1)
.and()
.build();
Arity can also be defined as an OptionArity enum, which are shortcuts
within the following table:
CommandRegistration.builder()
.withOption()
.longNames("arg1")
.arity(OptionArity.EXACTLY_ONE)
.and()
.build();
| Value | min/max |
|---|---|
ZERO |
0 / 0 |
ZERO_OR_ONE |
0 / 1 |
EXACTLY_ONE |
1 / 1 |
ZERO_OR_MORE |
0 / Integer MAX |
ONE_OR_MORE |
1 / Integer MAX |
The annotation model supports defining only the max value of an arity.
public String example(@ShellOption(arity = 1) String arg1) {
return "Hello " + arg1;
}
1.4. Positional
Positional information is mostly related to a command target method:
CommandRegistration.builder()
.withOption()
.longNames("arg1")
.position(0)
.and()
.build();
1.5. Optional Value
An option is either required or not and, generally speaking, how it behaves depends on a command target:
CommandRegistration.builder()
.withOption()
.longNames("arg1")
.required()
.and()
.build();
In the annotation model, there is no direct way to define if argument is
optional. Instead, it is instructed to be NULL:
public String example(
@ShellOption(defaultValue = ShellOption.NULL) String arg1
) {
return "Hello " + arg1;
}
1.6. Default Value
Having a default value for an option is somewhat related to Optional Value, as there are cases where you may want to know if the user defined an option and change behavior based on a default value:
CommandRegistration.builder()
.withOption()
.longNames("arg1")
.defaultValue("defaultValue")
.and()
.build();
The annotation model also supports defining default values:
public String example(
@ShellOption(defaultValue = "defaultValue") String arg1
) {
return "Hello " + arg1;
}
1.7. Validation
Spring Shell integrates with the Bean Validation API to support automatic and self-documenting constraints on command parameters.
Annotations found on command parameters and annotations at the method level are honored and trigger validation prior to the command executing. Consider the following command:
@ShellMethod("Change password.")
public String changePassword(@Size(min = 8, max = 40) String password) {
return "Password successfully set to " + password;
}
From the preceding example, you get the following behavior for free:
shell:>change-password hello The following constraints were not met: --password string : size must be between 8 and 40 (You passed 'hello')
1.8. Label
Option Label has no functional behaviour within a shell itself other than
what a default help command outputs. Within a command documentation
a type of an option is documented but this is not always super useful. Thus
you may want to give better descriptive word for an option.
CommandRegistration.builder()
.withOption()
.longNames("arg1")
.and()
.withOption()
.longNames("arg2")
.label("MYLABEL")
.and()
.build();
Defining label is then shown in help.
my-shell:>help mycommand
NAME
mycommand -
SYNOPSIS
mycommand --arg1 String --arg2 MYLABEL
OPTIONS
--arg1 String
[Optional]
--arg2 MYLABEL
[Optional]
1.9. Types
This section talks about how particular data type is used as an option value.
1.9.1. String
String is a most simplest type as there’s no conversion involved as what’s
coming in from a user is always a string.
String example(@ShellOption(value = "arg1") String arg1) {
return "Hello " + arg1;
}
While it’s not strictly required to define type as a String it’s always
adviced to do so.
CommandRegistration.builder()
.command("example")
.withOption()
.longNames("arg1")
.type(String.class)
.required()
.and()
.withTarget()
.function(ctx -> {
String arg1 = ctx.getOptionValue("arg1");
return "Hello " + arg1;
})
.and()
.build();
1.9.2. Boolean
Using boolean types is a bit more involved as there are boolean and
Boolean where latter can be null. Boolean types are usually used as
flags meaning argument value may not be needed.
String example(
@ShellOption() boolean arg1,
@ShellOption(defaultValue = "true") boolean arg2,
@ShellOption(defaultValue = "false") boolean arg3,
@ShellOption() Boolean arg4,
@ShellOption(defaultValue = "true") Boolean arg5,
@ShellOption(defaultValue = "false") Boolean arg6
) {
return String.format("arg1=%s arg2=%s arg3=%s arg4=%s arg5=%s arg6=%s",
arg1, arg2, arg3, arg4, arg5, arg6);
}
shell:>example
arg1=false arg2=true arg3=false arg4=false arg5=true arg6=false
shell:>example --arg4
arg1=false arg2=true arg3=false arg4=true arg5=true arg6=false
shell:>example --arg4 false
arg1=false arg2=true arg3=false arg4=false arg5=true arg6=false
CommandRegistration.builder()
.command("example")
.withOption()
.longNames("arg1").type(boolean.class).and()
.withOption()
.longNames("arg2").type(boolean.class).defaultValue("true").and()
.withOption()
.longNames("arg3").type(boolean.class).defaultValue("false").and()
.withOption()
.longNames("arg4").type(Boolean.class).and()
.withOption()
.longNames("arg5").type(Boolean.class).defaultValue("true").and()
.withOption()
.longNames("arg6").type(Boolean.class).defaultValue("false").and()
.withTarget()
.function(ctx -> {
boolean arg1 = ctx.hasMappedOption("arg1")
? ctx.getOptionValue("arg1")
: false;
boolean arg2 = ctx.getOptionValue("arg2");
boolean arg3 = ctx.getOptionValue("arg3");
Boolean arg4 = ctx.getOptionValue("arg4");
Boolean arg5 = ctx.getOptionValue("arg5");
Boolean arg6 = ctx.getOptionValue("arg6");
return String.format("Hello arg1=%s arg2=%s arg3=%s arg4=%s arg5=%s arg6=%s",
arg1, arg2, arg3, arg4, arg5, arg6);
})
.and()
.build();
shell:>example
arg1=false arg2=true arg3=false arg4=null arg5=true arg6=false
shell:>example --arg4
arg1=false arg2=true arg3=false arg4=true arg5=true arg6=false
shell:>example --arg4 false
arg1=false arg2=true arg3=false arg4=false arg5=true arg6=false
1.9.3. Number
Numbers are converted as is.
String example(@ShellOption(value = "arg1") int arg1) {
return "Hello " + arg1;
}
CommandRegistration.builder()
.command("example")
.withOption()
.longNames("arg1")
.type(int.class)
.required()
.and()
.withTarget()
.function(ctx -> {
boolean arg1 = ctx.getOptionValue("arg1");
return "Hello " + arg1;
})
.and()
.build();
1.9.4. Enum
Conversion to enums is possible if given value is exactly matching enum itself. Currently you can convert assuming case insensitivity.
enum OptionTypeEnum {
ONE,TWO,THREE
}
String example(@ShellOption(value = "arg1") OptionTypeEnum arg1) {
return "Hello " + arg1;
}
CommandRegistration.builder()
.command("example")
.withOption()
.longNames("arg1")
.type(OptionTypeEnum.class)
.required()
.and()
.withTarget()
.function(ctx -> {
OptionTypeEnum arg1 = ctx.getOptionValue("arg1");
return "Hello " + arg1;
})
.and()
.build();