add documentation/tests for the convert option
Project: http://git-wip-us.apache.org/repos/asf/groovy/repo Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/e8257541 Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/e8257541 Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/e8257541 Branch: refs/heads/master Commit: e8257541ce32f9c0274f8269147a585f2afd0989 Parents: 09f137b Author: paulk <pa...@asert.com.au> Authored: Fri Apr 8 19:58:30 2016 +1000 Committer: paulk <pa...@asert.com.au> Committed: Wed Apr 13 20:38:48 2016 +1000 ---------------------------------------------------------------------- src/main/groovy/cli/Option.java | 6 +- src/main/groovy/util/CliBuilder.groovy | 69 +++++--- .../doc/core-domain-specific-languages.adoc | 168 +++++++++++++------ src/spec/test/builder/CliBuilderTest.groovy | 54 +++++- src/test/groovy/util/CliBuilderTest.groovy | 27 ++- 5 files changed, 240 insertions(+), 84 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/groovy/blob/e8257541/src/main/groovy/cli/Option.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/cli/Option.java b/src/main/groovy/cli/Option.java index 619f65c..c5001a8 100644 --- a/src/main/groovy/cli/Option.java +++ b/src/main/groovy/cli/Option.java @@ -60,7 +60,7 @@ public @interface Option { * * @return the value separator for this multi-valued option */ - char valueSeparator() default 0; + String valueSeparator() default ""; /** * Whether this option can have an optional argument. @@ -73,12 +73,12 @@ public @interface Option { /** * How many arguments this option has. * A value greater than 1 is only allowed for array-typed arguments. - * Ignored for boolean options or if {@code numberOfArgumentsString} is set. + * Ignored for boolean options which are assumed to have a default of 0 + * or if {@code numberOfArgumentsString} is set. * * @return the number of arguments */ int numberOfArguments() default 1; - // TODO: 0 checked for boolean? What about def - Object as flag? /** * How many arguments this option has represented as a String. http://git-wip-us.apache.org/repos/asf/groovy/blob/e8257541/src/main/groovy/util/CliBuilder.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/util/CliBuilder.groovy b/src/main/groovy/util/CliBuilder.groovy index 94c22fb..e71ac3f 100644 --- a/src/main/groovy/util/CliBuilder.groovy +++ b/src/main/groovy/util/CliBuilder.groovy @@ -373,7 +373,8 @@ class CliBuilder { String shortName = annotation.shortName() String description = annotation.description() String defaultValue = annotation.defaultValue() - char valueSeparator = annotation.valueSeparator() + char valueSeparator = 0 + if (annotation.valueSeparator()) valueSeparator = annotation.valueSeparator() as char boolean optionalArg = annotation.optionalArg() Integer numberOfArguments = annotation.numberOfArguments() String numberOfArgumentsString = annotation.numberOfArgumentsString() @@ -381,31 +382,39 @@ class CliBuilder { if (convert == Undefined.CLASS) { convert = null } - String longName = adjustLongName(annotation.longName(), m, namesAreSetters) - def builder = shortName && !shortName.isEmpty() ? CliOption.builder(shortName) : CliOption.builder() - builder.longOpt(longName) + Map names = calculateNames(annotation.longName(), annotation.shortName(), m, namesAreSetters) + def builder = names.short ? CliOption.builder(names.short) : CliOption.builder() + if (names.long) { + builder.longOpt(names.long) + } if (numberOfArguments != 1) { if (numberOfArgumentsString) { throw new CliBuilderException("You can't specify both 'numberOfArguments' and 'numberOfArgumentsString'") } } def details = [:] + Class type = namesAreSetters ? (m.parameterTypes.size() > 0 ? m.parameterTypes[0] : null) : m.returnType + if (optionalArg && (!type || !type.isArray())) { + throw new CliBuilderException("Attempted to set optional argument for non array type") + } + def isFlag = type.simpleName.toLowerCase() == 'boolean' if (numberOfArgumentsString) { details.args = numberOfArgumentsString details = adjustDetails(details) if (details.optionalArg) optionalArg = true } else { - details.args = numberOfArguments + details.args = isFlag ? 0 : numberOfArguments + } + if (details?.args == 0 && !(isFlag || type.name == 'java.lang.Object')) { + throw new CliBuilderException("Flag '${names.long ?: names.short}' must be Boolean or Object") } if (description) builder.desc(description) if (valueSeparator) builder.valueSeparator(valueSeparator) - Class type = namesAreSetters ? (m.parameterTypes.size() > 0 ? m.parameterTypes[0] : null) : m.returnType - if (optionalArg && (!type || !type.isArray())) { - throw new CliBuilderException("Attempted to set optional argument for non array type") - } if (type) { - def simpleNameLower = type.simpleName.toLowerCase() - def isFlag = simpleNameLower == 'boolean' + if (isFlag && details.args == 1) { + // special flag: treat like normal not boolean expecting explicit 'true' or 'false' param + isFlag = false + } if (!isFlag) { builder.hasArg(true) if (details.containsKey('args')) builder.numberOfArgs(details.args) @@ -444,15 +453,15 @@ class CliBuilder { def setOptionsFromAnnotations(def cli, Class optionClass, Object t, boolean namesAreSetters) { optionClass.methods.findAll{ it.getAnnotation(Option) }.each { Method m -> Annotation annotation = m.getAnnotation(Option) - String longName = adjustLongName(annotation.longName(), m, namesAreSetters) - processSetAnnotation(m, t, longName, cli, namesAreSetters) + Map names = calculateNames(annotation.longName(), annotation.shortName(), m, namesAreSetters) + processSetAnnotation(m, t, names.long ?: names.short, cli, namesAreSetters) } optionClass.declaredFields.findAll { it.getAnnotation(Option) }.each { Field f -> Annotation annotation = f.getAnnotation(Option) String setterName = "set" + MetaClassHelper.capitalize(f.getName()); Method m = optionClass.getMethod(setterName, f.getType()) - String longName = adjustLongName(annotation.longName(), m, true) - processSetAnnotation(m, t, longName, cli, true) + Map names = calculateNames(annotation.longName(), annotation.shortName(), m, true) + processSetAnnotation(m, t, names.long ?: names.short, cli, true) } def remaining = cli.arguments() optionClass.methods.findAll{ it.getAnnotation(Unparsed) }.each { Method m -> @@ -469,8 +478,8 @@ class CliBuilder { if (namesAreSetters) { m.invoke(t, remaining.toList()) } else { - String longName = adjustLongName("", m, namesAreSetters) - t.put(longName, { -> remaining.toList() }) + Map names = calculateNames("", "", m, namesAreSetters) + t.put(names.long, { -> remaining.toList() }) } } @@ -479,13 +488,17 @@ class CliBuilder { if (conv && conv instanceof Class) { savedTypeOptions[name].convert = conv.newInstance(t, t) } + boolean hasArg = savedTypeOptions[name]?.cliOption?.numberOfArgs == 1 + boolean noArg = savedTypeOptions[name]?.cliOption?.numberOfArgs == 0 if (namesAreSetters) { - boolean isFlag = m.parameterTypes.size() > 0 && m.parameterTypes[0].simpleName.toLowerCase() == 'boolean' + def isBoolArg = m.parameterTypes.size() > 0 && m.parameterTypes[0].simpleName.toLowerCase() == 'boolean' + boolean isFlag = (isBoolArg && !hasArg) || noArg if (cli.hasOption(name) || isFlag) { m.invoke(t, [isFlag ? cli.hasOption(name) : optionValue(cli, name)] as Object[]) } } else { - boolean isFlag = m.returnType.simpleName.toLowerCase() == 'boolean' + def isBoolRetType = m.returnType.simpleName.toLowerCase() == 'boolean' + boolean isFlag = (isBoolRetType && !hasArg) || noArg t.put(m.getName(), cli.hasOption(name) ? { -> isFlag ? true : optionValue(cli, name) } : { -> isFlag ? false : null }) @@ -499,14 +512,17 @@ class CliBuilder { cli[name] } - private String adjustLongName(String longName, Method m, boolean namesAreSetters) { - if (!longName || longName.isEmpty()) { - longName = m.getName() - if (namesAreSetters && longName.startsWith("set")) { - longName = MetaClassHelper.convertPropertyName(longName.substring(3)) + private Map calculateNames(String longName, String shortName, Method m, boolean namesAreSetters) { + boolean useShort = longName == '_' + if (longName == '_') longName = "" + def result = longName + if (!longName) { + result = m.getName() + if (namesAreSetters && result.startsWith("set")) { + result = MetaClassHelper.convertPropertyName(result.substring(3)) } } - longName + [long: useShort ? "" : result, short: (useShort && !shortName) ? result : shortName] } // implementation details ------------------------------------- @@ -646,6 +662,9 @@ class OptionAccessor { if (Closure.isAssignableFrom(type) && savedTypeOptions[optionName]?.convert) { return (T) savedTypeOptions[optionName].convert(optionValue) } + if (savedTypeOptions[optionName]?.cliOption?.numberOfArgs == 0) { + return (T) commandLine.hasOption(optionName) + } if (type?.simpleName?.toLowerCase() == 'boolean') { return (T) Boolean.parseBoolean(optionValue) } http://git-wip-us.apache.org/repos/asf/groovy/blob/e8257541/src/spec/doc/core-domain-specific-languages.adoc ---------------------------------------------------------------------- diff --git a/src/spec/doc/core-domain-specific-languages.adoc b/src/spec/doc/core-domain-specific-languages.adoc index ff90f54..4aa387b 100644 --- a/src/spec/doc/core-domain-specific-languages.adoc +++ b/src/spec/doc/core-domain-specific-languages.adoc @@ -1083,11 +1083,19 @@ include::{projectdir}/subprojects/groovy-ant/{specfolder}/ant-builder.adoc[level ==== CliBuilder -`CliBuilder` provides a compact way to specify the available parameters for a command-line application and then -automatically parse the application's parameters according to that specification. -Even though the details of each command-line you create could be quite different, the same main steps are -followed each time. First, a `CliBuilder` instance is created. Then, allowed command-line parameters are defined. -The arguments are then parsed according to the parameter specification resulting in a +`CliBuilder` provides a compact way to specify the available options for a commandline application and then +automatically parse the application's commandline parameters according to that specification. By convention, +a distinction is made between _option_ commandline parameters and any remaining parameters which are passed +to an application as its arguments. Typically, several types of options might be supported such as `-V` or +`--tabsize=4`. `CliBuilder` removes the burden of developing lots of code for commandline processing. +Instead, it supports a somewhat declarative approach to declaring your options and then provides a single call +to parse the commandline parameters with a simple mechanism to interrogate the options (you can think of this +as a simple model for your options). + +Even though the details of each commandline you create could be quite different, the same main steps are +followed each time. First, a `CliBuilder` instance is created. Then, allowed commandline options are defined. +This can be done using a _dynamic api_ style or an _annotation_ style. +The commandline parameters are then parsed according to the options specification resulting in a collection of options which are then interrogated. Here is a simple example `Greeter.groovy` script illustrating usage: @@ -1105,13 +1113,13 @@ if (options.h) cli.usage() <5> else println "Hello ${options.a ? options.a : 'World'}" <6> --------------------------- <1> define a new `CliBuilder` instance specifying an optional usage string -<2> specify a `-a` parameter taking a single argument with an optional long variant `--audience` -<3> specify a `-h` parameter taking no arguments with an optional long variant `--help` -<4> parse the arguments supplied to the script -<5> if the `h` argument is found display a usage message -<6> display a standard greeting or if the `a` argument is found a customized greeting +<2> specify a `-a` option taking a single argument with an optional long variant `--audience` +<3> specify a `-h` option taking no arguments with an optional long variant `--help` +<4> parse the commandline parameters supplied to the script +<5> if the `h` option is found display a usage message +<6> display a standard greeting or, if the `a` option is found, a customized greeting -Running this script with no arguments, i.e.: +Running this script with no commandline parameters, i.e.: [source,shell] ---- @@ -1124,7 +1132,7 @@ results in the following output: Hello World ---- -Running this script with `-h` as the arguments, i.e.: +Running this script with `-h` as the single commandline parameter, i.e.: [source,shell] ---- @@ -1139,7 +1147,7 @@ usage: groovy Greeter [option] -h,--help display usage ---- -Running this script with `--audience Groovologist` as the arguments, i.e.: +Running this script with `--audience Groovologist` as the commandline parameters, i.e.: [source,shell] ---- @@ -1152,28 +1160,31 @@ results in the following output: Hello Groovologist ---- -When creating the `CliBuilder` instance in the above example, we used the optional `usage` named -parameter to the constructor. This follows Groovy's normal ability to set additional properties +When creating the `CliBuilder` instance in the above example, we set the optional `usage` property +within the constructor call. This follows Groovy's normal ability to set additional properties of the instance during construction. There are numerous other properties which can be set such as `header` and `footer`. For the complete set of available properties, see the available properties for the gapi:groovy.util.CliBuilder[CliBuilder] class. -When defining an allowed command-line parameter, a short description must be supplied, e.g. -"display usage" for the `help` option shown previously. In our example above, we also used some -additional named parameters such as `longOpt` and `args`. The following additional named -parameters are supported when specifying an allowed command-line parameter: +When defining an allowed commandline option, both a short name (e.g. "h" for the `help` option shown previously) +and a short description (e.g. "display usage" for the `help` option) must be supplied. +In our example above, we also set some +additional properties such as `longOpt` and `args`. The following additional +properties are supported when specifying an allowed commandline option: [options="header"] |====================== | Name | Description | Type -| argName | the name of the argument for this option used in output | String -| longOpt | the long representation of the option | String -| args | the number of argument values | int -| optionalArg | whether the argument value is optional | boolean -| required | whether the option is mandatory | boolean -| type | the type of this option | Class -| valueSeparator | the character that is the value separator | char +| argName | the name of the argument for this option used in output | `String` +| longOpt | the long representation or long name of the option | `String` +| args | the number of argument values | `int` or `String`<1> +| optionalArg | whether the argument value is optional | `boolean` +| required | whether the option is mandatory | `boolean` +| type | the type of this option | `Class` +| valueSeparator | the character that is the value separator | `char`<2> |====================== +<1> More details later +<2> Single character Strings are coerced to chars in special cases in Groovy If you have an option with only a `longOpt` variant, you can use the special shortname of 'pass:[_]' to specify the option, e.g. : `cli.pass:[_](longOpt: 'verbose', 'enable verbose logging')`. @@ -1183,10 +1194,10 @@ a bit more explanation. But before further explanations, let's look at ways of u ===== Using Annotations and an interface -Rather than making a series of method calls to specify the allowable options, you can -provide an interface specification of the allowable options where annotations are used -to indicate and provide details for the allowable options and for how un-processed -arguments are handled. Two annotations are used: gapi:groovy.cli.Option[] and gapi:groovy.cli.Unparsed[] +Rather than making a series of method calls (albeit in a very declarative mini-DSL form) +to specify the allowable options, you can provide an interface specification of the allowable options where +annotations are used to indicate and provide details for those options and for how unprocessed +parameters are handled. Two annotations are used: gapi:groovy.cli.Option[] and gapi:groovy.cli.Unparsed[]. Here is how such a specification can be defined: @@ -1196,11 +1207,12 @@ include::{projectdir}/src/spec/test/builder/CliBuilderTest.groovy[tags=annotatio ---- <1> Specify a Boolean option set using `-h` or `--help` <2> Specify a String option set using `-a` or `--audience` -<3> Specify where any remaining args will be stored +<3> Specify where any remaining parameters will be stored Note how the long name is automatically determined from the interface method name. You can use the `longName` annotation attribute to override that behavior and specify -a custom long name if you wish. +a custom long name if you wish or use a longName of 'pass:[_]' to indicate that no long name +is to be provided. You will need to specify a shortName in such a case. Here is how you could use the interface specification: @@ -1208,11 +1220,11 @@ Here is how you could use the interface specification: ---- include::{projectdir}/src/spec/test/builder/CliBuilderTest.groovy[tags=annotationInterface,indent=0] ---- -<1> Create a `CliBuilder` instance as before with optional parameters -<2> Parse arguments using the interface specification +<1> Create a `CliBuilder` instance as before with optional properties +<2> Parse parameters using the interface specification <3> Interrogate options using the methods from the interface -<4> Parse a different set of arguments -<5> Interrogate the remaining arguments +<4> Parse a different set of parameters +<5> Interrogate the remaining parameters When `parseFromSpec` is called, `CliBuilder` automatically creates an instance implementing the interface and populates it. You simply call the interface methods to interrogate the option values. @@ -1246,7 +1258,8 @@ include::{projectdir}/src/spec/test/builder/CliBuilderTest.groovy[tags=annotatio <5> Interrogate the remaining arguments property When `parseFromInstance` is called, `CliBuilder` automatically populates your instance. -You simply interrogate the instance properties to access the option values. +You simply interrogate the instance properties (or whatever accessor methods you have provided +in your domain object) to access the option values. ===== Using Annotations and a script @@ -1281,10 +1294,11 @@ include::{projectdir}/src/spec/test/builder/CliBuilderTest.groovy[tags=withArgum <5> An example using this spec where no argument is supplied to the 'c' option; it's just a flag Note: when an option with an optional argument is encountered, it will (somewhat) greedily consume the -next argument on the commandline. If however, the next argument matches a known long or short +next parameter from the supplied commandline parameters. If however, the next parameter matches a known long or short option (with leading single or double hyphens), that will take precedence, e.g. `-b` in the above example. -Arguments may also be specified using the annotation style. Here is an interface option specification: +Option arguments may also be specified using the annotation style. Here is an interface option specification +illustrating such a definition: [source,groovy] ---- @@ -1298,9 +1312,55 @@ And here is how it is used: include::{projectdir}/src/spec/test/builder/CliBuilderTest.groovy[tags=withArgumentInterface,indent=0] ---- -This example makes use of an array-typed option specification. We cover this in more detail next when we discuss +This example makes use of an array-typed option specification. We cover this in more detail shortly when we discuss multiple arguments. +===== Specifying a type + +Arguments on the commandline are by nature Strings (or arguably can be considered Booleans for flags) but can be +converted to richer types automatically by supplying additional typing information. For the +annotation-based argument definition style, these types are supplied using the field types for annotation +properties or return types of annotated methods (are the setter argument type for setter methods). +For the dynamic method style of argument definition a special 'type' property is supported +which allows you to specify a Class name. + +When an explicit type is defined, the `args` named-parameter is assumed to be 1 (except for Boolean-typed +options where it is 0 by default). An explicit `args` parameter can still be provided if needed. +Here is an example using types with the dynamic api argument definition style: + +[source,groovy] +---- +include::{projectdir}/src/spec/test/builder/CliBuilderTest.groovy[tags=withType,indent=0] +---- + +Primitives, numeric types, files, enums and arrays thereof, are supported (they are converted using +gapi:org.codehaus.groovy.runtime.StringGroovyMethods#asType[StringGroovyMethods#asType(String, Class)]). + +===== Custom parsing of the argument String + +If the supported types aren't sufficient, you can supply a closure to handle the String to rich type conversion +for you. Here is a sample using the dynamic api style: + +[source,groovy] +---- +include::{projectdir}/src/spec/test/builder/CliBuilderTest.groovy[tags=withConvert,indent=0] +---- + +Alternatively, you can use the annotation style by supplying the conversion closure as an annotation parameter. +Here is an example specification: + +[source,groovy] +---- +include::{projectdir}/src/spec/test/builder/CliBuilderTest.groovy[tags=withConvertInterfaceSpec,indent=0] +---- + +And an example using that specification: + +[source,groovy] +---- +include::{projectdir}/src/spec/test/builder/CliBuilderTest.groovy[tags=withConvertInterface,indent=0] +---- + ===== Options with multiple arguments Multiple arguments are also supported using an args value greater than 1. There is a special named parameter, @@ -1337,24 +1397,32 @@ As an alternative to obtaining multiple arguments is to use an array-based type option. In this case, all options will always be returned via the array. We'll see an example of this next when discussing types. It is also the only approach to use when using the annotation style. -===== Specifying a type +Multiple arguments are also supported using the annotation style of option definition by using an array type for the +annotated class member (method or property) as this example shows: -When an explicit type is defined, the `args` named-parameter is assumed to be 1 (except for Boolean-typed -options where it is 0 by default). An explicit `args` parameter can still be provided if needed. -Here is an example using types: +[source,groovy] +---- +include::{projectdir}/src/spec/test/builder/CliBuilderTest.groovy[tags=multipleArgsInterfaceSpec,indent=0] +---- + +And used as follows: [source,groovy] ---- -include::{projectdir}/src/spec/test/builder/CliBuilderTest.groovy[tags=withType,indent=0] +include::{projectdir}/src/spec/test/builder/CliBuilderTest.groovy[tags=multipleArgsInterface,indent=0] ---- -<1> For an array type, the trailing 's' can be used but isn't needed -Primitives, numeric types, files, enums and arrays thereof, are supported (they are converted using -gapi:org.codehaus.groovy.runtime.StringGroovyMethods#asType[StringGroovyMethods#asType(String, Class)]). +===== Types and multiple arguments -===== Setting a default value +Here is an example using types and multiple arguments with the dynamic api argument definition style: -===== Custom parsing of the argument String +[source,groovy] +---- +include::{projectdir}/src/spec/test/builder/CliBuilderTest.groovy[tags=withTypeMultiple,indent=0] +---- +<1> For an array type, the trailing 's' can be used but isn't needed + +===== Setting a default value ===== Advanced CLI Usage http://git-wip-us.apache.org/repos/asf/groovy/blob/e8257541/src/spec/test/builder/CliBuilderTest.groovy ---------------------------------------------------------------------- diff --git a/src/spec/test/builder/CliBuilderTest.groovy b/src/spec/test/builder/CliBuilderTest.groovy index 091ce7c..8284d04 100644 --- a/src/spec/test/builder/CliBuilderTest.groovy +++ b/src/spec/test/builder/CliBuilderTest.groovy @@ -174,10 +174,45 @@ class CliBuilderTest extends GroovyTestCase { // end::multipleArgs[] } + // tag::multipleArgsInterfaceSpec[] + interface ValSepI { + @Option(numberOfArguments=2) String[] a() + @Option(numberOfArgumentsString='2', valueSeparator=',') String[] b() + @Option(numberOfArgumentsString='+', valueSeparator=',') String[] c() + @Unparsed remaining() + } + // end::multipleArgsInterfaceSpec[] + + void testMultipleArgsAndOptionalValueSeparatorInterface() { + // tag::multipleArgsInterface[] + def cli = new CliBuilder() + + def options = cli.parseFromSpec(ValSepI, '-a 1 2 3 4'.split()) + assert options.a() == ['1', '2'] + assert options.remaining() == ['3', '4'] + + options = cli.parseFromSpec(ValSepI, '-a1 -a2 3'.split()) + assert options.a() == ['1', '2'] + assert options.remaining() == ['3'] + + options = cli.parseFromSpec(ValSepI, ['-b1,2'] as String[]) + assert options.b() == ['1', '2'] + + options = cli.parseFromSpec(ValSepI, ['-c', '1'] as String[]) + assert options.c() == ['1'] + + options = cli.parseFromSpec(ValSepI, ['-c1'] as String[]) + assert options.c() == ['1'] + + options = cli.parseFromSpec(ValSepI, ['-c1,2,3'] as String[]) + assert options.c() == ['1', '2', '3'] + // end::multipleArgsInterface[] + } + void testType() { // tag::withType[] def argz = '''-a John -b -d 21 -e 1980 -f 3.5 -g 3.14159 - -h cv.txt -i DOWN -j 3 4 5 -k1.5,2.5,3.5 and some more'''.split() + -h cv.txt -i DOWN and some more'''.split() def cli = new CliBuilder() cli.a(type: String, 'a-arg') cli.b(type: boolean, 'b-arg') @@ -188,8 +223,6 @@ class CliBuilderTest extends GroovyTestCase { cli.g(type: BigDecimal, 'g-arg') cli.h(type: File, 'h-arg') cli.i(type: RoundingMode, 'i-arg') - cli.j(args: 3, type: int[], 'j-arg') - cli.k(args: '+', valueSeparator: ',', type: BigDecimal[], 'k-arg') def options = cli.parse(argz) assert options.a == 'John' assert options.b @@ -200,11 +233,22 @@ class CliBuilderTest extends GroovyTestCase { assert options.g == 3.14159 assert options.h == new File('cv.txt') assert options.i == RoundingMode.DOWN + assert options.arguments() == ['and', 'some', 'more'] + // end::withType[] + } + + void testTypeMultiple() { + // tag::withTypeMultiple[] + def argz = '''-j 3 4 5 -k1.5,2.5,3.5 and some more'''.split() + def cli = new CliBuilder() + cli.j(args: 3, type: int[], 'j-arg') + cli.k(args: '+', valueSeparator: ',', type: BigDecimal[], 'k-arg') + def options = cli.parse(argz) assert options.js == [3, 4, 5] // <1> assert options.j == [3, 4, 5] // <1> assert options.k == [1.5, 2.5, 3.5] assert options.arguments() == ['and', 'some', 'more'] - // end::withType[] + // end::withTypeMultiple[] } void testConvert() { @@ -234,7 +278,7 @@ class CliBuilderTest extends GroovyTestCase { void testConvertInterface() { // tag::withConvertInterface[] - def newYears = Date.parse("yyyy-MM-dd", "2016-01-01") + Date newYears = Date.parse("yyyy-MM-dd", "2016-01-01") def argz = '''-a John -b Mary -d 2016-01-01 and some more'''.split() def cli = new CliBuilder() def options = cli.parseFromSpec(WithConvertI, argz) http://git-wip-us.apache.org/repos/asf/groovy/blob/e8257541/src/test/groovy/util/CliBuilderTest.groovy ---------------------------------------------------------------------- diff --git a/src/test/groovy/util/CliBuilderTest.groovy b/src/test/groovy/util/CliBuilderTest.groovy index 3bf47ef..4ec52d0 100644 --- a/src/test/groovy/util/CliBuilderTest.groovy +++ b/src/test/groovy/util/CliBuilderTest.groovy @@ -529,7 +529,32 @@ usage: groovy ' cv.txt, DOWN, [and, some, more])' } - void testParseScript() { + interface FlagEdgeCasesI { + @Option boolean abc() + @Option(numberOfArgumentsString='1') boolean efg() + @Option(numberOfArguments=1) ijk() + @Option(numberOfArguments=0) lmn() + @Unparsed List remaining() + } + + void testParseFromInstanceFlagEdgeCases() { + def cli = new CliBuilder() + def options = cli.parseFromSpec(FlagEdgeCasesI, '-abc -efg true --ijk foo --lmn bar baz'.split()) + + assert options.abc() && options.efg() + assert options.ijk() == 'foo' + assert options.lmn() == true + assert options.remaining() == ['bar', 'baz'] + + options = cli.parseFromSpec(FlagEdgeCasesI, '-abc -ijk cat -efg false bar baz'.split()) + assert options.abc() + assert options.ijk() == 'cat' + assert !options.efg() + assert options.lmn() == false + assert options.remaining() == ['bar', 'baz'] + } + + void testParseScript() { new GroovyShell().run(''' import groovy.cli.OptionField import groovy.cli.UnparsedField