All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.logging.log4j.core.tools.picocli.CommandLine Maven / Gradle / Ivy

There is a newer version: 3.0.0-beta2
Show newest version
/*
 * 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.tools.picocli;

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.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
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.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.Stack;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.regex.Pattern;

import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.IStyle;
import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.Style;
import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.Text;

import static java.util.Locale.ENGLISH;
import static org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Column.Overflow.SPAN;
import static org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Column.Overflow.TRUNCATE;
import static org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Column.Overflow.WRAP;

/**
 * 

* CommandLine interpreter that uses reflection to initialize an annotated domain object with values obtained from the * command line arguments. *

Example

*
import static picocli.CommandLine.*;
 *
 * @Command(header = "Encrypt FILE(s), or standard input, to standard output or to the output file.",
 *          version = "v1.2.3")
 * 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"}, usageHelp = true, description = "Display this help and exit")
 *     private boolean help;
 *
 *     @Option(names = { "-V", "--version"}, versionHelp = true, description = "Display version info and exit")
 *     private boolean versionHelp;
 * }
 * 
*

* Use {@code CommandLine} to initialize a domain object as follows: *

 * public static void main(String... args) {
 *     Encrypt encrypt = new Encrypt();
 *     try {
 *         List<CommandLine> parsedCommands = new CommandLine(encrypt).parse(args);
 *         if (!CommandLine.printHelpIfRequested(parsedCommands, System.err, Help.Ansi.AUTO)) {
 *             runProgram(encrypt);
 *         }
 *     } catch (ParameterException ex) { // command line arguments could not be parsed
 *         System.err.println(ex.getMessage());
 *         ex.getCommandLine().usage(System.err);
 *     }
 * }
 * 

* Invoke the above program with some command line arguments. The below are all equivalent: *

*
 * --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
 * 
*/ public class CommandLine { /** This is picocli version {@value}. */ public static final String VERSION = "2.0.3"; private final Tracer tracer = new Tracer(); private final Interpreter interpreter; private String commandName = Help.DEFAULT_COMMAND_NAME; private boolean overwrittenOptionsAllowed = false; private boolean unmatchedArgumentsAllowed = false; private final List unmatchedArguments = new ArrayList<>(); private CommandLine parent; private boolean usageHelpRequested; private boolean versionHelpRequested; private final List versionLines = new ArrayList<>(); /** * 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 InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation */ public CommandLine(final Object command) { interpreter = new Interpreter(command); } /** Registers a subcommand with the specified name. For example: *
     * 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())
     *         //...
     *         ;
     * 
* *

The specified object can be an annotated object or a * {@code CommandLine} instance with its own nested subcommands. For example:

*
     * 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())
     *                 )
     *         );
     * 
*

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.

*

See also the {@link Command#subcommands()} annotation to register subcommands declaratively.

* * @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 * @see Command#subcommands() */ public CommandLine addSubcommand(final String name, final Object command) { final 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 getSubcommands() { return new LinkedHashMap<>(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. * @param the type of the variable that the return value is being assigned to * @return the annotated object that this {@code CommandLine} instance was constructed with * @since 0.9.7 */ public T getCommand() { return (T) 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()}. * @since 0.9.8 */ 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()}. * @since 0.9.8 */ 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. *

The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added * later will have the default setting. To ensure a setting is applied to all * subcommands, call the setter last, after adding subcommands.

* @param newValue the new setting * @return this {@code CommandLine} object, to allow method chaining * @since 0.9.7 */ public CommandLine setOverwrittenOptionsAllowed(final boolean newValue) { this.overwrittenOptionsAllowed = newValue; for (final 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. *

The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added * later will have the default setting. To ensure a setting is applied to all * subcommands, call the setter last, after adding subcommands.

* @param newValue the new setting. When {@code true}, the last unmatched arguments are available via the {@link #getUnmatchedArguments()} method. * @return this {@code CommandLine} object, to allow method chaining * @since 0.9.7 * @see #getUnmatchedArguments() */ public CommandLine setUnmatchedArgumentsAllowed(final boolean newValue) { this.unmatchedArgumentsAllowed = newValue; for (final 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 getUnmatchedArguments() { return unmatchedArguments; } /** *

* Convenience method that initializes the specified annotated object from the specified command line arguments. *

* This is equivalent to *

     * CommandLine cli = new CommandLine(command);
     * cli.parse(args);
     * return command;
     * 
* * @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 the type of the annotated object * @return the specified annotated object * @throws InitializationException 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 populateCommand(final T command, final String... args) { final CommandLine cli = toCommandLine(command); cli.parse(args); return command; } /** Parses the specified command line arguments and returns a list of {@code CommandLine} objects representing the * top-level command and any subcommands (if any) that were recognized and initialized during the parsing process. *

* If parsing succeeds, the first element in the returned list is always {@code this CommandLine} object. The * returned list may contain more elements if subcommands were {@linkplain #addSubcommand(String, Object) registered} * and these subcommands were initialized by matching command line arguments. If parsing fails, a * {@link ParameterException} is thrown. *

* * @param args the command line arguments to parse * @return a list with the top-level command and any subcommands initialized by this method * @throws ParameterException if the specified command line arguments are invalid; use * {@link ParameterException#getCommandLine()} to get the command or subcommand whose user input was invalid */ public List parse(final String... args) { return interpreter.parse(args); } /** * Represents a function that can process a List of {@code CommandLine} objects resulting from successfully * {@linkplain #parse(String...) parsing} the command line arguments. This is a * functional interface * whose functional method is {@link #handleParseResult(List, PrintStream, CommandLine.Help.Ansi)}. *

* Implementations of this functions can be passed to the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) CommandLine::parseWithHandler} * methods to take some next step after the command line was successfully parsed. *

* @see RunFirst * @see RunLast * @see RunAll * @since 2.0 */ public static interface IParseResultHandler { /** Processes a List of {@code CommandLine} objects resulting from successfully * {@linkplain #parse(String...) parsing} the command line arguments and optionally returns a list of results. * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments * @param out the {@code PrintStream} to print help to if requested * @param ansi for printing help messages using ANSI styles and colors * @return a list of results, or an empty list if there are no results * @throws ExecutionException if a problem occurred while processing the parse results; use * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed */ List handleParseResult(List parsedCommands, PrintStream out, Help.Ansi ansi) throws ExecutionException; } /** * Represents a function that can handle a {@code ParameterException} that occurred while * {@linkplain #parse(String...) parsing} the command line arguments. This is a * functional interface * whose functional method is {@link #handleException(CommandLine.ParameterException, PrintStream, CommandLine.Help.Ansi, String...)}. *

* Implementations of this functions can be passed to the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) CommandLine::parseWithHandler} * methods to handle situations when the command line could not be parsed. *

* @see DefaultExceptionHandler * @since 2.0 */ public static interface IExceptionHandler { /** Handles a {@code ParameterException} that occurred while {@linkplain #parse(String...) parsing} the command * line arguments and optionally returns a list of results. * @param ex the ParameterException describing the problem that occurred while parsing the command line arguments, * and the CommandLine representing the command or subcommand whose input was invalid * @param out the {@code PrintStream} to print help to if requested * @param ansi for printing help messages using ANSI styles and colors * @param args the command line arguments that could not be parsed * @return a list of results, or an empty list if there are no results */ List handleException(ParameterException ex, PrintStream out, Help.Ansi ansi, String... args); } /** * Default exception handler that prints the exception message to the specified {@code PrintStream}, followed by the * usage message for the command or subcommand whose input was invalid. *

Implementation roughly looks like this:

*
     *     System.err.println(paramException.getMessage());
     *     paramException.getCommandLine().usage(System.err);
     * 
* @since 2.0 */ public static class DefaultExceptionHandler implements IExceptionHandler { @Override public List handleException(final ParameterException ex, final PrintStream out, final Help.Ansi ansi, final String... args) { out.println(ex.getMessage()); ex.getCommandLine().usage(out, ansi); return Collections.emptyList(); } } /** * Helper method that may be useful when processing the list of {@code CommandLine} objects that result from successfully * {@linkplain #parse(String...) parsing} command line arguments. This method prints out * {@linkplain #usage(PrintStream, Help.Ansi) usage help} if {@linkplain #isUsageHelpRequested() requested} * or {@linkplain #printVersionHelp(PrintStream, Help.Ansi) version help} if {@linkplain #isVersionHelpRequested() requested} * and returns {@code true}. Otherwise, if none of the specified {@code CommandLine} objects have help requested, * this method returns {@code false}. *

* Note that this method only looks at the {@link Option#usageHelp() usageHelp} and * {@link Option#versionHelp() versionHelp} attributes. The {@link Option#help() help} attribute is ignored. *

* @param parsedCommands the list of {@code CommandLine} objects to check if help was requested * @param out the {@code PrintStream} to print help to if requested * @param ansi for printing help messages using ANSI styles and colors * @return {@code true} if help was printed, {@code false} otherwise * @since 2.0 */ public static boolean printHelpIfRequested(final List parsedCommands, final PrintStream out, final Help.Ansi ansi) { for (final CommandLine parsed : parsedCommands) { if (parsed.isUsageHelpRequested()) { parsed.usage(out, ansi); return true; } else if (parsed.isVersionHelpRequested()) { parsed.printVersionHelp(out, ansi); return true; } } return false; } private static Object execute(final CommandLine parsed) { final Object command = parsed.getCommand(); if (command instanceof Runnable) { try { ((Runnable) command).run(); return null; } catch (final Exception ex) { throw new ExecutionException(parsed, "Error while running command (" + command + ")", ex); } } else if (command instanceof Callable) { try { return ((Callable) command).call(); } catch (final Exception ex) { throw new ExecutionException(parsed, "Error while calling command (" + command + ")", ex); } } throw new ExecutionException(parsed, "Parsed command (" + command + ") is not Runnable or Callable"); } /** * Command line parse result handler that prints help if requested, and otherwise executes the top-level * {@code Runnable} or {@code Callable} command. * For use in the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) parseWithHandler} methods. *

* From picocli v2.0, {@code RunFirst} is used to implement the {@link #run(Runnable, PrintStream, Help.Ansi, String...) run} * and {@link #call(Callable, PrintStream, Help.Ansi, String...) call} convenience methods. *

* @since 2.0 */ public static class RunFirst implements IParseResultHandler { /** Prints help if requested, and otherwise executes the top-level {@code Runnable} or {@code Callable} command. * If the top-level command does not implement either {@code Runnable} or {@code Callable}, a {@code ExecutionException} * is thrown detailing the problem and capturing the offending {@code CommandLine} object. * * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments * @param out the {@code PrintStream} to print help to if requested * @param ansi for printing help messages using ANSI styles and colors * @return an empty list if help was requested, or a list containing a single element: the result of calling the * {@code Callable}, or a {@code null} element if the top-level command was a {@code Runnable} * @throws ExecutionException if a problem occurred while processing the parse results; use * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed */ @Override public List handleParseResult(final List parsedCommands, final PrintStream out, final Help.Ansi ansi) { if (printHelpIfRequested(parsedCommands, out, ansi)) { return Collections.emptyList(); } return Arrays.asList(execute(parsedCommands.get(0))); } } /** * Command line parse result handler that prints help if requested, and otherwise executes the most specific * {@code Runnable} or {@code Callable} subcommand. * For use in the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) parseWithHandler} methods. *

* Something like this:

*
     *     // RunLast implementation: print help if requested, otherwise execute the most specific subcommand
     *     if (CommandLine.printHelpIfRequested(parsedCommands, System.err, Help.Ansi.AUTO)) {
     *         return emptyList();
     *     }
     *     CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
     *     Object command = last.getCommand();
     *     if (command instanceof Runnable) {
     *         try {
     *             ((Runnable) command).run();
     *         } catch (Exception ex) {
     *             throw new ExecutionException(last, "Error in runnable " + command, ex);
     *         }
     *     } else if (command instanceof Callable) {
     *         Object result;
     *         try {
     *             result = ((Callable) command).call();
     *         } catch (Exception ex) {
     *             throw new ExecutionException(last, "Error in callable " + command, ex);
     *         }
     *         // ...do something with result
     *     } else {
     *         throw new ExecutionException(last, "Parsed command (" + command + ") is not Runnable or Callable");
     *     }
     * 
* @since 2.0 */ public static class RunLast implements IParseResultHandler { /** Prints help if requested, and otherwise executes the most specific {@code Runnable} or {@code Callable} subcommand. * If the last (sub)command does not implement either {@code Runnable} or {@code Callable}, a {@code ExecutionException} * is thrown detailing the problem and capturing the offending {@code CommandLine} object. * * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments * @param out the {@code PrintStream} to print help to if requested * @param ansi for printing help messages using ANSI styles and colors * @return an empty list if help was requested, or a list containing a single element: the result of calling the * {@code Callable}, or a {@code null} element if the last (sub)command was a {@code Runnable} * @throws ExecutionException if a problem occurred while processing the parse results; use * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed */ @Override public List handleParseResult(final List parsedCommands, final PrintStream out, final Help.Ansi ansi) { if (printHelpIfRequested(parsedCommands, out, ansi)) { return Collections.emptyList(); } final CommandLine last = parsedCommands.get(parsedCommands.size() - 1); return Arrays.asList(execute(last)); } } /** * Command line parse result handler that prints help if requested, and otherwise executes the top-level command and * all subcommands as {@code Runnable} or {@code Callable}. * For use in the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) parseWithHandler} methods. * @since 2.0 */ public static class RunAll implements IParseResultHandler { /** Prints help if requested, and otherwise executes the top-level command and all subcommands as {@code Runnable} * or {@code Callable}. If any of the {@code CommandLine} commands does not implement either * {@code Runnable} or {@code Callable}, a {@code ExecutionException} * is thrown detailing the problem and capturing the offending {@code CommandLine} object. * * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments * @param out the {@code PrintStream} to print help to if requested * @param ansi for printing help messages using ANSI styles and colors * @return an empty list if help was requested, or a list containing the result of executing all commands: * the return values from calling the {@code Callable} commands, {@code null} elements for commands that implement {@code Runnable} * @throws ExecutionException if a problem occurred while processing the parse results; use * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed */ @Override public List handleParseResult(final List parsedCommands, final PrintStream out, final Help.Ansi ansi) { if (printHelpIfRequested(parsedCommands, out, ansi)) { return null; } final List result = new ArrayList<>(); for (final CommandLine parsed : parsedCommands) { result.add(execute(parsed)); } return result; } } /** * Returns the result of calling {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...)} * with {@code Help.Ansi.AUTO} and a new {@link DefaultExceptionHandler} in addition to the specified parse result handler, * {@code PrintStream}, and the specified command line arguments. *

* This is a convenience method intended to offer the same ease of use as the {@link #run(Runnable, PrintStream, Help.Ansi, String...) run} * and {@link #call(Callable, PrintStream, Help.Ansi, String...) call} methods, but with more flexibility and better * support for nested subcommands. *

*

Calling this method roughly expands to:

*
     * try {
     *     List<CommandLine> parsedCommands = parse(args);
     *     return parseResultsHandler.handleParseResult(parsedCommands, out, Help.Ansi.AUTO);
     * } catch (ParameterException ex) {
     *     return new DefaultExceptionHandler().handleException(ex, out, ansi, args);
     * }
     * 
*

* Picocli provides some default handlers that allow you to accomplish some common tasks with very little code. * The following handlers are available:

*
    *
  • {@link RunLast} handler prints help if requested, and otherwise gets the last specified command or subcommand * and tries to execute it as a {@code Runnable} or {@code Callable}.
  • *
  • {@link RunFirst} handler prints help if requested, and otherwise executes the top-level command as a {@code Runnable} or {@code Callable}.
  • *
  • {@link RunAll} handler prints help if requested, and otherwise executes all recognized commands and subcommands as {@code Runnable} or {@code Callable} tasks.
  • *
  • {@link DefaultExceptionHandler} prints the error message followed by usage help
  • *
* @param handler the function that will process the result of successfully parsing the command line arguments * @param out the {@code PrintStream} to print help to if requested * @param args the command line arguments * @return a list of results, or an empty list if there are no results * @throws ExecutionException if the command line arguments were parsed successfully but a problem occurred while processing the * parse results; use {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed * @see RunLast * @see RunAll * @since 2.0 */ public List parseWithHandler(final IParseResultHandler handler, final PrintStream out, final String... args) { return parseWithHandlers(handler, out, Help.Ansi.AUTO, new DefaultExceptionHandler(), args); } /** * Tries to {@linkplain #parse(String...) parse} the specified command line arguments, and if successful, delegates * the processing of the resulting list of {@code CommandLine} objects to the specified {@linkplain IParseResultHandler handler}. * If the command line arguments were invalid, the {@code ParameterException} thrown from the {@code parse} method * is caught and passed to the specified {@link IExceptionHandler}. *

* This is a convenience method intended to offer the same ease of use as the {@link #run(Runnable, PrintStream, Help.Ansi, String...) run} * and {@link #call(Callable, PrintStream, Help.Ansi, String...) call} methods, but with more flexibility and better * support for nested subcommands. *

*

Calling this method roughly expands to:

*
     * try {
     *     List<CommandLine> parsedCommands = parse(args);
     *     return parseResultsHandler.handleParseResult(parsedCommands, out, ansi);
     * } catch (ParameterException ex) {
     *     return new exceptionHandler.handleException(ex, out, ansi, args);
     * }
     * 
*

* Picocli provides some default handlers that allow you to accomplish some common tasks with very little code. * The following handlers are available:

*
    *
  • {@link RunLast} handler prints help if requested, and otherwise gets the last specified command or subcommand * and tries to execute it as a {@code Runnable} or {@code Callable}.
  • *
  • {@link RunFirst} handler prints help if requested, and otherwise executes the top-level command as a {@code Runnable} or {@code Callable}.
  • *
  • {@link RunAll} handler prints help if requested, and otherwise executes all recognized commands and subcommands as {@code Runnable} or {@code Callable} tasks.
  • *
  • {@link DefaultExceptionHandler} prints the error message followed by usage help
  • *
* * @param handler the function that will process the result of successfully parsing the command line arguments * @param out the {@code PrintStream} to print help to if requested * @param ansi for printing help messages using ANSI styles and colors * @param exceptionHandler the function that can handle the {@code ParameterException} thrown when the command line arguments are invalid * @param args the command line arguments * @return a list of results produced by the {@code IParseResultHandler} or the {@code IExceptionHandler}, or an empty list if there are no results * @throws ExecutionException if the command line arguments were parsed successfully but a problem occurred while processing the parse * result {@code CommandLine} objects; use {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed * @see RunLast * @see RunAll * @see DefaultExceptionHandler * @since 2.0 */ public List parseWithHandlers(final IParseResultHandler handler, final PrintStream out, final Help.Ansi ansi, final IExceptionHandler exceptionHandler, final String... args) { try { final List result = parse(args); return handler.handleParseResult(result, out, ansi); } catch (final ParameterException ex) { return exceptionHandler.handleException(ex, out, ansi, 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(final Object command, final 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(final Object command, final PrintStream out, final 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(final Object command, final PrintStream out, final 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(final 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(final PrintStream out, final 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: *
     * 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);
     * 
*

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. *

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.

* @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(final PrintStream out, final Help.ColorScheme colorScheme) { final Help help = new Help(interpreter.command, colorScheme).addAllSubcommands(getSubcommands()); if (!Help.DEFAULT_SEPARATOR.equals(getSeparator())) { help.separator = getSeparator(); help.parameterLabelRenderer = help.createDefaultParamLabelRenderer(); // update for new separator } if (!Help.DEFAULT_COMMAND_NAME.equals(getCommandName())) { help.commandName = getCommandName(); } final 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) * @since 0.9.8 */ public void printVersionHelp(final 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 * markup for colors and style. * @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() * @since 0.9.8 */ public void printVersionHelp(final PrintStream out, final Help.Ansi ansi) { for (final String versionInfo : versionLines) { out.println(ansi.new Text(versionInfo)); } } /** * Prints version information from the {@link Command#version()} annotation to the specified {@code PrintStream}. * Each element of the array of version strings is {@linkplain String#format(String, Object...) formatted} with the * specified parameters, and printed on a separate line. Both version strings and parameters may contain * markup for colors and style. * @param out the printStream to print to * @param ansi whether the usage message should include ANSI escape codes or not * @param params Arguments referenced by the format specifiers in the version strings * @see Command#version() * @see Option#versionHelp() * @see #isVersionHelpRequested() * @since 1.0.0 */ public void printVersionHelp(final PrintStream out, final Help.Ansi ansi, final Object... params) { for (final String versionInfo : versionLines) { out.println(ansi.new Text(String.format(versionInfo, params))); } } /** * Delegates to {@link #call(Callable, PrintStream, Help.Ansi, String...)} with {@link Help.Ansi#AUTO}. *

* From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, Help.Ansi) requested}, * and any exceptions thrown by the {@code Callable} are caught and rethrown wrapped in an {@code ExecutionException}. *

* @param callable the command to call when {@linkplain #parse(String...) parsing} succeeds. * @param out the printStream to print to * @param args the command line arguments to parse * @param the annotated object must implement Callable * @param the return type of the most specific command (must implement {@code Callable}) * @see #call(Callable, PrintStream, Help.Ansi, String...) * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation * @throws ExecutionException if the Callable throws an exception * @return {@code null} if an error occurred while parsing the command line options, otherwise returns the result of calling the Callable * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) * @see RunFirst */ public static , T> T call(final C callable, final PrintStream out, final String... args) { return call(callable, 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 Callable}. Calling this method is equivalent to: *
     * CommandLine cmd = new CommandLine(callable);
     * List<CommandLine> parsedCommands;
     * try {
     *     parsedCommands = cmd.parse(args);
     * } catch (ParameterException ex) {
     *     out.println(ex.getMessage());
     *     cmd.usage(out, ansi);
     *     return null;
     * }
     * if (CommandLine.printHelpIfRequested(parsedCommands, out, ansi)) {
     *     return null;
     * }
     * CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
     * try {
     *     Callable<Object> subcommand = last.getCommand();
     *     return subcommand.call();
     * } catch (Exception ex) {
     *     throw new ExecutionException(last, "Error calling " + last.getCommand(), ex);
     * }
     * 
*

* If the specified Callable command has subcommands, the {@linkplain RunLast last} subcommand specified on the * command line is executed. * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler, PrintStream, String...) parseWithHandler} * method with a {@link RunAll} handler or a custom handler. *

* From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, Help.Ansi) requested}, * and any exceptions thrown by the {@code Callable} are caught and rethrown wrapped in an {@code ExecutionException}. *

* @param callable the command to call when {@linkplain #parse(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 the annotated object must implement Callable * @param the return type of the specified {@code Callable} * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation * @throws ExecutionException if the Callable throws an exception * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) * @see RunLast */ public static , T> T call(final C callable, final PrintStream out, final Help.Ansi ansi, final String... args) { final CommandLine cmd = new CommandLine(callable); // validate command outside of try-catch final List results = cmd.parseWithHandlers(new RunLast(), out, ansi, new DefaultExceptionHandler(), args); return results == null || results.isEmpty() ? null : (T) results.get(0); } /** * Delegates to {@link #run(Runnable, PrintStream, Help.Ansi, String...)} with {@link Help.Ansi#AUTO}. *

* From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, Help.Ansi) requested}, * and any exceptions thrown by the {@code Runnable} are caught and rethrown wrapped in an {@code ExecutionException}. *

* @param runnable the command to run when {@linkplain #parse(String...) parsing} succeeds. * @param out the printStream to print to * @param args the command line arguments to parse * @param the annotated object must implement Runnable * @see #run(Runnable, PrintStream, Help.Ansi, String...) * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation * @throws ExecutionException if the Runnable throws an exception * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) * @see RunFirst */ public static void run(final R runnable, final PrintStream out, final String... args) { run(runnable, 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: *
     * CommandLine cmd = new CommandLine(runnable);
     * List<CommandLine> parsedCommands;
     * try {
     *     parsedCommands = cmd.parse(args);
     * } catch (ParameterException ex) {
     *     out.println(ex.getMessage());
     *     cmd.usage(out, ansi);
     *     return null;
     * }
     * if (CommandLine.printHelpIfRequested(parsedCommands, out, ansi)) {
     *     return null;
     * }
     * CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
     * try {
     *     Runnable subcommand = last.getCommand();
     *     subcommand.run();
     * } catch (Exception ex) {
     *     throw new ExecutionException(last, "Error running " + last.getCommand(), ex);
     * }
     * 
*

* If the specified Runnable command has subcommands, the {@linkplain RunLast last} subcommand specified on the * command line is executed. * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler, PrintStream, String...) parseWithHandler} * method with a {@link RunAll} handler or a custom handler. *

* From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, Help.Ansi) requested}, * and any exceptions thrown by the {@code Runnable} are caught and rethrown wrapped in an {@code ExecutionException}. *

* @param runnable the command to run when {@linkplain #parse(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 the annotated object must implement Runnable * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation * @throws ExecutionException if the Runnable throws an exception * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) * @see RunLast */ public static void run(final R runnable, final PrintStream out, final Help.Ansi ansi, final String... args) { final CommandLine cmd = new CommandLine(runnable); // validate command outside of try-catch cmd.parseWithHandlers(new RunLast(), out, ansi, new DefaultExceptionHandler(), args); } /** * 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. *

* Java 8 lambdas make it easy to register custom type converters: *

*
     * 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));
*

* Built-in type converters are pre-registered for the following java 1.5 types: *

*
    *
  • all primitive types
  • *
  • all primitive wrapper types: Boolean, Byte, Character, Double, Float, Integer, Long, Short
  • *
  • any enum
  • *
  • java.io.File
  • *
  • java.math.BigDecimal
  • *
  • java.math.BigInteger
  • *
  • java.net.InetAddress
  • *
  • java.net.URI
  • *
  • java.net.URL
  • *
  • java.nio.charset.Charset
  • *
  • java.sql.Time
  • *
  • java.util.Date
  • *
  • java.util.UUID
  • *
  • java.util.regex.Pattern
  • *
  • StringBuilder
  • *
  • CharSequence
  • *
  • String
  • *
*

The specified converter will be registered with this {@code CommandLine} and the full hierarchy of its * subcommands and nested sub-subcommands at the moment the converter is registered. 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.

* * @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 the target type * @return this CommandLine object, to allow method chaining * @see #addSubcommand(String, Object) */ public CommandLine registerConverter(final Class cls, final ITypeConverter converter) { interpreter.converterRegistry.put(Assert.notNull(cls, "class"), Assert.notNull(converter, "converter")); for (final 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. {@value Help#DEFAULT_SEPARATOR} 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. * The separator may also be set declaratively with the {@link CommandLine.Command#separator()} annotation attribute. * @param separator the String that separates option names from option values * @return this {@code CommandLine} object, to allow method chaining */ public CommandLine setSeparator(final String separator) { interpreter.separator = Assert.notNull(separator, "separator"); return this; } /** Returns the command name (also called program name) displayed in the usage help synopsis. {@value Help#DEFAULT_COMMAND_NAME} by default. * @return the command name (also called program name) displayed in the usage */ public String getCommandName() { return commandName; } /** Sets the command name (also called program name) displayed in the usage help synopsis to the specified value. * Note that this method only modifies the usage help message, it does not impact parsing behaviour. * The command name may also be set declaratively with the {@link CommandLine.Command#name()} annotation attribute. * @param commandName command name (also called program name) displayed in the usage help synopsis * @return this {@code CommandLine} object, to allow method chaining */ public CommandLine setCommandName(final String commandName) { this.commandName = Assert.notNull(commandName, "commandName"); return this; } private static boolean empty(final String str) { return str == null || str.trim().length() == 0; } private static boolean empty(final Object[] array) { return array == null || array.length == 0; } private static boolean empty(final Text txt) { return txt == null || txt.plain.toString().trim().length() == 0; } private static String str(final String[] arr, final int i) { return (arr == null || arr.length == 0) ? "" : arr[i]; } private static boolean isBoolean(final Class type) { return type == Boolean.class || type == Boolean.TYPE; } private static CommandLine toCommandLine(final Object obj) { return obj instanceof CommandLine ? (CommandLine) obj : new CommandLine(obj);} private static boolean isMultiValue(final Field field) { return isMultiValue(field.getType()); } private static boolean isMultiValue(final Class cls) { return cls.isArray() || Collection.class.isAssignableFrom(cls) || Map.class.isAssignableFrom(cls); } private static Class[] getTypeAttribute(final Field field) { final Class[] explicit = field.isAnnotationPresent(Parameters.class) ? field.getAnnotation(Parameters.class).type() : field.getAnnotation(Option.class).type(); if (explicit.length > 0) { return explicit; } if (field.getType().isArray()) { return new Class[] { field.getType().getComponentType() }; } if (isMultiValue(field)) { final Type type = field.getGenericType(); // e.g. Map if (type instanceof ParameterizedType) { final ParameterizedType parameterizedType = (ParameterizedType) type; final Type[] paramTypes = parameterizedType.getActualTypeArguments(); // e.g. ? extends Number final Class[] result = new Class[paramTypes.length]; for (int i = 0; i < paramTypes.length; i++) { if (paramTypes[i] instanceof Class) { result[i] = (Class) paramTypes[i]; continue; } // e.g. Long if (paramTypes[i] instanceof WildcardType) { // e.g. ? extends Number final WildcardType wildcardType = (WildcardType) paramTypes[i]; final Type[] lower = wildcardType.getLowerBounds(); // e.g. [] if (lower.length > 0 && lower[0] instanceof Class) { result[i] = (Class) lower[0]; continue; } final Type[] upper = wildcardType.getUpperBounds(); // e.g. Number if (upper.length > 0 && upper[0] instanceof Class) { result[i] = (Class) upper[0]; continue; } } Arrays.fill(result, String.class); return result; // too convoluted generic type, giving up } return result; // we inferred all types from ParameterizedType } return new Class[] {String.class, String.class}; // field is multi-value but not ParameterizedType } return new Class[] {field.getType()}; // not a multi-value field } /** *

* Annotate fields in your class with {@code @Option} and picocli will initialize these fields when matching * arguments are specified on the command line. *

* For example: *

*
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"}, usageHelp = true, description = "Display this help and exit")
     *     private boolean help;
     *
     *     @Option(names = { "-V", "--version"}, versionHelp = true, description = "Display version information and exit")
     *     private boolean version;
     * }
     * 
*

* A field cannot be annotated with both {@code @Parameters} and {@code @Option} or a * {@code ParameterException} is thrown. *

*/ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Option { /** * One or more option names. At least one option name is required. *

* 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. *

* *nix *

* In Unix and Linux, options have a short (single-character) name, a long name or both. * Short options * (POSIX * style are single-character and are preceded by the {@code '-'} character, e.g., {@code `-v'}. * GNU-style long * (or mnemonic) options start with two dashes in a row, e.g., {@code `--file'}. *

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: *

         * -xvfFILE
         * -xvf FILE
         * -xvf=FILE
         * -xv --file FILE
         * -xv --file=FILE
         * -x -v --file FILE
         * -x -v --file=FILE
         * 

* DOS *

* 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: *

         * DIR /S /A:D /T:C
         * 

* PowerShell *

* 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. *

* @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. *

* 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). *

*

* 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. *

* @return whether this option disables validation of the other arguments * @deprecated Use {@link #usageHelp()} and {@link #versionHelp()} instead. See {@link #printHelpIfRequested(List, PrintStream, CommandLine.Help.Ansi)} */ 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}. *

* This attribute is useful for special options like help ({@code -h} and {@code --help} on unix, * {@code -?} and {@code -Help} on Windows). *

*

* 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. *

* @return whether this option allows the user to request usage help * @since 0.9.8 */ 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}. *

* This attribute is useful for special options like version ({@code -V} and {@code --version} on unix, * {@code -Version} on Windows). *

*

* 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. *

* @return whether this option allows the user to request version information * @since 0.9.8 */ 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. *

* 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. *

* 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. *

* For example, if an option has 2 required parameters and any number of optional parameters, * specify {@code @Option(names = "-example", arity = "2..*")}. *

* A note on boolean options *

* 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:

*
@Option(names = "-v", arity = "1") boolean verbose;
*

* Because this boolean field is defined with arity 1, the user must specify either {@code -v false} * or {@code -v true} * on the command line, or a {@link MissingParameterException} is thrown by the {@link #parse(String...)} * method. *

* To make the boolean parameter possible but optional, define the field with {@code arity = "0..1"}. * For example:

*
@Option(names="-v", arity="0..1") boolean verbose;
*

This will accept any of the below without throwing an exception:

*
         * -v
         * -v true
         * -v false
         * 
* @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: *
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;
         * }
*

By default, the above gives a usage help message like the following:

         * 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.
         * 
* @return name of the option parameter used in the usage help message */ String paramLabel() default ""; /**

* Optionally specify a {@code type} to control exactly what Class the option parameter should be converted * to. This may be useful when the field type is an interface or an abstract class. For example, a field can * be declared to have type {@code java.lang.Number}, and annotating {@code @Option(type=Short.class)} * ensures that the option parameter value is converted to a {@code Short} before setting the field value. *

* For array fields whose component type is an interface or abstract class, specify the concrete component type. * For example, a field with type {@code Number[]} may be annotated with {@code @Option(type=Short.class)} * to ensure that option parameter values are converted to {@code Short} before adding an element to the array. *

* Picocli will use the {@link ITypeConverter} that is * {@linkplain #registerConverter(Class, ITypeConverter) registered} for the specified type to convert * the raw String values before modifying the field value. *

* Prior to 2.0, the {@code type} attribute was necessary for {@code Collection} and {@code Map} fields, * but starting from 2.0 picocli will infer the component type from the generic type's type arguments. * For example, for a field of type {@code Map} picocli will know the option parameter * should be split up in key=value pairs, where the key should be converted to a {@code java.util.concurrent.TimeUnit} * enum value, and the value should be converted to a {@code Long}. No {@code @Option(type=...)} type attribute * is required for this. For generic types with wildcards, picocli will take the specified upper or lower bound * as the Class to convert to, unless the {@code @Option} annotation specifies an explicit {@code type} attribute. *

* If the field type is a raw collection or a raw map, and you want it to contain other values than Strings, * or if the generic type's type arguments are interfaces or abstract classes, you may * specify a {@code type} attribute to control the Class that the option parameter should be converted to. * @return the type(s) to convert the raw String values */ Class[] type() default {}; /** * 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; } /** *

* 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). *

* 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. *

* For example: *

*
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;
     * }
     * 

* A field cannot be annotated with both {@code @Parameters} and {@code @Option} or a {@code ParameterException} * is thrown.

*/ @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. *

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.

* @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: *
class Example {
         *     @Parameters(paramLabel="FILE", description="path of the input FILE(s)")
         *     private File[] inputFiles;
         * }
*

By default, the above gives a usage help message like the following:

         * Usage: <main class> [FILE...]
         * [FILE...]       path of the input FILE(s)
         * 
* @return name of the positional parameter used in the usage help message */ String paramLabel() default ""; /** *

* Optionally specify a {@code type} to control exactly what Class the positional parameter should be converted * to. This may be useful when the field type is an interface or an abstract class. For example, a field can * be declared to have type {@code java.lang.Number}, and annotating {@code @Parameters(type=Short.class)} * ensures that the positional parameter value is converted to a {@code Short} before setting the field value. *

* For array fields whose component type is an interface or abstract class, specify the concrete component type. * For example, a field with type {@code Number[]} may be annotated with {@code @Parameters(type=Short.class)} * to ensure that positional parameter values are converted to {@code Short} before adding an element to the array. *

* Picocli will use the {@link ITypeConverter} that is * {@linkplain #registerConverter(Class, ITypeConverter) registered} for the specified type to convert * the raw String values before modifying the field value. *

* Prior to 2.0, the {@code type} attribute was necessary for {@code Collection} and {@code Map} fields, * but starting from 2.0 picocli will infer the component type from the generic type's type arguments. * For example, for a field of type {@code Map} picocli will know the positional parameter * should be split up in key=value pairs, where the key should be converted to a {@code java.util.concurrent.TimeUnit} * enum value, and the value should be converted to a {@code Long}. No {@code @Parameters(type=...)} type attribute * is required for this. For generic types with wildcards, picocli will take the specified upper or lower bound * as the Class to convert to, unless the {@code @Parameters} annotation specifies an explicit {@code type} attribute. *

* If the field type is a raw collection or a raw map, and you want it to contain other values than Strings, * or if the generic type's type arguments are interfaces or abstract classes, you may * specify a {@code type} attribute to control the Class that the positional parameter should be converted to. * @return the type(s) to convert the raw String values */ Class[] type() default {}; /** * 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; } /** *

Annotate your class with {@code @Command} when you want more control over the format of the generated help * message. *

     * @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;
     * }
*

* The structure of a help message looks like this: *

    *
  • [header]
  • *
  • [synopsis]: {@code Usage: [OPTIONS] [FILE...]}
  • *
  • [description]
  • *
  • [parameter list]: {@code [FILE...] Any number of input files}
  • *
  • [option list]: {@code -h, --help prints this help message and exits}
  • *
  • [footer]
  • *
*/ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.LOCAL_VARIABLE, ElementType.PACKAGE}) public @interface Command { /** Program name to show in the synopsis. If omitted, {@code "
"} 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 "
"; /** 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: *
         * @Command(subcommands = {
         *         GitStatus.class,
         *         GitCommit.class,
         *         GitBranch.class })
         * public class Git { ... }
         *
         * CommandLine commandLine = new CommandLine(new Git());
         * 
is equivalent to this: *
         * // 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());
         * 
* @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 "
[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 {}; } /** *

* 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. *

* 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. *

* Java 8 lambdas make it easy to register custom type converters: *

*
     * 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));
*

* Built-in type converters are pre-registered for the following java 1.5 types: *

*
    *
  • all primitive types
  • *
  • all primitive wrapper types: Boolean, Byte, Character, Double, Float, Integer, Long, Short
  • *
  • any enum
  • *
  • java.io.File
  • *
  • java.math.BigDecimal
  • *
  • java.math.BigInteger
  • *
  • java.net.InetAddress
  • *
  • java.net.URI
  • *
  • java.net.URL
  • *
  • java.nio.charset.Charset
  • *
  • java.sql.Time
  • *
  • java.util.Date
  • *
  • java.util.UUID
  • *
  • java.util.regex.Pattern
  • *
  • StringBuilder
  • *
  • CharSequence
  • *
  • String
  • *
* @param the type of the object that is the result of the conversion */ public interface ITypeConverter { /** * 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 { /** 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(final int min, final int max, final boolean variable, final boolean unspecified, final 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(final 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(final Field field) { return field.isAnnotationPresent(Parameters.class) ? adjustForType(Range.valueOf(field.getAnnotation(Parameters.class).arity()), field) : new Range(0, 0, false, true, "0"); } /** Returns a new {@code Range} based on the {@link Parameters#index()} annotation on the specified field. * @param field the field whose Parameters annotation to inspect * @return a new {@code Range} based on the Parameters index annotation on the specified field */ public static Range parameterIndex(final Field field) { return field.isAnnotationPresent(Parameters.class) ? Range.valueOf(field.getAnnotation(Parameters.class).index()) : new Range(0, 0, false, true, "0"); } static Range adjustForType(final Range result, final Field field) { return result.isUnspecified ? defaultArity(field) : result; } /** Returns the default arity {@code Range}: for {@link Option options} this is 0 for booleans and 1 for * other types, for {@link Parameters parameters} booleans have arity 0, arrays or Collections have * arity "0..*", and other types have arity 1. * @param field the field whose default arity to return * @return a new {@code Range} indicating the default arity of the specified field * @since 2.0 */ public static Range defaultArity(final Field field) { final Class type = field.getType(); if (field.isAnnotationPresent(Option.class)) { return defaultArity(type); } if (isMultiValue(type)) { return Range.valueOf("0..1"); } return Range.valueOf("1");// for single-valued fields (incl. boolean positional parameters) } /** Returns the default arity {@code Range} for {@link Option options}: booleans have arity 0, 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(final Class type) { return isBoolean(type) ? Range.valueOf("0") : Range.valueOf("1"); } private int size() { return 1 + max - min; } static Range parameterCapacity(final Field field) { final Range arity = parameterArity(field); if (!isMultiValue(field)) { return arity; } final Range index = parameterIndex(field); if (arity.max == 0) { return arity; } if (index.size() == 1) { return arity; } if (index.isVariable) { return Range.valueOf(arity.min + "..*"); } if (arity.size() == 1) { return Range.valueOf(arity.min * index.size() + ""); } if (arity.isVariable) { return Range.valueOf(arity.min * index.size() + "..*"); } return Range.valueOf(arity.min * index.size() + ".." + arity.max * index.size()); } /** 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(); final 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; } final Range result = new Range(min, max, variable, unspecified, range); return result; } private static int parseInt(final String str, final int defaultValue) { try { return Integer.parseInt(str); } catch (final 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(final 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(final int newMax) { return new Range(Math.min(min, newMax), newMax, isVariable, isUnspecified, originalValue); } /** * Returns {@code true} if this Range includes the specified value, {@code false} otherwise. * @param value the value to check * @return {@code true} if the specified value is not less than the minimum and not greater than the maximum of this Range */ public boolean contains(final int value) { return min <= value && max >= value; } @Override public boolean equals(final Object object) { if (!(object instanceof Range)) { return false; } final Range other = (Range) object; return other.max == this.max && other.min == this.min && other.isVariable == this.isVariable; } @Override public int hashCode() { return ((17 * 37 + max) * 37 + min) * 37 + (isVariable ? 1 : 0); } @Override public String toString() { return min == max ? String.valueOf(min) : min + ".." + (isVariable ? "*" : max); } @Override public int compareTo(final Range other) { final int result = min - other.min; return (result == 0) ? max - other.max : result; } } static void init(final Class cls, final List requiredFields, final Map optionName2Field, final Map singleCharOption2Field, final List positionalParametersFields) { final Field[] declaredFields = cls.getDeclaredFields(); for (final Field field : declaredFields) { field.setAccessible(true); if (field.isAnnotationPresent(Option.class)) { final Option option = field.getAnnotation(Option.class); if (option.required()) { requiredFields.add(field); } for (final String name : option.names()) { // cannot be null or empty final Field existing = optionName2Field.put(name, field); if (existing != null && existing != field) { throw DuplicateOptionAnnotationsException.create(name, field, existing); } if (name.length() == 2 && name.startsWith("-")) { final char flag = name.charAt(1); final Field existing2 = singleCharOption2Field.put(flag, field); if (existing2 != null && existing2 != field) { throw DuplicateOptionAnnotationsException.create(name, field, existing2); } } } } if (field.isAnnotationPresent(Parameters.class)) { if (field.isAnnotationPresent(Option.class)) { throw new DuplicateOptionAnnotationsException("A field can be either @Option or @Parameters, but '" + field.getName() + "' is both."); } positionalParametersFields.add(field); final Range arity = Range.parameterArity(field); if (arity.min > 0) { requiredFields.add(field); } } } } static void validatePositionalParameters(final List positionalParametersFields) { int min = 0; for (final Field field : positionalParametersFields) { final 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 Stack reverse(final Stack stack) { Collections.reverse(stack); return stack; } /** * Helper class responsible for processing command line arguments. */ private class Interpreter { private final Map commands = new LinkedHashMap<>(); private final Map, ITypeConverter> converterRegistry = new HashMap<>(); private final Map optionName2Field = new HashMap<>(); private final Map singleCharOption2Field = new HashMap<>(); private final List requiredFields = new ArrayList<>(); private final List positionalParametersFields = new ArrayList<>(); private final Object command; private boolean isHelpRequested; private String separator = Help.DEFAULT_SEPARATOR; private int position; Interpreter(final Object command) { converterRegistry.put(Path.class, new BuiltIn.PathConverter()); converterRegistry.put(Object.class, new BuiltIn.StringConverter()); 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 declaredName = null; String declaredSeparator = null; boolean hasCommandAnnotation = false; while (cls != null) { init(cls, requiredFields, optionName2Field, singleCharOption2Field, positionalParametersFields); if (cls.isAnnotationPresent(Command.class)) { hasCommandAnnotation = true; final Command cmd = cls.getAnnotation(Command.class); declaredSeparator = (declaredSeparator == null) ? cmd.separator() : declaredSeparator; declaredName = (declaredName == null) ? cmd.name() : declaredName; CommandLine.this.versionLines.addAll(Arrays.asList(cmd.version())); for (final Class sub : cmd.subcommands()) { final Command subCommand = sub.getAnnotation(Command.class); if (subCommand == null || Help.DEFAULT_COMMAND_NAME.equals(subCommand.name())) { throw new InitializationException("Subcommand " + sub.getName() + " is missing the mandatory @Command annotation with a 'name' attribute"); } try { final Constructor constructor = sub.getDeclaredConstructor(); constructor.setAccessible(true); final CommandLine commandLine = toCommandLine(constructor.newInstance()); commandLine.parent = CommandLine.this; commands.put(subCommand.name(), commandLine); } catch (final InitializationException ex) { throw ex; } catch (final NoSuchMethodException ex) { throw new InitializationException("Cannot instantiate subcommand " + sub.getName() + ": the class has no constructor", ex); } catch (final Exception ex) { throw new InitializationException("Could not instantiate and add subcommand " + sub.getName() + ": " + ex, ex); } } } cls = cls.getSuperclass(); } separator = declaredSeparator != null ? declaredSeparator : separator; CommandLine.this.commandName = declaredName != null ? declaredName : CommandLine.this.commandName; Collections.sort(positionalParametersFields, new PositionalParametersSorter()); validatePositionalParameters(positionalParametersFields); if (positionalParametersFields.isEmpty() && optionName2Field.isEmpty() && !hasCommandAnnotation) { throw new InitializationException(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 parse(final String... args) { Assert.notNull(args, "argument array"); if (tracer.isInfo()) {tracer.info("Parsing %d command line args %s%n", args.length, Arrays.toString(args));} final Stack arguments = new Stack<>(); for (int i = args.length - 1; i >= 0; i--) { arguments.push(args[i]); } final List result = new ArrayList<>(); parse(result, arguments, args); return result; } private void parse(final List parsedCommands, final Stack argumentStack, final String[] originalArgs) { // first reset any state in case this CommandLine instance is being reused isHelpRequested = false; CommandLine.this.versionHelpRequested = false; CommandLine.this.usageHelpRequested = false; final Class cmdClass = this.command.getClass(); if (tracer.isDebug()) {tracer.debug("Initializing %s: %d options, %d positional parameters, %d required, %d subcommands.%n", cmdClass.getName(), new HashSet<>(optionName2Field.values()).size(), positionalParametersFields.size(), requiredFields.size(), commands.size());} parsedCommands.add(CommandLine.this); final List required = new ArrayList<>(requiredFields); final Set initialized = new HashSet<>(); Collections.sort(required, new PositionalParametersSorter()); try { processArguments(parsedCommands, argumentStack, required, initialized, originalArgs); } catch (final ParameterException ex) { throw ex; } catch (final Exception ex) { final int offendingArgIndex = originalArgs.length - argumentStack.size() - 1; final String arg = offendingArgIndex >= 0 && offendingArgIndex < originalArgs.length ? originalArgs[offendingArgIndex] : "?"; throw ParameterException.create(CommandLine.this, ex, arg, offendingArgIndex, originalArgs); } if (!isAnyHelpRequested() && !required.isEmpty()) { for (final Field missing : required) { if (missing.isAnnotationPresent(Option.class)) { throw MissingParameterException.create(CommandLine.this, required, separator); } else { assertNoMissingParameters(missing, Range.parameterArity(missing).min, argumentStack); } } } if (!unmatchedArguments.isEmpty()) { if (!isUnmatchedArgumentsAllowed()) { throw new UnmatchedArgumentException(CommandLine.this, unmatchedArguments); } if (tracer.isWarn()) { tracer.warn("Unmatched arguments: %s%n", unmatchedArguments); } } } private void processArguments(final List parsedCommands, final Stack args, final Collection required, final Set initialized, final 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(); if (tracer.isDebug()) {tracer.debug("Processing argument '%s'. Remainder=%s%n", arg, reverse((Stack) args.clone()));} // Double-dash separates options from positional arguments. // If found, then interpret the remaining args as positional parameters. if ("--".equals(arg)) { tracer.info("Found end-of-options delimiter '--'. Treating remainder as positional parameters.%n"); processRemainderAsPositionalParameters(required, initialized, 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(CommandLine.this, required, separator); } if (tracer.isDebug()) {tracer.debug("Found subcommand '%s' (%s)%n", arg, commands.get(arg).interpreter.command.getClass().getName());} 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; final int separatorIndex = arg.indexOf(separator); if (separatorIndex > 0) { final 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; final String optionParam = arg.substring(separatorIndex + separator.length()); args.push(optionParam); arg = key; if (tracer.isDebug()) {tracer.debug("Separated '%s' option from '%s' option parameter%n", key, optionParam);} } else if (tracer.isDebug()) {tracer.debug("'%s' contains separator '%s' but '%s' is not a known option%n", arg, separator, key);} } else if (tracer.isDebug()) {tracer.debug("'%s' cannot be separated into