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

Reply via email to