Picocli is designed to be included as source with minimal impact. That's why it's a single source file.
Users should not have to specify a command line parser library jar just to run our Log4j2 utility applications. > On Aug 15, 2017, at 2:06, Matt Sicker <boa...@gmail.com> wrote: > > Embedding a single class? I don't see the problem with that. We do it with > several Commons classes. > >> On 14 August 2017 at 11:59, Gary Gregory <garydgreg...@gmail.com> wrote: >> >> Wait a minute? We are embedding a third party jar? Yuk! -1, sorry that is >> not what I thought was happening. >> >> Gary >> >>> On Aug 14, 2017 10:18, <rpo...@apache.org> wrote: >>> >>> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ >>> c2818bec/log4j-core/src/main/java/org/apache/logging/log4j/ >>> core/util/picocli/CommandLine.java >>> ---------------------------------------------------------------------- >>> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/ >> util/picocli/CommandLine.java >>> b/log4j-core/src/main/java/org/apache/logging/log4j/core/ >>> util/picocli/CommandLine.java >>> new file mode 100644 >>> index 0000000..5c4e9cc >>> --- /dev/null >>> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/ >>> util/picocli/CommandLine.java >>> @@ -0,0 +1,3900 @@ >>> +/* >>> + * 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 org.apache.logging.log4j.core.util.picocli; >>> + >>> +import java.awt.Point; >>> +import java.io.File; >>> +import java.io.PrintStream; >>> +import java.lang.annotation.ElementType; >>> +import java.lang.annotation.Retention; >>> +import java.lang.annotation.RetentionPolicy; >>> +import java.lang.annotation.Target; >>> +import java.lang.reflect.Array; >>> +import java.lang.reflect.Constructor; >>> +import java.lang.reflect.Field; >>> +import java.math.BigDecimal; >>> +import java.math.BigInteger; >>> +import java.net.InetAddress; >>> +import java.net.MalformedURLException; >>> +import java.net.URI; >>> +import java.net.URISyntaxException; >>> +import java.net.URL; >>> +import java.nio.charset.Charset; >>> +import java.nio.file.Path; >>> +import java.nio.file.Paths; >>> +import java.sql.Time; >>> +import java.text.BreakIterator; >>> +import java.text.ParseException; >>> +import java.text.SimpleDateFormat; >>> +import java.util.ArrayList; >>> +import java.util.Arrays; >>> +import java.util.Collection; >>> +import java.util.Collections; >>> +import java.util.Comparator; >>> +import java.util.Date; >>> +import java.util.HashMap; >>> +import java.util.HashSet; >>> +import java.util.LinkedHashMap; >>> +import java.util.LinkedList; >>> +import java.util.List; >>> +import java.util.Map; >>> +import java.util.Queue; >>> +import java.util.Set; >>> +import java.util.SortedMap; >>> +import java.util.SortedSet; >>> +import java.util.Stack; >>> +import java.util.TreeMap; >>> +import java.util.TreeSet; >>> +import java.util.UUID; >>> +import java.util.regex.Pattern; >>> + >>> +import org.apache.logging.log4j.core.util.picocli.CommandLine.Help. >> Ansi; >>> +import org.apache.logging.log4j.core.util.picocli.CommandLine.Help. >>> Ansi.IStyle; >>> +import org.apache.logging.log4j.core.util.picocli.CommandLine.Help. >>> Ansi.Style; >>> +import org.apache.logging.log4j.core.util.picocli.CommandLine.Help. >>> Ansi.Text; >>> + >>> +import static java.util.Locale.ENGLISH; >>> +import static org.apache.logging.log4j.core. >>> util.picocli.CommandLine.Help.Column.Overflow.SPAN; >>> +import static org.apache.logging.log4j.core. >>> util.picocli.CommandLine.Help.Column.Overflow.TRUNCATE; >>> +import static org.apache.logging.log4j.core. >>> util.picocli.CommandLine.Help.Column.Overflow.WRAP; >>> + >>> +/** >>> + * <p> >>> + * CommandLine interpreter that uses reflection to initialize an >>> annotated domain object with values obtained from the >>> + * command line arguments. >>> + * </p><h2>Example</h2> >>> + * <pre>import static picocli.CommandLine.*; >>> + * >>> + * @Command(header = "Encrypt FILE(s), or standard input, to >>> standard output or to the output file.") >>> + * public class Encrypt { >>> + * >>> + * @Parameters(type = File.class, description = "Any number of >>> input files") >>> + * private List<File> files = new ArrayList<File>(); >>> + * >>> + * @Option(names = { "-o", "--out" }, description = "Output >> file >>> (default: print to console)") >>> + * private File outputFile; >>> + * >>> + * @Option(names = { "-v", "--verbose"}, description = >>> "Verbosely list files processed") >>> + * private boolean verbose; >>> + * >>> + * @Option(names = { "-h", "--help", "-?", "-help"}, help = >>> true, description = "Display this help and exit") >>> + * private boolean help; >>> + * } >>> + * </pre> >>> + * <p> >>> + * Use {@code CommandLine} to initialize a domain object as follows: >>> + * </p><pre> >>> + * public static void main(String... args) { >>> + * try { >>> + * Encrypt encrypt = CommandLine.populateCommand(new Encrypt(), >>> args); >>> + * if (encrypt.help) { >>> + * CommandLine.usage(encrypt, System.out); >>> + * } else { >>> + * runProgram(encrypt); >>> + * } >>> + * } catch (ParameterException ex) { // command line arguments could >>> not be parsed >>> + * System.err.println(ex.getMessage()); >>> + * CommandLine.usage(new Encrypt(), System.err); >>> + * } >>> + * } >>> + * </pre><p> >>> + * Invoke the above program with some command line arguments. The below >>> are all equivalent: >>> + * </p> >>> + * <pre> >>> + * --verbose --out=outfile in1 in2 >>> + * --verbose --out outfile in1 in2 >>> + * -v --out=outfile in1 in2 >>> + * -v -o outfile in1 in2 >>> + * -v -o=outfile in1 in2 >>> + * -vo outfile in1 in2 >>> + * -vo=outfile in1 in2 >>> + * -v -ooutfile in1 in2 >>> + * -vooutfile in1 in2 >>> + * </pre> >>> + * >>> + * <p> >>> + * Copied and modified from <a href="http://github.com/remkop/picocli/ >>> ">picocli</a>. >>> + * </p> >>> + * >>> + * @since 2.9 >>> + */ >>> +public class CommandLine { >>> + /** This is picocli version {@value}. */ >>> + public static final String VERSION = "0.9.8"; >>> + >>> + private final Interpreter interpreter; >>> + private boolean overwrittenOptionsAllowed = false; >>> + private boolean unmatchedArgumentsAllowed = false; >>> + private List<String> unmatchedArguments = new ArrayList<String>(); >>> + private CommandLine parent; >>> + private boolean usageHelpRequested; >>> + private boolean versionHelpRequested; >>> + private List<String> versionLines = new ArrayList<String>(); >>> + >>> + /** >>> + * Constructs a new {@code CommandLine} interpreter with the >>> specified annotated object. >>> + * When the {@link #parse(String...)} method is called, fields of >> the >>> specified object that are annotated >>> + * with {@code @Option} or {@code @Parameters} will be initialized >>> based on command line arguments. >>> + * @param command the object to initialize from the command line >>> arguments >>> + * @throws IllegalArgumentException if the specified command object >>> does not have a {@link Command}, {@link Option} or {@link Parameters} >>> annotation >>> + */ >>> + public CommandLine(Object command) { >>> + interpreter = new Interpreter(command); >>> + } >>> + >>> + /** Registers a subcommand with the specified name. For example: >>> + * <pre> >>> + * CommandLine commandLine = new CommandLine(new Git()) >>> + * .addSubcommand("status", new GitStatus()) >>> + * .addSubcommand("commit", new GitCommit(); >>> + * .addSubcommand("add", new GitAdd()) >>> + * .addSubcommand("branch", new GitBranch()) >>> + * .addSubcommand("checkout", new GitCheckout()) >>> + * //... >>> + * ; >>> + * </pre> >>> + * >>> + * <p>The specified object can be an annotated object or a >>> + * {@code CommandLine} instance with its own nested subcommands. For >>> example:</p> >>> + * <pre> >>> + * CommandLine commandLine = new CommandLine(new MainCommand()) >>> + * .addSubcommand("cmd1", new >>> ChildCommand1()) // subcommand >>> + * .addSubcommand("cmd2", new >> ChildCommand2()) >>> + * .addSubcommand("cmd3", new CommandLine(new >>> ChildCommand3()) // subcommand with nested sub-subcommands >>> + * .addSubcommand("cmd3sub1", new >>> GrandChild3Command1()) >>> + * .addSubcommand("cmd3sub2", new >>> GrandChild3Command2()) >>> + * .addSubcommand("cmd3sub3", new CommandLine(new >>> GrandChild3Command3()) // deeper nesting >>> + * .addSubcommand("cmd3sub3sub1", new >>> GreatGrandChild3Command3_1()) >>> + * .addSubcommand("cmd3sub3sub2", new >>> GreatGrandChild3Command3_2()) >>> + * ) >>> + * ); >>> + * </pre> >>> + * <p>The default type converters are available on all subcommands >>> and nested sub-subcommands, but custom type >>> + * converters are registered only with the subcommand hierarchy as >> it >>> existed when the custom type was registered. >>> + * To ensure a custom type converter is available to all >> subcommands, >>> register the type converter last, after >>> + * adding subcommands.</p> >>> + * >>> + * @param name the string to recognize on the command line as a >>> subcommand >>> + * @param command the object to initialize with command line >>> arguments following the subcommand name. >>> + * This may be a {@code CommandLine} instance with its own >>> (nested) subcommands >>> + * @return this CommandLine object, to allow method chaining >>> + * @see #registerConverter(Class, ITypeConverter) >>> + * @since 0.9.7 >>> + */ >>> + public CommandLine addSubcommand(String name, Object command) { >>> + CommandLine commandLine = toCommandLine(command); >>> + commandLine.parent = this; >>> + interpreter.commands.put(name, commandLine); >>> + return this; >>> + } >>> + /** Returns a map with the subcommands {@linkplain >>> #addSubcommand(String, Object) registered} on this instance. >>> + * @return a map with the registered subcommands >>> + * @since 0.9.7 >>> + */ >>> + public Map<String, CommandLine> getSubcommands() { >>> + return new LinkedHashMap<String, CommandLine>(interpreter. >>> commands); >>> + } >>> + /** >>> + * Returns the command that this is a subcommand of, or {@code null} >>> if this is a top-level command. >>> + * @return the command that this is a subcommand of, or {@code null} >>> if this is a top-level command >>> + * @see #addSubcommand(String, Object) >>> + * @see Command#subcommands() >>> + * @since 0.9.8 >>> + */ >>> + public CommandLine getParent() { >>> + return parent; >>> + } >>> + >>> + /** >>> + * Returns the annotated object that this {@code CommandLine} >>> instance was constructed with. >>> + * @return the annotated object that this {@code CommandLine} >>> instance was constructed with >>> + * @since 0.9.7 >>> + */ >>> + public Object getCommand() { >>> + return interpreter.command; >>> + } >>> + >>> + /** Returns {@code true} if an option annotated with {@link >>> Option#usageHelp()} was specified on the command line. >>> + * @return whether the parser encountered an option annotated with >>> {@link Option#usageHelp()} */ >>> + public boolean isUsageHelpRequested() { return usageHelpRequested; } >>> + >>> + /** Returns {@code true} if an option annotated with {@link >>> Option#versionHelp()} was specified on the command line. >>> + * @return whether the parser encountered an option annotated with >>> {@link Option#versionHelp()} */ >>> + public boolean isVersionHelpRequested() { return >>> versionHelpRequested; } >>> + >>> + /** Returns whether options for single-value fields can be specified >>> multiple times on the command line. >>> + * The default is {@code false} and a {@link >>> OverwrittenOptionException} is thrown if this happens. >>> + * When {@code true}, the last specified value is retained. >>> + * @return {@code true} if options for single-value fields can be >>> specified multiple times on the command line, {@code false} otherwise >>> + * @since 0.9.7 >>> + */ >>> + public boolean isOverwrittenOptionsAllowed() { >>> + return overwrittenOptionsAllowed; >>> + } >>> + >>> + /** Sets whether options for single-value fields can be specified >>> multiple times on the command line without a {@link >>> OverwrittenOptionException} being thrown. >>> + * <p>The specified setting will be registered with this {@code >>> CommandLine} and the full hierarchy of its >>> + * subcommands and nested sub-subcommands <em>at the moment this >>> method is called</em>. Subcommands added >>> + * later will have the default setting. To ensure a setting is >>> applied to all >>> + * subcommands, call the setter last, after adding subcommands.</p> >>> + * @param newValue the new setting >>> + * @return this {@code CommandLine} object, to allow method chaining >>> + * @since 0.9.7 >>> + */ >>> + public CommandLine setOverwrittenOptionsAllowed(boolean newValue) { >>> + this.overwrittenOptionsAllowed = newValue; >>> + for (CommandLine command : interpreter.commands.values()) { >>> + command.setOverwrittenOptionsAllowed(newValue); >>> + } >>> + return this; >>> + } >>> + >>> + /** Returns whether the end user may specify arguments on the >> command >>> line that are not matched to any option or parameter fields. >>> + * The default is {@code false} and a {@link >>> UnmatchedArgumentException} is thrown if this happens. >>> + * When {@code true}, the last unmatched arguments are available via >>> the {@link #getUnmatchedArguments()} method. >>> + * @return {@code true} if the end use may specify unmatched >>> arguments on the command line, {@code false} otherwise >>> + * @see #getUnmatchedArguments() >>> + * @since 0.9.7 >>> + */ >>> + public boolean isUnmatchedArgumentsAllowed() { >>> + return unmatchedArgumentsAllowed; >>> + } >>> + >>> + /** Sets whether the end user may specify unmatched arguments on the >>> command line without a {@link UnmatchedArgumentException} being thrown. >>> + * <p>The specified setting will be registered with this {@code >>> CommandLine} and the full hierarchy of its >>> + * subcommands and nested sub-subcommands <em>at the moment this >>> method is called</em>. Subcommands added >>> + * later will have the default setting. To ensure a setting is >>> applied to all >>> + * subcommands, call the setter last, after adding subcommands.</p> >>> + * @param newValue the new setting >>> + * @return this {@code CommandLine} object, to allow method chaining >>> + * @since 0.9.7 >>> + */ >>> + public CommandLine setUnmatchedArgumentsAllowed(boolean newValue) { >>> + this.unmatchedArgumentsAllowed = newValue; >>> + for (CommandLine command : interpreter.commands.values()) { >>> + command.setUnmatchedArgumentsAllowed(newValue); >>> + } >>> + return this; >>> + } >>> + >>> + /** Returns the list of unmatched command line arguments, if any. >>> + * @return the list of unmatched command line arguments or an empty >>> list >>> + * @see #isUnmatchedArgumentsAllowed() >>> + * @since 0.9.7 >>> + */ >>> + public List<String> getUnmatchedArguments() { >>> + return unmatchedArguments; >>> + } >>> + >>> + /** >>> + * <p> >>> + * Convenience method that initializes the specified annotated >> object >>> from the specified command line arguments. >>> + * </p><p> >>> + * This is equivalent to >>> + * </p><pre> >>> + * CommandLine cli = new CommandLine(command); >>> + * cli.parse(args); >>> + * return command; >>> + * </pre> >>> + * >>> + * @param command the object to initialize. This object contains >>> fields annotated with >>> + * {@code @Option} or {@code @Parameters}. >>> + * @param args the command line arguments to parse >>> + * @param <T> the type of the annotated object >>> + * @return the specified annotated object >>> + * @throws IllegalArgumentException if the specified command object >>> does not have a {@link Command}, {@link Option} or {@link Parameters} >>> annotation >>> + * @throws ParameterException if the specified command line >> arguments >>> are invalid >>> + * @since 0.9.7 >>> + */ >>> + public static <T> T populateCommand(T command, String... args) { >>> + CommandLine cli = toCommandLine(command); >>> + cli.parse(args); >>> + return command; >>> + } >>> + >>> + /** >>> + * <p> >>> + * Initializes the annotated object that this {@code CommandLine} >> was >>> constructed with as well as >>> + * possibly any registered commands, based on the specified command >>> line arguments, >>> + * and returns a list of all commands and subcommands that were >>> initialized by this method. >>> + * </p> >>> + * >>> + * @param args the command line arguments to parse >>> + * @return a list with all commands and subcommands initialized by >>> this method >>> + * @throws ParameterException if the specified command line >> arguments >>> are invalid >>> + */ >>> + public List<CommandLine> parse(String... args) { >>> + return interpreter.parse(args); >>> + } >>> + >>> + /** >>> + * Equivalent to {@code new CommandLine(command).usage(out)}. See >>> {@link #usage(PrintStream)} for details. >>> + * @param command the object annotated with {@link Command}, {@link >>> Option} and {@link Parameters} >>> + * @param out the print stream to print the help message to >>> + * @throws IllegalArgumentException if the specified command object >>> does not have a {@link Command}, {@link Option} or {@link Parameters} >>> annotation >>> + */ >>> + public static void usage(Object command, PrintStream out) { >>> + toCommandLine(command).usage(out); >>> + } >>> + >>> + /** >>> + * Equivalent to {@code new CommandLine(command).usage(out, ansi)}. >>> + * See {@link #usage(PrintStream, Help.Ansi)} for details. >>> + * @param command the object annotated with {@link Command}, {@link >>> Option} and {@link Parameters} >>> + * @param out the print stream to print the help message to >>> + * @param ansi whether the usage message should contain ANSI escape >>> codes or not >>> + * @throws IllegalArgumentException if the specified command object >>> does not have a {@link Command}, {@link Option} or {@link Parameters} >>> annotation >>> + */ >>> + public static void usage(Object command, PrintStream out, Help.Ansi >>> ansi) { >>> + toCommandLine(command).usage(out, ansi); >>> + } >>> + >>> + /** >>> + * Equivalent to {@code new CommandLine(command).usage(out, >>> colorScheme)}. >>> + * See {@link #usage(PrintStream, Help.ColorScheme)} for details. >>> + * @param command the object annotated with {@link Command}, {@link >>> Option} and {@link Parameters} >>> + * @param out the print stream to print the help message to >>> + * @param colorScheme the {@code ColorScheme} defining the styles >> for >>> options, parameters and commands when ANSI is enabled >>> + * @throws IllegalArgumentException if the specified command object >>> does not have a {@link Command}, {@link Option} or {@link Parameters} >>> annotation >>> + */ >>> + public static void usage(Object command, PrintStream out, >>> Help.ColorScheme colorScheme) { >>> + toCommandLine(command).usage(out, colorScheme); >>> + } >>> + >>> + /** >>> + * Delegates to {@link #usage(PrintStream, Help.Ansi)} with the >>> {@linkplain Help.Ansi#AUTO platform default}. >>> + * @param out the printStream to print to >>> + * @see #usage(PrintStream, Help.ColorScheme) >>> + */ >>> + public void usage(PrintStream out) { >>> + usage(out, Help.Ansi.AUTO); >>> + } >>> + >>> + /** >>> + * Delegates to {@link #usage(PrintStream, Help.ColorScheme)} with >>> the {@linkplain Help#defaultColorScheme(CommandLine.Help.Ansi) default >>> color scheme}. >>> + * @param out the printStream to print to >>> + * @param ansi whether the usage message should include ANSI escape >>> codes or not >>> + * @see #usage(PrintStream, Help.ColorScheme) >>> + */ >>> + public void usage(PrintStream out, Help.Ansi ansi) { >>> + usage(out, Help.defaultColorScheme(ansi)); >>> + } >>> + /** >>> + * Prints a usage help message for the annotated command class to >> the >>> specified {@code PrintStream}. >>> + * Delegates construction of the usage help message to the {@link >>> Help} inner class and is equivalent to: >>> + * <pre> >>> + * Help help = new Help(command).addAllSubcommands( >> getSubcommands()); >>> + * StringBuilder sb = new StringBuilder() >>> + * .append(help.headerHeading()) >>> + * .append(help.header()) >>> + * .append(help.synopsisHeading()) //e.g. Usage: >>> + * .append(help.synopsis()) //e.g. <main >>> class> [OPTIONS] <command> [COMMAND-OPTIONS] [ARGUMENTS] >>> + * .append(help.descriptionHeading()) //e.g. >>> %nDescription:%n%n >>> + * .append(help.description()) //e.g. {"Converts >>> foos to bars.", "Use options to control conversion mode."} >>> + * .append(help.parameterListHeading()) //e.g. %nPositional >>> parameters:%n%n >>> + * .append(help.parameterList()) //e.g. [FILE...] the >>> files to convert >>> + * .append(help.optionListHeading()) //e.g. >> %nOptions:%n%n >>> + * .append(help.optionList()) //e.g. -h, --help >>> displays this help and exits >>> + * .append(help.commandListHeading()) //e.g. >>> %nCommands:%n%n >>> + * .append(help.commandList()) //e.g. add >>> adds the frup to the frooble >>> + * .append(help.footerHeading()) >>> + * .append(help.footer()); >>> + * out.print(sb); >>> + * </pre> >>> + * <p>Annotate your class with {@link Command} to control many >>> aspects of the usage help message, including >>> + * the program name, text of section headings and section contents, >>> and some aspects of the auto-generated sections >>> + * of the usage help message. >>> + * <p>To customize the auto-generated sections of the usage help >>> message, like how option details are displayed, >>> + * instantiate a {@link Help} object and use a {@link >> Help.TextTable} >>> with more of fewer columns, a custom >>> + * {@linkplain Help.Layout layout}, and/or a custom option >>> {@linkplain Help.IOptionRenderer renderer} >>> + * for ultimate control over which aspects of an Option or Field are >>> displayed where.</p> >>> + * @param out the {@code PrintStream} to print the usage help >> message >>> to >>> + * @param colorScheme the {@code ColorScheme} defining the styles >> for >>> options, parameters and commands when ANSI is enabled >>> + */ >>> + public void usage(PrintStream out, Help.ColorScheme colorScheme) { >>> + Help help = new Help(interpreter.command, colorScheme). >>> addAllSubcommands(getSubcommands()); >>> + StringBuilder sb = new StringBuilder() >>> + .append(help.headerHeading()) >>> + .append(help.header()) >>> + .append(help.synopsisHeading()) //e.g. Usage: >>> + .append(help.synopsis(help.synopsisHeadingLength())) >>> //e.g. <main class> [OPTIONS] <command> [COMMAND-OPTIONS] >>> [ARGUMENTS] >>> + .append(help.descriptionHeading()) //e.g. >>> %nDescription:%n%n >>> + .append(help.description()) //e.g. {"Converts >>> foos to bars.", "Use options to control conversion mode."} >>> + .append(help.parameterListHeading()) //e.g. >> %nPositional >>> parameters:%n%n >>> + .append(help.parameterList()) //e.g. [FILE...] >> the >>> files to convert >>> + .append(help.optionListHeading()) //e.g. >>> %nOptions:%n%n >>> + .append(help.optionList()) //e.g. -h, --help >>> displays this help and exits >>> + .append(help.commandListHeading()) //e.g. >>> %nCommands:%n%n >>> + .append(help.commandList()) //e.g. add >>> adds the frup to the frooble >>> + .append(help.footerHeading()) >>> + .append(help.footer()); >>> + out.print(sb); >>> + } >>> + >>> + /** >>> + * Delegates to {@link #printVersionHelp(PrintStream, Help.Ansi)} >>> with the {@linkplain Help.Ansi#AUTO platform default}. >>> + * @param out the printStream to print to >>> + * @see #printVersionHelp(PrintStream, Help.Ansi) >>> + */ >>> + public void printVersionHelp(PrintStream out) { >> printVersionHelp(out, >>> Help.Ansi.AUTO); } >>> + >>> + /** >>> + * Prints version information from the {@link Command#version()} >>> annotation to the specified {@code PrintStream}. >>> + * Each element of the array of version strings is printed on a >>> separate line. Version strings may contain >>> + * <a href="http://picocli.info/#_usage_help_with_styles_and_colors >> ">markup >>> for colors and style</a>. >>> + * @param out the printStream to print to >>> + * @param ansi whether the usage message should include ANSI escape >>> codes or not >>> + * @see Command#version() >>> + * @see Option#versionHelp() >>> + * @see #isVersionHelpRequested() >>> + */ >>> + public void printVersionHelp(PrintStream out, Help.Ansi ansi) { >>> + for (String versionInfo : versionLines) { >>> + out.println(ansi.new Text(versionInfo)); >>> + } >>> + } >>> + >>> + /** >>> + * Delegates to {@link #run(Runnable, PrintStream, Help.Ansi, >>> String...)} with {@link Help.Ansi#AUTO}. >>> + * @param command the command to run when {@linkplain >>> #populateCommand(Object, String...) parsing} succeeds. >>> + * @param out the printStream to print to >>> + * @param args the command line arguments to parse >>> + * @param <R> the annotated object must implement Runnable >>> + * @see #run(Runnable, PrintStream, Help.Ansi, String...) >>> + * @throws IllegalArgumentException if the specified command object >>> does not have a {@link Command}, {@link Option} or {@link Parameters} >>> annotation >>> + */ >>> + public static <R extends Runnable> void run(R command, PrintStream >>> out, String... args) { >>> + run(command, out, Help.Ansi.AUTO, args); >>> + } >>> + /** >>> + * Convenience method to allow command line application authors to >>> avoid some boilerplate code in their application. >>> + * The annotated object needs to implement {@link Runnable}. Calling >>> this method is equivalent to: >>> + * <pre> >>> + * CommandLine cmd = new CommandLine(command); >>> + * try { >>> + * cmd.parse(args); >>> + * } catch (Exception ex) { >>> + * System.err.println(ex.getMessage()); >>> + * cmd.usage(out, ansi); >>> + * return; >>> + * } >>> + * command.run(); >>> + * </pre> >>> + * Note that this method is not suitable for commands with >>> subcommands. >>> + * @param command the command to run when {@linkplain >>> #populateCommand(Object, String...) parsing} succeeds. >>> + * @param out the printStream to print to >>> + * @param ansi whether the usage message should include ANSI escape >>> codes or not >>> + * @param args the command line arguments to parse >>> + * @param <R> the annotated object must implement Runnable >>> + * @throws IllegalArgumentException if the specified command object >>> does not have a {@link Command}, {@link Option} or {@link Parameters} >>> annotation >>> + */ >>> + public static <R extends Runnable> void run(R command, PrintStream >>> out, Help.Ansi ansi, String... args) { >>> + CommandLine cmd = new CommandLine(command); // validate command >>> outside of try-catch >>> + try { >>> + cmd.parse(args); >>> + } catch (Exception ex) { >>> + out.println(ex.getMessage()); >>> + cmd.usage(out, ansi); >>> + return; >>> + } >>> + command.run(); >>> + } >>> + >>> + /** >>> + * Registers the specified type converter for the specified class. >>> When initializing fields annotated with >>> + * {@link Option}, the field's type is used as a lookup key to find >>> the associated type converter, and this >>> + * type converter converts the original command line argument string >>> value to the correct type. >>> + * <p> >>> + * Java 8 lambdas make it easy to register custom type converters: >>> + * </p> >>> + * <pre> >>> + * commandLine.registerConverter(java.nio.file.Path.class, s -> >>> java.nio.file.Paths.get(s)); >>> + * commandLine.registerConverter(java.time.Duration.class, s -> >>> java.time.Duration.parse(s));</pre> >>> + * <p> >>> + * Built-in type converters are pre-registered for the following >> java >>> 1.5 types: >>> + * </p> >>> + * <ul> >>> + * <li>all primitive types</li> >>> + * <li>all primitive wrapper types: Boolean, Byte, Character, >>> Double, Float, Integer, Long, Short</li> >>> + * <li>any enum</li> >>> + * <li>java.io.File</li> >>> + * <li>java.math.BigDecimal</li> >>> + * <li>java.math.BigInteger</li> >>> + * <li>java.net.InetAddress</li> >>> + * <li>java.net.URI</li> >>> + * <li>java.net.URL</li> >>> + * <li>java.nio.charset.Charset</li> >>> + * <li>java.sql.Time</li> >>> + * <li>java.util.Date</li> >>> + * <li>java.util.UUID</li> >>> + * <li>java.util.regex.Pattern</li> >>> + * <li>StringBuilder</li> >>> + * <li>CharSequence</li> >>> + * <li>String</li> >>> + * </ul> >>> + * <p>The specified converter will be registered with this {@code >>> CommandLine} and the full hierarchy of its >>> + * subcommands and nested sub-subcommands <em>at the moment the >>> converter is registered</em>. Subcommands added >>> + * later will not have this converter added automatically. To ensure >>> a custom type converter is available to all >>> + * subcommands, register the type converter last, after adding >>> subcommands.</p> >>> + * >>> + * @param cls the target class to convert parameter string values to >>> + * @param converter the class capable of converting string values to >>> the specified target type >>> + * @param <K> the target type >>> + * @return this CommandLine object, to allow method chaining >>> + * @see #addSubcommand(String, Object) >>> + */ >>> + public <K> CommandLine registerConverter(Class<K> cls, >>> ITypeConverter<K> converter) { >>> + interpreter.converterRegistry.put(Assert.notNull(cls, "class"), >>> Assert.notNull(converter, "converter")); >>> + for (CommandLine command : interpreter.commands.values()) { >>> + command.registerConverter(cls, converter); >>> + } >>> + return this; >>> + } >>> + >>> + /** Returns the String that separates option names from option >> values >>> when parsing command line options. {@code '='} by default. >>> + * @return the String the parser uses to separate option names from >>> option values */ >>> + public String getSeparator() { >>> + return interpreter.separator; >>> + } >>> + >>> + /** Sets the String the parser uses to separate option names from >>> option values to the specified value. >>> + * @param separator the String that separates option names from >>> option values */ >>> + public void setSeparator(String separator) { >>> + interpreter.separator = Assert.notNull(separator, "separator"); >>> + } >>> + private static boolean empty(String str) { return str == null || >>> str.trim().length() == 0; } >>> + private static boolean empty(Object[] array) { return array == null >>> || array.length == 0; } >>> + private static boolean empty(Text txt) { return txt == null || >>> txt.plain.toString().trim().length() == 0; } >>> + private static String str(String[] arr, int i) { return (arr == null >>> || arr.length == 0) ? "" : arr[i]; } >>> + private static boolean isBoolean(Class<?> type) { return type == >>> Boolean.class || type == Boolean.TYPE; } >>> + private static CommandLine toCommandLine(Object obj) { return obj >>> instanceof CommandLine ? (CommandLine) obj : new CommandLine(obj);} >>> + /** >>> + * <p> >>> + * Annotate fields in your class with {@code @Option} and picocli >>> will initialize these fields when matching >>> + * arguments are specified on the command line. >>> + * </p><p> >>> + * For example: >>> + * </p> >>> + * <pre>import static picocli.CommandLine.*; >>> + * >>> + * public class MyClass { >>> + * @Parameters(type = File.class, description = "Any number >>> of input files") >>> + * private List<File> files = new ArrayList<File>(); >>> + * >>> + * @Option(names = { "-o", "--out" }, description = "Output >>> file (default: print to console)") >>> + * private File outputFile; >>> + * >>> + * @Option(names = { "-v", "--verbose"}, description = >>> "Verbosely list files processed") >>> + * private boolean verbose; >>> + * >>> + * @Option(names = { "-h", "--help", "-?", "-help"}, help = >>> true, description = "Display this help and exit") >>> + * private boolean help; >>> + * >>> + * @Option(names = { "-V", "--version"}, help = true, >>> description = "Display version information and exit") >>> + * private boolean version; >>> + * } >>> + * </pre> >>> + * <p> >>> + * A field cannot be annotated with both {@code @Parameters} and >>> {@code @Option} or a >>> + * {@code ParameterException} is thrown. >>> + * </p> >>> + */ >>> + @Retention(RetentionPolicy.RUNTIME) >>> + @Target(ElementType.FIELD) >>> + public @interface Option { >>> + /** >>> + * One or more option names. At least one option name is >> required. >>> + * <p> >>> + * Different environments have different conventions for naming >>> options, but usually options have a prefix >>> + * that sets them apart from parameters. >>> + * Picocli supports all of the below styles. The default >>> separator is {@code '='}, but this can be configured. >>> + * </p><p> >>> + * <b>*nix</b> >>> + * </p><p> >>> + * In Unix and Linux, options have a short (single-character) >>> name, a long name or both. >>> + * Short options >>> + * (<a href="http://pubs.opengroup.org/onlinepubs/9699919799/ >>> basedefs/V1_chap12.html#tag_12_02">POSIX >>> + * style</a> are single-character and are preceded by the {@code >>> '-'} character, e.g., {@code `-v'}. >>> + * <a href="https://www.gnu.org/software/tar/manual/html_node/ >>> Long-Options.html">GNU-style</a> long >>> + * (or <em>mnemonic</em>) options start with two dashes in a >> row, >>> e.g., {@code `--file'}. >>> + * </p><p>Picocli supports the POSIX convention that short >>> options can be grouped, with the last option >>> + * optionally taking a parameter, which may be attached to the >>> option name or separated by a space or >>> + * a {@code '='} character. The below examples are all >> equivalent: >>> + * </p><pre> >>> + * -xvfFILE >>> + * -xvf FILE >>> + * -xvf=FILE >>> + * -xv --file FILE >>> + * -xv --file=FILE >>> + * -x -v --file FILE >>> + * -x -v --file=FILE >>> + * </pre><p> >>> + * <b>DOS</b> >>> + * </p><p> >>> + * DOS options mostly have upper case single-character names and >>> start with a single slash {@code '/'} character. >>> + * Option parameters are separated by a {@code ':'} character. >>> Options cannot be grouped together but >>> + * must be specified separately. For example: >>> + * </p><pre> >>> + * DIR /S /A:D /T:C >>> + * </pre><p> >>> + * <b>PowerShell</b> >>> + * </p><p> >>> + * Windows PowerShell options generally are a word preceded by a >>> single {@code '-'} character, e.g., {@code `-Help'}. >>> + * Option parameters are separated by a space or by a {@code >> ':'} >>> character. >>> + * </p> >>> + * @return one or more option names >>> + */ >>> + String[] names(); >>> + >>> + /** >>> + * Indicates whether this option is required. By default this is >>> false. >>> + * If an option is required, but a user invokes the program >>> without specifying the required option, >>> + * a {@link MissingParameterException} is thrown from the {@link >>> #parse(String...)} method. >>> + * @return whether this option is required >>> + */ >>> + boolean required() default false; >>> + >>> + /** >>> + * Set {@code help=true} if this option should disable >> validation >>> of the remaining arguments: >>> + * If the {@code help} option is specified, no error message is >>> generated for missing required options. >>> + * <p> >>> + * This attribute is useful for special options like help >> ({@code >>> -h} and {@code --help} on unix, >>> + * {@code -?} and {@code -Help} on Windows) or version ({@code >>> -V} and {@code --version} on unix, >>> + * {@code -Version} on Windows). >>> + * </p> >>> + * <p> >>> + * Note that the {@link #parse(String...)} method will not print >>> help documentation. It will only set >>> + * the value of the annotated field. It is the responsibility of >>> the caller to inspect the annotated fields >>> + * and take the appropriate action. >>> + * </p> >>> + * @return whether this option disables validation of the other >>> arguments >>> + */ >>> + boolean help() default false; >>> + >>> + /** >>> + * Set {@code usageHelp=true} if this option allows the user to >>> request usage help. If this option is >>> + * specified on the command line, picocli will not validate the >>> remaining arguments (so no "missing required >>> + * option" errors) and the {@link CommandLine# >> isUsageHelpRequested()} >>> method will return {@code true}. >>> + * <p> >>> + * This attribute is useful for special options like help >> ({@code >>> -h} and {@code --help} on unix, >>> + * {@code -?} and {@code -Help} on Windows). >>> + * </p> >>> + * <p> >>> + * Note that the {@link #parse(String...)} method will not print >>> usage help documentation. It will only set >>> + * the value of the annotated field. It is the responsibility of >>> the caller to inspect the annotated fields >>> + * and take the appropriate action. >>> + * </p> >>> + * @return whether this option allows the user to request usage >>> help >>> + */ >>> + boolean usageHelp() default false; >>> + >>> + /** >>> + * Set {@code versionHelp=true} if this option allows the user >> to >>> request version information. If this option is >>> + * specified on the command line, picocli will not validate the >>> remaining arguments (so no "missing required >>> + * option" errors) and the {@link CommandLine# >> isVersionHelpRequested()} >>> method will return {@code true}. >>> + * <p> >>> + * This attribute is useful for special options like version >>> ({@code -V} and {@code --version} on unix, >>> + * {@code -Version} on Windows). >>> + * </p> >>> + * <p> >>> + * Note that the {@link #parse(String...)} method will not print >>> version information. It will only set >>> + * the value of the annotated field. It is the responsibility of >>> the caller to inspect the annotated fields >>> + * and take the appropriate action. >>> + * </p> >>> + * @return whether this option allows the user to request >> version >>> information >>> + */ >>> + boolean versionHelp() default false; >>> + >>> + /** >>> + * Description of this option, used when generating the usage >>> documentation. >>> + * @return the description of this option >>> + */ >>> + String[] description() default {}; >>> + >>> + /** >>> + * Specifies the minimum number of required parameters and the >>> maximum number of accepted parameters. >>> + * If an option declares a positive arity, and the user >> specifies >>> an insufficient number of parameters on the >>> + * command line, a {@link MissingParameterException} is thrown >> by >>> the {@link #parse(String...)} method. >>> + * <p> >>> + * In many cases picocli can deduce the number of required >>> parameters from the field's type. >>> + * By default, flags (boolean options) have arity zero, >>> + * and single-valued type fields (String, int, Integer, double, >>> Double, File, Date, etc) have arity one. >>> + * Generally, fields with types that cannot hold multiple values >>> can omit the {@code arity} attribute. >>> + * </p><p> >>> + * Fields used to capture options with arity two or higher >> should >>> have a type that can hold multiple values, >>> + * like arrays or Collections. See {@link #type()} for >>> strongly-typed Collection fields. >>> + * </p><p> >>> + * For example, if an option has 2 required parameters and any >>> number of optional parameters, >>> + * specify {@code @Option(names = "-example", arity = "2..*")}. >>> + * </p> >>> + * <b>A note on boolean options</b> >>> + * <p> >>> + * By default picocli does not expect boolean options (also >>> called "flags" or "switches") to have a parameter. >>> + * You can make a boolean option take a required parameter by >>> annotating your field with {@code arity="1"}. >>> + * For example: </p> >>> + * <pre>@Option(names = "-v", arity = "1") boolean >>> verbose;</pre> >>> + * <p> >>> + * Because this boolean field is defined with arity 1, the user >>> must specify either {@code <program> -v false} >>> + * or {@code <program> -v true} >>> + * on the command line, or a {@link MissingParameterException} >> is >>> thrown by the {@link #parse(String...)} >>> + * method. >>> + * </p><p> >>> + * To make the boolean parameter possible but optional, define >>> the field with {@code arity = "0..1"}. >>> + * For example: </p> >>> + * <pre>@Option(names="-v", arity="0..1") boolean >>> verbose;</pre> >>> + * <p>This will accept any of the below without throwing an >>> exception:</p> >>> + * <pre> >>> + * -v >>> + * -v true >>> + * -v false >>> + * </pre> >>> + * @return how many arguments this option requires >>> + */ >>> + String arity() default ""; >>> + >>> + /** >>> + * Specify a {@code paramLabel} for the option parameter to be >>> used in the usage help message. If omitted, >>> + * picocli uses the field name in fish brackets ({@code '<'} and >>> {@code '>'}) by default. Example: >>> + * <pre>class Example { >>> + * @Option(names = {"-o", "--output"}, >>> paramLabel="FILE", description="path of the output file") >>> + * private File out; >>> + * @Option(names = {"-j", "--jobs"}, arity="0..1", >>> description="Allow N jobs at once; infinite jobs with no arg.") >>> + * private int maxJobs = -1; >>> + * }</pre> >>> + * <p>By default, the above gives a usage help message like the >>> following:</p><pre> >>> + * Usage: <main class> [OPTIONS] >>> + * -o, --output FILE path of the output file >>> + * -j, --jobs [<maxJobs>] Allow N jobs at once; infinite >>> jobs with no arg. >>> + * </pre> >>> + * @return name of the option parameter used in the usage help >>> message >>> + */ >>> + String paramLabel() default ""; >>> + >>> + /** >>> + * <p> >>> + * Specify a {@code type} if the annotated field is a {@code >>> Collection} that should hold objects other than Strings. >>> + * </p><p> >>> + * If the field's type is a {@code Collection}, the generic type >>> parameter of the collection is erased and >>> + * cannot be determined at runtime. Specify a {@code type} >>> attribute to store values other than String in >>> + * the Collection. Picocli will use the {@link ITypeConverter} >>> + * that is {@linkplain #registerConverter(Class, ITypeConverter) >>> registered} for that type to convert >>> + * the raw String values before they are added to the >> collection. >>> + * </p><p> >>> + * When the field's type is an array, the {@code type} attribute >>> is ignored: the values will be converted >>> + * to the array component type and the array will be replaced >>> with a new instance containing both the old and >>> + * the new values. </p> >>> + * @return the type to convert the raw String values to before >>> adding them to the Collection >>> + */ >>> + Class<?> type() default String.class; >>> + >>> + /** >>> + * Specify a regular expression to use to split option parameter >>> values before applying them to the field. >>> + * All elements resulting from the split are added to the array >>> or Collection. Ignored for single-value fields. >>> + * @return a regular expression to split option parameter values >>> or {@code ""} if the value should not be split >>> + * @see String#split(String) >>> + */ >>> + String split() default ""; >>> + >>> + /** >>> + * Set {@code hidden=true} if this option should not be included >>> in the usage documentation. >>> + * @return whether this option should be excluded from the usage >>> message >>> + */ >>> + boolean hidden() default false; >>> + } >>> + /** >>> + * <p> >>> + * Fields annotated with {@code @Parameters} will be initialized >> with >>> positional parameters. By specifying the >>> + * {@link #index()} attribute you can pick which (or what range) of >>> the positional parameters to apply. If no index >>> + * is specified, the field will get all positional parameters (so it >>> should be an array or a collection). >>> + * </p><p> >>> + * When parsing the command line arguments, picocli first tries to >>> match arguments to {@link Option Options}. >>> + * Positional parameters are the arguments that follow the options, >>> or the arguments that follow a "--" (double >>> + * dash) argument on the command line. >>> + * </p><p> >>> + * For example: >>> + * </p> >>> + * <pre>import static picocli.CommandLine.*; >>> + * >>> + * public class MyCalcParameters { >>> + * @Parameters(type = BigDecimal.class, description = "Any >>> number of input numbers") >>> + * private List<BigDecimal> files = new >>> ArrayList<BigDecimal>(); >>> + * >>> + * @Option(names = { "-h", "--help", "-?", "-help"}, help = >>> true, description = "Display this help and exit") >>> + * private boolean help; >>> + * } >>> + * </pre><p> >>> + * A field cannot be annotated with both {@code @Parameters} and >>> {@code @Option} or a {@code ParameterException} >>> + * is thrown.</p> >>> + */ >>> + @Retention(RetentionPolicy.RUNTIME) >>> + @Target(ElementType.FIELD) >>> + public @interface Parameters { >>> + /** Specify an index ("0", or "1", etc.) to pick which of the >>> command line arguments should be assigned to this >>> + * field. For array or Collection fields, you can also specify >> an >>> index range ("0..3", or "2..*", etc.) to assign >>> + * a subset of the command line arguments to this field. The >>> default is "*", meaning all command line arguments. >>> + * @return an index or range specifying which of the command >> line >>> arguments should be assigned to this field >>> + */ >>> + String index() default "*"; >>> + >>> + /** Description of the parameter(s), used when generating the >>> usage documentation. >>> + * @return the description of the parameter(s) >>> + */ >>> + String[] description() default {}; >>> + >>> + /** >>> + * Specifies the minimum number of required parameters and the >>> maximum number of accepted parameters. If a >>> + * positive arity is declared, and the user specifies an >>> insufficient number of parameters on the command line, >>> + * {@link MissingParameterException} is thrown by the {@link >>> #parse(String...)} method. >>> + * <p>The default depends on the type of the parameter: booleans >>> require no parameters, arrays and Collections >>> + * accept zero to any number of parameters, and any other type >>> accepts one parameter.</p> >>> + * @return the range of minimum and maximum parameters accepted >>> by this command >>> + */ >>> + String arity() default ""; >>> + >>> + /** >>> + * Specify a {@code paramLabel} for the parameter to be used in >>> the usage help message. If omitted, >>> + * picocli uses the field name in fish brackets ({@code '<'} and >>> {@code '>'}) by default. Example: >>> + * <pre>class Example { >>> + * @Parameters(paramLabel="FILE", description="path of >>> the input FILE(s)") >>> + * private File[] inputFiles; >>> + * }</pre> >>> + * <p>By default, the above gives a usage help message like the >>> following:</p><pre> >>> + * Usage: <main class> [FILE...] >>> + * [FILE...] path of the input FILE(s) >>> + * </pre> >>> + * @return name of the positional parameter used in the usage >>> help message >>> + */ >>> + String paramLabel() default ""; >>> + >>> + /** >>> + * <p> >>> + * Specify a {@code type} if the annotated field is a {@code >>> Collection} that should hold objects other than Strings. >>> + * </p><p> >>> + * If the field's type is a {@code Collection}, the generic type >>> parameter of the collection is erased and >>> + * cannot be determined at runtime. Specify a {@code type} >>> attribute to store values other than String in >>> + * the Collection. Picocli will use the {@link ITypeConverter} >>> + * that is {@linkplain #registerConverter(Class, ITypeConverter) >>> registered} for that type to convert >>> + * the raw String values before they are added to the >> collection. >>> + * </p><p> >>> + * When the field's type is an array, the {@code type} attribute >>> is ignored: the values will be converted >>> + * to the array component type and the array will be replaced >>> with a new instance containing both the old and >>> + * the new values. </p> >>> + * @return the type to convert the raw String values to before >>> adding them to the Collection >>> + */ >>> + Class<?> type() default String.class; >>> + >>> + /** >>> + * Specify a regular expression to use to split positional >>> parameter values before applying them to the field. >>> + * All elements resulting from the split are added to the array >>> or Collection. Ignored for single-value fields. >>> + * @return a regular expression to split operand values or >> {@code >>> ""} if the value should not be split >>> + * @see String#split(String) >>> + */ >>> + String split() default ""; >>> + >>> + /** >>> + * Set {@code hidden=true} if this parameter should not be >>> included in the usage message. >>> + * @return whether this parameter should be excluded from the >>> usage message >>> + */ >>> + boolean hidden() default false; >>> + } >>> + >>> + /** >>> + * <p>Annotate your class with {@code @Command} when you want more >>> control over the format of the generated help >>> + * message. >>> + * </p><pre> >>> + * @Command(name = "Encrypt", >>> + * description = "Encrypt FILE(s), or standard input, to >>> standard output or to the output file.", >>> + * footer = "Copyright (c) 2017") >>> + * public class Encrypt { >>> + * @Parameters(paramLabel = "FILE", type = File.class, >>> description = "Any number of input files") >>> + * private List<File> files = new >>> ArrayList<File>(); >>> + * >>> + * @Option(names = { "-o", "--out" }, description = "Output >>> file (default: print to console)") >>> + * private File outputFile; >>> + * }</pre> >>> + * <p> >>> + * The structure of a help message looks like this: >>> + * </p><ul> >>> + * <li>[header]</li> >>> + * <li>[synopsis]: {@code Usage: <commandName> [OPTIONS] >>> [FILE...]}</li> >>> + * <li>[description]</li> >>> + * <li>[parameter list]: {@code [FILE...] Any number of >>> input files}</li> >>> + * <li>[option list]: {@code -h, --help prints this help >>> message and exits}</li> >>> + * <li>[footer]</li> >>> + * </ul> */ >>> + @Retention(RetentionPolicy.RUNTIME) >>> + @Target(ElementType.TYPE) >>> + public @interface Command { >>> + /** Program name to show in the synopsis. If omitted, {@code >>> "<main class>"} is used. >>> + * For {@linkplain #subcommands() declaratively added} >>> subcommands, this attribute is also used >>> + * by the parser to recognize subcommands in the command line >>> arguments. >>> + * @return the program name to show in the synopsis >>> + * @see Help#commandName */ >>> + String name() default "<main class>"; >>> + >>> + /** A list of classes to instantiate and register as >> subcommands. >>> When registering subcommands declaratively >>> + * like this, you don't need to call the {@link >>> CommandLine#addSubcommand(String, Object)} method. For example, this: >>> + * <pre> >>> + * @Command(subcommands = { >>> + * GitStatus.class, >>> + * GitCommit.class, >>> + * GitBranch.class }) >>> + * public class Git { ... } >>> + * >>> + * CommandLine commandLine = new CommandLine(new Git()); >>> + * </pre> is equivalent to this: >>> + * <pre> >>> + * // alternative: programmatically add subcommands. >>> + * // NOTE: in this case there should be no `subcommands` >>> attribute on the @Command annotation. >>> + * @Command public class Git { ... } >>> + * >>> + * CommandLine commandLine = new CommandLine(new Git()) >>> + * .addSubcommand("status", new GitStatus()) >>> + * .addSubcommand("commit", new GitCommit()) >>> + * .addSubcommand("branch", new GitBranch()); >>> + * </pre> >>> + * @return the declaratively registered subcommands of this >>> command, or an empty array if none >>> + * @see CommandLine#addSubcommand(String, Object) >>> + * @since 0.9.8 >>> + */ >>> + Class<?>[] subcommands() default {}; >>> + >>> + /** String that separates options from option parameters. >> Default >>> is {@code "="}. Spaces are also accepted. >>> + * @return the string that separates options from option >>> parameters, used both when parsing and when generating usage help >>> + * @see Help#separator >>> + * @see CommandLine#setSeparator(String) */ >>> + String separator() default "="; >>> + >>> + /** Version information for this command, to print to the >> console >>> when the user specifies an >>> + * {@linkplain Option#versionHelp() option} to request version >>> help. This is not part of the usage help message. >>> + * >>> + * @return a string or an array of strings with version >>> information about this command. >>> + * @since 0.9.8 >>> + * @see CommandLine#printVersionHelp(PrintStream) >>> + */ >>> + String[] version() default {}; >>> + >>> + /** Set the heading preceding the header section. May contain >>> embedded {@linkplain java.util.Formatter format specifiers}. >>> + * @return the heading preceding the header section >>> + * @see Help#headerHeading(Object...) */ >>> + String headerHeading() default ""; >>> + >>> + /** Optional summary description of the command, shown before >> the >>> synopsis. >>> + * @return summary description of the command >>> + * @see Help#header >>> + * @see Help#header(Object...) */ >>> + String[] header() default {}; >>> + >>> + /** Set the heading preceding the synopsis text. May contain >>> embedded >>> + * {@linkplain java.util.Formatter format specifiers}. The >>> default heading is {@code "Usage: "} (without a line >>> + * break between the heading and the synopsis text). >>> + * @return the heading preceding the synopsis text >>> + * @see Help#synopsisHeading(Object...) */ >>> + String synopsisHeading() default "Usage: "; >>> + >>> + /** Specify {@code true} to generate an abbreviated synopsis >> like >>> {@code "<main> [OPTIONS] [PARAMETERS...]"}. >>> + * By default, a detailed synopsis with individual option names >>> and parameters is generated. >>> + * @return whether the synopsis should be abbreviated >>> + * @see Help#abbreviateSynopsis >>> + * @see Help#abbreviatedSynopsis() >>> + * @see Help#detailedSynopsis(Comparator, boolean) */ >>> + boolean abbreviateSynopsis() default false; >>> + >>> + /** Specify one or more custom synopsis lines to display instead >>> of an auto-generated synopsis. >>> + * @return custom synopsis text to replace the auto-generated >>> synopsis >>> + * @see Help#customSynopsis >>> + * @see Help#customSynopsis(Object...) */ >>> + String[] customSynopsis() default {}; >>> + >>> + /** Set the heading preceding the description section. May >>> contain embedded {@linkplain java.util.Formatter format specifiers}. >>> + * @return the heading preceding the description section >>> + * @see Help#descriptionHeading(Object...) */ >>> + String descriptionHeading() default ""; >>> + >>> + /** Optional text to display between the synopsis line(s) and >> the >>> list of options. >>> + * @return description of this command >>> + * @see Help#description >>> + * @see Help#description(Object...) */ >>> + String[] description() default {}; >>> + >>> + /** Set the heading preceding the parameters list. May contain >>> embedded {@linkplain java.util.Formatter format specifiers}. >>> + * @return the heading preceding the parameters list >>> + * @see Help#parameterListHeading(Object...) */ >>> + String parameterListHeading() default ""; >>> + >>> + /** Set the heading preceding the options list. May contain >>> embedded {@linkplain java.util.Formatter format specifiers}. >>> + * @return the heading preceding the options list >>> + * @see Help#optionListHeading(Object...) */ >>> + String optionListHeading() default ""; >>> + >>> + /** Specify {@code false} to show Options in declaration order. >>> The default is to sort alphabetically. >>> + * @return whether options should be shown in alphabetic order. >>> + * @see Help#sortOptions */ >>> + boolean sortOptions() default true; >>> + >>> + /** Prefix required options with this character in the options >>> list. The default is no marker: the synopsis >>> + * indicates which options and parameters are required. >>> + * @return the character to show in the options list to mark >>> required options >>> + * @see Help#requiredOptionMarker */ >>> + char requiredOptionMarker() default ' '; >>> + >>> + /** Specify {@code true} to show default values in the >>> description column of the options list (except for >>> + * boolean options). False by default. >>> + * @return whether the default values for options and parameters >>> should be shown in the description column >>> + * @see Help#showDefaultValues */ >>> + boolean showDefaultValues() default false; >>> + >>> + /** Set the heading preceding the subcommands list. May contain >>> embedded {@linkplain java.util.Formatter format specifiers}. >>> + * The default heading is {@code "Commands:%n"} (with a line >>> break at the end). >>> + * @return the heading preceding the subcommands list >>> + * @see Help#commandListHeading(Object...) */ >>> + String commandListHeading() default "Commands:%n"; >>> + >>> + /** Set the heading preceding the footer section. May contain >>> embedded {@linkplain java.util.Formatter format specifiers}. >>> + * @return the heading preceding the footer section >>> + * @see Help#footerHeading(Object...) */ >>> + String footerHeading() default ""; >>> + >>> + /** Optional text to display after the list of options. >>> + * @return text to display after the list of options >>> + * @see Help#footer >>> + * @see Help#footer(Object...) */ >>> + String[] footer() default {}; >>> + } >>> + /** >>> + * <p> >>> + * When parsing command line arguments and initializing >>> + * fields annotated with {@link Option @Option} or {@link Parameters >>> @Parameters}, >>> + * String values can be converted to any type for which a {@code >>> ITypeConverter} is registered. >>> + * </p><p> >>> + * This interface defines the contract for classes that know how to >>> convert a String into some domain object. >>> + * Custom converters can be registered with the {@link >>> #registerConverter(Class, ITypeConverter)} method. >>> + * </p><p> >>> + * Java 8 lambdas make it easy to register custom type converters: >>> + * </p> >>> + * <pre> >>> + * commandLine.registerConverter(java.nio.file.Path.class, s -> >>> java.nio.file.Paths.get(s)); >>> + * commandLine.registerConverter(java.time.Duration.class, s -> >>> java.time.Duration.parse(s));</pre> >>> + * <p> >>> + * Built-in type converters are pre-registered for the following >> java >>> 1.5 types: >>> + * </p> >>> + * <ul> >>> + * <li>all primitive types</li> >>> + * <li>all primitive wrapper types: Boolean, Byte, Character, >>> Double, Float, Integer, Long, Short</li> >>> + * <li>any enum</li> >>> + * <li>java.io.File</li> >>> + * <li>java.math.BigDecimal</li> >>> + * <li>java.math.BigInteger</li> >>> + * <li>java.net.InetAddress</li> >>> + * <li>java.net.URI</li> >>> + * <li>java.net.URL</li> >>> + * <li>java.nio.charset.Charset</li> >>> + * <li>java.sql.Time</li> >>> + * <li>java.util.Date</li> >>> + * <li>java.util.UUID</li> >>> + * <li>java.util.regex.Pattern</li> >>> + * <li>StringBuilder</li> >>> + * <li>CharSequence</li> >>> + * <li>String</li> >>> + * </ul> >>> + * @param <K> the type of the object that is the result of the >>> conversion >>> + */ >>> + public interface ITypeConverter<K> { >>> + /** >>> + * Converts the specified command line argument value to some >>> domain object. >>> + * @param value the command line argument String value >>> + * @return the resulting domain object >>> + * @throws Exception an exception detailing what went wrong >>> during the conversion >>> + */ >>> + K convert(String value) throws Exception; >>> + } >>> + /** Describes the number of parameters required and accepted by an >>> option or a positional parameter. >>> + * @since 0.9.7 >>> + */ >>> + public static class Range implements Comparable<Range> { >>> + /** Required number of parameters for an option or positional >>> parameter. */ >>> + public final int min; >>> + /** Maximum accepted number of parameters for an option or >>> positional parameter. */ >>> + public final int max; >>> + public final boolean isVariable; >>> + private final boolean isUnspecified; >>> + private final String originalValue; >>> + >>> + /** Constructs a new Range object with the specified parameters. >>> + * @param min minimum number of required parameters >>> + * @param max maximum number of allowed parameters (or >>> Integer.MAX_VALUE if variable) >>> + * @param variable {@code true} if any number or parameters is >>> allowed, {@code false} otherwise >>> + * @param unspecified {@code true} if no arity was specified on >>> the option/parameter (value is based on type) >>> + * @param originalValue the original value that was specified on >>> the option or parameter >>> + */ >>> + public Range(int min, int max, boolean variable, boolean >>> unspecified, String originalValue) { >>> + this.min = min; >>> + this.max = max; >>> + this.isVariable = variable; >>> + this.isUnspecified = unspecified; >>> + this.originalValue = originalValue; >>> + } >>> + /** Returns a new {@code Range} based on the {@link >>> Option#arity()} annotation on the specified field, >>> + * or the field type's default arity if no arity was specified. >>> + * @param field the field whose Option annotation to inspect >>> + * @return a new {@code Range} based on the Option arity >>> annotation on the specified field */ >>> + public static Range optionArity(Field field) { >>> + return field.isAnnotationPresent(Option.class) >>> + ? adjustForType(Range.valueOf( >>> field.getAnnotation(Option.class).arity()), field) >>> + : new Range(0, 0, false, true, "0"); >>> + } >>> + /** Returns a new {@code Range} based on the {@link >>> Parameters#arity()} annotation on the specified field, >>> + * or the field type's default arity if no arity was specified. >>> + * @param field the field whose Parameters annotation to inspect >>> + * @return a new {@code Range} based on the Parameters arity >>> annotation on the specified field */ >>> + public static Range parameterArity(Field field) { >>> + return field.isAnnotationPresent(Parameters.class) >>> + ? adjustForType(Range.valueOf(field.getAnnotation( >> Parameters.class).arity()), >>> field) >>> + : new Range(0, 0, false, true, "0"); >>> + } >>> + /** Returns a new {@code Range} based on the {@link >>> Parameters#index()} annotation on the specified field. >>> + * @param field the field whose Parameters annotation to inspect >>> + * @return a new {@code Range} based on the Parameters index >>> annotation on the specified field */ >>> + public static Range parameterIndex(Field field) { >>> + return field.isAnnotationPresent(Parameters.class) >>> + ? Range.valueOf(field.getAnnotation(Parameters. >>> class).index()) >>> + : new Range(0, 0, false, true, "0"); >>> + } >>> + static Range adjustForType(Range result, Field field) { >>> + return result.isUnspecified ? defaultArity(field.getType()) >> : >>> result; >>> + } >>> + /** Returns a new {@code Range} based on the specified type: >>> booleans have arity 0, arrays or Collections have >>> + * arity "0..*", and other types have arity 1. >>> + * @param type the type whose default arity to return >>> + * @return a new {@code Range} indicating the default arity of >>> the specified type */ >>> + public static Range defaultArity(Class<?> type) { >>> + if (isBoolean(type)) { >>> + return Range.valueOf("0"); >>> + } else if (type.isArray() || Collection.class. >> isAssignableFrom(type)) >>> { >>> + return Range.valueOf("0..*"); >>> + } >>> + return Range.valueOf("1");// for single-valued fields >>> + } >>> + /** Leniently parses the specified String as an {@code Range} >>> value and return the result. A range string can >>> + * be a fixed integer value or a range of the form {@code >>> MIN_VALUE + ".." + MAX_VALUE}. If the >>> + * {@code MIN_VALUE} string is not numeric, the minimum is zero. >>> If the {@code MAX_VALUE} is not numeric, the >>> + * range is taken to be variable and the maximum is {@code >>> Integer.MAX_VALUE}. >>> + * @param range the value range string to parse >>> + * @return a new {@code Range} value */ >>> + public static Range valueOf(String range) { >>> + range = range.trim(); >>> + boolean unspecified = range.length() == 0 || >>> range.startsWith(".."); // || range.endsWith(".."); >>> + int min = -1, max = -1; >>> + boolean variable = false; >>> + int dots = -1; >>> + if ((dots = range.indexOf("..")) >= 0) { >>> + min = parseInt(range.substring(0, dots), 0); >>> + max = parseInt(range.substring(dots + 2), >>> Integer.MAX_VALUE); >>> + variable = max == Integer.MAX_VALUE; >>> + } else { >>> + max = parseInt(range, Integer.MAX_VALUE); >>> + variable = max == Integer.MAX_VALUE; >>> + min = variable ? 0 : max; >>> + } >>> + Range result = new Range(min, max, variable, unspecified, >>> range); >>> + return result; >>> + } >>> + private static int parseInt(String str, int defaultValue) { >>> + try { >>> + return Integer.parseInt(str); >>> + } catch (Exception ex) { >>> + return defaultValue; >>> + } >>> + } >>> + /** Returns a new Range object with the {@code min} value >>> replaced by the specified value. >>> + * The {@code max} of the returned Range is guaranteed not to be >>> less than the new {@code min} value. >>> + * @param newMin the {@code min} value of the returned Range >>> object >>> + * @return a new Range object with the specified {@code min} >>> value */ >>> + public Range min(int newMin) { return new Range(newMin, >>> Math.max(newMin, max), isVariable, isUnspecified, originalValue); } >>> + >>> + /** Returns a new Range object with the {@code max} value >>> replaced by the specified value. >>> + * The {@code min} of the returned Range is guaranteed not to be >>> greater than the new {@code max} value. >>> + * @param newMax the {@code max} value of the returned Range >>> object >>> + * @return a new Range object with the specified {@code max} >>> value */ >>> + public Range max(int newMax) { return new Range(Math.min(min, >>> newMax), newMax, isVariable, isUnspecified, originalValue); } >>> + >>> + public boolean equals(Object object) { >>> + if (!(object instanceof Range)) { return false; } >>> + Range other = (Range) object; >>> + return other.max == this.max && other.min == this.min && >>> other.isVariable == this.isVariable; >>> + } >>> + public int hashCode() { >>> + return ((17 * 37 + max) * 37 + min) * 37 + (isVariable ? 1 : >>> 0); >>> + } >>> + public String toString() { >>> + return min == max ? String.valueOf(min) : min + ".." + >>> (isVariable ? "*" : max); >>> + } >>> + public int compareTo(Range other) { >>> + int result = min - other.min; >>> + return (result == 0) ? max - other.max : result; >>> + } >>> + } >>> + private static void init(Class<?> cls, >>> + List<Field> requiredFields, >>> + Map<String, Field> optionName2Field, >>> + Map<Character, Field> >> singleCharOption2Field, >>> + List<Field> positionalParametersFields) { >>> + Field[] declaredFields = cls.getDeclaredFields(); >>> + for (Field field : declaredFields) { >>> + field.setAccessible(true); >>> + if (field.isAnnotationPresent(Option.class)) { >>> + Option option = field.getAnnotation(Option.class); >>> + if (option.required()) { >>> + requiredFields.add(field); >>> + } >>> + for (String name : option.names()) { // cannot be null >> or >>> empty >>> + Field existing = optionName2Field.put(name, field); >>> + if (existing != null && existing != field) { >>> + throw DuplicateOptionAnnotationsExce >> ption.create(name, >>> field, existing); >>> + } >>> + if (name.length() == 2 && name.startsWith("-")) { >>> + char flag = name.charAt(1); >>> + Field existing2 = singleCharOption2Field.put( >> flag, >>> field); >>> + if (existing2 != null && existing2 != field) { >>> + throw DuplicateOptionAnnotationsExce >> ption.create(name, >>> field, existing2); >>> + } >>> + } >>> + } >>> + } >>> + if (field.isAnnotationPresent(Parameters.class)) { >>> + if (field.isAnnotationPresent(Option.class)) { >>> + throw new ParameterException("A field can be either >>> @Option or @Parameters, but '" >>> + + field.getName() + "' is both."); >>> + } >>> + positionalParametersFields.add(field); >>> + Range arity = Range.parameterArity(field); >>> + if (arity.min > 0) { >>> + requiredFields.add(field); >>> + } >>> + } >>> + } >>> + } >>> + static void validatePositionalParameters(List<Field> >>> positionalParametersFields) { >>> + int min = 0; >>> + for (Field field : positionalParametersFields) { >>> + Range index = Range.parameterIndex(field); >>> + if (index.min > min) { >>> + throw new ParameterIndexGapException("Missing field >>> annotated with @Parameter(index=" + min + >>> + "). Nearest field '" + field.getName() + "' has >>> index=" + index.min); >>> + } >>> + min = Math.max(min, index.max); >>> + min = min == Integer.MAX_VALUE ? min : min + 1; >>> + } >>> + } >>> + private static <T> Stack<T> reverse(Stack<T> stack) { >>> + Collections.reverse(stack); >>> + return stack; >>> + } >>> + /** >>> + * Helper class responsible for processing command line arguments. >>> + */ >>> + private class Interpreter { >>> + private final Map<String, CommandLine> commands >>> = new LinkedHashMap<String, CommandLine>(); >>> + private final Map<Class<?>, ITypeConverter<?>> converterRegistry >>> = new HashMap<Class<?>, ITypeConverter<?>>(); >>> + private final Map<String, Field> optionName2Field >>> = new HashMap<String, Field>(); >>> + private final Map<Character, Field> singleCharOption2Field >>> = new HashMap<Character, Field>(); >>> + private final List<Field> requiredFields >>> = new ArrayList<Field>(); >>> + private final List<Field> positionalParametersFields >>> = new ArrayList<Field>(); >>> + private final Object command; >>> + private boolean isHelpRequested; >>> + private String separator = "="; >>> + >>> + Interpreter(Object command) { >>> + converterRegistry.put(Path.class, new >>> BuiltIn.PathConverter()); >>> + converterRegistry.put(String.class, new >>> BuiltIn.StringConverter()); >>> + converterRegistry.put(StringBuilder.class, new BuiltIn. >>> StringBuilderConverter()); >>> + converterRegistry.put(CharSequence.class, new >>> BuiltIn.CharSequenceConverter()); >>> + converterRegistry.put(Byte.class, new >>> BuiltIn.ByteConverter()); >>> + converterRegistry.put(Byte.TYPE, new >>> BuiltIn.ByteConverter()); >>> + converterRegistry.put(Boolean.class, new >>> BuiltIn.BooleanConverter()); >>> + converterRegistry.put(Boolean.TYPE, new >>> BuiltIn.BooleanConverter()); >>> + converterRegistry.put(Character.class, new >>> BuiltIn.CharacterConverter()); >>> + converterRegistry.put(Character.TYPE, new >>> BuiltIn.CharacterConverter()); >>> + converterRegistry.put(Short.class, new >>> BuiltIn.ShortConverter()); >>> + converterRegistry.put(Short.TYPE, new >>> BuiltIn.ShortConverter()); >>> + converterRegistry.put(Integer.class, new >>> BuiltIn.IntegerConverter()); >>> + converterRegistry.put(Integer.TYPE, new >>> BuiltIn.IntegerConverter()); >>> + converterRegistry.put(Long.class, new >>> BuiltIn.LongConverter()); >>> + converterRegistry.put(Long.TYPE, new >>> BuiltIn.LongConverter()); >>> + converterRegistry.put(Float.class, new >>> BuiltIn.FloatConverter()); >>> + converterRegistry.put(Float.TYPE, new >>> BuiltIn.FloatConverter()); >>> + converterRegistry.put(Double.class, new >>> BuiltIn.DoubleConverter()); >>> + converterRegistry.put(Double.TYPE, new >>> BuiltIn.DoubleConverter()); >>> + converterRegistry.put(File.class, new >>> BuiltIn.FileConverter()); >>> + converterRegistry.put(URI.class, new >>> BuiltIn.URIConverter()); >>> + converterRegistry.put(URL.class, new >>> BuiltIn.URLConverter()); >>> + converterRegistry.put(Date.class, new >>> BuiltIn.ISO8601DateConverter()); >>> + converterRegistry.put(Time.class, new >>> BuiltIn.ISO8601TimeConverter()); >>> + converterRegistry.put(BigDecimal.class, new >>> BuiltIn.BigDecimalConverter()); >>> + converterRegistry.put(BigInteger.class, new >>> BuiltIn.BigIntegerConverter()); >>> + converterRegistry.put(Charset.class, new >>> BuiltIn.CharsetConverter()); >>> + converterRegistry.put(InetAddress.class, new >>> BuiltIn.InetAddressConverter()); >>> + converterRegistry.put(Pattern.class, new >>> BuiltIn.PatternConverter()); >>> + converterRegistry.put(UUID.class, new >>> BuiltIn.UUIDConverter()); >>> + >>> + this.command = Assert.notNull(command, >> "command"); >>> + Class<?> cls = command.getClass(); >>> + String declaredSeparator = null; >>> + boolean hasCommandAnnotation = false; >>> + while (cls != null) { >>> + init(cls, requiredFields, optionName2Field, >>> singleCharOption2Field, positionalParametersFields); >>> + if (cls.isAnnotationPresent(Command.class)) { >>> + hasCommandAnnotation = true; >>> + Command cmd = cls.getAnnotation(Command.class); >>> + declaredSeparator = (declaredSeparator == null) ? >>> cmd.separator() : declaredSeparator; >>> + CommandLine.this.versionLines. >>> addAll(Arrays.asList(cmd.version())); >>> + >>> + for (Class<?> sub : cmd.subcommands()) { >>> + Command subCommand = sub.getAnnotation(Command. >>> class); >>> + if (subCommand == null || >>> Help.DEFAULT_COMMAND_NAME.equals(subCommand.name())) { >>> + throw new IllegalArgumentException(" >> Subcommand >>> " + sub.getName() + >>> + " is missing the mandatory @Command >>> annotation with a 'name' attribute"); >>> + } >>> + try { >>> + Constructor<?> constructor = >>> sub.getDeclaredConstructor(); >>> + constructor.setAccessible(true); >>> + CommandLine commandLine = >>> toCommandLine(constructor.newInstance()); >>> + commandLine.parent = CommandLine.this; >>> + commands.put(subCommand.name(), >> commandLine); >>> + } >>> + catch (IllegalArgumentException ex) { throw ex; >> } >>> + catch (NoSuchMethodException ex) { throw new >>> IllegalArgumentException("Cannot instantiate subcommand " + >>> + sub.getName() + ": the class has no >>> constructor", ex); } >>> + catch (Exception ex) { >>> + throw new IllegalStateException("Could not >>> instantiate and add subcommand " + >>> + sub.getName() + ": " + ex, ex); >>> + } >>> + } >>> + } >>> + cls = cls.getSuperclass(); >>> + } >>> + separator = declaredSeparator != null ? declaredSeparator : >>> separator; >>> + Collections.sort(positionalParametersFields, new >>> PositionalParametersSorter()); >>> + validatePositionalParameters(positionalParametersFields); >>> + >>> + if (positionalParametersFields.isEmpty() && >>> optionName2Field.isEmpty() && !hasCommandAnnotation) { >>> + throw new IllegalArgumentException(command + " (" + >>> command.getClass() + >>> + ") is not a command: it has no @Command, @Option >>> or @Parameters annotations"); >>> + } >>> + } >>> + >>> + /** >>> + * Entry point into parsing command line arguments. >>> + * @param args the command line arguments >>> + * @return a list with all commands and subcommands initialized >>> by this method >>> + * @throws ParameterException if the specified command line >>> arguments are invalid >>> + */ >>> + List<CommandLine> parse(String... args) { >>> + Assert.notNull(args, "argument array"); >>> + Stack<String> arguments = new Stack<String>(); >>> + for (int i = args.length - 1; i >= 0; i--) { >>> + arguments.push(args[i]); >>> + } >>> + List<CommandLine> result = new ArrayList<CommandLine>(); >>> + parse(result, arguments, args); >>> + return result; >>> + } >>> + >>> + private void parse(List<CommandLine> parsedCommands, >>> Stack<String> argumentStack, String[] originalArgs) { >>> + // first reset any state in case this CommandLine instance >> is >>> being reused >>> + isHelpRequested = false; >>> + CommandLine.this.versionHelpRequested = false; >>> + CommandLine.this.usageHelpRequested = false; >>> + >>> + parsedCommands.add(CommandLine.this); >>> + List<Field> required = new ArrayList<Field>( >> requiredFields); >>> + Set<Field> initialized = new HashSet<Field>(); >>> + Collections.sort(required, new >> PositionalParametersSorter()); >>> + try { >>> + processArguments(parsedCommands, argumentStack, >>> required, initialized, originalArgs); >>> + } catch (ParameterException ex) { >>> + throw ex; >>> + } catch (Exception ex) { >>> + int offendingArgIndex = originalArgs.length - >>> argumentStack.size(); >>> + String arg = offendingArgIndex >= 0 && offendingArgIndex >>> < originalArgs.length ? originalArgs[offendingArgIndex] : "?"; >>> + throw ParameterException.create(ex, arg, >>> argumentStack.size(), originalArgs); >>> + } >>> + if (!isAnyHelpRequested() && !required.isEmpty()) { >>> + if (required.get(0).isAnnotationPresent(Option.class)) >> { >>> + throw MissingParameterException.create(required); >>> + } else { >>> + try { >>> + processPositionalParameters0(required, true, >> new >>> Stack<String>()); >>> + } catch (ParameterException ex) { throw ex; >>> + } catch (Exception ex) { throw new >>> IllegalStateException("Internal error: " + ex, ex); } >>> + } >>> + } >>> + } >>> + >>> + private void processArguments(List<CommandLine> parsedCommands, >>> + Stack<String> args, >>> + Collection<Field> required, >>> + Set<Field> initialized, >>> + String[] originalArgs) throws >>> Exception { >>> + // arg must be one of: >>> + // 1. the "--" double dash separating options from >> positional >>> arguments >>> + // 1. a stand-alone flag, like "-v" or "--verbose": no value >>> required, must map to boolean or Boolean field >>> + // 2. a short option followed by an argument, like "-f file" >>> or "-ffile": may map to any type of field >>> + // 3. a long option followed by an argument, like "-file >>> out.txt" or "-file=out.txt" >>> + // 3. one or more remaining arguments without any associated >>> options. Must be the last in the list. >>> + // 4. a combination of stand-alone options, like "-vxr". >>> Equivalent to "-v -x -r", "-v true -x true -r true" >>> + // 5. a combination of stand-alone options and one option >>> with an argument, like "-vxrffile" >>> + >>> + while (!args.isEmpty()) { >>> + String arg = args.pop(); >>> + >>> + // Double-dash separates options from positional >>> arguments. >>> + // If found, then interpret the remaining args as >>> positional parameters. >>> + if ("--".equals(arg)) { >>> + processPositionalParameters(required, args); >>> + return; // we are done >>> + } >>> + >>> + // if we find another command, we are done with the >>> current command >>> + if (commands.containsKey(arg)) { >>> + if (!isHelpRequested && !required.isEmpty()) { // >>> ensure current command portion is valid >>> + throw MissingParameterException. >> create(required); >>> + } >>> + commands.get(arg).interpreter.parse(parsedCommands, >>> args, originalArgs); >>> + return; // remainder done by the command >>> + } >>> + >>> + // First try to interpret the argument as a single >> option >>> (as opposed to a compact group of options). >>> + // A single option may be without option parameters, >> like >>> "-v" or "--verbose" (a boolean value), >>> + // or an option may have one or more option parameters. >>> + // A parameter may be attached to the option. >>> + boolean paramAttachedToOption = false; >>> + int separatorIndex = arg.indexOf(separator); >>> + if (separatorIndex > 0) { >>> + String key = arg.substring(0, separatorIndex); >>> + // be greedy. Consume the whole arg as an option if >>> possible. >>> + if (optionName2Field.containsKey(key) && >>> !optionName2Field.containsKey(arg)) { >>> + paramAttachedToOption = true; >>> + String optionParam = >> arg.substring(separatorIndex >>> + separator.length()); >>> + args.push(optionParam); >>> + arg = key; >>> + } >>> + } >>> + if (optionName2Field.containsKey(arg)) { >>> + processStandaloneOption(required, initialized, arg, >>> args, paramAttachedToOption); >>> + } >>> + // Compact (single-letter) options can be grouped with >>> other options or with an argument. >>> + // only single-letter options can be combined with other >>> options or with an argument >>> + else if (arg.length() > 2 && arg.startsWith("-")) { >>> + processClusteredShortOptions(required, initialized, >>> arg, args); >>> + } >>> + // The argument could not be interpreted as an option. >>> + // We take this to mean that the remainder are >> positional >>> arguments >>> + else { >>> + args.push(arg); >>> + processPositionalParameters(required, args); >>> + return; >>> + } >>> + } >>> + } >>> + >>> + private void processPositionalParameters(Collection<Field> >>> required, Stack<String> args) throws Exception { >>> + processPositionalParameters0(required, false, args); >>> + if (!args.empty()) { >>> + handleUnmatchedArguments(args); >>> + return; >>> + }; >>> + } >>> + >>> + private void handleUnmatchedArguments(Stack<String> args) { >>> + if (!isUnmatchedArgumentsAllowed()) { throw new >>> UnmatchedArgumentException(args); } >>> + while (!args.isEmpty()) { unmatchedArguments.add(args. >> pop()); >>> } // addAll would give args in reverse order >>> + } >>> + >>> + private void processPositionalParameters0(Collection<Field> >>> required, boolean validateOnly, Stack<String> args) throws Exception { >>> + int max = -1; >>> + for (Field positionalParam : positionalParametersFields) { >>> + Range indexRange = Range.parameterIndex( >> positionalParam); >>> + max = Math.max(max, indexRange.max); >>> + @SuppressWarnings("unchecked") >>> + Stack<String> argsCopy = reverse((Stack<String>) >>> args.clone()); >>> + if (!indexRange.isVariable) { >>> + for (int i = argsCopy.size() - 1; i > >> indexRange.max; >>> i--) { >>> + argsCopy.removeElementAt(i); >>> + } >>> + } >>> + Collections.reverse(argsCopy); >>> + for (int i = 0; i < indexRange.min && >>> !argsCopy.isEmpty(); i++) { argsCopy.pop(); } >>> + Range arity = Range.parameterArity(positionalParam); >>> + assertNoMissingParameters(positionalParam, arity.min, >>> argsCopy); >>> + if (!validateOnly) { >>> + applyOption(positionalParam, Parameters.class, >> arity, >>> false, argsCopy, null); >>> + required.remove(positionalParam); >>> + } >>> + } >>> + // remove processed args from the stack >>> + if (!validateOnly && !positionalParametersFields.isEmpty()) >> { >>> + int processedArgCount = Math.min(args.size(), max < >>> Integer.MAX_VALUE ? max + 1 : Integer.MAX_VALUE); >>> + for (int i = 0; i < processedArgCount; i++) { >> args.pop(); >>> } >>> + } >>> + } >>> + >>> + private void processStandaloneOption(Collection<Field> >> required, >>> + Set<Field> initialized, >>> + String arg, >>> + Stack<String> args, >>> + boolean paramAttachedToKey) >>> throws Exception { >>> + Field field = optionName2Field.get(arg); >>> + required.remove(field); >>> + Range arity = Range.optionArity(field); >>> + if (paramAttachedToKey) { >>> + arity = arity.min(Math.max(1, arity.min)); // if >>> key=value, minimum arity is at least 1 >>> + } >>> + applyOption(field, Option.class, arity, paramAttachedToKey, >>> args, initialized); >>> + } >>> + >>> + private void processClusteredShortOptions(Collection<Field> >>> required, >>> + Set<Field> >> initialized, >>> + String arg, >>> + Stack<String> args) >>> + throws Exception { >>> + String prefix = arg.substring(0, 1); >>> + String cluster = arg.substring(1); >>> + boolean paramAttachedToOption = true; >>> + do { >>> + if (cluster.length() > 0 && singleCharOption2Field. >>> containsKey(cluster.charAt(0))) { >>> + Field field = singleCharOption2Field.get( >>> cluster.charAt(0)); >>> + required.remove(field); >>> + cluster = cluster.length() > 0 ? >> cluster.substring(1) >>> : ""; >>> + paramAttachedToOption = cluster.length() > 0; >>> + Range arity = Range.optionArity(field); >>> + if (cluster.startsWith(separator)) {// attached >> with >>> separator, like -f=FILE or -v=true >>> + cluster = cluster.substring(separator. >> length()); >>> + arity = arity.min(Math.max(1, arity.min)); // if >>> key=value, minimum arity is at least 1 >>> + } >>> + args.push(cluster); // interpret remainder as option >>> parameter (CAUTION: may be empty string!) >>> + // arity may be >= 1, or >>> + // arity <= 0 && !cluster.startsWith(separator) >>> + // e.g., boolean @Option("-v", arity=0, >>> varargs=true); arg "-rvTRUE", remainder cluster="TRUE" >>> + int consumed = applyOption(field, Option.class, >>> arity, paramAttachedToOption, args, initialized); >>> + // only return if cluster (and maybe more) was >>> consumed, otherwise continue do-while loop >>> + if (consumed > 0) { >>> + return; >>> + } >>> + cluster = args.pop(); >>> + } else { // cluster is empty || cluster.charAt(0) is not >>> a short option key >>> + if (cluster.length() == 0) { // we finished parsing >> a >>> group of short options like -rxv >>> + return; // return normally and parse the next >> arg >>> + } >>> + // We get here when the remainder of the cluster >>> group is neither an option, >>> + // nor a parameter that the last option could >> consume. >>> + if (arg.endsWith(cluster)) { >>> + // remainder was part of a clustered group that >>> could not be completely parsed >>> + args.push(paramAttachedToOption ? prefix + >>> cluster : cluster); >>> + handleUnmatchedArguments(args); >>> + } >>> + args.push(cluster); >>> + processPositionalParameters(required, args); >>> + return; >>> + } >>> + } while (true); >>> + } >>> + >>> + private int applyOption(Field field, >>> + Class<?> annotation, >>> + Range arity, >>> + boolean valueAttachedToOption, >>> + Stack<String> args, >>> + Set<Field> initialized) throws >> Exception { >>> + updateHelpRequested(field); >>> + if (!args.isEmpty() && args.peek().length() == 0 && !v >>> >>> <TRUNCATED> >>> >> > > > > -- > Matt Sicker <boa...@gmail.com>