5. Options

Command line arguments can be separated into options and positional parameters. Following sections describes features how options are defined and used.spring-doc.cn

5.1. Definition

Options can be defined within a target method as annotations in a method arguments or with programmatically with CommandRegistration.spring-doc.cn

Having a target method with argument is automatically registered with a matching argument name.spring-doc.cn

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.spring-doc.cn

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.spring-doc.cn

public String example(@ShellOption(value = { "argx" }) String arg1) {
	return "Hello " + arg1;
}

Programmatic way with CommandRegistration is to use method adding a long name.spring-doc.cn

CommandRegistration.builder()
	.withOption()
		.longNames("arg1")
		.and()
	.build();

5.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.spring-doc.cn

Programmatically short option is defined by using short name function.spring-doc.cn

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.spring-doc.cn

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.spring-doc.cn

public String example(
	@ShellOption(value = { "-a" }) String arg1,
	@ShellOption(value = { "-b" }) String arg2,
	@ShellOption(value = { "-c" }) String arg3
) {
	return "Hello " + arg1;
}

5.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 zero or a positive integer and max has to be more or equal to min.spring-doc.cn

CommandRegistration.builder()
	.withOption()
		.longNames("arg1")
		.arity(0, 1)
		.and()
	.build();

Arity can also be defined as an OptionArity enum, which are shortcuts shown in below table OptionArity.spring-doc.cn

CommandRegistration.builder()
	.withOption()
		.longNames("arg1")
		.arity(OptionArity.EXACTLY_ONE)
		.and()
	.build();
Table 6. OptionArity
Value min/max

ZEROspring-doc.cn

0 / 0spring-doc.cn

ZERO_OR_ONEspring-doc.cn

0 / 1spring-doc.cn

EXACTLY_ONEspring-doc.cn

1 / 1spring-doc.cn

ZERO_OR_MOREspring-doc.cn

0 / Integer MAXspring-doc.cn

ONE_OR_MOREspring-doc.cn

1 / Integer MAXspring-doc.cn

The annotation model supports defining only the max value of an arity.spring-doc.cn

public String example(@ShellOption(arity = 1) String arg1) {
	return "Hello " + arg1;
}

One of a use cases to manually define arity is to impose restrictions how many parameters option accepts.spring-doc.cn

CommandRegistration.builder()
	.command("arity-errors")
	.withOption()
		.longNames("arg1")
		.type(String[].class)
		.required()
		.arity(1, 2)
		.and()
	.withTarget()
		.function(ctx -> {
			String[] arg1 = ctx.getOptionValue("arg1");
			return "Hello " + Arrays.asList(arg1);
		})
		.and()
	.build();

In above example we have option arg1 and it’s defined as type String[]. Arity defines that it needs at least 1 parameter and not more that 2. As seen in below spesific exceptions TooManyArgumentsOptionException and NotEnoughArgumentsOptionException are thrown to indicate arity mismatch.spring-doc.cn

shell:>e2e reg arity-errors --arg1
Not enough arguments --arg1 requires at least 1.

shell:>e2e reg arity-errors --arg1 one
Hello [one]

shell:>e2e reg arity-errors --arg1 one two
Hello [one, two]

shell:>e2e reg arity-errors --arg1 one two three
Too many arguments --arg1 requires at most 2.

5.4. Positional

Positional information is mostly related to a command target method:spring-doc.cn

CommandRegistration.builder()
	.withOption()
		.longNames("arg1")
		.position(0)
		.and()
	.build();
Be careful with positional parameters as it may soon become confusing which options those are mapped to.

Usually arguments are mapped to an option when those are defined in a command line whether it’s a long or short option. Generally speaking there are options, option arguments and arguments where latter are the ones which are not mapped to any spesific option.spring-doc.cn

Unrecognised arguments can then have a secondary mapping logic where positional information is important. With option position you’re essentially telling command parsing how to interpret plain raw ambiguous arguments.spring-doc.cn

Let’s look what happens when we don’t define a position.spring-doc.cn

CommandRegistration.builder()
	.command("arity-strings-1")
	.withOption()
		.longNames("arg1")
		.required()
		.type(String[].class)
		.arity(0, 2)
		.and()
	.withTarget()
		.function(ctx -> {
			String[] arg1 = ctx.getOptionValue("arg1");
			return "Hello " + Arrays.asList(arg1);
		})
		.and()
	.build();

Option arg1 is required and there is no info what to do with argument one resulting error for missing option.spring-doc.cn

shell:>arity-strings-1 one
Missing mandatory option --arg1.

Now let’s define a position 0.spring-doc.cn

CommandRegistration.builder()
	.command("arity-strings-2")
	.withOption()
		.longNames("arg1")
		.required()
		.type(String[].class)
		.arity(0, 2)
		.position(0)
		.and()
	.withTarget()
		.function(ctx -> {
			String[] arg1 = ctx.getOptionValue("arg1");
			return "Hello " + Arrays.asList(arg1);
		})
		.and()
	.build();

