Thank you for retracting your -1, Gary. All, Would core.tools be a better place for this package, just core, or is core.util okay?
On Tue, Aug 15, 2017 at 3:03 AM, Gary Gregory <garydgreg...@gmail.com> wrote: > Can we please avoid "util" packages, that just tells me "I can't be > bothered to think of a good name". Just remove the "util" level IMO. > > Gary > > On Mon, Aug 14, 2017 at 12:02 PM, Gary Gregory <garydgreg...@gmail.com> > wrote: > > > Ugh, not a fan, but I'll retract my -1. > > > > "a single class", yes, but a 4000 line class. > > > > Gary > > > > On Mon, Aug 14, 2017 at 11:06 AM, 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/#_us > >> age_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.or > >> g/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/soft > >> ware/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(fi > >> eld.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(p > >> ositionalParametersFields); > >> > > + > >> > > + 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).isAnnotationP > >> resent(Option.class)) > >> > { > >> > > + throw MissingParameterException.crea > >> te(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.is > >> Empty()) > >> > { > >> > > + 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> > >> > > > > >