groovy.cli.internal.CliBuilderInternal.groovy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of groovy Show documentation
Show all versions of groovy Show documentation
Groovy: A powerful, dynamic language for the JVM
/*
* 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 cli.usage()
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 cli.usage()
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 @}filename' will be expanded into the arguments contained within the file named filename (default true).
*/
boolean expandArgumentFiles = true
/**
* Configures what the parser should do when arguments not recognized
* as options are encountered: when true
(the default), the
* remaining arguments are all treated as positional parameters.
* When false
, 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
* true
if the parser should recognize long options with both
* a single hyphen and a double hyphen prefix. The default is false
,
* so only long options with a double hypen prefix (--option
) are recognized.
* @since 2.5
*/
boolean acceptLongOptionsWithSingleHyphen = false
/**
* The PrintWriter to write the {@link #usage} help message to
* when cli.usage()
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 savedTypeOptions = new HashMap()
// 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 CliBuilder
and the
* customSynopsis
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 CliBuilder
* 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 CliBuilder
and the
* description
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 CliBuilder
* 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 CliBuilder
* 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 CliBuilder
and the
* posixClusteredShortOptionsAllowed
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 CliBuilder
and the
* stopAtPositional
property on the {@link #parser}
* used by the underlying library.
* @param stopAtNonOption when true
(the default), the
* remaining arguments are all treated as positional parameters.
* When false
, 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 TypedOption option(Map args, Class 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 result = new TypedOption
© 2015 - 2024 Weber Informatics LLC | Privacy Policy