Arguments are processed until we get up to 2 arguments.spring-doc.cn

shell:>arity-strings-2 one
Hello [one]

shell:>arity-strings-2 one two
Hello [one, two]

shell:>arity-strings-2 one two three
Hello [one, two]

5.5. Optional Value

An option is either required or not and, generally speaking, how it behaves depends on a command target:spring-doc.cn

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:spring-doc.cn

public String example(
	@ShellOption(defaultValue = ShellOption.NULL) String arg1
) {
	return "Hello " + arg1;
}

5.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:spring-doc.cn

CommandRegistration.builder()
	.withOption()
		.longNames("arg1")
		.defaultValue("defaultValue")
		.and()
	.build();

The annotation model also supports defining default values:spring-doc.cn

public String example(
	@ShellOption(defaultValue = "defaultValue") String arg1
) {
	return "Hello " + arg1;
}

5.7. Validation

Spring Shell integrates with the Bean Validation API to support automatic and self-documenting constraints on command parameters.spring-doc.cn

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:spring-doc.cn

	@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:spring-doc.cn

shell:>change-password hello
The following constraints were not met:
	--password string : size must be between 8 and 40 (You passed 'hello')

5.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.spring-doc.cn

CommandRegistration.builder()
	.withOption()
		.longNames("arg1")
		.and()
	.withOption()
		.longNames("arg2")
		.label("MYLABEL")
		.and()
	.build();

Defining label is then shown in help.spring-doc.cn

my-shell:>help mycommand
NAME
       mycommand -

SYNOPSIS
       mycommand --arg1 String --arg2 MYLABEL

OPTIONS
       --arg1 String
       [Optional]

       --arg2 MYLABEL
       [Optional]

5.9. Types

This section talks about how particular data type is used as an option value.spring-doc.cn

5.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.spring-doc.cn

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.spring-doc.cn

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();

5.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.spring-doc.cn

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

5.9.3. Number

Numbers are converted as is.spring-doc.cn

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();

5.9.4. Enum

Conversion to enums is possible if given value is exactly matching enum itself. Currently you can convert assuming case insensitivity.spring-doc.cn

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();

5.9.5. Array

Arrays can be used as is with strings and primitive types.spring-doc.cn

String example(@ShellOption(value = "arg1") String[] arg1) {
	return "Hello " + arg1;
}
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();

5.10. Naming

If there is a need to modify option long names that can be done using OptionNameModifier interface which is a simple Function<String, String>. In this interface original option name goes in and modified name comes out.spring-doc.cn

Modifier can be defined per OptionSpec in CommandRegistration, defaulting globally as bean or via configuration properties. Modifier defined manually in OptionSpec takes takes precedence over one defined globally. There is no global modifier defined on default.spring-doc.cn

You can define one with an option in CommandRegistration.spring-doc.cn

CommandRegistration.builder()
	.withOption()
		.longNames("arg1")
		.nameModifier(name -> "x" + name)
		.and()
	.build();

Add one singleton bean as type OptionNameModifier and that becomes a global default.spring-doc.cn

@Bean
OptionNameModifier sampleOptionNameModifier() {
	return name -> "x" + name;
}

It’s also possible to just add configuration property with spring.shell.option.naming.case-type which auto-configures one based on a type defined.spring-doc.cn

noop is to do nothing, camel, snake, kebab, pascal activates build-in modifiers for camelCase, snake_case, kebab-case or PascalCase respectively.spring-doc.cn

If creating CommandRegistration beans directly, global default via configuration properies only work if using pre-configured Builder instance. See more Programmatic Model.
spring:
  shell:
     option:
       naming:
         case-type: noop
         # case-type: camel
         # case-type: snake
         # case-type: kebab
         # case-type: pascal

For example options defined in an annotated method like this.spring-doc.cn

@ShellMethod(key = "option-naming-sample")
public void optionNamingSample(
	@ShellOption("from_snake") String snake,
	@ShellOption("fromCamel") String camel,
	@ShellOption("from-kebab") String kebab,
	@ShellOption("FromPascal") String pascal
) {}

On default help for that command shows names coming directly from @ShellOption.spring-doc.cn

OPTIONS
       --from_snake String
       [Mandatory]

       --fromCamel String
       [Mandatory]

       --from-kebab String
       [Mandatory]

       --FromPascal String
       [Mandatory]

Define spring.shell.option.naming.case-type=kebab and default modifier is added and option names then look like.spring-doc.cn

OPTIONS
       --from-snake String
       [Mandatory]

       --from-camel String
       [Mandatory]

       --from-kebab String
       [Mandatory]

       --from-pascal String
       [Mandatory]