I kind of consider the tools package for command line tools, so it sorta does make sense to include the command line parser in the same package.
On 14 August 2017 at 17:36, Remko Popma <remko.po...@gmail.com> wrote: > 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> > > >> > > > > > > > > > -- Matt Sicker <boa...@gmail.com>