1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache license, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the license for the specific language governing permissions and
15 * limitations under the license.
16 */
17 package org.apache.logging.log4j.core.tools.picocli;
18
19 import java.io.File;
20 import java.io.PrintStream;
21 import java.lang.annotation.ElementType;
22 import java.lang.annotation.Retention;
23 import java.lang.annotation.RetentionPolicy;
24 import java.lang.annotation.Target;
25 import java.lang.reflect.Array;
26 import java.lang.reflect.Constructor;
27 import java.lang.reflect.Field;
28 import java.lang.reflect.ParameterizedType;
29 import java.lang.reflect.Type;
30 import java.lang.reflect.WildcardType;
31 import java.math.BigDecimal;
32 import java.math.BigInteger;
33 import java.net.InetAddress;
34 import java.net.MalformedURLException;
35 import java.net.URI;
36 import java.net.URISyntaxException;
37 import java.net.URL;
38 import java.nio.charset.Charset;
39 import java.nio.file.Path;
40 import java.nio.file.Paths;
41 import java.sql.Time;
42 import java.text.BreakIterator;
43 import java.text.ParseException;
44 import java.text.SimpleDateFormat;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.Collection;
48 import java.util.Collections;
49 import java.util.Comparator;
50 import java.util.Date;
51 import java.util.HashMap;
52 import java.util.HashSet;
53 import java.util.LinkedHashMap;
54 import java.util.LinkedHashSet;
55 import java.util.LinkedList;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.Queue;
59 import java.util.Set;
60 import java.util.SortedSet;
61 import java.util.Stack;
62 import java.util.TreeSet;
63 import java.util.UUID;
64 import java.util.concurrent.Callable;
65 import java.util.regex.Pattern;
66
67 import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.IStyle;
68 import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.Style;
69 import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.Text;
70
71 import static java.util.Locale.ENGLISH;
72 import static org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Column.Overflow.SPAN;
73 import static org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Column.Overflow.TRUNCATE;
74 import static org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Column.Overflow.WRAP;
75
76 /**
77 * <p>
78 * CommandLine interpreter that uses reflection to initialize an annotated domain object with values obtained from the
79 * command line arguments.
80 * </p><h2>Example</h2>
81 * <pre>import static picocli.CommandLine.*;
82 *
83 * @Command(header = "Encrypt FILE(s), or standard input, to standard output or to the output file.",
84 * version = "v1.2.3")
85 * public class Encrypt {
86 *
87 * @Parameters(type = File.class, description = "Any number of input files")
88 * private List<File> files = new ArrayList<File>();
89 *
90 * @Option(names = { "-o", "--out" }, description = "Output file (default: print to console)")
91 * private File outputFile;
92 *
93 * @Option(names = { "-v", "--verbose"}, description = "Verbosely list files processed")
94 * private boolean verbose;
95 *
96 * @Option(names = { "-h", "--help", "-?", "-help"}, usageHelp = true, description = "Display this help and exit")
97 * private boolean help;
98 *
99 * @Option(names = { "-V", "--version"}, versionHelp = true, description = "Display version info and exit")
100 * private boolean versionHelp;
101 * }
102 * </pre>
103 * <p>
104 * Use {@code CommandLine} to initialize a domain object as follows:
105 * </p><pre>
106 * public static void main(String... args) {
107 * Encrypt encrypt = new Encrypt();
108 * try {
109 * List<CommandLine> parsedCommands = new CommandLine(encrypt).parse(args);
110 * if (!CommandLine.printHelpIfRequested(parsedCommands, System.err, Help.Ansi.AUTO)) {
111 * runProgram(encrypt);
112 * }
113 * } catch (ParameterException ex) { // command line arguments could not be parsed
114 * System.err.println(ex.getMessage());
115 * ex.getCommandLine().usage(System.err);
116 * }
117 * }
118 * </pre><p>
119 * Invoke the above program with some command line arguments. The below are all equivalent:
120 * </p>
121 * <pre>
122 * --verbose --out=outfile in1 in2
123 * --verbose --out outfile in1 in2
124 * -v --out=outfile in1 in2
125 * -v -o outfile in1 in2
126 * -v -o=outfile in1 in2
127 * -vo outfile in1 in2
128 * -vo=outfile in1 in2
129 * -v -ooutfile in1 in2
130 * -vooutfile in1 in2
131 * </pre>
132 */
133 public class CommandLine {
134 /** This is picocli version {@value}. */
135 public static final String VERSION = "2.0.3";
136
137 private final Tracer tracer = new Tracer();
138 private final Interpreter interpreter;
139 private String commandName = Help.DEFAULT_COMMAND_NAME;
140 private boolean overwrittenOptionsAllowed = false;
141 private boolean unmatchedArgumentsAllowed = false;
142 private List<String> unmatchedArguments = new ArrayList<String>();
143 private CommandLine parent;
144 private boolean usageHelpRequested;
145 private boolean versionHelpRequested;
146 private List<String> versionLines = new ArrayList<String>();
147
148 /**
149 * Constructs a new {@code CommandLine} interpreter with the specified annotated object.
150 * When the {@link #parse(String...)} method is called, fields of the specified object that are annotated
151 * with {@code @Option} or {@code @Parameters} will be initialized based on command line arguments.
152 * @param command the object to initialize from the command line arguments
153 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
154 */
155 public CommandLine(Object command) {
156 interpreter = new Interpreter(command);
157 }
158
159 /** Registers a subcommand with the specified name. For example:
160 * <pre>
161 * CommandLine commandLine = new CommandLine(new Git())
162 * .addSubcommand("status", new GitStatus())
163 * .addSubcommand("commit", new GitCommit();
164 * .addSubcommand("add", new GitAdd())
165 * .addSubcommand("branch", new GitBranch())
166 * .addSubcommand("checkout", new GitCheckout())
167 * //...
168 * ;
169 * </pre>
170 *
171 * <p>The specified object can be an annotated object or a
172 * {@code CommandLine} instance with its own nested subcommands. For example:</p>
173 * <pre>
174 * CommandLine commandLine = new CommandLine(new MainCommand())
175 * .addSubcommand("cmd1", new ChildCommand1()) // subcommand
176 * .addSubcommand("cmd2", new ChildCommand2())
177 * .addSubcommand("cmd3", new CommandLine(new ChildCommand3()) // subcommand with nested sub-subcommands
178 * .addSubcommand("cmd3sub1", new GrandChild3Command1())
179 * .addSubcommand("cmd3sub2", new GrandChild3Command2())
180 * .addSubcommand("cmd3sub3", new CommandLine(new GrandChild3Command3()) // deeper nesting
181 * .addSubcommand("cmd3sub3sub1", new GreatGrandChild3Command3_1())
182 * .addSubcommand("cmd3sub3sub2", new GreatGrandChild3Command3_2())
183 * )
184 * );
185 * </pre>
186 * <p>The default type converters are available on all subcommands and nested sub-subcommands, but custom type
187 * converters are registered only with the subcommand hierarchy as it existed when the custom type was registered.
188 * To ensure a custom type converter is available to all subcommands, register the type converter last, after
189 * adding subcommands.</p>
190 * <p>See also the {@link Command#subcommands()} annotation to register subcommands declaratively.</p>
191 *
192 * @param name the string to recognize on the command line as a subcommand
193 * @param command the object to initialize with command line arguments following the subcommand name.
194 * This may be a {@code CommandLine} instance with its own (nested) subcommands
195 * @return this CommandLine object, to allow method chaining
196 * @see #registerConverter(Class, ITypeConverter)
197 * @since 0.9.7
198 * @see Command#subcommands()
199 */
200 public CommandLine addSubcommand(String name, Object command) {
201 CommandLine commandLine = toCommandLine(command);
202 commandLine.parent = this;
203 interpreter.commands.put(name, commandLine);
204 return this;
205 }
206 /** Returns a map with the subcommands {@linkplain #addSubcommand(String, Object) registered} on this instance.
207 * @return a map with the registered subcommands
208 * @since 0.9.7
209 */
210 public Map<String, CommandLine> getSubcommands() {
211 return new LinkedHashMap<String, CommandLine>(interpreter.commands);
212 }
213 /**
214 * Returns the command that this is a subcommand of, or {@code null} if this is a top-level command.
215 * @return the command that this is a subcommand of, or {@code null} if this is a top-level command
216 * @see #addSubcommand(String, Object)
217 * @see Command#subcommands()
218 * @since 0.9.8
219 */
220 public CommandLine getParent() {
221 return parent;
222 }
223
224 /** Returns the annotated object that this {@code CommandLine} instance was constructed with.
225 * @param <T> the type of the variable that the return value is being assigned to
226 * @return the annotated object that this {@code CommandLine} instance was constructed with
227 * @since 0.9.7
228 */
229 public <T> T getCommand() {
230 return (T) interpreter.command;
231 }
232
233 /** Returns {@code true} if an option annotated with {@link Option#usageHelp()} was specified on the command line.
234 * @return whether the parser encountered an option annotated with {@link Option#usageHelp()}.
235 * @since 0.9.8 */
236 public boolean isUsageHelpRequested() { return usageHelpRequested; }
237
238 /** Returns {@code true} if an option annotated with {@link Option#versionHelp()} was specified on the command line.
239 * @return whether the parser encountered an option annotated with {@link Option#versionHelp()}.
240 * @since 0.9.8 */
241 public boolean isVersionHelpRequested() { return versionHelpRequested; }
242
243 /** Returns whether options for single-value fields can be specified multiple times on the command line.
244 * The default is {@code false} and a {@link OverwrittenOptionException} is thrown if this happens.
245 * When {@code true}, the last specified value is retained.
246 * @return {@code true} if options for single-value fields can be specified multiple times on the command line, {@code false} otherwise
247 * @since 0.9.7
248 */
249 public boolean isOverwrittenOptionsAllowed() {
250 return overwrittenOptionsAllowed;
251 }
252
253 /** Sets whether options for single-value fields can be specified multiple times on the command line without a {@link OverwrittenOptionException} being thrown.
254 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
255 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
256 * later will have the default setting. To ensure a setting is applied to all
257 * subcommands, call the setter last, after adding subcommands.</p>
258 * @param newValue the new setting
259 * @return this {@code CommandLine} object, to allow method chaining
260 * @since 0.9.7
261 */
262 public CommandLine setOverwrittenOptionsAllowed(boolean newValue) {
263 this.overwrittenOptionsAllowed = newValue;
264 for (CommandLine command : interpreter.commands.values()) {
265 command.setOverwrittenOptionsAllowed(newValue);
266 }
267 return this;
268 }
269
270 /** Returns whether the end user may specify arguments on the command line that are not matched to any option or parameter fields.
271 * The default is {@code false} and a {@link UnmatchedArgumentException} is thrown if this happens.
272 * When {@code true}, the last unmatched arguments are available via the {@link #getUnmatchedArguments()} method.
273 * @return {@code true} if the end use may specify unmatched arguments on the command line, {@code false} otherwise
274 * @see #getUnmatchedArguments()
275 * @since 0.9.7
276 */
277 public boolean isUnmatchedArgumentsAllowed() {
278 return unmatchedArgumentsAllowed;
279 }
280
281 /** Sets whether the end user may specify unmatched arguments on the command line without a {@link UnmatchedArgumentException} being thrown.
282 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
283 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
284 * later will have the default setting. To ensure a setting is applied to all
285 * subcommands, call the setter last, after adding subcommands.</p>
286 * @param newValue the new setting. When {@code true}, the last unmatched arguments are available via the {@link #getUnmatchedArguments()} method.
287 * @return this {@code CommandLine} object, to allow method chaining
288 * @since 0.9.7
289 * @see #getUnmatchedArguments()
290 */
291 public CommandLine setUnmatchedArgumentsAllowed(boolean newValue) {
292 this.unmatchedArgumentsAllowed = newValue;
293 for (CommandLine command : interpreter.commands.values()) {
294 command.setUnmatchedArgumentsAllowed(newValue);
295 }
296 return this;
297 }
298
299 /** Returns the list of unmatched command line arguments, if any.
300 * @return the list of unmatched command line arguments or an empty list
301 * @see #isUnmatchedArgumentsAllowed()
302 * @since 0.9.7
303 */
304 public List<String> getUnmatchedArguments() {
305 return unmatchedArguments;
306 }
307
308 /**
309 * <p>
310 * Convenience method that initializes the specified annotated object from the specified command line arguments.
311 * </p><p>
312 * This is equivalent to
313 * </p><pre>
314 * CommandLine cli = new CommandLine(command);
315 * cli.parse(args);
316 * return command;
317 * </pre>
318 *
319 * @param command the object to initialize. This object contains fields annotated with
320 * {@code @Option} or {@code @Parameters}.
321 * @param args the command line arguments to parse
322 * @param <T> the type of the annotated object
323 * @return the specified annotated object
324 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
325 * @throws ParameterException if the specified command line arguments are invalid
326 * @since 0.9.7
327 */
328 public static <T> T populateCommand(T command, String... args) {
329 CommandLine cli = toCommandLine(command);
330 cli.parse(args);
331 return command;
332 }
333
334 /** Parses the specified command line arguments and returns a list of {@code CommandLine} objects representing the
335 * top-level command and any subcommands (if any) that were recognized and initialized during the parsing process.
336 * <p>
337 * If parsing succeeds, the first element in the returned list is always {@code this CommandLine} object. The
338 * returned list may contain more elements if subcommands were {@linkplain #addSubcommand(String, Object) registered}
339 * and these subcommands were initialized by matching command line arguments. If parsing fails, a
340 * {@link ParameterException} is thrown.
341 * </p>
342 *
343 * @param args the command line arguments to parse
344 * @return a list with the top-level command and any subcommands initialized by this method
345 * @throws ParameterException if the specified command line arguments are invalid; use
346 * {@link ParameterException#getCommandLine()} to get the command or subcommand whose user input was invalid
347 */
348 public List<CommandLine> parse(String... args) {
349 return interpreter.parse(args);
350 }
351 /**
352 * Represents a function that can process a List of {@code CommandLine} objects resulting from successfully
353 * {@linkplain #parse(String...) parsing} the command line arguments. This is a
354 * <a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">functional interface</a>
355 * whose functional method is {@link #handleParseResult(List, PrintStream, CommandLine.Help.Ansi)}.
356 * <p>
357 * Implementations of this functions can be passed to the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) CommandLine::parseWithHandler}
358 * methods to take some next step after the command line was successfully parsed.
359 * </p>
360 * @see RunFirst
361 * @see RunLast
362 * @see RunAll
363 * @since 2.0 */
364 public static interface IParseResultHandler {
365 /** Processes a List of {@code CommandLine} objects resulting from successfully
366 * {@linkplain #parse(String...) parsing} the command line arguments and optionally returns a list of results.
367 * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
368 * @param out the {@code PrintStream} to print help to if requested
369 * @param ansi for printing help messages using ANSI styles and colors
370 * @return a list of results, or an empty list if there are no results
371 * @throws ExecutionException if a problem occurred while processing the parse results; use
372 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
373 */
374 List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) throws ExecutionException;
375 }
376 /**
377 * Represents a function that can handle a {@code ParameterException} that occurred while
378 * {@linkplain #parse(String...) parsing} the command line arguments. This is a
379 * <a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">functional interface</a>
380 * whose functional method is {@link #handleException(CommandLine.ParameterException, PrintStream, CommandLine.Help.Ansi, String...)}.
381 * <p>
382 * Implementations of this functions can be passed to the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) CommandLine::parseWithHandler}
383 * methods to handle situations when the command line could not be parsed.
384 * </p>
385 * @see DefaultExceptionHandler
386 * @since 2.0 */
387 public static interface IExceptionHandler {
388 /** Handles a {@code ParameterException} that occurred while {@linkplain #parse(String...) parsing} the command
389 * line arguments and optionally returns a list of results.
390 * @param ex the ParameterException describing the problem that occurred while parsing the command line arguments,
391 * and the CommandLine representing the command or subcommand whose input was invalid
392 * @param out the {@code PrintStream} to print help to if requested
393 * @param ansi for printing help messages using ANSI styles and colors
394 * @param args the command line arguments that could not be parsed
395 * @return a list of results, or an empty list if there are no results
396 */
397 List<Object> handleException(ParameterException ex, PrintStream out, Help.Ansi ansi, String... args);
398 }
399 /**
400 * Default exception handler that prints the exception message to the specified {@code PrintStream}, followed by the
401 * usage message for the command or subcommand whose input was invalid.
402 * <p>Implementation roughly looks like this:</p>
403 * <pre>
404 * System.err.println(paramException.getMessage());
405 * paramException.getCommandLine().usage(System.err);
406 * </pre>
407 * @since 2.0 */
408 public static class DefaultExceptionHandler implements IExceptionHandler {
409 @Override
410 public List<Object> handleException(ParameterException ex, PrintStream out, Help.Ansi ansi, String... args) {
411 out.println(ex.getMessage());
412 ex.getCommandLine().usage(out, ansi);
413 return Collections.emptyList();
414 }
415 }
416 /**
417 * Helper method that may be useful when processing the list of {@code CommandLine} objects that result from successfully
418 * {@linkplain #parse(String...) parsing} command line arguments. This method prints out
419 * {@linkplain #usage(PrintStream, Help.Ansi) usage help} if {@linkplain #isUsageHelpRequested() requested}
420 * or {@linkplain #printVersionHelp(PrintStream, Help.Ansi) version help} if {@linkplain #isVersionHelpRequested() requested}
421 * and returns {@code true}. Otherwise, if none of the specified {@code CommandLine} objects have help requested,
422 * this method returns {@code false}.
423 * <p>
424 * Note that this method <em>only</em> looks at the {@link Option#usageHelp() usageHelp} and
425 * {@link Option#versionHelp() versionHelp} attributes. The {@link Option#help() help} attribute is ignored.
426 * </p>
427 * @param parsedCommands the list of {@code CommandLine} objects to check if help was requested
428 * @param out the {@code PrintStream} to print help to if requested
429 * @param ansi for printing help messages using ANSI styles and colors
430 * @return {@code true} if help was printed, {@code false} otherwise
431 * @since 2.0 */
432 public static boolean printHelpIfRequested(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
433 for (CommandLine parsed : parsedCommands) {
434 if (parsed.isUsageHelpRequested()) {
435 parsed.usage(out, ansi);
436 return true;
437 } else if (parsed.isVersionHelpRequested()) {
438 parsed.printVersionHelp(out, ansi);
439 return true;
440 }
441 }
442 return false;
443 }
444 private static Object execute(CommandLine parsed) {
445 Object command = parsed.getCommand();
446 if (command instanceof Runnable) {
447 try {
448 ((Runnable) command).run();
449 return null;
450 } catch (Exception ex) {
451 throw new ExecutionException(parsed, "Error while running command (" + command + ")", ex);
452 }
453 } else if (command instanceof Callable) {
454 try {
455 return ((Callable<Object>) command).call();
456 } catch (Exception ex) {
457 throw new ExecutionException(parsed, "Error while calling command (" + command + ")", ex);
458 }
459 }
460 throw new ExecutionException(parsed, "Parsed command (" + command + ") is not Runnable or Callable");
461 }
462 /**
463 * Command line parse result handler that prints help if requested, and otherwise executes the top-level
464 * {@code Runnable} or {@code Callable} command.
465 * For use in the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) parseWithHandler} methods.
466 * <p>
467 * From picocli v2.0, {@code RunFirst} is used to implement the {@link #run(Runnable, PrintStream, Help.Ansi, String...) run}
468 * and {@link #call(Callable, PrintStream, Help.Ansi, String...) call} convenience methods.
469 * </p>
470 * @since 2.0 */
471 public static class RunFirst implements IParseResultHandler {
472 /** Prints help if requested, and otherwise executes the top-level {@code Runnable} or {@code Callable} command.
473 * If the top-level command does not implement either {@code Runnable} or {@code Callable}, a {@code ExecutionException}
474 * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
475 *
476 * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
477 * @param out the {@code PrintStream} to print help to if requested
478 * @param ansi for printing help messages using ANSI styles and colors
479 * @return an empty list if help was requested, or a list containing a single element: the result of calling the
480 * {@code Callable}, or a {@code null} element if the top-level command was a {@code Runnable}
481 * @throws ExecutionException if a problem occurred while processing the parse results; use
482 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
483 */
484 @Override
485 public List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
486 if (printHelpIfRequested(parsedCommands, out, ansi)) { return Collections.emptyList(); }
487 return Arrays.asList(execute(parsedCommands.get(0)));
488 }
489 }
490 /**
491 * Command line parse result handler that prints help if requested, and otherwise executes the most specific
492 * {@code Runnable} or {@code Callable} subcommand.
493 * For use in the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) parseWithHandler} methods.
494 * <p>
495 * Something like this:</p>
496 * <pre>
497 * // RunLast implementation: print help if requested, otherwise execute the most specific subcommand
498 * if (CommandLine.printHelpIfRequested(parsedCommands, System.err, Help.Ansi.AUTO)) {
499 * return emptyList();
500 * }
501 * CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
502 * Object command = last.getCommand();
503 * if (command instanceof Runnable) {
504 * try {
505 * ((Runnable) command).run();
506 * } catch (Exception ex) {
507 * throw new ExecutionException(last, "Error in runnable " + command, ex);
508 * }
509 * } else if (command instanceof Callable) {
510 * Object result;
511 * try {
512 * result = ((Callable) command).call();
513 * } catch (Exception ex) {
514 * throw new ExecutionException(last, "Error in callable " + command, ex);
515 * }
516 * // ...do something with result
517 * } else {
518 * throw new ExecutionException(last, "Parsed command (" + command + ") is not Runnable or Callable");
519 * }
520 * </pre>
521 * @since 2.0 */
522 public static class RunLast implements IParseResultHandler {
523 /** Prints help if requested, and otherwise executes the most specific {@code Runnable} or {@code Callable} subcommand.
524 * If the last (sub)command does not implement either {@code Runnable} or {@code Callable}, a {@code ExecutionException}
525 * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
526 *
527 * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
528 * @param out the {@code PrintStream} to print help to if requested
529 * @param ansi for printing help messages using ANSI styles and colors
530 * @return an empty list if help was requested, or a list containing a single element: the result of calling the
531 * {@code Callable}, or a {@code null} element if the last (sub)command was a {@code Runnable}
532 * @throws ExecutionException if a problem occurred while processing the parse results; use
533 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
534 */
535 @Override
536 public List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
537 if (printHelpIfRequested(parsedCommands, out, ansi)) { return Collections.emptyList(); }
538 CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
539 return Arrays.asList(execute(last));
540 }
541 }
542 /**
543 * Command line parse result handler that prints help if requested, and otherwise executes the top-level command and
544 * all subcommands as {@code Runnable} or {@code Callable}.
545 * For use in the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) parseWithHandler} methods.
546 * @since 2.0 */
547 public static class RunAll implements IParseResultHandler {
548 /** Prints help if requested, and otherwise executes the top-level command and all subcommands as {@code Runnable}
549 * or {@code Callable}. If any of the {@code CommandLine} commands does not implement either
550 * {@code Runnable} or {@code Callable}, a {@code ExecutionException}
551 * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
552 *
553 * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
554 * @param out the {@code PrintStream} to print help to if requested
555 * @param ansi for printing help messages using ANSI styles and colors
556 * @return an empty list if help was requested, or a list containing the result of executing all commands:
557 * the return values from calling the {@code Callable} commands, {@code null} elements for commands that implement {@code Runnable}
558 * @throws ExecutionException if a problem occurred while processing the parse results; use
559 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
560 */
561 @Override
562 public List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
563 if (printHelpIfRequested(parsedCommands, out, ansi)) {
564 return null;
565 }
566 List<Object> result = new ArrayList<Object>();
567 for (CommandLine parsed : parsedCommands) {
568 result.add(execute(parsed));
569 }
570 return result;
571 }
572 }
573 /**
574 * Returns the result of calling {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...)}
575 * with {@code Help.Ansi.AUTO} and a new {@link DefaultExceptionHandler} in addition to the specified parse result handler,
576 * {@code PrintStream}, and the specified command line arguments.
577 * <p>
578 * This is a convenience method intended to offer the same ease of use as the {@link #run(Runnable, PrintStream, Help.Ansi, String...) run}
579 * and {@link #call(Callable, PrintStream, Help.Ansi, String...) call} methods, but with more flexibility and better
580 * support for nested subcommands.
581 * </p>
582 * <p>Calling this method roughly expands to:</p>
583 * <pre>
584 * try {
585 * List<CommandLine> parsedCommands = parse(args);
586 * return parseResultsHandler.handleParseResult(parsedCommands, out, Help.Ansi.AUTO);
587 * } catch (ParameterException ex) {
588 * return new DefaultExceptionHandler().handleException(ex, out, ansi, args);
589 * }
590 * </pre>
591 * <p>
592 * Picocli provides some default handlers that allow you to accomplish some common tasks with very little code.
593 * The following handlers are available:</p>
594 * <ul>
595 * <li>{@link RunLast} handler prints help if requested, and otherwise gets the last specified command or subcommand
596 * and tries to execute it as a {@code Runnable} or {@code Callable}.</li>
597 * <li>{@link RunFirst} handler prints help if requested, and otherwise executes the top-level command as a {@code Runnable} or {@code Callable}.</li>
598 * <li>{@link RunAll} handler prints help if requested, and otherwise executes all recognized commands and subcommands as {@code Runnable} or {@code Callable} tasks.</li>
599 * <li>{@link DefaultExceptionHandler} prints the error message followed by usage help</li>
600 * </ul>
601 * @param handler the function that will process the result of successfully parsing the command line arguments
602 * @param out the {@code PrintStream} to print help to if requested
603 * @param args the command line arguments
604 * @return a list of results, or an empty list if there are no results
605 * @throws ExecutionException if the command line arguments were parsed successfully but a problem occurred while processing the
606 * parse results; use {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
607 * @see RunLast
608 * @see RunAll
609 * @since 2.0 */
610 public List<Object> parseWithHandler(IParseResultHandler handler, PrintStream out, String... args) {
611 return parseWithHandlers(handler, out, Help.Ansi.AUTO, new DefaultExceptionHandler(), args);
612 }
613 /**
614 * Tries to {@linkplain #parse(String...) parse} the specified command line arguments, and if successful, delegates
615 * the processing of the resulting list of {@code CommandLine} objects to the specified {@linkplain IParseResultHandler handler}.
616 * If the command line arguments were invalid, the {@code ParameterException} thrown from the {@code parse} method
617 * is caught and passed to the specified {@link IExceptionHandler}.
618 * <p>
619 * This is a convenience method intended to offer the same ease of use as the {@link #run(Runnable, PrintStream, Help.Ansi, String...) run}
620 * and {@link #call(Callable, PrintStream, Help.Ansi, String...) call} methods, but with more flexibility and better
621 * support for nested subcommands.
622 * </p>
623 * <p>Calling this method roughly expands to:</p>
624 * <pre>
625 * try {
626 * List<CommandLine> parsedCommands = parse(args);
627 * return parseResultsHandler.handleParseResult(parsedCommands, out, ansi);
628 * } catch (ParameterException ex) {
629 * return new exceptionHandler.handleException(ex, out, ansi, args);
630 * }
631 * </pre>
632 * <p>
633 * Picocli provides some default handlers that allow you to accomplish some common tasks with very little code.
634 * The following handlers are available:</p>
635 * <ul>
636 * <li>{@link RunLast} handler prints help if requested, and otherwise gets the last specified command or subcommand
637 * and tries to execute it as a {@code Runnable} or {@code Callable}.</li>
638 * <li>{@link RunFirst} handler prints help if requested, and otherwise executes the top-level command as a {@code Runnable} or {@code Callable}.</li>
639 * <li>{@link RunAll} handler prints help if requested, and otherwise executes all recognized commands and subcommands as {@code Runnable} or {@code Callable} tasks.</li>
640 * <li>{@link DefaultExceptionHandler} prints the error message followed by usage help</li>
641 * </ul>
642 *
643 * @param handler the function that will process the result of successfully parsing the command line arguments
644 * @param out the {@code PrintStream} to print help to if requested
645 * @param ansi for printing help messages using ANSI styles and colors
646 * @param exceptionHandler the function that can handle the {@code ParameterException} thrown when the command line arguments are invalid
647 * @param args the command line arguments
648 * @return a list of results produced by the {@code IParseResultHandler} or the {@code IExceptionHandler}, or an empty list if there are no results
649 * @throws ExecutionException if the command line arguments were parsed successfully but a problem occurred while processing the parse
650 * result {@code CommandLine} objects; use {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
651 * @see RunLast
652 * @see RunAll
653 * @see DefaultExceptionHandler
654 * @since 2.0 */
655 public List<Object> parseWithHandlers(IParseResultHandler handler, PrintStream out, Help.Ansi ansi, IExceptionHandler exceptionHandler, String... args) {
656 try {
657 List<CommandLine> result = parse(args);
658 return handler.handleParseResult(result, out, ansi);
659 } catch (ParameterException ex) {
660 return exceptionHandler.handleException(ex, out, ansi, args);
661 }
662 }
663 /**
664 * Equivalent to {@code new CommandLine(command).usage(out)}. See {@link #usage(PrintStream)} for details.
665 * @param command the object annotated with {@link Command}, {@link Option} and {@link Parameters}
666 * @param out the print stream to print the help message to
667 * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
668 */
669 public static void usage(Object command, PrintStream out) {
670 toCommandLine(command).usage(out);
671 }
672
673 /**
674 * Equivalent to {@code new CommandLine(command).usage(out, ansi)}.
675 * See {@link #usage(PrintStream, Help.Ansi)} for details.
676 * @param command the object annotated with {@link Command}, {@link Option} and {@link Parameters}
677 * @param out the print stream to print the help message to
678 * @param ansi whether the usage message should contain ANSI escape codes or not
679 * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
680 */
681 public static void usage(Object command, PrintStream out, Help.Ansi ansi) {
682 toCommandLine(command).usage(out, ansi);
683 }
684
685 /**
686 * Equivalent to {@code new CommandLine(command).usage(out, colorScheme)}.
687 * See {@link #usage(PrintStream, Help.ColorScheme)} for details.
688 * @param command the object annotated with {@link Command}, {@link Option} and {@link Parameters}
689 * @param out the print stream to print the help message to
690 * @param colorScheme the {@code ColorScheme} defining the styles for options, parameters and commands when ANSI is enabled
691 * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
692 */
693 public static void usage(Object command, PrintStream out, Help.ColorScheme colorScheme) {
694 toCommandLine(command).usage(out, colorScheme);
695 }
696
697 /**
698 * Delegates to {@link #usage(PrintStream, Help.Ansi)} with the {@linkplain Help.Ansi#AUTO platform default}.
699 * @param out the printStream to print to
700 * @see #usage(PrintStream, Help.ColorScheme)
701 */
702 public void usage(PrintStream out) {
703 usage(out, Help.Ansi.AUTO);
704 }
705
706 /**
707 * Delegates to {@link #usage(PrintStream, Help.ColorScheme)} with the {@linkplain Help#defaultColorScheme(CommandLine.Help.Ansi) default color scheme}.
708 * @param out the printStream to print to
709 * @param ansi whether the usage message should include ANSI escape codes or not
710 * @see #usage(PrintStream, Help.ColorScheme)
711 */
712 public void usage(PrintStream out, Help.Ansi ansi) {
713 usage(out, Help.defaultColorScheme(ansi));
714 }
715 /**
716 * Prints a usage help message for the annotated command class to the specified {@code PrintStream}.
717 * Delegates construction of the usage help message to the {@link Help} inner class and is equivalent to:
718 * <pre>
719 * Help help = new Help(command).addAllSubcommands(getSubcommands());
720 * StringBuilder sb = new StringBuilder()
721 * .append(help.headerHeading())
722 * .append(help.header())
723 * .append(help.synopsisHeading()) //e.g. Usage:
724 * .append(help.synopsis()) //e.g. <main class> [OPTIONS] <command> [COMMAND-OPTIONS] [ARGUMENTS]
725 * .append(help.descriptionHeading()) //e.g. %nDescription:%n%n
726 * .append(help.description()) //e.g. {"Converts foos to bars.", "Use options to control conversion mode."}
727 * .append(help.parameterListHeading()) //e.g. %nPositional parameters:%n%n
728 * .append(help.parameterList()) //e.g. [FILE...] the files to convert
729 * .append(help.optionListHeading()) //e.g. %nOptions:%n%n
730 * .append(help.optionList()) //e.g. -h, --help displays this help and exits
731 * .append(help.commandListHeading()) //e.g. %nCommands:%n%n
732 * .append(help.commandList()) //e.g. add adds the frup to the frooble
733 * .append(help.footerHeading())
734 * .append(help.footer());
735 * out.print(sb);
736 * </pre>
737 * <p>Annotate your class with {@link Command} to control many aspects of the usage help message, including
738 * the program name, text of section headings and section contents, and some aspects of the auto-generated sections
739 * of the usage help message.
740 * <p>To customize the auto-generated sections of the usage help message, like how option details are displayed,
741 * instantiate a {@link Help} object and use a {@link Help.TextTable} with more of fewer columns, a custom
742 * {@linkplain Help.Layout layout}, and/or a custom option {@linkplain Help.IOptionRenderer renderer}
743 * for ultimate control over which aspects of an Option or Field are displayed where.</p>
744 * @param out the {@code PrintStream} to print the usage help message to
745 * @param colorScheme the {@code ColorScheme} defining the styles for options, parameters and commands when ANSI is enabled
746 */
747 public void usage(PrintStream out, Help.ColorScheme colorScheme) {
748 Help help = new Help(interpreter.command, colorScheme).addAllSubcommands(getSubcommands());
749 if (!Help.DEFAULT_SEPARATOR.equals(getSeparator())) {
750 help.separator = getSeparator();
751 help.parameterLabelRenderer = help.createDefaultParamLabelRenderer(); // update for new separator
752 }
753 if (!Help.DEFAULT_COMMAND_NAME.equals(getCommandName())) {
754 help.commandName = getCommandName();
755 }
756 StringBuilder sb = new StringBuilder()
757 .append(help.headerHeading())
758 .append(help.header())
759 .append(help.synopsisHeading()) //e.g. Usage:
760 .append(help.synopsis(help.synopsisHeadingLength())) //e.g. <main class> [OPTIONS] <command> [COMMAND-OPTIONS] [ARGUMENTS]
761 .append(help.descriptionHeading()) //e.g. %nDescription:%n%n
762 .append(help.description()) //e.g. {"Converts foos to bars.", "Use options to control conversion mode."}
763 .append(help.parameterListHeading()) //e.g. %nPositional parameters:%n%n
764 .append(help.parameterList()) //e.g. [FILE...] the files to convert
765 .append(help.optionListHeading()) //e.g. %nOptions:%n%n
766 .append(help.optionList()) //e.g. -h, --help displays this help and exits
767 .append(help.commandListHeading()) //e.g. %nCommands:%n%n
768 .append(help.commandList()) //e.g. add adds the frup to the frooble
769 .append(help.footerHeading())
770 .append(help.footer());
771 out.print(sb);
772 }
773
774 /**
775 * Delegates to {@link #printVersionHelp(PrintStream, Help.Ansi)} with the {@linkplain Help.Ansi#AUTO platform default}.
776 * @param out the printStream to print to
777 * @see #printVersionHelp(PrintStream, Help.Ansi)
778 * @since 0.9.8
779 */
780 public void printVersionHelp(PrintStream out) { printVersionHelp(out, Help.Ansi.AUTO); }
781
782 /**
783 * Prints version information from the {@link Command#version()} annotation to the specified {@code PrintStream}.
784 * Each element of the array of version strings is printed on a separate line. Version strings may contain
785 * <a href="http://picocli.info/#_usage_help_with_styles_and_colors">markup for colors and style</a>.
786 * @param out the printStream to print to
787 * @param ansi whether the usage message should include ANSI escape codes or not
788 * @see Command#version()
789 * @see Option#versionHelp()
790 * @see #isVersionHelpRequested()
791 * @since 0.9.8
792 */
793 public void printVersionHelp(PrintStream out, Help.Ansi ansi) {
794 for (String versionInfo : versionLines) {
795 out.println(ansi.new Text(versionInfo));
796 }
797 }
798 /**
799 * Prints version information from the {@link Command#version()} annotation to the specified {@code PrintStream}.
800 * Each element of the array of version strings is {@linkplain String#format(String, Object...) formatted} with the
801 * specified parameters, and printed on a separate line. Both version strings and parameters may contain
802 * <a href="http://picocli.info/#_usage_help_with_styles_and_colors">markup for colors and style</a>.
803 * @param out the printStream to print to
804 * @param ansi whether the usage message should include ANSI escape codes or not
805 * @param params Arguments referenced by the format specifiers in the version strings
806 * @see Command#version()
807 * @see Option#versionHelp()
808 * @see #isVersionHelpRequested()
809 * @since 1.0.0
810 */
811 public void printVersionHelp(PrintStream out, Help.Ansi ansi, Object... params) {
812 for (String versionInfo : versionLines) {
813 out.println(ansi.new Text(String.format(versionInfo, params)));
814 }
815 }
816
817 /**
818 * Delegates to {@link #call(Callable, PrintStream, Help.Ansi, String...)} with {@link Help.Ansi#AUTO}.
819 * <p>
820 * From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, Help.Ansi) requested},
821 * and any exceptions thrown by the {@code Callable} are caught and rethrown wrapped in an {@code ExecutionException}.
822 * </p>
823 * @param callable the command to call when {@linkplain #parse(String...) parsing} succeeds.
824 * @param out the printStream to print to
825 * @param args the command line arguments to parse
826 * @param <C> the annotated object must implement Callable
827 * @param <T> the return type of the most specific command (must implement {@code Callable})
828 * @see #call(Callable, PrintStream, Help.Ansi, String...)
829 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
830 * @throws ExecutionException if the Callable throws an exception
831 * @return {@code null} if an error occurred while parsing the command line options, otherwise returns the result of calling the Callable
832 * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...)
833 * @see RunFirst
834 */
835 public static <C extends Callable<T>, T> T call(C callable, PrintStream out, String... args) {
836 return call(callable, out, Help.Ansi.AUTO, args);
837 }
838 /**
839 * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
840 * The annotated object needs to implement {@link Callable}. Calling this method is equivalent to:
841 * <pre>
842 * CommandLine cmd = new CommandLine(callable);
843 * List<CommandLine> parsedCommands;
844 * try {
845 * parsedCommands = cmd.parse(args);
846 * } catch (ParameterException ex) {
847 * out.println(ex.getMessage());
848 * cmd.usage(out, ansi);
849 * return null;
850 * }
851 * if (CommandLine.printHelpIfRequested(parsedCommands, out, ansi)) {
852 * return null;
853 * }
854 * CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
855 * try {
856 * Callable<Object> subcommand = last.getCommand();
857 * return subcommand.call();
858 * } catch (Exception ex) {
859 * throw new ExecutionException(last, "Error calling " + last.getCommand(), ex);
860 * }
861 * </pre>
862 * <p>
863 * If the specified Callable command has subcommands, the {@linkplain RunLast last} subcommand specified on the
864 * command line is executed.
865 * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler, PrintStream, String...) parseWithHandler}
866 * method with a {@link RunAll} handler or a custom handler.
867 * </p><p>
868 * From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, Help.Ansi) requested},
869 * and any exceptions thrown by the {@code Callable} are caught and rethrown wrapped in an {@code ExecutionException}.
870 * </p>
871 * @param callable the command to call when {@linkplain #parse(String...) parsing} succeeds.
872 * @param out the printStream to print to
873 * @param ansi whether the usage message should include ANSI escape codes or not
874 * @param args the command line arguments to parse
875 * @param <C> the annotated object must implement Callable
876 * @param <T> the return type of the specified {@code Callable}
877 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
878 * @throws ExecutionException if the Callable throws an exception
879 * @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
880 * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...)
881 * @see RunLast
882 */
883 public static <C extends Callable<T>, T> T call(C callable, PrintStream out, Help.Ansi ansi, String... args) {
884 CommandLine cmd = new CommandLine(callable); // validate command outside of try-catch
885 List<Object> results = cmd.parseWithHandlers(new RunLast(), out, ansi, new DefaultExceptionHandler(), args);
886 return results == null || results.isEmpty() ? null : (T) results.get(0);
887 }
888
889 /**
890 * Delegates to {@link #run(Runnable, PrintStream, Help.Ansi, String...)} with {@link Help.Ansi#AUTO}.
891 * <p>
892 * From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, Help.Ansi) requested},
893 * and any exceptions thrown by the {@code Runnable} are caught and rethrown wrapped in an {@code ExecutionException}.
894 * </p>
895 * @param runnable the command to run when {@linkplain #parse(String...) parsing} succeeds.
896 * @param out the printStream to print to
897 * @param args the command line arguments to parse
898 * @param <R> the annotated object must implement Runnable
899 * @see #run(Runnable, PrintStream, Help.Ansi, String...)
900 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
901 * @throws ExecutionException if the Runnable throws an exception
902 * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...)
903 * @see RunFirst
904 */
905 public static <R extends Runnable> void run(R runnable, PrintStream out, String... args) {
906 run(runnable, out, Help.Ansi.AUTO, args);
907 }
908 /**
909 * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
910 * The annotated object needs to implement {@link Runnable}. Calling this method is equivalent to:
911 * <pre>
912 * CommandLine cmd = new CommandLine(runnable);
913 * List<CommandLine> parsedCommands;
914 * try {
915 * parsedCommands = cmd.parse(args);
916 * } catch (ParameterException ex) {
917 * out.println(ex.getMessage());
918 * cmd.usage(out, ansi);
919 * return null;
920 * }
921 * if (CommandLine.printHelpIfRequested(parsedCommands, out, ansi)) {
922 * return null;
923 * }
924 * CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
925 * try {
926 * Runnable subcommand = last.getCommand();
927 * subcommand.run();
928 * } catch (Exception ex) {
929 * throw new ExecutionException(last, "Error running " + last.getCommand(), ex);
930 * }
931 * </pre>
932 * <p>
933 * If the specified Runnable command has subcommands, the {@linkplain RunLast last} subcommand specified on the
934 * command line is executed.
935 * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler, PrintStream, String...) parseWithHandler}
936 * method with a {@link RunAll} handler or a custom handler.
937 * </p><p>
938 * From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, Help.Ansi) requested},
939 * and any exceptions thrown by the {@code Runnable} are caught and rethrown wrapped in an {@code ExecutionException}.
940 * </p>
941 * @param runnable the command to run when {@linkplain #parse(String...) parsing} succeeds.
942 * @param out the printStream to print to
943 * @param ansi whether the usage message should include ANSI escape codes or not
944 * @param args the command line arguments to parse
945 * @param <R> the annotated object must implement Runnable
946 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
947 * @throws ExecutionException if the Runnable throws an exception
948 * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...)
949 * @see RunLast
950 */
951 public static <R extends Runnable> void run(R runnable, PrintStream out, Help.Ansi ansi, String... args) {
952 CommandLine cmd = new CommandLine(runnable); // validate command outside of try-catch
953 cmd.parseWithHandlers(new RunLast(), out, ansi, new DefaultExceptionHandler(), args);
954 }
955
956 /**
957 * Registers the specified type converter for the specified class. When initializing fields annotated with
958 * {@link Option}, the field's type is used as a lookup key to find the associated type converter, and this
959 * type converter converts the original command line argument string value to the correct type.
960 * <p>
961 * Java 8 lambdas make it easy to register custom type converters:
962 * </p>
963 * <pre>
964 * commandLine.registerConverter(java.nio.file.Path.class, s -> java.nio.file.Paths.get(s));
965 * commandLine.registerConverter(java.time.Duration.class, s -> java.time.Duration.parse(s));</pre>
966 * <p>
967 * Built-in type converters are pre-registered for the following java 1.5 types:
968 * </p>
969 * <ul>
970 * <li>all primitive types</li>
971 * <li>all primitive wrapper types: Boolean, Byte, Character, Double, Float, Integer, Long, Short</li>
972 * <li>any enum</li>
973 * <li>java.io.File</li>
974 * <li>java.math.BigDecimal</li>
975 * <li>java.math.BigInteger</li>
976 * <li>java.net.InetAddress</li>
977 * <li>java.net.URI</li>
978 * <li>java.net.URL</li>
979 * <li>java.nio.charset.Charset</li>
980 * <li>java.sql.Time</li>
981 * <li>java.util.Date</li>
982 * <li>java.util.UUID</li>
983 * <li>java.util.regex.Pattern</li>
984 * <li>StringBuilder</li>
985 * <li>CharSequence</li>
986 * <li>String</li>
987 * </ul>
988 * <p>The specified converter will be registered with this {@code CommandLine} and the full hierarchy of its
989 * subcommands and nested sub-subcommands <em>at the moment the converter is registered</em>. Subcommands added
990 * later will not have this converter added automatically. To ensure a custom type converter is available to all
991 * subcommands, register the type converter last, after adding subcommands.</p>
992 *
993 * @param cls the target class to convert parameter string values to
994 * @param converter the class capable of converting string values to the specified target type
995 * @param <K> the target type
996 * @return this CommandLine object, to allow method chaining
997 * @see #addSubcommand(String, Object)
998 */
999 public <K> CommandLine registerConverter(Class<K> cls, ITypeConverter<K> converter) {
1000 interpreter.converterRegistry.put(Assert.notNull(cls, "class"), Assert.notNull(converter, "converter"));
1001 for (CommandLine command : interpreter.commands.values()) {
1002 command.registerConverter(cls, converter);
1003 }
1004 return this;
1005 }
1006
1007 /** Returns the String that separates option names from option values when parsing command line options. {@value Help#DEFAULT_SEPARATOR} by default.
1008 * @return the String the parser uses to separate option names from option values */
1009 public String getSeparator() {
1010 return interpreter.separator;
1011 }
1012
1013 /** Sets the String the parser uses to separate option names from option values to the specified value.
1014 * The separator may also be set declaratively with the {@link CommandLine.Command#separator()} annotation attribute.
1015 * @param separator the String that separates option names from option values
1016 * @return this {@code CommandLine} object, to allow method chaining */
1017 public CommandLine setSeparator(String separator) {
1018 interpreter.separator = Assert.notNull(separator, "separator");
1019 return this;
1020 }
1021
1022 /** Returns the command name (also called program name) displayed in the usage help synopsis. {@value Help#DEFAULT_COMMAND_NAME} by default.
1023 * @return the command name (also called program name) displayed in the usage */
1024 public String getCommandName() {
1025 return commandName;
1026 }
1027
1028 /** Sets the command name (also called program name) displayed in the usage help synopsis to the specified value.
1029 * Note that this method only modifies the usage help message, it does not impact parsing behaviour.
1030 * The command name may also be set declaratively with the {@link CommandLine.Command#name()} annotation attribute.
1031 * @param commandName command name (also called program name) displayed in the usage help synopsis
1032 * @return this {@code CommandLine} object, to allow method chaining */
1033 public CommandLine setCommandName(String commandName) {
1034 this.commandName = Assert.notNull(commandName, "commandName");
1035 return this;
1036 }
1037 private static boolean empty(String str) { return str == null || str.trim().length() == 0; }
1038 private static boolean empty(Object[] array) { return array == null || array.length == 0; }
1039 private static boolean empty(Text txt) { return txt == null || txt.plain.toString().trim().length() == 0; }
1040 private static String str(String[] arr, int i) { return (arr == null || arr.length == 0) ? "" : arr[i]; }
1041 private static boolean isBoolean(Class<?> type) { return type == Boolean.class || type == Boolean.TYPE; }
1042 private static CommandLine toCommandLine(Object obj) { return obj instanceof CommandLine ? (CommandLine) obj : new CommandLine(obj);}
1043 private static boolean isMultiValue(Field field) { return isMultiValue(field.getType()); }
1044 private static boolean isMultiValue(Class<?> cls) { return cls.isArray() || Collection.class.isAssignableFrom(cls) || Map.class.isAssignableFrom(cls); }
1045 private static Class<?>[] getTypeAttribute(Field field) {
1046 Class<?>[] explicit = field.isAnnotationPresent(Parameters.class) ? field.getAnnotation(Parameters.class).type() : field.getAnnotation(Option.class).type();
1047 if (explicit.length > 0) { return explicit; }
1048 if (field.getType().isArray()) { return new Class<?>[] { field.getType().getComponentType() }; }
1049 if (isMultiValue(field)) {
1050 Type type = field.getGenericType(); // e.g. Map<Long, ? extends Number>
1051 if (type instanceof ParameterizedType) {
1052 ParameterizedType parameterizedType = (ParameterizedType) type;
1053 Type[] paramTypes = parameterizedType.getActualTypeArguments(); // e.g. ? extends Number
1054 Class<?>[] result = new Class<?>[paramTypes.length];
1055 for (int i = 0; i < paramTypes.length; i++) {
1056 if (paramTypes[i] instanceof Class) { result[i] = (Class<?>) paramTypes[i]; continue; } // e.g. Long
1057 if (paramTypes[i] instanceof WildcardType) { // e.g. ? extends Number
1058 WildcardType wildcardType = (WildcardType) paramTypes[i];
1059 Type[] lower = wildcardType.getLowerBounds(); // e.g. []
1060 if (lower.length > 0 && lower[0] instanceof Class) { result[i] = (Class<?>) lower[0]; continue; }
1061 Type[] upper = wildcardType.getUpperBounds(); // e.g. Number
1062 if (upper.length > 0 && upper[0] instanceof Class) { result[i] = (Class<?>) upper[0]; continue; }
1063 }
1064 Arrays.fill(result, String.class); return result; // too convoluted generic type, giving up
1065 }
1066 return result; // we inferred all types from ParameterizedType
1067 }
1068 return new Class<?>[] {String.class, String.class}; // field is multi-value but not ParameterizedType
1069 }
1070 return new Class<?>[] {field.getType()}; // not a multi-value field
1071 }
1072 /**
1073 * <p>
1074 * Annotate fields in your class with {@code @Option} and picocli will initialize these fields when matching
1075 * arguments are specified on the command line.
1076 * </p><p>
1077 * For example:
1078 * </p>
1079 * <pre>import static picocli.CommandLine.*;
1080 *
1081 * public class MyClass {
1082 * @Parameters(type = File.class, description = "Any number of input files")
1083 * private List<File> files = new ArrayList<File>();
1084 *
1085 * @Option(names = { "-o", "--out" }, description = "Output file (default: print to console)")
1086 * private File outputFile;
1087 *
1088 * @Option(names = { "-v", "--verbose"}, description = "Verbosely list files processed")
1089 * private boolean verbose;
1090 *
1091 * @Option(names = { "-h", "--help", "-?", "-help"}, usageHelp = true, description = "Display this help and exit")
1092 * private boolean help;
1093 *
1094 * @Option(names = { "-V", "--version"}, versionHelp = true, description = "Display version information and exit")
1095 * private boolean version;
1096 * }
1097 * </pre>
1098 * <p>
1099 * A field cannot be annotated with both {@code @Parameters} and {@code @Option} or a
1100 * {@code ParameterException} is thrown.
1101 * </p>
1102 */
1103 @Retention(RetentionPolicy.RUNTIME)
1104 @Target(ElementType.FIELD)
1105 public @interface Option {
1106 /**
1107 * One or more option names. At least one option name is required.
1108 * <p>
1109 * Different environments have different conventions for naming options, but usually options have a prefix
1110 * that sets them apart from parameters.
1111 * Picocli supports all of the below styles. The default separator is {@code '='}, but this can be configured.
1112 * </p><p>
1113 * <b>*nix</b>
1114 * </p><p>
1115 * In Unix and Linux, options have a short (single-character) name, a long name or both.
1116 * Short options
1117 * (<a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02">POSIX
1118 * style</a> are single-character and are preceded by the {@code '-'} character, e.g., {@code `-v'}.
1119 * <a href="https://www.gnu.org/software/tar/manual/html_node/Long-Options.html">GNU-style</a> long
1120 * (or <em>mnemonic</em>) options start with two dashes in a row, e.g., {@code `--file'}.
1121 * </p><p>Picocli supports the POSIX convention that short options can be grouped, with the last option
1122 * optionally taking a parameter, which may be attached to the option name or separated by a space or
1123 * a {@code '='} character. The below examples are all equivalent:
1124 * </p><pre>
1125 * -xvfFILE
1126 * -xvf FILE
1127 * -xvf=FILE
1128 * -xv --file FILE
1129 * -xv --file=FILE
1130 * -x -v --file FILE
1131 * -x -v --file=FILE
1132 * </pre><p>
1133 * <b>DOS</b>
1134 * </p><p>
1135 * DOS options mostly have upper case single-character names and start with a single slash {@code '/'} character.
1136 * Option parameters are separated by a {@code ':'} character. Options cannot be grouped together but
1137 * must be specified separately. For example:
1138 * </p><pre>
1139 * DIR /S /A:D /T:C
1140 * </pre><p>
1141 * <b>PowerShell</b>
1142 * </p><p>
1143 * Windows PowerShell options generally are a word preceded by a single {@code '-'} character, e.g., {@code `-Help'}.
1144 * Option parameters are separated by a space or by a {@code ':'} character.
1145 * </p>
1146 * @return one or more option names
1147 */
1148 String[] names();
1149
1150 /**
1151 * Indicates whether this option is required. By default this is false.
1152 * If an option is required, but a user invokes the program without specifying the required option,
1153 * a {@link MissingParameterException} is thrown from the {@link #parse(String...)} method.
1154 * @return whether this option is required
1155 */
1156 boolean required() default false;
1157
1158 /**
1159 * Set {@code help=true} if this option should disable validation of the remaining arguments:
1160 * If the {@code help} option is specified, no error message is generated for missing required options.
1161 * <p>
1162 * This attribute is useful for special options like help ({@code -h} and {@code --help} on unix,
1163 * {@code -?} and {@code -Help} on Windows) or version ({@code -V} and {@code --version} on unix,
1164 * {@code -Version} on Windows).
1165 * </p>
1166 * <p>
1167 * Note that the {@link #parse(String...)} method will not print help documentation. It will only set
1168 * the value of the annotated field. It is the responsibility of the caller to inspect the annotated fields
1169 * and take the appropriate action.
1170 * </p>
1171 * @return whether this option disables validation of the other arguments
1172 * @deprecated Use {@link #usageHelp()} and {@link #versionHelp()} instead. See {@link #printHelpIfRequested(List, PrintStream, CommandLine.Help.Ansi)}
1173 */
1174 boolean help() default false;
1175
1176 /**
1177 * Set {@code usageHelp=true} if this option allows the user to request usage help. If this option is
1178 * specified on the command line, picocli will not validate the remaining arguments (so no "missing required
1179 * option" errors) and the {@link CommandLine#isUsageHelpRequested()} method will return {@code true}.
1180 * <p>
1181 * This attribute is useful for special options like help ({@code -h} and {@code --help} on unix,
1182 * {@code -?} and {@code -Help} on Windows).
1183 * </p>
1184 * <p>
1185 * Note that the {@link #parse(String...)} method will not print usage help documentation. It will only set
1186 * the value of the annotated field. It is the responsibility of the caller to inspect the annotated fields
1187 * and take the appropriate action.
1188 * </p>
1189 * @return whether this option allows the user to request usage help
1190 * @since 0.9.8
1191 */
1192 boolean usageHelp() default false;
1193
1194 /**
1195 * Set {@code versionHelp=true} if this option allows the user to request version information. If this option is
1196 * specified on the command line, picocli will not validate the remaining arguments (so no "missing required
1197 * option" errors) and the {@link CommandLine#isVersionHelpRequested()} method will return {@code true}.
1198 * <p>
1199 * This attribute is useful for special options like version ({@code -V} and {@code --version} on unix,
1200 * {@code -Version} on Windows).
1201 * </p>
1202 * <p>
1203 * Note that the {@link #parse(String...)} method will not print version information. It will only set
1204 * the value of the annotated field. It is the responsibility of the caller to inspect the annotated fields
1205 * and take the appropriate action.
1206 * </p>
1207 * @return whether this option allows the user to request version information
1208 * @since 0.9.8
1209 */
1210 boolean versionHelp() default false;
1211
1212 /**
1213 * Description of this option, used when generating the usage documentation.
1214 * @return the description of this option
1215 */
1216 String[] description() default {};
1217
1218 /**
1219 * Specifies the minimum number of required parameters and the maximum number of accepted parameters.
1220 * If an option declares a positive arity, and the user specifies an insufficient number of parameters on the
1221 * command line, a {@link MissingParameterException} is thrown by the {@link #parse(String...)} method.
1222 * <p>
1223 * In many cases picocli can deduce the number of required parameters from the field's type.
1224 * By default, flags (boolean options) have arity zero,
1225 * and single-valued type fields (String, int, Integer, double, Double, File, Date, etc) have arity one.
1226 * Generally, fields with types that cannot hold multiple values can omit the {@code arity} attribute.
1227 * </p><p>
1228 * Fields used to capture options with arity two or higher should have a type that can hold multiple values,
1229 * like arrays or Collections. See {@link #type()} for strongly-typed Collection fields.
1230 * </p><p>
1231 * For example, if an option has 2 required parameters and any number of optional parameters,
1232 * specify {@code @Option(names = "-example", arity = "2..*")}.
1233 * </p>
1234 * <b>A note on boolean options</b>
1235 * <p>
1236 * By default picocli does not expect boolean options (also called "flags" or "switches") to have a parameter.
1237 * You can make a boolean option take a required parameter by annotating your field with {@code arity="1"}.
1238 * For example: </p>
1239 * <pre>@Option(names = "-v", arity = "1") boolean verbose;</pre>
1240 * <p>
1241 * Because this boolean field is defined with arity 1, the user must specify either {@code <program> -v false}
1242 * or {@code <program> -v true}
1243 * on the command line, or a {@link MissingParameterException} is thrown by the {@link #parse(String...)}
1244 * method.
1245 * </p><p>
1246 * To make the boolean parameter possible but optional, define the field with {@code arity = "0..1"}.
1247 * For example: </p>
1248 * <pre>@Option(names="-v", arity="0..1") boolean verbose;</pre>
1249 * <p>This will accept any of the below without throwing an exception:</p>
1250 * <pre>
1251 * -v
1252 * -v true
1253 * -v false
1254 * </pre>
1255 * @return how many arguments this option requires
1256 */
1257 String arity() default "";
1258
1259 /**
1260 * Specify a {@code paramLabel} for the option parameter to be used in the usage help message. If omitted,
1261 * picocli uses the field name in fish brackets ({@code '<'} and {@code '>'}) by default. Example:
1262 * <pre>class Example {
1263 * @Option(names = {"-o", "--output"}, paramLabel="FILE", description="path of the output file")
1264 * private File out;
1265 * @Option(names = {"-j", "--jobs"}, arity="0..1", description="Allow N jobs at once; infinite jobs with no arg.")
1266 * private int maxJobs = -1;
1267 * }</pre>
1268 * <p>By default, the above gives a usage help message like the following:</p><pre>
1269 * Usage: <main class> [OPTIONS]
1270 * -o, --output FILE path of the output file
1271 * -j, --jobs [<maxJobs>] Allow N jobs at once; infinite jobs with no arg.
1272 * </pre>
1273 * @return name of the option parameter used in the usage help message
1274 */
1275 String paramLabel() default "";
1276
1277 /** <p>
1278 * Optionally specify a {@code type} to control exactly what Class the option parameter should be converted
1279 * to. This may be useful when the field type is an interface or an abstract class. For example, a field can
1280 * be declared to have type {@code java.lang.Number}, and annotating {@code @Option(type=Short.class)}
1281 * ensures that the option parameter value is converted to a {@code Short} before setting the field value.
1282 * </p><p>
1283 * For array fields whose <em>component</em> type is an interface or abstract class, specify the concrete <em>component</em> type.
1284 * For example, a field with type {@code Number[]} may be annotated with {@code @Option(type=Short.class)}
1285 * to ensure that option parameter values are converted to {@code Short} before adding an element to the array.
1286 * </p><p>
1287 * Picocli will use the {@link ITypeConverter} that is
1288 * {@linkplain #registerConverter(Class, ITypeConverter) registered} for the specified type to convert
1289 * the raw String values before modifying the field value.
1290 * </p><p>
1291 * Prior to 2.0, the {@code type} attribute was necessary for {@code Collection} and {@code Map} fields,
1292 * but starting from 2.0 picocli will infer the component type from the generic type's type arguments.
1293 * For example, for a field of type {@code Map<TimeUnit, Long>} picocli will know the option parameter
1294 * should be split up in key=value pairs, where the key should be converted to a {@code java.util.concurrent.TimeUnit}
1295 * enum value, and the value should be converted to a {@code Long}. No {@code @Option(type=...)} type attribute
1296 * is required for this. For generic types with wildcards, picocli will take the specified upper or lower bound
1297 * as the Class to convert to, unless the {@code @Option} annotation specifies an explicit {@code type} attribute.
1298 * </p><p>
1299 * If the field type is a raw collection or a raw map, and you want it to contain other values than Strings,
1300 * or if the generic type's type arguments are interfaces or abstract classes, you may
1301 * specify a {@code type} attribute to control the Class that the option parameter should be converted to.
1302 * @return the type(s) to convert the raw String values
1303 */
1304 Class<?>[] type() default {};
1305
1306 /**
1307 * Specify a regular expression to use to split option parameter values before applying them to the field.
1308 * All elements resulting from the split are added to the array or Collection. Ignored for single-value fields.
1309 * @return a regular expression to split option parameter values or {@code ""} if the value should not be split
1310 * @see String#split(String)
1311 */
1312 String split() default "";
1313
1314 /**
1315 * Set {@code hidden=true} if this option should not be included in the usage documentation.
1316 * @return whether this option should be excluded from the usage message
1317 */
1318 boolean hidden() default false;
1319 }
1320 /**
1321 * <p>
1322 * Fields annotated with {@code @Parameters} will be initialized with positional parameters. By specifying the
1323 * {@link #index()} attribute you can pick which (or what range) of the positional parameters to apply. If no index
1324 * is specified, the field will get all positional parameters (so it should be an array or a collection).
1325 * </p><p>
1326 * When parsing the command line arguments, picocli first tries to match arguments to {@link Option Options}.
1327 * Positional parameters are the arguments that follow the options, or the arguments that follow a "--" (double
1328 * dash) argument on the command line.
1329 * </p><p>
1330 * For example:
1331 * </p>
1332 * <pre>import static picocli.CommandLine.*;
1333 *
1334 * public class MyCalcParameters {
1335 * @Parameters(type = BigDecimal.class, description = "Any number of input numbers")
1336 * private List<BigDecimal> files = new ArrayList<BigDecimal>();
1337 *
1338 * @Option(names = { "-h", "--help", "-?", "-help"}, help = true, description = "Display this help and exit")
1339 * private boolean help;
1340 * }
1341 * </pre><p>
1342 * A field cannot be annotated with both {@code @Parameters} and {@code @Option} or a {@code ParameterException}
1343 * is thrown.</p>
1344 */
1345 @Retention(RetentionPolicy.RUNTIME)
1346 @Target(ElementType.FIELD)
1347 public @interface Parameters {
1348 /** Specify an index ("0", or "1", etc.) to pick which of the command line arguments should be assigned to this
1349 * field. For array or Collection fields, you can also specify an index range ("0..3", or "2..*", etc.) to assign
1350 * a subset of the command line arguments to this field. The default is "*", meaning all command line arguments.
1351 * @return an index or range specifying which of the command line arguments should be assigned to this field
1352 */
1353 String index() default "*";
1354
1355 /** Description of the parameter(s), used when generating the usage documentation.
1356 * @return the description of the parameter(s)
1357 */
1358 String[] description() default {};
1359
1360 /**
1361 * Specifies the minimum number of required parameters and the maximum number of accepted parameters. If a
1362 * positive arity is declared, and the user specifies an insufficient number of parameters on the command line,
1363 * {@link MissingParameterException} is thrown by the {@link #parse(String...)} method.
1364 * <p>The default depends on the type of the parameter: booleans require no parameters, arrays and Collections
1365 * accept zero to any number of parameters, and any other type accepts one parameter.</p>
1366 * @return the range of minimum and maximum parameters accepted by this command
1367 */
1368 String arity() default "";
1369
1370 /**
1371 * Specify a {@code paramLabel} for the parameter to be used in the usage help message. If omitted,
1372 * picocli uses the field name in fish brackets ({@code '<'} and {@code '>'}) by default. Example:
1373 * <pre>class Example {
1374 * @Parameters(paramLabel="FILE", description="path of the input FILE(s)")
1375 * private File[] inputFiles;
1376 * }</pre>
1377 * <p>By default, the above gives a usage help message like the following:</p><pre>
1378 * Usage: <main class> [FILE...]
1379 * [FILE...] path of the input FILE(s)
1380 * </pre>
1381 * @return name of the positional parameter used in the usage help message
1382 */
1383 String paramLabel() default "";
1384
1385 /**
1386 * <p>
1387 * Optionally specify a {@code type} to control exactly what Class the positional parameter should be converted
1388 * to. This may be useful when the field type is an interface or an abstract class. For example, a field can
1389 * be declared to have type {@code java.lang.Number}, and annotating {@code @Parameters(type=Short.class)}
1390 * ensures that the positional parameter value is converted to a {@code Short} before setting the field value.
1391 * </p><p>
1392 * For array fields whose <em>component</em> type is an interface or abstract class, specify the concrete <em>component</em> type.
1393 * For example, a field with type {@code Number[]} may be annotated with {@code @Parameters(type=Short.class)}
1394 * to ensure that positional parameter values are converted to {@code Short} before adding an element to the array.
1395 * </p><p>
1396 * Picocli will use the {@link ITypeConverter} that is
1397 * {@linkplain #registerConverter(Class, ITypeConverter) registered} for the specified type to convert
1398 * the raw String values before modifying the field value.
1399 * </p><p>
1400 * Prior to 2.0, the {@code type} attribute was necessary for {@code Collection} and {@code Map} fields,
1401 * but starting from 2.0 picocli will infer the component type from the generic type's type arguments.
1402 * For example, for a field of type {@code Map<TimeUnit, Long>} picocli will know the positional parameter
1403 * should be split up in key=value pairs, where the key should be converted to a {@code java.util.concurrent.TimeUnit}
1404 * enum value, and the value should be converted to a {@code Long}. No {@code @Parameters(type=...)} type attribute
1405 * is required for this. For generic types with wildcards, picocli will take the specified upper or lower bound
1406 * as the Class to convert to, unless the {@code @Parameters} annotation specifies an explicit {@code type} attribute.
1407 * </p><p>
1408 * If the field type is a raw collection or a raw map, and you want it to contain other values than Strings,
1409 * or if the generic type's type arguments are interfaces or abstract classes, you may
1410 * specify a {@code type} attribute to control the Class that the positional parameter should be converted to.
1411 * @return the type(s) to convert the raw String values
1412 */
1413 Class<?>[] type() default {};
1414
1415 /**
1416 * Specify a regular expression to use to split positional parameter values before applying them to the field.
1417 * All elements resulting from the split are added to the array or Collection. Ignored for single-value fields.
1418 * @return a regular expression to split operand values or {@code ""} if the value should not be split
1419 * @see String#split(String)
1420 */
1421 String split() default "";
1422
1423 /**
1424 * Set {@code hidden=true} if this parameter should not be included in the usage message.
1425 * @return whether this parameter should be excluded from the usage message
1426 */
1427 boolean hidden() default false;
1428 }
1429
1430 /**
1431 * <p>Annotate your class with {@code @Command} when you want more control over the format of the generated help
1432 * message.
1433 * </p><pre>
1434 * @Command(name = "Encrypt",
1435 * description = "Encrypt FILE(s), or standard input, to standard output or to the output file.",
1436 * footer = "Copyright (c) 2017")
1437 * public class Encrypt {
1438 * @Parameters(paramLabel = "FILE", type = File.class, description = "Any number of input files")
1439 * private List<File> files = new ArrayList<File>();
1440 *
1441 * @Option(names = { "-o", "--out" }, description = "Output file (default: print to console)")
1442 * private File outputFile;
1443 * }</pre>
1444 * <p>
1445 * The structure of a help message looks like this:
1446 * </p><ul>
1447 * <li>[header]</li>
1448 * <li>[synopsis]: {@code Usage: <commandName> [OPTIONS] [FILE...]}</li>
1449 * <li>[description]</li>
1450 * <li>[parameter list]: {@code [FILE...] Any number of input files}</li>
1451 * <li>[option list]: {@code -h, --help prints this help message and exits}</li>
1452 * <li>[footer]</li>
1453 * </ul> */
1454 @Retention(RetentionPolicy.RUNTIME)
1455 @Target({ElementType.TYPE, ElementType.LOCAL_VARIABLE, ElementType.PACKAGE})
1456 public @interface Command {
1457 /** Program name to show in the synopsis. If omitted, {@code "<main class>"} is used.
1458 * For {@linkplain #subcommands() declaratively added} subcommands, this attribute is also used
1459 * by the parser to recognize subcommands in the command line arguments.
1460 * @return the program name to show in the synopsis
1461 * @see Help#commandName */
1462 String name() default "<main class>";
1463
1464 /** A list of classes to instantiate and register as subcommands. When registering subcommands declaratively
1465 * like this, you don't need to call the {@link CommandLine#addSubcommand(String, Object)} method. For example, this:
1466 * <pre>
1467 * @Command(subcommands = {
1468 * GitStatus.class,
1469 * GitCommit.class,
1470 * GitBranch.class })
1471 * public class Git { ... }
1472 *
1473 * CommandLine commandLine = new CommandLine(new Git());
1474 * </pre> is equivalent to this:
1475 * <pre>
1476 * // alternative: programmatically add subcommands.
1477 * // NOTE: in this case there should be no `subcommands` attribute on the @Command annotation.
1478 * @Command public class Git { ... }
1479 *
1480 * CommandLine commandLine = new CommandLine(new Git())
1481 * .addSubcommand("status", new GitStatus())
1482 * .addSubcommand("commit", new GitCommit())
1483 * .addSubcommand("branch", new GitBranch());
1484 * </pre>
1485 * @return the declaratively registered subcommands of this command, or an empty array if none
1486 * @see CommandLine#addSubcommand(String, Object)
1487 * @since 0.9.8
1488 */
1489 Class<?>[] subcommands() default {};
1490
1491 /** String that separates options from option parameters. Default is {@code "="}. Spaces are also accepted.
1492 * @return the string that separates options from option parameters, used both when parsing and when generating usage help
1493 * @see Help#separator
1494 * @see CommandLine#setSeparator(String) */
1495 String separator() default "=";
1496
1497 /** Version information for this command, to print to the console when the user specifies an
1498 * {@linkplain Option#versionHelp() option} to request version help. This is not part of the usage help message.
1499 *
1500 * @return a string or an array of strings with version information about this command.
1501 * @since 0.9.8
1502 * @see CommandLine#printVersionHelp(PrintStream)
1503 */
1504 String[] version() default {};
1505
1506 /** Set the heading preceding the header section. May contain embedded {@linkplain java.util.Formatter format specifiers}.
1507 * @return the heading preceding the header section
1508 * @see Help#headerHeading(Object...) */
1509 String headerHeading() default "";
1510
1511 /** Optional summary description of the command, shown before the synopsis.
1512 * @return summary description of the command
1513 * @see Help#header
1514 * @see Help#header(Object...) */
1515 String[] header() default {};
1516
1517 /** Set the heading preceding the synopsis text. May contain embedded
1518 * {@linkplain java.util.Formatter format specifiers}. The default heading is {@code "Usage: "} (without a line
1519 * break between the heading and the synopsis text).
1520 * @return the heading preceding the synopsis text
1521 * @see Help#synopsisHeading(Object...) */
1522 String synopsisHeading() default "Usage: ";
1523
1524 /** Specify {@code true} to generate an abbreviated synopsis like {@code "<main> [OPTIONS] [PARAMETERS...]"}.
1525 * By default, a detailed synopsis with individual option names and parameters is generated.
1526 * @return whether the synopsis should be abbreviated
1527 * @see Help#abbreviateSynopsis
1528 * @see Help#abbreviatedSynopsis()
1529 * @see Help#detailedSynopsis(Comparator, boolean) */
1530 boolean abbreviateSynopsis() default false;
1531
1532 /** Specify one or more custom synopsis lines to display instead of an auto-generated synopsis.
1533 * @return custom synopsis text to replace the auto-generated synopsis
1534 * @see Help#customSynopsis
1535 * @see Help#customSynopsis(Object...) */
1536 String[] customSynopsis() default {};
1537
1538 /** Set the heading preceding the description section. May contain embedded {@linkplain java.util.Formatter format specifiers}.
1539 * @return the heading preceding the description section
1540 * @see Help#descriptionHeading(Object...) */
1541 String descriptionHeading() default "";
1542
1543 /** Optional text to display between the synopsis line(s) and the list of options.
1544 * @return description of this command
1545 * @see Help#description
1546 * @see Help#description(Object...) */
1547 String[] description() default {};
1548
1549 /** Set the heading preceding the parameters list. May contain embedded {@linkplain java.util.Formatter format specifiers}.
1550 * @return the heading preceding the parameters list
1551 * @see Help#parameterListHeading(Object...) */
1552 String parameterListHeading() default "";
1553
1554 /** Set the heading preceding the options list. May contain embedded {@linkplain java.util.Formatter format specifiers}.
1555 * @return the heading preceding the options list
1556 * @see Help#optionListHeading(Object...) */
1557 String optionListHeading() default "";
1558
1559 /** Specify {@code false} to show Options in declaration order. The default is to sort alphabetically.
1560 * @return whether options should be shown in alphabetic order.
1561 * @see Help#sortOptions */
1562 boolean sortOptions() default true;
1563
1564 /** Prefix required options with this character in the options list. The default is no marker: the synopsis
1565 * indicates which options and parameters are required.
1566 * @return the character to show in the options list to mark required options
1567 * @see Help#requiredOptionMarker */
1568 char requiredOptionMarker() default ' ';
1569
1570 /** Specify {@code true} to show default values in the description column of the options list (except for
1571 * boolean options). False by default.
1572 * @return whether the default values for options and parameters should be shown in the description column
1573 * @see Help#showDefaultValues */
1574 boolean showDefaultValues() default false;
1575
1576 /** Set the heading preceding the subcommands list. May contain embedded {@linkplain java.util.Formatter format specifiers}.
1577 * The default heading is {@code "Commands:%n"} (with a line break at the end).
1578 * @return the heading preceding the subcommands list
1579 * @see Help#commandListHeading(Object...) */
1580 String commandListHeading() default "Commands:%n";
1581
1582 /** Set the heading preceding the footer section. May contain embedded {@linkplain java.util.Formatter format specifiers}.
1583 * @return the heading preceding the footer section
1584 * @see Help#footerHeading(Object...) */
1585 String footerHeading() default "";
1586
1587 /** Optional text to display after the list of options.
1588 * @return text to display after the list of options
1589 * @see Help#footer
1590 * @see Help#footer(Object...) */
1591 String[] footer() default {};
1592 }
1593 /**
1594 * <p>
1595 * When parsing command line arguments and initializing
1596 * fields annotated with {@link Option @Option} or {@link Parameters @Parameters},
1597 * String values can be converted to any type for which a {@code ITypeConverter} is registered.
1598 * </p><p>
1599 * This interface defines the contract for classes that know how to convert a String into some domain object.
1600 * Custom converters can be registered with the {@link #registerConverter(Class, ITypeConverter)} method.
1601 * </p><p>
1602 * Java 8 lambdas make it easy to register custom type converters:
1603 * </p>
1604 * <pre>
1605 * commandLine.registerConverter(java.nio.file.Path.class, s -> java.nio.file.Paths.get(s));
1606 * commandLine.registerConverter(java.time.Duration.class, s -> java.time.Duration.parse(s));</pre>
1607 * <p>
1608 * Built-in type converters are pre-registered for the following java 1.5 types:
1609 * </p>
1610 * <ul>
1611 * <li>all primitive types</li>
1612 * <li>all primitive wrapper types: Boolean, Byte, Character, Double, Float, Integer, Long, Short</li>
1613 * <li>any enum</li>
1614 * <li>java.io.File</li>
1615 * <li>java.math.BigDecimal</li>
1616 * <li>java.math.BigInteger</li>
1617 * <li>java.net.InetAddress</li>
1618 * <li>java.net.URI</li>
1619 * <li>java.net.URL</li>
1620 * <li>java.nio.charset.Charset</li>
1621 * <li>java.sql.Time</li>
1622 * <li>java.util.Date</li>
1623 * <li>java.util.UUID</li>
1624 * <li>java.util.regex.Pattern</li>
1625 * <li>StringBuilder</li>
1626 * <li>CharSequence</li>
1627 * <li>String</li>
1628 * </ul>
1629 * @param <K> the type of the object that is the result of the conversion
1630 */
1631 public interface ITypeConverter<K> {
1632 /**
1633 * Converts the specified command line argument value to some domain object.
1634 * @param value the command line argument String value
1635 * @return the resulting domain object
1636 * @throws Exception an exception detailing what went wrong during the conversion
1637 */
1638 K convert(String value) throws Exception;
1639 }
1640 /** Describes the number of parameters required and accepted by an option or a positional parameter.
1641 * @since 0.9.7
1642 */
1643 public static class Range implements Comparable<Range> {
1644 /** Required number of parameters for an option or positional parameter. */
1645 public final int min;
1646 /** Maximum accepted number of parameters for an option or positional parameter. */
1647 public final int max;
1648 public final boolean isVariable;
1649 private final boolean isUnspecified;
1650 private final String originalValue;
1651
1652 /** Constructs a new Range object with the specified parameters.
1653 * @param min minimum number of required parameters
1654 * @param max maximum number of allowed parameters (or Integer.MAX_VALUE if variable)
1655 * @param variable {@code true} if any number or parameters is allowed, {@code false} otherwise
1656 * @param unspecified {@code true} if no arity was specified on the option/parameter (value is based on type)
1657 * @param originalValue the original value that was specified on the option or parameter
1658 */
1659 public Range(int min, int max, boolean variable, boolean unspecified, String originalValue) {
1660 this.min = min;
1661 this.max = max;
1662 this.isVariable = variable;
1663 this.isUnspecified = unspecified;
1664 this.originalValue = originalValue;
1665 }
1666 /** Returns a new {@code Range} based on the {@link Option#arity()} annotation on the specified field,
1667 * or the field type's default arity if no arity was specified.
1668 * @param field the field whose Option annotation to inspect
1669 * @return a new {@code Range} based on the Option arity annotation on the specified field */
1670 public static Range optionArity(Field field) {
1671 return field.isAnnotationPresent(Option.class)
1672 ? adjustForType(Range.valueOf(field.getAnnotation(Option.class).arity()), field)
1673 : new Range(0, 0, false, true, "0");
1674 }
1675 /** Returns a new {@code Range} based on the {@link Parameters#arity()} annotation on the specified field,
1676 * or the field type's default arity if no arity was specified.
1677 * @param field the field whose Parameters annotation to inspect
1678 * @return a new {@code Range} based on the Parameters arity annotation on the specified field */
1679 public static Range parameterArity(Field field) {
1680 return field.isAnnotationPresent(Parameters.class)
1681 ? adjustForType(Range.valueOf(field.getAnnotation(Parameters.class).arity()), field)
1682 : new Range(0, 0, false, true, "0");
1683 }
1684 /** Returns a new {@code Range} based on the {@link Parameters#index()} annotation on the specified field.
1685 * @param field the field whose Parameters annotation to inspect
1686 * @return a new {@code Range} based on the Parameters index annotation on the specified field */
1687 public static Range parameterIndex(Field field) {
1688 return field.isAnnotationPresent(Parameters.class)
1689 ? Range.valueOf(field.getAnnotation(Parameters.class).index())
1690 : new Range(0, 0, false, true, "0");
1691 }
1692 static Range adjustForType(Range result, Field field) {
1693 return result.isUnspecified ? defaultArity(field) : result;
1694 }
1695 /** Returns the default arity {@code Range}: for {@link Option options} this is 0 for booleans and 1 for
1696 * other types, for {@link Parameters parameters} booleans have arity 0, arrays or Collections have
1697 * arity "0..*", and other types have arity 1.
1698 * @param field the field whose default arity to return
1699 * @return a new {@code Range} indicating the default arity of the specified field
1700 * @since 2.0 */
1701 public static Range defaultArity(Field field) {
1702 Class<?> type = field.getType();
1703 if (field.isAnnotationPresent(Option.class)) {
1704 return defaultArity(type);
1705 }
1706 if (isMultiValue(type)) {
1707 return Range.valueOf("0..1");
1708 }
1709 return Range.valueOf("1");// for single-valued fields (incl. boolean positional parameters)
1710 }
1711 /** Returns the default arity {@code Range} for {@link Option options}: booleans have arity 0, other types have arity 1.
1712 * @param type the type whose default arity to return
1713 * @return a new {@code Range} indicating the default arity of the specified type */
1714 public static Range defaultArity(Class<?> type) {
1715 return isBoolean(type) ? Range.valueOf("0") : Range.valueOf("1");
1716 }
1717 private int size() { return 1 + max - min; }
1718 static Range parameterCapacity(Field field) {
1719 Range arity = parameterArity(field);
1720 if (!isMultiValue(field)) { return arity; }
1721 Range index = parameterIndex(field);
1722 if (arity.max == 0) { return arity; }
1723 if (index.size() == 1) { return arity; }
1724 if (index.isVariable) { return Range.valueOf(arity.min + "..*"); }
1725 if (arity.size() == 1) { return Range.valueOf(arity.min * index.size() + ""); }
1726 if (arity.isVariable) { return Range.valueOf(arity.min * index.size() + "..*"); }
1727 return Range.valueOf(arity.min * index.size() + ".." + arity.max * index.size());
1728 }
1729 /** Leniently parses the specified String as an {@code Range} value and return the result. A range string can
1730 * be a fixed integer value or a range of the form {@code MIN_VALUE + ".." + MAX_VALUE}. If the
1731 * {@code MIN_VALUE} string is not numeric, the minimum is zero. If the {@code MAX_VALUE} is not numeric, the
1732 * range is taken to be variable and the maximum is {@code Integer.MAX_VALUE}.
1733 * @param range the value range string to parse
1734 * @return a new {@code Range} value */
1735 public static Range valueOf(String range) {
1736 range = range.trim();
1737 boolean unspecified = range.length() == 0 || range.startsWith(".."); // || range.endsWith("..");
1738 int min = -1, max = -1;
1739 boolean variable = false;
1740 int dots = -1;
1741 if ((dots = range.indexOf("..")) >= 0) {
1742 min = parseInt(range.substring(0, dots), 0);
1743 max = parseInt(range.substring(dots + 2), Integer.MAX_VALUE);
1744 variable = max == Integer.MAX_VALUE;
1745 } else {
1746 max = parseInt(range, Integer.MAX_VALUE);
1747 variable = max == Integer.MAX_VALUE;
1748 min = variable ? 0 : max;
1749 }
1750 Range result = new Range(min, max, variable, unspecified, range);
1751 return result;
1752 }
1753 private static int parseInt(String str, int defaultValue) {
1754 try {
1755 return Integer.parseInt(str);
1756 } catch (Exception ex) {
1757 return defaultValue;
1758 }
1759 }
1760 /** Returns a new Range object with the {@code min} value replaced by the specified value.
1761 * The {@code max} of the returned Range is guaranteed not to be less than the new {@code min} value.
1762 * @param newMin the {@code min} value of the returned Range object
1763 * @return a new Range object with the specified {@code min} value */
1764 public Range min(int newMin) { return new Range(newMin, Math.max(newMin, max), isVariable, isUnspecified, originalValue); }
1765
1766 /** Returns a new Range object with the {@code max} value replaced by the specified value.
1767 * The {@code min} of the returned Range is guaranteed not to be greater than the new {@code max} value.
1768 * @param newMax the {@code max} value of the returned Range object
1769 * @return a new Range object with the specified {@code max} value */
1770 public Range max(int newMax) { return new Range(Math.min(min, newMax), newMax, isVariable, isUnspecified, originalValue); }
1771
1772 /**
1773 * Returns {@code true} if this Range includes the specified value, {@code false} otherwise.
1774 * @param value the value to check
1775 * @return {@code true} if the specified value is not less than the minimum and not greater than the maximum of this Range
1776 */
1777 public boolean contains(int value) { return min <= value && max >= value; }
1778
1779 @Override
1780 public boolean equals(Object object) {
1781 if (!(object instanceof Range)) { return false; }
1782 Range other = (Range) object;
1783 return other.max == this.max && other.min == this.min && other.isVariable == this.isVariable;
1784 }
1785 @Override
1786 public int hashCode() {
1787 return ((17 * 37 + max) * 37 + min) * 37 + (isVariable ? 1 : 0);
1788 }
1789 @Override
1790 public String toString() {
1791 return min == max ? String.valueOf(min) : min + ".." + (isVariable ? "*" : max);
1792 }
1793 @Override
1794 public int compareTo(Range other) {
1795 int result = min - other.min;
1796 return (result == 0) ? max - other.max : result;
1797 }
1798 }
1799 static void init(Class<?> cls,
1800 List<Field> requiredFields,
1801 Map<String, Field> optionName2Field,
1802 Map<Character, Field> singleCharOption2Field,
1803 List<Field> positionalParametersFields) {
1804 Field[] declaredFields = cls.getDeclaredFields();
1805 for (Field field : declaredFields) {
1806 field.setAccessible(true);
1807 if (field.isAnnotationPresent(Option.class)) {
1808 Option option = field.getAnnotation(Option.class);
1809 if (option.required()) {
1810 requiredFields.add(field);
1811 }
1812 for (String name : option.names()) { // cannot be null or empty
1813 Field existing = optionName2Field.put(name, field);
1814 if (existing != null && existing != field) {
1815 throw DuplicateOptionAnnotationsException.create(name, field, existing);
1816 }
1817 if (name.length() == 2 && name.startsWith("-")) {
1818 char flag = name.charAt(1);
1819 Field existing2 = singleCharOption2Field.put(flag, field);
1820 if (existing2 != null && existing2 != field) {
1821 throw DuplicateOptionAnnotationsException.create(name, field, existing2);
1822 }
1823 }
1824 }
1825 }
1826 if (field.isAnnotationPresent(Parameters.class)) {
1827 if (field.isAnnotationPresent(Option.class)) {
1828 throw new DuplicateOptionAnnotationsException("A field can be either @Option or @Parameters, but '"
1829 + field.getName() + "' is both.");
1830 }
1831 positionalParametersFields.add(field);
1832 Range arity = Range.parameterArity(field);
1833 if (arity.min > 0) {
1834 requiredFields.add(field);
1835 }
1836 }
1837 }
1838 }
1839 static void validatePositionalParameters(List<Field> positionalParametersFields) {
1840 int min = 0;
1841 for (Field field : positionalParametersFields) {
1842 Range index = Range.parameterIndex(field);
1843 if (index.min > min) {
1844 throw new ParameterIndexGapException("Missing field annotated with @Parameter(index=" + min +
1845 "). Nearest field '" + field.getName() + "' has index=" + index.min);
1846 }
1847 min = Math.max(min, index.max);
1848 min = min == Integer.MAX_VALUE ? min : min + 1;
1849 }
1850 }
1851 private static <T> Stack<T> reverse(Stack<T> stack) {
1852 Collections.reverse(stack);
1853 return stack;
1854 }
1855 /**
1856 * Helper class responsible for processing command line arguments.
1857 */
1858 private class Interpreter {
1859 private final Map<String, CommandLine> commands = new LinkedHashMap<String, CommandLine>();
1860 private final Map<Class<?>, ITypeConverter<?>> converterRegistry = new HashMap<Class<?>, ITypeConverter<?>>();
1861 private final Map<String, Field> optionName2Field = new HashMap<String, Field>();
1862 private final Map<Character, Field> singleCharOption2Field = new HashMap<Character, Field>();
1863 private final List<Field> requiredFields = new ArrayList<Field>();
1864 private final List<Field> positionalParametersFields = new ArrayList<Field>();
1865 private final Object command;
1866 private boolean isHelpRequested;
1867 private String separator = Help.DEFAULT_SEPARATOR;
1868 private int position;
1869
1870 Interpreter(Object command) {
1871 converterRegistry.put(Path.class, new BuiltIn.PathConverter());
1872 converterRegistry.put(Object.class, new BuiltIn.StringConverter());
1873 converterRegistry.put(String.class, new BuiltIn.StringConverter());
1874 converterRegistry.put(StringBuilder.class, new BuiltIn.StringBuilderConverter());
1875 converterRegistry.put(CharSequence.class, new BuiltIn.CharSequenceConverter());
1876 converterRegistry.put(Byte.class, new BuiltIn.ByteConverter());
1877 converterRegistry.put(Byte.TYPE, new BuiltIn.ByteConverter());
1878 converterRegistry.put(Boolean.class, new BuiltIn.BooleanConverter());
1879 converterRegistry.put(Boolean.TYPE, new BuiltIn.BooleanConverter());
1880 converterRegistry.put(Character.class, new BuiltIn.CharacterConverter());
1881 converterRegistry.put(Character.TYPE, new BuiltIn.CharacterConverter());
1882 converterRegistry.put(Short.class, new BuiltIn.ShortConverter());
1883 converterRegistry.put(Short.TYPE, new BuiltIn.ShortConverter());
1884 converterRegistry.put(Integer.class, new BuiltIn.IntegerConverter());
1885 converterRegistry.put(Integer.TYPE, new BuiltIn.IntegerConverter());
1886 converterRegistry.put(Long.class, new BuiltIn.LongConverter());
1887 converterRegistry.put(Long.TYPE, new BuiltIn.LongConverter());
1888 converterRegistry.put(Float.class, new BuiltIn.FloatConverter());
1889 converterRegistry.put(Float.TYPE, new BuiltIn.FloatConverter());
1890 converterRegistry.put(Double.class, new BuiltIn.DoubleConverter());
1891 converterRegistry.put(Double.TYPE, new BuiltIn.DoubleConverter());
1892 converterRegistry.put(File.class, new BuiltIn.FileConverter());
1893 converterRegistry.put(URI.class, new BuiltIn.URIConverter());
1894 converterRegistry.put(URL.class, new BuiltIn.URLConverter());
1895 converterRegistry.put(Date.class, new BuiltIn.ISO8601DateConverter());
1896 converterRegistry.put(Time.class, new BuiltIn.ISO8601TimeConverter());
1897 converterRegistry.put(BigDecimal.class, new BuiltIn.BigDecimalConverter());
1898 converterRegistry.put(BigInteger.class, new BuiltIn.BigIntegerConverter());
1899 converterRegistry.put(Charset.class, new BuiltIn.CharsetConverter());
1900 converterRegistry.put(InetAddress.class, new BuiltIn.InetAddressConverter());
1901 converterRegistry.put(Pattern.class, new BuiltIn.PatternConverter());
1902 converterRegistry.put(UUID.class, new BuiltIn.UUIDConverter());
1903
1904 this.command = Assert.notNull(command, "command");
1905 Class<?> cls = command.getClass();
1906 String declaredName = null;
1907 String declaredSeparator = null;
1908 boolean hasCommandAnnotation = false;
1909 while (cls != null) {
1910 init(cls, requiredFields, optionName2Field, singleCharOption2Field, positionalParametersFields);
1911 if (cls.isAnnotationPresent(Command.class)) {
1912 hasCommandAnnotation = true;
1913 Command cmd = cls.getAnnotation(Command.class);
1914 declaredSeparator = (declaredSeparator == null) ? cmd.separator() : declaredSeparator;
1915 declaredName = (declaredName == null) ? cmd.name() : declaredName;
1916 CommandLine.this.versionLines.addAll(Arrays.asList(cmd.version()));
1917
1918 for (Class<?> sub : cmd.subcommands()) {
1919 Command subCommand = sub.getAnnotation(Command.class);
1920 if (subCommand == null || Help.DEFAULT_COMMAND_NAME.equals(subCommand.name())) {
1921 throw new InitializationException("Subcommand " + sub.getName() +
1922 " is missing the mandatory @Command annotation with a 'name' attribute");
1923 }
1924 try {
1925 Constructor<?> constructor = sub.getDeclaredConstructor();
1926 constructor.setAccessible(true);
1927 CommandLine commandLine = toCommandLine(constructor.newInstance());
1928 commandLine.parent = CommandLine.this;
1929 commands.put(subCommand.name(), commandLine);
1930 }
1931 catch (InitializationException ex) { throw ex; }
1932 catch (NoSuchMethodException ex) { throw new InitializationException("Cannot instantiate subcommand " +
1933 sub.getName() + ": the class has no constructor", ex); }
1934 catch (Exception ex) {
1935 throw new InitializationException("Could not instantiate and add subcommand " +
1936 sub.getName() + ": " + ex, ex);
1937 }
1938 }
1939 }
1940 cls = cls.getSuperclass();
1941 }
1942 separator = declaredSeparator != null ? declaredSeparator : separator;
1943 CommandLine.this.commandName = declaredName != null ? declaredName : CommandLine.this.commandName;
1944 Collections.sort(positionalParametersFields, new PositionalParametersSorter());
1945 validatePositionalParameters(positionalParametersFields);
1946
1947 if (positionalParametersFields.isEmpty() && optionName2Field.isEmpty() && !hasCommandAnnotation) {
1948 throw new InitializationException(command + " (" + command.getClass() +
1949 ") is not a command: it has no @Command, @Option or @Parameters annotations");
1950 }
1951 }
1952
1953 /**
1954 * Entry point into parsing command line arguments.
1955 * @param args the command line arguments
1956 * @return a list with all commands and subcommands initialized by this method
1957 * @throws ParameterException if the specified command line arguments are invalid
1958 */
1959 List<CommandLine> parse(String... args) {
1960 Assert.notNull(args, "argument array");
1961 if (tracer.isInfo()) {tracer.info("Parsing %d command line args %s%n", args.length, Arrays.toString(args));}
1962 Stack<String> arguments = new Stack<String>();
1963 for (int i = args.length - 1; i >= 0; i--) {
1964 arguments.push(args[i]);
1965 }
1966 List<CommandLine> result = new ArrayList<CommandLine>();
1967 parse(result, arguments, args);
1968 return result;
1969 }
1970
1971 private void parse(List<CommandLine> parsedCommands, Stack<String> argumentStack, String[] originalArgs) {
1972 // first reset any state in case this CommandLine instance is being reused
1973 isHelpRequested = false;
1974 CommandLine.this.versionHelpRequested = false;
1975 CommandLine.this.usageHelpRequested = false;
1976
1977 Class<?> cmdClass = this.command.getClass();
1978 if (tracer.isDebug()) {tracer.debug("Initializing %s: %d options, %d positional parameters, %d required, %d subcommands.%n", cmdClass.getName(), new HashSet<Field>(optionName2Field.values()).size(), positionalParametersFields.size(), requiredFields.size(), commands.size());}
1979 parsedCommands.add(CommandLine.this);
1980 List<Field> required = new ArrayList<Field>(requiredFields);
1981 Set<Field> initialized = new HashSet<Field>();
1982 Collections.sort(required, new PositionalParametersSorter());
1983 try {
1984 processArguments(parsedCommands, argumentStack, required, initialized, originalArgs);
1985 } catch (ParameterException ex) {
1986 throw ex;
1987 } catch (Exception ex) {
1988 int offendingArgIndex = originalArgs.length - argumentStack.size() - 1;
1989 String arg = offendingArgIndex >= 0 && offendingArgIndex < originalArgs.length ? originalArgs[offendingArgIndex] : "?";
1990 throw ParameterException.create(CommandLine.this, ex, arg, offendingArgIndex, originalArgs);
1991 }
1992 if (!isAnyHelpRequested() && !required.isEmpty()) {
1993 for (Field missing : required) {
1994 if (missing.isAnnotationPresent(Option.class)) {
1995 throw MissingParameterException.create(CommandLine.this, required, separator);
1996 } else {
1997 assertNoMissingParameters(missing, Range.parameterArity(missing).min, argumentStack);
1998 }
1999 }
2000 }
2001 if (!unmatchedArguments.isEmpty()) {
2002 if (!isUnmatchedArgumentsAllowed()) { throw new UnmatchedArgumentException(CommandLine.this, unmatchedArguments); }
2003 if (tracer.isWarn()) { tracer.warn("Unmatched arguments: %s%n", unmatchedArguments); }
2004 }
2005 }
2006
2007 private void processArguments(List<CommandLine> parsedCommands,
2008 Stack<String> args,
2009 Collection<Field> required,
2010 Set<Field> initialized,
2011 String[] originalArgs) throws Exception {
2012 // arg must be one of:
2013 // 1. the "--" double dash separating options from positional arguments
2014 // 1. a stand-alone flag, like "-v" or "--verbose": no value required, must map to boolean or Boolean field
2015 // 2. a short option followed by an argument, like "-f file" or "-ffile": may map to any type of field
2016 // 3. a long option followed by an argument, like "-file out.txt" or "-file=out.txt"
2017 // 3. one or more remaining arguments without any associated options. Must be the last in the list.
2018 // 4. a combination of stand-alone options, like "-vxr". Equivalent to "-v -x -r", "-v true -x true -r true"
2019 // 5. a combination of stand-alone options and one option with an argument, like "-vxrffile"
2020
2021 while (!args.isEmpty()) {
2022 String arg = args.pop();
2023 if (tracer.isDebug()) {tracer.debug("Processing argument '%s'. Remainder=%s%n", arg, reverse((Stack<String>) args.clone()));}
2024
2025 // Double-dash separates options from positional arguments.
2026 // If found, then interpret the remaining args as positional parameters.
2027 if ("--".equals(arg)) {
2028 tracer.info("Found end-of-options delimiter '--'. Treating remainder as positional parameters.%n");
2029 processRemainderAsPositionalParameters(required, initialized, args);
2030 return; // we are done
2031 }
2032
2033 // if we find another command, we are done with the current command
2034 if (commands.containsKey(arg)) {
2035 if (!isHelpRequested && !required.isEmpty()) { // ensure current command portion is valid
2036 throw MissingParameterException.create(CommandLine.this, required, separator);
2037 }
2038 if (tracer.isDebug()) {tracer.debug("Found subcommand '%s' (%s)%n", arg, commands.get(arg).interpreter.command.getClass().getName());}
2039 commands.get(arg).interpreter.parse(parsedCommands, args, originalArgs);
2040 return; // remainder done by the command
2041 }
2042
2043 // First try to interpret the argument as a single option (as opposed to a compact group of options).
2044 // A single option may be without option parameters, like "-v" or "--verbose" (a boolean value),
2045 // or an option may have one or more option parameters.
2046 // A parameter may be attached to the option.
2047 boolean paramAttachedToOption = false;
2048 int separatorIndex = arg.indexOf(separator);
2049 if (separatorIndex > 0) {
2050 String key = arg.substring(0, separatorIndex);
2051 // be greedy. Consume the whole arg as an option if possible.
2052 if (optionName2Field.containsKey(key) && !optionName2Field.containsKey(arg)) {
2053 paramAttachedToOption = true;
2054 String optionParam = arg.substring(separatorIndex + separator.length());
2055 args.push(optionParam);
2056 arg = key;
2057 if (tracer.isDebug()) {tracer.debug("Separated '%s' option from '%s' option parameter%n", key, optionParam);}
2058 } else {
2059 if (tracer.isDebug()) {tracer.debug("'%s' contains separator '%s' but '%s' is not a known option%n", arg, separator, key);}
2060 }
2061 } else {
2062 if (tracer.isDebug()) {tracer.debug("'%s' cannot be separated into <option>%s<option-parameter>%n", arg, separator);}
2063 }
2064 if (optionName2Field.containsKey(arg)) {
2065 processStandaloneOption(required, initialized, arg, args, paramAttachedToOption);
2066 }
2067 // Compact (single-letter) options can be grouped with other options or with an argument.
2068 // only single-letter options can be combined with other options or with an argument
2069 else if (arg.length() > 2 && arg.startsWith("-")) {
2070 if (tracer.isDebug()) {tracer.debug("Trying to process '%s' as clustered short options%n", arg, args);}
2071 processClusteredShortOptions(required, initialized, arg, args);
2072 }
2073 // The argument could not be interpreted as an option.
2074 // We take this to mean that the remainder are positional arguments
2075 else {
2076 args.push(arg);
2077 if (tracer.isDebug()) {tracer.debug("Could not find option '%s', deciding whether to treat as unmatched option or positional parameter...%n", arg);}
2078 if (resemblesOption(arg)) { handleUnmatchedArguments(args.pop()); continue; } // #149
2079 if (tracer.isDebug()) {tracer.debug("No option named '%s' found. Processing remainder as positional parameters%n", arg);}
2080 processPositionalParameter(required, initialized, args);
2081 }
2082 }
2083 }
2084 private boolean resemblesOption(String arg) {
2085 int count = 0;
2086 for (String optionName : optionName2Field.keySet()) {
2087 for (int i = 0; i < arg.length(); i++) {
2088 if (optionName.length() > i && arg.charAt(i) == optionName.charAt(i)) { count++; } else { break; }
2089 }
2090 }
2091 boolean result = count > 0 && count * 10 >= optionName2Field.size() * 9; // at least one prefix char in common with 9 out of 10 options
2092 if (tracer.isDebug()) {tracer.debug("%s %s an option: %d matching prefix chars out of %d option names%n", arg, (result ? "resembles" : "doesn't resemble"), count, optionName2Field.size());}
2093 return result;
2094 }
2095 private void handleUnmatchedArguments(String arg) {Stack<String> args = new Stack<String>(); args.add(arg); handleUnmatchedArguments(args);}
2096 private void handleUnmatchedArguments(Stack<String> args) {
2097 while (!args.isEmpty()) { unmatchedArguments.add(args.pop()); } // addAll would give args in reverse order
2098 }
2099
2100 private void processRemainderAsPositionalParameters(Collection<Field> required, Set<Field> initialized, Stack<String> args) throws Exception {
2101 while (!args.empty()) {
2102 processPositionalParameter(required, initialized, args);
2103 }
2104 }
2105 private void processPositionalParameter(Collection<Field> required, Set<Field> initialized, Stack<String> args) throws Exception {
2106 if (tracer.isDebug()) {tracer.debug("Processing next arg as a positional parameter at index=%d. Remainder=%s%n", position, reverse((Stack<String>) args.clone()));}
2107 int consumed = 0;
2108 for (Field positionalParam : positionalParametersFields) {
2109 Range indexRange = Range.parameterIndex(positionalParam);
2110 if (!indexRange.contains(position)) {
2111 continue;
2112 }
2113 @SuppressWarnings("unchecked")
2114 Stack<String> argsCopy = (Stack<String>) args.clone();
2115 Range arity = Range.parameterArity(positionalParam);
2116 if (tracer.isDebug()) {tracer.debug("Position %d is in index range %s. Trying to assign args to %s, arity=%s%n", position, indexRange, positionalParam, arity);}
2117 assertNoMissingParameters(positionalParam, arity.min, argsCopy);
2118 int originalSize = argsCopy.size();
2119 applyOption(positionalParam, Parameters.class, arity, false, argsCopy, initialized, "args[" + indexRange + "] at position " + position);
2120 int count = originalSize - argsCopy.size();
2121 if (count > 0) { required.remove(positionalParam); }
2122 consumed = Math.max(consumed, count);
2123 }
2124 // remove processed args from the stack
2125 for (int i = 0; i < consumed; i++) { args.pop(); }
2126 position += consumed;
2127 if (tracer.isDebug()) {tracer.debug("Consumed %d arguments, moving position to index %d.%n", consumed, position);}
2128 if (consumed == 0 && !args.isEmpty()) {
2129 handleUnmatchedArguments(args.pop());
2130 }
2131 }
2132
2133 private void processStandaloneOption(Collection<Field> required,
2134 Set<Field> initialized,
2135 String arg,
2136 Stack<String> args,
2137 boolean paramAttachedToKey) throws Exception {
2138 Field field = optionName2Field.get(arg);
2139 required.remove(field);
2140 Range arity = Range.optionArity(field);
2141 if (paramAttachedToKey) {
2142 arity = arity.min(Math.max(1, arity.min)); // if key=value, minimum arity is at least 1
2143 }
2144 if (tracer.isDebug()) {tracer.debug("Found option named '%s': field %s, arity=%s%n", arg, field, arity);}
2145 applyOption(field, Option.class, arity, paramAttachedToKey, args, initialized, "option " + arg);
2146 }
2147
2148 private void processClusteredShortOptions(Collection<Field> required,
2149 Set<Field> initialized,
2150 String arg,
2151 Stack<String> args)
2152 throws Exception {
2153 String prefix = arg.substring(0, 1);
2154 String cluster = arg.substring(1);
2155 boolean paramAttachedToOption = true;
2156 do {
2157 if (cluster.length() > 0 && singleCharOption2Field.containsKey(cluster.charAt(0))) {
2158 Field field = singleCharOption2Field.get(cluster.charAt(0));
2159 Range arity = Range.optionArity(field);
2160 String argDescription = "option " + prefix + cluster.charAt(0);
2161 if (tracer.isDebug()) {tracer.debug("Found option '%s%s' in %s: field %s, arity=%s%n", prefix, cluster.charAt(0), arg, field, arity);}
2162 required.remove(field);
2163 cluster = cluster.length() > 0 ? cluster.substring(1) : "";
2164 paramAttachedToOption = cluster.length() > 0;
2165 if (cluster.startsWith(separator)) {// attached with separator, like -f=FILE or -v=true
2166 cluster = cluster.substring(separator.length());
2167 arity = arity.min(Math.max(1, arity.min)); // if key=value, minimum arity is at least 1
2168 }
2169 if (arity.min > 0 && !empty(cluster)) {
2170 if (tracer.isDebug()) {tracer.debug("Trying to process '%s' as option parameter%n", cluster);}
2171 }
2172 // arity may be >= 1, or
2173 // arity <= 0 && !cluster.startsWith(separator)
2174 // e.g., boolean @Option("-v", arity=0, varargs=true); arg "-rvTRUE", remainder cluster="TRUE"
2175 if (!empty(cluster)) {
2176 args.push(cluster); // interpret remainder as option parameter
2177 }
2178 int consumed = applyOption(field, Option.class, arity, paramAttachedToOption, args, initialized, argDescription);
2179 // only return if cluster (and maybe more) was consumed, otherwise continue do-while loop
2180 if (empty(cluster) || consumed > 0 || args.isEmpty()) {
2181 return;
2182 }
2183 cluster = args.pop();
2184 } else { // cluster is empty || cluster.charAt(0) is not a short option key
2185 if (cluster.length() == 0) { // we finished parsing a group of short options like -rxv
2186 return; // return normally and parse the next arg
2187 }
2188 // We get here when the remainder of the cluster group is neither an option,
2189 // nor a parameter that the last option could consume.
2190 if (arg.endsWith(cluster)) {
2191 args.push(paramAttachedToOption ? prefix + cluster : cluster);
2192 if (args.peek().equals(arg)) { // #149 be consistent between unmatched short and long options
2193 if (tracer.isDebug()) {tracer.debug("Could not match any short options in %s, deciding whether to treat as unmatched option or positional parameter...%n", arg);}
2194 if (resemblesOption(arg)) { handleUnmatchedArguments(args.pop()); return; } // #149
2195 processPositionalParameter(required, initialized, args);
2196 return;
2197 }
2198 // remainder was part of a clustered group that could not be completely parsed
2199 if (tracer.isDebug()) {tracer.debug("No option found for %s in %s%n", cluster, arg);}
2200 handleUnmatchedArguments(args.pop());
2201 } else {
2202 args.push(cluster);
2203 if (tracer.isDebug()) {tracer.debug("%s is not an option parameter for %s%n", cluster, arg);}
2204 processPositionalParameter(required, initialized, args);
2205 }
2206 return;
2207 }
2208 } while (true);
2209 }
2210
2211 private int applyOption(Field field,
2212 Class<?> annotation,
2213 Range arity,
2214 boolean valueAttachedToOption,
2215 Stack<String> args,
2216 Set<Field> initialized,
2217 String argDescription) throws Exception {
2218 updateHelpRequested(field);
2219 int length = args.size();
2220 assertNoMissingParameters(field, arity.min, args);
2221
2222 Class<?> cls = field.getType();
2223 if (cls.isArray()) {
2224 return applyValuesToArrayField(field, annotation, arity, args, cls, argDescription);
2225 }
2226 if (Collection.class.isAssignableFrom(cls)) {
2227 return applyValuesToCollectionField(field, annotation, arity, args, cls, argDescription);
2228 }
2229 if (Map.class.isAssignableFrom(cls)) {
2230 return applyValuesToMapField(field, annotation, arity, args, cls, argDescription);
2231 }
2232 cls = getTypeAttribute(field)[0]; // field may be interface/abstract type, use annotation to get concrete type
2233 return applyValueToSingleValuedField(field, arity, args, cls, initialized, argDescription);
2234 }
2235
2236 private int applyValueToSingleValuedField(Field field,
2237 Range arity,
2238 Stack<String> args,
2239 Class<?> cls,
2240 Set<Field> initialized,
2241 String argDescription) throws Exception {
2242 boolean noMoreValues = args.isEmpty();
2243 String value = args.isEmpty() ? null : trim(args.pop()); // unquote the value
2244 int result = arity.min; // the number or args we need to consume
2245
2246 // special logic for booleans: BooleanConverter accepts only "true" or "false".
2247 if ((cls == Boolean.class || cls == Boolean.TYPE) && arity.min <= 0) {
2248
2249 // boolean option with arity = 0..1 or 0..*: value MAY be a param
2250 if (arity.max > 0 && ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value))) {
2251 result = 1; // if it is a varargs we only consume 1 argument if it is a boolean value
2252 } else {
2253 if (value != null) {
2254 args.push(value); // we don't consume the value
2255 }
2256 Boolean currentValue = (Boolean) field.get(command);
2257 value = String.valueOf(currentValue == null ? true : !currentValue); // #147 toggle existing boolean value
2258 }
2259 }
2260 if (noMoreValues && value == null) {
2261 return 0;
2262 }
2263 ITypeConverter<?> converter = getTypeConverter(cls, field);
2264 Object newValue = tryConvert(field, -1, converter, value, cls);
2265 Object oldValue = field.get(command);
2266 TraceLevel level = TraceLevel.INFO;
2267 String traceMessage = "Setting %s field '%s.%s' to '%5$s' (was '%4$s') for %6$s%n";
2268 if (initialized != null) {
2269 if (initialized.contains(field)) {
2270 if (!isOverwrittenOptionsAllowed()) {
2271 throw new OverwrittenOptionException(CommandLine.this, optionDescription("", field, 0) + " should be specified only once");
2272 }
2273 level = TraceLevel.WARN;
2274 traceMessage = "Overwriting %s field '%s.%s' value '%s' with '%s' for %s%n";
2275 }
2276 initialized.add(field);
2277 }
2278 if (tracer.level.isEnabled(level)) { level.print(tracer, traceMessage, field.getType().getSimpleName(),
2279 field.getDeclaringClass().getSimpleName(), field.getName(), String.valueOf(oldValue), String.valueOf(newValue), argDescription);
2280 }
2281 field.set(command, newValue);
2282 return result;
2283 }
2284 private int applyValuesToMapField(Field field,
2285 Class<?> annotation,
2286 Range arity,
2287 Stack<String> args,
2288 Class<?> cls,
2289 String argDescription) throws Exception {
2290 Class<?>[] classes = getTypeAttribute(field);
2291 if (classes.length < 2) { throw new ParameterException(CommandLine.this, "Field " + field + " needs two types (one for the map key, one for the value) but only has " + classes.length + " types configured."); }
2292 ITypeConverter<?> keyConverter = getTypeConverter(classes[0], field);
2293 ITypeConverter<?> valueConverter = getTypeConverter(classes[1], field);
2294 Map<Object, Object> result = (Map<Object, Object>) field.get(command);
2295 if (result == null) {
2296 result = createMap(cls);
2297 field.set(command, result);
2298 }
2299 int originalSize = result.size();
2300 consumeMapArguments(field, arity, args, classes, keyConverter, valueConverter, result, argDescription);
2301 return result.size() - originalSize;
2302 }
2303
2304 private void consumeMapArguments(Field field,
2305 Range arity,
2306 Stack<String> args,
2307 Class<?>[] classes,
2308 ITypeConverter<?> keyConverter,
2309 ITypeConverter<?> valueConverter,
2310 Map<Object, Object> result,
2311 String argDescription) throws Exception {
2312 // first do the arity.min mandatory parameters
2313 for (int i = 0; i < arity.min; i++) {
2314 consumeOneMapArgument(field, arity, args, classes, keyConverter, valueConverter, result, i, argDescription);
2315 }
2316 // now process the varargs if any
2317 for (int i = arity.min; i < arity.max && !args.isEmpty(); i++) {
2318 if (!field.isAnnotationPresent(Parameters.class)) {
2319 if (commands.containsKey(args.peek()) || isOption(args.peek())) {
2320 return;
2321 }
2322 }
2323 consumeOneMapArgument(field, arity, args, classes, keyConverter, valueConverter, result, i, argDescription);
2324 }
2325 }
2326
2327 private void consumeOneMapArgument(Field field,
2328 Range arity,
2329 Stack<String> args,
2330 Class<?>[] classes,
2331 ITypeConverter<?> keyConverter, ITypeConverter<?> valueConverter,
2332 Map<Object, Object> result,
2333 int index,
2334 String argDescription) throws Exception {
2335 String[] values = split(trim(args.pop()), field);
2336 for (String value : values) {
2337 String[] keyValue = value.split("=");
2338 if (keyValue.length < 2) {
2339 String splitRegex = splitRegex(field);
2340 if (splitRegex.length() == 0) {
2341 throw new ParameterException(CommandLine.this, "Value for option " + optionDescription("", field,
2342 0) + " should be in KEY=VALUE format but was " + value);
2343 } else {
2344 throw new ParameterException(CommandLine.this, "Value for option " + optionDescription("", field,
2345 0) + " should be in KEY=VALUE[" + splitRegex + "KEY=VALUE]... format but was " + value);
2346 }
2347 }
2348 Object mapKey = tryConvert(field, index, keyConverter, keyValue[0], classes[0]);
2349 Object mapValue = tryConvert(field, index, valueConverter, keyValue[1], classes[1]);
2350 result.put(mapKey, mapValue);
2351 if (tracer.isInfo()) {tracer.info("Putting [%s : %s] in %s<%s, %s> field '%s.%s' for %s%n", String.valueOf(mapKey), String.valueOf(mapValue),
2352 result.getClass().getSimpleName(), classes[0].getSimpleName(), classes[1].getSimpleName(), field.getDeclaringClass().getSimpleName(), field.getName(), argDescription);}
2353 }
2354 }
2355
2356 private void checkMaxArityExceeded(Range arity, int remainder, Field field, String[] values) {
2357 if (values.length <= remainder) { return; }
2358 String desc = arity.max == remainder ? "" + remainder : arity + ", remainder=" + remainder;
2359 throw new MaxValuesforFieldExceededException(CommandLine.this, optionDescription("", field, -1) +
2360 " max number of values (" + arity.max + ") exceeded: remainder is " + remainder + " but " +
2361 values.length + " values were specified: " + Arrays.toString(values));
2362 }
2363
2364 private int applyValuesToArrayField(Field field,
2365 Class<?> annotation,
2366 Range arity,
2367 Stack<String> args,
2368 Class<?> cls,
2369 String argDescription) throws Exception {
2370 Object existing = field.get(command);
2371 int length = existing == null ? 0 : Array.getLength(existing);
2372 Class<?> type = getTypeAttribute(field)[0];
2373 List<Object> converted = consumeArguments(field, annotation, arity, args, type, length, argDescription);
2374 List<Object> newValues = new ArrayList<Object>();
2375 for (int i = 0; i < length; i++) {
2376 newValues.add(Array.get(existing, i));
2377 }
2378 for (Object obj : converted) {
2379 if (obj instanceof Collection<?>) {
2380 newValues.addAll((Collection<?>) obj);
2381 } else {
2382 newValues.add(obj);
2383 }
2384 }
2385 Object array = Array.newInstance(type, newValues.size());
2386 field.set(command, array);
2387 for (int i = 0; i < newValues.size(); i++) {
2388 Array.set(array, i, newValues.get(i));
2389 }
2390 return converted.size(); // return how many args were consumed
2391 }
2392
2393 @SuppressWarnings("unchecked")
2394 private int applyValuesToCollectionField(Field field,
2395 Class<?> annotation,
2396 Range arity,
2397 Stack<String> args,
2398 Class<?> cls,
2399 String argDescription) throws Exception {
2400 Collection<Object> collection = (Collection<Object>) field.get(command);
2401 Class<?> type = getTypeAttribute(field)[0];
2402 int length = collection == null ? 0 : collection.size();
2403 List<Object> converted = consumeArguments(field, annotation, arity, args, type, length, argDescription);
2404 if (collection == null) {
2405 collection = createCollection(cls);
2406 field.set(command, collection);
2407 }
2408 for (Object element : converted) {
2409 if (element instanceof Collection<?>) {
2410 collection.addAll((Collection<?>) element);
2411 } else {
2412 collection.add(element);
2413 }
2414 }
2415 return converted.size();
2416 }
2417
2418 private List<Object> consumeArguments(Field field,
2419 Class<?> annotation,
2420 Range arity,
2421 Stack<String> args,
2422 Class<?> type,
2423 int originalSize,
2424 String argDescription) throws Exception {
2425 List<Object> result = new ArrayList<Object>();
2426
2427 // first do the arity.min mandatory parameters
2428 for (int i = 0; i < arity.min; i++) {
2429 consumeOneArgument(field, arity, args, type, result, i, originalSize, argDescription);
2430 }
2431 // now process the varargs if any
2432 for (int i = arity.min; i < arity.max && !args.isEmpty(); i++) {
2433 if (annotation != Parameters.class) { // for vararg Options, we stop if we encounter '--', a command, or another option
2434 if (commands.containsKey(args.peek()) || isOption(args.peek())) {
2435 return result;
2436 }
2437 }
2438 consumeOneArgument(field, arity, args, type, result, i, originalSize, argDescription);
2439 }
2440 return result;
2441 }
2442
2443 private int consumeOneArgument(Field field,
2444 Range arity,
2445 Stack<String> args,
2446 Class<?> type,
2447 List<Object> result,
2448 int index,
2449 int originalSize,
2450 String argDescription) throws Exception {
2451 String[] values = split(trim(args.pop()), field);
2452 ITypeConverter<?> converter = getTypeConverter(type, field);
2453
2454 for (int j = 0; j < values.length; j++) {
2455 result.add(tryConvert(field, index, converter, values[j], type));
2456 if (tracer.isInfo()) {
2457 if (field.getType().isArray()) {
2458 tracer.info("Adding [%s] to %s[] field '%s.%s' for %s%n", String.valueOf(result.get(result.size() - 1)), type.getSimpleName(), field.getDeclaringClass().getSimpleName(), field.getName(), argDescription);
2459 } else {
2460 tracer.info("Adding [%s] to %s<%s> field '%s.%s' for %s%n", String.valueOf(result.get(result.size() - 1)), field.getType().getSimpleName(), type.getSimpleName(), field.getDeclaringClass().getSimpleName(), field.getName(), argDescription);
2461 }
2462 }
2463 }
2464 //checkMaxArityExceeded(arity, max, field, values);
2465 return ++index;
2466 }
2467
2468 private String splitRegex(Field field) {
2469 if (field.isAnnotationPresent(Option.class)) { return field.getAnnotation(Option.class).split(); }
2470 if (field.isAnnotationPresent(Parameters.class)) { return field.getAnnotation(Parameters.class).split(); }
2471 return "";
2472 }
2473 private String[] split(String value, Field field) {
2474 String regex = splitRegex(field);
2475 return regex.length() == 0 ? new String[] {value} : value.split(regex);
2476 }
2477
2478 /**
2479 * Called when parsing varargs parameters for a multi-value option.
2480 * When an option is encountered, the remainder should not be interpreted as vararg elements.
2481 * @param arg the string to determine whether it is an option or not
2482 * @return true if it is an option, false otherwise
2483 */
2484 private boolean isOption(String arg) {
2485 if ("--".equals(arg)) {
2486 return true;
2487 }
2488 // not just arg prefix: we may be in the middle of parsing -xrvfFILE
2489 if (optionName2Field.containsKey(arg)) { // -v or -f or --file (not attached to param or other option)
2490 return true;
2491 }
2492 int separatorIndex = arg.indexOf(separator);
2493 if (separatorIndex > 0) { // -f=FILE or --file==FILE (attached to param via separator)
2494 if (optionName2Field.containsKey(arg.substring(0, separatorIndex))) {
2495 return true;
2496 }
2497 }
2498 return (arg.length() > 2 && arg.startsWith("-") && singleCharOption2Field.containsKey(arg.charAt(1)));
2499 }
2500 private Object tryConvert(Field field, int index, ITypeConverter<?> converter, String value, Class<?> type)
2501 throws Exception {
2502 try {
2503 return converter.convert(value);
2504 } catch (TypeConversionException ex) {
2505 throw new ParameterException(CommandLine.this, ex.getMessage() + optionDescription(" for ", field, index));
2506 } catch (Exception other) {
2507 String desc = optionDescription(" for ", field, index) + ": " + other;
2508 throw new ParameterException(CommandLine.this, "Could not convert '" + value + "' to " + type.getSimpleName() + desc, other);
2509 }
2510 }
2511
2512 private String optionDescription(String prefix, Field field, int index) {
2513 Help.IParamLabelRenderer labelRenderer = Help.createMinimalParamLabelRenderer();
2514 String desc = "";
2515 if (field.isAnnotationPresent(Option.class)) {
2516 desc = prefix + "option '" + field.getAnnotation(Option.class).names()[0] + "'";
2517 if (index >= 0) {
2518 Range arity = Range.optionArity(field);
2519 if (arity.max > 1) {
2520 desc += " at index " + index;
2521 }
2522 desc += " (" + labelRenderer.renderParameterLabel(field, Help.Ansi.OFF, Collections.<IStyle>emptyList()) + ")";
2523 }
2524 } else if (field.isAnnotationPresent(Parameters.class)) {
2525 Range indexRange = Range.parameterIndex(field);
2526 Text label = labelRenderer.renderParameterLabel(field, Help.Ansi.OFF, Collections.<IStyle>emptyList());
2527 desc = prefix + "positional parameter at index " + indexRange + " (" + label + ")";
2528 }
2529 return desc;
2530 }
2531
2532 private boolean isAnyHelpRequested() { return isHelpRequested || versionHelpRequested || usageHelpRequested; }
2533
2534 private void updateHelpRequested(Field field) {
2535 if (field.isAnnotationPresent(Option.class)) {
2536 isHelpRequested |= is(field, "help", field.getAnnotation(Option.class).help());
2537 CommandLine.this.versionHelpRequested |= is(field, "versionHelp", field.getAnnotation(Option.class).versionHelp());
2538 CommandLine.this.usageHelpRequested |= is(field, "usageHelp", field.getAnnotation(Option.class).usageHelp());
2539 }
2540 }
2541 private boolean is(Field f, String description, boolean value) {
2542 if (value) { if (tracer.isInfo()) {tracer.info("Field '%s.%s' has '%s' annotation: not validating required fields%n", f.getDeclaringClass().getSimpleName(), f.getName(), description); }}
2543 return value;
2544 }
2545 @SuppressWarnings("unchecked")
2546 private Collection<Object> createCollection(Class<?> collectionClass) throws Exception {
2547 if (collectionClass.isInterface()) {
2548 if (List.class.isAssignableFrom(collectionClass)) {
2549 return new ArrayList<Object>();
2550 } else if (SortedSet.class.isAssignableFrom(collectionClass)) {
2551 return new TreeSet<Object>();
2552 } else if (Set.class.isAssignableFrom(collectionClass)) {
2553 return new LinkedHashSet<Object>();
2554 } else if (Queue.class.isAssignableFrom(collectionClass)) {
2555 return new LinkedList<Object>(); // ArrayDeque is only available since 1.6
2556 }
2557 return new ArrayList<Object>();
2558 }
2559 // custom Collection implementation class must have default constructor
2560 return (Collection<Object>) collectionClass.newInstance();
2561 }
2562 private Map<Object, Object> createMap(Class<?> mapClass) throws Exception {
2563 try { // if it is an implementation class, instantiate it
2564 return (Map<Object, Object>) mapClass.newInstance();
2565 } catch (Exception ignored) {}
2566 return new LinkedHashMap<Object, Object>();
2567 }
2568 private ITypeConverter<?> getTypeConverter(final Class<?> type, Field field) {
2569 ITypeConverter<?> result = converterRegistry.get(type);
2570 if (result != null) {
2571 return result;
2572 }
2573 if (type.isEnum()) {
2574 return new ITypeConverter<Object>() {
2575 @Override
2576 @SuppressWarnings("unchecked")
2577 public Object convert(String value) throws Exception {
2578 return Enum.valueOf((Class<Enum>) type, value);
2579 }
2580 };
2581 }
2582 throw new MissingTypeConverterException(CommandLine.this, "No TypeConverter registered for " + type.getName() + " of field " + field);
2583 }
2584
2585 private void assertNoMissingParameters(Field field, int arity, Stack<String> args) {
2586 if (arity > args.size()) {
2587 if (arity == 1) {
2588 if (field.isAnnotationPresent(Option.class)) {
2589 throw new MissingParameterException(CommandLine.this, "Missing required parameter for " +
2590 optionDescription("", field, 0));
2591 }
2592 Range indexRange = Range.parameterIndex(field);
2593 Help.IParamLabelRenderer labelRenderer = Help.createMinimalParamLabelRenderer();
2594 String sep = "";
2595 String names = "";
2596 int count = 0;
2597 for (int i = indexRange.min; i < positionalParametersFields.size(); i++) {
2598 if (Range.parameterArity(positionalParametersFields.get(i)).min > 0) {
2599 names += sep + labelRenderer.renderParameterLabel(positionalParametersFields.get(i),
2600 Help.Ansi.OFF, Collections.<IStyle>emptyList());
2601 sep = ", ";
2602 count++;
2603 }
2604 }
2605 String msg = "Missing required parameter";
2606 Range paramArity = Range.parameterArity(field);
2607 if (paramArity.isVariable) {
2608 msg += "s at positions " + indexRange + ": ";
2609 } else {
2610 msg += (count > 1 ? "s: " : ": ");
2611 }
2612 throw new MissingParameterException(CommandLine.this, msg + names);
2613 }
2614 if (args.isEmpty()) {
2615 throw new MissingParameterException(CommandLine.this, optionDescription("", field, 0) +
2616 " requires at least " + arity + " values, but none were specified.");
2617 }
2618 throw new MissingParameterException(CommandLine.this, optionDescription("", field, 0) +
2619 " requires at least " + arity + " values, but only " + args.size() + " were specified: " + reverse(args));
2620 }
2621 }
2622 private String trim(String value) {
2623 return unquote(value);
2624 }
2625
2626 private String unquote(String value) {
2627 return value == null
2628 ? null
2629 : (value.length() > 1 && value.startsWith("\"") && value.endsWith("\""))
2630 ? value.substring(1, value.length() - 1)
2631 : value;
2632 }
2633 }
2634 private static class PositionalParametersSorter implements Comparator<Field> {
2635 @Override
2636 public int compare(Field o1, Field o2) {
2637 int result = Range.parameterIndex(o1).compareTo(Range.parameterIndex(o2));
2638 return (result == 0) ? Range.parameterArity(o1).compareTo(Range.parameterArity(o2)) : result;
2639 }
2640 }
2641 /**
2642 * Inner class to group the built-in {@link ITypeConverter} implementations.
2643 */
2644 private static class BuiltIn {
2645 static class PathConverter implements ITypeConverter<Path> {
2646 @Override public Path convert(final String value) { return Paths.get(value); }
2647 }
2648 static class StringConverter implements ITypeConverter<String> {
2649 @Override
2650 public String convert(String value) { return value; }
2651 }
2652 static class StringBuilderConverter implements ITypeConverter<StringBuilder> {
2653 @Override
2654 public StringBuilder convert(String value) { return new StringBuilder(value); }
2655 }
2656 static class CharSequenceConverter implements ITypeConverter<CharSequence> {
2657 @Override
2658 public String convert(String value) { return value; }
2659 }
2660 /** Converts text to a {@code Byte} by delegating to {@link Byte#valueOf(String)}.*/
2661 static class ByteConverter implements ITypeConverter<Byte> {
2662 @Override
2663 public Byte convert(String value) { return Byte.valueOf(value); }
2664 }
2665 /** Converts {@code "true"} or {@code "false"} to a {@code Boolean}. Other values result in a ParameterException.*/
2666 static class BooleanConverter implements ITypeConverter<Boolean> {
2667 @Override
2668 public Boolean convert(String value) {
2669 if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) {
2670 return Boolean.parseBoolean(value);
2671 } else {
2672 throw new TypeConversionException("'" + value + "' is not a boolean");
2673 }
2674 }
2675 }
2676 static class CharacterConverter implements ITypeConverter<Character> {
2677 @Override
2678 public Character convert(String value) {
2679 if (value.length() > 1) {
2680 throw new TypeConversionException("'" + value + "' is not a single character");
2681 }
2682 return value.charAt(0);
2683 }
2684 }
2685 /** Converts text to a {@code Short} by delegating to {@link Short#valueOf(String)}.*/
2686 static class ShortConverter implements ITypeConverter<Short> {
2687 @Override
2688 public Short convert(String value) { return Short.valueOf(value); }
2689 }
2690 /** Converts text to an {@code Integer} by delegating to {@link Integer#valueOf(String)}.*/
2691 static class IntegerConverter implements ITypeConverter<Integer> {
2692 @Override
2693 public Integer convert(String value) { return Integer.valueOf(value); }
2694 }
2695 /** Converts text to a {@code Long} by delegating to {@link Long#valueOf(String)}.*/
2696 static class LongConverter implements ITypeConverter<Long> {
2697 @Override
2698 public Long convert(String value) { return Long.valueOf(value); }
2699 }
2700 static class FloatConverter implements ITypeConverter<Float> {
2701 @Override
2702 public Float convert(String value) { return Float.valueOf(value); }
2703 }
2704 static class DoubleConverter implements ITypeConverter<Double> {
2705 @Override
2706 public Double convert(String value) { return Double.valueOf(value); }
2707 }
2708 static class FileConverter implements ITypeConverter<File> {
2709 @Override
2710 public File convert(String value) { return new File(value); }
2711 }
2712 static class URLConverter implements ITypeConverter<URL> {
2713 @Override
2714 public URL convert(String value) throws MalformedURLException { return new URL(value); }
2715 }
2716 static class URIConverter implements ITypeConverter<URI> {
2717 @Override
2718 public URI convert(String value) throws URISyntaxException { return new URI(value); }
2719 }
2720 /** Converts text in {@code yyyy-mm-dd} format to a {@code java.util.Date}. ParameterException on failure. */
2721 static class ISO8601DateConverter implements ITypeConverter<Date> {
2722 @Override
2723 public Date convert(String value) {
2724 try {
2725 return new SimpleDateFormat("yyyy-MM-dd").parse(value);
2726 } catch (ParseException e) {
2727 throw new TypeConversionException("'" + value + "' is not a yyyy-MM-dd date");
2728 }
2729 }
2730 }
2731 /** Converts text in any of the following formats to a {@code java.sql.Time}: {@code HH:mm}, {@code HH:mm:ss},
2732 * {@code HH:mm:ss.SSS}, {@code HH:mm:ss,SSS}. Other formats result in a ParameterException. */
2733 static class ISO8601TimeConverter implements ITypeConverter<Time> {
2734 @Override
2735 public Time convert(String value) {
2736 try {
2737 if (value.length() <= 5) {
2738 return new Time(new SimpleDateFormat("HH:mm").parse(value).getTime());
2739 } else if (value.length() <= 8) {
2740 return new Time(new SimpleDateFormat("HH:mm:ss").parse(value).getTime());
2741 } else if (value.length() <= 12) {
2742 try {
2743 return new Time(new SimpleDateFormat("HH:mm:ss.SSS").parse(value).getTime());
2744 } catch (ParseException e2) {
2745 return new Time(new SimpleDateFormat("HH:mm:ss,SSS").parse(value).getTime());
2746 }
2747 }
2748 } catch (ParseException ignored) {
2749 // ignored because we throw a ParameterException below
2750 }
2751 throw new TypeConversionException("'" + value + "' is not a HH:mm[:ss[.SSS]] time");
2752 }
2753 }
2754 static class BigDecimalConverter implements ITypeConverter<BigDecimal> {
2755 @Override
2756 public BigDecimal convert(String value) { return new BigDecimal(value); }
2757 }
2758 static class BigIntegerConverter implements ITypeConverter<BigInteger> {
2759 @Override
2760 public BigInteger convert(String value) { return new BigInteger(value); }
2761 }
2762 static class CharsetConverter implements ITypeConverter<Charset> {
2763 @Override
2764 public Charset convert(String s) { return Charset.forName(s); }
2765 }
2766 /** Converts text to a {@code InetAddress} by delegating to {@link InetAddress#getByName(String)}. */
2767 static class InetAddressConverter implements ITypeConverter<InetAddress> {
2768 @Override
2769 public InetAddress convert(String s) throws Exception { return InetAddress.getByName(s); }
2770 }
2771 static class PatternConverter implements ITypeConverter<Pattern> {
2772 @Override
2773 public Pattern convert(String s) { return Pattern.compile(s); }
2774 }
2775 static class UUIDConverter implements ITypeConverter<UUID> {
2776 @Override
2777 public UUID convert(String s) throws Exception { return UUID.fromString(s); }
2778 }
2779 private BuiltIn() {} // private constructor: never instantiate
2780 }
2781
2782 /**
2783 * A collection of methods and inner classes that provide fine-grained control over the contents and layout of
2784 * the usage help message to display to end users when help is requested or invalid input values were specified.
2785 * <h3>Layered API</h3>
2786 * <p>The {@link Command} annotation provides the easiest way to customize usage help messages. See
2787 * the <a href="https://remkop.github.io/picocli/index.html#_usage_help">Manual</a> for details.</p>
2788 * <p>This Help class provides high-level functions to create sections of the usage help message and headings
2789 * for these sections. Instead of calling the {@link CommandLine#usage(PrintStream, CommandLine.Help.ColorScheme)}
2790 * method, application authors may want to create a custom usage help message by reorganizing sections in a
2791 * different order and/or adding custom sections.</p>
2792 * <p>Finally, the Help class contains inner classes and interfaces that can be used to create custom help messages.</p>
2793 * <h4>IOptionRenderer and IParameterRenderer</h4>
2794 * <p>Renders a field annotated with {@link Option} or {@link Parameters} to an array of {@link Text} values.
2795 * By default, these values are</p><ul>
2796 * <li>mandatory marker character (if the option/parameter is {@link Option#required() required})</li>
2797 * <li>short option name (empty for parameters)</li>
2798 * <li>comma or empty (empty for parameters)</li>
2799 * <li>long option names (the parameter {@link IParamLabelRenderer label} for parameters)</li>
2800 * <li>description</li>
2801 * </ul>
2802 * <p>Other components rely on this ordering.</p>
2803 * <h4>Layout</h4>
2804 * <p>Delegates to the renderers to create {@link Text} values for the annotated fields, and uses a
2805 * {@link TextTable} to display these values in tabular format. Layout is responsible for deciding which values
2806 * to display where in the table. By default, Layout shows one option or parameter per table row.</p>
2807 * <h4>TextTable</h4>
2808 * <p>Responsible for spacing out {@link Text} values according to the {@link Column} definitions the table was
2809 * created with. Columns have a width, indentation, and an overflow policy that decides what to do if a value is
2810 * longer than the column's width.</p>
2811 * <h4>Text</h4>
2812 * <p>Encapsulates rich text with styles and colors in a way that other components like {@link TextTable} are
2813 * unaware of the embedded ANSI escape codes.</p>
2814 */
2815 public static class Help {
2816 /** Constant String holding the default program name: {@value} */
2817 protected static final String DEFAULT_COMMAND_NAME = "<main class>";
2818
2819 /** Constant String holding the default string that separates options from option parameters: {@value} */
2820 protected static final String DEFAULT_SEPARATOR = "=";
2821
2822 private final static int usageHelpWidth = 80;
2823 private final static int optionsColumnWidth = 2 + 2 + 1 + 24;
2824 private final Object command;
2825 private final Map<String, Help> commands = new LinkedHashMap<String, Help>();
2826 final ColorScheme colorScheme;
2827
2828 /** Immutable list of fields annotated with {@link Option}, in declaration order. */
2829 public final List<Field> optionFields;
2830
2831 /** Immutable list of fields annotated with {@link Parameters}, or an empty list if no such field exists. */
2832 public final List<Field> positionalParametersFields;
2833
2834 /** The String to use as the separator between options and option parameters. {@code "="} by default,
2835 * initialized from {@link Command#separator()} if defined.
2836 * @see #parameterLabelRenderer */
2837 public String separator;
2838
2839 /** The String to use as the program name in the synopsis line of the help message.
2840 * {@link #DEFAULT_COMMAND_NAME} by default, initialized from {@link Command#name()} if defined. */
2841 public String commandName = DEFAULT_COMMAND_NAME;
2842
2843 /** Optional text lines to use as the description of the help message, displayed between the synopsis and the
2844 * options list. Initialized from {@link Command#description()} if the {@code Command} annotation is present,
2845 * otherwise this is an empty array and the help message has no description.
2846 * Applications may programmatically set this field to create a custom help message. */
2847 public String[] description = {};
2848
2849 /** Optional custom synopsis lines to use instead of the auto-generated synopsis.
2850 * Initialized from {@link Command#customSynopsis()} if the {@code Command} annotation is present,
2851 * otherwise this is an empty array and the synopsis is generated.
2852 * Applications may programmatically set this field to create a custom help message. */
2853 public String[] customSynopsis = {};
2854
2855 /** Optional header lines displayed at the top of the help message. For subcommands, the first header line is
2856 * displayed in the list of commands. Values are initialized from {@link Command#header()}
2857 * if the {@code Command} annotation is present, otherwise this is an empty array and the help message has no
2858 * header. Applications may programmatically set this field to create a custom help message. */
2859 public String[] header = {};
2860
2861 /** Optional footer text lines displayed at the bottom of the help message. Initialized from
2862 * {@link Command#footer()} if the {@code Command} annotation is present, otherwise this is an empty array and
2863 * the help message has no footer.
2864 * Applications may programmatically set this field to create a custom help message. */
2865 public String[] footer = {};
2866
2867 /** Option and positional parameter value label renderer used for the synopsis line(s) and the option list.
2868 * By default initialized to the result of {@link #createDefaultParamLabelRenderer()}, which takes a snapshot
2869 * of the {@link #separator} at construction time. If the separator is modified after Help construction, you
2870 * may need to re-initialize this field by calling {@link #createDefaultParamLabelRenderer()} again. */
2871 public IParamLabelRenderer parameterLabelRenderer;
2872
2873 /** If {@code true}, the synopsis line(s) will show an abbreviated synopsis without detailed option names. */
2874 public Boolean abbreviateSynopsis;
2875
2876 /** If {@code true}, the options list is sorted alphabetically. */
2877 public Boolean sortOptions;
2878
2879 /** If {@code true}, the options list will show default values for all options except booleans. */
2880 public Boolean showDefaultValues;
2881
2882 /** Character used to prefix required options in the options list. */
2883 public Character requiredOptionMarker;
2884
2885 /** Optional heading preceding the header section. Initialized from {@link Command#headerHeading()}, or null. */
2886 public String headerHeading;
2887
2888 /** Optional heading preceding the synopsis. Initialized from {@link Command#synopsisHeading()}, {@code "Usage: "} by default. */
2889 public String synopsisHeading;
2890
2891 /** Optional heading preceding the description section. Initialized from {@link Command#descriptionHeading()}, or null. */
2892 public String descriptionHeading;
2893
2894 /** Optional heading preceding the parameter list. Initialized from {@link Command#parameterListHeading()}, or null. */
2895 public String parameterListHeading;
2896
2897 /** Optional heading preceding the options list. Initialized from {@link Command#optionListHeading()}, or null. */
2898 public String optionListHeading;
2899
2900 /** Optional heading preceding the subcommand list. Initialized from {@link Command#commandListHeading()}. {@code "Commands:%n"} by default. */
2901 public String commandListHeading;
2902
2903 /** Optional heading preceding the footer section. Initialized from {@link Command#footerHeading()}, or null. */
2904 public String footerHeading;
2905
2906 /** Constructs a new {@code Help} instance with a default color scheme, initialized from annotatations
2907 * on the specified class and superclasses.
2908 * @param command the annotated object to create usage help for */
2909 public Help(Object command) {
2910 this(command, Ansi.AUTO);
2911 }
2912
2913 /** Constructs a new {@code Help} instance with a default color scheme, initialized from annotatations
2914 * on the specified class and superclasses.
2915 * @param command the annotated object to create usage help for
2916 * @param ansi whether to emit ANSI escape codes or not */
2917 public Help(Object command, Ansi ansi) {
2918 this(command, defaultColorScheme(ansi));
2919 }
2920
2921 /** Constructs a new {@code Help} instance with the specified color scheme, initialized from annotatations
2922 * on the specified class and superclasses.
2923 * @param command the annotated object to create usage help for
2924 * @param colorScheme the color scheme to use */
2925 public Help(Object command, ColorScheme colorScheme) {
2926 this.command = Assert.notNull(command, "command");
2927 this.colorScheme = Assert.notNull(colorScheme, "colorScheme").applySystemProperties();
2928 List<Field> options = new ArrayList<Field>();
2929 List<Field> operands = new ArrayList<Field>();
2930 Class<?> cls = command.getClass();
2931 while (cls != null) {
2932 for (Field field : cls.getDeclaredFields()) {
2933 field.setAccessible(true);
2934 if (field.isAnnotationPresent(Option.class)) {
2935 Option option = field.getAnnotation(Option.class);
2936 if (!option.hidden()) { // hidden options should not appear in usage help
2937 // TODO remember longest concatenated option string length (issue #45)
2938 options.add(field);
2939 }
2940 }
2941 if (field.isAnnotationPresent(Parameters.class)) {
2942 operands.add(field);
2943 }
2944 }
2945 // superclass values should not overwrite values if both class and superclass have a @Command annotation
2946 if (cls.isAnnotationPresent(Command.class)) {
2947 Command cmd = cls.getAnnotation(Command.class);
2948 if (DEFAULT_COMMAND_NAME.equals(commandName)) {
2949 commandName = cmd.name();
2950 }
2951 separator = (separator == null) ? cmd.separator() : separator;
2952 abbreviateSynopsis = (abbreviateSynopsis == null) ? cmd.abbreviateSynopsis() : abbreviateSynopsis;
2953 sortOptions = (sortOptions == null) ? cmd.sortOptions() : sortOptions;
2954 requiredOptionMarker = (requiredOptionMarker == null) ? cmd.requiredOptionMarker() : requiredOptionMarker;
2955 showDefaultValues = (showDefaultValues == null) ? cmd.showDefaultValues() : showDefaultValues;
2956 customSynopsis = empty(customSynopsis) ? cmd.customSynopsis() : customSynopsis;
2957 description = empty(description) ? cmd.description() : description;
2958 header = empty(header) ? cmd.header() : header;
2959 footer = empty(footer) ? cmd.footer() : footer;
2960 headerHeading = empty(headerHeading) ? cmd.headerHeading() : headerHeading;
2961 synopsisHeading = empty(synopsisHeading) || "Usage: ".equals(synopsisHeading) ? cmd.synopsisHeading() : synopsisHeading;
2962 descriptionHeading = empty(descriptionHeading) ? cmd.descriptionHeading() : descriptionHeading;
2963 parameterListHeading = empty(parameterListHeading) ? cmd.parameterListHeading() : parameterListHeading;
2964 optionListHeading = empty(optionListHeading) ? cmd.optionListHeading() : optionListHeading;
2965 commandListHeading = empty(commandListHeading) || "Commands:%n".equals(commandListHeading) ? cmd.commandListHeading() : commandListHeading;
2966 footerHeading = empty(footerHeading) ? cmd.footerHeading() : footerHeading;
2967 }
2968 cls = cls.getSuperclass();
2969 }
2970 sortOptions = (sortOptions == null) ? true : sortOptions;
2971 abbreviateSynopsis = (abbreviateSynopsis == null) ? false : abbreviateSynopsis;
2972 requiredOptionMarker = (requiredOptionMarker == null) ? ' ' : requiredOptionMarker;
2973 showDefaultValues = (showDefaultValues == null) ? false : showDefaultValues;
2974 synopsisHeading = (synopsisHeading == null) ? "Usage: " : synopsisHeading;
2975 commandListHeading = (commandListHeading == null) ? "Commands:%n" : commandListHeading;
2976 separator = (separator == null) ? DEFAULT_SEPARATOR : separator;
2977 parameterLabelRenderer = createDefaultParamLabelRenderer(); // uses help separator
2978 Collections.sort(operands, new PositionalParametersSorter());
2979 positionalParametersFields = Collections.unmodifiableList(operands);
2980 optionFields = Collections.unmodifiableList(options);
2981 }
2982
2983 /** Registers all specified subcommands with this Help.
2984 * @param commands maps the command names to the associated CommandLine object
2985 * @return this Help instance (for method chaining)
2986 * @see CommandLine#getSubcommands()
2987 */
2988 public Help addAllSubcommands(Map<String, CommandLine> commands) {
2989 if (commands != null) {
2990 for (Map.Entry<String, CommandLine> entry : commands.entrySet()) {
2991 addSubcommand(entry.getKey(), entry.getValue().getCommand());
2992 }
2993 }
2994 return this;
2995 }
2996
2997 /** Registers the specified subcommand with this Help.
2998 * @param commandName the name of the subcommand to display in the usage message
2999 * @param command the annotated object to get more information from
3000 * @return this Help instance (for method chaining)
3001 */
3002 public Help addSubcommand(String commandName, Object command) {
3003 commands.put(commandName, new Help(command));
3004 return this;
3005 }
3006
3007 /** Returns a synopsis for the command without reserving space for the synopsis heading.
3008 * @return a synopsis
3009 * @see #abbreviatedSynopsis()
3010 * @see #detailedSynopsis(Comparator, boolean)
3011 * @deprecated use {@link #synopsis(int)} instead
3012 */
3013 @Deprecated
3014 public String synopsis() { return synopsis(0); }
3015
3016 /**
3017 * Returns a synopsis for the command, reserving the specified space for the synopsis heading.
3018 * @param synopsisHeadingLength the length of the synopsis heading that will be displayed on the same line
3019 * @return a synopsis
3020 * @see #abbreviatedSynopsis()
3021 * @see #detailedSynopsis(Comparator, boolean)
3022 * @see #synopsisHeading
3023 */
3024 public String synopsis(int synopsisHeadingLength) {
3025 if (!empty(customSynopsis)) { return customSynopsis(); }
3026 return abbreviateSynopsis ? abbreviatedSynopsis()
3027 : detailedSynopsis(synopsisHeadingLength, createShortOptionArityAndNameComparator(), true);
3028 }
3029
3030 /** Generates a generic synopsis like {@code <command name> [OPTIONS] [PARAM1 [PARAM2]...]}, omitting parts
3031 * that don't apply to the command (e.g., does not show [OPTIONS] if the command has no options).
3032 * @return a generic synopsis */
3033 public String abbreviatedSynopsis() {
3034 StringBuilder sb = new StringBuilder();
3035 if (!optionFields.isEmpty()) { // only show if annotated object actually has options
3036 sb.append(" [OPTIONS]");
3037 }
3038 // sb.append(" [--] "); // implied
3039 for (Field positionalParam : positionalParametersFields) {
3040 if (!positionalParam.getAnnotation(Parameters.class).hidden()) {
3041 sb.append(' ').append(parameterLabelRenderer.renderParameterLabel(positionalParam, ansi(), colorScheme.parameterStyles));
3042 }
3043 }
3044 return colorScheme.commandText(commandName).toString()
3045 + (sb.toString()) + System.getProperty("line.separator");
3046 }
3047 /** Generates a detailed synopsis message showing all options and parameters. Follows the unix convention of
3048 * showing optional options and parameters in square brackets ({@code [ ]}).
3049 * @param optionSort comparator to sort options or {@code null} if options should not be sorted
3050 * @param clusterBooleanOptions {@code true} if boolean short options should be clustered into a single string
3051 * @return a detailed synopsis
3052 * @deprecated use {@link #detailedSynopsis(int, Comparator, boolean)} instead. */
3053 @Deprecated
3054 public String detailedSynopsis(Comparator<Field> optionSort, boolean clusterBooleanOptions) {
3055 return detailedSynopsis(0, optionSort, clusterBooleanOptions);
3056 }
3057
3058 /** Generates a detailed synopsis message showing all options and parameters. Follows the unix convention of
3059 * showing optional options and parameters in square brackets ({@code [ ]}).
3060 * @param synopsisHeadingLength the length of the synopsis heading that will be displayed on the same line
3061 * @param optionSort comparator to sort options or {@code null} if options should not be sorted
3062 * @param clusterBooleanOptions {@code true} if boolean short options should be clustered into a single string
3063 * @return a detailed synopsis */
3064 public String detailedSynopsis(int synopsisHeadingLength, Comparator<Field> optionSort, boolean clusterBooleanOptions) {
3065 Text optionText = ansi().new Text(0);
3066 List<Field> fields = new ArrayList<Field>(optionFields); // iterate in declaration order
3067 if (optionSort != null) {
3068 Collections.sort(fields, optionSort);// iterate in specified sort order
3069 }
3070 if (clusterBooleanOptions) { // cluster all short boolean options into a single string
3071 List<Field> booleanOptions = new ArrayList<Field>();
3072 StringBuilder clusteredRequired = new StringBuilder("-");
3073 StringBuilder clusteredOptional = new StringBuilder("-");
3074 for (Field field : fields) {
3075 if (field.getType() == boolean.class || field.getType() == Boolean.class) {
3076 Option option = field.getAnnotation(Option.class);
3077 String shortestName = ShortestFirst.sort(option.names())[0];
3078 if (shortestName.length() == 2 && shortestName.startsWith("-")) {
3079 booleanOptions.add(field);
3080 if (option.required()) {
3081 clusteredRequired.append(shortestName.substring(1));
3082 } else {
3083 clusteredOptional.append(shortestName.substring(1));
3084 }
3085 }
3086 }
3087 }
3088 fields.removeAll(booleanOptions);
3089 if (clusteredRequired.length() > 1) { // initial length was 1
3090 optionText = optionText.append(" ").append(colorScheme.optionText(clusteredRequired.toString()));
3091 }
3092 if (clusteredOptional.length() > 1) { // initial length was 1
3093 optionText = optionText.append(" [").append(colorScheme.optionText(clusteredOptional.toString())).append("]");
3094 }
3095 }
3096 for (Field field : fields) {
3097 Option option = field.getAnnotation(Option.class);
3098 if (!option.hidden()) {
3099 if (option.required()) {
3100 optionText = appendOptionSynopsis(optionText, field, ShortestFirst.sort(option.names())[0], " ", "");
3101 if (isMultiValue(field)) {
3102 optionText = appendOptionSynopsis(optionText, field, ShortestFirst.sort(option.names())[0], " [", "]...");
3103 }
3104 } else {
3105 optionText = appendOptionSynopsis(optionText, field, ShortestFirst.sort(option.names())[0], " [", "]");
3106 if (isMultiValue(field)) {
3107 optionText = optionText.append("...");
3108 }
3109 }
3110 }
3111 }
3112 for (Field positionalParam : positionalParametersFields) {
3113 if (!positionalParam.getAnnotation(Parameters.class).hidden()) {
3114 optionText = optionText.append(" ");
3115 Text label = parameterLabelRenderer.renderParameterLabel(positionalParam, colorScheme.ansi(), colorScheme.parameterStyles);
3116 optionText = optionText.append(label);
3117 }
3118 }
3119 // Fix for #142: first line of synopsis overshoots max. characters
3120 int firstColumnLength = commandName.length() + synopsisHeadingLength;
3121
3122 // synopsis heading ("Usage: ") may be on the same line, so adjust column width
3123 TextTable textTable = new TextTable(ansi(), firstColumnLength, usageHelpWidth - firstColumnLength);
3124 textTable.indentWrappedLines = 1; // don't worry about first line: options (2nd column) always start with a space
3125
3126 // right-adjust the command name by length of synopsis heading
3127 Text PADDING = Ansi.OFF.new Text(stringOf('X', synopsisHeadingLength));
3128 textTable.addRowValues(new Text[] {PADDING.append(colorScheme.commandText(commandName)), optionText});
3129 return textTable.toString().substring(synopsisHeadingLength); // cut off leading synopsis heading spaces
3130 }
3131
3132 private Text appendOptionSynopsis(Text optionText, Field field, String optionName, String prefix, String suffix) {
3133 Text optionParamText = parameterLabelRenderer.renderParameterLabel(field, colorScheme.ansi(), colorScheme.optionParamStyles);
3134 return optionText.append(prefix)
3135 .append(colorScheme.optionText(optionName))
3136 .append(optionParamText)
3137 .append(suffix);
3138 }
3139
3140 /** Returns the number of characters the synopsis heading will take on the same line as the synopsis.
3141 * @return the number of characters the synopsis heading will take on the same line as the synopsis.
3142 * @see #detailedSynopsis(int, Comparator, boolean)
3143 */
3144 public int synopsisHeadingLength() {
3145 String[] lines = Ansi.OFF.new Text(synopsisHeading).toString().split("\\r?\\n|\\r|%n", -1);
3146 return lines[lines.length - 1].length();
3147 }
3148 /**
3149 * <p>Returns a description of the {@linkplain Option options} supported by the application.
3150 * This implementation {@linkplain #createShortOptionNameComparator() sorts options alphabetically}, and shows
3151 * only the {@linkplain Option#hidden() non-hidden} options in a {@linkplain TextTable tabular format}
3152 * using the {@linkplain #createDefaultOptionRenderer() default renderer} and {@linkplain Layout default layout}.</p>
3153 * @return the fully formatted option list
3154 * @see #optionList(Layout, Comparator, IParamLabelRenderer)
3155 */
3156 public String optionList() {
3157 Comparator<Field> sortOrder = sortOptions == null || sortOptions.booleanValue()
3158 ? createShortOptionNameComparator()
3159 : null;
3160 return optionList(createDefaultLayout(), sortOrder, parameterLabelRenderer);
3161 }
3162
3163 /** Sorts all {@code Options} with the specified {@code comparator} (if the comparator is non-{@code null}),
3164 * then {@linkplain Layout#addOption(Field, CommandLine.Help.IParamLabelRenderer) adds} all non-hidden options to the
3165 * specified TextTable and returns the result of TextTable.toString().
3166 * @param layout responsible for rendering the option list
3167 * @param optionSort determines in what order {@code Options} should be listed. Declared order if {@code null}
3168 * @param valueLabelRenderer used for options with a parameter
3169 * @return the fully formatted option list
3170 */
3171 public String optionList(Layout layout, Comparator<Field> optionSort, IParamLabelRenderer valueLabelRenderer) {
3172 List<Field> fields = new ArrayList<Field>(optionFields); // options are stored in order of declaration
3173 if (optionSort != null) {
3174 Collections.sort(fields, optionSort); // default: sort options ABC
3175 }
3176 layout.addOptions(fields, valueLabelRenderer);
3177 return layout.toString();
3178 }
3179
3180 /**
3181 * Returns the section of the usage help message that lists the parameters with their descriptions.
3182 * @return the section of the usage help message that lists the parameters
3183 */
3184 public String parameterList() {
3185 return parameterList(createDefaultLayout(), parameterLabelRenderer);
3186 }
3187 /**
3188 * Returns the section of the usage help message that lists the parameters with their descriptions.
3189 * @param layout the layout to use
3190 * @param paramLabelRenderer for rendering parameter names
3191 * @return the section of the usage help message that lists the parameters
3192 */
3193 public String parameterList(Layout layout, IParamLabelRenderer paramLabelRenderer) {
3194 layout.addPositionalParameters(positionalParametersFields, paramLabelRenderer);
3195 return layout.toString();
3196 }
3197
3198 private static String heading(Ansi ansi, String values, Object... params) {
3199 StringBuilder sb = join(ansi, new String[] {values}, new StringBuilder(), params);
3200 String result = sb.toString();
3201 result = result.endsWith(System.getProperty("line.separator"))
3202 ? result.substring(0, result.length() - System.getProperty("line.separator").length()) : result;
3203 return result + new String(spaces(countTrailingSpaces(values)));
3204 }
3205 private static char[] spaces(int length) { char[] result = new char[length]; Arrays.fill(result, ' '); return result; }
3206 private static int countTrailingSpaces(String str) {
3207 if (str == null) {return 0;}
3208 int trailingSpaces = 0;
3209 for (int i = str.length() - 1; i >= 0 && str.charAt(i) == ' '; i--) { trailingSpaces++; }
3210 return trailingSpaces;
3211 }
3212
3213 /** Formats each of the specified values and appends it to the specified StringBuilder.
3214 * @param ansi whether the result should contain ANSI escape codes or not
3215 * @param values the values to format and append to the StringBuilder
3216 * @param sb the StringBuilder to collect the formatted strings
3217 * @param params the parameters to pass to the format method when formatting each value
3218 * @return the specified StringBuilder */
3219 public static StringBuilder join(Ansi ansi, String[] values, StringBuilder sb, Object... params) {
3220 if (values != null) {
3221 TextTable table = new TextTable(ansi, usageHelpWidth);
3222 table.indentWrappedLines = 0;
3223 for (String summaryLine : values) {
3224 Text[] lines = ansi.new Text(format(summaryLine, params)).splitLines();
3225 for (Text line : lines) { table.addRowValues(line); }
3226 }
3227 table.toString(sb);
3228 }
3229 return sb;
3230 }
3231 private static String format(String formatString, Object... params) {
3232 return formatString == null ? "" : String.format(formatString, params);
3233 }
3234 /** Returns command custom synopsis as a string. A custom synopsis can be zero or more lines, and can be
3235 * specified declaratively with the {@link Command#customSynopsis()} annotation attribute or programmatically
3236 * by setting the Help instance's {@link Help#customSynopsis} field.
3237 * @param params Arguments referenced by the format specifiers in the synopsis strings
3238 * @return the custom synopsis lines combined into a single String (which may be empty)
3239 */
3240 public String customSynopsis(Object... params) {
3241 return join(ansi(), customSynopsis, new StringBuilder(), params).toString();
3242 }
3243 /** Returns command description text as a string. Description text can be zero or more lines, and can be specified
3244 * declaratively with the {@link Command#description()} annotation attribute or programmatically by
3245 * setting the Help instance's {@link Help#description} field.
3246 * @param params Arguments referenced by the format specifiers in the description strings
3247 * @return the description lines combined into a single String (which may be empty)
3248 */
3249 public String description(Object... params) {
3250 return join(ansi(), description, new StringBuilder(), params).toString();
3251 }
3252 /** Returns the command header text as a string. Header text can be zero or more lines, and can be specified
3253 * declaratively with the {@link Command#header()} annotation attribute or programmatically by
3254 * setting the Help instance's {@link Help#header} field.
3255 * @param params Arguments referenced by the format specifiers in the header strings
3256 * @return the header lines combined into a single String (which may be empty)
3257 */
3258 public String header(Object... params) {
3259 return join(ansi(), header, new StringBuilder(), params).toString();
3260 }
3261 /** Returns command footer text as a string. Footer text can be zero or more lines, and can be specified
3262 * declaratively with the {@link Command#footer()} annotation attribute or programmatically by
3263 * setting the Help instance's {@link Help#footer} field.
3264 * @param params Arguments referenced by the format specifiers in the footer strings
3265 * @return the footer lines combined into a single String (which may be empty)
3266 */
3267 public String footer(Object... params) {
3268 return join(ansi(), footer, new StringBuilder(), params).toString();
3269 }
3270
3271 /** Returns the text displayed before the header text; the result of {@code String.format(headerHeading, params)}.
3272 * @param params the parameters to use to format the header heading
3273 * @return the formatted header heading */
3274 public String headerHeading(Object... params) {
3275 return heading(ansi(), headerHeading, params);
3276 }
3277
3278 /** Returns the text displayed before the synopsis text; the result of {@code String.format(synopsisHeading, params)}.
3279 * @param params the parameters to use to format the synopsis heading
3280 * @return the formatted synopsis heading */
3281 public String synopsisHeading(Object... params) {
3282 return heading(ansi(), synopsisHeading, params);
3283 }
3284
3285 /** Returns the text displayed before the description text; an empty string if there is no description,
3286 * otherwise the result of {@code String.format(descriptionHeading, params)}.
3287 * @param params the parameters to use to format the description heading
3288 * @return the formatted description heading */
3289 public String descriptionHeading(Object... params) {
3290 return empty(descriptionHeading) ? "" : heading(ansi(), descriptionHeading, params);
3291 }
3292
3293 /** Returns the text displayed before the positional parameter list; an empty string if there are no positional
3294 * parameters, otherwise the result of {@code String.format(parameterListHeading, params)}.
3295 * @param params the parameters to use to format the parameter list heading
3296 * @return the formatted parameter list heading */
3297 public String parameterListHeading(Object... params) {
3298 return positionalParametersFields.isEmpty() ? "" : heading(ansi(), parameterListHeading, params);
3299 }
3300
3301 /** Returns the text displayed before the option list; an empty string if there are no options,
3302 * otherwise the result of {@code String.format(optionListHeading, params)}.
3303 * @param params the parameters to use to format the option list heading
3304 * @return the formatted option list heading */
3305 public String optionListHeading(Object... params) {
3306 return optionFields.isEmpty() ? "" : heading(ansi(), optionListHeading, params);
3307 }
3308
3309 /** Returns the text displayed before the command list; an empty string if there are no commands,
3310 * otherwise the result of {@code String.format(commandListHeading, params)}.
3311 * @param params the parameters to use to format the command list heading
3312 * @return the formatted command list heading */
3313 public String commandListHeading(Object... params) {
3314 return commands.isEmpty() ? "" : heading(ansi(), commandListHeading, params);
3315 }
3316
3317 /** Returns the text displayed before the footer text; the result of {@code String.format(footerHeading, params)}.
3318 * @param params the parameters to use to format the footer heading
3319 * @return the formatted footer heading */
3320 public String footerHeading(Object... params) {
3321 return heading(ansi(), footerHeading, params);
3322 }
3323 /** Returns a 2-column list with command names and the first line of their header or (if absent) description.
3324 * @return a usage help section describing the added commands */
3325 public String commandList() {
3326 if (commands.isEmpty()) { return ""; }
3327 int commandLength = maxLength(commands.keySet());
3328 Help.TextTable textTable = new Help.TextTable(ansi(),
3329 new Help.Column(commandLength + 2, 2, Help.Column.Overflow.SPAN),
3330 new Help.Column(usageHelpWidth - (commandLength + 2), 2, Help.Column.Overflow.WRAP));
3331
3332 for (Map.Entry<String, Help> entry : commands.entrySet()) {
3333 Help command = entry.getValue();
3334 String header = command.header != null && command.header.length > 0 ? command.header[0]
3335 : (command.description != null && command.description.length > 0 ? command.description[0] : "");
3336 textTable.addRowValues(colorScheme.commandText(entry.getKey()), ansi().new Text(header));
3337 }
3338 return textTable.toString();
3339 }
3340 private static int maxLength(Collection<String> any) {
3341 List<String> strings = new ArrayList<String>(any);
3342 Collections.sort(strings, Collections.reverseOrder(Help.shortestFirst()));
3343 return strings.get(0).length();
3344 }
3345 private static String join(String[] names, int offset, int length, String separator) {
3346 if (names == null) { return ""; }
3347 StringBuilder result = new StringBuilder();
3348 for (int i = offset; i < offset + length; i++) {
3349 result.append((i > offset) ? separator : "").append(names[i]);
3350 }
3351 return result.toString();
3352 }
3353 private static String stringOf(char chr, int length) {
3354 char[] buff = new char[length];
3355 Arrays.fill(buff, chr);
3356 return new String(buff);
3357 }
3358
3359 /** Returns a {@code Layout} instance configured with the user preferences captured in this Help instance.
3360 * @return a Layout */
3361 public Layout createDefaultLayout() {
3362 return new Layout(colorScheme, new TextTable(colorScheme.ansi()), createDefaultOptionRenderer(), createDefaultParameterRenderer());
3363 }
3364 /** Returns a new default OptionRenderer which converts {@link Option Options} to five columns of text to match
3365 * the default {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
3366 * <ol>
3367 * <li>the required option marker</li>
3368 * <li>2-character short option name (or empty string if no short option exists)</li>
3369 * <li>comma separator (only if both short option and long option exist, empty string otherwise)</li>
3370 * <li>comma-separated string with long option name(s)</li>
3371 * <li>first element of the {@link Option#description()} array</li>
3372 * </ol>
3373 * <p>Following this, there will be one row for each of the remaining elements of the {@link
3374 * Option#description()} array, and these rows look like {@code {"", "", "", "", option.description()[i]}}.</p>
3375 * <p>If configured, this option renderer adds an additional row to display the default field value.</p>
3376 * @return a new default OptionRenderer
3377 */
3378 public IOptionRenderer createDefaultOptionRenderer() {
3379 DefaultOptionRenderer result = new DefaultOptionRenderer();
3380 result.requiredMarker = String.valueOf(requiredOptionMarker);
3381 if (showDefaultValues != null && showDefaultValues.booleanValue()) {
3382 result.command = this.command;
3383 }
3384 return result;
3385 }
3386 /** Returns a new minimal OptionRenderer which converts {@link Option Options} to a single row with two columns
3387 * of text: an option name and a description. If multiple names or descriptions exist, the first value is used.
3388 * @return a new minimal OptionRenderer */
3389 public static IOptionRenderer createMinimalOptionRenderer() {
3390 return new MinimalOptionRenderer();
3391 }
3392
3393 /** Returns a new default ParameterRenderer which converts {@link Parameters Parameters} to four columns of
3394 * text to match the default {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
3395 * <ol>
3396 * <li>empty string </li>
3397 * <li>empty string </li>
3398 * <li>parameter(s) label as rendered by the {@link IParamLabelRenderer}</li>
3399 * <li>first element of the {@link Parameters#description()} array</li>
3400 * </ol>
3401 * <p>Following this, there will be one row for each of the remaining elements of the {@link
3402 * Parameters#description()} array, and these rows look like {@code {"", "", "", param.description()[i]}}.</p>
3403 * <p>If configured, this parameter renderer adds an additional row to display the default field value.</p>
3404 * @return a new default ParameterRenderer
3405 */
3406 public IParameterRenderer createDefaultParameterRenderer() {
3407 DefaultParameterRenderer result = new DefaultParameterRenderer();
3408 result.requiredMarker = String.valueOf(requiredOptionMarker);
3409 return result;
3410 }
3411 /** Returns a new minimal ParameterRenderer which converts {@link Parameters Parameters} to a single row with
3412 * two columns of text: an option name and a description. If multiple descriptions exist, the first value is used.
3413 * @return a new minimal ParameterRenderer */
3414 public static IParameterRenderer createMinimalParameterRenderer() {
3415 return new MinimalParameterRenderer();
3416 }
3417
3418 /** Returns a value renderer that returns the {@code paramLabel} if defined or the field name otherwise.
3419 * @return a new minimal ParamLabelRenderer */
3420 public static IParamLabelRenderer createMinimalParamLabelRenderer() {
3421 return new IParamLabelRenderer() {
3422 @Override
3423 public Text renderParameterLabel(Field field, Ansi ansi, List<IStyle> styles) {
3424 String text = DefaultParamLabelRenderer.renderParameterName(field);
3425 return ansi.apply(text, styles);
3426 }
3427 @Override
3428 public String separator() { return ""; }
3429 };
3430 }
3431 /** Returns a new default value renderer that separates option parameters from their {@linkplain Option
3432 * options} with the specified separator string, surrounds optional parameters with {@code '['} and {@code ']'}
3433 * characters and uses ellipses ("...") to indicate that any number of a parameter are allowed.
3434 * @return a new default ParamLabelRenderer
3435 */
3436 public IParamLabelRenderer createDefaultParamLabelRenderer() {
3437 return new DefaultParamLabelRenderer(separator);
3438 }
3439 /** Sorts Fields annotated with {@code Option} by their option name in case-insensitive alphabetic order. If an
3440 * Option has multiple names, the shortest name is used for the sorting. Help options follow non-help options.
3441 * @return a comparator that sorts fields by their option name in case-insensitive alphabetic order */
3442 public static Comparator<Field> createShortOptionNameComparator() {
3443 return new SortByShortestOptionNameAlphabetically();
3444 }
3445 /** Sorts Fields annotated with {@code Option} by their option {@linkplain Range#max max arity} first, by
3446 * {@linkplain Range#min min arity} next, and by {@linkplain #createShortOptionNameComparator() option name} last.
3447 * @return a comparator that sorts fields by arity first, then their option name */
3448 public static Comparator<Field> createShortOptionArityAndNameComparator() {
3449 return new SortByOptionArityAndNameAlphabetically();
3450 }
3451 /** Sorts short strings before longer strings.
3452 * @return a comparators that sorts short strings before longer strings */
3453 public static Comparator<String> shortestFirst() {
3454 return new ShortestFirst();
3455 }
3456
3457 /** Returns whether ANSI escape codes are enabled or not.
3458 * @return whether ANSI escape codes are enabled or not
3459 */
3460 public Ansi ansi() {
3461 return colorScheme.ansi;
3462 }
3463
3464 /** When customizing online help for {@link Option Option} details, a custom {@code IOptionRenderer} can be
3465 * used to create textual representation of an Option in a tabular format: one or more rows, each containing
3466 * one or more columns. The {@link Layout Layout} is responsible for placing these text values in the
3467 * {@link TextTable TextTable}. */
3468 public interface IOptionRenderer {
3469 /**
3470 * Returns a text representation of the specified Option and the Field that captures the option value.
3471 * @param option the command line option to show online usage help for
3472 * @param field the field that will hold the value for the command line option
3473 * @param parameterLabelRenderer responsible for rendering option parameters to text
3474 * @param scheme color scheme for applying ansi color styles to options and option parameters
3475 * @return a 2-dimensional array of text values: one or more rows, each containing one or more columns
3476 */
3477 Text[][] render(Option option, Field field, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme);
3478 }
3479 /** The DefaultOptionRenderer converts {@link Option Options} to five columns of text to match the default
3480 * {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
3481 * <ol>
3482 * <li>the required option marker (if the option is required)</li>
3483 * <li>2-character short option name (or empty string if no short option exists)</li>
3484 * <li>comma separator (only if both short option and long option exist, empty string otherwise)</li>
3485 * <li>comma-separated string with long option name(s)</li>
3486 * <li>first element of the {@link Option#description()} array</li>
3487 * </ol>
3488 * <p>Following this, there will be one row for each of the remaining elements of the {@link
3489 * Option#description()} array, and these rows look like {@code {"", "", "", option.description()[i]}}.</p>
3490 */
3491 static class DefaultOptionRenderer implements IOptionRenderer {
3492 public String requiredMarker = " ";
3493 public Object command;
3494 private String sep;
3495 private boolean showDefault;
3496 @Override
3497 public Text[][] render(Option option, Field field, IParamLabelRenderer paramLabelRenderer, ColorScheme scheme) {
3498 String[] names = ShortestFirst.sort(option.names());
3499 int shortOptionCount = names[0].length() == 2 ? 1 : 0;
3500 String shortOption = shortOptionCount > 0 ? names[0] : "";
3501 sep = shortOptionCount > 0 && names.length > 1 ? "," : "";
3502
3503 String longOption = join(names, shortOptionCount, names.length - shortOptionCount, ", ");
3504 Text longOptionText = createLongOptionText(field, paramLabelRenderer, scheme, longOption);
3505
3506 showDefault = command != null && !option.help() && !isBoolean(field.getType());
3507 Object defaultValue = createDefaultValue(field);
3508
3509 String requiredOption = option.required() ? requiredMarker : "";
3510 return renderDescriptionLines(option, scheme, requiredOption, shortOption, longOptionText, defaultValue);
3511 }
3512
3513 private Object createDefaultValue(Field field) {
3514 Object defaultValue = null;
3515 try {
3516 defaultValue = field.get(command);
3517 if (defaultValue == null) { showDefault = false; } // #201 don't show null default values
3518 else if (field.getType().isArray()) {
3519 StringBuilder sb = new StringBuilder();
3520 for (int i = 0; i < Array.getLength(defaultValue); i++) {
3521 sb.append(i > 0 ? ", " : "").append(Array.get(defaultValue, i));
3522 }
3523 defaultValue = sb.insert(0, "[").append("]").toString();
3524 }
3525 } catch (Exception ex) {
3526 showDefault = false;
3527 }
3528 return defaultValue;
3529 }
3530
3531 private Text createLongOptionText(Field field, IParamLabelRenderer renderer, ColorScheme scheme, String longOption) {
3532 Text paramLabelText = renderer.renderParameterLabel(field, scheme.ansi(), scheme.optionParamStyles);
3533
3534 // if no long option, fill in the space between the short option name and the param label value
3535 if (paramLabelText.length > 0 && longOption.length() == 0) {
3536 sep = renderer.separator();
3537 // #181 paramLabelText may be =LABEL or [=LABEL...]
3538 int sepStart = paramLabelText.plainString().indexOf(sep);
3539 Text prefix = paramLabelText.substring(0, sepStart);
3540 paramLabelText = prefix.append(paramLabelText.substring(sepStart + sep.length()));
3541 }
3542 Text longOptionText = scheme.optionText(longOption);
3543 longOptionText = longOptionText.append(paramLabelText);
3544 return longOptionText;
3545 }
3546
3547 private Text[][] renderDescriptionLines(Option option,
3548 ColorScheme scheme,
3549 String requiredOption,
3550 String shortOption,
3551 Text longOptionText,
3552 Object defaultValue) {
3553 Text EMPTY = Ansi.EMPTY_TEXT;
3554 List<Text[]> result = new ArrayList<Text[]>();
3555 Text[] descriptionFirstLines = scheme.ansi().new Text(str(option.description(), 0)).splitLines();
3556 if (descriptionFirstLines.length == 0) {
3557 if (showDefault) {
3558 descriptionFirstLines = new Text[]{scheme.ansi().new Text(" Default: " + defaultValue)};
3559 showDefault = false; // don't show the default value twice
3560 } else {
3561 descriptionFirstLines = new Text[]{ EMPTY };
3562 }
3563 }
3564 result.add(new Text[] { scheme.optionText(requiredOption), scheme.optionText(shortOption),
3565 scheme.ansi().new Text(sep), longOptionText, descriptionFirstLines[0] });
3566 for (int i = 1; i < descriptionFirstLines.length; i++) {
3567 result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, descriptionFirstLines[i] });
3568 }
3569 for (int i = 1; i < option.description().length; i++) {
3570 Text[] descriptionNextLines = scheme.ansi().new Text(option.description()[i]).splitLines();
3571 for (Text line : descriptionNextLines) {
3572 result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, line });
3573 }
3574 }
3575 if (showDefault) {
3576 result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, scheme.ansi().new Text(" Default: " + defaultValue) });
3577 }
3578 return result.toArray(new Text[result.size()][]);
3579 }
3580 }
3581 /** The MinimalOptionRenderer converts {@link Option Options} to a single row with two columns of text: an
3582 * option name and a description. If multiple names or description lines exist, the first value is used. */
3583 static class MinimalOptionRenderer implements IOptionRenderer {
3584 @Override
3585 public Text[][] render(Option option, Field field, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme) {
3586 Text optionText = scheme.optionText(option.names()[0]);
3587 Text paramLabelText = parameterLabelRenderer.renderParameterLabel(field, scheme.ansi(), scheme.optionParamStyles);
3588 optionText = optionText.append(paramLabelText);
3589 return new Text[][] {{ optionText,
3590 scheme.ansi().new Text(option.description().length == 0 ? "" : option.description()[0]) }};
3591 }
3592 }
3593 /** The MinimalParameterRenderer converts {@link Parameters Parameters} to a single row with two columns of
3594 * text: the parameters label and a description. If multiple description lines exist, the first value is used. */
3595 static class MinimalParameterRenderer implements IParameterRenderer {
3596 @Override
3597 public Text[][] render(Parameters param, Field field, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme) {
3598 return new Text[][] {{ parameterLabelRenderer.renderParameterLabel(field, scheme.ansi(), scheme.parameterStyles),
3599 scheme.ansi().new Text(param.description().length == 0 ? "" : param.description()[0]) }};
3600 }
3601 }
3602 /** When customizing online help for {@link Parameters Parameters} details, a custom {@code IParameterRenderer}
3603 * can be used to create textual representation of a Parameters field in a tabular format: one or more rows,
3604 * each containing one or more columns. The {@link Layout Layout} is responsible for placing these text
3605 * values in the {@link TextTable TextTable}. */
3606 public interface IParameterRenderer {
3607 /**
3608 * Returns a text representation of the specified Parameters and the Field that captures the parameter values.
3609 * @param parameters the command line parameters to show online usage help for
3610 * @param field the field that will hold the value for the command line parameters
3611 * @param parameterLabelRenderer responsible for rendering parameter labels to text
3612 * @param scheme color scheme for applying ansi color styles to positional parameters
3613 * @return a 2-dimensional array of text values: one or more rows, each containing one or more columns
3614 */
3615 Text[][] render(Parameters parameters, Field field, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme);
3616 }
3617 /** The DefaultParameterRenderer converts {@link Parameters Parameters} to five columns of text to match the
3618 * default {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
3619 * <ol>
3620 * <li>the required option marker (if the parameter's arity is to have at least one value)</li>
3621 * <li>empty string </li>
3622 * <li>empty string </li>
3623 * <li>parameter(s) label as rendered by the {@link IParamLabelRenderer}</li>
3624 * <li>first element of the {@link Parameters#description()} array</li>
3625 * </ol>
3626 * <p>Following this, there will be one row for each of the remaining elements of the {@link
3627 * Parameters#description()} array, and these rows look like {@code {"", "", "", param.description()[i]}}.</p>
3628 */
3629 static class DefaultParameterRenderer implements IParameterRenderer {
3630 public String requiredMarker = " ";
3631 @Override
3632 public Text[][] render(Parameters params, Field field, IParamLabelRenderer paramLabelRenderer, ColorScheme scheme) {
3633 Text label = paramLabelRenderer.renderParameterLabel(field, scheme.ansi(), scheme.parameterStyles);
3634 Text requiredParameter = scheme.parameterText(Range.parameterArity(field).min > 0 ? requiredMarker : "");
3635
3636 Text EMPTY = Ansi.EMPTY_TEXT;
3637 List<Text[]> result = new ArrayList<Text[]>();
3638 Text[] descriptionFirstLines = scheme.ansi().new Text(str(params.description(), 0)).splitLines();
3639 if (descriptionFirstLines.length == 0) { descriptionFirstLines = new Text[]{ EMPTY }; }
3640 result.add(new Text[] { requiredParameter, EMPTY, EMPTY, label, descriptionFirstLines[0] });
3641 for (int i = 1; i < descriptionFirstLines.length; i++) {
3642 result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, descriptionFirstLines[i] });
3643 }
3644 for (int i = 1; i < params.description().length; i++) {
3645 Text[] descriptionNextLines = scheme.ansi().new Text(params.description()[i]).splitLines();
3646 for (Text line : descriptionNextLines) {
3647 result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, line });
3648 }
3649 }
3650 return result.toArray(new Text[result.size()][]);
3651 }
3652 }
3653 /** When customizing online usage help for an option parameter or a positional parameter, a custom
3654 * {@code IParamLabelRenderer} can be used to render the parameter name or label to a String. */
3655 public interface IParamLabelRenderer {
3656
3657 /** Returns a text rendering of the Option parameter or positional parameter; returns an empty string
3658 * {@code ""} if the option is a boolean and does not take a parameter.
3659 * @param field the annotated field with a parameter label
3660 * @param ansi determines whether ANSI escape codes should be emitted or not
3661 * @param styles the styles to apply to the parameter label
3662 * @return a text rendering of the Option parameter or positional parameter */
3663 Text renderParameterLabel(Field field, Ansi ansi, List<IStyle> styles);
3664
3665 /** Returns the separator between option name and param label.
3666 * @return the separator between option name and param label */
3667 String separator();
3668 }
3669 /**
3670 * DefaultParamLabelRenderer separates option parameters from their {@linkplain Option options} with a
3671 * {@linkplain DefaultParamLabelRenderer#separator separator} string, surrounds optional values
3672 * with {@code '['} and {@code ']'} characters and uses ellipses ("...") to indicate that any number of
3673 * values is allowed for options or parameters with variable arity.
3674 */
3675 static class DefaultParamLabelRenderer implements IParamLabelRenderer {
3676 /** The string to use to separate option parameters from their options. */
3677 public final String separator;
3678 /** Constructs a new DefaultParamLabelRenderer with the specified separator string. */
3679 public DefaultParamLabelRenderer(String separator) {
3680 this.separator = Assert.notNull(separator, "separator");
3681 }
3682 @Override
3683 public String separator() { return separator; }
3684 @Override
3685 public Text renderParameterLabel(Field field, Ansi ansi, List<IStyle> styles) {
3686 boolean isOptionParameter = field.isAnnotationPresent(Option.class);
3687 Range arity = isOptionParameter ? Range.optionArity(field) : Range.parameterCapacity(field);
3688 String split = isOptionParameter ? field.getAnnotation(Option.class).split() : field.getAnnotation(Parameters.class).split();
3689 Text result = ansi.new Text("");
3690 String sep = isOptionParameter ? separator : "";
3691 Text paramName = ansi.apply(renderParameterName(field), styles);
3692 if (!empty(split)) { paramName = paramName.append("[" + split).append(paramName).append("]..."); } // #194
3693 for (int i = 0; i < arity.min; i++) {
3694 result = result.append(sep).append(paramName);
3695 sep = " ";
3696 }
3697 if (arity.isVariable) {
3698 if (result.length == 0) { // arity="*" or arity="0..*"
3699 result = result.append(sep + "[").append(paramName).append("]...");
3700 } else if (!result.plainString().endsWith("...")) { // split param may already end with "..."
3701 result = result.append("...");
3702 }
3703 } else {
3704 sep = result.length == 0 ? (isOptionParameter ? separator : "") : " ";
3705 for (int i = arity.min; i < arity.max; i++) {
3706 if (sep.trim().length() == 0) {
3707 result = result.append(sep + "[").append(paramName);
3708 } else {
3709 result = result.append("[" + sep).append(paramName);
3710 }
3711 sep = " ";
3712 }
3713 for (int i = arity.min; i < arity.max; i++) { result = result.append("]"); }
3714 }
3715 return result;
3716 }
3717 private static String renderParameterName(Field field) {
3718 String result = null;
3719 if (field.isAnnotationPresent(Option.class)) {
3720 result = field.getAnnotation(Option.class).paramLabel();
3721 } else if (field.isAnnotationPresent(Parameters.class)) {
3722 result = field.getAnnotation(Parameters.class).paramLabel();
3723 }
3724 if (result != null && result.trim().length() > 0) {
3725 return result.trim();
3726 }
3727 String name = field.getName();
3728 if (Map.class.isAssignableFrom(field.getType())) { // #195 better param labels for map fields
3729 Class<?>[] paramTypes = getTypeAttribute(field);
3730 if (paramTypes.length < 2 || paramTypes[0] == null || paramTypes[1] == null) {
3731 name = "String=String";
3732 } else { name = paramTypes[0].getSimpleName() + "=" + paramTypes[1].getSimpleName(); }
3733 }
3734 return "<" + name + ">";
3735 }
3736 }
3737 /** Use a Layout to format usage help text for options and parameters in tabular format.
3738 * <p>Delegates to the renderers to create {@link Text} values for the annotated fields, and uses a
3739 * {@link TextTable} to display these values in tabular format. Layout is responsible for deciding which values
3740 * to display where in the table. By default, Layout shows one option or parameter per table row.</p>
3741 * <p>Customize by overriding the {@link #layout(Field, CommandLine.Help.Ansi.Text[][])} method.</p>
3742 * @see IOptionRenderer rendering options to text
3743 * @see IParameterRenderer rendering parameters to text
3744 * @see TextTable showing values in a tabular format
3745 */
3746 public static class Layout {
3747 protected final ColorScheme colorScheme;
3748 protected final TextTable table;
3749 protected IOptionRenderer optionRenderer;
3750 protected IParameterRenderer parameterRenderer;
3751
3752 /** Constructs a Layout with the specified color scheme, a new default TextTable, the
3753 * {@linkplain Help#createDefaultOptionRenderer() default option renderer}, and the
3754 * {@linkplain Help#createDefaultParameterRenderer() default parameter renderer}.
3755 * @param colorScheme the color scheme to use for common, auto-generated parts of the usage help message */
3756 public Layout(ColorScheme colorScheme) { this(colorScheme, new TextTable(colorScheme.ansi())); }
3757
3758 /** Constructs a Layout with the specified color scheme, the specified TextTable, the
3759 * {@linkplain Help#createDefaultOptionRenderer() default option renderer}, and the
3760 * {@linkplain Help#createDefaultParameterRenderer() default parameter renderer}.
3761 * @param colorScheme the color scheme to use for common, auto-generated parts of the usage help message
3762 * @param textTable the TextTable to lay out parts of the usage help message in tabular format */
3763 public Layout(ColorScheme colorScheme, TextTable textTable) {
3764 this(colorScheme, textTable, new DefaultOptionRenderer(), new DefaultParameterRenderer());
3765 }
3766 /** Constructs a Layout with the specified color scheme, the specified TextTable, the
3767 * specified option renderer and the specified parameter renderer.
3768 * @param colorScheme the color scheme to use for common, auto-generated parts of the usage help message
3769 * @param optionRenderer the object responsible for rendering Options to Text
3770 * @param parameterRenderer the object responsible for rendering Parameters to Text
3771 * @param textTable the TextTable to lay out parts of the usage help message in tabular format */
3772 public Layout(ColorScheme colorScheme, TextTable textTable, IOptionRenderer optionRenderer, IParameterRenderer parameterRenderer) {
3773 this.colorScheme = Assert.notNull(colorScheme, "colorScheme");
3774 this.table = Assert.notNull(textTable, "textTable");
3775 this.optionRenderer = Assert.notNull(optionRenderer, "optionRenderer");
3776 this.parameterRenderer = Assert.notNull(parameterRenderer, "parameterRenderer");
3777 }
3778 /**
3779 * Copies the specified text values into the correct cells in the {@link TextTable}. This implementation
3780 * delegates to {@link TextTable#addRowValues(CommandLine.Help.Ansi.Text...)} for each row of values.
3781 * <p>Subclasses may override.</p>
3782 * @param field the field annotated with the specified Option or Parameters
3783 * @param cellValues the text values representing the Option/Parameters, to be displayed in tabular form
3784 */
3785 public void layout(Field field, Text[][] cellValues) {
3786 for (Text[] oneRow : cellValues) {
3787 table.addRowValues(oneRow);
3788 }
3789 }
3790 /** Calls {@link #addOption(Field, CommandLine.Help.IParamLabelRenderer)} for all non-hidden Options in the list.
3791 * @param fields fields annotated with {@link Option} to add usage descriptions for
3792 * @param paramLabelRenderer object that knows how to render option parameters */
3793 public void addOptions(List<Field> fields, IParamLabelRenderer paramLabelRenderer) {
3794 for (Field field : fields) {
3795 Option option = field.getAnnotation(Option.class);
3796 if (!option.hidden()) {
3797 addOption(field, paramLabelRenderer);
3798 }
3799 }
3800 }
3801 /**
3802 * Delegates to the {@link #optionRenderer option renderer} of this layout to obtain
3803 * text values for the specified {@link Option}, and then calls the {@link #layout(Field, CommandLine.Help.Ansi.Text[][])}
3804 * method to write these text values into the correct cells in the TextTable.
3805 * @param field the field annotated with the specified Option
3806 * @param paramLabelRenderer knows how to render option parameters
3807 */
3808 public void addOption(Field field, IParamLabelRenderer paramLabelRenderer) {
3809 Option option = field.getAnnotation(Option.class);
3810 Text[][] values = optionRenderer.render(option, field, paramLabelRenderer, colorScheme);
3811 layout(field, values);
3812 }
3813 /** Calls {@link #addPositionalParameter(Field, CommandLine.Help.IParamLabelRenderer)} for all non-hidden Parameters in the list.
3814 * @param fields fields annotated with {@link Parameters} to add usage descriptions for
3815 * @param paramLabelRenderer knows how to render option parameters */
3816 public void addPositionalParameters(List<Field> fields, IParamLabelRenderer paramLabelRenderer) {
3817 for (Field field : fields) {
3818 Parameters parameters = field.getAnnotation(Parameters.class);
3819 if (!parameters.hidden()) {
3820 addPositionalParameter(field, paramLabelRenderer);
3821 }
3822 }
3823 }
3824 /**
3825 * Delegates to the {@link #parameterRenderer parameter renderer} of this layout
3826 * to obtain text values for the specified {@link Parameters}, and then calls
3827 * {@link #layout(Field, CommandLine.Help.Ansi.Text[][])} to write these text values into the correct cells in the TextTable.
3828 * @param field the field annotated with the specified Parameters
3829 * @param paramLabelRenderer knows how to render option parameters
3830 */
3831 public void addPositionalParameter(Field field, IParamLabelRenderer paramLabelRenderer) {
3832 Parameters option = field.getAnnotation(Parameters.class);
3833 Text[][] values = parameterRenderer.render(option, field, paramLabelRenderer, colorScheme);
3834 layout(field, values);
3835 }
3836 /** Returns the section of the usage help message accumulated in the TextTable owned by this layout. */
3837 @Override public String toString() {
3838 return table.toString();
3839 }
3840 }
3841 /** Sorts short strings before longer strings. */
3842 static class ShortestFirst implements Comparator<String> {
3843 @Override
3844 public int compare(String o1, String o2) {
3845 return o1.length() - o2.length();
3846 }
3847 /** Sorts the specified array of Strings shortest-first and returns it. */
3848 public static String[] sort(String[] names) {
3849 Arrays.sort(names, new ShortestFirst());
3850 return names;
3851 }
3852 }
3853 /** Sorts {@code Option} instances by their name in case-insensitive alphabetic order. If an Option has
3854 * multiple names, the shortest name is used for the sorting. Help options follow non-help options. */
3855 static class SortByShortestOptionNameAlphabetically implements Comparator<Field> {
3856 @Override
3857 public int compare(Field f1, Field f2) {
3858 Option o1 = f1.getAnnotation(Option.class);
3859 Option o2 = f2.getAnnotation(Option.class);
3860 if (o1 == null) { return 1; } else if (o2 == null) { return -1; } // options before params
3861 String[] names1 = ShortestFirst.sort(o1.names());
3862 String[] names2 = ShortestFirst.sort(o2.names());
3863 int result = names1[0].toUpperCase().compareTo(names2[0].toUpperCase()); // case insensitive sort
3864 result = result == 0 ? -names1[0].compareTo(names2[0]) : result; // lower case before upper case
3865 return o1.help() == o2.help() ? result : o2.help() ? -1 : 1; // help options come last
3866 }
3867 }
3868 /** Sorts {@code Option} instances by their max arity first, then their min arity, then delegates to super class. */
3869 static class SortByOptionArityAndNameAlphabetically extends SortByShortestOptionNameAlphabetically {
3870 @Override
3871 public int compare(Field f1, Field f2) {
3872 Option o1 = f1.getAnnotation(Option.class);
3873 Option o2 = f2.getAnnotation(Option.class);
3874 Range arity1 = Range.optionArity(f1);
3875 Range arity2 = Range.optionArity(f2);
3876 int result = arity1.max - arity2.max;
3877 if (result == 0) {
3878 result = arity1.min - arity2.min;
3879 }
3880 if (result == 0) { // arity is same
3881 if (isMultiValue(f1) && !isMultiValue(f2)) { result = 1; } // f1 > f2
3882 if (!isMultiValue(f1) && isMultiValue(f2)) { result = -1; } // f1 < f2
3883 }
3884 return result == 0 ? super.compare(f1, f2) : result;
3885 }
3886 }
3887 /**
3888 * <p>Responsible for spacing out {@link Text} values according to the {@link Column} definitions the table was
3889 * created with. Columns have a width, indentation, and an overflow policy that decides what to do if a value is
3890 * longer than the column's width.</p>
3891 */
3892 public static class TextTable {
3893 /**
3894 * Helper class to index positions in a {@code Help.TextTable}.
3895 * @since 2.0
3896 */
3897 public static class Cell {
3898 /** Table column index (zero based). */
3899 public final int column;
3900 /** Table row index (zero based). */
3901 public final int row;
3902 /** Constructs a new Cell with the specified coordinates in the table.
3903 * @param column the zero-based table column
3904 * @param row the zero-based table row */
3905 public Cell(int column, int row) { this.column = column; this.row = row; }
3906 }
3907
3908 /** The column definitions of this table. */
3909 public final Column[] columns;
3910
3911 /** The {@code char[]} slots of the {@code TextTable} to copy text values into. */
3912 protected final List<Text> columnValues = new ArrayList<Text>();
3913
3914 /** By default, indent wrapped lines by 2 spaces. */
3915 public int indentWrappedLines = 2;
3916
3917 private final Ansi ansi;
3918
3919 /** Constructs a TextTable with five columns as follows:
3920 * <ol>
3921 * <li>required option/parameter marker (width: 2, indent: 0, TRUNCATE on overflow)</li>
3922 * <li>short option name (width: 2, indent: 0, TRUNCATE on overflow)</li>
3923 * <li>comma separator (width: 1, indent: 0, TRUNCATE on overflow)</li>
3924 * <li>long option name(s) (width: 24, indent: 1, SPAN multiple columns on overflow)</li>
3925 * <li>description line(s) (width: 51, indent: 1, WRAP to next row on overflow)</li>
3926 * </ol>
3927 * @param ansi whether to emit ANSI escape codes or not
3928 */
3929 public TextTable(Ansi ansi) {
3930 // "* -c, --create Creates a ...."
3931 this(ansi, new Column[] {
3932 new Column(2, 0, TRUNCATE), // "*"
3933 new Column(2, 0, TRUNCATE), // "-c"
3934 new Column(1, 0, TRUNCATE), // ","
3935 new Column(optionsColumnWidth - 2 - 2 - 1 , 1, SPAN), // " --create"
3936 new Column(usageHelpWidth - optionsColumnWidth, 1, WRAP) // " Creates a ..."
3937 });
3938 }
3939
3940 /** Constructs a new TextTable with columns with the specified width, all SPANning multiple columns on
3941 * overflow except the last column which WRAPS to the next row.
3942 * @param ansi whether to emit ANSI escape codes or not
3943 * @param columnWidths the width of the table columns (all columns have zero indent)
3944 */
3945 public TextTable(Ansi ansi, int... columnWidths) {
3946 this.ansi = Assert.notNull(ansi, "ansi");
3947 columns = new Column[columnWidths.length];
3948 for (int i = 0; i < columnWidths.length; i++) {
3949 columns[i] = new Column(columnWidths[i], 0, i == columnWidths.length - 1 ? SPAN: WRAP);
3950 }
3951 }
3952 /** Constructs a {@code TextTable} with the specified columns.
3953 * @param ansi whether to emit ANSI escape codes or not
3954 * @param columns columns to construct this TextTable with */
3955 public TextTable(Ansi ansi, Column... columns) {
3956 this.ansi = Assert.notNull(ansi, "ansi");
3957 this.columns = Assert.notNull(columns, "columns");
3958 if (columns.length == 0) { throw new IllegalArgumentException("At least one column is required"); }
3959 }
3960 /** Returns the {@code Text} slot at the specified row and column to write a text value into.
3961 * @param row the row of the cell whose Text to return
3962 * @param col the column of the cell whose Text to return
3963 * @return the Text object at the specified row and column
3964 * @since 2.0 */
3965 public Text textAt(int row, int col) { return columnValues.get(col + (row * columns.length)); }
3966
3967 /** Returns the {@code Text} slot at the specified row and column to write a text value into.
3968 * @param row the row of the cell whose Text to return
3969 * @param col the column of the cell whose Text to return
3970 * @return the Text object at the specified row and column
3971 * @deprecated use {@link #textAt(int, int)} instead */
3972 @Deprecated
3973 public Text cellAt(int row, int col) { return textAt(row, col); }
3974
3975 /** Returns the current number of rows of this {@code TextTable}.
3976 * @return the current number of rows in this TextTable */
3977 public int rowCount() { return columnValues.size() / columns.length; }
3978
3979 /** Adds the required {@code char[]} slots for a new row to the {@link #columnValues} field. */
3980 public void addEmptyRow() {
3981 for (int i = 0; i < columns.length; i++) {
3982 columnValues.add(ansi.new Text(columns[i].width));
3983 }
3984 }
3985
3986 /** Delegates to {@link #addRowValues(CommandLine.Help.Ansi.Text...)}.
3987 * @param values the text values to display in each column of the current row */
3988 public void addRowValues(String... values) {
3989 Text[] array = new Text[values.length];
3990 for (int i = 0; i < array.length; i++) {
3991 array[i] = values[i] == null ? Ansi.EMPTY_TEXT : ansi.new Text(values[i]);
3992 }
3993 addRowValues(array);
3994 }
3995 /**
3996 * Adds a new {@linkplain TextTable#addEmptyRow() empty row}, then calls {@link
3997 * TextTable#putValue(int, int, CommandLine.Help.Ansi.Text) putValue} for each of the specified values, adding more empty rows
3998 * if the return value indicates that the value spanned multiple columns or was wrapped to multiple rows.
3999 * @param values the values to write into a new row in this TextTable
4000 * @throws IllegalArgumentException if the number of values exceeds the number of Columns in this table
4001 */
4002 public void addRowValues(Text... values) {
4003 if (values.length > columns.length) {
4004 throw new IllegalArgumentException(values.length + " values don't fit in " +
4005 columns.length + " columns");
4006 }
4007 addEmptyRow();
4008 for (int col = 0; col < values.length; col++) {
4009 int row = rowCount() - 1;// write to last row: previous value may have wrapped to next row
4010 Cell cell = putValue(row, col, values[col]);
4011
4012 // add row if a value spanned/wrapped and there are still remaining values
4013 if ((cell.row != row || cell.column != col) && col != values.length - 1) {
4014 addEmptyRow();
4015 }
4016 }
4017 }
4018 /**
4019 * Writes the specified value into the cell at the specified row and column and returns the last row and
4020 * column written to. Depending on the Column's {@link Column#overflow Overflow} policy, the value may span
4021 * multiple columns or wrap to multiple rows when larger than the column width.
4022 * @param row the target row in the table
4023 * @param col the target column in the table to write to
4024 * @param value the value to write
4025 * @return a Cell indicating the position in the table that was last written to (since 2.0)
4026 * @throws IllegalArgumentException if the specified row exceeds the table's {@linkplain
4027 * TextTable#rowCount() row count}
4028 * @since 2.0 (previous versions returned a {@code java.awt.Point} object)
4029 */
4030 public Cell putValue(int row, int col, Text value) {
4031 if (row > rowCount() - 1) {
4032 throw new IllegalArgumentException("Cannot write to row " + row + ": rowCount=" + rowCount());
4033 }
4034 if (value == null || value.plain.length() == 0) { return new Cell(col, row); }
4035 Column column = columns[col];
4036 int indent = column.indent;
4037 switch (column.overflow) {
4038 case TRUNCATE:
4039 copy(value, textAt(row, col), indent);
4040 return new Cell(col, row);
4041 case SPAN:
4042 int startColumn = col;
4043 do {
4044 boolean lastColumn = col == columns.length - 1;
4045 int charsWritten = lastColumn
4046 ? copy(BreakIterator.getLineInstance(), value, textAt(row, col), indent)
4047 : copy(value, textAt(row, col), indent);
4048 value = value.substring(charsWritten);
4049 indent = 0;
4050 if (value.length > 0) { // value did not fit in column
4051 ++col; // write remainder of value in next column
4052 }
4053 if (value.length > 0 && col >= columns.length) { // we filled up all columns on this row
4054 addEmptyRow();
4055 row++;
4056 col = startColumn;
4057 indent = column.indent + indentWrappedLines;
4058 }
4059 } while (value.length > 0);
4060 return new Cell(col, row);
4061 case WRAP:
4062 BreakIterator lineBreakIterator = BreakIterator.getLineInstance();
4063 do {
4064 int charsWritten = copy(lineBreakIterator, value, textAt(row, col), indent);
4065 value = value.substring(charsWritten);
4066 indent = column.indent + indentWrappedLines;
4067 if (value.length > 0) { // value did not fit in column
4068 ++row; // write remainder of value in next row
4069 addEmptyRow();
4070 }
4071 } while (value.length > 0);
4072 return new Cell(col, row);
4073 }
4074 throw new IllegalStateException(column.overflow.toString());
4075 }
4076 private static int length(Text str) {
4077 return str.length; // TODO count some characters as double length
4078 }
4079
4080 private int copy(BreakIterator line, Text text, Text columnValue, int offset) {
4081 // Deceive the BreakIterator to ensure no line breaks after '-' character
4082 line.setText(text.plainString().replace("-", "\u00ff"));
4083 int done = 0;
4084 for (int start = line.first(), end = line.next(); end != BreakIterator.DONE; start = end, end = line.next()) {
4085 Text word = text.substring(start, end); //.replace("\u00ff", "-"); // not needed
4086 if (columnValue.maxLength >= offset + done + length(word)) {
4087 done += copy(word, columnValue, offset + done); // TODO localized length
4088 } else {
4089 break;
4090 }
4091 }
4092 if (done == 0 && length(text) > columnValue.maxLength) {
4093 // The value is a single word that is too big to be written to the column. Write as much as we can.
4094 done = copy(text, columnValue, offset);
4095 }
4096 return done;
4097 }
4098 private static int copy(Text value, Text destination, int offset) {
4099 int length = Math.min(value.length, destination.maxLength - offset);
4100 value.getStyledChars(value.from, length, destination, offset);
4101 return length;
4102 }
4103
4104 /** Copies the text representation that we built up from the options into the specified StringBuilder.
4105 * @param text the StringBuilder to write into
4106 * @return the specified StringBuilder object (to allow method chaining and a more fluid API) */
4107 public StringBuilder toString(StringBuilder text) {
4108 int columnCount = this.columns.length;
4109 StringBuilder row = new StringBuilder(usageHelpWidth);
4110 for (int i = 0; i < columnValues.size(); i++) {
4111 Text column = columnValues.get(i);
4112 row.append(column.toString());
4113 row.append(new String(spaces(columns[i % columnCount].width - column.length)));
4114 if (i % columnCount == columnCount - 1) {
4115 int lastChar = row.length() - 1;
4116 while (lastChar >= 0 && row.charAt(lastChar) == ' ') {lastChar--;} // rtrim
4117 row.setLength(lastChar + 1);
4118 text.append(row.toString()).append(System.getProperty("line.separator"));
4119 row.setLength(0);
4120 }
4121 }
4122 //if (Ansi.enabled()) { text.append(Style.reset.off()); }
4123 return text;
4124 }
4125 @Override
4126 public String toString() { return toString(new StringBuilder()).toString(); }
4127 }
4128 /** Columns define the width, indent (leading number of spaces in a column before the value) and
4129 * {@linkplain Overflow Overflow} policy of a column in a {@linkplain TextTable TextTable}. */
4130 public static class Column {
4131
4132 /** Policy for handling text that is longer than the column width:
4133 * span multiple columns, wrap to the next row, or simply truncate the portion that doesn't fit. */
4134 public enum Overflow { TRUNCATE, SPAN, WRAP }
4135
4136 /** Column width in characters */
4137 public final int width;
4138
4139 /** Indent (number of empty spaces at the start of the column preceding the text value) */
4140 public final int indent;
4141
4142 /** Policy that determines how to handle values larger than the column width. */
4143 public final Overflow overflow;
4144 public Column(int width, int indent, Overflow overflow) {
4145 this.width = width;
4146 this.indent = indent;
4147 this.overflow = Assert.notNull(overflow, "overflow");
4148 }
4149 }
4150
4151 /** All usage help message are generated with a color scheme that assigns certain styles and colors to common
4152 * parts of a usage message: the command name, options, positional parameters and option parameters.
4153 * Users may customize these styles by creating Help with a custom color scheme.
4154 * <p>Note that these options and styles may not be rendered if ANSI escape codes are not
4155 * {@linkplain Ansi#enabled() enabled}.</p>
4156 * @see Help#defaultColorScheme(Ansi)
4157 */
4158 public static class ColorScheme {
4159 public final List<IStyle> commandStyles = new ArrayList<IStyle>();
4160 public final List<IStyle> optionStyles = new ArrayList<IStyle>();
4161 public final List<IStyle> parameterStyles = new ArrayList<IStyle>();
4162 public final List<IStyle> optionParamStyles = new ArrayList<IStyle>();
4163 private final Ansi ansi;
4164
4165 /** Constructs a new ColorScheme with {@link Help.Ansi#AUTO}. */
4166 public ColorScheme() { this(Ansi.AUTO); }
4167
4168 /** Constructs a new ColorScheme with the specified Ansi enabled mode.
4169 * @param ansi whether to emit ANSI escape codes or not
4170 */
4171 public ColorScheme(Ansi ansi) {this.ansi = Assert.notNull(ansi, "ansi"); }
4172
4173 /** Adds the specified styles to the registered styles for commands in this color scheme and returns this color scheme.
4174 * @param styles the styles to add to the registered styles for commands in this color scheme
4175 * @return this color scheme to enable method chaining for a more fluent API */
4176 public ColorScheme commands(IStyle... styles) { return addAll(commandStyles, styles); }
4177 /** Adds the specified styles to the registered styles for options in this color scheme and returns this color scheme.
4178 * @param styles the styles to add to registered the styles for options in this color scheme
4179 * @return this color scheme to enable method chaining for a more fluent API */
4180 public ColorScheme options(IStyle... styles) { return addAll(optionStyles, styles);}
4181 /** Adds the specified styles to the registered styles for positional parameters in this color scheme and returns this color scheme.
4182 * @param styles the styles to add to registered the styles for parameters in this color scheme
4183 * @return this color scheme to enable method chaining for a more fluent API */
4184 public ColorScheme parameters(IStyle... styles) { return addAll(parameterStyles, styles);}
4185 /** Adds the specified styles to the registered styles for option parameters in this color scheme and returns this color scheme.
4186 * @param styles the styles to add to the registered styles for option parameters in this color scheme
4187 * @return this color scheme to enable method chaining for a more fluent API */
4188 public ColorScheme optionParams(IStyle... styles) { return addAll(optionParamStyles, styles);}
4189 /** Returns a Text with all command styles applied to the specified command string.
4190 * @param command the command string to apply the registered command styles to
4191 * @return a Text with all command styles applied to the specified command string */
4192 public Ansi.Text commandText(String command) { return ansi().apply(command, commandStyles); }
4193 /** Returns a Text with all option styles applied to the specified option string.
4194 * @param option the option string to apply the registered option styles to
4195 * @return a Text with all option styles applied to the specified option string */
4196 public Ansi.Text optionText(String option) { return ansi().apply(option, optionStyles); }
4197 /** Returns a Text with all parameter styles applied to the specified parameter string.
4198 * @param parameter the parameter string to apply the registered parameter styles to
4199 * @return a Text with all parameter styles applied to the specified parameter string */
4200 public Ansi.Text parameterText(String parameter) { return ansi().apply(parameter, parameterStyles); }
4201 /** Returns a Text with all optionParam styles applied to the specified optionParam string.
4202 * @param optionParam the option parameter string to apply the registered option parameter styles to
4203 * @return a Text with all option parameter styles applied to the specified option parameter string */
4204 public Ansi.Text optionParamText(String optionParam) { return ansi().apply(optionParam, optionParamStyles); }
4205
4206 /** Replaces colors and styles in this scheme with ones specified in system properties, and returns this scheme.
4207 * Supported property names:<ul>
4208 * <li>{@code picocli.color.commands}</li>
4209 * <li>{@code picocli.color.options}</li>
4210 * <li>{@code picocli.color.parameters}</li>
4211 * <li>{@code picocli.color.optionParams}</li>
4212 * </ul><p>Property values can be anything that {@link Help.Ansi.Style#parse(String)} can handle.</p>
4213 * @return this ColorScheme
4214 */
4215 public ColorScheme applySystemProperties() {
4216 replace(commandStyles, System.getProperty("picocli.color.commands"));
4217 replace(optionStyles, System.getProperty("picocli.color.options"));
4218 replace(parameterStyles, System.getProperty("picocli.color.parameters"));
4219 replace(optionParamStyles, System.getProperty("picocli.color.optionParams"));
4220 return this;
4221 }
4222 private void replace(List<IStyle> styles, String property) {
4223 if (property != null) {
4224 styles.clear();
4225 addAll(styles, Style.parse(property));
4226 }
4227 }
4228 private ColorScheme addAll(List<IStyle> styles, IStyle... add) {
4229 styles.addAll(Arrays.asList(add));
4230 return this;
4231 }
4232
4233 public Ansi ansi() {
4234 return ansi;
4235 }
4236 }
4237
4238 /** Creates and returns a new {@link ColorScheme} initialized with picocli default values: commands are bold,
4239 * options and parameters use a yellow foreground, and option parameters use italic.
4240 * @param ansi whether the usage help message should contain ANSI escape codes or not
4241 * @return a new default color scheme
4242 */
4243 public static ColorScheme defaultColorScheme(Ansi ansi) {
4244 return new ColorScheme(ansi)
4245 .commands(Style.bold)
4246 .options(Style.fg_yellow)
4247 .parameters(Style.fg_yellow)
4248 .optionParams(Style.italic);
4249 }
4250
4251 /** Provides methods and inner classes to support using ANSI escape codes in usage help messages. */
4252 public enum Ansi {
4253 /** Only emit ANSI escape codes if the platform supports it and system property {@code "picocli.ansi"}
4254 * is not set to any value other than {@code "true"} (case insensitive). */
4255 AUTO,
4256 /** Forced ON: always emit ANSI escape code regardless of the platform. */
4257 ON,
4258 /** Forced OFF: never emit ANSI escape code regardless of the platform. */
4259 OFF;
4260 static Text EMPTY_TEXT = OFF.new Text(0);
4261 static final boolean isWindows = System.getProperty("os.name").startsWith("Windows");
4262 static final boolean isXterm = System.getenv("TERM") != null && System.getenv("TERM").startsWith("xterm");
4263 static final boolean ISATTY = calcTTY();
4264
4265 // http://stackoverflow.com/questions/1403772/how-can-i-check-if-a-java-programs-input-output-streams-are-connected-to-a-term
4266 static final boolean calcTTY() {
4267 if (isWindows && isXterm) { return true; } // Cygwin uses pseudo-tty and console is always null...
4268 try { return System.class.getDeclaredMethod("console").invoke(null) != null; }
4269 catch (Throwable reflectionFailed) { return true; }
4270 }
4271 private static boolean ansiPossible() { return ISATTY && (!isWindows || isXterm); }
4272
4273 /** Returns {@code true} if ANSI escape codes should be emitted, {@code false} otherwise.
4274 * @return ON: {@code true}, OFF: {@code false}, AUTO: if system property {@code "picocli.ansi"} is
4275 * defined then return its boolean value, otherwise return whether the platform supports ANSI escape codes */
4276 public boolean enabled() {
4277 if (this == ON) { return true; }
4278 if (this == OFF) { return false; }
4279 return (System.getProperty("picocli.ansi") == null ? ansiPossible() : Boolean.getBoolean("picocli.ansi"));
4280 }
4281
4282 /** Defines the interface for an ANSI escape sequence. */
4283 public interface IStyle {
4284
4285 /** The Control Sequence Introducer (CSI) escape sequence {@value}. */
4286 String CSI = "\u001B[";
4287
4288 /** Returns the ANSI escape code for turning this style on.
4289 * @return the ANSI escape code for turning this style on */
4290 String on();
4291
4292 /** Returns the ANSI escape code for turning this style off.
4293 * @return the ANSI escape code for turning this style off */
4294 String off();
4295 }
4296
4297 /**
4298 * A set of pre-defined ANSI escape code styles and colors, and a set of convenience methods for parsing
4299 * text with embedded markup style names, as well as convenience methods for converting
4300 * styles to strings with embedded escape codes.
4301 */
4302 public enum Style implements IStyle {
4303 reset(0, 0), bold(1, 21), faint(2, 22), italic(3, 23), underline(4, 24), blink(5, 25), reverse(7, 27),
4304 fg_black(30, 39), fg_red(31, 39), fg_green(32, 39), fg_yellow(33, 39), fg_blue(34, 39), fg_magenta(35, 39), fg_cyan(36, 39), fg_white(37, 39),
4305 bg_black(40, 49), bg_red(41, 49), bg_green(42, 49), bg_yellow(43, 49), bg_blue(44, 49), bg_magenta(45, 49), bg_cyan(46, 49), bg_white(47, 49),
4306 ;
4307 private final int startCode;
4308 private final int endCode;
4309
4310 Style(int startCode, int endCode) {this.startCode = startCode; this.endCode = endCode; }
4311 @Override
4312 public String on() { return CSI + startCode + "m"; }
4313 @Override
4314 public String off() { return CSI + endCode + "m"; }
4315
4316 /** Returns the concatenated ANSI escape codes for turning all specified styles on.
4317 * @param styles the styles to generate ANSI escape codes for
4318 * @return the concatenated ANSI escape codes for turning all specified styles on */
4319 public static String on(IStyle... styles) {
4320 StringBuilder result = new StringBuilder();
4321 for (IStyle style : styles) {
4322 result.append(style.on());
4323 }
4324 return result.toString();
4325 }
4326 /** Returns the concatenated ANSI escape codes for turning all specified styles off.
4327 * @param styles the styles to generate ANSI escape codes for
4328 * @return the concatenated ANSI escape codes for turning all specified styles off */
4329 public static String off(IStyle... styles) {
4330 StringBuilder result = new StringBuilder();
4331 for (IStyle style : styles) {
4332 result.append(style.off());
4333 }
4334 return result.toString();
4335 }
4336 /** Parses the specified style markup and returns the associated style.
4337 * The markup may be one of the Style enum value names, or it may be one of the Style enum value
4338 * names when {@code "fg_"} is prepended, or it may be one of the indexed colors in the 256 color palette.
4339 * @param str the case-insensitive style markup to convert, e.g. {@code "blue"} or {@code "fg_blue"},
4340 * or {@code "46"} (indexed color) or {@code "0;5;0"} (RGB components of an indexed color)
4341 * @return the IStyle for the specified converter
4342 */
4343 public static IStyle fg(String str) {
4344 try { return Style.valueOf(str.toLowerCase(ENGLISH)); } catch (Exception ignored) {}
4345 try { return Style.valueOf("fg_" + str.toLowerCase(ENGLISH)); } catch (Exception ignored) {}
4346 return new Palette256Color(true, str);
4347 }
4348 /** Parses the specified style markup and returns the associated style.
4349 * The markup may be one of the Style enum value names, or it may be one of the Style enum value
4350 * names when {@code "bg_"} is prepended, or it may be one of the indexed colors in the 256 color palette.
4351 * @param str the case-insensitive style markup to convert, e.g. {@code "blue"} or {@code "bg_blue"},
4352 * or {@code "46"} (indexed color) or {@code "0;5;0"} (RGB components of an indexed color)
4353 * @return the IStyle for the specified converter
4354 */
4355 public static IStyle bg(String str) {
4356 try { return Style.valueOf(str.toLowerCase(ENGLISH)); } catch (Exception ignored) {}
4357 try { return Style.valueOf("bg_" + str.toLowerCase(ENGLISH)); } catch (Exception ignored) {}
4358 return new Palette256Color(false, str);
4359 }
4360 /** Parses the specified comma-separated sequence of style descriptors and returns the associated
4361 * styles. For each markup, strings starting with {@code "bg("} are delegated to
4362 * {@link #bg(String)}, others are delegated to {@link #bg(String)}.
4363 * @param commaSeparatedCodes one or more descriptors, e.g. {@code "bg(blue),underline,red"}
4364 * @return an array with all styles for the specified descriptors
4365 */
4366 public static IStyle[] parse(String commaSeparatedCodes) {
4367 String[] codes = commaSeparatedCodes.split(",");
4368 IStyle[] styles = new IStyle[codes.length];
4369 for(int i = 0; i < codes.length; ++i) {
4370 if (codes[i].toLowerCase(ENGLISH).startsWith("fg(")) {
4371 int end = codes[i].indexOf(')');
4372 styles[i] = Style.fg(codes[i].substring(3, end < 0 ? codes[i].length() : end));
4373 } else if (codes[i].toLowerCase(ENGLISH).startsWith("bg(")) {
4374 int end = codes[i].indexOf(')');
4375 styles[i] = Style.bg(codes[i].substring(3, end < 0 ? codes[i].length() : end));
4376 } else {
4377 styles[i] = Style.fg(codes[i]);
4378 }
4379 }
4380 return styles;
4381 }
4382 }
4383
4384 /** Defines a palette map of 216 colors: 6 * 6 * 6 cube (216 colors):
4385 * 16 + 36 * r + 6 * g + b (0 <= r, g, b <= 5). */
4386 static class Palette256Color implements IStyle {
4387 private final int fgbg;
4388 private final int color;
4389
4390 Palette256Color(boolean foreground, String color) {
4391 this.fgbg = foreground ? 38 : 48;
4392 String[] rgb = color.split(";");
4393 if (rgb.length == 3) {
4394 this.color = 16 + 36 * Integer.decode(rgb[0]) + 6 * Integer.decode(rgb[1]) + Integer.decode(rgb[2]);
4395 } else {
4396 this.color = Integer.decode(color);
4397 }
4398 }
4399 @Override
4400 public String on() { return String.format(CSI + "%d;5;%dm", fgbg, color); }
4401 @Override
4402 public String off() { return CSI + (fgbg + 1) + "m"; }
4403 }
4404 private static class StyledSection {
4405 int startIndex, length;
4406 String startStyles, endStyles;
4407 StyledSection(int start, int len, String style1, String style2) {
4408 startIndex = start; length = len; startStyles = style1; endStyles = style2;
4409 }
4410 StyledSection withStartIndex(int newStart) {
4411 return new StyledSection(newStart, length, startStyles, endStyles);
4412 }
4413 }
4414
4415 /**
4416 * Returns a new Text object where all the specified styles are applied to the full length of the
4417 * specified plain text.
4418 * @param plainText the string to apply all styles to. Must not contain markup!
4419 * @param styles the styles to apply to the full plain text
4420 * @return a new Text object
4421 */
4422 public Text apply(String plainText, List<IStyle> styles) {
4423 if (plainText.length() == 0) { return new Text(0); }
4424 Text result = new Text(plainText.length());
4425 IStyle[] all = styles.toArray(new IStyle[styles.size()]);
4426 result.sections.add(new StyledSection(
4427 0, plainText.length(), Style.on(all), Style.off(reverse(all)) + Style.reset.off()));
4428 result.plain.append(plainText);
4429 result.length = result.plain.length();
4430 return result;
4431 }
4432
4433 private static <T> T[] reverse(T[] all) {
4434 for (int i = 0; i < all.length / 2; i++) {
4435 T temp = all[i];
4436 all[i] = all[all.length - i - 1];
4437 all[all.length - i - 1] = temp;
4438 }
4439 return all;
4440 }
4441 /** Encapsulates rich text with styles and colors. Text objects may be constructed with Strings containing
4442 * markup like {@code @|bg(red),white,underline some text|@}, and this class converts the markup to ANSI
4443 * escape codes.
4444 * <p>
4445 * Internally keeps both an enriched and a plain text representation to allow layout components to calculate
4446 * text width while remaining unaware of the embedded ANSI escape codes.</p> */
4447 public class Text implements Cloneable {
4448 private final int maxLength;
4449 private int from;
4450 private int length;
4451 private StringBuilder plain = new StringBuilder();
4452 private List<StyledSection> sections = new ArrayList<StyledSection>();
4453
4454 /** Constructs a Text with the specified max length (for use in a TextTable Column).
4455 * @param maxLength max length of this text */
4456 public Text(int maxLength) { this.maxLength = maxLength; }
4457
4458 /**
4459 * Constructs a Text with the specified String, which may contain markup like
4460 * {@code @|bg(red),white,underline some text|@}.
4461 * @param input the string with markup to parse
4462 */
4463 public Text(String input) {
4464 maxLength = -1;
4465 plain.setLength(0);
4466 int i = 0;
4467
4468 while (true) {
4469 int j = input.indexOf("@|", i);
4470 if (j == -1) {
4471 if (i == 0) {
4472 plain.append(input);
4473 length = plain.length();
4474 return;
4475 }
4476 plain.append(input.substring(i, input.length()));
4477 length = plain.length();
4478 return;
4479 }
4480 plain.append(input.substring(i, j));
4481 int k = input.indexOf("|@", j);
4482 if (k == -1) {
4483 plain.append(input);
4484 length = plain.length();
4485 return;
4486 }
4487
4488 j += 2;
4489 String spec = input.substring(j, k);
4490 String[] items = spec.split(" ", 2);
4491 if (items.length == 1) {
4492 plain.append(input);
4493 length = plain.length();
4494 return;
4495 }
4496
4497 IStyle[] styles = Style.parse(items[0]);
4498 addStyledSection(plain.length(), items[1].length(),
4499 Style.on(styles), Style.off(reverse(styles)) + Style.reset.off());
4500 plain.append(items[1]);
4501 i = k + 2;
4502 }
4503 }
4504 private void addStyledSection(int start, int length, String startStyle, String endStyle) {
4505 sections.add(new StyledSection(start, length, startStyle, endStyle));
4506 }
4507 @Override
4508 public Object clone() {
4509 try { return super.clone(); } catch (CloneNotSupportedException e) { throw new IllegalStateException(e); }
4510 }
4511
4512 public Text[] splitLines() {
4513 List<Text> result = new ArrayList<Text>();
4514 boolean trailingEmptyString = false;
4515 int start = 0, end = 0;
4516 for (int i = 0; i < plain.length(); i++, end = i) {
4517 char c = plain.charAt(i);
4518 boolean eol = c == '\n';
4519 eol |= (c == '\r' && i + 1 < plain.length() && plain.charAt(i + 1) == '\n' && ++i > 0); // \r\n
4520 eol |= c == '\r';
4521 if (eol) {
4522 result.add(this.substring(start, end));
4523 trailingEmptyString = i == plain.length() - 1;
4524 start = i + 1;
4525 }
4526 }
4527 if (start < plain.length() || trailingEmptyString) {
4528 result.add(this.substring(start, plain.length()));
4529 }
4530 return result.toArray(new Text[result.size()]);
4531 }
4532
4533 /** Returns a new {@code Text} instance that is a substring of this Text. Does not modify this instance!
4534 * @param start index in the plain text where to start the substring
4535 * @return a new Text instance that is a substring of this Text */
4536 public Text substring(int start) {
4537 return substring(start, length);
4538 }
4539
4540 /** Returns a new {@code Text} instance that is a substring of this Text. Does not modify this instance!
4541 * @param start index in the plain text where to start the substring
4542 * @param end index in the plain text where to end the substring
4543 * @return a new Text instance that is a substring of this Text */
4544 public Text substring(int start, int end) {
4545 Text result = (Text) clone();
4546 result.from = from + start;
4547 result.length = end - start;
4548 return result;
4549 }
4550 /** Returns a new {@code Text} instance with the specified text appended. Does not modify this instance!
4551 * @param string the text to append
4552 * @return a new Text instance */
4553 public Text append(String string) {
4554 return append(new Text(string));
4555 }
4556
4557 /** Returns a new {@code Text} instance with the specified text appended. Does not modify this instance!
4558 * @param other the text to append
4559 * @return a new Text instance */
4560 public Text append(Text other) {
4561 Text result = (Text) clone();
4562 result.plain = new StringBuilder(plain.toString().substring(from, from + length));
4563 result.from = 0;
4564 result.sections = new ArrayList<StyledSection>();
4565 for (StyledSection section : sections) {
4566 result.sections.add(section.withStartIndex(section.startIndex - from));
4567 }
4568 result.plain.append(other.plain.toString().substring(other.from, other.from + other.length));
4569 for (StyledSection section : other.sections) {
4570 int index = result.length + section.startIndex - other.from;
4571 result.sections.add(section.withStartIndex(index));
4572 }
4573 result.length = result.plain.length();
4574 return result;
4575 }
4576
4577 /**
4578 * Copies the specified substring of this Text into the specified destination, preserving the markup.
4579 * @param from start of the substring
4580 * @param length length of the substring
4581 * @param destination destination Text to modify
4582 * @param offset indentation (padding)
4583 */
4584 public void getStyledChars(int from, int length, Text destination, int offset) {
4585 if (destination.length < offset) {
4586 for (int i = destination.length; i < offset; i++) {
4587 destination.plain.append(' ');
4588 }
4589 destination.length = offset;
4590 }
4591 for (StyledSection section : sections) {
4592 destination.sections.add(section.withStartIndex(section.startIndex - from + destination.length));
4593 }
4594 destination.plain.append(plain.toString().substring(from, from + length));
4595 destination.length = destination.plain.length();
4596 }
4597 /** Returns the plain text without any formatting.
4598 * @return the plain text without any formatting */
4599 public String plainString() { return plain.toString().substring(from, from + length); }
4600
4601 @Override
4602 public boolean equals(Object obj) { return toString().equals(String.valueOf(obj)); }
4603 @Override
4604 public int hashCode() { return toString().hashCode(); }
4605
4606 /** Returns a String representation of the text with ANSI escape codes embedded, unless ANSI is
4607 * {@linkplain Ansi#enabled()} not enabled}, in which case the plain text is returned.
4608 * @return a String representation of the text with ANSI escape codes embedded (if enabled) */
4609 @Override
4610 public String toString() {
4611 if (!Ansi.this.enabled()) {
4612 return plain.toString().substring(from, from + length);
4613 }
4614 if (length == 0) { return ""; }
4615 StringBuilder sb = new StringBuilder(plain.length() + 20 * sections.size());
4616 StyledSection current = null;
4617 int end = Math.min(from + length, plain.length());
4618 for (int i = from; i < end; i++) {
4619 StyledSection section = findSectionContaining(i);
4620 if (section != current) {
4621 if (current != null) { sb.append(current.endStyles); }
4622 if (section != null) { sb.append(section.startStyles); }
4623 current = section;
4624 }
4625 sb.append(plain.charAt(i));
4626 }
4627 if (current != null) { sb.append(current.endStyles); }
4628 return sb.toString();
4629 }
4630
4631 private StyledSection findSectionContaining(int index) {
4632 for (StyledSection section : sections) {
4633 if (index >= section.startIndex && index < section.startIndex + section.length) {
4634 return section;
4635 }
4636 }
4637 return null;
4638 }
4639 }
4640 }
4641 }
4642
4643 /**
4644 * Utility class providing some defensive coding convenience methods.
4645 */
4646 private static final class Assert {
4647 /**
4648 * Throws a NullPointerException if the specified object is null.
4649 * @param object the object to verify
4650 * @param description error message
4651 * @param <T> type of the object to check
4652 * @return the verified object
4653 */
4654 static <T> T notNull(T object, String description) {
4655 if (object == null) {
4656 throw new NullPointerException(description);
4657 }
4658 return object;
4659 }
4660 private Assert() {} // private constructor: never instantiate
4661 }
4662 private enum TraceLevel { OFF, WARN, INFO, DEBUG;
4663 public boolean isEnabled(TraceLevel other) { return ordinal() >= other.ordinal(); }
4664 private void print(Tracer tracer, String msg, Object... params) {
4665 if (tracer.level.isEnabled(this)) { tracer.stream.printf(prefix(msg), params); }
4666 }
4667 private String prefix(String msg) { return "[picocli " + this + "] " + msg; }
4668 static TraceLevel lookup(String key) { return key == null ? WARN : empty(key) || "true".equalsIgnoreCase(key) ? INFO : valueOf(key); }
4669 }
4670 private static class Tracer {
4671 TraceLevel level = TraceLevel.lookup(System.getProperty("picocli.trace"));
4672 PrintStream stream = System.err;
4673 void warn (String msg, Object... params) { TraceLevel.WARN.print(this, msg, params); }
4674 void info (String msg, Object... params) { TraceLevel.INFO.print(this, msg, params); }
4675 void debug(String msg, Object... params) { TraceLevel.DEBUG.print(this, msg, params); }
4676 boolean isWarn() { return level.isEnabled(TraceLevel.WARN); }
4677 boolean isInfo() { return level.isEnabled(TraceLevel.INFO); }
4678 boolean isDebug() { return level.isEnabled(TraceLevel.DEBUG); }
4679 }
4680 /** Base class of all exceptions thrown by {@code picocli.CommandLine}.
4681 * @since 2.0 */
4682 public static class PicocliException extends RuntimeException {
4683 private static final long serialVersionUID = -2574128880125050818L;
4684 public PicocliException(String msg) { super(msg); }
4685 public PicocliException(String msg, Exception ex) { super(msg, ex); }
4686 }
4687 /** Exception indicating a problem during {@code CommandLine} initialization.
4688 * @since 2.0 */
4689 public static class InitializationException extends PicocliException {
4690 private static final long serialVersionUID = 8423014001666638895L;
4691 public InitializationException(String msg) { super(msg); }
4692 public InitializationException(String msg, Exception ex) { super(msg, ex); }
4693 }
4694 /** Exception indicating a problem while invoking a command or subcommand.
4695 * @since 2.0 */
4696 public static class ExecutionException extends PicocliException {
4697 private static final long serialVersionUID = 7764539594267007998L;
4698 private final CommandLine commandLine;
4699 public ExecutionException(CommandLine commandLine, String msg) {
4700 super(msg);
4701 this.commandLine = Assert.notNull(commandLine, "commandLine");
4702 }
4703 public ExecutionException(CommandLine commandLine, String msg, Exception ex) {
4704 super(msg, ex);
4705 this.commandLine = Assert.notNull(commandLine, "commandLine");
4706 }
4707 /** Returns the {@code CommandLine} object for the (sub)command that could not be invoked.
4708 * @return the {@code CommandLine} object for the (sub)command where invocation failed.
4709 */
4710 public CommandLine getCommandLine() { return commandLine; }
4711 }
4712
4713 /** Exception thrown by {@link ITypeConverter} implementations to indicate a String could not be converted. */
4714 public static class TypeConversionException extends PicocliException {
4715 private static final long serialVersionUID = 4251973913816346114L;
4716 public TypeConversionException(String msg) { super(msg); }
4717 }
4718 /** Exception indicating something went wrong while parsing command line options. */
4719 public static class ParameterException extends PicocliException {
4720 private static final long serialVersionUID = 1477112829129763139L;
4721 private final CommandLine commandLine;
4722
4723 /** Constructs a new ParameterException with the specified CommandLine and error message.
4724 * @param commandLine the command or subcommand whose input was invalid
4725 * @param msg describes the problem
4726 * @since 2.0 */
4727 public ParameterException(CommandLine commandLine, String msg) {
4728 super(msg);
4729 this.commandLine = Assert.notNull(commandLine, "commandLine");
4730 }
4731 /** Constructs a new ParameterException with the specified CommandLine and error message.
4732 * @param commandLine the command or subcommand whose input was invalid
4733 * @param msg describes the problem
4734 * @param ex the exception that caused this ParameterException
4735 * @since 2.0 */
4736 public ParameterException(CommandLine commandLine, String msg, Exception ex) {
4737 super(msg, ex);
4738 this.commandLine = Assert.notNull(commandLine, "commandLine");
4739 }
4740
4741 /** Returns the {@code CommandLine} object for the (sub)command whose input could not be parsed.
4742 * @return the {@code CommandLine} object for the (sub)command where parsing failed.
4743 * @since 2.0
4744 */
4745 public CommandLine getCommandLine() { return commandLine; }
4746
4747 private static ParameterException create(CommandLine cmd, Exception ex, String arg, int i, String[] args) {
4748 String msg = ex.getClass().getSimpleName() + ": " + ex.getLocalizedMessage()
4749 + " while processing argument at or before arg[" + i + "] '" + arg + "' in " + Arrays.toString(args) + ": " + ex.toString();
4750 return new ParameterException(cmd, msg, ex);
4751 }
4752 }
4753 /**
4754 * Exception indicating that a required parameter was not specified.
4755 */
4756 public static class MissingParameterException extends ParameterException {
4757 private static final long serialVersionUID = 5075678535706338753L;
4758 public MissingParameterException(CommandLine commandLine, String msg) {
4759 super(commandLine, msg);
4760 }
4761
4762 private static MissingParameterException create(CommandLine cmd, Collection<Field> missing, String separator) {
4763 if (missing.size() == 1) {
4764 return new MissingParameterException(cmd, "Missing required option '"
4765 + describe(missing.iterator().next(), separator) + "'");
4766 }
4767 List<String> names = new ArrayList<String>(missing.size());
4768 for (Field field : missing) {
4769 names.add(describe(field, separator));
4770 }
4771 return new MissingParameterException(cmd, "Missing required options " + names.toString());
4772 }
4773 private static String describe(Field field, String separator) {
4774 String prefix = (field.isAnnotationPresent(Option.class))
4775 ? field.getAnnotation(Option.class).names()[0] + separator
4776 : "params[" + field.getAnnotation(Parameters.class).index() + "]" + separator;
4777 return prefix + Help.DefaultParamLabelRenderer.renderParameterName(field);
4778 }
4779 }
4780
4781 /**
4782 * Exception indicating that multiple fields have been annotated with the same Option name.
4783 */
4784 public static class DuplicateOptionAnnotationsException extends InitializationException {
4785 private static final long serialVersionUID = -3355128012575075641L;
4786 public DuplicateOptionAnnotationsException(String msg) { super(msg); }
4787
4788 private static DuplicateOptionAnnotationsException create(String name, Field field1, Field field2) {
4789 return new DuplicateOptionAnnotationsException("Option name '" + name + "' is used by both " +
4790 field1.getDeclaringClass().getName() + "." + field1.getName() + " and " +
4791 field2.getDeclaringClass().getName() + "." + field2.getName());
4792 }
4793 }
4794 /** Exception indicating that there was a gap in the indices of the fields annotated with {@link Parameters}. */
4795 public static class ParameterIndexGapException extends InitializationException {
4796 private static final long serialVersionUID = -1520981133257618319L;
4797 public ParameterIndexGapException(String msg) { super(msg); }
4798 }
4799 /** Exception indicating that a command line argument could not be mapped to any of the fields annotated with
4800 * {@link Option} or {@link Parameters}. */
4801 public static class UnmatchedArgumentException extends ParameterException {
4802 private static final long serialVersionUID = -8700426380701452440L;
4803 public UnmatchedArgumentException(CommandLine commandLine, String msg) { super(commandLine, msg); }
4804 public UnmatchedArgumentException(CommandLine commandLine, Stack<String> args) { this(commandLine, new ArrayList<String>(reverse(args))); }
4805 public UnmatchedArgumentException(CommandLine commandLine, List<String> args) { this(commandLine, "Unmatched argument" + (args.size() == 1 ? " " : "s ") + args); }
4806 }
4807 /** Exception indicating that more values were specified for an option or parameter than its {@link Option#arity() arity} allows. */
4808 public static class MaxValuesforFieldExceededException extends ParameterException {
4809 private static final long serialVersionUID = 6536145439570100641L;
4810 public MaxValuesforFieldExceededException(CommandLine commandLine, String msg) { super(commandLine, msg); }
4811 }
4812 /** Exception indicating that an option for a single-value option field has been specified multiple times on the command line. */
4813 public static class OverwrittenOptionException extends ParameterException {
4814 private static final long serialVersionUID = 1338029208271055776L;
4815 public OverwrittenOptionException(CommandLine commandLine, String msg) { super(commandLine, msg); }
4816 }
4817 /**
4818 * Exception indicating that an annotated field had a type for which no {@link ITypeConverter} was
4819 * {@linkplain #registerConverter(Class, ITypeConverter) registered}.
4820 */
4821 public static class MissingTypeConverterException extends ParameterException {
4822 private static final long serialVersionUID = -6050931703233083760L;
4823 public MissingTypeConverterException(CommandLine commandLine, String msg) { super(commandLine, msg); }
4824 }
4825 }