001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.tools.picocli; 018 019import java.io.File; 020import java.io.PrintStream; 021import java.lang.annotation.ElementType; 022import java.lang.annotation.Retention; 023import java.lang.annotation.RetentionPolicy; 024import java.lang.annotation.Target; 025import java.lang.reflect.Array; 026import java.lang.reflect.Constructor; 027import java.lang.reflect.Field; 028import java.lang.reflect.ParameterizedType; 029import java.lang.reflect.Type; 030import java.lang.reflect.WildcardType; 031import java.math.BigDecimal; 032import java.math.BigInteger; 033import java.net.InetAddress; 034import java.net.MalformedURLException; 035import java.net.URI; 036import java.net.URISyntaxException; 037import java.net.URL; 038import java.nio.charset.Charset; 039import java.nio.file.Path; 040import java.nio.file.Paths; 041import java.sql.Time; 042import java.text.BreakIterator; 043import java.text.ParseException; 044import java.text.SimpleDateFormat; 045import java.util.ArrayList; 046import java.util.Arrays; 047import java.util.Collection; 048import java.util.Collections; 049import java.util.Comparator; 050import java.util.Date; 051import java.util.HashMap; 052import java.util.HashSet; 053import java.util.LinkedHashMap; 054import java.util.LinkedHashSet; 055import java.util.LinkedList; 056import java.util.List; 057import java.util.Map; 058import java.util.Queue; 059import java.util.Set; 060import java.util.SortedSet; 061import java.util.Stack; 062import java.util.TreeSet; 063import java.util.UUID; 064import java.util.concurrent.Callable; 065import java.util.regex.Pattern; 066 067import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.IStyle; 068import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.Style; 069import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.Text; 070 071import static java.util.Locale.ENGLISH; 072import static org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Column.Overflow.SPAN; 073import static org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Column.Overflow.TRUNCATE; 074import static org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Column.Overflow.WRAP; 075 076/** 077 * <p> 078 * CommandLine interpreter that uses reflection to initialize an annotated domain object with values obtained from the 079 * command line arguments. 080 * </p><h2>Example</h2> 081 * <pre>import static picocli.CommandLine.*; 082 * 083 * @Command(header = "Encrypt FILE(s), or standard input, to standard output or to the output file.", 084 * version = "v1.2.3") 085 * public class Encrypt { 086 * 087 * @Parameters(type = File.class, description = "Any number of input files") 088 * private List<File> files = new ArrayList<File>(); 089 * 090 * @Option(names = { "-o", "--out" }, description = "Output file (default: print to console)") 091 * private File outputFile; 092 * 093 * @Option(names = { "-v", "--verbose"}, description = "Verbosely list files processed") 094 * private boolean verbose; 095 * 096 * @Option(names = { "-h", "--help", "-?", "-help"}, usageHelp = true, description = "Display this help and exit") 097 * private boolean help; 098 * 099 * @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 */ 133public 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 final List<String> unmatchedArguments = new ArrayList<String>(); 143 private CommandLine parent; 144 private boolean usageHelpRequested; 145 private boolean versionHelpRequested; 146 private final 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(final 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(final String name, final Object command) { 201 final 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(final boolean newValue) { 263 this.overwrittenOptionsAllowed = newValue; 264 for (final 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(final boolean newValue) { 292 this.unmatchedArgumentsAllowed = newValue; 293 for (final 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(final T command, final String... args) { 329 final 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(final 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(final ParameterException ex, final PrintStream out, final Help.Ansi ansi, final 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(final List<CommandLine> parsedCommands, final PrintStream out, final Help.Ansi ansi) { 433 for (final 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(final CommandLine parsed) { 445 final Object command = parsed.getCommand(); 446 if (command instanceof Runnable) { 447 try { 448 ((Runnable) command).run(); 449 return null; 450 } catch (final 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 (final 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(final List<CommandLine> parsedCommands, final PrintStream out, final 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(final List<CommandLine> parsedCommands, final PrintStream out, final Help.Ansi ansi) { 537 if (printHelpIfRequested(parsedCommands, out, ansi)) { return Collections.emptyList(); } 538 final 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(final List<CommandLine> parsedCommands, final PrintStream out, final Help.Ansi ansi) { 563 if (printHelpIfRequested(parsedCommands, out, ansi)) { 564 return null; 565 } 566 final List<Object> result = new ArrayList<Object>(); 567 for (final 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(final IParseResultHandler handler, final PrintStream out, final 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(final IParseResultHandler handler, final PrintStream out, final Help.Ansi ansi, final IExceptionHandler exceptionHandler, final String... args) { 656 try { 657 final List<CommandLine> result = parse(args); 658 return handler.handleParseResult(result, out, ansi); 659 } catch (final 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(final Object command, final 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(final Object command, final PrintStream out, final 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(final Object command, final PrintStream out, final 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(final 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(final PrintStream out, final 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(final PrintStream out, final Help.ColorScheme colorScheme) { 748 final 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 final 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(final 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(final PrintStream out, final Help.Ansi ansi) { 794 for (final 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(final PrintStream out, final Help.Ansi ansi, final Object... params) { 812 for (final 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(final C callable, final PrintStream out, final 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(final C callable, final PrintStream out, final Help.Ansi ansi, final String... args) { 884 final CommandLine cmd = new CommandLine(callable); // validate command outside of try-catch 885 final 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(final R runnable, final PrintStream out, final 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(final R runnable, final PrintStream out, final Help.Ansi ansi, final String... args) { 952 final 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(final Class<K> cls, final ITypeConverter<K> converter) { 1000 interpreter.converterRegistry.put(Assert.notNull(cls, "class"), Assert.notNull(converter, "converter")); 1001 for (final 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(final 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(final String commandName) { 1034 this.commandName = Assert.notNull(commandName, "commandName"); 1035 return this; 1036 } 1037 private static boolean empty(final String str) { return str == null || str.trim().length() == 0; } 1038 private static boolean empty(final Object[] array) { return array == null || array.length == 0; } 1039 private static boolean empty(final Text txt) { return txt == null || txt.plain.toString().trim().length() == 0; } 1040 private static String str(final String[] arr, final int i) { return (arr == null || arr.length == 0) ? "" : arr[i]; } 1041 private static boolean isBoolean(final Class<?> type) { return type == Boolean.class || type == Boolean.TYPE; } 1042 private static CommandLine toCommandLine(final Object obj) { return obj instanceof CommandLine ? (CommandLine) obj : new CommandLine(obj);} 1043 private static boolean isMultiValue(final Field field) { return isMultiValue(field.getType()); } 1044 private static boolean isMultiValue(final Class<?> cls) { return cls.isArray() || Collection.class.isAssignableFrom(cls) || Map.class.isAssignableFrom(cls); } 1045 private static Class<?>[] getTypeAttribute(final Field field) { 1046 final 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 final Type type = field.getGenericType(); // e.g. Map<Long, ? extends Number> 1051 if (type instanceof ParameterizedType) { 1052 final ParameterizedType parameterizedType = (ParameterizedType) type; 1053 final Type[] paramTypes = parameterizedType.getActualTypeArguments(); // e.g. ? extends Number 1054 final 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 final WildcardType wildcardType = (WildcardType) paramTypes[i]; 1059 final Type[] lower = wildcardType.getLowerBounds(); // e.g. [] 1060 if (lower.length > 0 && lower[0] instanceof Class) { result[i] = (Class<?>) lower[0]; continue; } 1061 final 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(final int min, final int max, final boolean variable, final boolean unspecified, final 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(final 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(final 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(final 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(final Range result, final 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(final Field field) { 1702 final 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(final 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(final Field field) { 1719 final Range arity = parameterArity(field); 1720 if (!isMultiValue(field)) { return arity; } 1721 final 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 final 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 final Range result = new Range(min, max, variable, unspecified, range); 1751 return result; 1752 } 1753 private static int parseInt(final String str, final int defaultValue) { 1754 try { 1755 return Integer.parseInt(str); 1756 } catch (final 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(final 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(final 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(final int value) { return min <= value && max >= value; } 1778 1779 @Override 1780 public boolean equals(final Object object) { 1781 if (!(object instanceof Range)) { return false; } 1782 final 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(final Range other) { 1795 final int result = min - other.min; 1796 return (result == 0) ? max - other.max : result; 1797 } 1798 } 1799 static void init(final Class<?> cls, 1800 final List<Field> requiredFields, 1801 final Map<String, Field> optionName2Field, 1802 final Map<Character, Field> singleCharOption2Field, 1803 final List<Field> positionalParametersFields) { 1804 final Field[] declaredFields = cls.getDeclaredFields(); 1805 for (final Field field : declaredFields) { 1806 field.setAccessible(true); 1807 if (field.isAnnotationPresent(Option.class)) { 1808 final Option option = field.getAnnotation(Option.class); 1809 if (option.required()) { 1810 requiredFields.add(field); 1811 } 1812 for (final String name : option.names()) { // cannot be null or empty 1813 final 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 final char flag = name.charAt(1); 1819 final 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 final Range arity = Range.parameterArity(field); 1833 if (arity.min > 0) { 1834 requiredFields.add(field); 1835 } 1836 } 1837 } 1838 } 1839 static void validatePositionalParameters(final List<Field> positionalParametersFields) { 1840 int min = 0; 1841 for (final Field field : positionalParametersFields) { 1842 final 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(final 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(final 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 final 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 (final Class<?> sub : cmd.subcommands()) { 1919 final 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 final Constructor<?> constructor = sub.getDeclaredConstructor(); 1926 constructor.setAccessible(true); 1927 final CommandLine commandLine = toCommandLine(constructor.newInstance()); 1928 commandLine.parent = CommandLine.this; 1929 commands.put(subCommand.name(), commandLine); 1930 } 1931 catch (final InitializationException ex) { throw ex; } 1932 catch (final NoSuchMethodException ex) { throw new InitializationException("Cannot instantiate subcommand " + 1933 sub.getName() + ": the class has no constructor", ex); } 1934 catch (final 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(final 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 final Stack<String> arguments = new Stack<String>(); 1963 for (int i = args.length - 1; i >= 0; i--) { 1964 arguments.push(args[i]); 1965 } 1966 final List<CommandLine> result = new ArrayList<CommandLine>(); 1967 parse(result, arguments, args); 1968 return result; 1969 } 1970 1971 private void parse(final List<CommandLine> parsedCommands, final Stack<String> argumentStack, final 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 final 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 final List<Field> required = new ArrayList<Field>(requiredFields); 1981 final Set<Field> initialized = new HashSet<Field>(); 1982 Collections.sort(required, new PositionalParametersSorter()); 1983 try { 1984 processArguments(parsedCommands, argumentStack, required, initialized, originalArgs); 1985 } catch (final ParameterException ex) { 1986 throw ex; 1987 } catch (final Exception ex) { 1988 final int offendingArgIndex = originalArgs.length - argumentStack.size() - 1; 1989 final 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 (final 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(final List<CommandLine> parsedCommands, 2008 final Stack<String> args, 2009 final Collection<Field> required, 2010 final Set<Field> initialized, 2011 final 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 final int separatorIndex = arg.indexOf(separator); 2049 if (separatorIndex > 0) { 2050 final 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 final 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(final String arg) { 2085 int count = 0; 2086 for (final 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 final 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(final String arg) {final Stack<String> args = new Stack<String>(); args.add(arg); handleUnmatchedArguments(args);} 2096 private void handleUnmatchedArguments(final Stack<String> args) { 2097 while (!args.isEmpty()) { unmatchedArguments.add(args.pop()); } // addAll would give args in reverse order 2098 } 2099 2100 private void processRemainderAsPositionalParameters(final Collection<Field> required, final Set<Field> initialized, final Stack<String> args) throws Exception { 2101 while (!args.empty()) { 2102 processPositionalParameter(required, initialized, args); 2103 } 2104 } 2105 private void processPositionalParameter(final Collection<Field> required, final Set<Field> initialized, final 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 (final Field positionalParam : positionalParametersFields) { 2109 final Range indexRange = Range.parameterIndex(positionalParam); 2110 if (!indexRange.contains(position)) { 2111 continue; 2112 } 2113 @SuppressWarnings("unchecked") 2114 final 2115 Stack<String> argsCopy = (Stack<String>) args.clone(); 2116 final Range arity = Range.parameterArity(positionalParam); 2117 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);} 2118 assertNoMissingParameters(positionalParam, arity.min, argsCopy); 2119 final int originalSize = argsCopy.size(); 2120 applyOption(positionalParam, Parameters.class, arity, false, argsCopy, initialized, "args[" + indexRange + "] at position " + position); 2121 final int count = originalSize - argsCopy.size(); 2122 if (count > 0) { required.remove(positionalParam); } 2123 consumed = Math.max(consumed, count); 2124 } 2125 // remove processed args from the stack 2126 for (int i = 0; i < consumed; i++) { args.pop(); } 2127 position += consumed; 2128 if (tracer.isDebug()) {tracer.debug("Consumed %d arguments, moving position to index %d.%n", consumed, position);} 2129 if (consumed == 0 && !args.isEmpty()) { 2130 handleUnmatchedArguments(args.pop()); 2131 } 2132 } 2133 2134 private void processStandaloneOption(final Collection<Field> required, 2135 final Set<Field> initialized, 2136 final String arg, 2137 final Stack<String> args, 2138 final boolean paramAttachedToKey) throws Exception { 2139 final Field field = optionName2Field.get(arg); 2140 required.remove(field); 2141 Range arity = Range.optionArity(field); 2142 if (paramAttachedToKey) { 2143 arity = arity.min(Math.max(1, arity.min)); // if key=value, minimum arity is at least 1 2144 } 2145 if (tracer.isDebug()) {tracer.debug("Found option named '%s': field %s, arity=%s%n", arg, field, arity);} 2146 applyOption(field, Option.class, arity, paramAttachedToKey, args, initialized, "option " + arg); 2147 } 2148 2149 private void processClusteredShortOptions(final Collection<Field> required, 2150 final Set<Field> initialized, 2151 final String arg, 2152 final Stack<String> args) 2153 throws Exception { 2154 final String prefix = arg.substring(0, 1); 2155 String cluster = arg.substring(1); 2156 boolean paramAttachedToOption = true; 2157 do { 2158 if (cluster.length() > 0 && singleCharOption2Field.containsKey(cluster.charAt(0))) { 2159 final Field field = singleCharOption2Field.get(cluster.charAt(0)); 2160 Range arity = Range.optionArity(field); 2161 final String argDescription = "option " + prefix + cluster.charAt(0); 2162 if (tracer.isDebug()) {tracer.debug("Found option '%s%s' in %s: field %s, arity=%s%n", prefix, cluster.charAt(0), arg, field, arity);} 2163 required.remove(field); 2164 cluster = cluster.length() > 0 ? cluster.substring(1) : ""; 2165 paramAttachedToOption = cluster.length() > 0; 2166 if (cluster.startsWith(separator)) {// attached with separator, like -f=FILE or -v=true 2167 cluster = cluster.substring(separator.length()); 2168 arity = arity.min(Math.max(1, arity.min)); // if key=value, minimum arity is at least 1 2169 } 2170 if (arity.min > 0 && !empty(cluster)) { 2171 if (tracer.isDebug()) {tracer.debug("Trying to process '%s' as option parameter%n", cluster);} 2172 } 2173 // arity may be >= 1, or 2174 // arity <= 0 && !cluster.startsWith(separator) 2175 // e.g., boolean @Option("-v", arity=0, varargs=true); arg "-rvTRUE", remainder cluster="TRUE" 2176 if (!empty(cluster)) { 2177 args.push(cluster); // interpret remainder as option parameter 2178 } 2179 final int consumed = applyOption(field, Option.class, arity, paramAttachedToOption, args, initialized, argDescription); 2180 // only return if cluster (and maybe more) was consumed, otherwise continue do-while loop 2181 if (empty(cluster) || consumed > 0 || args.isEmpty()) { 2182 return; 2183 } 2184 cluster = args.pop(); 2185 } else { // cluster is empty || cluster.charAt(0) is not a short option key 2186 if (cluster.length() == 0) { // we finished parsing a group of short options like -rxv 2187 return; // return normally and parse the next arg 2188 } 2189 // We get here when the remainder of the cluster group is neither an option, 2190 // nor a parameter that the last option could consume. 2191 if (arg.endsWith(cluster)) { 2192 args.push(paramAttachedToOption ? prefix + cluster : cluster); 2193 if (args.peek().equals(arg)) { // #149 be consistent between unmatched short and long options 2194 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);} 2195 if (resemblesOption(arg)) { handleUnmatchedArguments(args.pop()); return; } // #149 2196 processPositionalParameter(required, initialized, args); 2197 return; 2198 } 2199 // remainder was part of a clustered group that could not be completely parsed 2200 if (tracer.isDebug()) {tracer.debug("No option found for %s in %s%n", cluster, arg);} 2201 handleUnmatchedArguments(args.pop()); 2202 } else { 2203 args.push(cluster); 2204 if (tracer.isDebug()) {tracer.debug("%s is not an option parameter for %s%n", cluster, arg);} 2205 processPositionalParameter(required, initialized, args); 2206 } 2207 return; 2208 } 2209 } while (true); 2210 } 2211 2212 private int applyOption(final Field field, 2213 final Class<?> annotation, 2214 final Range arity, 2215 final boolean valueAttachedToOption, 2216 final Stack<String> args, 2217 final Set<Field> initialized, 2218 final String argDescription) throws Exception { 2219 updateHelpRequested(field); 2220 final int length = args.size(); 2221 assertNoMissingParameters(field, arity.min, args); 2222 2223 Class<?> cls = field.getType(); 2224 if (cls.isArray()) { 2225 return applyValuesToArrayField(field, annotation, arity, args, cls, argDescription); 2226 } 2227 if (Collection.class.isAssignableFrom(cls)) { 2228 return applyValuesToCollectionField(field, annotation, arity, args, cls, argDescription); 2229 } 2230 if (Map.class.isAssignableFrom(cls)) { 2231 return applyValuesToMapField(field, annotation, arity, args, cls, argDescription); 2232 } 2233 cls = getTypeAttribute(field)[0]; // field may be interface/abstract type, use annotation to get concrete type 2234 return applyValueToSingleValuedField(field, arity, args, cls, initialized, argDescription); 2235 } 2236 2237 private int applyValueToSingleValuedField(final Field field, 2238 final Range arity, 2239 final Stack<String> args, 2240 final Class<?> cls, 2241 final Set<Field> initialized, 2242 final String argDescription) throws Exception { 2243 final boolean noMoreValues = args.isEmpty(); 2244 String value = args.isEmpty() ? null : trim(args.pop()); // unquote the value 2245 int result = arity.min; // the number or args we need to consume 2246 2247 // special logic for booleans: BooleanConverter accepts only "true" or "false". 2248 if ((cls == Boolean.class || cls == Boolean.TYPE) && arity.min <= 0) { 2249 2250 // boolean option with arity = 0..1 or 0..*: value MAY be a param 2251 if (arity.max > 0 && ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value))) { 2252 result = 1; // if it is a varargs we only consume 1 argument if it is a boolean value 2253 } else { 2254 if (value != null) { 2255 args.push(value); // we don't consume the value 2256 } 2257 final Boolean currentValue = (Boolean) field.get(command); 2258 value = String.valueOf(currentValue == null ? true : !currentValue); // #147 toggle existing boolean value 2259 } 2260 } 2261 if (noMoreValues && value == null) { 2262 return 0; 2263 } 2264 final ITypeConverter<?> converter = getTypeConverter(cls, field); 2265 final Object newValue = tryConvert(field, -1, converter, value, cls); 2266 final Object oldValue = field.get(command); 2267 TraceLevel level = TraceLevel.INFO; 2268 String traceMessage = "Setting %s field '%s.%s' to '%5$s' (was '%4$s') for %6$s%n"; 2269 if (initialized != null) { 2270 if (initialized.contains(field)) { 2271 if (!isOverwrittenOptionsAllowed()) { 2272 throw new OverwrittenOptionException(CommandLine.this, optionDescription("", field, 0) + " should be specified only once"); 2273 } 2274 level = TraceLevel.WARN; 2275 traceMessage = "Overwriting %s field '%s.%s' value '%s' with '%s' for %s%n"; 2276 } 2277 initialized.add(field); 2278 } 2279 if (tracer.level.isEnabled(level)) { level.print(tracer, traceMessage, field.getType().getSimpleName(), 2280 field.getDeclaringClass().getSimpleName(), field.getName(), String.valueOf(oldValue), String.valueOf(newValue), argDescription); 2281 } 2282 field.set(command, newValue); 2283 return result; 2284 } 2285 private int applyValuesToMapField(final Field field, 2286 final Class<?> annotation, 2287 final Range arity, 2288 final Stack<String> args, 2289 final Class<?> cls, 2290 final String argDescription) throws Exception { 2291 final Class<?>[] classes = getTypeAttribute(field); 2292 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."); } 2293 final ITypeConverter<?> keyConverter = getTypeConverter(classes[0], field); 2294 final ITypeConverter<?> valueConverter = getTypeConverter(classes[1], field); 2295 Map<Object, Object> result = (Map<Object, Object>) field.get(command); 2296 if (result == null) { 2297 result = createMap(cls); 2298 field.set(command, result); 2299 } 2300 final int originalSize = result.size(); 2301 consumeMapArguments(field, arity, args, classes, keyConverter, valueConverter, result, argDescription); 2302 return result.size() - originalSize; 2303 } 2304 2305 private void consumeMapArguments(final Field field, 2306 final Range arity, 2307 final Stack<String> args, 2308 final Class<?>[] classes, 2309 final ITypeConverter<?> keyConverter, 2310 final ITypeConverter<?> valueConverter, 2311 final Map<Object, Object> result, 2312 final String argDescription) throws Exception { 2313 // first do the arity.min mandatory parameters 2314 for (int i = 0; i < arity.min; i++) { 2315 consumeOneMapArgument(field, arity, args, classes, keyConverter, valueConverter, result, i, argDescription); 2316 } 2317 // now process the varargs if any 2318 for (int i = arity.min; i < arity.max && !args.isEmpty(); i++) { 2319 if (!field.isAnnotationPresent(Parameters.class)) { 2320 if (commands.containsKey(args.peek()) || isOption(args.peek())) { 2321 return; 2322 } 2323 } 2324 consumeOneMapArgument(field, arity, args, classes, keyConverter, valueConverter, result, i, argDescription); 2325 } 2326 } 2327 2328 private void consumeOneMapArgument(final Field field, 2329 final Range arity, 2330 final Stack<String> args, 2331 final Class<?>[] classes, 2332 final ITypeConverter<?> keyConverter, final ITypeConverter<?> valueConverter, 2333 final Map<Object, Object> result, 2334 final int index, 2335 final String argDescription) throws Exception { 2336 final String[] values = split(trim(args.pop()), field); 2337 for (final String value : values) { 2338 final String[] keyValue = value.split("="); 2339 if (keyValue.length < 2) { 2340 final String splitRegex = splitRegex(field); 2341 if (splitRegex.length() == 0) { 2342 throw new ParameterException(CommandLine.this, "Value for option " + optionDescription("", field, 2343 0) + " should be in KEY=VALUE format but was " + value); 2344 } else { 2345 throw new ParameterException(CommandLine.this, "Value for option " + optionDescription("", field, 2346 0) + " should be in KEY=VALUE[" + splitRegex + "KEY=VALUE]... format but was " + value); 2347 } 2348 } 2349 final Object mapKey = tryConvert(field, index, keyConverter, keyValue[0], classes[0]); 2350 final Object mapValue = tryConvert(field, index, valueConverter, keyValue[1], classes[1]); 2351 result.put(mapKey, mapValue); 2352 if (tracer.isInfo()) {tracer.info("Putting [%s : %s] in %s<%s, %s> field '%s.%s' for %s%n", String.valueOf(mapKey), String.valueOf(mapValue), 2353 result.getClass().getSimpleName(), classes[0].getSimpleName(), classes[1].getSimpleName(), field.getDeclaringClass().getSimpleName(), field.getName(), argDescription);} 2354 } 2355 } 2356 2357 private void checkMaxArityExceeded(final Range arity, final int remainder, final Field field, final String[] values) { 2358 if (values.length <= remainder) { return; } 2359 final String desc = arity.max == remainder ? "" + remainder : arity + ", remainder=" + remainder; 2360 throw new MaxValuesforFieldExceededException(CommandLine.this, optionDescription("", field, -1) + 2361 " max number of values (" + arity.max + ") exceeded: remainder is " + remainder + " but " + 2362 values.length + " values were specified: " + Arrays.toString(values)); 2363 } 2364 2365 private int applyValuesToArrayField(final Field field, 2366 final Class<?> annotation, 2367 final Range arity, 2368 final Stack<String> args, 2369 final Class<?> cls, 2370 final String argDescription) throws Exception { 2371 final Object existing = field.get(command); 2372 final int length = existing == null ? 0 : Array.getLength(existing); 2373 final Class<?> type = getTypeAttribute(field)[0]; 2374 final List<Object> converted = consumeArguments(field, annotation, arity, args, type, length, argDescription); 2375 final List<Object> newValues = new ArrayList<Object>(); 2376 for (int i = 0; i < length; i++) { 2377 newValues.add(Array.get(existing, i)); 2378 } 2379 for (final Object obj : converted) { 2380 if (obj instanceof Collection<?>) { 2381 newValues.addAll((Collection<?>) obj); 2382 } else { 2383 newValues.add(obj); 2384 } 2385 } 2386 final Object array = Array.newInstance(type, newValues.size()); 2387 field.set(command, array); 2388 for (int i = 0; i < newValues.size(); i++) { 2389 Array.set(array, i, newValues.get(i)); 2390 } 2391 return converted.size(); // return how many args were consumed 2392 } 2393 2394 @SuppressWarnings("unchecked") 2395 private int applyValuesToCollectionField(final Field field, 2396 final Class<?> annotation, 2397 final Range arity, 2398 final Stack<String> args, 2399 final Class<?> cls, 2400 final String argDescription) throws Exception { 2401 Collection<Object> collection = (Collection<Object>) field.get(command); 2402 final Class<?> type = getTypeAttribute(field)[0]; 2403 final int length = collection == null ? 0 : collection.size(); 2404 final List<Object> converted = consumeArguments(field, annotation, arity, args, type, length, argDescription); 2405 if (collection == null) { 2406 collection = createCollection(cls); 2407 field.set(command, collection); 2408 } 2409 for (final Object element : converted) { 2410 if (element instanceof Collection<?>) { 2411 collection.addAll((Collection<?>) element); 2412 } else { 2413 collection.add(element); 2414 } 2415 } 2416 return converted.size(); 2417 } 2418 2419 private List<Object> consumeArguments(final Field field, 2420 final Class<?> annotation, 2421 final Range arity, 2422 final Stack<String> args, 2423 final Class<?> type, 2424 final int originalSize, 2425 final String argDescription) throws Exception { 2426 final List<Object> result = new ArrayList<Object>(); 2427 2428 // first do the arity.min mandatory parameters 2429 for (int i = 0; i < arity.min; i++) { 2430 consumeOneArgument(field, arity, args, type, result, i, originalSize, argDescription); 2431 } 2432 // now process the varargs if any 2433 for (int i = arity.min; i < arity.max && !args.isEmpty(); i++) { 2434 if (annotation != Parameters.class) { // for vararg Options, we stop if we encounter '--', a command, or another option 2435 if (commands.containsKey(args.peek()) || isOption(args.peek())) { 2436 return result; 2437 } 2438 } 2439 consumeOneArgument(field, arity, args, type, result, i, originalSize, argDescription); 2440 } 2441 return result; 2442 } 2443 2444 private int consumeOneArgument(final Field field, 2445 final Range arity, 2446 final Stack<String> args, 2447 final Class<?> type, 2448 final List<Object> result, 2449 int index, 2450 final int originalSize, 2451 final String argDescription) throws Exception { 2452 final String[] values = split(trim(args.pop()), field); 2453 final ITypeConverter<?> converter = getTypeConverter(type, field); 2454 2455 for (int j = 0; j < values.length; j++) { 2456 result.add(tryConvert(field, index, converter, values[j], type)); 2457 if (tracer.isInfo()) { 2458 if (field.getType().isArray()) { 2459 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); 2460 } else { 2461 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); 2462 } 2463 } 2464 } 2465 //checkMaxArityExceeded(arity, max, field, values); 2466 return ++index; 2467 } 2468 2469 private String splitRegex(final Field field) { 2470 if (field.isAnnotationPresent(Option.class)) { return field.getAnnotation(Option.class).split(); } 2471 if (field.isAnnotationPresent(Parameters.class)) { return field.getAnnotation(Parameters.class).split(); } 2472 return ""; 2473 } 2474 private String[] split(final String value, final Field field) { 2475 final String regex = splitRegex(field); 2476 return regex.length() == 0 ? new String[] {value} : value.split(regex); 2477 } 2478 2479 /** 2480 * Called when parsing varargs parameters for a multi-value option. 2481 * When an option is encountered, the remainder should not be interpreted as vararg elements. 2482 * @param arg the string to determine whether it is an option or not 2483 * @return true if it is an option, false otherwise 2484 */ 2485 private boolean isOption(final String arg) { 2486 if ("--".equals(arg)) { 2487 return true; 2488 } 2489 // not just arg prefix: we may be in the middle of parsing -xrvfFILE 2490 if (optionName2Field.containsKey(arg)) { // -v or -f or --file (not attached to param or other option) 2491 return true; 2492 } 2493 final int separatorIndex = arg.indexOf(separator); 2494 if (separatorIndex > 0) { // -f=FILE or --file==FILE (attached to param via separator) 2495 if (optionName2Field.containsKey(arg.substring(0, separatorIndex))) { 2496 return true; 2497 } 2498 } 2499 return (arg.length() > 2 && arg.startsWith("-") && singleCharOption2Field.containsKey(arg.charAt(1))); 2500 } 2501 private Object tryConvert(final Field field, final int index, final ITypeConverter<?> converter, final String value, final Class<?> type) 2502 throws Exception { 2503 try { 2504 return converter.convert(value); 2505 } catch (final TypeConversionException ex) { 2506 throw new ParameterException(CommandLine.this, ex.getMessage() + optionDescription(" for ", field, index)); 2507 } catch (final Exception other) { 2508 final String desc = optionDescription(" for ", field, index) + ": " + other; 2509 throw new ParameterException(CommandLine.this, "Could not convert '" + value + "' to " + type.getSimpleName() + desc, other); 2510 } 2511 } 2512 2513 private String optionDescription(final String prefix, final Field field, final int index) { 2514 final Help.IParamLabelRenderer labelRenderer = Help.createMinimalParamLabelRenderer(); 2515 String desc = ""; 2516 if (field.isAnnotationPresent(Option.class)) { 2517 desc = prefix + "option '" + field.getAnnotation(Option.class).names()[0] + "'"; 2518 if (index >= 0) { 2519 final Range arity = Range.optionArity(field); 2520 if (arity.max > 1) { 2521 desc += " at index " + index; 2522 } 2523 desc += " (" + labelRenderer.renderParameterLabel(field, Help.Ansi.OFF, Collections.<IStyle>emptyList()) + ")"; 2524 } 2525 } else if (field.isAnnotationPresent(Parameters.class)) { 2526 final Range indexRange = Range.parameterIndex(field); 2527 final Text label = labelRenderer.renderParameterLabel(field, Help.Ansi.OFF, Collections.<IStyle>emptyList()); 2528 desc = prefix + "positional parameter at index " + indexRange + " (" + label + ")"; 2529 } 2530 return desc; 2531 } 2532 2533 private boolean isAnyHelpRequested() { return isHelpRequested || versionHelpRequested || usageHelpRequested; } 2534 2535 private void updateHelpRequested(final Field field) { 2536 if (field.isAnnotationPresent(Option.class)) { 2537 isHelpRequested |= is(field, "help", field.getAnnotation(Option.class).help()); 2538 CommandLine.this.versionHelpRequested |= is(field, "versionHelp", field.getAnnotation(Option.class).versionHelp()); 2539 CommandLine.this.usageHelpRequested |= is(field, "usageHelp", field.getAnnotation(Option.class).usageHelp()); 2540 } 2541 } 2542 private boolean is(final Field f, final String description, final boolean value) { 2543 if (value) { if (tracer.isInfo()) {tracer.info("Field '%s.%s' has '%s' annotation: not validating required fields%n", f.getDeclaringClass().getSimpleName(), f.getName(), description); }} 2544 return value; 2545 } 2546 @SuppressWarnings("unchecked") 2547 private Collection<Object> createCollection(final Class<?> collectionClass) throws Exception { 2548 if (collectionClass.isInterface()) { 2549 if (List.class.isAssignableFrom(collectionClass)) { 2550 return new ArrayList<Object>(); 2551 } else if (SortedSet.class.isAssignableFrom(collectionClass)) { 2552 return new TreeSet<Object>(); 2553 } else if (Set.class.isAssignableFrom(collectionClass)) { 2554 return new LinkedHashSet<Object>(); 2555 } else if (Queue.class.isAssignableFrom(collectionClass)) { 2556 return new LinkedList<Object>(); // ArrayDeque is only available since 1.6 2557 } 2558 return new ArrayList<Object>(); 2559 } 2560 // custom Collection implementation class must have default constructor 2561 return (Collection<Object>) collectionClass.newInstance(); 2562 } 2563 private Map<Object, Object> createMap(final Class<?> mapClass) throws Exception { 2564 try { // if it is an implementation class, instantiate it 2565 return (Map<Object, Object>) mapClass.newInstance(); 2566 } catch (final Exception ignored) {} 2567 return new LinkedHashMap<Object, Object>(); 2568 } 2569 private ITypeConverter<?> getTypeConverter(final Class<?> type, final Field field) { 2570 final ITypeConverter<?> result = converterRegistry.get(type); 2571 if (result != null) { 2572 return result; 2573 } 2574 if (type.isEnum()) { 2575 return new ITypeConverter<Object>() { 2576 @Override 2577 @SuppressWarnings("unchecked") 2578 public Object convert(final String value) throws Exception { 2579 return Enum.valueOf((Class<Enum>) type, value); 2580 } 2581 }; 2582 } 2583 throw new MissingTypeConverterException(CommandLine.this, "No TypeConverter registered for " + type.getName() + " of field " + field); 2584 } 2585 2586 private void assertNoMissingParameters(final Field field, final int arity, final Stack<String> args) { 2587 if (arity > args.size()) { 2588 if (arity == 1) { 2589 if (field.isAnnotationPresent(Option.class)) { 2590 throw new MissingParameterException(CommandLine.this, "Missing required parameter for " + 2591 optionDescription("", field, 0)); 2592 } 2593 final Range indexRange = Range.parameterIndex(field); 2594 final Help.IParamLabelRenderer labelRenderer = Help.createMinimalParamLabelRenderer(); 2595 String sep = ""; 2596 String names = ""; 2597 int count = 0; 2598 for (int i = indexRange.min; i < positionalParametersFields.size(); i++) { 2599 if (Range.parameterArity(positionalParametersFields.get(i)).min > 0) { 2600 names += sep + labelRenderer.renderParameterLabel(positionalParametersFields.get(i), 2601 Help.Ansi.OFF, Collections.<IStyle>emptyList()); 2602 sep = ", "; 2603 count++; 2604 } 2605 } 2606 String msg = "Missing required parameter"; 2607 final Range paramArity = Range.parameterArity(field); 2608 if (paramArity.isVariable) { 2609 msg += "s at positions " + indexRange + ": "; 2610 } else { 2611 msg += (count > 1 ? "s: " : ": "); 2612 } 2613 throw new MissingParameterException(CommandLine.this, msg + names); 2614 } 2615 if (args.isEmpty()) { 2616 throw new MissingParameterException(CommandLine.this, optionDescription("", field, 0) + 2617 " requires at least " + arity + " values, but none were specified."); 2618 } 2619 throw new MissingParameterException(CommandLine.this, optionDescription("", field, 0) + 2620 " requires at least " + arity + " values, but only " + args.size() + " were specified: " + reverse(args)); 2621 } 2622 } 2623 private String trim(final String value) { 2624 return unquote(value); 2625 } 2626 2627 private String unquote(final String value) { 2628 return value == null 2629 ? null 2630 : (value.length() > 1 && value.startsWith("\"") && value.endsWith("\"")) 2631 ? value.substring(1, value.length() - 1) 2632 : value; 2633 } 2634 } 2635 private static class PositionalParametersSorter implements Comparator<Field> { 2636 @Override 2637 public int compare(final Field o1, final Field o2) { 2638 final int result = Range.parameterIndex(o1).compareTo(Range.parameterIndex(o2)); 2639 return (result == 0) ? Range.parameterArity(o1).compareTo(Range.parameterArity(o2)) : result; 2640 } 2641 } 2642 /** 2643 * Inner class to group the built-in {@link ITypeConverter} implementations. 2644 */ 2645 private static class BuiltIn { 2646 static class PathConverter implements ITypeConverter<Path> { 2647 @Override public Path convert(final String value) { return Paths.get(value); } 2648 } 2649 static class StringConverter implements ITypeConverter<String> { 2650 @Override 2651 public String convert(final String value) { return value; } 2652 } 2653 static class StringBuilderConverter implements ITypeConverter<StringBuilder> { 2654 @Override 2655 public StringBuilder convert(final String value) { return new StringBuilder(value); } 2656 } 2657 static class CharSequenceConverter implements ITypeConverter<CharSequence> { 2658 @Override 2659 public String convert(final String value) { return value; } 2660 } 2661 /** Converts text to a {@code Byte} by delegating to {@link Byte#valueOf(String)}.*/ 2662 static class ByteConverter implements ITypeConverter<Byte> { 2663 @Override 2664 public Byte convert(final String value) { return Byte.valueOf(value); } 2665 } 2666 /** Converts {@code "true"} or {@code "false"} to a {@code Boolean}. Other values result in a ParameterException.*/ 2667 static class BooleanConverter implements ITypeConverter<Boolean> { 2668 @Override 2669 public Boolean convert(final String value) { 2670 if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) { 2671 return Boolean.parseBoolean(value); 2672 } else { 2673 throw new TypeConversionException("'" + value + "' is not a boolean"); 2674 } 2675 } 2676 } 2677 static class CharacterConverter implements ITypeConverter<Character> { 2678 @Override 2679 public Character convert(final String value) { 2680 if (value.length() > 1) { 2681 throw new TypeConversionException("'" + value + "' is not a single character"); 2682 } 2683 return value.charAt(0); 2684 } 2685 } 2686 /** Converts text to a {@code Short} by delegating to {@link Short#valueOf(String)}.*/ 2687 static class ShortConverter implements ITypeConverter<Short> { 2688 @Override 2689 public Short convert(final String value) { return Short.valueOf(value); } 2690 } 2691 /** Converts text to an {@code Integer} by delegating to {@link Integer#valueOf(String)}.*/ 2692 static class IntegerConverter implements ITypeConverter<Integer> { 2693 @Override 2694 public Integer convert(final String value) { return Integer.valueOf(value); } 2695 } 2696 /** Converts text to a {@code Long} by delegating to {@link Long#valueOf(String)}.*/ 2697 static class LongConverter implements ITypeConverter<Long> { 2698 @Override 2699 public Long convert(final String value) { return Long.valueOf(value); } 2700 } 2701 static class FloatConverter implements ITypeConverter<Float> { 2702 @Override 2703 public Float convert(final String value) { return Float.valueOf(value); } 2704 } 2705 static class DoubleConverter implements ITypeConverter<Double> { 2706 @Override 2707 public Double convert(final String value) { return Double.valueOf(value); } 2708 } 2709 static class FileConverter implements ITypeConverter<File> { 2710 @Override 2711 public File convert(final String value) { return new File(value); } 2712 } 2713 static class URLConverter implements ITypeConverter<URL> { 2714 @Override 2715 public URL convert(final String value) throws MalformedURLException { return new URL(value); } 2716 } 2717 static class URIConverter implements ITypeConverter<URI> { 2718 @Override 2719 public URI convert(final String value) throws URISyntaxException { return new URI(value); } 2720 } 2721 /** Converts text in {@code yyyy-mm-dd} format to a {@code java.util.Date}. ParameterException on failure. */ 2722 static class ISO8601DateConverter implements ITypeConverter<Date> { 2723 @Override 2724 public Date convert(final String value) { 2725 try { 2726 return new SimpleDateFormat("yyyy-MM-dd").parse(value); 2727 } catch (final ParseException e) { 2728 throw new TypeConversionException("'" + value + "' is not a yyyy-MM-dd date"); 2729 } 2730 } 2731 } 2732 /** Converts text in any of the following formats to a {@code java.sql.Time}: {@code HH:mm}, {@code HH:mm:ss}, 2733 * {@code HH:mm:ss.SSS}, {@code HH:mm:ss,SSS}. Other formats result in a ParameterException. */ 2734 static class ISO8601TimeConverter implements ITypeConverter<Time> { 2735 @Override 2736 public Time convert(final String value) { 2737 try { 2738 if (value.length() <= 5) { 2739 return new Time(new SimpleDateFormat("HH:mm").parse(value).getTime()); 2740 } else if (value.length() <= 8) { 2741 return new Time(new SimpleDateFormat("HH:mm:ss").parse(value).getTime()); 2742 } else if (value.length() <= 12) { 2743 try { 2744 return new Time(new SimpleDateFormat("HH:mm:ss.SSS").parse(value).getTime()); 2745 } catch (final ParseException e2) { 2746 return new Time(new SimpleDateFormat("HH:mm:ss,SSS").parse(value).getTime()); 2747 } 2748 } 2749 } catch (final ParseException ignored) { 2750 // ignored because we throw a ParameterException below 2751 } 2752 throw new TypeConversionException("'" + value + "' is not a HH:mm[:ss[.SSS]] time"); 2753 } 2754 } 2755 static class BigDecimalConverter implements ITypeConverter<BigDecimal> { 2756 @Override 2757 public BigDecimal convert(final String value) { return new BigDecimal(value); } 2758 } 2759 static class BigIntegerConverter implements ITypeConverter<BigInteger> { 2760 @Override 2761 public BigInteger convert(final String value) { return new BigInteger(value); } 2762 } 2763 static class CharsetConverter implements ITypeConverter<Charset> { 2764 @Override 2765 public Charset convert(final String s) { return Charset.forName(s); } 2766 } 2767 /** Converts text to a {@code InetAddress} by delegating to {@link InetAddress#getByName(String)}. */ 2768 static class InetAddressConverter implements ITypeConverter<InetAddress> { 2769 @Override 2770 public InetAddress convert(final String s) throws Exception { return InetAddress.getByName(s); } 2771 } 2772 static class PatternConverter implements ITypeConverter<Pattern> { 2773 @Override 2774 public Pattern convert(final String s) { return Pattern.compile(s); } 2775 } 2776 static class UUIDConverter implements ITypeConverter<UUID> { 2777 @Override 2778 public UUID convert(final String s) throws Exception { return UUID.fromString(s); } 2779 } 2780 private BuiltIn() {} // private constructor: never instantiate 2781 } 2782 2783 /** 2784 * A collection of methods and inner classes that provide fine-grained control over the contents and layout of 2785 * the usage help message to display to end users when help is requested or invalid input values were specified. 2786 * <h3>Layered API</h3> 2787 * <p>The {@link Command} annotation provides the easiest way to customize usage help messages. See 2788 * the <a href="https://remkop.github.io/picocli/#_usage_help">Manual</a> for details.</p> 2789 * <p>This Help class provides high-level functions to create sections of the usage help message and headings 2790 * for these sections. Instead of calling the {@link CommandLine#usage(PrintStream, CommandLine.Help.ColorScheme)} 2791 * method, application authors may want to create a custom usage help message by reorganizing sections in a 2792 * different order and/or adding custom sections.</p> 2793 * <p>Finally, the Help class contains inner classes and interfaces that can be used to create custom help messages.</p> 2794 * <h4>IOptionRenderer and IParameterRenderer</h4> 2795 * <p>Renders a field annotated with {@link Option} or {@link Parameters} to an array of {@link Text} values. 2796 * By default, these values are</p><ul> 2797 * <li>mandatory marker character (if the option/parameter is {@link Option#required() required})</li> 2798 * <li>short option name (empty for parameters)</li> 2799 * <li>comma or empty (empty for parameters)</li> 2800 * <li>long option names (the parameter {@link IParamLabelRenderer label} for parameters)</li> 2801 * <li>description</li> 2802 * </ul> 2803 * <p>Other components rely on this ordering.</p> 2804 * <h4>Layout</h4> 2805 * <p>Delegates to the renderers to create {@link Text} values for the annotated fields, and uses a 2806 * {@link TextTable} to display these values in tabular format. Layout is responsible for deciding which values 2807 * to display where in the table. By default, Layout shows one option or parameter per table row.</p> 2808 * <h4>TextTable</h4> 2809 * <p>Responsible for spacing out {@link Text} values according to the {@link Column} definitions the table was 2810 * created with. Columns have a width, indentation, and an overflow policy that decides what to do if a value is 2811 * longer than the column's width.</p> 2812 * <h4>Text</h4> 2813 * <p>Encapsulates rich text with styles and colors in a way that other components like {@link TextTable} are 2814 * unaware of the embedded ANSI escape codes.</p> 2815 */ 2816 public static class Help { 2817 /** Constant String holding the default program name: {@value} */ 2818 protected static final String DEFAULT_COMMAND_NAME = "<main class>"; 2819 2820 /** Constant String holding the default string that separates options from option parameters: {@value} */ 2821 protected static final String DEFAULT_SEPARATOR = "="; 2822 2823 private final static int usageHelpWidth = 80; 2824 private final static int optionsColumnWidth = 2 + 2 + 1 + 24; 2825 private final Object command; 2826 private final Map<String, Help> commands = new LinkedHashMap<String, Help>(); 2827 final ColorScheme colorScheme; 2828 2829 /** Immutable list of fields annotated with {@link Option}, in declaration order. */ 2830 public final List<Field> optionFields; 2831 2832 /** Immutable list of fields annotated with {@link Parameters}, or an empty list if no such field exists. */ 2833 public final List<Field> positionalParametersFields; 2834 2835 /** The String to use as the separator between options and option parameters. {@code "="} by default, 2836 * initialized from {@link Command#separator()} if defined. 2837 * @see #parameterLabelRenderer */ 2838 public String separator; 2839 2840 /** The String to use as the program name in the synopsis line of the help message. 2841 * {@link #DEFAULT_COMMAND_NAME} by default, initialized from {@link Command#name()} if defined. */ 2842 public String commandName = DEFAULT_COMMAND_NAME; 2843 2844 /** Optional text lines to use as the description of the help message, displayed between the synopsis and the 2845 * options list. Initialized from {@link Command#description()} if the {@code Command} annotation is present, 2846 * otherwise this is an empty array and the help message has no description. 2847 * Applications may programmatically set this field to create a custom help message. */ 2848 public String[] description = {}; 2849 2850 /** Optional custom synopsis lines to use instead of the auto-generated synopsis. 2851 * Initialized from {@link Command#customSynopsis()} if the {@code Command} annotation is present, 2852 * otherwise this is an empty array and the synopsis is generated. 2853 * Applications may programmatically set this field to create a custom help message. */ 2854 public String[] customSynopsis = {}; 2855 2856 /** Optional header lines displayed at the top of the help message. For subcommands, the first header line is 2857 * displayed in the list of commands. Values are initialized from {@link Command#header()} 2858 * if the {@code Command} annotation is present, otherwise this is an empty array and the help message has no 2859 * header. Applications may programmatically set this field to create a custom help message. */ 2860 public String[] header = {}; 2861 2862 /** Optional footer text lines displayed at the bottom of the help message. Initialized from 2863 * {@link Command#footer()} if the {@code Command} annotation is present, otherwise this is an empty array and 2864 * the help message has no footer. 2865 * Applications may programmatically set this field to create a custom help message. */ 2866 public String[] footer = {}; 2867 2868 /** Option and positional parameter value label renderer used for the synopsis line(s) and the option list. 2869 * By default initialized to the result of {@link #createDefaultParamLabelRenderer()}, which takes a snapshot 2870 * of the {@link #separator} at construction time. If the separator is modified after Help construction, you 2871 * may need to re-initialize this field by calling {@link #createDefaultParamLabelRenderer()} again. */ 2872 public IParamLabelRenderer parameterLabelRenderer; 2873 2874 /** If {@code true}, the synopsis line(s) will show an abbreviated synopsis without detailed option names. */ 2875 public Boolean abbreviateSynopsis; 2876 2877 /** If {@code true}, the options list is sorted alphabetically. */ 2878 public Boolean sortOptions; 2879 2880 /** If {@code true}, the options list will show default values for all options except booleans. */ 2881 public Boolean showDefaultValues; 2882 2883 /** Character used to prefix required options in the options list. */ 2884 public Character requiredOptionMarker; 2885 2886 /** Optional heading preceding the header section. Initialized from {@link Command#headerHeading()}, or null. */ 2887 public String headerHeading; 2888 2889 /** Optional heading preceding the synopsis. Initialized from {@link Command#synopsisHeading()}, {@code "Usage: "} by default. */ 2890 public String synopsisHeading; 2891 2892 /** Optional heading preceding the description section. Initialized from {@link Command#descriptionHeading()}, or null. */ 2893 public String descriptionHeading; 2894 2895 /** Optional heading preceding the parameter list. Initialized from {@link Command#parameterListHeading()}, or null. */ 2896 public String parameterListHeading; 2897 2898 /** Optional heading preceding the options list. Initialized from {@link Command#optionListHeading()}, or null. */ 2899 public String optionListHeading; 2900 2901 /** Optional heading preceding the subcommand list. Initialized from {@link Command#commandListHeading()}. {@code "Commands:%n"} by default. */ 2902 public String commandListHeading; 2903 2904 /** Optional heading preceding the footer section. Initialized from {@link Command#footerHeading()}, or null. */ 2905 public String footerHeading; 2906 2907 /** Constructs a new {@code Help} instance with a default color scheme, initialized from annotatations 2908 * on the specified class and superclasses. 2909 * @param command the annotated object to create usage help for */ 2910 public Help(final Object command) { 2911 this(command, Ansi.AUTO); 2912 } 2913 2914 /** Constructs a new {@code Help} instance with a default color scheme, initialized from annotatations 2915 * on the specified class and superclasses. 2916 * @param command the annotated object to create usage help for 2917 * @param ansi whether to emit ANSI escape codes or not */ 2918 public Help(final Object command, final Ansi ansi) { 2919 this(command, defaultColorScheme(ansi)); 2920 } 2921 2922 /** Constructs a new {@code Help} instance with the specified color scheme, initialized from annotatations 2923 * on the specified class and superclasses. 2924 * @param command the annotated object to create usage help for 2925 * @param colorScheme the color scheme to use */ 2926 public Help(final Object command, final ColorScheme colorScheme) { 2927 this.command = Assert.notNull(command, "command"); 2928 this.colorScheme = Assert.notNull(colorScheme, "colorScheme").applySystemProperties(); 2929 final List<Field> options = new ArrayList<Field>(); 2930 final List<Field> operands = new ArrayList<Field>(); 2931 Class<?> cls = command.getClass(); 2932 while (cls != null) { 2933 for (final Field field : cls.getDeclaredFields()) { 2934 field.setAccessible(true); 2935 if (field.isAnnotationPresent(Option.class)) { 2936 final Option option = field.getAnnotation(Option.class); 2937 if (!option.hidden()) { // hidden options should not appear in usage help 2938 // TODO remember longest concatenated option string length (issue #45) 2939 options.add(field); 2940 } 2941 } 2942 if (field.isAnnotationPresent(Parameters.class)) { 2943 operands.add(field); 2944 } 2945 } 2946 // superclass values should not overwrite values if both class and superclass have a @Command annotation 2947 if (cls.isAnnotationPresent(Command.class)) { 2948 final Command cmd = cls.getAnnotation(Command.class); 2949 if (DEFAULT_COMMAND_NAME.equals(commandName)) { 2950 commandName = cmd.name(); 2951 } 2952 separator = (separator == null) ? cmd.separator() : separator; 2953 abbreviateSynopsis = (abbreviateSynopsis == null) ? cmd.abbreviateSynopsis() : abbreviateSynopsis; 2954 sortOptions = (sortOptions == null) ? cmd.sortOptions() : sortOptions; 2955 requiredOptionMarker = (requiredOptionMarker == null) ? cmd.requiredOptionMarker() : requiredOptionMarker; 2956 showDefaultValues = (showDefaultValues == null) ? cmd.showDefaultValues() : showDefaultValues; 2957 customSynopsis = empty(customSynopsis) ? cmd.customSynopsis() : customSynopsis; 2958 description = empty(description) ? cmd.description() : description; 2959 header = empty(header) ? cmd.header() : header; 2960 footer = empty(footer) ? cmd.footer() : footer; 2961 headerHeading = empty(headerHeading) ? cmd.headerHeading() : headerHeading; 2962 synopsisHeading = empty(synopsisHeading) || "Usage: ".equals(synopsisHeading) ? cmd.synopsisHeading() : synopsisHeading; 2963 descriptionHeading = empty(descriptionHeading) ? cmd.descriptionHeading() : descriptionHeading; 2964 parameterListHeading = empty(parameterListHeading) ? cmd.parameterListHeading() : parameterListHeading; 2965 optionListHeading = empty(optionListHeading) ? cmd.optionListHeading() : optionListHeading; 2966 commandListHeading = empty(commandListHeading) || "Commands:%n".equals(commandListHeading) ? cmd.commandListHeading() : commandListHeading; 2967 footerHeading = empty(footerHeading) ? cmd.footerHeading() : footerHeading; 2968 } 2969 cls = cls.getSuperclass(); 2970 } 2971 sortOptions = (sortOptions == null) ? true : sortOptions; 2972 abbreviateSynopsis = (abbreviateSynopsis == null) ? false : abbreviateSynopsis; 2973 requiredOptionMarker = (requiredOptionMarker == null) ? ' ' : requiredOptionMarker; 2974 showDefaultValues = (showDefaultValues == null) ? false : showDefaultValues; 2975 synopsisHeading = (synopsisHeading == null) ? "Usage: " : synopsisHeading; 2976 commandListHeading = (commandListHeading == null) ? "Commands:%n" : commandListHeading; 2977 separator = (separator == null) ? DEFAULT_SEPARATOR : separator; 2978 parameterLabelRenderer = createDefaultParamLabelRenderer(); // uses help separator 2979 Collections.sort(operands, new PositionalParametersSorter()); 2980 positionalParametersFields = Collections.unmodifiableList(operands); 2981 optionFields = Collections.unmodifiableList(options); 2982 } 2983 2984 /** Registers all specified subcommands with this Help. 2985 * @param commands maps the command names to the associated CommandLine object 2986 * @return this Help instance (for method chaining) 2987 * @see CommandLine#getSubcommands() 2988 */ 2989 public Help addAllSubcommands(final Map<String, CommandLine> commands) { 2990 if (commands != null) { 2991 for (final Map.Entry<String, CommandLine> entry : commands.entrySet()) { 2992 addSubcommand(entry.getKey(), entry.getValue().getCommand()); 2993 } 2994 } 2995 return this; 2996 } 2997 2998 /** Registers the specified subcommand with this Help. 2999 * @param commandName the name of the subcommand to display in the usage message 3000 * @param command the annotated object to get more information from 3001 * @return this Help instance (for method chaining) 3002 */ 3003 public Help addSubcommand(final String commandName, final Object command) { 3004 commands.put(commandName, new Help(command)); 3005 return this; 3006 } 3007 3008 /** Returns a synopsis for the command without reserving space for the synopsis heading. 3009 * @return a synopsis 3010 * @see #abbreviatedSynopsis() 3011 * @see #detailedSynopsis(Comparator, boolean) 3012 * @deprecated use {@link #synopsis(int)} instead 3013 */ 3014 @Deprecated 3015 public String synopsis() { return synopsis(0); } 3016 3017 /** 3018 * Returns a synopsis for the command, reserving the specified space for the synopsis heading. 3019 * @param synopsisHeadingLength the length of the synopsis heading that will be displayed on the same line 3020 * @return a synopsis 3021 * @see #abbreviatedSynopsis() 3022 * @see #detailedSynopsis(Comparator, boolean) 3023 * @see #synopsisHeading 3024 */ 3025 public String synopsis(final int synopsisHeadingLength) { 3026 if (!empty(customSynopsis)) { return customSynopsis(); } 3027 return abbreviateSynopsis ? abbreviatedSynopsis() 3028 : detailedSynopsis(synopsisHeadingLength, createShortOptionArityAndNameComparator(), true); 3029 } 3030 3031 /** Generates a generic synopsis like {@code <command name> [OPTIONS] [PARAM1 [PARAM2]...]}, omitting parts 3032 * that don't apply to the command (e.g., does not show [OPTIONS] if the command has no options). 3033 * @return a generic synopsis */ 3034 public String abbreviatedSynopsis() { 3035 final StringBuilder sb = new StringBuilder(); 3036 if (!optionFields.isEmpty()) { // only show if annotated object actually has options 3037 sb.append(" [OPTIONS]"); 3038 } 3039 // sb.append(" [--] "); // implied 3040 for (final Field positionalParam : positionalParametersFields) { 3041 if (!positionalParam.getAnnotation(Parameters.class).hidden()) { 3042 sb.append(' ').append(parameterLabelRenderer.renderParameterLabel(positionalParam, ansi(), colorScheme.parameterStyles)); 3043 } 3044 } 3045 return colorScheme.commandText(commandName).toString() 3046 + (sb.toString()) + System.getProperty("line.separator"); 3047 } 3048 /** Generates a detailed synopsis message showing all options and parameters. Follows the unix convention of 3049 * showing optional options and parameters in square brackets ({@code [ ]}). 3050 * @param optionSort comparator to sort options or {@code null} if options should not be sorted 3051 * @param clusterBooleanOptions {@code true} if boolean short options should be clustered into a single string 3052 * @return a detailed synopsis 3053 * @deprecated use {@link #detailedSynopsis(int, Comparator, boolean)} instead. */ 3054 @Deprecated 3055 public String detailedSynopsis(final Comparator<Field> optionSort, final boolean clusterBooleanOptions) { 3056 return detailedSynopsis(0, optionSort, clusterBooleanOptions); 3057 } 3058 3059 /** Generates a detailed synopsis message showing all options and parameters. Follows the unix convention of 3060 * showing optional options and parameters in square brackets ({@code [ ]}). 3061 * @param synopsisHeadingLength the length of the synopsis heading that will be displayed on the same line 3062 * @param optionSort comparator to sort options or {@code null} if options should not be sorted 3063 * @param clusterBooleanOptions {@code true} if boolean short options should be clustered into a single string 3064 * @return a detailed synopsis */ 3065 public String detailedSynopsis(final int synopsisHeadingLength, final Comparator<Field> optionSort, final boolean clusterBooleanOptions) { 3066 Text optionText = ansi().new Text(0); 3067 final List<Field> fields = new ArrayList<Field>(optionFields); // iterate in declaration order 3068 if (optionSort != null) { 3069 Collections.sort(fields, optionSort);// iterate in specified sort order 3070 } 3071 if (clusterBooleanOptions) { // cluster all short boolean options into a single string 3072 final List<Field> booleanOptions = new ArrayList<Field>(); 3073 final StringBuilder clusteredRequired = new StringBuilder("-"); 3074 final StringBuilder clusteredOptional = new StringBuilder("-"); 3075 for (final Field field : fields) { 3076 if (field.getType() == boolean.class || field.getType() == Boolean.class) { 3077 final Option option = field.getAnnotation(Option.class); 3078 final String shortestName = ShortestFirst.sort(option.names())[0]; 3079 if (shortestName.length() == 2 && shortestName.startsWith("-")) { 3080 booleanOptions.add(field); 3081 if (option.required()) { 3082 clusteredRequired.append(shortestName.substring(1)); 3083 } else { 3084 clusteredOptional.append(shortestName.substring(1)); 3085 } 3086 } 3087 } 3088 } 3089 fields.removeAll(booleanOptions); 3090 if (clusteredRequired.length() > 1) { // initial length was 1 3091 optionText = optionText.append(" ").append(colorScheme.optionText(clusteredRequired.toString())); 3092 } 3093 if (clusteredOptional.length() > 1) { // initial length was 1 3094 optionText = optionText.append(" [").append(colorScheme.optionText(clusteredOptional.toString())).append("]"); 3095 } 3096 } 3097 for (final Field field : fields) { 3098 final Option option = field.getAnnotation(Option.class); 3099 if (!option.hidden()) { 3100 if (option.required()) { 3101 optionText = appendOptionSynopsis(optionText, field, ShortestFirst.sort(option.names())[0], " ", ""); 3102 if (isMultiValue(field)) { 3103 optionText = appendOptionSynopsis(optionText, field, ShortestFirst.sort(option.names())[0], " [", "]..."); 3104 } 3105 } else { 3106 optionText = appendOptionSynopsis(optionText, field, ShortestFirst.sort(option.names())[0], " [", "]"); 3107 if (isMultiValue(field)) { 3108 optionText = optionText.append("..."); 3109 } 3110 } 3111 } 3112 } 3113 for (final Field positionalParam : positionalParametersFields) { 3114 if (!positionalParam.getAnnotation(Parameters.class).hidden()) { 3115 optionText = optionText.append(" "); 3116 final Text label = parameterLabelRenderer.renderParameterLabel(positionalParam, colorScheme.ansi(), colorScheme.parameterStyles); 3117 optionText = optionText.append(label); 3118 } 3119 } 3120 // Fix for #142: first line of synopsis overshoots max. characters 3121 final int firstColumnLength = commandName.length() + synopsisHeadingLength; 3122 3123 // synopsis heading ("Usage: ") may be on the same line, so adjust column width 3124 final TextTable textTable = new TextTable(ansi(), firstColumnLength, usageHelpWidth - firstColumnLength); 3125 textTable.indentWrappedLines = 1; // don't worry about first line: options (2nd column) always start with a space 3126 3127 // right-adjust the command name by length of synopsis heading 3128 final Text PADDING = Ansi.OFF.new Text(stringOf('X', synopsisHeadingLength)); 3129 textTable.addRowValues(new Text[] {PADDING.append(colorScheme.commandText(commandName)), optionText}); 3130 return textTable.toString().substring(synopsisHeadingLength); // cut off leading synopsis heading spaces 3131 } 3132 3133 private Text appendOptionSynopsis(final Text optionText, final Field field, final String optionName, final String prefix, final String suffix) { 3134 final Text optionParamText = parameterLabelRenderer.renderParameterLabel(field, colorScheme.ansi(), colorScheme.optionParamStyles); 3135 return optionText.append(prefix) 3136 .append(colorScheme.optionText(optionName)) 3137 .append(optionParamText) 3138 .append(suffix); 3139 } 3140 3141 /** Returns the number of characters the synopsis heading will take on the same line as the synopsis. 3142 * @return the number of characters the synopsis heading will take on the same line as the synopsis. 3143 * @see #detailedSynopsis(int, Comparator, boolean) 3144 */ 3145 public int synopsisHeadingLength() { 3146 final String[] lines = Ansi.OFF.new Text(synopsisHeading).toString().split("\\r?\\n|\\r|%n", -1); 3147 return lines[lines.length - 1].length(); 3148 } 3149 /** 3150 * <p>Returns a description of the {@linkplain Option options} supported by the application. 3151 * This implementation {@linkplain #createShortOptionNameComparator() sorts options alphabetically}, and shows 3152 * only the {@linkplain Option#hidden() non-hidden} options in a {@linkplain TextTable tabular format} 3153 * using the {@linkplain #createDefaultOptionRenderer() default renderer} and {@linkplain Layout default layout}.</p> 3154 * @return the fully formatted option list 3155 * @see #optionList(Layout, Comparator, IParamLabelRenderer) 3156 */ 3157 public String optionList() { 3158 final Comparator<Field> sortOrder = sortOptions == null || sortOptions.booleanValue() 3159 ? createShortOptionNameComparator() 3160 : null; 3161 return optionList(createDefaultLayout(), sortOrder, parameterLabelRenderer); 3162 } 3163 3164 /** Sorts all {@code Options} with the specified {@code comparator} (if the comparator is non-{@code null}), 3165 * then {@linkplain Layout#addOption(Field, CommandLine.Help.IParamLabelRenderer) adds} all non-hidden options to the 3166 * specified TextTable and returns the result of TextTable.toString(). 3167 * @param layout responsible for rendering the option list 3168 * @param optionSort determines in what order {@code Options} should be listed. Declared order if {@code null} 3169 * @param valueLabelRenderer used for options with a parameter 3170 * @return the fully formatted option list 3171 */ 3172 public String optionList(final Layout layout, final Comparator<Field> optionSort, final IParamLabelRenderer valueLabelRenderer) { 3173 final List<Field> fields = new ArrayList<Field>(optionFields); // options are stored in order of declaration 3174 if (optionSort != null) { 3175 Collections.sort(fields, optionSort); // default: sort options ABC 3176 } 3177 layout.addOptions(fields, valueLabelRenderer); 3178 return layout.toString(); 3179 } 3180 3181 /** 3182 * Returns the section of the usage help message that lists the parameters with their descriptions. 3183 * @return the section of the usage help message that lists the parameters 3184 */ 3185 public String parameterList() { 3186 return parameterList(createDefaultLayout(), parameterLabelRenderer); 3187 } 3188 /** 3189 * Returns the section of the usage help message that lists the parameters with their descriptions. 3190 * @param layout the layout to use 3191 * @param paramLabelRenderer for rendering parameter names 3192 * @return the section of the usage help message that lists the parameters 3193 */ 3194 public String parameterList(final Layout layout, final IParamLabelRenderer paramLabelRenderer) { 3195 layout.addPositionalParameters(positionalParametersFields, paramLabelRenderer); 3196 return layout.toString(); 3197 } 3198 3199 private static String heading(final Ansi ansi, final String values, final Object... params) { 3200 final StringBuilder sb = join(ansi, new String[] {values}, new StringBuilder(), params); 3201 String result = sb.toString(); 3202 result = result.endsWith(System.getProperty("line.separator")) 3203 ? result.substring(0, result.length() - System.getProperty("line.separator").length()) : result; 3204 return result + new String(spaces(countTrailingSpaces(values))); 3205 } 3206 private static char[] spaces(final int length) { final char[] result = new char[length]; Arrays.fill(result, ' '); return result; } 3207 private static int countTrailingSpaces(final String str) { 3208 if (str == null) {return 0;} 3209 int trailingSpaces = 0; 3210 for (int i = str.length() - 1; i >= 0 && str.charAt(i) == ' '; i--) { trailingSpaces++; } 3211 return trailingSpaces; 3212 } 3213 3214 /** Formats each of the specified values and appends it to the specified StringBuilder. 3215 * @param ansi whether the result should contain ANSI escape codes or not 3216 * @param values the values to format and append to the StringBuilder 3217 * @param sb the StringBuilder to collect the formatted strings 3218 * @param params the parameters to pass to the format method when formatting each value 3219 * @return the specified StringBuilder */ 3220 public static StringBuilder join(final Ansi ansi, final String[] values, final StringBuilder sb, final Object... params) { 3221 if (values != null) { 3222 final TextTable table = new TextTable(ansi, usageHelpWidth); 3223 table.indentWrappedLines = 0; 3224 for (final String summaryLine : values) { 3225 final Text[] lines = ansi.new Text(format(summaryLine, params)).splitLines(); 3226 for (final Text line : lines) { table.addRowValues(line); } 3227 } 3228 table.toString(sb); 3229 } 3230 return sb; 3231 } 3232 private static String format(final String formatString, final Object... params) { 3233 return formatString == null ? "" : String.format(formatString, params); 3234 } 3235 /** Returns command custom synopsis as a string. A custom synopsis can be zero or more lines, and can be 3236 * specified declaratively with the {@link Command#customSynopsis()} annotation attribute or programmatically 3237 * by setting the Help instance's {@link Help#customSynopsis} field. 3238 * @param params Arguments referenced by the format specifiers in the synopsis strings 3239 * @return the custom synopsis lines combined into a single String (which may be empty) 3240 */ 3241 public String customSynopsis(final Object... params) { 3242 return join(ansi(), customSynopsis, new StringBuilder(), params).toString(); 3243 } 3244 /** Returns command description text as a string. Description text can be zero or more lines, and can be specified 3245 * declaratively with the {@link Command#description()} annotation attribute or programmatically by 3246 * setting the Help instance's {@link Help#description} field. 3247 * @param params Arguments referenced by the format specifiers in the description strings 3248 * @return the description lines combined into a single String (which may be empty) 3249 */ 3250 public String description(final Object... params) { 3251 return join(ansi(), description, new StringBuilder(), params).toString(); 3252 } 3253 /** Returns the command header text as a string. Header text can be zero or more lines, and can be specified 3254 * declaratively with the {@link Command#header()} annotation attribute or programmatically by 3255 * setting the Help instance's {@link Help#header} field. 3256 * @param params Arguments referenced by the format specifiers in the header strings 3257 * @return the header lines combined into a single String (which may be empty) 3258 */ 3259 public String header(final Object... params) { 3260 return join(ansi(), header, new StringBuilder(), params).toString(); 3261 } 3262 /** Returns command footer text as a string. Footer text can be zero or more lines, and can be specified 3263 * declaratively with the {@link Command#footer()} annotation attribute or programmatically by 3264 * setting the Help instance's {@link Help#footer} field. 3265 * @param params Arguments referenced by the format specifiers in the footer strings 3266 * @return the footer lines combined into a single String (which may be empty) 3267 */ 3268 public String footer(final Object... params) { 3269 return join(ansi(), footer, new StringBuilder(), params).toString(); 3270 } 3271 3272 /** Returns the text displayed before the header text; the result of {@code String.format(headerHeading, params)}. 3273 * @param params the parameters to use to format the header heading 3274 * @return the formatted header heading */ 3275 public String headerHeading(final Object... params) { 3276 return heading(ansi(), headerHeading, params); 3277 } 3278 3279 /** Returns the text displayed before the synopsis text; the result of {@code String.format(synopsisHeading, params)}. 3280 * @param params the parameters to use to format the synopsis heading 3281 * @return the formatted synopsis heading */ 3282 public String synopsisHeading(final Object... params) { 3283 return heading(ansi(), synopsisHeading, params); 3284 } 3285 3286 /** Returns the text displayed before the description text; an empty string if there is no description, 3287 * otherwise the result of {@code String.format(descriptionHeading, params)}. 3288 * @param params the parameters to use to format the description heading 3289 * @return the formatted description heading */ 3290 public String descriptionHeading(final Object... params) { 3291 return empty(descriptionHeading) ? "" : heading(ansi(), descriptionHeading, params); 3292 } 3293 3294 /** Returns the text displayed before the positional parameter list; an empty string if there are no positional 3295 * parameters, otherwise the result of {@code String.format(parameterListHeading, params)}. 3296 * @param params the parameters to use to format the parameter list heading 3297 * @return the formatted parameter list heading */ 3298 public String parameterListHeading(final Object... params) { 3299 return positionalParametersFields.isEmpty() ? "" : heading(ansi(), parameterListHeading, params); 3300 } 3301 3302 /** Returns the text displayed before the option list; an empty string if there are no options, 3303 * otherwise the result of {@code String.format(optionListHeading, params)}. 3304 * @param params the parameters to use to format the option list heading 3305 * @return the formatted option list heading */ 3306 public String optionListHeading(final Object... params) { 3307 return optionFields.isEmpty() ? "" : heading(ansi(), optionListHeading, params); 3308 } 3309 3310 /** Returns the text displayed before the command list; an empty string if there are no commands, 3311 * otherwise the result of {@code String.format(commandListHeading, params)}. 3312 * @param params the parameters to use to format the command list heading 3313 * @return the formatted command list heading */ 3314 public String commandListHeading(final Object... params) { 3315 return commands.isEmpty() ? "" : heading(ansi(), commandListHeading, params); 3316 } 3317 3318 /** Returns the text displayed before the footer text; the result of {@code String.format(footerHeading, params)}. 3319 * @param params the parameters to use to format the footer heading 3320 * @return the formatted footer heading */ 3321 public String footerHeading(final Object... params) { 3322 return heading(ansi(), footerHeading, params); 3323 } 3324 /** Returns a 2-column list with command names and the first line of their header or (if absent) description. 3325 * @return a usage help section describing the added commands */ 3326 public String commandList() { 3327 if (commands.isEmpty()) { return ""; } 3328 final int commandLength = maxLength(commands.keySet()); 3329 final Help.TextTable textTable = new Help.TextTable(ansi(), 3330 new Help.Column(commandLength + 2, 2, Help.Column.Overflow.SPAN), 3331 new Help.Column(usageHelpWidth - (commandLength + 2), 2, Help.Column.Overflow.WRAP)); 3332 3333 for (final Map.Entry<String, Help> entry : commands.entrySet()) { 3334 final Help command = entry.getValue(); 3335 final String header = command.header != null && command.header.length > 0 ? command.header[0] 3336 : (command.description != null && command.description.length > 0 ? command.description[0] : ""); 3337 textTable.addRowValues(colorScheme.commandText(entry.getKey()), ansi().new Text(header)); 3338 } 3339 return textTable.toString(); 3340 } 3341 private static int maxLength(final Collection<String> any) { 3342 final List<String> strings = new ArrayList<String>(any); 3343 Collections.sort(strings, Collections.reverseOrder(Help.shortestFirst())); 3344 return strings.get(0).length(); 3345 } 3346 private static String join(final String[] names, final int offset, final int length, final String separator) { 3347 if (names == null) { return ""; } 3348 final StringBuilder result = new StringBuilder(); 3349 for (int i = offset; i < offset + length; i++) { 3350 result.append((i > offset) ? separator : "").append(names[i]); 3351 } 3352 return result.toString(); 3353 } 3354 private static String stringOf(final char chr, final int length) { 3355 final char[] buff = new char[length]; 3356 Arrays.fill(buff, chr); 3357 return new String(buff); 3358 } 3359 3360 /** Returns a {@code Layout} instance configured with the user preferences captured in this Help instance. 3361 * @return a Layout */ 3362 public Layout createDefaultLayout() { 3363 return new Layout(colorScheme, new TextTable(colorScheme.ansi()), createDefaultOptionRenderer(), createDefaultParameterRenderer()); 3364 } 3365 /** Returns a new default OptionRenderer which converts {@link Option Options} to five columns of text to match 3366 * the default {@linkplain TextTable TextTable} column layout. The first row of values looks like this: 3367 * <ol> 3368 * <li>the required option marker</li> 3369 * <li>2-character short option name (or empty string if no short option exists)</li> 3370 * <li>comma separator (only if both short option and long option exist, empty string otherwise)</li> 3371 * <li>comma-separated string with long option name(s)</li> 3372 * <li>first element of the {@link Option#description()} array</li> 3373 * </ol> 3374 * <p>Following this, there will be one row for each of the remaining elements of the {@link 3375 * Option#description()} array, and these rows look like {@code {"", "", "", "", option.description()[i]}}.</p> 3376 * <p>If configured, this option renderer adds an additional row to display the default field value.</p> 3377 * @return a new default OptionRenderer 3378 */ 3379 public IOptionRenderer createDefaultOptionRenderer() { 3380 final DefaultOptionRenderer result = new DefaultOptionRenderer(); 3381 result.requiredMarker = String.valueOf(requiredOptionMarker); 3382 if (showDefaultValues != null && showDefaultValues.booleanValue()) { 3383 result.command = this.command; 3384 } 3385 return result; 3386 } 3387 /** Returns a new minimal OptionRenderer which converts {@link Option Options} to a single row with two columns 3388 * of text: an option name and a description. If multiple names or descriptions exist, the first value is used. 3389 * @return a new minimal OptionRenderer */ 3390 public static IOptionRenderer createMinimalOptionRenderer() { 3391 return new MinimalOptionRenderer(); 3392 } 3393 3394 /** Returns a new default ParameterRenderer which converts {@link Parameters Parameters} to four columns of 3395 * text to match the default {@linkplain TextTable TextTable} column layout. The first row of values looks like this: 3396 * <ol> 3397 * <li>empty string </li> 3398 * <li>empty string </li> 3399 * <li>parameter(s) label as rendered by the {@link IParamLabelRenderer}</li> 3400 * <li>first element of the {@link Parameters#description()} array</li> 3401 * </ol> 3402 * <p>Following this, there will be one row for each of the remaining elements of the {@link 3403 * Parameters#description()} array, and these rows look like {@code {"", "", "", param.description()[i]}}.</p> 3404 * <p>If configured, this parameter renderer adds an additional row to display the default field value.</p> 3405 * @return a new default ParameterRenderer 3406 */ 3407 public IParameterRenderer createDefaultParameterRenderer() { 3408 final DefaultParameterRenderer result = new DefaultParameterRenderer(); 3409 result.requiredMarker = String.valueOf(requiredOptionMarker); 3410 return result; 3411 } 3412 /** Returns a new minimal ParameterRenderer which converts {@link Parameters Parameters} to a single row with 3413 * two columns of text: an option name and a description. If multiple descriptions exist, the first value is used. 3414 * @return a new minimal ParameterRenderer */ 3415 public static IParameterRenderer createMinimalParameterRenderer() { 3416 return new MinimalParameterRenderer(); 3417 } 3418 3419 /** Returns a value renderer that returns the {@code paramLabel} if defined or the field name otherwise. 3420 * @return a new minimal ParamLabelRenderer */ 3421 public static IParamLabelRenderer createMinimalParamLabelRenderer() { 3422 return new IParamLabelRenderer() { 3423 @Override 3424 public Text renderParameterLabel(final Field field, final Ansi ansi, final List<IStyle> styles) { 3425 final String text = DefaultParamLabelRenderer.renderParameterName(field); 3426 return ansi.apply(text, styles); 3427 } 3428 @Override 3429 public String separator() { return ""; } 3430 }; 3431 } 3432 /** Returns a new default value renderer that separates option parameters from their {@linkplain Option 3433 * options} with the specified separator string, surrounds optional parameters with {@code '['} and {@code ']'} 3434 * characters and uses ellipses ("...") to indicate that any number of a parameter are allowed. 3435 * @return a new default ParamLabelRenderer 3436 */ 3437 public IParamLabelRenderer createDefaultParamLabelRenderer() { 3438 return new DefaultParamLabelRenderer(separator); 3439 } 3440 /** Sorts Fields annotated with {@code Option} by their option name in case-insensitive alphabetic order. If an 3441 * Option has multiple names, the shortest name is used for the sorting. Help options follow non-help options. 3442 * @return a comparator that sorts fields by their option name in case-insensitive alphabetic order */ 3443 public static Comparator<Field> createShortOptionNameComparator() { 3444 return new SortByShortestOptionNameAlphabetically(); 3445 } 3446 /** Sorts Fields annotated with {@code Option} by their option {@linkplain Range#max max arity} first, by 3447 * {@linkplain Range#min min arity} next, and by {@linkplain #createShortOptionNameComparator() option name} last. 3448 * @return a comparator that sorts fields by arity first, then their option name */ 3449 public static Comparator<Field> createShortOptionArityAndNameComparator() { 3450 return new SortByOptionArityAndNameAlphabetically(); 3451 } 3452 /** Sorts short strings before longer strings. 3453 * @return a comparators that sorts short strings before longer strings */ 3454 public static Comparator<String> shortestFirst() { 3455 return new ShortestFirst(); 3456 } 3457 3458 /** Returns whether ANSI escape codes are enabled or not. 3459 * @return whether ANSI escape codes are enabled or not 3460 */ 3461 public Ansi ansi() { 3462 return colorScheme.ansi; 3463 } 3464 3465 /** When customizing online help for {@link Option Option} details, a custom {@code IOptionRenderer} can be 3466 * used to create textual representation of an Option in a tabular format: one or more rows, each containing 3467 * one or more columns. The {@link Layout Layout} is responsible for placing these text values in the 3468 * {@link TextTable TextTable}. */ 3469 public interface IOptionRenderer { 3470 /** 3471 * Returns a text representation of the specified Option and the Field that captures the option value. 3472 * @param option the command line option to show online usage help for 3473 * @param field the field that will hold the value for the command line option 3474 * @param parameterLabelRenderer responsible for rendering option parameters to text 3475 * @param scheme color scheme for applying ansi color styles to options and option parameters 3476 * @return a 2-dimensional array of text values: one or more rows, each containing one or more columns 3477 */ 3478 Text[][] render(Option option, Field field, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme); 3479 } 3480 /** The DefaultOptionRenderer converts {@link Option Options} to five columns of text to match the default 3481 * {@linkplain TextTable TextTable} column layout. The first row of values looks like this: 3482 * <ol> 3483 * <li>the required option marker (if the option is required)</li> 3484 * <li>2-character short option name (or empty string if no short option exists)</li> 3485 * <li>comma separator (only if both short option and long option exist, empty string otherwise)</li> 3486 * <li>comma-separated string with long option name(s)</li> 3487 * <li>first element of the {@link Option#description()} array</li> 3488 * </ol> 3489 * <p>Following this, there will be one row for each of the remaining elements of the {@link 3490 * Option#description()} array, and these rows look like {@code {"", "", "", option.description()[i]}}.</p> 3491 */ 3492 static class DefaultOptionRenderer implements IOptionRenderer { 3493 public String requiredMarker = " "; 3494 public Object command; 3495 private String sep; 3496 private boolean showDefault; 3497 @Override 3498 public Text[][] render(final Option option, final Field field, final IParamLabelRenderer paramLabelRenderer, final ColorScheme scheme) { 3499 final String[] names = ShortestFirst.sort(option.names()); 3500 final int shortOptionCount = names[0].length() == 2 ? 1 : 0; 3501 final String shortOption = shortOptionCount > 0 ? names[0] : ""; 3502 sep = shortOptionCount > 0 && names.length > 1 ? "," : ""; 3503 3504 final String longOption = join(names, shortOptionCount, names.length - shortOptionCount, ", "); 3505 final Text longOptionText = createLongOptionText(field, paramLabelRenderer, scheme, longOption); 3506 3507 showDefault = command != null && !option.help() && !isBoolean(field.getType()); 3508 final Object defaultValue = createDefaultValue(field); 3509 3510 final String requiredOption = option.required() ? requiredMarker : ""; 3511 return renderDescriptionLines(option, scheme, requiredOption, shortOption, longOptionText, defaultValue); 3512 } 3513 3514 private Object createDefaultValue(final Field field) { 3515 Object defaultValue = null; 3516 try { 3517 defaultValue = field.get(command); 3518 if (defaultValue == null) { showDefault = false; } // #201 don't show null default values 3519 else if (field.getType().isArray()) { 3520 final StringBuilder sb = new StringBuilder(); 3521 for (int i = 0; i < Array.getLength(defaultValue); i++) { 3522 sb.append(i > 0 ? ", " : "").append(Array.get(defaultValue, i)); 3523 } 3524 defaultValue = sb.insert(0, "[").append("]").toString(); 3525 } 3526 } catch (final Exception ex) { 3527 showDefault = false; 3528 } 3529 return defaultValue; 3530 } 3531 3532 private Text createLongOptionText(final Field field, final IParamLabelRenderer renderer, final ColorScheme scheme, final String longOption) { 3533 Text paramLabelText = renderer.renderParameterLabel(field, scheme.ansi(), scheme.optionParamStyles); 3534 3535 // if no long option, fill in the space between the short option name and the param label value 3536 if (paramLabelText.length > 0 && longOption.length() == 0) { 3537 sep = renderer.separator(); 3538 // #181 paramLabelText may be =LABEL or [=LABEL...] 3539 final int sepStart = paramLabelText.plainString().indexOf(sep); 3540 final Text prefix = paramLabelText.substring(0, sepStart); 3541 paramLabelText = prefix.append(paramLabelText.substring(sepStart + sep.length())); 3542 } 3543 Text longOptionText = scheme.optionText(longOption); 3544 longOptionText = longOptionText.append(paramLabelText); 3545 return longOptionText; 3546 } 3547 3548 private Text[][] renderDescriptionLines(final Option option, 3549 final ColorScheme scheme, 3550 final String requiredOption, 3551 final String shortOption, 3552 final Text longOptionText, 3553 final Object defaultValue) { 3554 final Text EMPTY = Ansi.EMPTY_TEXT; 3555 final List<Text[]> result = new ArrayList<Text[]>(); 3556 Text[] descriptionFirstLines = scheme.ansi().new Text(str(option.description(), 0)).splitLines(); 3557 if (descriptionFirstLines.length == 0) { 3558 if (showDefault) { 3559 descriptionFirstLines = new Text[]{scheme.ansi().new Text(" Default: " + defaultValue)}; 3560 showDefault = false; // don't show the default value twice 3561 } else { 3562 descriptionFirstLines = new Text[]{ EMPTY }; 3563 } 3564 } 3565 result.add(new Text[] { scheme.optionText(requiredOption), scheme.optionText(shortOption), 3566 scheme.ansi().new Text(sep), longOptionText, descriptionFirstLines[0] }); 3567 for (int i = 1; i < descriptionFirstLines.length; i++) { 3568 result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, descriptionFirstLines[i] }); 3569 } 3570 for (int i = 1; i < option.description().length; i++) { 3571 final Text[] descriptionNextLines = scheme.ansi().new Text(option.description()[i]).splitLines(); 3572 for (final Text line : descriptionNextLines) { 3573 result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, line }); 3574 } 3575 } 3576 if (showDefault) { 3577 result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, scheme.ansi().new Text(" Default: " + defaultValue) }); 3578 } 3579 return result.toArray(new Text[result.size()][]); 3580 } 3581 } 3582 /** The MinimalOptionRenderer converts {@link Option Options} to a single row with two columns of text: an 3583 * option name and a description. If multiple names or description lines exist, the first value is used. */ 3584 static class MinimalOptionRenderer implements IOptionRenderer { 3585 @Override 3586 public Text[][] render(final Option option, final Field field, final IParamLabelRenderer parameterLabelRenderer, final ColorScheme scheme) { 3587 Text optionText = scheme.optionText(option.names()[0]); 3588 final Text paramLabelText = parameterLabelRenderer.renderParameterLabel(field, scheme.ansi(), scheme.optionParamStyles); 3589 optionText = optionText.append(paramLabelText); 3590 return new Text[][] {{ optionText, 3591 scheme.ansi().new Text(option.description().length == 0 ? "" : option.description()[0]) }}; 3592 } 3593 } 3594 /** The MinimalParameterRenderer converts {@link Parameters Parameters} to a single row with two columns of 3595 * text: the parameters label and a description. If multiple description lines exist, the first value is used. */ 3596 static class MinimalParameterRenderer implements IParameterRenderer { 3597 @Override 3598 public Text[][] render(final Parameters param, final Field field, final IParamLabelRenderer parameterLabelRenderer, final ColorScheme scheme) { 3599 return new Text[][] {{ parameterLabelRenderer.renderParameterLabel(field, scheme.ansi(), scheme.parameterStyles), 3600 scheme.ansi().new Text(param.description().length == 0 ? "" : param.description()[0]) }}; 3601 } 3602 } 3603 /** When customizing online help for {@link Parameters Parameters} details, a custom {@code IParameterRenderer} 3604 * can be used to create textual representation of a Parameters field in a tabular format: one or more rows, 3605 * each containing one or more columns. The {@link Layout Layout} is responsible for placing these text 3606 * values in the {@link TextTable TextTable}. */ 3607 public interface IParameterRenderer { 3608 /** 3609 * Returns a text representation of the specified Parameters and the Field that captures the parameter values. 3610 * @param parameters the command line parameters to show online usage help for 3611 * @param field the field that will hold the value for the command line parameters 3612 * @param parameterLabelRenderer responsible for rendering parameter labels to text 3613 * @param scheme color scheme for applying ansi color styles to positional parameters 3614 * @return a 2-dimensional array of text values: one or more rows, each containing one or more columns 3615 */ 3616 Text[][] render(Parameters parameters, Field field, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme); 3617 } 3618 /** The DefaultParameterRenderer converts {@link Parameters Parameters} to five columns of text to match the 3619 * default {@linkplain TextTable TextTable} column layout. The first row of values looks like this: 3620 * <ol> 3621 * <li>the required option marker (if the parameter's arity is to have at least one value)</li> 3622 * <li>empty string </li> 3623 * <li>empty string </li> 3624 * <li>parameter(s) label as rendered by the {@link IParamLabelRenderer}</li> 3625 * <li>first element of the {@link Parameters#description()} array</li> 3626 * </ol> 3627 * <p>Following this, there will be one row for each of the remaining elements of the {@link 3628 * Parameters#description()} array, and these rows look like {@code {"", "", "", param.description()[i]}}.</p> 3629 */ 3630 static class DefaultParameterRenderer implements IParameterRenderer { 3631 public String requiredMarker = " "; 3632 @Override 3633 public Text[][] render(final Parameters params, final Field field, final IParamLabelRenderer paramLabelRenderer, final ColorScheme scheme) { 3634 final Text label = paramLabelRenderer.renderParameterLabel(field, scheme.ansi(), scheme.parameterStyles); 3635 final Text requiredParameter = scheme.parameterText(Range.parameterArity(field).min > 0 ? requiredMarker : ""); 3636 3637 final Text EMPTY = Ansi.EMPTY_TEXT; 3638 final List<Text[]> result = new ArrayList<Text[]>(); 3639 Text[] descriptionFirstLines = scheme.ansi().new Text(str(params.description(), 0)).splitLines(); 3640 if (descriptionFirstLines.length == 0) { descriptionFirstLines = new Text[]{ EMPTY }; } 3641 result.add(new Text[] { requiredParameter, EMPTY, EMPTY, label, descriptionFirstLines[0] }); 3642 for (int i = 1; i < descriptionFirstLines.length; i++) { 3643 result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, descriptionFirstLines[i] }); 3644 } 3645 for (int i = 1; i < params.description().length; i++) { 3646 final Text[] descriptionNextLines = scheme.ansi().new Text(params.description()[i]).splitLines(); 3647 for (final Text line : descriptionNextLines) { 3648 result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, line }); 3649 } 3650 } 3651 return result.toArray(new Text[result.size()][]); 3652 } 3653 } 3654 /** When customizing online usage help for an option parameter or a positional parameter, a custom 3655 * {@code IParamLabelRenderer} can be used to render the parameter name or label to a String. */ 3656 public interface IParamLabelRenderer { 3657 3658 /** Returns a text rendering of the Option parameter or positional parameter; returns an empty string 3659 * {@code ""} if the option is a boolean and does not take a parameter. 3660 * @param field the annotated field with a parameter label 3661 * @param ansi determines whether ANSI escape codes should be emitted or not 3662 * @param styles the styles to apply to the parameter label 3663 * @return a text rendering of the Option parameter or positional parameter */ 3664 Text renderParameterLabel(Field field, Ansi ansi, List<IStyle> styles); 3665 3666 /** Returns the separator between option name and param label. 3667 * @return the separator between option name and param label */ 3668 String separator(); 3669 } 3670 /** 3671 * DefaultParamLabelRenderer separates option parameters from their {@linkplain Option options} with a 3672 * {@linkplain DefaultParamLabelRenderer#separator separator} string, surrounds optional values 3673 * with {@code '['} and {@code ']'} characters and uses ellipses ("...") to indicate that any number of 3674 * values is allowed for options or parameters with variable arity. 3675 */ 3676 static class DefaultParamLabelRenderer implements IParamLabelRenderer { 3677 /** The string to use to separate option parameters from their options. */ 3678 public final String separator; 3679 /** Constructs a new DefaultParamLabelRenderer with the specified separator string. */ 3680 public DefaultParamLabelRenderer(final String separator) { 3681 this.separator = Assert.notNull(separator, "separator"); 3682 } 3683 @Override 3684 public String separator() { return separator; } 3685 @Override 3686 public Text renderParameterLabel(final Field field, final Ansi ansi, final List<IStyle> styles) { 3687 final boolean isOptionParameter = field.isAnnotationPresent(Option.class); 3688 final Range arity = isOptionParameter ? Range.optionArity(field) : Range.parameterCapacity(field); 3689 final String split = isOptionParameter ? field.getAnnotation(Option.class).split() : field.getAnnotation(Parameters.class).split(); 3690 Text result = ansi.new Text(""); 3691 String sep = isOptionParameter ? separator : ""; 3692 Text paramName = ansi.apply(renderParameterName(field), styles); 3693 if (!empty(split)) { paramName = paramName.append("[" + split).append(paramName).append("]..."); } // #194 3694 for (int i = 0; i < arity.min; i++) { 3695 result = result.append(sep).append(paramName); 3696 sep = " "; 3697 } 3698 if (arity.isVariable) { 3699 if (result.length == 0) { // arity="*" or arity="0..*" 3700 result = result.append(sep + "[").append(paramName).append("]..."); 3701 } else if (!result.plainString().endsWith("...")) { // split param may already end with "..." 3702 result = result.append("..."); 3703 } 3704 } else { 3705 sep = result.length == 0 ? (isOptionParameter ? separator : "") : " "; 3706 for (int i = arity.min; i < arity.max; i++) { 3707 if (sep.trim().length() == 0) { 3708 result = result.append(sep + "[").append(paramName); 3709 } else { 3710 result = result.append("[" + sep).append(paramName); 3711 } 3712 sep = " "; 3713 } 3714 for (int i = arity.min; i < arity.max; i++) { result = result.append("]"); } 3715 } 3716 return result; 3717 } 3718 private static String renderParameterName(final Field field) { 3719 String result = null; 3720 if (field.isAnnotationPresent(Option.class)) { 3721 result = field.getAnnotation(Option.class).paramLabel(); 3722 } else if (field.isAnnotationPresent(Parameters.class)) { 3723 result = field.getAnnotation(Parameters.class).paramLabel(); 3724 } 3725 if (result != null && result.trim().length() > 0) { 3726 return result.trim(); 3727 } 3728 String name = field.getName(); 3729 if (Map.class.isAssignableFrom(field.getType())) { // #195 better param labels for map fields 3730 final Class<?>[] paramTypes = getTypeAttribute(field); 3731 if (paramTypes.length < 2 || paramTypes[0] == null || paramTypes[1] == null) { 3732 name = "String=String"; 3733 } else { name = paramTypes[0].getSimpleName() + "=" + paramTypes[1].getSimpleName(); } 3734 } 3735 return "<" + name + ">"; 3736 } 3737 } 3738 /** Use a Layout to format usage help text for options and parameters in tabular format. 3739 * <p>Delegates to the renderers to create {@link Text} values for the annotated fields, and uses a 3740 * {@link TextTable} to display these values in tabular format. Layout is responsible for deciding which values 3741 * to display where in the table. By default, Layout shows one option or parameter per table row.</p> 3742 * <p>Customize by overriding the {@link #layout(Field, CommandLine.Help.Ansi.Text[][])} method.</p> 3743 * @see IOptionRenderer rendering options to text 3744 * @see IParameterRenderer rendering parameters to text 3745 * @see TextTable showing values in a tabular format 3746 */ 3747 public static class Layout { 3748 protected final ColorScheme colorScheme; 3749 protected final TextTable table; 3750 protected IOptionRenderer optionRenderer; 3751 protected IParameterRenderer parameterRenderer; 3752 3753 /** Constructs a Layout with the specified color scheme, a new default TextTable, the 3754 * {@linkplain Help#createDefaultOptionRenderer() default option renderer}, and the 3755 * {@linkplain Help#createDefaultParameterRenderer() default parameter renderer}. 3756 * @param colorScheme the color scheme to use for common, auto-generated parts of the usage help message */ 3757 public Layout(final ColorScheme colorScheme) { this(colorScheme, new TextTable(colorScheme.ansi())); } 3758 3759 /** Constructs a Layout with the specified color scheme, the specified TextTable, the 3760 * {@linkplain Help#createDefaultOptionRenderer() default option renderer}, and the 3761 * {@linkplain Help#createDefaultParameterRenderer() default parameter renderer}. 3762 * @param colorScheme the color scheme to use for common, auto-generated parts of the usage help message 3763 * @param textTable the TextTable to lay out parts of the usage help message in tabular format */ 3764 public Layout(final ColorScheme colorScheme, final TextTable textTable) { 3765 this(colorScheme, textTable, new DefaultOptionRenderer(), new DefaultParameterRenderer()); 3766 } 3767 /** Constructs a Layout with the specified color scheme, the specified TextTable, the 3768 * specified option renderer and the specified parameter renderer. 3769 * @param colorScheme the color scheme to use for common, auto-generated parts of the usage help message 3770 * @param optionRenderer the object responsible for rendering Options to Text 3771 * @param parameterRenderer the object responsible for rendering Parameters to Text 3772 * @param textTable the TextTable to lay out parts of the usage help message in tabular format */ 3773 public Layout(final ColorScheme colorScheme, final TextTable textTable, final IOptionRenderer optionRenderer, final IParameterRenderer parameterRenderer) { 3774 this.colorScheme = Assert.notNull(colorScheme, "colorScheme"); 3775 this.table = Assert.notNull(textTable, "textTable"); 3776 this.optionRenderer = Assert.notNull(optionRenderer, "optionRenderer"); 3777 this.parameterRenderer = Assert.notNull(parameterRenderer, "parameterRenderer"); 3778 } 3779 /** 3780 * Copies the specified text values into the correct cells in the {@link TextTable}. This implementation 3781 * delegates to {@link TextTable#addRowValues(CommandLine.Help.Ansi.Text...)} for each row of values. 3782 * <p>Subclasses may override.</p> 3783 * @param field the field annotated with the specified Option or Parameters 3784 * @param cellValues the text values representing the Option/Parameters, to be displayed in tabular form 3785 */ 3786 public void layout(final Field field, final Text[][] cellValues) { 3787 for (final Text[] oneRow : cellValues) { 3788 table.addRowValues(oneRow); 3789 } 3790 } 3791 /** Calls {@link #addOption(Field, CommandLine.Help.IParamLabelRenderer)} for all non-hidden Options in the list. 3792 * @param fields fields annotated with {@link Option} to add usage descriptions for 3793 * @param paramLabelRenderer object that knows how to render option parameters */ 3794 public void addOptions(final List<Field> fields, final IParamLabelRenderer paramLabelRenderer) { 3795 for (final Field field : fields) { 3796 final Option option = field.getAnnotation(Option.class); 3797 if (!option.hidden()) { 3798 addOption(field, paramLabelRenderer); 3799 } 3800 } 3801 } 3802 /** 3803 * Delegates to the {@link #optionRenderer option renderer} of this layout to obtain 3804 * text values for the specified {@link Option}, and then calls the {@link #layout(Field, CommandLine.Help.Ansi.Text[][])} 3805 * method to write these text values into the correct cells in the TextTable. 3806 * @param field the field annotated with the specified Option 3807 * @param paramLabelRenderer knows how to render option parameters 3808 */ 3809 public void addOption(final Field field, final IParamLabelRenderer paramLabelRenderer) { 3810 final Option option = field.getAnnotation(Option.class); 3811 final Text[][] values = optionRenderer.render(option, field, paramLabelRenderer, colorScheme); 3812 layout(field, values); 3813 } 3814 /** Calls {@link #addPositionalParameter(Field, CommandLine.Help.IParamLabelRenderer)} for all non-hidden Parameters in the list. 3815 * @param fields fields annotated with {@link Parameters} to add usage descriptions for 3816 * @param paramLabelRenderer knows how to render option parameters */ 3817 public void addPositionalParameters(final List<Field> fields, final IParamLabelRenderer paramLabelRenderer) { 3818 for (final Field field : fields) { 3819 final Parameters parameters = field.getAnnotation(Parameters.class); 3820 if (!parameters.hidden()) { 3821 addPositionalParameter(field, paramLabelRenderer); 3822 } 3823 } 3824 } 3825 /** 3826 * Delegates to the {@link #parameterRenderer parameter renderer} of this layout 3827 * to obtain text values for the specified {@link Parameters}, and then calls 3828 * {@link #layout(Field, CommandLine.Help.Ansi.Text[][])} to write these text values into the correct cells in the TextTable. 3829 * @param field the field annotated with the specified Parameters 3830 * @param paramLabelRenderer knows how to render option parameters 3831 */ 3832 public void addPositionalParameter(final Field field, final IParamLabelRenderer paramLabelRenderer) { 3833 final Parameters option = field.getAnnotation(Parameters.class); 3834 final Text[][] values = parameterRenderer.render(option, field, paramLabelRenderer, colorScheme); 3835 layout(field, values); 3836 } 3837 /** Returns the section of the usage help message accumulated in the TextTable owned by this layout. */ 3838 @Override public String toString() { 3839 return table.toString(); 3840 } 3841 } 3842 /** Sorts short strings before longer strings. */ 3843 static class ShortestFirst implements Comparator<String> { 3844 @Override 3845 public int compare(final String o1, final String o2) { 3846 return o1.length() - o2.length(); 3847 } 3848 /** Sorts the specified array of Strings shortest-first and returns it. */ 3849 public static String[] sort(final String[] names) { 3850 Arrays.sort(names, new ShortestFirst()); 3851 return names; 3852 } 3853 } 3854 /** Sorts {@code Option} instances by their name in case-insensitive alphabetic order. If an Option has 3855 * multiple names, the shortest name is used for the sorting. Help options follow non-help options. */ 3856 static class SortByShortestOptionNameAlphabetically implements Comparator<Field> { 3857 @Override 3858 public int compare(final Field f1, final Field f2) { 3859 final Option o1 = f1.getAnnotation(Option.class); 3860 final Option o2 = f2.getAnnotation(Option.class); 3861 if (o1 == null) { return 1; } else if (o2 == null) { return -1; } // options before params 3862 final String[] names1 = ShortestFirst.sort(o1.names()); 3863 final String[] names2 = ShortestFirst.sort(o2.names()); 3864 int result = names1[0].toUpperCase().compareTo(names2[0].toUpperCase()); // case insensitive sort 3865 result = result == 0 ? -names1[0].compareTo(names2[0]) : result; // lower case before upper case 3866 return o1.help() == o2.help() ? result : o2.help() ? -1 : 1; // help options come last 3867 } 3868 } 3869 /** Sorts {@code Option} instances by their max arity first, then their min arity, then delegates to super class. */ 3870 static class SortByOptionArityAndNameAlphabetically extends SortByShortestOptionNameAlphabetically { 3871 @Override 3872 public int compare(final Field f1, final Field f2) { 3873 final Option o1 = f1.getAnnotation(Option.class); 3874 final Option o2 = f2.getAnnotation(Option.class); 3875 final Range arity1 = Range.optionArity(f1); 3876 final Range arity2 = Range.optionArity(f2); 3877 int result = arity1.max - arity2.max; 3878 if (result == 0) { 3879 result = arity1.min - arity2.min; 3880 } 3881 if (result == 0) { // arity is same 3882 if (isMultiValue(f1) && !isMultiValue(f2)) { result = 1; } // f1 > f2 3883 if (!isMultiValue(f1) && isMultiValue(f2)) { result = -1; } // f1 < f2 3884 } 3885 return result == 0 ? super.compare(f1, f2) : result; 3886 } 3887 } 3888 /** 3889 * <p>Responsible for spacing out {@link Text} values according to the {@link Column} definitions the table was 3890 * created with. Columns have a width, indentation, and an overflow policy that decides what to do if a value is 3891 * longer than the column's width.</p> 3892 */ 3893 public static class TextTable { 3894 /** 3895 * Helper class to index positions in a {@code Help.TextTable}. 3896 * @since 2.0 3897 */ 3898 public static class Cell { 3899 /** Table column index (zero based). */ 3900 public final int column; 3901 /** Table row index (zero based). */ 3902 public final int row; 3903 /** Constructs a new Cell with the specified coordinates in the table. 3904 * @param column the zero-based table column 3905 * @param row the zero-based table row */ 3906 public Cell(final int column, final int row) { this.column = column; this.row = row; } 3907 } 3908 3909 /** The column definitions of this table. */ 3910 public final Column[] columns; 3911 3912 /** The {@code char[]} slots of the {@code TextTable} to copy text values into. */ 3913 protected final List<Text> columnValues = new ArrayList<Text>(); 3914 3915 /** By default, indent wrapped lines by 2 spaces. */ 3916 public int indentWrappedLines = 2; 3917 3918 private final Ansi ansi; 3919 3920 /** Constructs a TextTable with five columns as follows: 3921 * <ol> 3922 * <li>required option/parameter marker (width: 2, indent: 0, TRUNCATE on overflow)</li> 3923 * <li>short option name (width: 2, indent: 0, TRUNCATE on overflow)</li> 3924 * <li>comma separator (width: 1, indent: 0, TRUNCATE on overflow)</li> 3925 * <li>long option name(s) (width: 24, indent: 1, SPAN multiple columns on overflow)</li> 3926 * <li>description line(s) (width: 51, indent: 1, WRAP to next row on overflow)</li> 3927 * </ol> 3928 * @param ansi whether to emit ANSI escape codes or not 3929 */ 3930 public TextTable(final Ansi ansi) { 3931 // "* -c, --create Creates a ...." 3932 this(ansi, new Column[] { 3933 new Column(2, 0, TRUNCATE), // "*" 3934 new Column(2, 0, TRUNCATE), // "-c" 3935 new Column(1, 0, TRUNCATE), // "," 3936 new Column(optionsColumnWidth - 2 - 2 - 1 , 1, SPAN), // " --create" 3937 new Column(usageHelpWidth - optionsColumnWidth, 1, WRAP) // " Creates a ..." 3938 }); 3939 } 3940 3941 /** Constructs a new TextTable with columns with the specified width, all SPANning multiple columns on 3942 * overflow except the last column which WRAPS to the next row. 3943 * @param ansi whether to emit ANSI escape codes or not 3944 * @param columnWidths the width of the table columns (all columns have zero indent) 3945 */ 3946 public TextTable(final Ansi ansi, final int... columnWidths) { 3947 this.ansi = Assert.notNull(ansi, "ansi"); 3948 columns = new Column[columnWidths.length]; 3949 for (int i = 0; i < columnWidths.length; i++) { 3950 columns[i] = new Column(columnWidths[i], 0, i == columnWidths.length - 1 ? SPAN: WRAP); 3951 } 3952 } 3953 /** Constructs a {@code TextTable} with the specified columns. 3954 * @param ansi whether to emit ANSI escape codes or not 3955 * @param columns columns to construct this TextTable with */ 3956 public TextTable(final Ansi ansi, final Column... columns) { 3957 this.ansi = Assert.notNull(ansi, "ansi"); 3958 this.columns = Assert.notNull(columns, "columns"); 3959 if (columns.length == 0) { throw new IllegalArgumentException("At least one column is required"); } 3960 } 3961 /** Returns the {@code Text} slot at the specified row and column to write a text value into. 3962 * @param row the row of the cell whose Text to return 3963 * @param col the column of the cell whose Text to return 3964 * @return the Text object at the specified row and column 3965 * @since 2.0 */ 3966 public Text textAt(final int row, final int col) { return columnValues.get(col + (row * columns.length)); } 3967 3968 /** Returns the {@code Text} slot at the specified row and column to write a text value into. 3969 * @param row the row of the cell whose Text to return 3970 * @param col the column of the cell whose Text to return 3971 * @return the Text object at the specified row and column 3972 * @deprecated use {@link #textAt(int, int)} instead */ 3973 @Deprecated 3974 public Text cellAt(final int row, final int col) { return textAt(row, col); } 3975 3976 /** Returns the current number of rows of this {@code TextTable}. 3977 * @return the current number of rows in this TextTable */ 3978 public int rowCount() { return columnValues.size() / columns.length; } 3979 3980 /** Adds the required {@code char[]} slots for a new row to the {@link #columnValues} field. */ 3981 public void addEmptyRow() { 3982 for (int i = 0; i < columns.length; i++) { 3983 columnValues.add(ansi.new Text(columns[i].width)); 3984 } 3985 } 3986 3987 /** Delegates to {@link #addRowValues(CommandLine.Help.Ansi.Text...)}. 3988 * @param values the text values to display in each column of the current row */ 3989 public void addRowValues(final String... values) { 3990 final Text[] array = new Text[values.length]; 3991 for (int i = 0; i < array.length; i++) { 3992 array[i] = values[i] == null ? Ansi.EMPTY_TEXT : ansi.new Text(values[i]); 3993 } 3994 addRowValues(array); 3995 } 3996 /** 3997 * Adds a new {@linkplain TextTable#addEmptyRow() empty row}, then calls {@link 3998 * TextTable#putValue(int, int, CommandLine.Help.Ansi.Text) putValue} for each of the specified values, adding more empty rows 3999 * if the return value indicates that the value spanned multiple columns or was wrapped to multiple rows. 4000 * @param values the values to write into a new row in this TextTable 4001 * @throws IllegalArgumentException if the number of values exceeds the number of Columns in this table 4002 */ 4003 public void addRowValues(final Text... values) { 4004 if (values.length > columns.length) { 4005 throw new IllegalArgumentException(values.length + " values don't fit in " + 4006 columns.length + " columns"); 4007 } 4008 addEmptyRow(); 4009 for (int col = 0; col < values.length; col++) { 4010 final int row = rowCount() - 1;// write to last row: previous value may have wrapped to next row 4011 final Cell cell = putValue(row, col, values[col]); 4012 4013 // add row if a value spanned/wrapped and there are still remaining values 4014 if ((cell.row != row || cell.column != col) && col != values.length - 1) { 4015 addEmptyRow(); 4016 } 4017 } 4018 } 4019 /** 4020 * Writes the specified value into the cell at the specified row and column and returns the last row and 4021 * column written to. Depending on the Column's {@link Column#overflow Overflow} policy, the value may span 4022 * multiple columns or wrap to multiple rows when larger than the column width. 4023 * @param row the target row in the table 4024 * @param col the target column in the table to write to 4025 * @param value the value to write 4026 * @return a Cell indicating the position in the table that was last written to (since 2.0) 4027 * @throws IllegalArgumentException if the specified row exceeds the table's {@linkplain 4028 * TextTable#rowCount() row count} 4029 * @since 2.0 (previous versions returned a {@code java.awt.Point} object) 4030 */ 4031 public Cell putValue(int row, int col, Text value) { 4032 if (row > rowCount() - 1) { 4033 throw new IllegalArgumentException("Cannot write to row " + row + ": rowCount=" + rowCount()); 4034 } 4035 if (value == null || value.plain.length() == 0) { return new Cell(col, row); } 4036 final Column column = columns[col]; 4037 int indent = column.indent; 4038 switch (column.overflow) { 4039 case TRUNCATE: 4040 copy(value, textAt(row, col), indent); 4041 return new Cell(col, row); 4042 case SPAN: 4043 final int startColumn = col; 4044 do { 4045 final boolean lastColumn = col == columns.length - 1; 4046 final int charsWritten = lastColumn 4047 ? copy(BreakIterator.getLineInstance(), value, textAt(row, col), indent) 4048 : copy(value, textAt(row, col), indent); 4049 value = value.substring(charsWritten); 4050 indent = 0; 4051 if (value.length > 0) { // value did not fit in column 4052 ++col; // write remainder of value in next column 4053 } 4054 if (value.length > 0 && col >= columns.length) { // we filled up all columns on this row 4055 addEmptyRow(); 4056 row++; 4057 col = startColumn; 4058 indent = column.indent + indentWrappedLines; 4059 } 4060 } while (value.length > 0); 4061 return new Cell(col, row); 4062 case WRAP: 4063 final BreakIterator lineBreakIterator = BreakIterator.getLineInstance(); 4064 do { 4065 final int charsWritten = copy(lineBreakIterator, value, textAt(row, col), indent); 4066 value = value.substring(charsWritten); 4067 indent = column.indent + indentWrappedLines; 4068 if (value.length > 0) { // value did not fit in column 4069 ++row; // write remainder of value in next row 4070 addEmptyRow(); 4071 } 4072 } while (value.length > 0); 4073 return new Cell(col, row); 4074 } 4075 throw new IllegalStateException(column.overflow.toString()); 4076 } 4077 private static int length(final Text str) { 4078 return str.length; // TODO count some characters as double length 4079 } 4080 4081 private int copy(final BreakIterator line, final Text text, final Text columnValue, final int offset) { 4082 // Deceive the BreakIterator to ensure no line breaks after '-' character 4083 line.setText(text.plainString().replace("-", "\u00ff")); 4084 int done = 0; 4085 for (int start = line.first(), end = line.next(); end != BreakIterator.DONE; start = end, end = line.next()) { 4086 final Text word = text.substring(start, end); //.replace("\u00ff", "-"); // not needed 4087 if (columnValue.maxLength >= offset + done + length(word)) { 4088 done += copy(word, columnValue, offset + done); // TODO localized length 4089 } else { 4090 break; 4091 } 4092 } 4093 if (done == 0 && length(text) > columnValue.maxLength) { 4094 // The value is a single word that is too big to be written to the column. Write as much as we can. 4095 done = copy(text, columnValue, offset); 4096 } 4097 return done; 4098 } 4099 private static int copy(final Text value, final Text destination, final int offset) { 4100 final int length = Math.min(value.length, destination.maxLength - offset); 4101 value.getStyledChars(value.from, length, destination, offset); 4102 return length; 4103 } 4104 4105 /** Copies the text representation that we built up from the options into the specified StringBuilder. 4106 * @param text the StringBuilder to write into 4107 * @return the specified StringBuilder object (to allow method chaining and a more fluid API) */ 4108 public StringBuilder toString(final StringBuilder text) { 4109 final int columnCount = this.columns.length; 4110 final StringBuilder row = new StringBuilder(usageHelpWidth); 4111 for (int i = 0; i < columnValues.size(); i++) { 4112 final Text column = columnValues.get(i); 4113 row.append(column.toString()); 4114 row.append(new String(spaces(columns[i % columnCount].width - column.length))); 4115 if (i % columnCount == columnCount - 1) { 4116 int lastChar = row.length() - 1; 4117 while (lastChar >= 0 && row.charAt(lastChar) == ' ') {lastChar--;} // rtrim 4118 row.setLength(lastChar + 1); 4119 text.append(row.toString()).append(System.getProperty("line.separator")); 4120 row.setLength(0); 4121 } 4122 } 4123 //if (Ansi.enabled()) { text.append(Style.reset.off()); } 4124 return text; 4125 } 4126 @Override 4127 public String toString() { return toString(new StringBuilder()).toString(); } 4128 } 4129 /** Columns define the width, indent (leading number of spaces in a column before the value) and 4130 * {@linkplain Overflow Overflow} policy of a column in a {@linkplain TextTable TextTable}. */ 4131 public static class Column { 4132 4133 /** Policy for handling text that is longer than the column width: 4134 * span multiple columns, wrap to the next row, or simply truncate the portion that doesn't fit. */ 4135 public enum Overflow { TRUNCATE, SPAN, WRAP } 4136 4137 /** Column width in characters */ 4138 public final int width; 4139 4140 /** Indent (number of empty spaces at the start of the column preceding the text value) */ 4141 public final int indent; 4142 4143 /** Policy that determines how to handle values larger than the column width. */ 4144 public final Overflow overflow; 4145 public Column(final int width, final int indent, final Overflow overflow) { 4146 this.width = width; 4147 this.indent = indent; 4148 this.overflow = Assert.notNull(overflow, "overflow"); 4149 } 4150 } 4151 4152 /** All usage help message are generated with a color scheme that assigns certain styles and colors to common 4153 * parts of a usage message: the command name, options, positional parameters and option parameters. 4154 * Users may customize these styles by creating Help with a custom color scheme. 4155 * <p>Note that these options and styles may not be rendered if ANSI escape codes are not 4156 * {@linkplain Ansi#enabled() enabled}.</p> 4157 * @see Help#defaultColorScheme(Ansi) 4158 */ 4159 public static class ColorScheme { 4160 public final List<IStyle> commandStyles = new ArrayList<IStyle>(); 4161 public final List<IStyle> optionStyles = new ArrayList<IStyle>(); 4162 public final List<IStyle> parameterStyles = new ArrayList<IStyle>(); 4163 public final List<IStyle> optionParamStyles = new ArrayList<IStyle>(); 4164 private final Ansi ansi; 4165 4166 /** Constructs a new ColorScheme with {@link Help.Ansi#AUTO}. */ 4167 public ColorScheme() { this(Ansi.AUTO); } 4168 4169 /** Constructs a new ColorScheme with the specified Ansi enabled mode. 4170 * @param ansi whether to emit ANSI escape codes or not 4171 */ 4172 public ColorScheme(final Ansi ansi) {this.ansi = Assert.notNull(ansi, "ansi"); } 4173 4174 /** Adds the specified styles to the registered styles for commands in this color scheme and returns this color scheme. 4175 * @param styles the styles to add to the registered styles for commands in this color scheme 4176 * @return this color scheme to enable method chaining for a more fluent API */ 4177 public ColorScheme commands(final IStyle... styles) { return addAll(commandStyles, styles); } 4178 /** Adds the specified styles to the registered styles for options in this color scheme and returns this color scheme. 4179 * @param styles the styles to add to registered the styles for options in this color scheme 4180 * @return this color scheme to enable method chaining for a more fluent API */ 4181 public ColorScheme options(final IStyle... styles) { return addAll(optionStyles, styles);} 4182 /** Adds the specified styles to the registered styles for positional parameters in this color scheme and returns this color scheme. 4183 * @param styles the styles to add to registered the styles for parameters in this color scheme 4184 * @return this color scheme to enable method chaining for a more fluent API */ 4185 public ColorScheme parameters(final IStyle... styles) { return addAll(parameterStyles, styles);} 4186 /** Adds the specified styles to the registered styles for option parameters in this color scheme and returns this color scheme. 4187 * @param styles the styles to add to the registered styles for option parameters in this color scheme 4188 * @return this color scheme to enable method chaining for a more fluent API */ 4189 public ColorScheme optionParams(final IStyle... styles) { return addAll(optionParamStyles, styles);} 4190 /** Returns a Text with all command styles applied to the specified command string. 4191 * @param command the command string to apply the registered command styles to 4192 * @return a Text with all command styles applied to the specified command string */ 4193 public Ansi.Text commandText(final String command) { return ansi().apply(command, commandStyles); } 4194 /** Returns a Text with all option styles applied to the specified option string. 4195 * @param option the option string to apply the registered option styles to 4196 * @return a Text with all option styles applied to the specified option string */ 4197 public Ansi.Text optionText(final String option) { return ansi().apply(option, optionStyles); } 4198 /** Returns a Text with all parameter styles applied to the specified parameter string. 4199 * @param parameter the parameter string to apply the registered parameter styles to 4200 * @return a Text with all parameter styles applied to the specified parameter string */ 4201 public Ansi.Text parameterText(final String parameter) { return ansi().apply(parameter, parameterStyles); } 4202 /** Returns a Text with all optionParam styles applied to the specified optionParam string. 4203 * @param optionParam the option parameter string to apply the registered option parameter styles to 4204 * @return a Text with all option parameter styles applied to the specified option parameter string */ 4205 public Ansi.Text optionParamText(final String optionParam) { return ansi().apply(optionParam, optionParamStyles); } 4206 4207 /** Replaces colors and styles in this scheme with ones specified in system properties, and returns this scheme. 4208 * Supported property names:<ul> 4209 * <li>{@code picocli.color.commands}</li> 4210 * <li>{@code picocli.color.options}</li> 4211 * <li>{@code picocli.color.parameters}</li> 4212 * <li>{@code picocli.color.optionParams}</li> 4213 * </ul><p>Property values can be anything that {@link Help.Ansi.Style#parse(String)} can handle.</p> 4214 * @return this ColorScheme 4215 */ 4216 public ColorScheme applySystemProperties() { 4217 replace(commandStyles, System.getProperty("picocli.color.commands")); 4218 replace(optionStyles, System.getProperty("picocli.color.options")); 4219 replace(parameterStyles, System.getProperty("picocli.color.parameters")); 4220 replace(optionParamStyles, System.getProperty("picocli.color.optionParams")); 4221 return this; 4222 } 4223 private void replace(final List<IStyle> styles, final String property) { 4224 if (property != null) { 4225 styles.clear(); 4226 addAll(styles, Style.parse(property)); 4227 } 4228 } 4229 private ColorScheme addAll(final List<IStyle> styles, final IStyle... add) { 4230 styles.addAll(Arrays.asList(add)); 4231 return this; 4232 } 4233 4234 public Ansi ansi() { 4235 return ansi; 4236 } 4237 } 4238 4239 /** Creates and returns a new {@link ColorScheme} initialized with picocli default values: commands are bold, 4240 * options and parameters use a yellow foreground, and option parameters use italic. 4241 * @param ansi whether the usage help message should contain ANSI escape codes or not 4242 * @return a new default color scheme 4243 */ 4244 public static ColorScheme defaultColorScheme(final Ansi ansi) { 4245 return new ColorScheme(ansi) 4246 .commands(Style.bold) 4247 .options(Style.fg_yellow) 4248 .parameters(Style.fg_yellow) 4249 .optionParams(Style.italic); 4250 } 4251 4252 /** Provides methods and inner classes to support using ANSI escape codes in usage help messages. */ 4253 public enum Ansi { 4254 /** Only emit ANSI escape codes if the platform supports it and system property {@code "picocli.ansi"} 4255 * is not set to any value other than {@code "true"} (case insensitive). */ 4256 AUTO, 4257 /** Forced ON: always emit ANSI escape code regardless of the platform. */ 4258 ON, 4259 /** Forced OFF: never emit ANSI escape code regardless of the platform. */ 4260 OFF; 4261 static Text EMPTY_TEXT = OFF.new Text(0); 4262 static final boolean isWindows = System.getProperty("os.name").startsWith("Windows"); 4263 static final boolean isXterm = System.getenv("TERM") != null && System.getenv("TERM").startsWith("xterm"); 4264 static final boolean ISATTY = calcTTY(); 4265 4266 // http://stackoverflow.com/questions/1403772/how-can-i-check-if-a-java-programs-input-output-streams-are-connected-to-a-term 4267 static final boolean calcTTY() { 4268 if (isWindows && isXterm) { return true; } // Cygwin uses pseudo-tty and console is always null... 4269 try { return System.class.getDeclaredMethod("console").invoke(null) != null; } 4270 catch (final Throwable reflectionFailed) { return true; } 4271 } 4272 private static boolean ansiPossible() { return ISATTY && (!isWindows || isXterm); } 4273 4274 /** Returns {@code true} if ANSI escape codes should be emitted, {@code false} otherwise. 4275 * @return ON: {@code true}, OFF: {@code false}, AUTO: if system property {@code "picocli.ansi"} is 4276 * defined then return its boolean value, otherwise return whether the platform supports ANSI escape codes */ 4277 public boolean enabled() { 4278 if (this == ON) { return true; } 4279 if (this == OFF) { return false; } 4280 return (System.getProperty("picocli.ansi") == null ? ansiPossible() : Boolean.getBoolean("picocli.ansi")); 4281 } 4282 4283 /** Defines the interface for an ANSI escape sequence. */ 4284 public interface IStyle { 4285 4286 /** The Control Sequence Introducer (CSI) escape sequence {@value}. */ 4287 String CSI = "\u001B["; 4288 4289 /** Returns the ANSI escape code for turning this style on. 4290 * @return the ANSI escape code for turning this style on */ 4291 String on(); 4292 4293 /** Returns the ANSI escape code for turning this style off. 4294 * @return the ANSI escape code for turning this style off */ 4295 String off(); 4296 } 4297 4298 /** 4299 * A set of pre-defined ANSI escape code styles and colors, and a set of convenience methods for parsing 4300 * text with embedded markup style names, as well as convenience methods for converting 4301 * styles to strings with embedded escape codes. 4302 */ 4303 public enum Style implements IStyle { 4304 reset(0, 0), bold(1, 21), faint(2, 22), italic(3, 23), underline(4, 24), blink(5, 25), reverse(7, 27), 4305 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), 4306 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), 4307 ; 4308 private final int startCode; 4309 private final int endCode; 4310 4311 Style(final int startCode, final int endCode) {this.startCode = startCode; this.endCode = endCode; } 4312 @Override 4313 public String on() { return CSI + startCode + "m"; } 4314 @Override 4315 public String off() { return CSI + endCode + "m"; } 4316 4317 /** Returns the concatenated ANSI escape codes for turning all specified styles on. 4318 * @param styles the styles to generate ANSI escape codes for 4319 * @return the concatenated ANSI escape codes for turning all specified styles on */ 4320 public static String on(final IStyle... styles) { 4321 final StringBuilder result = new StringBuilder(); 4322 for (final IStyle style : styles) { 4323 result.append(style.on()); 4324 } 4325 return result.toString(); 4326 } 4327 /** Returns the concatenated ANSI escape codes for turning all specified styles off. 4328 * @param styles the styles to generate ANSI escape codes for 4329 * @return the concatenated ANSI escape codes for turning all specified styles off */ 4330 public static String off(final IStyle... styles) { 4331 final StringBuilder result = new StringBuilder(); 4332 for (final IStyle style : styles) { 4333 result.append(style.off()); 4334 } 4335 return result.toString(); 4336 } 4337 /** Parses the specified style markup and returns the associated style. 4338 * The markup may be one of the Style enum value names, or it may be one of the Style enum value 4339 * names when {@code "fg_"} is prepended, or it may be one of the indexed colors in the 256 color palette. 4340 * @param str the case-insensitive style markup to convert, e.g. {@code "blue"} or {@code "fg_blue"}, 4341 * or {@code "46"} (indexed color) or {@code "0;5;0"} (RGB components of an indexed color) 4342 * @return the IStyle for the specified converter 4343 */ 4344 public static IStyle fg(final String str) { 4345 try { return Style.valueOf(str.toLowerCase(ENGLISH)); } catch (final Exception ignored) {} 4346 try { return Style.valueOf("fg_" + str.toLowerCase(ENGLISH)); } catch (final Exception ignored) {} 4347 return new Palette256Color(true, str); 4348 } 4349 /** Parses the specified style markup and returns the associated style. 4350 * The markup may be one of the Style enum value names, or it may be one of the Style enum value 4351 * names when {@code "bg_"} is prepended, or it may be one of the indexed colors in the 256 color palette. 4352 * @param str the case-insensitive style markup to convert, e.g. {@code "blue"} or {@code "bg_blue"}, 4353 * or {@code "46"} (indexed color) or {@code "0;5;0"} (RGB components of an indexed color) 4354 * @return the IStyle for the specified converter 4355 */ 4356 public static IStyle bg(final String str) { 4357 try { return Style.valueOf(str.toLowerCase(ENGLISH)); } catch (final Exception ignored) {} 4358 try { return Style.valueOf("bg_" + str.toLowerCase(ENGLISH)); } catch (final Exception ignored) {} 4359 return new Palette256Color(false, str); 4360 } 4361 /** Parses the specified comma-separated sequence of style descriptors and returns the associated 4362 * styles. For each markup, strings starting with {@code "bg("} are delegated to 4363 * {@link #bg(String)}, others are delegated to {@link #bg(String)}. 4364 * @param commaSeparatedCodes one or more descriptors, e.g. {@code "bg(blue),underline,red"} 4365 * @return an array with all styles for the specified descriptors 4366 */ 4367 public static IStyle[] parse(final String commaSeparatedCodes) { 4368 final String[] codes = commaSeparatedCodes.split(","); 4369 final IStyle[] styles = new IStyle[codes.length]; 4370 for(int i = 0; i < codes.length; ++i) { 4371 if (codes[i].toLowerCase(ENGLISH).startsWith("fg(")) { 4372 final int end = codes[i].indexOf(')'); 4373 styles[i] = Style.fg(codes[i].substring(3, end < 0 ? codes[i].length() : end)); 4374 } else if (codes[i].toLowerCase(ENGLISH).startsWith("bg(")) { 4375 final int end = codes[i].indexOf(')'); 4376 styles[i] = Style.bg(codes[i].substring(3, end < 0 ? codes[i].length() : end)); 4377 } else { 4378 styles[i] = Style.fg(codes[i]); 4379 } 4380 } 4381 return styles; 4382 } 4383 } 4384 4385 /** Defines a palette map of 216 colors: 6 * 6 * 6 cube (216 colors): 4386 * 16 + 36 * r + 6 * g + b (0 <= r, g, b <= 5). */ 4387 static class Palette256Color implements IStyle { 4388 private final int fgbg; 4389 private final int color; 4390 4391 Palette256Color(final boolean foreground, final String color) { 4392 this.fgbg = foreground ? 38 : 48; 4393 final String[] rgb = color.split(";"); 4394 if (rgb.length == 3) { 4395 this.color = 16 + 36 * Integer.decode(rgb[0]) + 6 * Integer.decode(rgb[1]) + Integer.decode(rgb[2]); 4396 } else { 4397 this.color = Integer.decode(color); 4398 } 4399 } 4400 @Override 4401 public String on() { return String.format(CSI + "%d;5;%dm", fgbg, color); } 4402 @Override 4403 public String off() { return CSI + (fgbg + 1) + "m"; } 4404 } 4405 private static class StyledSection { 4406 int startIndex, length; 4407 String startStyles, endStyles; 4408 StyledSection(final int start, final int len, final String style1, final String style2) { 4409 startIndex = start; length = len; startStyles = style1; endStyles = style2; 4410 } 4411 StyledSection withStartIndex(final int newStart) { 4412 return new StyledSection(newStart, length, startStyles, endStyles); 4413 } 4414 } 4415 4416 /** 4417 * Returns a new Text object where all the specified styles are applied to the full length of the 4418 * specified plain text. 4419 * @param plainText the string to apply all styles to. Must not contain markup! 4420 * @param styles the styles to apply to the full plain text 4421 * @return a new Text object 4422 */ 4423 public Text apply(final String plainText, final List<IStyle> styles) { 4424 if (plainText.length() == 0) { return new Text(0); } 4425 final Text result = new Text(plainText.length()); 4426 final IStyle[] all = styles.toArray(new IStyle[styles.size()]); 4427 result.sections.add(new StyledSection( 4428 0, plainText.length(), Style.on(all), Style.off(reverse(all)) + Style.reset.off())); 4429 result.plain.append(plainText); 4430 result.length = result.plain.length(); 4431 return result; 4432 } 4433 4434 private static <T> T[] reverse(final T[] all) { 4435 for (int i = 0; i < all.length / 2; i++) { 4436 final T temp = all[i]; 4437 all[i] = all[all.length - i - 1]; 4438 all[all.length - i - 1] = temp; 4439 } 4440 return all; 4441 } 4442 /** Encapsulates rich text with styles and colors. Text objects may be constructed with Strings containing 4443 * markup like {@code @|bg(red),white,underline some text|@}, and this class converts the markup to ANSI 4444 * escape codes. 4445 * <p> 4446 * Internally keeps both an enriched and a plain text representation to allow layout components to calculate 4447 * text width while remaining unaware of the embedded ANSI escape codes.</p> */ 4448 public class Text implements Cloneable { 4449 private final int maxLength; 4450 private int from; 4451 private int length; 4452 private StringBuilder plain = new StringBuilder(); 4453 private List<StyledSection> sections = new ArrayList<StyledSection>(); 4454 4455 /** Constructs a Text with the specified max length (for use in a TextTable Column). 4456 * @param maxLength max length of this text */ 4457 public Text(final int maxLength) { this.maxLength = maxLength; } 4458 4459 /** 4460 * Constructs a Text with the specified String, which may contain markup like 4461 * {@code @|bg(red),white,underline some text|@}. 4462 * @param input the string with markup to parse 4463 */ 4464 public Text(final String input) { 4465 maxLength = -1; 4466 plain.setLength(0); 4467 int i = 0; 4468 4469 while (true) { 4470 int j = input.indexOf("@|", i); 4471 if (j == -1) { 4472 if (i == 0) { 4473 plain.append(input); 4474 length = plain.length(); 4475 return; 4476 } 4477 plain.append(input.substring(i, input.length())); 4478 length = plain.length(); 4479 return; 4480 } 4481 plain.append(input.substring(i, j)); 4482 final int k = input.indexOf("|@", j); 4483 if (k == -1) { 4484 plain.append(input); 4485 length = plain.length(); 4486 return; 4487 } 4488 4489 j += 2; 4490 final String spec = input.substring(j, k); 4491 final String[] items = spec.split(" ", 2); 4492 if (items.length == 1) { 4493 plain.append(input); 4494 length = plain.length(); 4495 return; 4496 } 4497 4498 final IStyle[] styles = Style.parse(items[0]); 4499 addStyledSection(plain.length(), items[1].length(), 4500 Style.on(styles), Style.off(reverse(styles)) + Style.reset.off()); 4501 plain.append(items[1]); 4502 i = k + 2; 4503 } 4504 } 4505 private void addStyledSection(final int start, final int length, final String startStyle, final String endStyle) { 4506 sections.add(new StyledSection(start, length, startStyle, endStyle)); 4507 } 4508 @Override 4509 public Object clone() { 4510 try { return super.clone(); } catch (final CloneNotSupportedException e) { throw new IllegalStateException(e); } 4511 } 4512 4513 public Text[] splitLines() { 4514 final List<Text> result = new ArrayList<Text>(); 4515 boolean trailingEmptyString = false; 4516 int start = 0, end = 0; 4517 for (int i = 0; i < plain.length(); i++, end = i) { 4518 final char c = plain.charAt(i); 4519 boolean eol = c == '\n'; 4520 eol |= (c == '\r' && i + 1 < plain.length() && plain.charAt(i + 1) == '\n' && ++i > 0); // \r\n 4521 eol |= c == '\r'; 4522 if (eol) { 4523 result.add(this.substring(start, end)); 4524 trailingEmptyString = i == plain.length() - 1; 4525 start = i + 1; 4526 } 4527 } 4528 if (start < plain.length() || trailingEmptyString) { 4529 result.add(this.substring(start, plain.length())); 4530 } 4531 return result.toArray(new Text[result.size()]); 4532 } 4533 4534 /** Returns a new {@code Text} instance that is a substring of this Text. Does not modify this instance! 4535 * @param start index in the plain text where to start the substring 4536 * @return a new Text instance that is a substring of this Text */ 4537 public Text substring(final int start) { 4538 return substring(start, length); 4539 } 4540 4541 /** Returns a new {@code Text} instance that is a substring of this Text. Does not modify this instance! 4542 * @param start index in the plain text where to start the substring 4543 * @param end index in the plain text where to end the substring 4544 * @return a new Text instance that is a substring of this Text */ 4545 public Text substring(final int start, final int end) { 4546 final Text result = (Text) clone(); 4547 result.from = from + start; 4548 result.length = end - start; 4549 return result; 4550 } 4551 /** Returns a new {@code Text} instance with the specified text appended. Does not modify this instance! 4552 * @param string the text to append 4553 * @return a new Text instance */ 4554 public Text append(final String string) { 4555 return append(new Text(string)); 4556 } 4557 4558 /** Returns a new {@code Text} instance with the specified text appended. Does not modify this instance! 4559 * @param other the text to append 4560 * @return a new Text instance */ 4561 public Text append(final Text other) { 4562 final Text result = (Text) clone(); 4563 result.plain = new StringBuilder(plain.toString().substring(from, from + length)); 4564 result.from = 0; 4565 result.sections = new ArrayList<StyledSection>(); 4566 for (final StyledSection section : sections) { 4567 result.sections.add(section.withStartIndex(section.startIndex - from)); 4568 } 4569 result.plain.append(other.plain.toString().substring(other.from, other.from + other.length)); 4570 for (final StyledSection section : other.sections) { 4571 final int index = result.length + section.startIndex - other.from; 4572 result.sections.add(section.withStartIndex(index)); 4573 } 4574 result.length = result.plain.length(); 4575 return result; 4576 } 4577 4578 /** 4579 * Copies the specified substring of this Text into the specified destination, preserving the markup. 4580 * @param from start of the substring 4581 * @param length length of the substring 4582 * @param destination destination Text to modify 4583 * @param offset indentation (padding) 4584 */ 4585 public void getStyledChars(final int from, final int length, final Text destination, final int offset) { 4586 if (destination.length < offset) { 4587 for (int i = destination.length; i < offset; i++) { 4588 destination.plain.append(' '); 4589 } 4590 destination.length = offset; 4591 } 4592 for (final StyledSection section : sections) { 4593 destination.sections.add(section.withStartIndex(section.startIndex - from + destination.length)); 4594 } 4595 destination.plain.append(plain.toString().substring(from, from + length)); 4596 destination.length = destination.plain.length(); 4597 } 4598 /** Returns the plain text without any formatting. 4599 * @return the plain text without any formatting */ 4600 public String plainString() { return plain.toString().substring(from, from + length); } 4601 4602 @Override 4603 public boolean equals(final Object obj) { return toString().equals(String.valueOf(obj)); } 4604 @Override 4605 public int hashCode() { return toString().hashCode(); } 4606 4607 /** Returns a String representation of the text with ANSI escape codes embedded, unless ANSI is 4608 * {@linkplain Ansi#enabled()} not enabled}, in which case the plain text is returned. 4609 * @return a String representation of the text with ANSI escape codes embedded (if enabled) */ 4610 @Override 4611 public String toString() { 4612 if (!Ansi.this.enabled()) { 4613 return plain.toString().substring(from, from + length); 4614 } 4615 if (length == 0) { return ""; } 4616 final StringBuilder sb = new StringBuilder(plain.length() + 20 * sections.size()); 4617 StyledSection current = null; 4618 final int end = Math.min(from + length, plain.length()); 4619 for (int i = from; i < end; i++) { 4620 final StyledSection section = findSectionContaining(i); 4621 if (section != current) { 4622 if (current != null) { sb.append(current.endStyles); } 4623 if (section != null) { sb.append(section.startStyles); } 4624 current = section; 4625 } 4626 sb.append(plain.charAt(i)); 4627 } 4628 if (current != null) { sb.append(current.endStyles); } 4629 return sb.toString(); 4630 } 4631 4632 private StyledSection findSectionContaining(final int index) { 4633 for (final StyledSection section : sections) { 4634 if (index >= section.startIndex && index < section.startIndex + section.length) { 4635 return section; 4636 } 4637 } 4638 return null; 4639 } 4640 } 4641 } 4642 } 4643 4644 /** 4645 * Utility class providing some defensive coding convenience methods. 4646 */ 4647 private static final class Assert { 4648 /** 4649 * Throws a NullPointerException if the specified object is null. 4650 * @param object the object to verify 4651 * @param description error message 4652 * @param <T> type of the object to check 4653 * @return the verified object 4654 */ 4655 static <T> T notNull(final T object, final String description) { 4656 if (object == null) { 4657 throw new NullPointerException(description); 4658 } 4659 return object; 4660 } 4661 private Assert() {} // private constructor: never instantiate 4662 } 4663 private enum TraceLevel { OFF, WARN, INFO, DEBUG; 4664 public boolean isEnabled(final TraceLevel other) { return ordinal() >= other.ordinal(); } 4665 private void print(final Tracer tracer, final String msg, final Object... params) { 4666 if (tracer.level.isEnabled(this)) { tracer.stream.printf(prefix(msg), params); } 4667 } 4668 private String prefix(final String msg) { return "[picocli " + this + "] " + msg; } 4669 static TraceLevel lookup(final String key) { return key == null ? WARN : empty(key) || "true".equalsIgnoreCase(key) ? INFO : valueOf(key); } 4670 } 4671 private static class Tracer { 4672 TraceLevel level = TraceLevel.lookup(System.getProperty("picocli.trace")); 4673 PrintStream stream = System.err; 4674 void warn (final String msg, final Object... params) { TraceLevel.WARN.print(this, msg, params); } 4675 void info (final String msg, final Object... params) { TraceLevel.INFO.print(this, msg, params); } 4676 void debug(final String msg, final Object... params) { TraceLevel.DEBUG.print(this, msg, params); } 4677 boolean isWarn() { return level.isEnabled(TraceLevel.WARN); } 4678 boolean isInfo() { return level.isEnabled(TraceLevel.INFO); } 4679 boolean isDebug() { return level.isEnabled(TraceLevel.DEBUG); } 4680 } 4681 /** Base class of all exceptions thrown by {@code picocli.CommandLine}. 4682 * @since 2.0 */ 4683 public static class PicocliException extends RuntimeException { 4684 private static final long serialVersionUID = -2574128880125050818L; 4685 public PicocliException(final String msg) { super(msg); } 4686 public PicocliException(final String msg, final Exception ex) { super(msg, ex); } 4687 } 4688 /** Exception indicating a problem during {@code CommandLine} initialization. 4689 * @since 2.0 */ 4690 public static class InitializationException extends PicocliException { 4691 private static final long serialVersionUID = 8423014001666638895L; 4692 public InitializationException(final String msg) { super(msg); } 4693 public InitializationException(final String msg, final Exception ex) { super(msg, ex); } 4694 } 4695 /** Exception indicating a problem while invoking a command or subcommand. 4696 * @since 2.0 */ 4697 public static class ExecutionException extends PicocliException { 4698 private static final long serialVersionUID = 7764539594267007998L; 4699 private final CommandLine commandLine; 4700 public ExecutionException(final CommandLine commandLine, final String msg) { 4701 super(msg); 4702 this.commandLine = Assert.notNull(commandLine, "commandLine"); 4703 } 4704 public ExecutionException(final CommandLine commandLine, final String msg, final Exception ex) { 4705 super(msg, ex); 4706 this.commandLine = Assert.notNull(commandLine, "commandLine"); 4707 } 4708 /** Returns the {@code CommandLine} object for the (sub)command that could not be invoked. 4709 * @return the {@code CommandLine} object for the (sub)command where invocation failed. 4710 */ 4711 public CommandLine getCommandLine() { return commandLine; } 4712 } 4713 4714 /** Exception thrown by {@link ITypeConverter} implementations to indicate a String could not be converted. */ 4715 public static class TypeConversionException extends PicocliException { 4716 private static final long serialVersionUID = 4251973913816346114L; 4717 public TypeConversionException(final String msg) { super(msg); } 4718 } 4719 /** Exception indicating something went wrong while parsing command line options. */ 4720 public static class ParameterException extends PicocliException { 4721 private static final long serialVersionUID = 1477112829129763139L; 4722 private final CommandLine commandLine; 4723 4724 /** Constructs a new ParameterException with the specified CommandLine and error message. 4725 * @param commandLine the command or subcommand whose input was invalid 4726 * @param msg describes the problem 4727 * @since 2.0 */ 4728 public ParameterException(final CommandLine commandLine, final String msg) { 4729 super(msg); 4730 this.commandLine = Assert.notNull(commandLine, "commandLine"); 4731 } 4732 /** Constructs a new ParameterException with the specified CommandLine and error message. 4733 * @param commandLine the command or subcommand whose input was invalid 4734 * @param msg describes the problem 4735 * @param ex the exception that caused this ParameterException 4736 * @since 2.0 */ 4737 public ParameterException(final CommandLine commandLine, final String msg, final Exception ex) { 4738 super(msg, ex); 4739 this.commandLine = Assert.notNull(commandLine, "commandLine"); 4740 } 4741 4742 /** Returns the {@code CommandLine} object for the (sub)command whose input could not be parsed. 4743 * @return the {@code CommandLine} object for the (sub)command where parsing failed. 4744 * @since 2.0 4745 */ 4746 public CommandLine getCommandLine() { return commandLine; } 4747 4748 private static ParameterException create(final CommandLine cmd, final Exception ex, final String arg, final int i, final String[] args) { 4749 final String msg = ex.getClass().getSimpleName() + ": " + ex.getLocalizedMessage() 4750 + " while processing argument at or before arg[" + i + "] '" + arg + "' in " + Arrays.toString(args) + ": " + ex.toString(); 4751 return new ParameterException(cmd, msg, ex); 4752 } 4753 } 4754 /** 4755 * Exception indicating that a required parameter was not specified. 4756 */ 4757 public static class MissingParameterException extends ParameterException { 4758 private static final long serialVersionUID = 5075678535706338753L; 4759 public MissingParameterException(final CommandLine commandLine, final String msg) { 4760 super(commandLine, msg); 4761 } 4762 4763 private static MissingParameterException create(final CommandLine cmd, final Collection<Field> missing, final String separator) { 4764 if (missing.size() == 1) { 4765 return new MissingParameterException(cmd, "Missing required option '" 4766 + describe(missing.iterator().next(), separator) + "'"); 4767 } 4768 final List<String> names = new ArrayList<String>(missing.size()); 4769 for (final Field field : missing) { 4770 names.add(describe(field, separator)); 4771 } 4772 return new MissingParameterException(cmd, "Missing required options " + names.toString()); 4773 } 4774 private static String describe(final Field field, final String separator) { 4775 final String prefix = (field.isAnnotationPresent(Option.class)) 4776 ? field.getAnnotation(Option.class).names()[0] + separator 4777 : "params[" + field.getAnnotation(Parameters.class).index() + "]" + separator; 4778 return prefix + Help.DefaultParamLabelRenderer.renderParameterName(field); 4779 } 4780 } 4781 4782 /** 4783 * Exception indicating that multiple fields have been annotated with the same Option name. 4784 */ 4785 public static class DuplicateOptionAnnotationsException extends InitializationException { 4786 private static final long serialVersionUID = -3355128012575075641L; 4787 public DuplicateOptionAnnotationsException(final String msg) { super(msg); } 4788 4789 private static DuplicateOptionAnnotationsException create(final String name, final Field field1, final Field field2) { 4790 return new DuplicateOptionAnnotationsException("Option name '" + name + "' is used by both " + 4791 field1.getDeclaringClass().getName() + "." + field1.getName() + " and " + 4792 field2.getDeclaringClass().getName() + "." + field2.getName()); 4793 } 4794 } 4795 /** Exception indicating that there was a gap in the indices of the fields annotated with {@link Parameters}. */ 4796 public static class ParameterIndexGapException extends InitializationException { 4797 private static final long serialVersionUID = -1520981133257618319L; 4798 public ParameterIndexGapException(final String msg) { super(msg); } 4799 } 4800 /** Exception indicating that a command line argument could not be mapped to any of the fields annotated with 4801 * {@link Option} or {@link Parameters}. */ 4802 public static class UnmatchedArgumentException extends ParameterException { 4803 private static final long serialVersionUID = -8700426380701452440L; 4804 public UnmatchedArgumentException(final CommandLine commandLine, final String msg) { super(commandLine, msg); } 4805 public UnmatchedArgumentException(final CommandLine commandLine, final Stack<String> args) { this(commandLine, new ArrayList<String>(reverse(args))); } 4806 public UnmatchedArgumentException(final CommandLine commandLine, final List<String> args) { this(commandLine, "Unmatched argument" + (args.size() == 1 ? " " : "s ") + args); } 4807 } 4808 /** Exception indicating that more values were specified for an option or parameter than its {@link Option#arity() arity} allows. */ 4809 public static class MaxValuesforFieldExceededException extends ParameterException { 4810 private static final long serialVersionUID = 6536145439570100641L; 4811 public MaxValuesforFieldExceededException(final CommandLine commandLine, final String msg) { super(commandLine, msg); } 4812 } 4813 /** Exception indicating that an option for a single-value option field has been specified multiple times on the command line. */ 4814 public static class OverwrittenOptionException extends ParameterException { 4815 private static final long serialVersionUID = 1338029208271055776L; 4816 public OverwrittenOptionException(final CommandLine commandLine, final String msg) { super(commandLine, msg); } 4817 } 4818 /** 4819 * Exception indicating that an annotated field had a type for which no {@link ITypeConverter} was 4820 * {@linkplain #registerConverter(Class, ITypeConverter) registered}. 4821 */ 4822 public static class MissingTypeConverterException extends ParameterException { 4823 private static final long serialVersionUID = -6050931703233083760L; 4824 public MissingTypeConverterException(final CommandLine commandLine, final String msg) { super(commandLine, msg); } 4825 } 4826}