Support explicit arity min max with @Option
- @Option now has arityMin/arityMax which if defined, non-negative, are used instead of arity. - Fixes #731
This commit is contained in:
@@ -74,6 +74,28 @@ public @interface Option {
|
||||
* Define option arity.
|
||||
*
|
||||
* @return option arity
|
||||
* @see #arityMin()
|
||||
* @see #arityMax()
|
||||
*/
|
||||
OptionArity arity() default OptionArity.NONE;
|
||||
|
||||
/**
|
||||
* Define option arity min. If Defined non-negative will be used instead of
|
||||
* {@link #arity()}. If {@code arityMax} is not set non-negative it is set to
|
||||
* same as this.
|
||||
*
|
||||
* @return option arity min
|
||||
* @see #arity()
|
||||
*/
|
||||
int arityMin() default -1;
|
||||
|
||||
/**
|
||||
* Define option arity max. If Defined non-negative will be used instead of
|
||||
* {@link #arity()}. If {@code arityMin} is not set non-negative it is set to
|
||||
* zero.
|
||||
*
|
||||
* @return option arity max
|
||||
* @see #arity()
|
||||
*/
|
||||
int arityMax() default -1;
|
||||
}
|
||||
|
||||
@@ -251,7 +251,22 @@ class CommandRegistrationFactoryBean implements FactoryBean<CommandRegistration>
|
||||
optionSpec.shortNames(shortNames.toArray(new Character[0]));
|
||||
optionSpec.position(mp.getParameterIndex());
|
||||
optionSpec.description(so.description());
|
||||
if (so.arity() != OptionArity.NONE) {
|
||||
int arityMin = so.arityMin();
|
||||
int arityMax = so.arityMax();
|
||||
if (arityMin > -1) {
|
||||
if (arityMax < arityMin) {
|
||||
arityMax = arityMin;
|
||||
}
|
||||
}
|
||||
else if (arityMax > -1) {
|
||||
if (arityMin < 0) {
|
||||
arityMin = 0;
|
||||
}
|
||||
}
|
||||
if (arityMin > -1 && arityMax > -1) {
|
||||
optionSpec.arity(arityMin, arityMax);
|
||||
}
|
||||
else if (so.arity() != OptionArity.NONE) {
|
||||
optionSpec.arity(so.arity());
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.shell.Availability;
|
||||
import org.springframework.shell.AvailabilityProvider;
|
||||
import org.springframework.shell.command.CommandRegistration;
|
||||
import org.springframework.shell.command.CommandRegistration.OptionArity;
|
||||
import org.springframework.shell.command.annotation.Command;
|
||||
import org.springframework.shell.command.annotation.CommandAvailability;
|
||||
import org.springframework.shell.command.annotation.Option;
|
||||
@@ -242,6 +243,78 @@ class CommandRegistrationFactoryBeanTests {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void setsOptionWithArity() {
|
||||
configCommon(OptionWithArity.class, new OptionWithArity(), "command1", new Class[] { String.class })
|
||||
.run((context) -> {
|
||||
CommandRegistrationFactoryBean fb = context.getBean(FACTORYBEANREF,
|
||||
CommandRegistrationFactoryBean.class);
|
||||
assertThat(fb).isNotNull();
|
||||
CommandRegistration registration = fb.getObject();
|
||||
assertThat(registration).isNotNull();
|
||||
assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(1);
|
||||
assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(1);
|
||||
});
|
||||
configCommon(OptionWithArity.class, new OptionWithArity(), "command2", new Class[] { String.class })
|
||||
.run((context) -> {
|
||||
CommandRegistrationFactoryBean fb = context.getBean(FACTORYBEANREF,
|
||||
CommandRegistrationFactoryBean.class);
|
||||
assertThat(fb).isNotNull();
|
||||
CommandRegistration registration = fb.getObject();
|
||||
assertThat(registration).isNotNull();
|
||||
assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(1);
|
||||
assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(1);
|
||||
});
|
||||
configCommon(OptionWithArity.class, new OptionWithArity(), "command3", new Class[] { String.class })
|
||||
.run((context) -> {
|
||||
CommandRegistrationFactoryBean fb = context.getBean(FACTORYBEANREF,
|
||||
CommandRegistrationFactoryBean.class);
|
||||
assertThat(fb).isNotNull();
|
||||
CommandRegistration registration = fb.getObject();
|
||||
assertThat(registration).isNotNull();
|
||||
assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(0);
|
||||
assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(2);
|
||||
});
|
||||
configCommon(OptionWithArity.class, new OptionWithArity(), "command4", new Class[] { String.class })
|
||||
.run((context) -> {
|
||||
CommandRegistrationFactoryBean fb = context.getBean(FACTORYBEANREF,
|
||||
CommandRegistrationFactoryBean.class);
|
||||
assertThat(fb).isNotNull();
|
||||
CommandRegistration registration = fb.getObject();
|
||||
assertThat(registration).isNotNull();
|
||||
assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(0);
|
||||
assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(2);
|
||||
});
|
||||
}
|
||||
|
||||
@Command
|
||||
private static class OptionWithArity {
|
||||
|
||||
@Command
|
||||
void command1(@Option(longNames = "arg", arity = OptionArity.EXACTLY_ONE) String arg) {
|
||||
}
|
||||
|
||||
@Command
|
||||
void command2(@Option(longNames = "arg", arityMin = 1) String arg) {
|
||||
}
|
||||
|
||||
@Command
|
||||
void command3(@Option(longNames = "arg", arityMax = 2) String arg) {
|
||||
}
|
||||
|
||||
@Command
|
||||
void command4(@Option(longNames = "arg", arityMax = 2, arity = OptionArity.EXACTLY_ONE) String arg) {
|
||||
}
|
||||
|
||||
@Bean
|
||||
CompletionProvider completionProvider() {
|
||||
return ctx -> {
|
||||
return Collections.emptyList();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private <T> ApplicationContextRunner configCommon(Class<T> type, T bean) {
|
||||
return configCommon(type, bean, "command", new Class[0]);
|
||||
}
|
||||
|
||||
@@ -2,26 +2,29 @@
|
||||
=== Arity
|
||||
ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs]
|
||||
|
||||
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.
|
||||
Arity defines how many parameters option parsing takes.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/OptionSnippets.java[tag=option-registration-arityints]
|
||||
----
|
||||
====
|
||||
NOTE: There are limitations in a `legacy annotation` compared to `annotation`
|
||||
and `programmatic` use of arity settings. These are mentioned in notes in
|
||||
below samples.
|
||||
|
||||
Arity can also be defined as an `OptionArity` enum, which are shortcuts
|
||||
shown in below table <<using-shell-options-arity-optionarity-table>>.
|
||||
[source,java,indent=0,role="primary"]
|
||||
.Programmatic
|
||||
----
|
||||
include::{snippets}/OptionSnippets.java[tag=option-registration-zeroorone-programmatic]
|
||||
----
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
[source,java,indent=0,role="secondary"]
|
||||
.Annotation
|
||||
----
|
||||
include::{snippets}/OptionSnippets.java[tag=option-registration-arityenum]
|
||||
include::{snippets}/OptionSnippets.java[tag=option-registration-zeroorone-annotation]
|
||||
----
|
||||
|
||||
[source,java,indent=0,role="secondary"]
|
||||
.Legacy Annotation
|
||||
----
|
||||
include::{snippets}/OptionSnippets.java[tag=option-registration-zeroorone-legacyannotation]
|
||||
----
|
||||
====
|
||||
|
||||
[[using-shell-options-arity-optionarity-table]]
|
||||
.OptionArity
|
||||
@@ -44,26 +47,28 @@ include::{snippets}/OptionSnippets.java[tag=option-registration-arityenum]
|
||||
|1 / Integer MAX
|
||||
|===
|
||||
|
||||
The annotation model supports defining only the max value of an arity.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/OptionSnippets.java[tag=option-with-annotation-arity]
|
||||
----
|
||||
====
|
||||
NOTE: `legacy annotation` doesn't support defining minimum arity.
|
||||
|
||||
One of a use cases to manually define arity is to impose restrictions how
|
||||
many parameters option accepts.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
[source,java,indent=0,role="primary"]
|
||||
.Programmatic
|
||||
----
|
||||
include::{snippets}/OptionSnippets.java[tag=option-registration-aritystrings-sample]
|
||||
include::{snippets}/OptionSnippets.java[tag=option-registration-zerooronewithminmax-programmatic]
|
||||
----
|
||||
====
|
||||
|
||||
In above example we have option _arg1_ and it's defined as type _String[]_. Arity
|
||||
[source,java,indent=0,role="secondary"]
|
||||
.Annotation
|
||||
----
|
||||
include::{snippets}/OptionSnippets.java[tag=option-registration-zerooronewithminmax-annotation]
|
||||
----
|
||||
|
||||
[source,java,indent=0,role="secondary"]
|
||||
.Legacy Annotation
|
||||
----
|
||||
include::{snippets}/OptionSnippets.java[tag=option-registration-zerooronewithminmax-legacyannotation]
|
||||
----
|
||||
|
||||
In below 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.
|
||||
|
||||
@@ -155,14 +155,14 @@ public class OptionSnippets {
|
||||
.build();
|
||||
// end::option-registration-arityenum[]
|
||||
|
||||
// tag::option-registration-arityints[]
|
||||
CommandRegistration.builder()
|
||||
.withOption()
|
||||
.longNames("arg1")
|
||||
.arity(0, 1)
|
||||
.and()
|
||||
.build();
|
||||
// end::option-registration-arityints[]
|
||||
// // tag::option-registration-arityints[]
|
||||
// CommandRegistration.builder()
|
||||
// .withOption()
|
||||
// .longNames("arg1")
|
||||
// .arity(0, 1)
|
||||
// .and()
|
||||
// .build();
|
||||
// // end::option-registration-arityints[]
|
||||
|
||||
// tag::option-registration-aritystrings-sample[]
|
||||
CommandRegistration.builder()
|
||||
@@ -288,4 +288,74 @@ public class OptionSnippets {
|
||||
|
||||
}
|
||||
|
||||
static class LegacyAnnotation {
|
||||
|
||||
// tag::option-registration-zeroorone-legacyannotation[]
|
||||
@ShellMethod(key = "example")
|
||||
String zeroOrOne(
|
||||
@ShellOption(arity = 1) String arg)
|
||||
{
|
||||
return String.format("Hi '%s'", arg);
|
||||
}
|
||||
// end::option-registration-zeroorone-legacyannotation[]
|
||||
|
||||
// tag::option-registration-zerooronewithminmax-legacyannotation[]
|
||||
@ShellMethod(key = "example")
|
||||
String zeroOrOneWithMinMax(
|
||||
@ShellOption(arity = 1) String arg)
|
||||
{
|
||||
return String.format("Hi '%s'", arg);
|
||||
}
|
||||
// end::option-registration-zerooronewithminmax-legacyannotation[]
|
||||
}
|
||||
|
||||
static class Annotation {
|
||||
|
||||
// tag::option-registration-zeroorone-annotation[]
|
||||
@Command(command = "example")
|
||||
String zeroOrOne(
|
||||
@Option(arity = OptionArity.ZERO_OR_ONE) String arg)
|
||||
{
|
||||
return String.format("Hi '%s'", arg);
|
||||
}
|
||||
// end::option-registration-zeroorone-annotation[]
|
||||
|
||||
// tag::option-registration-zerooronewithminmax-annotation[]
|
||||
@Command(command = "example")
|
||||
String zeroOrOneWithMinMax(
|
||||
@Option(arityMin = 0, arityMax = 1) String arg)
|
||||
{
|
||||
return String.format("Hi '%s'", arg);
|
||||
}
|
||||
// end::option-registration-zerooronewithminmax-annotation[]
|
||||
|
||||
}
|
||||
|
||||
static class Registration {
|
||||
|
||||
// tag::option-registration-zeroorone-programmatic[]
|
||||
CommandRegistration zeroOrOne() {
|
||||
return CommandRegistration.builder()
|
||||
.command("example")
|
||||
.withOption()
|
||||
.longNames("arg")
|
||||
.arity(OptionArity.ZERO_OR_ONE)
|
||||
.and()
|
||||
.build();
|
||||
}
|
||||
// end::option-registration-zeroorone-programmatic[]
|
||||
|
||||
// tag::option-registration-zerooronewithminmax-programmatic[]
|
||||
CommandRegistration zeroOrOneWithMinMax() {
|
||||
return CommandRegistration.builder()
|
||||
.command("example")
|
||||
.withOption()
|
||||
.longNames("arg")
|
||||
.arity(0, 1)
|
||||
.and()
|
||||
.build();
|
||||
}
|
||||
// end::option-registration-zerooronewithminmax-programmatic[]
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public class ArityCommands {
|
||||
|
||||
@Command(command = "arity-string-array")
|
||||
public String testArityStringArrayAnnotation(
|
||||
@Option(longNames = "arg1", defaultValue = "true", arity = OptionArity.ZERO_OR_MORE)
|
||||
@Option(longNames = "arg1", defaultValue = "true", arityMax = 3)
|
||||
String[] arg1
|
||||
) {
|
||||
return "Hello " + Arrays.asList(arg1);
|
||||
|
||||
Reference in New Issue
Block a user