[1/2] groovy git commit: move commons-cli CliBuilder into its own subproject

classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

[1/2] groovy git commit: move commons-cli CliBuilder into its own subproject

paulk
Repository: groovy
Updated Branches:
  refs/heads/GROOVY_2_6_X 6a198673e -> 00cc9e5e5


http://git-wip-us.apache.org/repos/asf/groovy/blob/00cc9e5e/subprojects/groovy-cli-commons/src/main/groovy/groovy/util/CliBuilder.groovy
----------------------------------------------------------------------
diff --git a/subprojects/groovy-cli-commons/src/main/groovy/groovy/util/CliBuilder.groovy b/subprojects/groovy-cli-commons/src/main/groovy/groovy/util/CliBuilder.groovy
new file mode 100644
index 0000000..bc7d44a
--- /dev/null
+++ b/subprojects/groovy-cli-commons/src/main/groovy/groovy/util/CliBuilder.groovy
@@ -0,0 +1,798 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.util
+
+import groovy.cli.CliBuilderException
+import groovy.cli.Option
+import groovy.cli.TypedOption
+import groovy.cli.Unparsed
+import groovy.transform.Undefined
+import org.apache.commons.cli.CommandLine
+import org.apache.commons.cli.CommandLineParser
+import org.apache.commons.cli.DefaultParser
+import org.apache.commons.cli.GnuParser
+import org.apache.commons.cli.HelpFormatter
+import org.apache.commons.cli.Option as CliOption
+import org.apache.commons.cli.Options
+import org.apache.commons.cli.ParseException
+import org.codehaus.groovy.runtime.InvokerHelper
+import org.codehaus.groovy.runtime.MetaClassHelper
+import org.codehaus.groovy.runtime.StringGroovyMethods
+
+import java.lang.annotation.Annotation
+import java.lang.reflect.Array
+import java.lang.reflect.Field
+import java.lang.reflect.Method
+
+/**
+ * Provides a builder to assist the processing of command line arguments.
+ * Two styles are supported: dynamic api style (declarative method calls provide a mini DSL for describing options)
+ * and annotation style (annotations on an interface or class describe options).
+ * <p>
+ * <b>Dynamic api style</b>
+ * <p>
+ * Typical usage (emulate partial arg processing of unix command: ls -alt *.groovy):
+ * <pre>
+ * def cli = new CliBuilder(usage:'ls')
+ * cli.a('display all files')
+ * cli.l('use a long listing format')
+ * cli.t('sort by modification time')
+ * def options = cli.parse(args)
+ * assert options // would be null (false) on failure
+ * assert options.arguments() == ['*.groovy']
+ * assert options.a && options.l && options.t
+ * </pre>
+ * The usage message for this example (obtained using <code>cli.usage()</code>) is shown below:
+ * <pre>
+ * usage: ls
+ *  -a   display all files
+ *  -l   use a long listing format
+ *  -t   sort by modification time
+ * </pre>
+ * An underlying parser that supports what is called argument 'bursting' is used
+ * by default. Bursting would convert '-alt' into '-a -l -t' provided no long
+ * option exists with value 'alt' and provided that none of 'a', 'l' or 't'
+ * takes an argument (in fact the last one is allowed to take an argument).
+ * The bursting behavior can be turned off by using an
+ * alternate underlying parser. The simplest way to achieve this is by using
+ * the deprecated GnuParser from Commons CLI with the parser property on the CliBuilder,
+ * i.e. include <code>parser: new GnuParser()</code> in the constructor call.
+ * <p>
+ * Another example (partial emulation of arg processing for 'ant' command line):
+ * <pre>
+ * def cli = new CliBuilder(usage:'ant [options] [targets]',
+ *                          header:'Options:')
+ * cli.help('print this message')
+ * cli.logfile(args:1, argName:'file', 'use given file for log')
+ * cli.D(args:2, valueSeparator:'=', argName:'property=value',
+ *       'use value for given property')
+ * def options = cli.parse(args)
+ * ...
+ * </pre>
+ * Usage message would be:
+ * <pre>
+ * usage: ant [options] [targets]
+ * Options:
+ *  -D &lt;property=value>   use value for given property
+ *  -help                 print this message
+ *  -logfile &lt;file>       use given file for log
+ * </pre>
+ * And if called with the following arguments '-logfile foo -Dbar=baz target'
+ * then the following assertions would be true:
+ * <pre>
+ * assert options // would be null (false) on failure
+ * assert options.arguments() == ['target']
+ * assert options.Ds == ['bar', 'baz']
+ * assert options.logfile == 'foo'
+ * </pre>
+ * Note the use of some special notation. By adding 's' onto an option
+ * that may appear multiple times and has an argument or as in this case
+ * uses a valueSeparator to separate multiple argument values
+ * causes the list of associated argument values to be returned.
+ * <p>
+ * Another example showing long options (partial emulation of arg processing for 'curl' command line):
+ * <pre>
+ * def cli = new CliBuilder(usage:'curl [options] &lt;url&gt;')
+ * cli._(longOpt:'basic', 'Use HTTP Basic Authentication')
+ * cli.d(longOpt:'data', args:1, argName:'data', 'HTTP POST data')
+ * cli.G(longOpt:'get', 'Send the -d data with a HTTP GET')
+ * cli.q('If used as the first parameter disables .curlrc')
+ * cli._(longOpt:'url', args:1, argName:'URL', 'Set URL to work with')
+ * </pre>
+ * Which has the following usage message:
+ * <pre>
+ * usage: curl [options] &lt;url>
+ *     --basic         Use HTTP Basic Authentication
+ *  -d,--data &lt;data>   HTTP POST data
+ *  -G,--get           Send the -d data with a HTTP GET
+ *  -q                 If used as the first parameter disables .curlrc
+ *     --url &lt;URL>     Set URL to work with
+ * </pre>
+ * This example shows a common convention. When mixing short and long names, the
+ * short names are often one character in size. One character options with
+ * arguments don't require a space between the option and the argument, e.g.
+ * <code>-Ddebug=true</code>. The example also shows
+ * the use of '_' when no short option is applicable.
+ * <p>
+ * Also note that '_' was used multiple times. This is supported but if
+ * any other shortOpt or any longOpt is repeated, then the behavior is undefined.
+ * <p>
+ * Short option names may not contain a hyphen. If a long option name contains a hyphen, e.g. '--max-wait' then you can either
+ * use the long hand method call <code>options.hasOption('max-wait')</code> or surround
+ * the option name in quotes, e.g. <code>options.'max-wait'</code>.
+ * <p>
+ * Although CliBuilder on the whole hides away the underlying library used
+ * for processing the arguments, it does provide some hooks which let you
+ * make use of the underlying library directly should the need arise. For
+ * example, the last two lines of the 'curl' example above could be replaced
+ * with the following:
+ * <pre>
+ * import org.apache.commons.cli.*
+ * ... as before ...
+ * cli << new Option('q', false, 'If used as the first parameter disables .curlrc')
+ * cli << Option.builder().longOpt('url').hasArg().argName('URL').
+ *                      desc('Set URL to work with').build()
+ * ...
+ * </pre>
+ *
+ * CliBuilder also supports Argument File processing. If an argument starts with
+ * an '@' character followed by a filename, then the contents of the file with name
+ * filename are placed into the command line. The feature can be turned off by
+ * setting expandArgumentFiles to false. If turned on, you can still pass a real
+ * parameter with an initial '@' character by escaping it with an additional '@'
+ * symbol, e.g. '@@foo' will become '@foo' and not be subject to expansion. As an
+ * example, if the file temp.args contains the content:
+ * <pre>
+ * -arg1
+ * paramA
+ * paramB paramC
+ * </pre>
+ * Then calling the command line with:
+ * <pre>
+ * someCommand @temp.args -arg2 paramD
+ * </pre>
+ * Is the same as calling this:
+ * <pre>
+ * someCommand -arg1 paramA paramB paramC -arg2 paramD
+ * </pre>
+ * This feature is particularly useful on operating systems which place limitations
+ * on the size of the command line (e.g. Windows). The feature is similar to
+ * the 'Command Line Argument File' processing supported by javadoc and javac.
+ * Consult the corresponding documentation for those tools if you wish to see further examples.
+ * <p>
+ * <b>Supported Option Properties</b>:
+ * <pre>
+ *   argName:        String
+ *   longOpt:        String
+ *   args:           int or String
+ *   optionalArg:    boolean
+ *   required:       boolean
+ *   type:           Class
+ *   valueSeparator: char
+ *   convert:        Closure
+ *   defaultValue:   String
+ * </pre>
+ * See {@link org.apache.commons.cli.Option} for the meaning of most of these properties
+ * and {@link CliBuilderTest} for further examples.
+ * <p>
+ * <b>Annotation style with an interface</b>
+ * <p>
+ * With this style an interface is defined containing an annotated method for each option.
+ * It might look like this (following roughly the earlier 'ls' example):
+ * <pre>
+ * import groovy.cli.Option
+ * import groovy.cli.Unparsed
+ *
+ * interface OptionInterface {
+ *     @{@link groovy.cli.Option}(shortName='a', description='display all files') boolean all()
+ *     @{@link groovy.cli.Option}(shortName='l', description='use a long listing format') boolean longFormat()
+ *     @{@link groovy.cli.Option}(shortName='t', description='sort by modification time') boolean time()
+ *     @{@link groovy.cli.Unparsed} List remaining()
+ * }
+ * </pre>
+ * Then this description is supplied to CliBuilder during parsing, e.g.:
+ * <pre>
+ * def args = '-alt *.groovy'.split() // normally from commandline itself
+ * def cli = new CliBuilder(usage:'ls')
+ * def options = cli.parseFromSpec(OptionInterface, args)
+ * assert options.remaining() == ['*.groovy']
+ * assert options.all() && options.longFormat() && options.time()
+ * </pre>
+ * <p>
+ * <b>Annotation style with a class</b>
+ * <p>
+ * With this style a user-supplied instance is used. Annotations on that instance's class
+ * members (properties and setter methods) indicate how to set options and provide the option details
+ * using annotation attributes.
+ * It might look like this (again using the earlier 'ls' example):
+ * <pre>
+ * import groovy.cli.Option
+ * import groovy.cli.Unparsed
+ *
+ * class OptionClass {
+ *     @{@link groovy.cli.Option}(shortName='a', description='display all files') boolean all
+ *     @{@link groovy.cli.Option}(shortName='l', description='use a long listing format') boolean longFormat
+ *     @{@link groovy.cli.Option}(shortName='t', description='sort by modification time') boolean time
+ *     @{@link groovy.cli.Unparsed} List remaining
+ * }
+ * </pre>
+ * Then this description is supplied to CliBuilder during parsing, e.g.:
+ * <pre>
+ * def args = '-alt *.groovy'.split() // normally from commandline itself
+ * def cli = new CliBuilder(usage:'ls')
+ * def options = new OptionClass()
+ * cli.parseFromInstance(options, args)
+ * assert options.remaining == ['*.groovy']
+ * assert options.all && options.longFormat && options.time
+ * </pre>
+ */
+class CliBuilder {
+
+    /**
+     * Usage summary displayed as the first line when <code>cli.usage()</code> is called.
+     */
+    String usage = 'groovy'
+
+    /**
+     * Normally set internally but allows you full customisation of the underlying processing engine.
+     */
+    CommandLineParser parser = null
+
+    /**
+     * To change from the default PosixParser to the GnuParser, set this to false. Ignored if the parser is explicitly set.
+     * @deprecated use the parser option instead with an instance of your preferred parser
+     */
+    @Deprecated
+    Boolean posix = null
+
+    /**
+     * Whether arguments of the form '{@code @}<i>filename</i>' will be expanded into the arguments contained within the file named <i>filename</i> (default true).
+     */
+    boolean expandArgumentFiles = true
+
+    /**
+     * Normally set internally but can be overridden if you want to customise how the usage message is displayed.
+     */
+    HelpFormatter formatter = new HelpFormatter()
+
+    /**
+     * Defaults to stdout but you can provide your own PrintWriter if desired.
+     */
+    PrintWriter writer = new PrintWriter(System.out)
+
+    /**
+     * Optional additional message for usage; displayed after the usage summary but before the options are displayed.
+     */
+    String header = ''
+
+    /**
+     * Optional additional message for usage; displayed after the options are displayed.
+     */
+    String footer = ''
+
+    /**
+     * Indicates that option processing should continue for all arguments even
+     * if arguments not recognized as options are encountered (default true).
+     */
+    boolean stopAtNonOption = true
+
+    /**
+     * Allows customisation of the usage message width.
+     */
+    int width = HelpFormatter.DEFAULT_WIDTH
+
+    /**
+     * Not normally accessed directly but full access to underlying options if needed.
+     */
+    Options options = new Options()
+
+    Map<String, TypedOption> savedTypeOptions = new HashMap<String, TypedOption>()
+
+    public <T> TypedOption<T> option(Map args, Class<T> type, String description) {
+        def name = args.opt ?: '_'
+        args.type = type
+        args.remove('opt')
+        "$name"(args, description)
+    }
+
+    /**
+     * Internal method: Detect option specification method calls.
+     */
+    def invokeMethod(String name, Object args) {
+        if (args instanceof Object[]) {
+            if (args.size() == 1 && (args[0] instanceof String || args[0] instanceof GString)) {
+                def option = option(name, [:], args[0])
+                options.addOption(option)
+
+                return create(option, null, null, null)
+            }
+            if (args.size() == 1 && args[0] instanceof CliOption && name == 'leftShift') {
+                CliOption option = args[0]
+                options.addOption(option)
+                return create(option, null, null, null)
+            }
+            if (args.size() == 2 && args[0] instanceof Map) {
+                def convert = args[0].remove('convert')
+                def type = args[0].remove('type')
+                def defaultValue = args[0].remove('defaultValue')
+                if (type && !(type instanceof Class)) {
+                    throw new CliBuilderException("'type' must be a Class")
+                }
+                if ((convert || type) && !args[0].containsKey('args') &&
+                        type?.simpleName?.toLowerCase() != 'boolean') {
+                    args[0].args = 1
+                }
+                def option = option(name, args[0], args[1])
+                options.addOption(option)
+                return create(option, type, defaultValue, convert)
+            }
+        }
+        return InvokerHelper.getMetaClass(this).invokeMethod(this, name, args)
+    }
+
+    /**
+     * Make options accessible from command line args with parser.
+     * Returns null on bad command lines after displaying usage message.
+     */
+    OptionAccessor parse(args) {
+        if (expandArgumentFiles) args = expandArgumentFiles(args)
+        if (!parser) {
+            parser = posix != null && posix == false ? new GnuParser() : new DefaultParser()
+        }
+        try {
+            def accessor = new OptionAccessor(
+                    parser.parse(options, args as String[], stopAtNonOption))
+            accessor.savedTypeOptions = savedTypeOptions
+            return accessor
+        } catch (ParseException pe) {
+            writer.println("error: " + pe.message)
+            usage()
+            return null
+        }
+    }
+
+    /**
+     * Print the usage message with writer (default: System.out) and formatter (default: HelpFormatter)
+     */
+    void usage() {
+        formatter.printHelp(writer, width, usage, header, options, HelpFormatter.DEFAULT_LEFT_PAD, HelpFormatter.DEFAULT_DESC_PAD, footer)
+        writer.flush()
+    }
+
+    /**
+     * Given an interface containing members with annotations, derive
+     * the options specification.
+     *
+     * @param optionsClass
+     * @param args
+     * @return an instance containing the processed options
+     */
+    public <T> T parseFromSpec(Class<T> optionsClass, String[] args) {
+        addOptionsFromAnnotations(optionsClass, false)
+        def cli = parse(args)
+        def cliOptions = [:]
+        setOptionsFromAnnotations(cli, optionsClass, cliOptions, false)
+        cliOptions as T
+    }
+
+    /**
+     * Given an instance containing members with annotations, derive
+     * the options specification.
+     *
+     * @param optionInstance
+     * @param args
+     * @return the options instance populated with the processed options
+     */
+    public <T> T parseFromInstance(T optionInstance, args) {
+        addOptionsFromAnnotations(optionInstance.getClass(), true)
+        def cli = parse(args)
+        setOptionsFromAnnotations(cli, optionInstance.getClass(), optionInstance, true)
+        optionInstance
+    }
+
+    void addOptionsFromAnnotations(Class optionClass, boolean namesAreSetters) {
+        optionClass.methods.findAll{ it.getAnnotation(Option) }.each { Method m ->
+            Annotation annotation = m.getAnnotation(Option)
+            def typedOption = processAddAnnotation(annotation, m, namesAreSetters)
+            options.addOption(typedOption.cliOption)
+        }
+
+        def optionFields = optionClass.declaredFields.findAll { it.getAnnotation(Option) }
+        if (optionClass.isInterface() && !optionFields.isEmpty()) {
+            throw new CliBuilderException("@Option only allowed on methods in interface " + optionClass.simpleName)
+        }
+        optionFields.each { Field f ->
+            Annotation annotation = f.getAnnotation(Option)
+            String setterName = "set" + MetaClassHelper.capitalize(f.getName());
+            Method m = optionClass.getMethod(setterName, f.getType())
+            def typedOption = processAddAnnotation(annotation, m, true)
+            options.addOption(typedOption.cliOption)
+        }
+    }
+
+    private TypedOption processAddAnnotation(Option annotation, Method m, boolean namesAreSetters) {
+        String shortName = annotation.shortName()
+        String description = annotation.description()
+        String defaultValue = annotation.defaultValue()
+        char valueSeparator = 0
+        if (annotation.valueSeparator()) valueSeparator = annotation.valueSeparator() as char
+        boolean optionalArg = annotation.optionalArg()
+        Integer numberOfArguments = annotation.numberOfArguments()
+        String numberOfArgumentsString = annotation.numberOfArgumentsString()
+        Class convert = annotation.convert()
+        if (convert == Undefined.CLASS) {
+            convert = null
+        }
+        Map names = calculateNames(annotation.longName(), 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 = 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)
+        if (type) {
+            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)
+            }
+            if (type.isArray()) {
+                builder.optionalArg(optionalArg)
+            }
+        }
+        def typedOption = create(builder.build(), convert ? null : type, defaultValue, convert)
+        typedOption
+    }
+
+    private TypedOption create(CliOption o, Class theType, defaultValue, convert) {
+        Map<String, Object> result = new TypedOption<Object>()
+        o.with {
+            if (opt != null) result.put("opt", opt)
+            result.put("longOpt", longOpt)
+            result.put("cliOption", o)
+            if (defaultValue) {
+                result.put("defaultValue", defaultValue)
+            }
+            if (convert) {
+                if (theType) {
+                    throw new CliBuilderException("You can't specify 'type' when using 'convert'")
+                }
+                result.put("convert", convert)
+                result.put("type", convert instanceof Class ? convert : convert.getClass())
+            } else {
+                result.put("type", theType)
+            }
+        }
+        savedTypeOptions[o.longOpt ?: o.opt] = result
+        result
+    }
+
+    def setOptionsFromAnnotations(def cli, Class optionClass, Object t, boolean namesAreSetters) {
+        optionClass.methods.findAll{ it.getAnnotation(Option) }.each { Method m ->
+            Annotation annotation = m.getAnnotation(Option)
+            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())
+            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 ->
+            processSetRemaining(m, remaining, t, cli, namesAreSetters)
+        }
+        optionClass.declaredFields.findAll{ it.getAnnotation(Unparsed) }.each { Field f ->
+            String setterName = "set" + MetaClassHelper.capitalize(f.getName());
+            Method m = optionClass.getMethod(setterName, f.getType())
+            processSetRemaining(m, remaining, t, cli, namesAreSetters)
+        }
+    }
+
+    private void processSetRemaining(Method m, remaining, Object t, cli, boolean namesAreSetters) {
+        def resultType = namesAreSetters ? m.parameterTypes[0] : m.returnType
+        def isTyped = resultType?.isArray()
+        def result
+        def type = null
+        if (isTyped) {
+            type = resultType.componentType
+            result = remaining.collect{ cli.getValue(type, it, null) }
+        } else {
+            result = remaining.toList()
+        }
+        if (namesAreSetters) {
+            m.invoke(t, isTyped ? [result.toArray(Array.newInstance(type, result.size()))] as Object[] : result)
+        } else {
+            Map names = calculateNames("", "", m, namesAreSetters)
+            t.put(names.long, { -> result })
+        }
+    }
+
+    private void processSetAnnotation(Method m, Object t, String name, cli, boolean namesAreSetters) {
+        def conv = savedTypeOptions[name]?.convert
+        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) {
+            def isBoolArg = m.parameterTypes.size() > 0 && m.parameterTypes[0].simpleName.toLowerCase() == 'boolean'
+            boolean isFlag = (isBoolArg && !hasArg) || noArg
+            if (cli.hasOption(name) || isFlag || cli.defaultValue(name)) {
+                m.invoke(t, [isFlag ? cli.hasOption(name) :
+                                     cli.hasOption(name) ? optionValue(cli, name) : cli.defaultValue(name)] as Object[])
+            }
+        } else {
+            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 : cli.defaultValue(name) })
+        }
+    }
+
+    private optionValue(cli, String name) {
+        if (savedTypeOptions.containsKey(name)) {
+            return cli.getOptionValue(savedTypeOptions[name])
+        }
+        cli[name]
+    }
+
+    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))
+            }
+        }
+        [long: useShort ? "" : result, short: (useShort && !shortName) ? result : shortName]
+    }
+
+    // implementation details -------------------------------------
+
+    /**
+     * Internal method: How to create an option from the specification.
+     */
+    CliOption option(shortname, Map details, info) {
+        CliOption option
+        if (shortname == '_') {
+            option = CliOption.builder().desc(info).longOpt(details.longOpt).build()
+            details.remove('longOpt')
+        } else {
+            option = new CliOption(shortname, info)
+        }
+        adjustDetails(details).each { key, value ->
+            option[key] = value
+        }
+        return option
+    }
+
+    static Map adjustDetails(Map m) {
+        m.collectMany { k, v ->
+            if (k == 'args' && v == '+') {
+                [[args: org.apache.commons.cli.Option.UNLIMITED_VALUES]]
+            } else if (k == 'args' && v == '*') {
+                [[args: org.apache.commons.cli.Option.UNLIMITED_VALUES,
+                  optionalArg: true]]
+            } else if (k == 'args' && v instanceof String) {
+                [[args: Integer.parseInt(v)]]
+            } else {
+                [[(k): v]]
+            }
+        }.sum()
+    }
+
+    static expandArgumentFiles(args) throws IOException {
+        def result = []
+        for (arg in args) {
+            if (arg && arg != '@' && arg[0] == '@') {
+                arg = arg.substring(1)
+                if (arg[0] != '@') {
+                    expandArgumentFile(arg, result)
+                    continue
+                }
+            }
+            result << arg
+        }
+        return result
+    }
+
+    private static expandArgumentFile(name, args) throws IOException {
+        def charAsInt = { String s -> s.toCharacter() as int }
+        new File(name).withReader { r ->
+            new StreamTokenizer(r).with {
+                resetSyntax()
+                wordChars(charAsInt(' '), 255)
+                whitespaceChars(0, charAsInt(' '))
+                commentChar(charAsInt('#'))
+                quoteChar(charAsInt('"'))
+                quoteChar(charAsInt('\''))
+                while (nextToken() != StreamTokenizer.TT_EOF) {
+                    args << sval
+                }
+            }
+        }
+    }
+
+}
+
+class OptionAccessor {
+    CommandLine commandLine
+    Map<String, TypedOption> savedTypeOptions
+
+    OptionAccessor(CommandLine commandLine) {
+        this.commandLine = commandLine
+    }
+
+    boolean hasOption(TypedOption typedOption) {
+        commandLine.hasOption(typedOption.longOpt ?: typedOption.opt)
+    }
+
+    public <T> T defaultValue(String name) {
+        Class<T> type = savedTypeOptions[name]?.type
+        String value = savedTypeOptions[name]?.defaultValue() ? savedTypeOptions[name].defaultValue() : null
+        return (T) value ? getTypedValue(type, name, value) : null
+    }
+
+    public <T> T getOptionValue(TypedOption<T> typedOption) {
+        getOptionValue(typedOption, null)
+    }
+
+    public <T> T getOptionValue(TypedOption<T> typedOption, T defaultValue) {
+        String optionName = (String) typedOption.longOpt ?: typedOption.opt
+        if (commandLine.hasOption(optionName)) {
+            if (typedOption.containsKey('type') && typedOption.type.isArray()) {
+                def compType = typedOption.type.componentType
+                return (T) getTypedValuesFromName(optionName, compType)
+            }
+            return getTypedValueFromName(optionName)
+        }
+        return defaultValue
+    }
+
+    private <T> T[] getTypedValuesFromName(String optionName, Class<T> compType) {
+        CliOption option = commandLine.options.find{ it.longOpt == optionName }
+        T[] result = null
+        if (option) {
+            int count = 0
+            def optionValues = commandLine.getOptionValues(optionName)
+            for (String optionValue : optionValues) {
+                if (result == null) {
+                    result = (T[]) Array.newInstance(compType, optionValues.length)
+                }
+                result[count++] = (T) getTypedValue(compType, optionName, optionValue)
+            }
+        }
+        if (result == null) {
+            result = (T[]) Array.newInstance(compType, 0)
+        }
+        return result
+    }
+
+    public <T> T getAt(TypedOption<T> typedOption) {
+        getAt(typedOption, null)
+    }
+
+    public <T> T getAt(TypedOption<T> typedOption, T defaultValue) {
+        String optionName = (String) typedOption.longOpt ?: typedOption.opt
+        if (savedTypeOptions.containsKey(optionName)) {
+            return getTypedValueFromName(optionName)
+        }
+        return defaultValue
+    }
+
+    private <T> T getTypedValueFromName(String optionName) {
+        Class type = savedTypeOptions[optionName].type
+        String optionValue = commandLine.getOptionValue(optionName)
+        return (T) getTypedValue(type, optionName, optionValue)
+    }
+
+    private <T> T getTypedValue(Class<T> type, String optionName, String optionValue) {
+        if (savedTypeOptions[optionName]?.cliOption?.numberOfArgs == 0) {
+            return (T) commandLine.hasOption(optionName)
+        }
+        def convert = savedTypeOptions[optionName]?.convert
+        return getValue(type, optionValue, convert)
+    }
+
+    private <T> T getValue(Class<T> type, String optionValue, Closure convert) {
+        if (!type) {
+            return (T) optionValue
+        }
+        if (Closure.isAssignableFrom(type) && convert) {
+            return (T) convert(optionValue)
+        }
+        if (type?.simpleName?.toLowerCase() == 'boolean') {
+            return (T) Boolean.parseBoolean(optionValue)
+        }
+        StringGroovyMethods.asType(optionValue, (Class<T>) type)
+    }
+
+    def invokeMethod(String name, Object args) {
+        return InvokerHelper.getMetaClass(commandLine).invokeMethod(commandLine, name, args)
+    }
+
+    def getProperty(String name) {
+        if (!savedTypeOptions.containsKey(name)) {
+            def alt = savedTypeOptions.find{ it.value.opt == name }
+            if (alt) name = alt.key
+        }
+        def methodname = 'getOptionValue'
+        Class type = savedTypeOptions[name]?.type
+        def foundArray = type?.isArray()
+        if (name.size() > 1 && name.endsWith('s')) {
+            def singularName = name[0..-2]
+            if (commandLine.hasOption(singularName) || foundArray) {
+                name = singularName
+                methodname += 's'
+                type = savedTypeOptions[name]?.type
+            }
+        }
+        if (type?.isArray()) {
+            methodname = 'getOptionValues'
+        }
+        if (name.size() == 1) name = name as char
+        def result = InvokerHelper.getMetaClass(commandLine).invokeMethod(commandLine, methodname, name)
+        if (result != null) {
+            if (result instanceof String[]) {
+                result = result.collect{ type ? getTypedValue(type.isArray() ? type.componentType : type, name, it) : it }
+            } else {
+                if (type) result = getTypedValue(type, name, result)
+            }
+        } else if (type?.simpleName != 'boolean' && savedTypeOptions[name]?.defaultValue) {
+            result = getTypedValue(type, name, savedTypeOptions[name].defaultValue)
+        } else {
+            result = commandLine.hasOption(name)
+        }
+        return result
+    }
+
+    List<String> arguments() {
+        commandLine.args.toList()
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/00cc9e5e/subprojects/groovy-cli-commons/src/test/groovy/groovy/util/CliBuilderTest.groovy
----------------------------------------------------------------------
diff --git a/subprojects/groovy-cli-commons/src/test/groovy/groovy/util/CliBuilderTest.groovy b/subprojects/groovy-cli-commons/src/test/groovy/groovy/util/CliBuilderTest.groovy
new file mode 100644
index 0000000..938c79a
--- /dev/null
+++ b/subprojects/groovy-cli-commons/src/test/groovy/groovy/util/CliBuilderTest.groovy
@@ -0,0 +1,707 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.util
+
+import groovy.cli.Option
+import groovy.cli.Unparsed
+import groovy.transform.ToString
+import groovy.transform.TypeChecked
+import org.apache.commons.cli.BasicParser
+import org.apache.commons.cli.DefaultParser
+import org.apache.commons.cli.GnuParser
+import org.codehaus.groovy.cli.GroovyPosixParser
+
+import java.math.RoundingMode
+
+import static org.apache.commons.cli.Option.UNLIMITED_VALUES
+import static org.apache.commons.cli.Option.builder
+
+/**
+ * Test class for the CliBuilder.
+ * <p>
+ * Commons CLI has a long history of different parsers with slightly differing behavior and bugs.
+ * In nearly all cases, we now recommend using DefaultParser. In case you have very unique circumstances
+ * and really need behavior that can only be supplied by one of the legacy parsers, we also include
+ * some test case runs against some of the legacy parsers.
+ */
+
+class CliBuilderTest extends GroovyTestCase {
+
+    private StringWriter stringWriter
+    private PrintWriter printWriter
+
+    void setUp() {
+        resetPrintWriter()
+    }
+
+    private final expectedParameter = 'ASCII'
+    private final usageString = 'groovy [option]* filename'
+
+    private void runSample(parser, optionList) {
+        resetPrintWriter()
+        def cli = new CliBuilder(usage: usageString, writer: printWriter, parser: parser)
+        cli.h(longOpt: 'help', 'usage information')
+        cli.c(argName: 'charset', args: 1, longOpt: 'encoding', 'character encoding')
+        cli.i(argName: 'extension', optionalArg: true, 'modify files in place, create backup if extension is given (e.g. \'.bak\')')
+        def stringified = cli.options.toString()
+        assert stringified =~ /i=\[ option: i  :: modify files in place, create backup if extension is given/
+        assert stringified =~ /c=\[ option: c encoding  \[ARG] :: character encoding/
+        assert stringified =~ /h=\[ option: h help  :: usage information/
+        assert stringified =~ /encoding=\[ option: c encoding  \[ARG] :: character encoding/
+        assert stringified =~ /help=\[ option: h help  :: usage information/
+        def options = cli.parse(optionList)
+        assert options.hasOption('h')
+        assert options.hasOption('help')
+        assert options.h
+        assert options.help
+        if (options.h) { cli.usage() }
+        def expectedUsage = """usage: $usageString
+ -c,--encoding <charset>   character encoding
+ -h,--help                 usage information
+ -i                        modify files in place, create backup if
+                           extension is given (e.g. '.bak')"""
+        assertEquals(expectedUsage, stringWriter.toString().tokenize('\r\n').join('\n'))
+        resetPrintWriter()
+        cli.writer = printWriter
+        if (options.help) { cli.usage() }
+        assertEquals(expectedUsage, stringWriter.toString().tokenize('\r\n').join('\n'))
+        assert options.hasOption('c')
+        assert options.c
+        assert options.hasOption('encoding')
+        assert options.encoding
+        assertEquals(expectedParameter, options.getOptionValue('c'))
+        assertEquals(expectedParameter, options.c)
+        assertEquals(expectedParameter, options.getOptionValue('encoding'))
+        assertEquals(expectedParameter, options.encoding)
+        assertEquals(false, options.noSuchOptionGiven)
+        assertEquals(false, options.hasOption('noSuchOptionGiven'))
+        assertEquals(false, options.x)
+        assertEquals(false, options.hasOption('x'))
+    }
+
+    private void resetPrintWriter() {
+        stringWriter = new StringWriter()
+        printWriter = new PrintWriter(stringWriter)
+    }
+
+    void testSampleShort() {
+        [new DefaultParser(), new GroovyPosixParser(), new GnuParser(), new BasicParser()].each { parser ->
+            runSample(parser, ['-h', '-c', expectedParameter])
+        }
+    }
+
+    void testSampleLong() {
+        [new DefaultParser(), new GroovyPosixParser(), new GnuParser(), new BasicParser()].each { parser ->
+            runSample(parser, ['--help', '--encoding', expectedParameter])
+        }
+    }
+
+    void testSimpleArg() {
+        [new DefaultParser(), new GroovyPosixParser(), new GnuParser(), new BasicParser()].each { parser ->
+            def cli = new CliBuilder(parser: parser)
+            cli.a([:], '')
+            def options = cli.parse(['-a', '1', '2'])
+            assertEquals(['1', '2'], options.arguments())
+        }
+    }
+
+    void testMultipleArgs() {
+        [new DefaultParser(), new GroovyPosixParser(), new GnuParser(), new BasicParser()].each { parser ->
+            def cli = new CliBuilder(parser: parser)
+            cli.a(longOpt: 'arg', args: 2, valueSeparator: ',' as char, 'arguments')
+            def options = cli.parse(['-a', '1,2'])
+            assertEquals('1', options.a)
+            assertEquals(['1', '2'], options.as)
+            assertEquals('1', options.arg)
+            assertEquals(['1', '2'], options.args)
+        }
+    }
+
+    void testFailedParsePrintsUsage() {
+        def cli = new CliBuilder(writer: printWriter)
+        cli.x(required: true, 'message')
+        cli.parse([])
+        // NB: This test is very fragile and is bound to fail on different locales and versions of commons-cli... :-(
+        assert stringWriter.toString().normalize() == '''error: Missing required option: x
+usage: groovy
+ -x   message
+'''
+    }
+
+    void testLongOptsOnly_nonOptionShouldStopArgProcessing() {
+        [new DefaultParser(), new GroovyPosixParser(), new GnuParser()].each { parser ->
+            def cli = new CliBuilder(parser: parser)
+            def anOption = builder().longOpt('anOption').hasArg().desc('An option.')
+                    .build()
+            cli.options.addOption(anOption)
+            def options = cli.parse(['-v', '--anOption', 'something'])
+            // no options should be found
+            assert options.getOptionValue('anOption') == null
+            assert !options.anOption
+            assert !options.v
+            // arguments should be still sitting there
+            assert options.arguments() == ['-v', '--anOption', 'something']
+        }
+    }
+
+    void testLongAndShortOpts_allOptionsValid() {
+        [new DefaultParser(), new GroovyPosixParser(), new GnuParser(), new BasicParser()].each { parser ->
+            def cli = new CliBuilder(parser: parser)
+            def anOption = builder().longOpt('anOption').hasArg().desc('An option.').build()
+            cli.options.addOption(anOption)
+            cli.v(longOpt: 'verbose', 'verbose mode')
+            def options = cli.parse(['-v', '--anOption', 'something'])
+            assert options.v
+            assert options.getOptionValue('anOption') == 'something'
+            assert options.anOption == 'something'
+            assert !options.arguments()
+        }
+    }
+
+    void testUnrecognizedOptions() {
+        [new DefaultParser(), new GroovyPosixParser(), new GnuParser(), new BasicParser()].each { parser ->
+            def cli = new CliBuilder(parser: parser)
+            cli.v(longOpt: 'verbose', 'verbose mode')
+            def options = cli.parse(['-x', '-yyy', '--zzz', 'something'])
+            assertEquals(['-x', '-yyy', '--zzz', 'something'], options.arguments())
+        }
+    }
+
+    void testMultipleOccurrencesSeparateSeparate() {
+        [new DefaultParser(), new GroovyPosixParser(), new GnuParser(), new BasicParser()].each { parser ->
+            def cli = new CliBuilder(parser: parser)
+            cli.a(longOpt: 'arg', args: UNLIMITED_VALUES, 'arguments')
+            def options = cli.parse(['-a', '1', '-a', '2', '-a', '3'])
+            assertEquals('1', options.a)
+            assertEquals(['1', '2', '3'], options.as)
+            assertEquals('1', options.arg)
+            assertEquals(['1', '2', '3'], options.args)
+            assertEquals([], options.arguments())
+        }
+    }
+
+    void testMultipleOccurrencesSeparateJuxtaposed() {
+        [new DefaultParser(), new GroovyPosixParser(), new GnuParser()].each { parser ->
+            def cli = new CliBuilder(parser: parser)
+            //cli.a ( longOpt : 'arg' , args : UNLIMITED_VALUES , 'arguments' )
+            cli.a(longOpt: 'arg', args: 1, 'arguments')
+            def options = cli.parse(['-a1', '-a2', '-a3'])
+            assertEquals('1', options.a)
+            assertEquals(['1', '2', '3'], options.as)
+            assertEquals('1', options.arg)
+            assertEquals(['1', '2', '3'], options.args)
+            assertEquals([], options.arguments())
+        }
+    }
+
+    void testMultipleOccurrencesTogetherSeparate() {
+        [new DefaultParser(), new GroovyPosixParser(), new GnuParser()].each { parser ->
+            def cli = new CliBuilder(parser: parser)
+            cli.a(longOpt: 'arg', args: UNLIMITED_VALUES, valueSeparator: ',' as char, 'arguments')
+            def options = cli.parse(['-a 1,2,3'])
+            assertEquals(' 1', options.a)
+            assertEquals([' 1', '2', '3'], options.as)
+            assertEquals(' 1', options.arg)
+            assertEquals([' 1', '2', '3'], options.args)
+            assertEquals([], options.arguments())
+        }
+    }
+
+    void testMultipleOccurrencesTogetherJuxtaposed() {
+        [new DefaultParser(), new GroovyPosixParser(), new GnuParser()].each { parser ->
+            def cli1 = new CliBuilder(parser: parser)
+            cli1.a(longOpt: 'arg', args: UNLIMITED_VALUES, valueSeparator: ',' as char, 'arguments')
+            def options = cli1.parse(['-a1,2,3'])
+            assertEquals('1', options.a)
+            assertEquals(['1', '2', '3'], options.as)
+            assertEquals('1', options.arg)
+            assertEquals(['1', '2', '3'], options.args)
+            assertEquals([], options.arguments()) }
+        }
+
+    /*
+    *  Behaviour with unrecognized options.
+    *
+    *  TODO: Should add the BasicParser here as well?
+    */
+
+    void testUnrecognizedOptionSilentlyIgnored_GnuParser() {
+        def cli = new CliBuilder(usage: usageString, writer: printWriter, parser: new GnuParser())
+        def options = cli.parse(['-v'])
+        assertEquals('''''', stringWriter.toString().tokenize('\r\n').join('\n'))
+        assert !options.v
+    }
+
+    private void checkNoOutput() {
+        assert stringWriter.toString().tokenize('\r\n').join('\n') == ''''''
+    }
+
+    void testUnrecognizedOptionSilentlyIgnored_DefaultParser() {
+        def cli = new CliBuilder(usage: usageString, writer: printWriter, parser: new DefaultParser())
+        def options = cli.parse(['-v'])
+        checkNoOutput()
+        assert !options.v
+    }
+
+    void testUnrecognizedOptionTerminatesParse_GnuParser() {
+        def cli = new CliBuilder(usage: usageString, writer: printWriter, parser: new GnuParser())
+        cli.h(longOpt: 'help', 'usage information')
+        def options = cli.parse(['-v', '-h'])
+        checkNoOutput()
+        assert !options.v
+        assert !options.h
+        assertEquals(['-v', '-h'], options.arguments())
+    }
+
+    void testUnrecognizedOptionTerminatesParse_DefaultParser() {
+        def cli = new CliBuilder(usage: usageString, writer: printWriter, parser: new DefaultParser())
+        cli.h(longOpt: 'help', 'usage information')
+        def options = cli.parse(['-v', '-h'])
+        checkNoOutput()
+        assert !options.v
+        assert !options.h
+        assertEquals(['-v', '-h'], options.arguments())
+    }
+
+    void testMultiCharShortOpt() {
+        [new DefaultParser(), new GroovyPosixParser(), new GnuParser()].each { parser ->
+            def cli = new CliBuilder(writer: printWriter, parser: parser)
+            cli.abc('abc option')
+            cli.def(longOpt: 'defdef', 'def option')
+            def options = cli.parse(['-abc', '--defdef', 'ghi'])
+            assert options
+            assert options.arguments() == ['ghi']
+            assert options.abc && options.def && options.defdef
+            checkNoOutput()
+        }
+    }
+
+    void testArgumentBursting_DefaultParserOnly() {
+        def cli = new CliBuilder(writer: printWriter)
+        // must not have longOpt 'abc' and also no args for a or b
+        cli.a('a')
+        cli.b('b')
+        cli.c('c')
+        def options = cli.parse(['-abc', '-d'])
+        assert options
+        assert options.arguments() == ['-d']
+        assert options.a && options.b && options.c && !options.d
+        checkNoOutput()
+    }
+
+    void testLongOptEndingWithS() {
+        def cli = new CliBuilder()
+        cli.s(longOpt: 'number_of_seconds', 'a long arg that ends with an "s"')
+
+        def options = cli.parse(['-s'])
+
+        assert options.hasOption('s')
+        assert options.hasOption('number_of_seconds')
+        assert options.s
+        assert options.number_of_seconds
+    }
+
+    void testArgumentFileExpansion() {
+        def cli = new CliBuilder(usage: 'test usage')
+        cli.h(longOpt: 'help', 'usage information')
+        cli.d(longOpt: 'debug', 'turn on debug info')
+        def args = ['-h', '@temp.args', 'foo', '@@baz']
+        def temp = new File('temp.args')
+        temp.deleteOnExit()
+        temp.text = '-d bar'
+        def options = cli.parse(args)
+        assert options.h
+        assert options.d
+        assert options.arguments() == ['bar', 'foo', '@baz']
+    }
+
+    void testArgumentFileExpansionArgOrdering() {
+        def cli = new CliBuilder(usage: 'test usage')
+        def args = ['one', '@temp1.args', 'potato', '@temp2.args', 'four']
+        def temp1 = new File('temp1.args')
+        temp1.deleteOnExit()
+        temp1.text = 'potato two'
+        def temp2 = new File('temp2.args')
+        temp2.deleteOnExit()
+        temp2.text = 'three potato'
+        def options = cli.parse(args)
+        assert options.arguments() == 'one potato two potato three potato four'.split()
+    }
+
+    void testArgumentFileExpansionTurnedOff() {
+        def cli = new CliBuilder(usage: 'test usage', expandArgumentFiles:false)
+        cli.h(longOpt: 'help', 'usage information')
+        cli.d(longOpt: 'debug', 'turn on debug info')
+        def args = ['-h', '@temp.args', 'foo', '@@baz']
+        def temp = new File('temp.args')
+        temp.deleteOnExit()
+        temp.text = '-d bar'
+        def options = cli.parse(args)
+        assert options.h
+        assert !options.d
+        assert options.arguments() == ['@temp.args', 'foo', '@@baz']
+    }
+
+    void testGStringSpecification_Groovy4621() {
+        def user = 'scott'
+        def pass = 'tiger'
+        def ignore = false
+        def longOptName = 'user'
+        def cli = new CliBuilder(usage: 'blah')
+        cli.dbusername(longOpt:"$longOptName", args: 1, "Database username [default $user]")
+        cli.dbpassword(args: 1, "Database password [default $pass]")
+        cli.i("ignore case [default $ignore]")
+        def args = ['-dbpassword', 'foo', '--user', 'bar', '-i']
+        def options = cli.parse(args)
+        assert options.user == 'bar'
+        assert options.dbusername == 'bar'
+        assert options.dbpassword == 'foo'
+        assert options.i
+    }
+
+    void testNoExpandArgsWithEmptyArg() {
+        def cli = new CliBuilder(expandArgumentFiles: false)
+        cli.parse(['something', ''])
+    }
+
+    void testExpandArgsWithEmptyArg() {
+        def cli = new CliBuilder(expandArgumentFiles: true)
+        cli.parse(['something', ''])
+    }
+
+    void testDoubleHyphenShortOptions() {
+        def cli = new CliBuilder()
+        cli.a([:], '')
+        cli.b([:], '')
+        def options = cli.parse(['-a', '--', '-b', 'foo'])
+        assert options.arguments() == ['-b', 'foo']
+    }
+
+    void testDoubleHyphenLongOptions() {
+        def cli = new CliBuilder()
+        cli._([longOpt:'alpha'], '')
+        cli._([longOpt:'beta'], '')
+        def options = cli.parse(['--alpha', '--', '--beta', 'foo'])
+        assert options.alpha
+        assert options.arguments() == ['--beta', 'foo']
+    }
+
+    void testMixedShortAndLongOptions() {
+        def cli = new CliBuilder()
+        cli.a([longOpt:'alpha', args:1], '')
+        cli.b([:], '')
+        def options = cli.parse(['-b', '--alpha', 'param', 'foo'])
+        assert options.a == 'param'
+        assert options.arguments() == ['foo']
+    }
+
+    void testMixedBurstingAndLongOptions() {
+        def cli = new CliBuilder()
+        cli.a([:], '')
+        cli.b([:], '')
+        cli.c([:], '')
+        cli.d([longOpt:'abacus'], '')
+        def options = cli.parse(['-abc', 'foo'])
+        assert options.a
+        assert options.b
+        assert options.c
+        assert options.arguments() == ['foo']
+        options = cli.parse(['-abacus', 'foo'])
+        assert !options.a
+        assert !options.b
+        assert !options.c
+        assert options.d
+        assert options.arguments() == ['foo']
+    }
+
+    interface PersonI {
+        @Option String first()
+        @Option String last()
+        @Option boolean flag1()
+        @Option Boolean flag2()
+        @Option(longName = 'specialFlag') Boolean flag3()
+        @Option flag4()
+        @Option int age()
+        @Option Integer born()
+        @Option float discount()
+        @Option BigDecimal pi()
+        @Option File biography()
+        @Option RoundingMode roundingMode()
+        @Unparsed List remaining()
+    }
+
+    def argz = "--first John --last Smith --flag1 --flag2 --specialFlag --age  21 --born 1980 --discount 3.5 --pi 3.14159 --biography cv.txt --roundingMode DOWN and some more".split()
+
+    void testParseFromSpec() {
+        def builder1 = new CliBuilder()
+        def p1 = builder1.parseFromSpec(PersonI, argz)
+        assert p1.first() == 'John'
+        assert p1.last() == 'Smith'
+        assert p1.flag1()
+        assert p1.flag2()
+        assert p1.flag3()
+        assert !p1.flag4()
+        assert p1.born() == 1980
+        assert p1.age() == 21
+        assert p1.discount() == 3.5f
+        assert p1.pi() == 3.14159
+        assert p1.biography() == new File('cv.txt')
+        assert p1.roundingMode() == RoundingMode.DOWN
+        assert p1.remaining() == ['and', 'some', 'more']
+    }
+
+    @ToString(includeFields=true, includePackage=false)
+    class PersonC {
+        @Option String first
+        private String last
+        @Option boolean flag1
+        private Boolean flag2
+        private Boolean flag3
+        private Boolean flag4
+        private int age
+        private Integer born
+        private float discount
+        private BigDecimal pi
+        private File biography
+        private RoundingMode roundingMode
+        private List remaining
+
+        @Option void setLast(String last) {
+            this.last = last
+        }
+        @Option void setFlag2(boolean flag2) {
+            this.flag2 = flag2
+        }
+        @Option(longName = 'specialFlag') void setFlag3(boolean flag3) {
+            this.flag3 = flag3
+        }
+        @Option void setFlag4(boolean flag4) {
+            this.flag4 = flag4
+        }
+        @Option void setAge(int age) {
+            this.age = age
+        }
+        @Option void setBorn(Integer born) {
+            this.born = born
+        }
+        @Option void setDiscount(float discount) {
+            this.discount = discount
+        }
+        @Option void setPi(BigDecimal pi) {
+            this.pi = pi
+        }
+        @Option void setBiography(File biography) {
+            this.biography = biography
+        }
+        @Option void setRoundingMode(RoundingMode roundingMode) {
+            this.roundingMode = roundingMode
+        }
+        @Unparsed void setRemaining(List remaining) {
+            this.remaining = remaining
+        }
+    }
+    class DefaultValueC {
+        @Option(shortName='f', defaultValue='one') String from
+        @Option(shortName='t', defaultValue='35') int to
+        @Option(shortName='b') int by = 1
+    }
+
+    void testDefaultValueClass() {
+        def cli = new CliBuilder()
+        def options = new DefaultValueC()
+        cli.parseFromInstance(options, '-f two'.split())
+        assert options.from == 'two'
+        assert options.to == 35
+        assert options.by == 1
+
+        options = new DefaultValueC()
+        cli.parseFromInstance(options, '-t 45 --by 2'.split())
+        assert options.from == 'one'
+        assert options.to == 45
+        assert options.by == 2
+    }
+
+    class ValSepC {
+        @Option(numberOfArguments=2) String[] a
+        @Option(numberOfArgumentsString='2', valueSeparator=',') String[] b
+        @Option(numberOfArgumentsString='+', valueSeparator=',') String[] c
+        @Unparsed remaining
+    }
+
+    void testValSepClass() {
+        def cli = new CliBuilder()
+
+        def options = new ValSepC()
+        cli.parseFromInstance(options, '-a 1 2 3 4'.split())
+        assert options.a == ['1', '2']
+        assert options.remaining == ['3', '4']
+
+        options = new ValSepC()
+        cli.parseFromInstance(options, '-a1 -a2 3'.split())
+        assert options.a == ['1', '2']
+        assert options.remaining == ['3']
+
+        options = new ValSepC()
+        cli.parseFromInstance(options, ['-b1,2'] as String[])
+        assert options.b == ['1', '2']
+
+        options = new ValSepC()
+        cli.parseFromInstance(options, ['-c', '1'] as String[])
+        assert options.c == ['1']
+
+        options = new ValSepC()
+        cli.parseFromInstance(options, ['-c1'] as String[])
+        assert options.c == ['1']
+
+        options = new ValSepC()
+        cli.parseFromInstance(options, ['-c1,2,3'] as String[])
+        assert options.c == ['1', '2', '3']
+    }
+
+    class WithConvertC {
+        @Option(convert={ it.toLowerCase() }) String a
+        @Option(convert={ it.toUpperCase() }) String b
+        @Option(convert={ Date.parse("yyyy-MM-dd", it) }) Date d
+        @Unparsed List remaining
+    }
+
+    void testConvertClass() {
+        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 = new WithConvertC()
+        cli.parseFromInstance(options, argz)
+        assert options.a == 'john'
+        assert options.b == 'MARY'
+        assert options.d == newYears
+        assert options.remaining == ['and', 'some', 'more']
+    }
+
+    class TypeCheckedC {
+        @Option String name
+        @Option int age
+        @Unparsed List remaining
+    }
+
+    @TypeChecked
+    void testTypeCheckedClass() {
+        def argz = "--name John --age 21 and some more".split()
+        def cli = new CliBuilder()
+        def options = new TypeCheckedC()
+        cli.parseFromInstance(options, argz)
+        String n = options.name
+        int a = options.age
+        assert n == 'John' && a == 21
+        assert options.remaining == ['and', 'some', 'more']
+    }
+
+    void testParseFromInstance() {
+        def p2 = new PersonC()
+        def builder2 = new CliBuilder()
+        builder2.parseFromInstance(p2, argz)
+        // properties show first in toString()
+        assert p2.toString() == 'CliBuilderTest$PersonC(John, true, Smith, true, true, false, 21, 1980, 3.5, 3.14159,' +
+                ' cv.txt, DOWN, [and, some, more])'
+    }
+
+    interface RetTypeI {
+        @Unparsed Integer[] nums()
+    }
+
+    // this feature is incubating
+    void testTypedUnparsedFromSpec() {
+        def argz = '12 34 56'.split()
+        def cli = new CliBuilder()
+        def options = cli.parseFromSpec(RetTypeI, argz)
+        assert options.nums() == [12, 34, 56]
+    }
+
+    class RetTypeC {
+        @Unparsed Integer[] nums
+    }
+
+    // this feature is incubating
+    void testTypedUnparsedFromInstance() {
+        def argz = '12 34 56'.split()
+        def cli = new CliBuilder()
+        def options = new RetTypeC()
+        cli.parseFromInstance(options, argz)
+        assert options.nums == [12, 34, 56]
+    }
+
+    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
+            import java.math.RoundingMode
+            @OptionField String first
+            @OptionField String last
+            @OptionField boolean flag1
+            @OptionField Boolean flag2
+            @OptionField(longName = 'specialFlag') Boolean flag3
+            @OptionField Boolean flag4
+            @OptionField int age
+            @OptionField Integer born
+            @OptionField float discount
+            @OptionField BigDecimal pi
+            @OptionField File biography
+            @OptionField RoundingMode roundingMode
+            @UnparsedField List remaining
+            new CliBuilder().parseFromInstance(this, args)
+            assert first == 'John'
+            assert last == 'Smith'
+            assert flag1
+            assert flag2
+            assert flag3
+            assert !flag4
+            assert born == 1980
+            assert age == 21
+            assert discount == 3.5f
+            assert pi == 3.14159
+            assert biography == new File('cv.txt')
+            assert roundingMode == RoundingMode.DOWN
+            assert remaining == ['and', 'some', 'more']
+        ''', 'CliBuilderTestScript.groovy', argz)
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/00cc9e5e/subprojects/groovy-console/build.gradle
----------------------------------------------------------------------
diff --git a/subprojects/groovy-console/build.gradle b/subprojects/groovy-console/build.gradle
index bc38c53..6362468 100644
--- a/subprojects/groovy-console/build.gradle
+++ b/subprojects/groovy-console/build.gradle
@@ -20,6 +20,7 @@ evaluationDependsOn(':groovy-swing')
 
 dependencies {
     compile rootProject
+    compile project(':groovy-cli-commons')
     compile project(':groovy-swing')
     compile project(':groovy-templates')
     testCompile project(':groovy-test')

http://git-wip-us.apache.org/repos/asf/groovy/blob/00cc9e5e/subprojects/groovy-docgenerator/build.gradle
----------------------------------------------------------------------
diff --git a/subprojects/groovy-docgenerator/build.gradle b/subprojects/groovy-docgenerator/build.gradle
index d6c88c3..77d93f2 100644
--- a/subprojects/groovy-docgenerator/build.gradle
+++ b/subprojects/groovy-docgenerator/build.gradle
@@ -18,6 +18,7 @@
  */
 dependencies {
     compile rootProject
+    compile project(':groovy-cli-commons')
     compile project(':groovy-templates')
     testCompile project(':groovy-test')
     compile "com.thoughtworks.qdox:qdox:$qdoxVersion"

http://git-wip-us.apache.org/repos/asf/groovy/blob/00cc9e5e/subprojects/groovy-groovydoc/build.gradle
----------------------------------------------------------------------
diff --git a/subprojects/groovy-groovydoc/build.gradle b/subprojects/groovy-groovydoc/build.gradle
index 67a7030..3e86d0b 100644
--- a/subprojects/groovy-groovydoc/build.gradle
+++ b/subprojects/groovy-groovydoc/build.gradle
@@ -19,10 +19,10 @@
 dependencies {
     compile rootProject
     testCompile rootProject.sourceSets.test.runtimeClasspath
+    compile project(':groovy-cli-commons')
     compile project(':groovy-templates')
     runtime project(':groovy-dateutil')
     testCompile project(':groovy-test')
-    testCompile project(':groovy-ant')
     testCompile "org.apache.ant:ant-testutil:$antVersion"
 }
 

http://git-wip-us.apache.org/repos/asf/groovy/blob/00cc9e5e/subprojects/groovy-groovysh/build.gradle
----------------------------------------------------------------------
diff --git a/subprojects/groovy-groovysh/build.gradle b/subprojects/groovy-groovysh/build.gradle
index bd2d968..e234781 100644
--- a/subprojects/groovy-groovysh/build.gradle
+++ b/subprojects/groovy-groovysh/build.gradle
@@ -18,6 +18,7 @@
  */
 dependencies {
     compile rootProject
+    compile project(':groovy-cli-commons')
     compile project(':groovy-console')
     testCompile project(':groovy-test')
     compile("jline:jline:$jlineVersion") {

http://git-wip-us.apache.org/repos/asf/groovy/blob/00cc9e5e/subprojects/groovy-test/build.gradle
----------------------------------------------------------------------
diff --git a/subprojects/groovy-test/build.gradle b/subprojects/groovy-test/build.gradle
index 8dfd6ed..9a68ebc 100644
--- a/subprojects/groovy-test/build.gradle
+++ b/subprojects/groovy-test/build.gradle
@@ -19,7 +19,10 @@
 dependencies {
     compile rootProject
     compile 'junit:junit:4.12'
-    testRuntime project(':groovy-ant')
+    // groovy-ant needed for FileNameFinder used in AllTestSuite and JavadocAssertionTestSuite
+    testRuntime(project(':groovy-ant')) {
+        transitive = false
+    }
 }
 
-apply from: "${rootProject.projectDir}/gradle/jacoco/jacocofix.gradle"
\ No newline at end of file
+apply from: "${rootProject.projectDir}/gradle/jacoco/jacocofix.gradle"

Reply | Threaded
Open this post in threaded view
|

[2/2] groovy git commit: move commons-cli CliBuilder into its own subproject

paulk
move commons-cli CliBuilder into its own subproject


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/00cc9e5e
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/00cc9e5e
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/00cc9e5e

Branch: refs/heads/GROOVY_2_6_X
Commit: 00cc9e5e5536b1e9bb2e60c46b086227984e26b5
Parents: 6a19867
Author: Paul King <[hidden email]>
Authored: Sat Apr 14 11:05:35 2018 +1000
Committer: Paul King <[hidden email]>
Committed: Sun Apr 15 18:10:34 2018 +1000

----------------------------------------------------------------------
 gradle/binarycompatibility.gradle               |   2 +-
 settings.gradle                                 |   1 +
 src/main/groovy/groovy/util/CliBuilder.groovy   | 798 -------------------
 src/test/groovy/util/CliBuilderTest.groovy      | 707 ----------------
 subprojects/groovy-cli-commons/build.gradle     |  24 +
 .../main/groovy/groovy/util/CliBuilder.groovy   | 798 +++++++++++++++++++
 .../groovy/groovy/util/CliBuilderTest.groovy    | 707 ++++++++++++++++
 subprojects/groovy-console/build.gradle         |   1 +
 subprojects/groovy-docgenerator/build.gradle    |   1 +
 subprojects/groovy-groovydoc/build.gradle       |   2 +-
 subprojects/groovy-groovysh/build.gradle        |   1 +
 subprojects/groovy-test/build.gradle            |   7 +-
 12 files changed, 1540 insertions(+), 1509 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/00cc9e5e/gradle/binarycompatibility.gradle
----------------------------------------------------------------------
diff --git a/gradle/binarycompatibility.gradle b/gradle/binarycompatibility.gradle
index aa0175a..66b41c2 100644
--- a/gradle/binarycompatibility.gradle
+++ b/gradle/binarycompatibility.gradle
@@ -35,7 +35,7 @@ task checkBinaryCompatibility {
 check.dependsOn(checkBinaryCompatibility)
 
 // for comparing between versions with different modules, set excludeModules to differing modules, e.g.
-def excludeModules = ['groovy-dateutil', 'groovy-datetime', 'performance', 'groovy-macro', 'tests-vm8', 'groovy-json-direct']
+def excludeModules = ['groovy-cli-commons', 'groovy-dateutil', 'groovy-datetime', 'performance', 'groovy-macro', 'tests-vm8', 'groovy-json-direct']
 //def excludeModules = []
 
 Set projectsToCheck = allprojects.findAll{ !(it.name in excludeModules) }

http://git-wip-us.apache.org/repos/asf/groovy/blob/00cc9e5e/settings.gradle
----------------------------------------------------------------------
diff --git a/settings.gradle b/settings.gradle
index 9ad6235..9348605 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -18,6 +18,7 @@
  */
 def subprojects = ['groovy-ant',
         'groovy-bsf',
+        'groovy-cli-commons',
         'groovy-console',
         'groovy-dateutil',
         'groovy-docgenerator',

http://git-wip-us.apache.org/repos/asf/groovy/blob/00cc9e5e/src/main/groovy/groovy/util/CliBuilder.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/util/CliBuilder.groovy b/src/main/groovy/groovy/util/CliBuilder.groovy
deleted file mode 100644
index bc7d44a..0000000
--- a/src/main/groovy/groovy/util/CliBuilder.groovy
+++ /dev/null
@@ -1,798 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package groovy.util
-
-import groovy.cli.CliBuilderException
-import groovy.cli.Option
-import groovy.cli.TypedOption
-import groovy.cli.Unparsed
-import groovy.transform.Undefined
-import org.apache.commons.cli.CommandLine
-import org.apache.commons.cli.CommandLineParser
-import org.apache.commons.cli.DefaultParser
-import org.apache.commons.cli.GnuParser
-import org.apache.commons.cli.HelpFormatter
-import org.apache.commons.cli.Option as CliOption
-import org.apache.commons.cli.Options
-import org.apache.commons.cli.ParseException
-import org.codehaus.groovy.runtime.InvokerHelper
-import org.codehaus.groovy.runtime.MetaClassHelper
-import org.codehaus.groovy.runtime.StringGroovyMethods
-
-import java.lang.annotation.Annotation
-import java.lang.reflect.Array
-import java.lang.reflect.Field
-import java.lang.reflect.Method
-
-/**
- * Provides a builder to assist the processing of command line arguments.
- * Two styles are supported: dynamic api style (declarative method calls provide a mini DSL for describing options)
- * and annotation style (annotations on an interface or class describe options).
- * <p>
- * <b>Dynamic api style</b>
- * <p>
- * Typical usage (emulate partial arg processing of unix command: ls -alt *.groovy):
- * <pre>
- * def cli = new CliBuilder(usage:'ls')
- * cli.a('display all files')
- * cli.l('use a long listing format')
- * cli.t('sort by modification time')
- * def options = cli.parse(args)
- * assert options // would be null (false) on failure
- * assert options.arguments() == ['*.groovy']
- * assert options.a && options.l && options.t
- * </pre>
- * The usage message for this example (obtained using <code>cli.usage()</code>) is shown below:
- * <pre>
- * usage: ls
- *  -a   display all files
- *  -l   use a long listing format
- *  -t   sort by modification time
- * </pre>
- * An underlying parser that supports what is called argument 'bursting' is used
- * by default. Bursting would convert '-alt' into '-a -l -t' provided no long
- * option exists with value 'alt' and provided that none of 'a', 'l' or 't'
- * takes an argument (in fact the last one is allowed to take an argument).
- * The bursting behavior can be turned off by using an
- * alternate underlying parser. The simplest way to achieve this is by using
- * the deprecated GnuParser from Commons CLI with the parser property on the CliBuilder,
- * i.e. include <code>parser: new GnuParser()</code> in the constructor call.
- * <p>
- * Another example (partial emulation of arg processing for 'ant' command line):
- * <pre>
- * def cli = new CliBuilder(usage:'ant [options] [targets]',
- *                          header:'Options:')
- * cli.help('print this message')
- * cli.logfile(args:1, argName:'file', 'use given file for log')
- * cli.D(args:2, valueSeparator:'=', argName:'property=value',
- *       'use value for given property')
- * def options = cli.parse(args)
- * ...
- * </pre>
- * Usage message would be:
- * <pre>
- * usage: ant [options] [targets]
- * Options:
- *  -D &lt;property=value>   use value for given property
- *  -help                 print this message
- *  -logfile &lt;file>       use given file for log
- * </pre>
- * And if called with the following arguments '-logfile foo -Dbar=baz target'
- * then the following assertions would be true:
- * <pre>
- * assert options // would be null (false) on failure
- * assert options.arguments() == ['target']
- * assert options.Ds == ['bar', 'baz']
- * assert options.logfile == 'foo'
- * </pre>
- * Note the use of some special notation. By adding 's' onto an option
- * that may appear multiple times and has an argument or as in this case
- * uses a valueSeparator to separate multiple argument values
- * causes the list of associated argument values to be returned.
- * <p>
- * Another example showing long options (partial emulation of arg processing for 'curl' command line):
- * <pre>
- * def cli = new CliBuilder(usage:'curl [options] &lt;url&gt;')
- * cli._(longOpt:'basic', 'Use HTTP Basic Authentication')
- * cli.d(longOpt:'data', args:1, argName:'data', 'HTTP POST data')
- * cli.G(longOpt:'get', 'Send the -d data with a HTTP GET')
- * cli.q('If used as the first parameter disables .curlrc')
- * cli._(longOpt:'url', args:1, argName:'URL', 'Set URL to work with')
- * </pre>
- * Which has the following usage message:
- * <pre>
- * usage: curl [options] &lt;url>
- *     --basic         Use HTTP Basic Authentication
- *  -d,--data &lt;data>   HTTP POST data
- *  -G,--get           Send the -d data with a HTTP GET
- *  -q                 If used as the first parameter disables .curlrc
- *     --url &lt;URL>     Set URL to work with
- * </pre>
- * This example shows a common convention. When mixing short and long names, the
- * short names are often one character in size. One character options with
- * arguments don't require a space between the option and the argument, e.g.
- * <code>-Ddebug=true</code>. The example also shows
- * the use of '_' when no short option is applicable.
- * <p>
- * Also note that '_' was used multiple times. This is supported but if
- * any other shortOpt or any longOpt is repeated, then the behavior is undefined.
- * <p>
- * Short option names may not contain a hyphen. If a long option name contains a hyphen, e.g. '--max-wait' then you can either
- * use the long hand method call <code>options.hasOption('max-wait')</code> or surround
- * the option name in quotes, e.g. <code>options.'max-wait'</code>.
- * <p>
- * Although CliBuilder on the whole hides away the underlying library used
- * for processing the arguments, it does provide some hooks which let you
- * make use of the underlying library directly should the need arise. For
- * example, the last two lines of the 'curl' example above could be replaced
- * with the following:
- * <pre>
- * import org.apache.commons.cli.*
- * ... as before ...
- * cli << new Option('q', false, 'If used as the first parameter disables .curlrc')
- * cli << Option.builder().longOpt('url').hasArg().argName('URL').
- *                      desc('Set URL to work with').build()
- * ...
- * </pre>
- *
- * CliBuilder also supports Argument File processing. If an argument starts with
- * an '@' character followed by a filename, then the contents of the file with name
- * filename are placed into the command line. The feature can be turned off by
- * setting expandArgumentFiles to false. If turned on, you can still pass a real
- * parameter with an initial '@' character by escaping it with an additional '@'
- * symbol, e.g. '@@foo' will become '@foo' and not be subject to expansion. As an
- * example, if the file temp.args contains the content:
- * <pre>
- * -arg1
- * paramA
- * paramB paramC
- * </pre>
- * Then calling the command line with:
- * <pre>
- * someCommand @temp.args -arg2 paramD
- * </pre>
- * Is the same as calling this:
- * <pre>
- * someCommand -arg1 paramA paramB paramC -arg2 paramD
- * </pre>
- * This feature is particularly useful on operating systems which place limitations
- * on the size of the command line (e.g. Windows). The feature is similar to
- * the 'Command Line Argument File' processing supported by javadoc and javac.
- * Consult the corresponding documentation for those tools if you wish to see further examples.
- * <p>
- * <b>Supported Option Properties</b>:
- * <pre>
- *   argName:        String
- *   longOpt:        String
- *   args:           int or String
- *   optionalArg:    boolean
- *   required:       boolean
- *   type:           Class
- *   valueSeparator: char
- *   convert:        Closure
- *   defaultValue:   String
- * </pre>
- * See {@link org.apache.commons.cli.Option} for the meaning of most of these properties
- * and {@link CliBuilderTest} for further examples.
- * <p>
- * <b>Annotation style with an interface</b>
- * <p>
- * With this style an interface is defined containing an annotated method for each option.
- * It might look like this (following roughly the earlier 'ls' example):
- * <pre>
- * import groovy.cli.Option
- * import groovy.cli.Unparsed
- *
- * interface OptionInterface {
- *     @{@link groovy.cli.Option}(shortName='a', description='display all files') boolean all()
- *     @{@link groovy.cli.Option}(shortName='l', description='use a long listing format') boolean longFormat()
- *     @{@link groovy.cli.Option}(shortName='t', description='sort by modification time') boolean time()
- *     @{@link groovy.cli.Unparsed} List remaining()
- * }
- * </pre>
- * Then this description is supplied to CliBuilder during parsing, e.g.:
- * <pre>
- * def args = '-alt *.groovy'.split() // normally from commandline itself
- * def cli = new CliBuilder(usage:'ls')
- * def options = cli.parseFromSpec(OptionInterface, args)
- * assert options.remaining() == ['*.groovy']
- * assert options.all() && options.longFormat() && options.time()
- * </pre>
- * <p>
- * <b>Annotation style with a class</b>
- * <p>
- * With this style a user-supplied instance is used. Annotations on that instance's class
- * members (properties and setter methods) indicate how to set options and provide the option details
- * using annotation attributes.
- * It might look like this (again using the earlier 'ls' example):
- * <pre>
- * import groovy.cli.Option
- * import groovy.cli.Unparsed
- *
- * class OptionClass {
- *     @{@link groovy.cli.Option}(shortName='a', description='display all files') boolean all
- *     @{@link groovy.cli.Option}(shortName='l', description='use a long listing format') boolean longFormat
- *     @{@link groovy.cli.Option}(shortName='t', description='sort by modification time') boolean time
- *     @{@link groovy.cli.Unparsed} List remaining
- * }
- * </pre>
- * Then this description is supplied to CliBuilder during parsing, e.g.:
- * <pre>
- * def args = '-alt *.groovy'.split() // normally from commandline itself
- * def cli = new CliBuilder(usage:'ls')
- * def options = new OptionClass()
- * cli.parseFromInstance(options, args)
- * assert options.remaining == ['*.groovy']
- * assert options.all && options.longFormat && options.time
- * </pre>
- */
-class CliBuilder {
-
-    /**
-     * Usage summary displayed as the first line when <code>cli.usage()</code> is called.
-     */
-    String usage = 'groovy'
-
-    /**
-     * Normally set internally but allows you full customisation of the underlying processing engine.
-     */
-    CommandLineParser parser = null
-
-    /**
-     * To change from the default PosixParser to the GnuParser, set this to false. Ignored if the parser is explicitly set.
-     * @deprecated use the parser option instead with an instance of your preferred parser
-     */
-    @Deprecated
-    Boolean posix = null
-
-    /**
-     * Whether arguments of the form '{@code @}<i>filename</i>' will be expanded into the arguments contained within the file named <i>filename</i> (default true).
-     */
-    boolean expandArgumentFiles = true
-
-    /**
-     * Normally set internally but can be overridden if you want to customise how the usage message is displayed.
-     */
-    HelpFormatter formatter = new HelpFormatter()
-
-    /**
-     * Defaults to stdout but you can provide your own PrintWriter if desired.
-     */
-    PrintWriter writer = new PrintWriter(System.out)
-
-    /**
-     * Optional additional message for usage; displayed after the usage summary but before the options are displayed.
-     */
-    String header = ''
-
-    /**
-     * Optional additional message for usage; displayed after the options are displayed.
-     */
-    String footer = ''
-
-    /**
-     * Indicates that option processing should continue for all arguments even
-     * if arguments not recognized as options are encountered (default true).
-     */
-    boolean stopAtNonOption = true
-
-    /**
-     * Allows customisation of the usage message width.
-     */
-    int width = HelpFormatter.DEFAULT_WIDTH
-
-    /**
-     * Not normally accessed directly but full access to underlying options if needed.
-     */
-    Options options = new Options()
-
-    Map<String, TypedOption> savedTypeOptions = new HashMap<String, TypedOption>()
-
-    public <T> TypedOption<T> option(Map args, Class<T> type, String description) {
-        def name = args.opt ?: '_'
-        args.type = type
-        args.remove('opt')
-        "$name"(args, description)
-    }
-
-    /**
-     * Internal method: Detect option specification method calls.
-     */
-    def invokeMethod(String name, Object args) {
-        if (args instanceof Object[]) {
-            if (args.size() == 1 && (args[0] instanceof String || args[0] instanceof GString)) {
-                def option = option(name, [:], args[0])
-                options.addOption(option)
-
-                return create(option, null, null, null)
-            }
-            if (args.size() == 1 && args[0] instanceof CliOption && name == 'leftShift') {
-                CliOption option = args[0]
-                options.addOption(option)
-                return create(option, null, null, null)
-            }
-            if (args.size() == 2 && args[0] instanceof Map) {
-                def convert = args[0].remove('convert')
-                def type = args[0].remove('type')
-                def defaultValue = args[0].remove('defaultValue')
-                if (type && !(type instanceof Class)) {
-                    throw new CliBuilderException("'type' must be a Class")
-                }
-                if ((convert || type) && !args[0].containsKey('args') &&
-                        type?.simpleName?.toLowerCase() != 'boolean') {
-                    args[0].args = 1
-                }
-                def option = option(name, args[0], args[1])
-                options.addOption(option)
-                return create(option, type, defaultValue, convert)
-            }
-        }
-        return InvokerHelper.getMetaClass(this).invokeMethod(this, name, args)
-    }
-
-    /**
-     * Make options accessible from command line args with parser.
-     * Returns null on bad command lines after displaying usage message.
-     */
-    OptionAccessor parse(args) {
-        if (expandArgumentFiles) args = expandArgumentFiles(args)
-        if (!parser) {
-            parser = posix != null && posix == false ? new GnuParser() : new DefaultParser()
-        }
-        try {
-            def accessor = new OptionAccessor(
-                    parser.parse(options, args as String[], stopAtNonOption))
-            accessor.savedTypeOptions = savedTypeOptions
-            return accessor
-        } catch (ParseException pe) {
-            writer.println("error: " + pe.message)
-            usage()
-            return null
-        }
-    }
-
-    /**
-     * Print the usage message with writer (default: System.out) and formatter (default: HelpFormatter)
-     */
-    void usage() {
-        formatter.printHelp(writer, width, usage, header, options, HelpFormatter.DEFAULT_LEFT_PAD, HelpFormatter.DEFAULT_DESC_PAD, footer)
-        writer.flush()
-    }
-
-    /**
-     * Given an interface containing members with annotations, derive
-     * the options specification.
-     *
-     * @param optionsClass
-     * @param args
-     * @return an instance containing the processed options
-     */
-    public <T> T parseFromSpec(Class<T> optionsClass, String[] args) {
-        addOptionsFromAnnotations(optionsClass, false)
-        def cli = parse(args)
-        def cliOptions = [:]
-        setOptionsFromAnnotations(cli, optionsClass, cliOptions, false)
-        cliOptions as T
-    }
-
-    /**
-     * Given an instance containing members with annotations, derive
-     * the options specification.
-     *
-     * @param optionInstance
-     * @param args
-     * @return the options instance populated with the processed options
-     */
-    public <T> T parseFromInstance(T optionInstance, args) {
-        addOptionsFromAnnotations(optionInstance.getClass(), true)
-        def cli = parse(args)
-        setOptionsFromAnnotations(cli, optionInstance.getClass(), optionInstance, true)
-        optionInstance
-    }
-
-    void addOptionsFromAnnotations(Class optionClass, boolean namesAreSetters) {
-        optionClass.methods.findAll{ it.getAnnotation(Option) }.each { Method m ->
-            Annotation annotation = m.getAnnotation(Option)
-            def typedOption = processAddAnnotation(annotation, m, namesAreSetters)
-            options.addOption(typedOption.cliOption)
-        }
-
-        def optionFields = optionClass.declaredFields.findAll { it.getAnnotation(Option) }
-        if (optionClass.isInterface() && !optionFields.isEmpty()) {
-            throw new CliBuilderException("@Option only allowed on methods in interface " + optionClass.simpleName)
-        }
-        optionFields.each { Field f ->
-            Annotation annotation = f.getAnnotation(Option)
-            String setterName = "set" + MetaClassHelper.capitalize(f.getName());
-            Method m = optionClass.getMethod(setterName, f.getType())
-            def typedOption = processAddAnnotation(annotation, m, true)
-            options.addOption(typedOption.cliOption)
-        }
-    }
-
-    private TypedOption processAddAnnotation(Option annotation, Method m, boolean namesAreSetters) {
-        String shortName = annotation.shortName()
-        String description = annotation.description()
-        String defaultValue = annotation.defaultValue()
-        char valueSeparator = 0
-        if (annotation.valueSeparator()) valueSeparator = annotation.valueSeparator() as char
-        boolean optionalArg = annotation.optionalArg()
-        Integer numberOfArguments = annotation.numberOfArguments()
-        String numberOfArgumentsString = annotation.numberOfArgumentsString()
-        Class convert = annotation.convert()
-        if (convert == Undefined.CLASS) {
-            convert = null
-        }
-        Map names = calculateNames(annotation.longName(), 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 = 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)
-        if (type) {
-            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)
-            }
-            if (type.isArray()) {
-                builder.optionalArg(optionalArg)
-            }
-        }
-        def typedOption = create(builder.build(), convert ? null : type, defaultValue, convert)
-        typedOption
-    }
-
-    private TypedOption create(CliOption o, Class theType, defaultValue, convert) {
-        Map<String, Object> result = new TypedOption<Object>()
-        o.with {
-            if (opt != null) result.put("opt", opt)
-            result.put("longOpt", longOpt)
-            result.put("cliOption", o)
-            if (defaultValue) {
-                result.put("defaultValue", defaultValue)
-            }
-            if (convert) {
-                if (theType) {
-                    throw new CliBuilderException("You can't specify 'type' when using 'convert'")
-                }
-                result.put("convert", convert)
-                result.put("type", convert instanceof Class ? convert : convert.getClass())
-            } else {
-                result.put("type", theType)
-            }
-        }
-        savedTypeOptions[o.longOpt ?: o.opt] = result
-        result
-    }
-
-    def setOptionsFromAnnotations(def cli, Class optionClass, Object t, boolean namesAreSetters) {
-        optionClass.methods.findAll{ it.getAnnotation(Option) }.each { Method m ->
-            Annotation annotation = m.getAnnotation(Option)
-            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())
-            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 ->
-            processSetRemaining(m, remaining, t, cli, namesAreSetters)
-        }
-        optionClass.declaredFields.findAll{ it.getAnnotation(Unparsed) }.each { Field f ->
-            String setterName = "set" + MetaClassHelper.capitalize(f.getName());
-            Method m = optionClass.getMethod(setterName, f.getType())
-            processSetRemaining(m, remaining, t, cli, namesAreSetters)
-        }
-    }
-
-    private void processSetRemaining(Method m, remaining, Object t, cli, boolean namesAreSetters) {
-        def resultType = namesAreSetters ? m.parameterTypes[0] : m.returnType
-        def isTyped = resultType?.isArray()
-        def result
-        def type = null
-        if (isTyped) {
-            type = resultType.componentType
-            result = remaining.collect{ cli.getValue(type, it, null) }
-        } else {
-            result = remaining.toList()
-        }
-        if (namesAreSetters) {
-            m.invoke(t, isTyped ? [result.toArray(Array.newInstance(type, result.size()))] as Object[] : result)
-        } else {
-            Map names = calculateNames("", "", m, namesAreSetters)
-            t.put(names.long, { -> result })
-        }
-    }
-
-    private void processSetAnnotation(Method m, Object t, String name, cli, boolean namesAreSetters) {
-        def conv = savedTypeOptions[name]?.convert
-        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) {
-            def isBoolArg = m.parameterTypes.size() > 0 && m.parameterTypes[0].simpleName.toLowerCase() == 'boolean'
-            boolean isFlag = (isBoolArg && !hasArg) || noArg
-            if (cli.hasOption(name) || isFlag || cli.defaultValue(name)) {
-                m.invoke(t, [isFlag ? cli.hasOption(name) :
-                                     cli.hasOption(name) ? optionValue(cli, name) : cli.defaultValue(name)] as Object[])
-            }
-        } else {
-            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 : cli.defaultValue(name) })
-        }
-    }
-
-    private optionValue(cli, String name) {
-        if (savedTypeOptions.containsKey(name)) {
-            return cli.getOptionValue(savedTypeOptions[name])
-        }
-        cli[name]
-    }
-
-    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))
-            }
-        }
-        [long: useShort ? "" : result, short: (useShort && !shortName) ? result : shortName]
-    }
-
-    // implementation details -------------------------------------
-
-    /**
-     * Internal method: How to create an option from the specification.
-     */
-    CliOption option(shortname, Map details, info) {
-        CliOption option
-        if (shortname == '_') {
-            option = CliOption.builder().desc(info).longOpt(details.longOpt).build()
-            details.remove('longOpt')
-        } else {
-            option = new CliOption(shortname, info)
-        }
-        adjustDetails(details).each { key, value ->
-            option[key] = value
-        }
-        return option
-    }
-
-    static Map adjustDetails(Map m) {
-        m.collectMany { k, v ->
-            if (k == 'args' && v == '+') {
-                [[args: org.apache.commons.cli.Option.UNLIMITED_VALUES]]
-            } else if (k == 'args' && v == '*') {
-                [[args: org.apache.commons.cli.Option.UNLIMITED_VALUES,
-                  optionalArg: true]]
-            } else if (k == 'args' && v instanceof String) {
-                [[args: Integer.parseInt(v)]]
-            } else {
-                [[(k): v]]
-            }
-        }.sum()
-    }
-
-    static expandArgumentFiles(args) throws IOException {
-        def result = []
-        for (arg in args) {
-            if (arg && arg != '@' && arg[0] == '@') {
-                arg = arg.substring(1)
-                if (arg[0] != '@') {
-                    expandArgumentFile(arg, result)
-                    continue
-                }
-            }
-            result << arg
-        }
-        return result
-    }
-
-    private static expandArgumentFile(name, args) throws IOException {
-        def charAsInt = { String s -> s.toCharacter() as int }
-        new File(name).withReader { r ->
-            new StreamTokenizer(r).with {
-                resetSyntax()
-                wordChars(charAsInt(' '), 255)
-                whitespaceChars(0, charAsInt(' '))
-                commentChar(charAsInt('#'))
-                quoteChar(charAsInt('"'))
-                quoteChar(charAsInt('\''))
-                while (nextToken() != StreamTokenizer.TT_EOF) {
-                    args << sval
-                }
-            }
-        }
-    }
-
-}
-
-class OptionAccessor {
-    CommandLine commandLine
-    Map<String, TypedOption> savedTypeOptions
-
-    OptionAccessor(CommandLine commandLine) {
-        this.commandLine = commandLine
-    }
-
-    boolean hasOption(TypedOption typedOption) {
-        commandLine.hasOption(typedOption.longOpt ?: typedOption.opt)
-    }
-
-    public <T> T defaultValue(String name) {
-        Class<T> type = savedTypeOptions[name]?.type
-        String value = savedTypeOptions[name]?.defaultValue() ? savedTypeOptions[name].defaultValue() : null
-        return (T) value ? getTypedValue(type, name, value) : null
-    }
-
-    public <T> T getOptionValue(TypedOption<T> typedOption) {
-        getOptionValue(typedOption, null)
-    }
-
-    public <T> T getOptionValue(TypedOption<T> typedOption, T defaultValue) {
-        String optionName = (String) typedOption.longOpt ?: typedOption.opt
-        if (commandLine.hasOption(optionName)) {
-            if (typedOption.containsKey('type') && typedOption.type.isArray()) {
-                def compType = typedOption.type.componentType
-                return (T) getTypedValuesFromName(optionName, compType)
-            }
-            return getTypedValueFromName(optionName)
-        }
-        return defaultValue
-    }
-
-    private <T> T[] getTypedValuesFromName(String optionName, Class<T> compType) {
-        CliOption option = commandLine.options.find{ it.longOpt == optionName }
-        T[] result = null
-        if (option) {
-            int count = 0
-            def optionValues = commandLine.getOptionValues(optionName)
-            for (String optionValue : optionValues) {
-                if (result == null) {
-                    result = (T[]) Array.newInstance(compType, optionValues.length)
-                }
-                result[count++] = (T) getTypedValue(compType, optionName, optionValue)
-            }
-        }
-        if (result == null) {
-            result = (T[]) Array.newInstance(compType, 0)
-        }
-        return result
-    }
-
-    public <T> T getAt(TypedOption<T> typedOption) {
-        getAt(typedOption, null)
-    }
-
-    public <T> T getAt(TypedOption<T> typedOption, T defaultValue) {
-        String optionName = (String) typedOption.longOpt ?: typedOption.opt
-        if (savedTypeOptions.containsKey(optionName)) {
-            return getTypedValueFromName(optionName)
-        }
-        return defaultValue
-    }
-
-    private <T> T getTypedValueFromName(String optionName) {
-        Class type = savedTypeOptions[optionName].type
-        String optionValue = commandLine.getOptionValue(optionName)
-        return (T) getTypedValue(type, optionName, optionValue)
-    }
-
-    private <T> T getTypedValue(Class<T> type, String optionName, String optionValue) {
-        if (savedTypeOptions[optionName]?.cliOption?.numberOfArgs == 0) {
-            return (T) commandLine.hasOption(optionName)
-        }
-        def convert = savedTypeOptions[optionName]?.convert
-        return getValue(type, optionValue, convert)
-    }
-
-    private <T> T getValue(Class<T> type, String optionValue, Closure convert) {
-        if (!type) {
-            return (T) optionValue
-        }
-        if (Closure.isAssignableFrom(type) && convert) {
-            return (T) convert(optionValue)
-        }
-        if (type?.simpleName?.toLowerCase() == 'boolean') {
-            return (T) Boolean.parseBoolean(optionValue)
-        }
-        StringGroovyMethods.asType(optionValue, (Class<T>) type)
-    }
-
-    def invokeMethod(String name, Object args) {
-        return InvokerHelper.getMetaClass(commandLine).invokeMethod(commandLine, name, args)
-    }
-
-    def getProperty(String name) {
-        if (!savedTypeOptions.containsKey(name)) {
-            def alt = savedTypeOptions.find{ it.value.opt == name }
-            if (alt) name = alt.key
-        }
-        def methodname = 'getOptionValue'
-        Class type = savedTypeOptions[name]?.type
-        def foundArray = type?.isArray()
-        if (name.size() > 1 && name.endsWith('s')) {
-            def singularName = name[0..-2]
-            if (commandLine.hasOption(singularName) || foundArray) {
-                name = singularName
-                methodname += 's'
-                type = savedTypeOptions[name]?.type
-            }
-        }
-        if (type?.isArray()) {
-            methodname = 'getOptionValues'
-        }
-        if (name.size() == 1) name = name as char
-        def result = InvokerHelper.getMetaClass(commandLine).invokeMethod(commandLine, methodname, name)
-        if (result != null) {
-            if (result instanceof String[]) {
-                result = result.collect{ type ? getTypedValue(type.isArray() ? type.componentType : type, name, it) : it }
-            } else {
-                if (type) result = getTypedValue(type, name, result)
-            }
-        } else if (type?.simpleName != 'boolean' && savedTypeOptions[name]?.defaultValue) {
-            result = getTypedValue(type, name, savedTypeOptions[name].defaultValue)
-        } else {
-            result = commandLine.hasOption(name)
-        }
-        return result
-    }
-
-    List<String> arguments() {
-        commandLine.args.toList()
-    }
-}

http://git-wip-us.apache.org/repos/asf/groovy/blob/00cc9e5e/src/test/groovy/util/CliBuilderTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/groovy/util/CliBuilderTest.groovy b/src/test/groovy/util/CliBuilderTest.groovy
deleted file mode 100644
index 175228b..0000000
--- a/src/test/groovy/util/CliBuilderTest.groovy
+++ /dev/null
@@ -1,707 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package groovy.util
-
-import groovy.cli.Option
-import groovy.cli.Unparsed
-import groovy.transform.ToString
-import groovy.transform.TypeChecked
-import org.apache.commons.cli.BasicParser
-import org.apache.commons.cli.DefaultParser
-import org.apache.commons.cli.GnuParser
-import org.codehaus.groovy.cli.GroovyPosixParser
-
-import java.math.RoundingMode
-
-import static org.apache.commons.cli.Option.UNLIMITED_VALUES
-import static org.apache.commons.cli.Option.builder
-
-/**
- * Test class for the CliBuilder.
- * <p>
- * Commons CLI has a long history of different parsers with slightly differing behavior and bugs.
- * In nearly all cases, we now recommend using DefaultParser. In case you have very unique circumstances
- * and really need behavior that can only be supplied by one of the legacy parsers, we also include
- * some test case runs against some of the legacy parsers.
- */
-
-class CliBuilderTest extends GroovyTestCase {
-
-    private StringWriter stringWriter
-    private PrintWriter printWriter
-
-    void setUp() {
-        resetPrintWriter()
-    }
-
-    private final expectedParameter = 'ASCII'
-    private final usageString = 'groovy [option]* filename'
-
-    private void runSample(parser, optionList) {
-        resetPrintWriter()
-        def cli = new CliBuilder(usage: usageString, writer: printWriter, parser: parser)
-        cli.h(longOpt: 'help', 'usage information')
-        cli.c(argName: 'charset', args: 1, longOpt: 'encoding', 'character encoding')
-        cli.i(argName: 'extension', optionalArg: true, 'modify files in place, create backup if extension is given (e.g. \'.bak\')')
-        def stringified = cli.options.toString()
-        assert stringified =~ /i=\[ option: i  :: modify files in place, create backup if extension is given/
-        assert stringified =~ /c=\[ option: c encoding  \[ARG] :: character encoding/
-        assert stringified =~ /h=\[ option: h help  :: usage information/
-        assert stringified =~ /encoding=\[ option: c encoding  \[ARG] :: character encoding/
-        assert stringified =~ /help=\[ option: h help  :: usage information/
-        def options = cli.parse(optionList)
-        assert options.hasOption('h')
-        assert options.hasOption('help')
-        assert options.h
-        assert options.help
-        if (options.h) { cli.usage() }
-        def expectedUsage = """usage: $usageString
- -c,--encoding <charset>   character encoding
- -h,--help                 usage information
- -i                        modify files in place, create backup if
-                           extension is given (e.g. '.bak')"""
-        assertEquals(expectedUsage, stringWriter.toString().tokenize('\r\n').join('\n'))
-        resetPrintWriter()
-        cli.writer = printWriter
-        if (options.help) { cli.usage() }
-        assertEquals(expectedUsage, stringWriter.toString().tokenize('\r\n').join('\n'))
-        assert options.hasOption('c')
-        assert options.c
-        assert options.hasOption('encoding')
-        assert options.encoding
-        assertEquals(expectedParameter, options.getOptionValue('c'))
-        assertEquals(expectedParameter, options.c)
-        assertEquals(expectedParameter, options.getOptionValue('encoding'))
-        assertEquals(expectedParameter, options.encoding)
-        assertEquals(false, options.noSuchOptionGiven)
-        assertEquals(false, options.hasOption('noSuchOptionGiven'))
-        assertEquals(false, options.x)
-        assertEquals(false, options.hasOption('x'))
-    }
-
-    private void resetPrintWriter() {
-        stringWriter = new StringWriter()
-        printWriter = new PrintWriter(stringWriter)
-    }
-
-    void testSampleShort() {
-        [new DefaultParser(), new GroovyPosixParser(), new GnuParser(), new BasicParser()].each { parser ->
-            runSample(parser, ['-h', '-c', expectedParameter])
-        }
-    }
-
-    void testSampleLong() {
-        [new DefaultParser(), new GroovyPosixParser(), new GnuParser(), new BasicParser()].each { parser ->
-            runSample(parser, ['--help', '--encoding', expectedParameter])
-        }
-    }
-
-    void testSimpleArg() {
-        [new DefaultParser(), new GroovyPosixParser(), new GnuParser(), new BasicParser()].each { parser ->
-            def cli = new CliBuilder(parser: parser)
-            cli.a([:], '')
-            def options = cli.parse(['-a', '1', '2'])
-            assertEquals(['1', '2'], options.arguments())
-        }
-    }
-
-    void testMultipleArgs() {
-        [new DefaultParser(), new GroovyPosixParser(), new GnuParser(), new BasicParser()].each { parser ->
-            def cli = new CliBuilder(parser: parser)
-            cli.a(longOpt: 'arg', args: 2, valueSeparator: ',' as char, 'arguments')
-            def options = cli.parse(['-a', '1,2'])
-            assertEquals('1', options.a)
-            assertEquals(['1', '2'], options.as)
-            assertEquals('1', options.arg)
-            assertEquals(['1', '2'], options.args)
-        }
-    }
-
-    void testFailedParsePrintsUsage() {
-        def cli = new CliBuilder(writer: printWriter)
-        cli.x(required: true, 'message')
-        cli.parse([])
-        // NB: This test is very fragile and is bound to fail on different locales and versions of commons-cli... :-(
-        assert stringWriter.toString().normalize() == '''error: Missing required option: x
-usage: groovy
- -x   message
-'''
-    }
-
-    void testLongOptsOnly_nonOptionShouldStopArgProcessing() {
-        [new DefaultParser(), new GroovyPosixParser(), new GnuParser()].each { parser ->
-            def cli = new CliBuilder(parser: parser)
-            def anOption = builder().longOpt('anOption').hasArg().desc('An option.')
-                    .build()
-            cli.options.addOption(anOption)
-            def options = cli.parse(['-v', '--anOption', 'something'])
-            // no options should be found
-            assert options.getOptionValue('anOption') == null
-            assert !options.anOption
-            assert !options.v
-            // arguments should be still sitting there
-            assert options.arguments() == ['-v', '--anOption', 'something']
-        }
-    }
-
-    void testLongAndShortOpts_allOptionsValid() {
-        [new DefaultParser(), new GroovyPosixParser(), new GnuParser(), new BasicParser()].each { parser ->
-            def cli = new CliBuilder(parser: parser)
-            def anOption = builder().longOpt('anOption').hasArg().desc('An option.').build()
-            cli.options.addOption(anOption)
-            cli.v(longOpt: 'verbose', 'verbose mode')
-            def options = cli.parse(['-v', '--anOption', 'something'])
-            assert options.v
-            assert options.getOptionValue('anOption') == 'something'
-            assert options.anOption == 'something'
-            assert !options.arguments()
-        }
-    }
-
-    void testUnrecognizedOptions() {
-        [new DefaultParser(), new GroovyPosixParser(), new GnuParser(), new BasicParser()].each { parser ->
-            def cli = new CliBuilder(parser: parser)
-            cli.v(longOpt: 'verbose', 'verbose mode')
-            def options = cli.parse(['-x', '-yyy', '--zzz', 'something'])
-            assertEquals(['-x', '-yyy', '--zzz', 'something'], options.arguments())
-        }
-    }
-
-    void testMultipleOccurrencesSeparateSeparate() {
-        [new DefaultParser(), new GroovyPosixParser(), new GnuParser(), new BasicParser()].each { parser ->
-            def cli = new CliBuilder(parser: parser)
-            cli.a(longOpt: 'arg', args: UNLIMITED_VALUES, 'arguments')
-            def options = cli.parse(['-a', '1', '-a', '2', '-a', '3'])
-            assertEquals('1', options.a)
-            assertEquals(['1', '2', '3'], options.as)
-            assertEquals('1', options.arg)
-            assertEquals(['1', '2', '3'], options.args)
-            assertEquals([], options.arguments())
-        }
-    }
-
-    void testMultipleOccurrencesSeparateJuxtaposed() {
-        [new DefaultParser(), new GroovyPosixParser(), new GnuParser()].each { parser ->
-            def cli = new CliBuilder(parser: parser)
-            //cli.a ( longOpt : 'arg' , args : UNLIMITED_VALUES , 'arguments' )
-            cli.a(longOpt: 'arg', args: 1, 'arguments')
-            def options = cli.parse(['-a1', '-a2', '-a3'])
-            assertEquals('1', options.a)
-            assertEquals(['1', '2', '3'], options.as)
-            assertEquals('1', options.arg)
-            assertEquals(['1', '2', '3'], options.args)
-            assertEquals([], options.arguments())
-        }
-    }
-
-    void testMultipleOccurrencesTogetherSeparate() {
-        [new DefaultParser(), new GroovyPosixParser(), new GnuParser()].each { parser ->
-            def cli = new CliBuilder(parser: parser)
-            cli.a(longOpt: 'arg', args: UNLIMITED_VALUES, valueSeparator: ',' as char, 'arguments')
-            def options = cli.parse(['-a 1,2,3'])
-            assertEquals(' 1', options.a)
-            assertEquals([' 1', '2', '3'], options.as)
-            assertEquals(' 1', options.arg)
-            assertEquals([' 1', '2', '3'], options.args)
-            assertEquals([], options.arguments())
-        }
-    }
-
-    void testMultipleOccurrencesTogetherJuxtaposed() {
-        [new DefaultParser(), new GroovyPosixParser(), new GnuParser()].each { parser ->
-            def cli1 = new CliBuilder(parser: parser)
-            cli1.a(longOpt: 'arg', args: UNLIMITED_VALUES, valueSeparator: ',' as char, 'arguments')
-            def options = cli1.parse(['-a1,2,3'])
-            assertEquals('1', options.a)
-            assertEquals(['1', '2', '3'], options.as)
-            assertEquals('1', options.arg)
-            assertEquals(['1', '2', '3'], options.args)
-            assertEquals([], options.arguments()) }
-        }
-
-    /*
-    *  Behaviour with unrecognized options.
-    *
-    *  TODO: Should add the BasicParser here as well?
-    */
-
-    void testUnrecognizedOptionSilentlyIgnored_GnuParser() {
-        def cli = new CliBuilder(usage: usageString, writer: printWriter, parser: new GnuParser())
-        def options = cli.parse(['-v'])
-        assertEquals('''''', stringWriter.toString().tokenize('\r\n').join('\n'))
-        assert !options.v
-    }
-
-    private void checkNoOutput() {
-        assert stringWriter.toString().tokenize('\r\n').join('\n') == ''''''
-    }
-
-    void testUnrecognizedOptionSilentlyIgnored_DefaultParser() {
-        def cli = new CliBuilder(usage: usageString, writer: printWriter, parser: new DefaultParser())
-        def options = cli.parse(['-v'])
-        checkNoOutput()
-        assert !options.v
-    }
-
-    void testUnrecognizedOptionTerminatesParse_GnuParser() {
-        def cli = new CliBuilder(usage: usageString, writer: printWriter, parser: new GnuParser())
-        cli.h(longOpt: 'help', 'usage information')
-        def options = cli.parse(['-v', '-h'])
-        checkNoOutput()
-        assert !options.v
-        assert !options.h
-        assertEquals(['-v', '-h'], options.arguments())
-    }
-
-    void testUnrecognizedOptionTerminatesParse_DefaultParser() {
-        def cli = new CliBuilder(usage: usageString, writer: printWriter, parser: new DefaultParser())
-        cli.h(longOpt: 'help', 'usage information')
-        def options = cli.parse(['-v', '-h'])
-        checkNoOutput()
-        assert !options.v
-        assert !options.h
-        assertEquals(['-v', '-h'], options.arguments())
-    }
-
-    void testMultiCharShortOpt() {
-        [new DefaultParser(), new GroovyPosixParser(), new GnuParser()].each { parser ->
-            def cli = new CliBuilder(writer: printWriter, parser: parser)
-            cli.abc('abc option')
-            cli.def(longOpt: 'defdef', 'def option')
-            def options = cli.parse(['-abc', '--defdef', 'ghi'])
-            assert options
-            assert options.arguments() == ['ghi']
-            assert options.abc && options.def && options.defdef
-            checkNoOutput()
-        }
-    }
-
-    void testArgumentBursting_DefaultParserOnly() {
-        def cli = new CliBuilder(writer: printWriter)
-        // must not have longOpt 'abc' and also no args for a or b
-        cli.a('a')
-        cli.b('b')
-        cli.c('c')
-        def options = cli.parse(['-abc', '-d'])
-        assert options
-        assert options.arguments() == ['-d']
-        assert options.a && options.b && options.c && !options.d
-        checkNoOutput()
-    }
-
-    void testLongOptEndingWithS() {
-        def cli = new CliBuilder()
-        cli.s(longOpt: 'number_of_seconds', 'a long arg that ends with an "s"')
-
-        def options = cli.parse(['-s'])
-
-        assert options.hasOption('s')
-        assert options.hasOption('number_of_seconds')
-        assert options.s
-        assert options.number_of_seconds
-    }
-
-    void testArgumentFileExpansion() {
-        def cli = new CliBuilder(usage: 'test usage')
-        cli.h(longOpt: 'help', 'usage information')
-        cli.d(longOpt: 'debug', 'turn on debug info')
-        def args = ['-h', '@temp.args', 'foo', '@@baz']
-        def temp = new File('temp.args')
-        temp.deleteOnExit()
-        temp.text = '-d bar'
-        def options = cli.parse(args)
-        assert options.h
-        assert options.d
-        assert options.arguments() == ['bar', 'foo', '@baz']
-    }
-
-    void testArgumentFileExpansionArgOrdering() {
-        def cli = new CliBuilder(usage: 'test usage')
-        def args = ['one', '@temp1.args', 'potato', '@temp2.args', 'four']
-        def temp1 = new File('temp1.args')
-        temp1.deleteOnExit()
-        temp1.text = 'potato two'
-        def temp2 = new File('temp2.args')
-        temp2.deleteOnExit()
-        temp2.text = 'three potato'
-        def options = cli.parse(args)
-        assert options.arguments() == 'one potato two potato three potato four'.split()
-    }
-
-    void testArgumentFileExpansionTurnedOff() {
-        def cli = new CliBuilder(usage: 'test usage', expandArgumentFiles:false)
-        cli.h(longOpt: 'help', 'usage information')
-        cli.d(longOpt: 'debug', 'turn on debug info')
-        def args = ['-h', '@temp.args', 'foo', '@@baz']
-        def temp = new File('temp.args')
-        temp.deleteOnExit()
-        temp.text = '-d bar'
-        def options = cli.parse(args)
-        assert options.h
-        assert !options.d
-        assert options.arguments() == ['@temp.args', 'foo', '@@baz']
-    }
-
-    void testGStringSpecification_Groovy4621() {
-        def user = 'scott'
-        def pass = 'tiger'
-        def ignore = false
-        def longOptName = 'user'
-        def cli = new CliBuilder(usage: 'blah')
-        cli.dbusername(longOpt:"$longOptName", args: 1, "Database username [default $user]")
-        cli.dbpassword(args: 1, "Database password [default $pass]")
-        cli.i("ignore case [default $ignore]")
-        def args = ['-dbpassword', 'foo', '--user', 'bar', '-i']
-        def options = cli.parse(args)
-        assert options.user == 'bar'
-        assert options.dbusername == 'bar'
-        assert options.dbpassword == 'foo'
-        assert options.i
-    }
-
-    void testNoExpandArgsWithEmptyArg() {
-        def cli = new CliBuilder(expandArgumentFiles: false)
-        cli.parse(['something', ''])
-    }
-
-    void testExpandArgsWithEmptyArg() {
-        def cli = new CliBuilder(expandArgumentFiles: true)
-        cli.parse(['something', ''])
-    }
-
-    void testDoubleHyphenShortOptions() {
-        def cli = new CliBuilder()
-        cli.a([:], '')
-        cli.b([:], '')
-        def options = cli.parse(['-a', '--', '-b', 'foo'])
-        assert options.arguments() == ['-b', 'foo']
-    }
-
-    void testDoubleHyphenLongOptions() {
-        def cli = new CliBuilder()
-        cli._([longOpt:'alpha'], '')
-        cli._([longOpt:'beta'], '')
-        def options = cli.parse(['--alpha', '--', '--beta', 'foo'])
-        assert options.alpha
-        assert options.arguments() == ['--beta', 'foo']
-    }
-
-    void testMixedShortAndLongOptions() {
-        def cli = new CliBuilder()
-        cli.a([longOpt:'alpha', args:1], '')
-        cli.b([:], '')
-        def options = cli.parse(['-b', '--alpha', 'param', 'foo'])
-        assert options.a == 'param'
-        assert options.arguments() == ['foo']
-    }
-
-    void testMixedBurstingAndLongOptions() {
-        def cli = new CliBuilder()
-        cli.a([:], '')
-        cli.b([:], '')
-        cli.c([:], '')
-        cli.d([longOpt:'abacus'], '')
-        def options = cli.parse(['-abc', 'foo'])
-        assert options.a
-        assert options.b
-        assert options.c
-        assert options.arguments() == ['foo']
-        options = cli.parse(['-abacus', 'foo'])
-        assert !options.a
-        assert !options.b
-        assert !options.c
-        assert options.d
-        assert options.arguments() == ['foo']
-    }
-
-    interface PersonI {
-        @Option String first()
-        @Option String last()
-        @Option boolean flag1()
-        @Option Boolean flag2()
-        @Option(longName = 'specialFlag') Boolean flag3()
-        @Option flag4()
-        @Option int age()
-        @Option Integer born()
-        @Option float discount()
-        @Option BigDecimal pi()
-        @Option File biography()
-        @Option RoundingMode roundingMode()
-        @Unparsed List remaining()
-    }
-
-    def argz = "--first John --last Smith --flag1 --flag2 --specialFlag --age  21 --born 1980 --discount 3.5 --pi 3.14159 --biography cv.txt --roundingMode DOWN and some more".split()
-
-    void testParseFromSpec() {
-        def builder1 = new CliBuilder()
-        def p1 = builder1.parseFromSpec(PersonI, argz)
-        assert p1.first() == 'John'
-        assert p1.last() == 'Smith'
-        assert p1.flag1()
-        assert p1.flag2()
-        assert p1.flag3()
-        assert !p1.flag4()
-        assert p1.born() == 1980
-        assert p1.age() == 21
-        assert p1.discount() == 3.5f
-        assert p1.pi() == 3.14159
-        assert p1.biography() == new File('cv.txt')
-        assert p1.roundingMode() == RoundingMode.DOWN
-        assert p1.remaining() == ['and', 'some', 'more']
-    }
-
-    @ToString(includeFields=true, excludes='metaClass', includePackage=false)
-    class PersonC {
-        @Option String first
-        private String last
-        @Option boolean flag1
-        private Boolean flag2
-        private Boolean flag3
-        private Boolean flag4
-        private int age
-        private Integer born
-        private float discount
-        private BigDecimal pi
-        private File biography
-        private RoundingMode roundingMode
-        private List remaining
-
-        @Option void setLast(String last) {
-            this.last = last
-        }
-        @Option void setFlag2(boolean flag2) {
-            this.flag2 = flag2
-        }
-        @Option(longName = 'specialFlag') void setFlag3(boolean flag3) {
-            this.flag3 = flag3
-        }
-        @Option void setFlag4(boolean flag4) {
-            this.flag4 = flag4
-        }
-        @Option void setAge(int age) {
-            this.age = age
-        }
-        @Option void setBorn(Integer born) {
-            this.born = born
-        }
-        @Option void setDiscount(float discount) {
-            this.discount = discount
-        }
-        @Option void setPi(BigDecimal pi) {
-            this.pi = pi
-        }
-        @Option void setBiography(File biography) {
-            this.biography = biography
-        }
-        @Option void setRoundingMode(RoundingMode roundingMode) {
-            this.roundingMode = roundingMode
-        }
-        @Unparsed void setRemaining(List remaining) {
-            this.remaining = remaining
-        }
-    }
-    class DefaultValueC {
-        @Option(shortName='f', defaultValue='one') String from
-        @Option(shortName='t', defaultValue='35') int to
-        @Option(shortName='b') int by = 1
-    }
-
-    void testDefaultValueClass() {
-        def cli = new CliBuilder()
-        def options = new DefaultValueC()
-        cli.parseFromInstance(options, '-f two'.split())
-        assert options.from == 'two'
-        assert options.to == 35
-        assert options.by == 1
-
-        options = new DefaultValueC()
-        cli.parseFromInstance(options, '-t 45 --by 2'.split())
-        assert options.from == 'one'
-        assert options.to == 45
-        assert options.by == 2
-    }
-
-    class ValSepC {
-        @Option(numberOfArguments=2) String[] a
-        @Option(numberOfArgumentsString='2', valueSeparator=',') String[] b
-        @Option(numberOfArgumentsString='+', valueSeparator=',') String[] c
-        @Unparsed remaining
-    }
-
-    void testValSepClass() {
-        def cli = new CliBuilder()
-
-        def options = new ValSepC()
-        cli.parseFromInstance(options, '-a 1 2 3 4'.split())
-        assert options.a == ['1', '2']
-        assert options.remaining == ['3', '4']
-
-        options = new ValSepC()
-        cli.parseFromInstance(options, '-a1 -a2 3'.split())
-        assert options.a == ['1', '2']
-        assert options.remaining == ['3']
-
-        options = new ValSepC()
-        cli.parseFromInstance(options, ['-b1,2'] as String[])
-        assert options.b == ['1', '2']
-
-        options = new ValSepC()
-        cli.parseFromInstance(options, ['-c', '1'] as String[])
-        assert options.c == ['1']
-
-        options = new ValSepC()
-        cli.parseFromInstance(options, ['-c1'] as String[])
-        assert options.c == ['1']
-
-        options = new ValSepC()
-        cli.parseFromInstance(options, ['-c1,2,3'] as String[])
-        assert options.c == ['1', '2', '3']
-    }
-
-    class WithConvertC {
-        @Option(convert={ it.toLowerCase() }) String a
-        @Option(convert={ it.toUpperCase() }) String b
-        @Option(convert={ Date.parse("yyyy-MM-dd", it) }) Date d
-        @Unparsed List remaining
-    }
-
-    void testConvertClass() {
-        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 = new WithConvertC()
-        cli.parseFromInstance(options, argz)
-        assert options.a == 'john'
-        assert options.b == 'MARY'
-        assert options.d == newYears
-        assert options.remaining == ['and', 'some', 'more']
-    }
-
-    class TypeCheckedC {
-        @Option String name
-        @Option int age
-        @Unparsed List remaining
-    }
-
-    @TypeChecked
-    void testTypeCheckedClass() {
-        def argz = "--name John --age 21 and some more".split()
-        def cli = new CliBuilder()
-        def options = new TypeCheckedC()
-        cli.parseFromInstance(options, argz)
-        String n = options.name
-        int a = options.age
-        assert n == 'John' && a == 21
-        assert options.remaining == ['and', 'some', 'more']
-    }
-
-    void testParseFromInstance() {
-        def p2 = new PersonC()
-        def builder2 = new CliBuilder()
-        builder2.parseFromInstance(p2, argz)
-        // properties show first in toString()
-        assert p2.toString() == 'CliBuilderTest$PersonC(John, true, Smith, true, true, false, 21, 1980, 3.5, 3.14159,' +
-                ' cv.txt, DOWN, [and, some, more])'
-    }
-
-    interface RetTypeI {
-        @Unparsed Integer[] nums()
-    }
-
-    // this feature is incubating
-    void testTypedUnparsedFromSpec() {
-        def argz = '12 34 56'.split()
-        def cli = new CliBuilder()
-        def options = cli.parseFromSpec(RetTypeI, argz)
-        assert options.nums() == [12, 34, 56]
-    }
-
-    class RetTypeC {
-        @Unparsed Integer[] nums
-    }
-
-    // this feature is incubating
-    void testTypedUnparsedFromInstance() {
-        def argz = '12 34 56'.split()
-        def cli = new CliBuilder()
-        def options = new RetTypeC()
-        cli.parseFromInstance(options, argz)
-        assert options.nums == [12, 34, 56]
-    }
-
-    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
-            import java.math.RoundingMode
-            @OptionField String first
-            @OptionField String last
-            @OptionField boolean flag1
-            @OptionField Boolean flag2
-            @OptionField(longName = 'specialFlag') Boolean flag3
-            @OptionField Boolean flag4
-            @OptionField int age
-            @OptionField Integer born
-            @OptionField float discount
-            @OptionField BigDecimal pi
-            @OptionField File biography
-            @OptionField RoundingMode roundingMode
-            @UnparsedField List remaining
-            new CliBuilder().parseFromInstance(this, args)
-            assert first == 'John'
-            assert last == 'Smith'
-            assert flag1
-            assert flag2
-            assert flag3
-            assert !flag4
-            assert born == 1980
-            assert age == 21
-            assert discount == 3.5f
-            assert pi == 3.14159
-            assert biography == new File('cv.txt')
-            assert roundingMode == RoundingMode.DOWN
-            assert remaining == ['and', 'some', 'more']
-        ''', 'CliBuilderTestScript.groovy', argz)
-    }
-}

http://git-wip-us.apache.org/repos/asf/groovy/blob/00cc9e5e/subprojects/groovy-cli-commons/build.gradle
----------------------------------------------------------------------
diff --git a/subprojects/groovy-cli-commons/build.gradle b/subprojects/groovy-cli-commons/build.gradle
new file mode 100644
index 0000000..c77cf10
--- /dev/null
+++ b/subprojects/groovy-cli-commons/build.gradle
@@ -0,0 +1,24 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+dependencies {
+    compile rootProject
+    compile "commons-cli:commons-cli:$commonsCliVersion"
+    testCompile project(':groovy-test')
+    testCompile project(':groovy-dateutil')
+}