You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2019/07/07 11:00:01 UTC
[groovy] branch master updated: GROOVY-9165: Grape cannot pull in
picocli (don't use stock CliBuilder in our own classes) (closes #962)
This is an automated email from the ASF dual-hosted git repository.
paulk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new bdab243 GROOVY-9165: Grape cannot pull in picocli (don't use stock CliBuilder in our own classes) (closes #962)
bdab243 is described below
commit bdab24389e153567382ac356dab9c997800aa80e
Author: Paul King <pa...@asert.com.au>
AuthorDate: Sun Jul 7 18:56:19 2019 +1000
GROOVY-9165: Grape cannot pull in picocli (don't use stock CliBuilder in our own classes) (closes #962)
---
gradle/assemble.gradle | 6 +-
gradle/docs.gradle | 4 +-
.../groovy/cli/internal/CliBuilderInternal.groovy | 420 +++++++++++++++++++++
.../groovy/cli/internal/OptionAccessor.groovy | 149 ++++++++
subprojects/groovy-cli-picocli/build.gradle | 2 +-
subprojects/groovy-console/build.gradle | 1 -
.../main/groovy/groovy/console/ui/Console.groovy | 6 +-
.../src/main/groovy/groovy/ui/Console.groovy | 6 +-
subprojects/groovy-docgenerator/build.gradle | 1 -
.../apache/groovy/docgenerator/DocGenerator.groovy | 4 +-
subprojects/groovy-groovydoc/build.gradle | 1 -
.../codehaus/groovy/tools/groovydoc/Main.groovy | 4 +-
subprojects/groovy-groovysh/build.gradle | 1 -
.../groovy/org/apache/groovy/groovysh/Main.groovy | 6 +-
.../org/codehaus/groovy/tools/shell/Main.groovy | 6 +-
15 files changed, 592 insertions(+), 25 deletions(-)
diff --git a/gradle/assemble.gradle b/gradle/assemble.gradle
index a1063e4..164fe77 100644
--- a/gradle/assemble.gradle
+++ b/gradle/assemble.gradle
@@ -368,7 +368,8 @@ ext.distSpec = copySpec {
it.file.name.startsWith('openbeans-') ||
it.file.name.startsWith('asm-') ||
it.file.name.startsWith('antlr-') ||
- it.file.name.startsWith('antlr4-')
+ it.file.name.startsWith('antlr4-') ||
+ it.file.name.startsWith('picocli-')
}
}
from('src/bin/groovy.icns')
@@ -382,7 +383,8 @@ ext.distSpec = copySpec {
it.file.name.startsWith('asm-') ||
it.file.name.startsWith('antlr-') ||
it.file.name.startsWith('antlr4-') ||
- it.file.name.startsWith('openbeans-')
+ it.file.name.startsWith('openbeans-') ||
+ it.file.name.startsWith('picocli-')
}
}
}
diff --git a/gradle/docs.gradle b/gradle/docs.gradle
index 8585a28..9b966a8 100644
--- a/gradle/docs.gradle
+++ b/gradle/docs.gradle
@@ -126,12 +126,12 @@ task docProjectVersionInfo(type: Copy) {
task docGDK {
outputs.cacheIf { true }
- dependsOn([project(':groovy-groovydoc'), project(':groovy-docgenerator'), project(':groovy-cli-picocli')]*.classes)
+ dependsOn([project(':groovy-groovydoc'), project(':groovy-docgenerator')]*.classes)
dependsOn docProjectVersionInfo
ext.destinationDir = "$buildDir/html/groovy-jdk"
inputs.files sourceSets.main.runtimeClasspath + configurations.tools + files(docProjectVersionInfo.destinationDir)
outputs.dir destinationDir
- def docGeneratorPath = files(project(':groovy-docgenerator').sourceSets.main.output.classesDirs) + files(project(':groovy-cli-picocli').sourceSets.main.output.classesDirs)
+ def docGeneratorPath = files(project(':groovy-docgenerator').sourceSets.main.output.classesDirs)
doLast { task ->
try {
ant {
diff --git a/src/main/groovy/groovy/cli/internal/CliBuilderInternal.groovy b/src/main/groovy/groovy/cli/internal/CliBuilderInternal.groovy
new file mode 100644
index 0000000..a3bc3ee
--- /dev/null
+++ b/src/main/groovy/groovy/cli/internal/CliBuilderInternal.groovy
@@ -0,0 +1,420 @@
+/*
+ * 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.cli.internal
+
+import groovy.cli.CliBuilderException
+import groovy.cli.TypedOption
+import org.codehaus.groovy.runtime.InvokerHelper
+import picocli.CommandLine
+
+/**
+ * Cut-down version of CliBuilder with just enough functionality for Groovy's internal usage.
+ * Uses the embedded version of picocli classes.
+ * TODO: prune this right back to have only the functionality needed by Groovy commandline tools
+ */
+class CliBuilderInternal {
+ /**
+ * The command synopsis displayed as the first line in the usage help message, e.g., when <code>cli.usage()</code> is called.
+ * When not set, a default synopsis is generated that shows the supported options and parameters.
+ * @see #name
+ */
+ String usage = 'groovy'
+
+ /**
+ * This property allows customizing the program name displayed in the synopsis when <code>cli.usage()</code> is called.
+ * Ignored if the {@link #usage} property is set.
+ * @since 2.5
+ */
+ String name = 'groovy'
+
+ /**
+ * To disallow clustered POSIX short options, set this to false.
+ */
+ Boolean posix = true
+
+ /**
+ * 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
+
+ /**
+ * Configures what the parser should do when arguments not recognized
+ * as options are encountered: when <code>true</code> (the default), the
+ * remaining arguments are all treated as positional parameters.
+ * When <code>false</code>, the parser will continue to look for options, and
+ * only the unrecognized arguments are treated as positional parameters.
+ */
+ boolean stopAtNonOption = true
+
+ /**
+ * For backwards compatibility with Apache Commons CLI, set this property to
+ * <code>true</code> if the parser should recognize long options with both
+ * a single hyphen and a double hyphen prefix. The default is <code>false</code>,
+ * so only long options with a double hypen prefix (<code>--option</code>) are recognized.
+ * @since 2.5
+ */
+ boolean acceptLongOptionsWithSingleHyphen = false
+
+ /**
+ * The PrintWriter to write the {@link #usage} help message to
+ * when <code>cli.usage()</code> is called.
+ * Defaults to stdout but you can provide your own PrintWriter if desired.
+ */
+ PrintWriter writer = new PrintWriter(System.out)
+
+ /**
+ * The PrintWriter to write to when invalid user input was provided to
+ * the {@link #parse(java.lang.String[])} method.
+ * Defaults to stderr but you can provide your own PrintWriter if desired.
+ * @since 2.5
+ */
+ PrintWriter errorWriter = new PrintWriter(System.err)
+
+ /**
+ * Optional additional message for usage; displayed after the usage summary
+ * but before the options are displayed.
+ */
+ String header = null
+
+ /**
+ * Optional additional message for usage; displayed after the options.
+ */
+ String footer = null
+
+ /**
+ * Allows customisation of the usage message width.
+ */
+ int width = CommandLine.Model.UsageMessageSpec.DEFAULT_USAGE_WIDTH
+
+ /**
+ * Not normally accessed directly but allows fine-grained control over the
+ * parser behaviour via the API of the underlying library if needed.
+ * @since 2.5
+ */
+ // Implementation note: this object is separate from the CommandSpec.
+ // The values collected here are copied into the ParserSpec of the command.
+ final CommandLine.Model.ParserSpec parser = new CommandLine.Model.ParserSpec()
+ .stopAtPositional(true)
+ .unmatchedOptionsArePositionalParams(true)
+ .aritySatisfiedByAttachedOptionParam(true)
+ .limitSplit(true)
+ .overwrittenOptionsAllowed(true)
+ .toggleBooleanFlags(false)
+
+ /**
+ * Not normally accessed directly but allows fine-grained control over the
+ * usage help message via the API of the underlying library if needed.
+ * @since 2.5
+ */
+ // Implementation note: this object is separate from the CommandSpec.
+ // The values collected here are copied into the UsageMessageSpec of the command.
+ final CommandLine.Model.UsageMessageSpec usageMessage = new CommandLine.Model.UsageMessageSpec()
+
+ /**
+ * Internal data structure mapping option names to their associated {@link groovy.cli.TypedOption} object.
+ */
+ Map<String, TypedOption> savedTypeOptions = new HashMap<String, TypedOption>()
+
+ // CommandSpec is the entry point into the picocli object model for a command.
+ // It gives access to a ParserSpec to customize the parser behaviour and
+ // a UsageMessageSpec to customize the usage help message.
+ // Add OptionSpec and PositionalParamSpec objects to this object to define
+ // the options and positional parameters this command recognizes.
+ //
+ // This field is private for now.
+ // It is initialized to an empty spec so options and positional parameter specs
+ // can be added dynamically via the programmatic API.
+ // When a command spec is defined via annotations, the existing instance is
+ // replaced with a new one. This allows the outer CliBuilder instance can be reused.
+ private CommandLine.Model.CommandSpec commandSpec = CommandLine.Model.CommandSpec.create()
+
+ /**
+ * Sets the {@link #usage usage} property on this <code>CliBuilder</code> and the
+ * <code>customSynopsis</code> on the {@link #usageMessage} used by the underlying library.
+ * @param usage the custom synopsis of the usage help message
+ */
+ void setUsage(String usage) {
+ this.usage = usage
+ usageMessage.customSynopsis(usage)
+ }
+
+ /**
+ * Sets the {@link #footer} property on this <code>CliBuilder</code>
+ * and on the {@link #usageMessage} used by the underlying library.
+ * @param footer the footer of the usage help message
+ */
+ void setFooter(String footer) {
+ this.footer = footer
+ usageMessage.footer(footer)
+ }
+
+ /**
+ * Sets the {@link #header} property on this <code>CliBuilder</code> and the
+ * <code>description</code> on the {@link #usageMessage} used by the underlying library.
+ * @param header the description text of the usage help message
+ */
+ void setHeader(String header) {
+ this.header = header
+ // "header" is displayed after the synopsis in previous CliBuilder versions.
+ // The picocli equivalent is the "description".
+ usageMessage.description(header)
+ }
+
+ /**
+ * Sets the {@link #width} property on this <code>CliBuilder</code>
+ * and on the {@link #usageMessage} used by the underlying library.
+ * @param width the width of the usage help message
+ */
+ void setWidth(int width) {
+ this.width = width
+ usageMessage.width(width)
+ }
+
+ /**
+ * Sets the {@link #expandArgumentFiles} property on this <code>CliBuilder</code>
+ * and on the {@link #parser} used by the underlying library.
+ * @param expand whether to expand argument @-files
+ */
+ void setExpandArgumentFiles(boolean expand) {
+ this.expandArgumentFiles = expand
+ parser.expandAtFiles(expand)
+ }
+
+ /**
+ * Sets the {@link #posix} property on this <code>CliBuilder</code> and the
+ * <code>posixClusteredShortOptionsAllowed</code> property on the {@link #parser}
+ * used by the underlying library.
+ * @param posix whether to allow clustered short options
+ */
+ void setPosix(Boolean posix) {
+ this.posix = posix
+ parser.posixClusteredShortOptionsAllowed(posix ?: false)
+ }
+
+ /**
+ * Sets the {@link #stopAtNonOption} property on this <code>CliBuilder</code> and the
+ * <code>stopAtPositional</code> property on the {@link #parser}
+ * used by the underlying library.
+ * @param stopAtNonOption when <code>true</code> (the default), the
+ * remaining arguments are all treated as positional parameters.
+ * When <code>false</code>, the parser will continue to look for options, and
+ * only the unrecognized arguments are treated as positional parameters.
+ */
+ void setStopAtNonOption(boolean stopAtNonOption) {
+ this.stopAtNonOption = stopAtNonOption
+ parser.stopAtPositional(stopAtNonOption)
+ parser.unmatchedOptionsArePositionalParams(stopAtNonOption)
+ }
+
+ /**
+ * For backwards compatibility reasons, if a custom {@code writer} is set, this sets
+ * both the {@link #writer} and the {@link #errorWriter} to the specified writer.
+ * @param writer the writer to initialize both the {@code writer} and the {@code errorWriter} to
+ */
+ void setWriter(PrintWriter writer) {
+ this.writer = writer
+ this.errorWriter = writer
+ }
+
+ 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]) // args[0] is description
+ commandSpec.addOption(option)
+ return create(option, null, null, null)
+ }
+ if (args.size() == 1 && args[0] instanceof CommandLine.Model.OptionSpec && name == 'leftShift') {
+ CommandLine.Model.OptionSpec option = args[0] as CommandLine.Model.OptionSpec
+ commandSpec.addOption(option)
+ return create(option, null, null, null)
+ }
+ if (args.size() == 2 && args[0] instanceof Map) {
+ Map m = args[0] as Map
+ if (m.type && !(m.type instanceof Class)) {
+ throw new CliBuilderException("'type' must be a Class")
+ }
+ def option = option(name, m, args[1])
+ commandSpec.addOption(option)
+ return create(option, m.type, option.defaultValue(), option.converters())
+ }
+ }
+ return InvokerHelper.getMetaClass(this).invokeMethod(this, name, args)
+ }
+
+ private TypedOption create(CommandLine.Model.OptionSpec o, Class theType, defaultValue, convert) {
+ String opt = o.names().sort { a, b -> a.length() - b.length() }.first()
+ opt = opt?.length() == 2 ? opt.substring(1) : null
+
+ String longOpt = o.names().sort { a, b -> b.length() - a.length() }.first()
+ longOpt = longOpt?.startsWith("--") ? longOpt.substring(2) : null
+
+ Map<String, Object> result = new TypedOption<Object>()
+ 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[longOpt ?: opt] = result
+ result
+ }
+
+ /**
+ * Make options accessible from command line args with parser.
+ * Returns null on bad command lines after displaying usage message.
+ */
+ OptionAccessor parse(args) {
+ CommandLine commandLine = createCommandLine()
+ try {
+ def accessor = new OptionAccessor(commandLine.parseArgs(args as String[]))
+ accessor.savedTypeOptions = savedTypeOptions
+ return accessor
+ } catch (CommandLine.ParameterException pe) {
+ errorWriter.println("error: " + pe.message)
+ printUsage(pe.commandLine, errorWriter)
+ return null
+ }
+ }
+
+ private CommandLine createCommandLine() {
+ commandSpec.parser(parser)
+ commandSpec.name(name).usageMessage(usageMessage)
+ if (commandSpec.positionalParameters().empty) {
+ commandSpec.addPositional(CommandLine.Model.PositionalParamSpec.builder().type(String[]).arity("*").paramLabel("P").hidden(true).build())
+ }
+ return new CommandLine(commandSpec)
+ }
+
+ /**
+ * Prints the usage message with the specified {@link #header header}, {@link #footer footer} and {@link #width width}
+ * to the specified {@link #writer writer} (default: System.out).
+ */
+ void usage() {
+ printUsage(commandSpec.commandLine() ?: createCommandLine(), writer)
+ }
+
+ private void printUsage(CommandLine commandLine, PrintWriter pw) {
+ commandLine.usage(pw)
+ pw.flush()
+ }
+
+ private static class ArgSpecAttributes {
+ Class type
+ Class[] auxiliaryTypes
+ String label
+ CommandLine.Model.IGetter getter
+ CommandLine.Model.ISetter setter
+ Object initialValue
+ boolean hasInitialValue
+ }
+
+ // implementation details -------------------------------------
+ /**
+ * Internal method: How to create an OptionSpec from the specification.
+ */
+ CommandLine.Model.OptionSpec option(shortname, Map details, description) {
+ CommandLine.Model.OptionSpec.Builder builder
+ if (shortname == '_') {
+ builder = CommandLine.Model.OptionSpec.builder("--$details.longOpt").description(description)
+ if (acceptLongOptionsWithSingleHyphen) {
+ builder.names("-$details.longOpt", "--$details.longOpt")
+ }
+ details.remove('longOpt')
+ } else {
+ builder = CommandLine.Model.OptionSpec.builder("-$shortname").description(description)
+ }
+ commons2picocli(shortname, details).each { key, value ->
+ if (builder.hasProperty(key)) {
+ builder[key] = value
+ } else if (key != 'opt') { // GROOVY-8607 ignore opt since we already have that
+ builder.invokeMethod(key, value)
+ }
+ }
+ if (!builder.type() && !builder.arity() && builder.converters()?.length > 0) {
+ builder.arity("1").type(details.convert ? Object : String[])
+ }
+ return builder.build()
+ }
+
+ /** Commons-cli constant that specifies the number of argument values is infinite */
+ private static final int COMMONS_CLI_UNLIMITED_VALUES = -2
+
+ // - argName: String
+ // - longOpt: String
+ // - args: int or String
+ // - optionalArg: boolean
+ // - required: boolean
+ // - type: Class
+ // - valueSeparator: char
+ // - convert: Closure
+ // - defaultValue: String
+ private Map commons2picocli(shortname, Map m) {
+ if (m.args && m.optionalArg) {
+ m.arity = "0..${m.args}"
+ m.remove('args')
+ m.remove('optionalArg')
+ }
+ if (!m.defaultValue) {
+ m.remove('defaultValue') // don't default the picocli model to empty string
+ }
+ def result = m.collectMany { k, v ->
+ if (k == 'args' && v == '+') {
+ [[arity: '1..*']]
+ } else if (k == 'args' && v == 0) {
+ [[arity: '0']]
+ } else if (k == 'args') {
+ v == COMMONS_CLI_UNLIMITED_VALUES ? [[arity: "*"]] : [[arity: "$v"]]
+ } else if (k == 'optionalArg') {
+ v ? [[arity: '0..1']] : [[arity: '1']]
+ } else if (k == 'argName') {
+ [[paramLabel: "<$v>"]]
+ } else if (k == 'longOpt') {
+ acceptLongOptionsWithSingleHyphen ?
+ [[names: ["-$shortname", "-$v", "--$v"] as String[] ]] :
+ [[names: ["-$shortname", "--$v"] as String[] ]]
+ } else if (k == 'valueSeparator') {
+ [[splitRegex: "$v"]]
+ } else if (k == 'convert') {
+ [[converters: [v] as CommandLine.ITypeConverter[] ]]
+ } else {
+ [[(k): v]]
+ }
+ }.sum() as Map
+ result
+ }
+}
diff --git a/src/main/groovy/groovy/cli/internal/OptionAccessor.groovy b/src/main/groovy/groovy/cli/internal/OptionAccessor.groovy
new file mode 100644
index 0000000..af6105a
--- /dev/null
+++ b/src/main/groovy/groovy/cli/internal/OptionAccessor.groovy
@@ -0,0 +1,149 @@
+/*
+ * 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.cli.internal
+
+import groovy.cli.TypedOption
+import org.codehaus.groovy.runtime.InvokerHelper
+import org.codehaus.groovy.runtime.StringGroovyMethods
+import picocli.CommandLine.Model.OptionSpec
+import picocli.CommandLine.ParseResult
+
+class OptionAccessor {
+ ParseResult parseResult
+ Map<String, TypedOption> savedTypeOptions
+
+ OptionAccessor(ParseResult parseResult) {
+ this.parseResult = parseResult
+ }
+
+ boolean hasOption(TypedOption typedOption) {
+ parseResult.hasMatchedOption(typedOption.longOpt ?: typedOption.opt as String)
+ }
+
+ 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 (parseResult.hasMatchedOption(optionName)) {
+ return parseResult.matchedOptionValue(optionName, defaultValue)
+ } else {
+ OptionSpec option = parseResult.commandSpec().findOption(optionName)
+ return option ? option.value : defaultValue
+ }
+ }
+
+ public <T> T getAt(TypedOption<T> typedOption) {
+ getAt(typedOption, null)
+ }
+
+ public <T> T getAt(TypedOption<T> typedOption, T defaultValue) {
+ getOptionValue(typedOption, defaultValue)
+ }
+
+ private <T> T getTypedValue(Class<T> type, String optionName, String optionValue) {
+ if (savedTypeOptions[optionName]?.cliOption?.arity?.min == 0) { // TODO is this not a bug?
+ return (T) parseResult.hasMatchedOption(optionName) // TODO should defaultValue not simply convert the type regardless of the matched value?
+ }
+ 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 == Boolean || type == Boolean.TYPE) {
+ return type.cast(Boolean.parseBoolean(optionValue))
+ }
+ StringGroovyMethods.asType(optionValue, (Class<T>) type)
+ }
+
+ Properties getOptionProperties(String name) {
+ if (!parseResult.hasMatchedOption(name)) {
+ return null
+ }
+ List<String> keyValues = parseResult.matchedOption(name).stringValues()
+ Properties result = new Properties()
+ keyValues.toSpreadMap().each { k, v -> result.setProperty(k, v) }
+ result
+ }
+
+ def invokeMethod(String name, Object args) {
+ // TODO we could just declare normal methods to map commons-cli CommandLine methods to picocli ParseResult methods
+ if (name == 'hasOption') { name = 'hasMatchedOption'; args = [args[0] ].toArray() }
+ if (name == 'getOptionValue') { name = 'matchedOptionValue'; args = [args[0], null].toArray() }
+ return InvokerHelper.getMetaClass(parseResult).invokeMethod(parseResult, name, args)
+ }
+
+ def getProperty(String name) {
+ if (name == 'parseResult') { return parseResult }
+ if (parseResult.hasMatchedOption(name)) {
+ def result = parseResult.matchedOptionValue(name, null)
+
+ // if user specified an array type, return the full array (regardless of 's' suffix on name)
+ Class userSpecifiedType = savedTypeOptions[name]?.type
+ if (userSpecifiedType?.isArray()) { return result }
+
+ // otherwise, if the result is multi-value, return the first value
+ Class derivedType = parseResult.matchedOption(name).type()
+ if (derivedType.isArray()) {
+ return result ? result[0] : null
+ } else if (Collection.class.isAssignableFrom(derivedType)) {
+ return (result as Collection)?.first()
+ }
+ if (!userSpecifiedType && result == '' && parseResult.matchedOption(name).arity().min == 0) {
+ return true
+ }
+ return parseResult.matchedOption(name).typedValues().get(0)
+ }
+ if (parseResult.commandSpec().findOption(name)) { // requested option was not matched: return its default
+ def option = parseResult.commandSpec().findOption(name)
+ def result = option.value
+ return result ? result : false
+ }
+ if (name.size() > 1 && name.endsWith('s')) { // user wants multi-value result
+ def singularName = name[0..-2]
+ if (parseResult.hasMatchedOption(singularName)) {
+ // if picocli has a strongly typed multi-value result, return it
+ Class type = parseResult.matchedOption(singularName).type()
+ if (type.isArray() || Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type)) {
+ return parseResult.matchedOptionValue(singularName, null)
+ }
+ // otherwise, return the raw string values as a list
+ return parseResult.matchedOption(singularName).stringValues()
+ }
+ }
+ false
+ }
+
+ List<String> arguments() {
+ parseResult.hasMatchedPositional(0) ? parseResult.matchedPositional(0).stringValues() : []
+ }
+}
diff --git a/subprojects/groovy-cli-picocli/build.gradle b/subprojects/groovy-cli-picocli/build.gradle
index d83ddfd..87f0177 100644
--- a/subprojects/groovy-cli-picocli/build.gradle
+++ b/subprojects/groovy-cli-picocli/build.gradle
@@ -18,7 +18,7 @@
*/
dependencies {
compile rootProject
- compile "info.picocli:picocli:$picocliVersion"
+ provided "info.picocli:picocli:$picocliVersion"
testCompile rootProject.sourceSets.test.output
testCompile project(':groovy-test')
testCompile project(':groovy-dateutil')
diff --git a/subprojects/groovy-console/build.gradle b/subprojects/groovy-console/build.gradle
index 44f9bdb..a8ace80 100644
--- a/subprojects/groovy-console/build.gradle
+++ b/subprojects/groovy-console/build.gradle
@@ -20,7 +20,6 @@ evaluationDependsOn(':groovy-swing')
dependencies {
compile rootProject
- compile project(':groovy-cli-picocli')
compile project(':groovy-swing')
compile project(':groovy-templates')
testCompile project(':groovy-test')
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/Console.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/Console.groovy
index 7c82101..f194342 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/Console.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/Console.groovy
@@ -18,8 +18,8 @@
*/
package groovy.console.ui
-import groovy.cli.picocli.CliBuilder
-import groovy.cli.picocli.OptionAccessor
+import groovy.cli.internal.CliBuilderInternal
+import groovy.cli.internal.OptionAccessor
import groovy.console.ui.text.FindReplaceUtility
import groovy.console.ui.text.GroovyFilter
import groovy.console.ui.text.SmartDocumentFilter
@@ -235,7 +235,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
static void main(args) {
MessageSource messages = new MessageSource(Console)
- CliBuilder cli = new CliBuilder(usage: 'groovyConsole [options] [filename]', stopAtNonOption: false,
+ def cli = new CliBuilderInternal(usage: 'groovyConsole [options] [filename]', stopAtNonOption: false,
header: messages['cli.option.header'])
cli.with {
_(names: ['-cp', '-classpath', '--classpath'], messages['cli.option.classpath.description'])
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/ui/Console.groovy b/subprojects/groovy-console/src/main/groovy/groovy/ui/Console.groovy
index 52d83e6..181ef6a 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/ui/Console.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/ui/Console.groovy
@@ -18,8 +18,8 @@
*/
package groovy.ui
-import groovy.cli.picocli.CliBuilder
-import groovy.cli.picocli.OptionAccessor
+import groovy.cli.internal.CliBuilderInternal
+import groovy.cli.internal.OptionAccessor
import groovy.inspect.swingui.AstBrowser
import groovy.inspect.swingui.ObjectBrowser
import groovy.swing.SwingBuilder
@@ -229,7 +229,7 @@ class Console implements CaretListener, HyperlinkListener, ComponentListener, Fo
static void main(args) {
MessageSource messages = new MessageSource(Console)
- CliBuilder cli = new CliBuilder(usage: 'groovyConsole [options] [filename]', stopAtNonOption: false,
+ def cli = new CliBuilderInternal(usage: 'groovyConsole [options] [filename]', stopAtNonOption: false,
header: messages['cli.option.header'])
cli.with {
_(names: ['-cp', '-classpath', '--classpath'], messages['cli.option.classpath.description'])
diff --git a/subprojects/groovy-docgenerator/build.gradle b/subprojects/groovy-docgenerator/build.gradle
index 0d270e4..3106ed0 100644
--- a/subprojects/groovy-docgenerator/build.gradle
+++ b/subprojects/groovy-docgenerator/build.gradle
@@ -18,7 +18,6 @@
*/
dependencies {
compile rootProject
- compile project(':groovy-cli-picocli')
compile project(':groovy-templates')
testCompile project(':groovy-test')
compile "com.thoughtworks.qdox:qdox:$qdoxVersion"
diff --git a/subprojects/groovy-docgenerator/src/main/groovy/org/apache/groovy/docgenerator/DocGenerator.groovy b/subprojects/groovy-docgenerator/src/main/groovy/org/apache/groovy/docgenerator/DocGenerator.groovy
index da325d7..b02f60c 100644
--- a/subprojects/groovy-docgenerator/src/main/groovy/org/apache/groovy/docgenerator/DocGenerator.groovy
+++ b/subprojects/groovy-docgenerator/src/main/groovy/org/apache/groovy/docgenerator/DocGenerator.groovy
@@ -23,7 +23,7 @@ import com.thoughtworks.qdox.model.JavaClass
import com.thoughtworks.qdox.model.JavaMethod
import com.thoughtworks.qdox.model.JavaParameter
import com.thoughtworks.qdox.model.Type
-import groovy.cli.picocli.CliBuilder
+import groovy.cli.internal.CliBuilderInternal
import groovy.text.SimpleTemplateEngine
import groovy.text.Template
import groovy.text.TemplateEngine
@@ -207,7 +207,7 @@ class DocGenerator {
* Main entry point.
*/
static void main(String... args) {
- def cli = new CliBuilder(usage : 'DocGenerator [options] [sourcefiles]', posix:false)
+ def cli = new CliBuilderInternal(usage : 'DocGenerator [options] [sourcefiles]', posix:false)
cli.help(longOpt: 'help', messages['cli.option.help.description'])
cli._(longOpt: 'version', messages['cli.option.version.description'])
cli.o(longOpt: 'outputDir', args:1, argName: 'path', messages['cli.option.output.dir.description'])
diff --git a/subprojects/groovy-groovydoc/build.gradle b/subprojects/groovy-groovydoc/build.gradle
index 80696b1..77938d4 100644
--- a/subprojects/groovy-groovydoc/build.gradle
+++ b/subprojects/groovy-groovydoc/build.gradle
@@ -19,7 +19,6 @@
dependencies {
compile rootProject
testCompile rootProject.sourceSets.test.runtimeClasspath
- compile project(':groovy-cli-picocli')
compile project(':groovy-templates')
runtime project(':groovy-docgenerator')
testCompile project(':groovy-test')
diff --git a/subprojects/groovy-groovydoc/src/main/groovy/org/codehaus/groovy/tools/groovydoc/Main.groovy b/subprojects/groovy-groovydoc/src/main/groovy/org/codehaus/groovy/tools/groovydoc/Main.groovy
index 4646dec..5a5bcb8 100644
--- a/subprojects/groovy-groovydoc/src/main/groovy/org/codehaus/groovy/tools/groovydoc/Main.groovy
+++ b/subprojects/groovy-groovydoc/src/main/groovy/org/codehaus/groovy/tools/groovydoc/Main.groovy
@@ -18,7 +18,7 @@
*/
package org.codehaus.groovy.tools.groovydoc
-import groovy.cli.picocli.CliBuilder
+import groovy.cli.internal.CliBuilderInternal
import groovy.io.FileType
import org.codehaus.groovy.tools.groovydoc.gstringTemplates.GroovyDocTemplateInfo
import org.codehaus.groovy.tools.shell.IO
@@ -58,7 +58,7 @@ class Main {
IO io = new IO()
Logger.io = io
- def cli = new CliBuilder(usage: 'groovydoc [options] [packagenames] [sourcefiles]', writer: io.out, posix: false,
+ def cli = new CliBuilderInternal(usage: 'groovydoc [options] [packagenames] [sourcefiles]', writer: io.out, posix: false,
header: messages['cli.option.header'])
cli._(names: ['-h', '-help', '--help'], messages['cli.option.help.description'])
diff --git a/subprojects/groovy-groovysh/build.gradle b/subprojects/groovy-groovysh/build.gradle
index 2b7dfbb..7cec73f 100644
--- a/subprojects/groovy-groovysh/build.gradle
+++ b/subprojects/groovy-groovysh/build.gradle
@@ -18,7 +18,6 @@
*/
dependencies {
compile rootProject
- compile project(':groovy-cli-picocli')
compile project(':groovy-console')
testCompile project(':groovy-test')
compile("jline:jline:$jlineVersion") {
diff --git a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Main.groovy b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Main.groovy
index fc3d5b8..a36ffce 100644
--- a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Main.groovy
+++ b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Main.groovy
@@ -18,8 +18,8 @@
*/
package org.apache.groovy.groovysh
-import groovy.cli.picocli.CliBuilder
-import groovy.cli.picocli.OptionAccessor
+import groovy.cli.internal.CliBuilderInternal
+import groovy.cli.internal.OptionAccessor
import jline.TerminalFactory
import jline.UnixTerminal
import jline.UnsupportedTerminal
@@ -72,7 +72,7 @@ class Main {
*/
static void main(final String[] args) {
MessageSource messages = new MessageSource(Main)
- CliBuilder cli = new CliBuilder(usage: 'groovysh [options] [...]', stopAtNonOption: false,
+ def cli = new CliBuilderInternal(usage: 'groovysh [options] [...]', stopAtNonOption: false,
header: messages['cli.option.header'])
cli.with {
_(names: ['-cp', '-classpath', '--classpath'], messages['cli.option.classpath.description'])
diff --git a/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/Main.groovy b/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/Main.groovy
index 8017171..6d8bd81 100644
--- a/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/Main.groovy
+++ b/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/Main.groovy
@@ -18,8 +18,8 @@
*/
package org.codehaus.groovy.tools.shell
-import groovy.cli.picocli.CliBuilder
-import groovy.cli.picocli.OptionAccessor
+import groovy.cli.internal.CliBuilderInternal
+import groovy.cli.internal.OptionAccessor
import jline.TerminalFactory
import jline.UnixTerminal
import jline.UnsupportedTerminal
@@ -72,7 +72,7 @@ class Main {
*/
static void main(final String[] args) {
MessageSource messages = new MessageSource(Main)
- CliBuilder cli = new CliBuilder(usage: 'groovysh [options] [...]', stopAtNonOption: false,
+ def cli = new CliBuilderInternal(usage: 'groovysh [options] [...]', stopAtNonOption: false,
header: messages['cli.option.header'])
cli.with {
_(names: ['-cp', '-classpath', '--classpath'], messages['cli.option.classpath.description'])