All Downloads are FREE. Search and download functionalities are using the official Maven repository.

groovy.util.CliBuilder.groovy Maven / Gradle / Ivy

There is a newer version: 3.9
Show newest version
/*
 * Copyright 2003-2013 the original author or authors.
 *
 * Licensed 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 org.apache.commons.cli.*
import org.codehaus.groovy.cli.GroovyPosixParser
import org.codehaus.groovy.runtime.InvokerHelper

/**
 * Provides a builder to assist the processing of command line arguments.
 * 

* Typical usage (emulate partial arg processing of unix command: ls -alt *.groovy): *

 * 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
 * 
* The usage message for this example (obtained using cli.usage()) is shown below: *
 * usage: ls
 *  -a   display all files
 *  -l   use a long listing format
 *  -t   sort by modification time
 * 
* 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 setting * the posix property on the CliBuilder to false, i.e. include * posix: false in the constructor call. *

* Another example (partial emulation of arg processing for 'ant' command line): *

 * 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)
 * ...
 * 
* Usage message would be: *
 * usage: ant [options] [targets]
 * Options:
 *  -D <property=value>   use value for given property
 *  -help                 print this message
 *  -logfile <file>       use given file for log
 * 
* And if called with the following arguments '-logfile foo -Dbar=baz target' * then the following assertions would be true: *
 * assert options // would be null (false) on failure
 * assert options.arguments() == ['target']
 * assert options.Ds == ['bar', 'baz']
 * assert options.logfile == 'foo'
 * 
* 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. *

* Another example showing long options (partial emulation of arg processing for 'curl' command line): *

 * def cli = new CliBuilder(usage:'curl [options] <url>')
 * 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')
 * 
* Which has the following usage message: *
 * usage: curl [options] <url>
 *     --basic         Use HTTP Basic Authentication
 *  -d,--data <data>   HTTP POST data
 *  -G,--get           Send the -d data with a HTTP GET
 *  -q                 If used as the first parameter disables .curlrc
 *     --url <URL>     Set URL to work with
 * 
* 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. * -Ddebug=true. The example also shows * the use of '_' when no short option is applicable. *

* 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. *

* 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 options.hasOption('max-wait') or surround * the option name in quotes, e.g. options.'max-wait'. *

* 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: *

 * import org.apache.commons.cli.*
 * ... as before ...
 * cli << new Option('q', false, 'If used as the first parameter disables .curlrc')
 * cli << OptionBuilder.withLongOpt('url').hasArg().withArgName('URL').
 *                      withDescription('Set URL to work with').create()
 * ...
 * 
* * 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: *
 * -arg1
 * paramA
 * paramB paramC
 * 
* Then calling the command line with: *
 * someCommand @temp.args -arg2 paramD
 * 
* Is the same as calling this: *
 * someCommand -arg1 paramA paramB paramC -arg2 paramD
 * 
* 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. *

* Supported Option Properties: *

 *   argName:        String
 *   longOpt:        String
 *   args:           int
 *   optionalArg:    boolean
 *   required:       boolean
 *   type:           Object (not currently used)
 *   valueSeparator: char
 * 
* See {@link org.apache.commons.cli.Option} for the meaning of these properties * and {@link CliBuilderTest} for further examples. *

* * @author Dierk Koenig * @author Paul King */ class CliBuilder { /** * Usage summary displayed as the first line when cli.usage() 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 @}filename' will be expanded into the arguments contained within the file named filename (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 = formatter.defaultWidth /** * Not normally accessed directly but full access to underlying options if needed. */ Options options = new Options() /** * 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)) { options.addOption(option(name, [:], args[0])) return null } if (args.size() == 1 && args[0] instanceof Option && name == 'leftShift') { options.addOption(args[0]) return null } if (args.size() == 2 && args[0] instanceof Map) { options.addOption(option(name, args[0], args[1])) return null } } 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 ? new GroovyPosixParser() : posix == true ? new PosixParser() : new GnuParser() } try { return new OptionAccessor(parser.parse(options, args as String[], stopAtNonOption)) } 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, formatter.defaultLeftPad, formatter.defaultDescPad, footer) writer.flush() } // implementation details ------------------------------------- /** * Internal method: How to create an option from the specification. */ Option option(shortname, Map details, info) { Option option if (shortname == '_') { option = OptionBuilder.withDescription(info).withLongOpt(details.longOpt).create() details.remove('longOpt') } else { option = new Option(shortname, info) } details.each { key, value -> option[key] = value } return option } 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 inner OptionAccessor(CommandLine inner) { this.inner = inner } def invokeMethod(String name, Object args) { return InvokerHelper.getMetaClass(inner).invokeMethod(inner, name, args) } def getProperty(String name) { def methodname = 'getOptionValue' if (name.size() > 1 && name.endsWith('s')) { def singularName = name[0..-2] if (hasOption(singularName)) { name = singularName methodname += 's' } } if (name.size() == 1) name = name as char def result = InvokerHelper.getMetaClass(inner).invokeMethod(inner, methodname, name) if (null == result) result = inner.hasOption(name) if (result instanceof String[]) result = result.toList() return result } List arguments() { inner.args.toList() } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy