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 */ 017 018package org.apache.commons.cli; 019 020import java.io.BufferedReader; 021import java.io.IOException; 022import java.io.PrintWriter; 023import java.io.Serializable; 024import java.io.StringReader; 025import java.util.ArrayList; 026import java.util.Arrays; 027import java.util.Collection; 028import java.util.Collections; 029import java.util.Comparator; 030import java.util.Iterator; 031import java.util.List; 032import java.util.Objects; 033import java.util.function.Supplier; 034 035/** 036 * A formatter of help messages for command line options. 037 * <p> 038 * Example: 039 * </p> 040 * <pre> 041 * Options options = new Options(); 042 * options.addOption(OptionBuilder.withLongOpt("file").withDescription("The file to be processed").hasArg().withArgName("FILE").isRequired().create('f')); 043 * options.addOption(OptionBuilder.withLongOpt("version").withDescription("Print the version of the application").create('v')); 044 * options.addOption(OptionBuilder.withLongOpt("help").create('h')); 045 * 046 * String header = "Do something useful with an input file\n\n"; 047 * String footer = "\nPlease report issues at https://example.com/issues"; 048 * 049 * HelpFormatter formatter = new HelpFormatter(); 050 * formatter.printHelp("myapp", header, options, footer, true); 051 * </pre> 052 * <p> 053 * This produces the following output: 054 * </p> 055 * <pre> 056 * usage: myapp -f <FILE> [-h] [-v] 057 * Do something useful with an input file 058 * 059 * -f,--file <FILE> The file to be processed 060 * -h,--help 061 * -v,--version Print the version of the application 062 * 063 * Please report issues at https://example.com/issues 064 * </pre> 065 */ 066public class HelpFormatter { 067 068 /** 069 * Builds {@link HelpFormatter}. 070 * 071 * @since 1.7.0 072 */ 073 public static final class Builder implements Supplier<HelpFormatter> { 074 // TODO All other instance HelpFormatter instance variables. 075 // Make HelpFormatter immutable for 2.0 076 077 /** 078 * Whether to show deprecated options. 079 */ 080 private boolean showDeprecated; 081 082 /** 083 * The output PrintWriter, defaults to wrapping {@link System#out}. 084 */ 085 private PrintWriter printStream = createDefaultPrintWriter(); 086 087 @Override 088 public HelpFormatter get() { 089 return new HelpFormatter(showDeprecated, printStream); 090 } 091 092 /** 093 * Sets the output PrintWriter, defaults to wrapping {@link System#out}. 094 * 095 * @param printWriter the output PrintWriter, not null. 096 * @return this. 097 */ 098 public Builder setPrintWriter(final PrintWriter printWriter) { 099 this.printStream = Objects.requireNonNull(printWriter, "printWriter"); 100 return this; 101 } 102 103 /** 104 * Sets whether to show deprecated options. 105 * 106 * @param showDeprecated Whether to show deprecated options. 107 * @return this. 108 */ 109 public Builder setShowDeprecated(final boolean showDeprecated) { 110 this.showDeprecated = showDeprecated; 111 return this; 112 } 113 114 } 115 116 /** 117 * This class implements the {@code Comparator} interface for comparing Options. 118 */ 119 private static final class OptionComparator implements Comparator<Option>, Serializable { 120 121 /** The serial version UID. */ 122 private static final long serialVersionUID = 5305467873966684014L; 123 124 /** 125 * Compares its two arguments for order. Returns a negative integer, zero, or a positive integer as the first argument 126 * is less than, equal to, or greater than the second. 127 * 128 * @param opt1 The first Option to be compared. 129 * @param opt2 The second Option to be compared. 130 * @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than 131 * the second. 132 */ 133 @Override 134 public int compare(final Option opt1, final Option opt2) { 135 return opt1.getKey().compareToIgnoreCase(opt2.getKey()); 136 } 137 } 138 139 /** Default number of characters per line */ 140 public static final int DEFAULT_WIDTH = 74; 141 142 /** Default padding to the left of each line */ 143 public static final int DEFAULT_LEFT_PAD = 1; 144 145 /** Number of space characters to be prefixed to each description line */ 146 public static final int DEFAULT_DESC_PAD = 3; 147 148 /** The string to display at the beginning of the usage statement */ 149 public static final String DEFAULT_SYNTAX_PREFIX = "usage: "; 150 151 /** Default prefix for shortOpts */ 152 public static final String DEFAULT_OPT_PREFIX = "-"; 153 154 /** Default prefix for long Option */ 155 public static final String DEFAULT_LONG_OPT_PREFIX = "--"; 156 157 /** 158 * Default separator displayed between a long Option and its value 159 * 160 * @since 1.3 161 */ 162 public static final String DEFAULT_LONG_OPT_SEPARATOR = " "; 163 164 /** Default name for an argument */ 165 public static final String DEFAULT_ARG_NAME = "arg"; 166 167 /** 168 * Creates a new builder. 169 * 170 * @return a new builder. 171 * @since 1.7.0 172 */ 173 public static Builder builder() { 174 return new Builder(); 175 } 176 177 private static PrintWriter createDefaultPrintWriter() { 178 return new PrintWriter(System.out); 179 } 180 181 /** 182 * Number of characters per line 183 * 184 * @deprecated Scope will be made private for next major version - use get/setWidth methods instead. 185 */ 186 @Deprecated 187 public int defaultWidth = DEFAULT_WIDTH; 188 189 /** 190 * Amount of padding to the left of each line 191 * 192 * @deprecated Scope will be made private for next major version - use get/setLeftPadding methods instead. 193 */ 194 @Deprecated 195 public int defaultLeftPad = DEFAULT_LEFT_PAD; 196 197 /** 198 * The number of characters of padding to be prefixed to each description line 199 * 200 * @deprecated Scope will be made private for next major version - use get/setDescPadding methods instead. 201 */ 202 @Deprecated 203 public int defaultDescPad = DEFAULT_DESC_PAD; 204 205 /** 206 * The string to display at the beginning of the usage statement 207 * 208 * @deprecated Scope will be made private for next major version - use get/setSyntaxPrefix methods instead. 209 */ 210 @Deprecated 211 public String defaultSyntaxPrefix = DEFAULT_SYNTAX_PREFIX; 212 213 /** 214 * The new line string 215 * 216 * @deprecated Scope will be made private for next major version - use get/setNewLine methods instead. 217 */ 218 @Deprecated 219 public String defaultNewLine = System.lineSeparator(); 220 221 /** 222 * The shortOpt prefix 223 * 224 * @deprecated Scope will be made private for next major version - use get/setOptPrefix methods instead. 225 */ 226 @Deprecated 227 public String defaultOptPrefix = DEFAULT_OPT_PREFIX; 228 229 /** 230 * The long Opt prefix 231 * 232 * @deprecated Scope will be made private for next major version - use get/setLongOptPrefix methods instead. 233 */ 234 @Deprecated 235 public String defaultLongOptPrefix = DEFAULT_LONG_OPT_PREFIX; 236 237 /** 238 * The name of the argument 239 * 240 * @deprecated Scope will be made private for next major version - use get/setArgName methods instead. 241 */ 242 @Deprecated 243 public String defaultArgName = DEFAULT_ARG_NAME; 244 245 /** 246 * Comparator used to sort the options when they output in help text 247 * 248 * Defaults to case-insensitive alphabetical sorting by option key 249 */ 250 protected Comparator<Option> optionComparator = new OptionComparator(); 251 252 /** 253 * Whether to show deprecated options. 254 */ 255 private final boolean showDeprecated; 256 257 /** 258 * Where to print help. 259 */ 260 private final PrintWriter printWriter; 261 262 /** 263 * The separator displayed between the long option and its value. 264 */ 265 private String longOptSeparator = DEFAULT_LONG_OPT_SEPARATOR; 266 267 /** 268 * Constructs a new instance. 269 */ 270 public HelpFormatter() { 271 this(false, createDefaultPrintWriter()); 272 } 273 274 /** 275 * Constructs a new instance. 276 * @param printStream TODO 277 */ 278 private HelpFormatter(final boolean showDeprecated, final PrintWriter printStream) { 279 // TODO All other instance HelpFormatter instance variables. 280 // Make HelpFormatter immutable for 2.0 281 this.showDeprecated = showDeprecated; 282 this.printWriter = printStream; 283 } 284 285 /** 286 * Appends the usage clause for an Option to a StringBuffer. 287 * 288 * @param buff the StringBuffer to append to 289 * @param option the Option to append 290 * @param required whether the Option is required or not 291 */ 292 private void appendOption(final StringBuffer buff, final Option option, final boolean required) { 293 if (!required) { 294 buff.append("["); 295 } 296 if (option.getOpt() != null) { 297 buff.append("-").append(option.getOpt()); 298 } else { 299 buff.append("--").append(option.getLongOpt()); 300 } 301 // if the Option has a value and a non blank argname 302 if (option.hasArg() && (option.getArgName() == null || !option.getArgName().isEmpty())) { 303 buff.append(option.getOpt() == null ? longOptSeparator : " "); 304 buff.append("<").append(option.getArgName() != null ? option.getArgName() : getArgName()).append(">"); 305 } 306 // if the Option is not a required option 307 if (!required) { 308 buff.append("]"); 309 } 310 } 311 312 /** 313 * Appends the usage clause for an OptionGroup to a StringBuffer. The clause is wrapped in square brackets if the group 314 * is required. The display of the options is handled by appendOption 315 * 316 * @param buff the StringBuffer to append to 317 * @param group the group to append 318 * @see #appendOption(StringBuffer,Option,boolean) 319 */ 320 private void appendOptionGroup(final StringBuffer buff, final OptionGroup group) { 321 if (!group.isRequired()) { 322 buff.append("["); 323 } 324 325 final List<Option> optList = new ArrayList<>(group.getOptions()); 326 if (getOptionComparator() != null) { 327 Collections.sort(optList, getOptionComparator()); 328 } 329 // for each option in the OptionGroup 330 for (final Iterator<Option> it = optList.iterator(); it.hasNext();) { 331 // whether the option is required or not is handled at group level 332 appendOption(buff, it.next(), true); 333 334 if (it.hasNext()) { 335 buff.append(" | "); 336 } 337 } 338 339 if (!group.isRequired()) { 340 buff.append("]"); 341 } 342 } 343 344 /** 345 * Creates a String of padding of length {@code len}. 346 * 347 * @param len The length of the String of padding to create. 348 * 349 * @return The String of padding 350 */ 351 protected String createPadding(final int len) { 352 final char[] padding = new char[len]; 353 Arrays.fill(padding, ' '); 354 return new String(padding); 355 } 356 357 /** 358 * Finds the next text wrap position after {@code startPos} for the text in {@code text} with the column width 359 * {@code width}. The wrap point is the last position before startPos+width having a whitespace character (space, 360 * \n, \r). If there is no whitespace character before startPos+width, it will return startPos+width. 361 * 362 * @param text The text being searched for the wrap position 363 * @param width width of the wrapped text 364 * @param startPos position from which to start the lookup whitespace character 365 * @return position on which the text must be wrapped or -1 if the wrap position is at the end of the text 366 */ 367 protected int findWrapPos(final String text, final int width, final int startPos) { 368 // the line ends before the max wrap pos or a new line char found 369 int pos = text.indexOf(Char.LF, startPos); 370 if (pos != -1 && pos <= width) { 371 return pos + 1; 372 } 373 pos = text.indexOf(Char.TAB, startPos); 374 if (pos != -1 && pos <= width) { 375 return pos + 1; 376 } 377 if (startPos + width >= text.length()) { 378 return -1; 379 } 380 // look for the last whitespace character before startPos+width 381 for (pos = startPos + width; pos >= startPos; --pos) { 382 final char c = text.charAt(pos); 383 if (c == Char.SP || c == Char.LF || c == Char.CR) { 384 break; 385 } 386 } 387 // if we found it - just return 388 if (pos > startPos) { 389 return pos; 390 } 391 // if we didn't find one, simply chop at startPos+width 392 pos = startPos + width; 393 394 return pos == text.length() ? -1 : pos; 395 } 396 397 /** 398 * Gets the 'argName'. 399 * 400 * @return the 'argName' 401 */ 402 public String getArgName() { 403 return defaultArgName; 404 } 405 406 /** 407 * Gets the 'descPadding'. 408 * 409 * @return the 'descPadding' 410 */ 411 public int getDescPadding() { 412 return defaultDescPad; 413 } 414 415 /** 416 * Gets the 'leftPadding'. 417 * 418 * @return the 'leftPadding' 419 */ 420 public int getLeftPadding() { 421 return defaultLeftPad; 422 } 423 424 /** 425 * Gets the 'longOptPrefix'. 426 * 427 * @return the 'longOptPrefix' 428 */ 429 public String getLongOptPrefix() { 430 return defaultLongOptPrefix; 431 } 432 433 /** 434 * Gets the separator displayed between a long option and its value. 435 * 436 * @return the separator 437 * @since 1.3 438 */ 439 public String getLongOptSeparator() { 440 return longOptSeparator; 441 } 442 443 /** 444 * Gets the 'newLine'. 445 * 446 * @return the 'newLine' 447 */ 448 public String getNewLine() { 449 return defaultNewLine; 450 } 451 452 /** 453 * Comparator used to sort the options when they output in help text. Defaults to case-insensitive alphabetical sorting 454 * by option key. 455 * 456 * @return the {@link Comparator} currently in use to sort the options 457 * @since 1.2 458 */ 459 public Comparator<Option> getOptionComparator() { 460 return optionComparator; 461 } 462 463 /** 464 * Gets the 'optPrefix'. 465 * 466 * @return the 'optPrefix' 467 */ 468 public String getOptPrefix() { 469 return defaultOptPrefix; 470 } 471 472 /** 473 * Gets the 'syntaxPrefix'. 474 * 475 * @return the 'syntaxPrefix' 476 */ 477 public String getSyntaxPrefix() { 478 return defaultSyntaxPrefix; 479 } 480 481 /** 482 * Gets the 'width'. 483 * 484 * @return the 'width' 485 */ 486 public int getWidth() { 487 return defaultWidth; 488 } 489 490 /** 491 * Prints the help for {@code options} with the specified command line syntax. This method prints help information 492 * to {@link System#out} by default. 493 * 494 * @param width the number of characters to be displayed on each line 495 * @param cmdLineSyntax the syntax for this application 496 * @param header the banner to display at the beginning of the help 497 * @param options the Options instance 498 * @param footer the banner to display at the end of the help 499 */ 500 public void printHelp(final int width, final String cmdLineSyntax, final String header, final Options options, final String footer) { 501 printHelp(width, cmdLineSyntax, header, options, footer, false); 502 } 503 504 /** 505 * Prints the help for {@code options} with the specified command line syntax. This method prints help information 506 * to {@link System#out} by default. 507 * 508 * @param width the number of characters to be displayed on each line 509 * @param cmdLineSyntax the syntax for this application 510 * @param header the banner to display at the beginning of the help 511 * @param options the Options instance 512 * @param footer the banner to display at the end of the help 513 * @param autoUsage whether to print an automatically generated usage statement 514 */ 515 public void printHelp(final int width, final String cmdLineSyntax, final String header, final Options options, final String footer, 516 final boolean autoUsage) { 517 final PrintWriter pw = new PrintWriter(printWriter); 518 printHelp(pw, width, cmdLineSyntax, header, options, getLeftPadding(), getDescPadding(), footer, autoUsage); 519 pw.flush(); 520 } 521 522 /** 523 * Prints the help for {@code options} with the specified command line syntax. 524 * 525 * @param pw the writer to which the help will be written 526 * @param width the number of characters to be displayed on each line 527 * @param cmdLineSyntax the syntax for this application 528 * @param header the banner to display at the beginning of the help 529 * @param options the Options instance 530 * @param leftPad the number of characters of padding to be prefixed to each line 531 * @param descPad the number of characters of padding to be prefixed to each description line 532 * @param footer the banner to display at the end of the help 533 * 534 * @throws IllegalStateException if there is no room to print a line 535 */ 536 public void printHelp(final PrintWriter pw, final int width, final String cmdLineSyntax, final String header, final Options options, final int leftPad, 537 final int descPad, final String footer) { 538 printHelp(pw, width, cmdLineSyntax, header, options, leftPad, descPad, footer, false); 539 } 540 541 /** 542 * Prints the help for {@code options} with the specified command line syntax. 543 * 544 * @param pw the writer to which the help will be written 545 * @param width the number of characters to be displayed on each line 546 * @param cmdLineSyntax the syntax for this application 547 * @param header the banner to display at the beginning of the help 548 * @param options the Options instance 549 * @param leftPad the number of characters of padding to be prefixed to each line 550 * @param descPad the number of characters of padding to be prefixed to each description line 551 * @param footer the banner to display at the end of the help 552 * @param autoUsage whether to print an automatically generated usage statement 553 * @throws IllegalStateException if there is no room to print a line 554 */ 555 public void printHelp(final PrintWriter pw, final int width, final String cmdLineSyntax, final String header, final Options options, final int leftPad, 556 final int descPad, final String footer, final boolean autoUsage) { 557 if (cmdLineSyntax == null || cmdLineSyntax.isEmpty()) { 558 throw new IllegalArgumentException("cmdLineSyntax not provided"); 559 } 560 if (autoUsage) { 561 printUsage(pw, width, cmdLineSyntax, options); 562 } else { 563 printUsage(pw, width, cmdLineSyntax); 564 } 565 if (header != null && !header.isEmpty()) { 566 printWrapped(pw, width, header); 567 } 568 printOptions(pw, width, options, leftPad, descPad); 569 if (footer != null && !footer.isEmpty()) { 570 printWrapped(pw, width, footer); 571 } 572 } 573 574 /** 575 * Prints the help for {@code options} with the specified command line syntax. This method prints help information 576 * to {@link System#out} by default. 577 * 578 * @param cmdLineSyntax the syntax for this application 579 * @param options the Options instance 580 */ 581 public void printHelp(final String cmdLineSyntax, final Options options) { 582 printHelp(getWidth(), cmdLineSyntax, null, options, null, false); 583 } 584 585 /** 586 * Prints the help for {@code options} with the specified command line syntax. This method prints help information 587 * to {@link System#out} by default. 588 * 589 * @param cmdLineSyntax the syntax for this application 590 * @param options the Options instance 591 * @param autoUsage whether to print an automatically generated usage statement 592 */ 593 public void printHelp(final String cmdLineSyntax, final Options options, final boolean autoUsage) { 594 printHelp(getWidth(), cmdLineSyntax, null, options, null, autoUsage); 595 } 596 597 /** 598 * Prints the help for {@code options} with the specified command line syntax. This method prints help information 599 * to {@link System#out} by default. 600 * 601 * @param cmdLineSyntax the syntax for this application 602 * @param header the banner to display at the beginning of the help 603 * @param options the Options instance 604 * @param footer the banner to display at the end of the help 605 */ 606 public void printHelp(final String cmdLineSyntax, final String header, final Options options, final String footer) { 607 printHelp(cmdLineSyntax, header, options, footer, false); 608 } 609 610 /** 611 * Prints the help for {@code options} with the specified command line syntax. This method prints help information 612 * to {@link System#out} by default. 613 * 614 * @param cmdLineSyntax the syntax for this application 615 * @param header the banner to display at the beginning of the help 616 * @param options the Options instance 617 * @param footer the banner to display at the end of the help 618 * @param autoUsage whether to print an automatically generated usage statement 619 */ 620 public void printHelp(final String cmdLineSyntax, final String header, final Options options, final String footer, final boolean autoUsage) { 621 printHelp(getWidth(), cmdLineSyntax, header, options, footer, autoUsage); 622 } 623 624 /** 625 * Prints the help for the specified Options to the specified writer, using the specified width, left padding and 626 * description padding. 627 * 628 * @param pw The printWriter to write the help to 629 * @param width The number of characters to display per line 630 * @param options The command line Options 631 * @param leftPad the number of characters of padding to be prefixed to each line 632 * @param descPad the number of characters of padding to be prefixed to each description line 633 */ 634 public void printOptions(final PrintWriter pw, final int width, final Options options, final int leftPad, final int descPad) { 635 final StringBuffer sb = new StringBuffer(); 636 renderOptions(sb, width, options, leftPad, descPad); 637 pw.println(sb.toString()); 638 } 639 640 /** 641 * Prints the cmdLineSyntax to the specified writer, using the specified width. 642 * 643 * @param pw The printWriter to write the help to 644 * @param width The number of characters per line for the usage statement. 645 * @param cmdLineSyntax The usage statement. 646 */ 647 public void printUsage(final PrintWriter pw, final int width, final String cmdLineSyntax) { 648 final int argPos = cmdLineSyntax.indexOf(' ') + 1; 649 printWrapped(pw, width, getSyntaxPrefix().length() + argPos, getSyntaxPrefix() + cmdLineSyntax); 650 } 651 652 /** 653 * Prints the usage statement for the specified application. 654 * 655 * @param pw The PrintWriter to print the usage statement 656 * @param width The number of characters to display per line 657 * @param app The application name 658 * @param options The command line Options 659 */ 660 public void printUsage(final PrintWriter pw, final int width, final String app, final Options options) { 661 // initialize the string buffer 662 final StringBuffer buff = new StringBuffer(getSyntaxPrefix()).append(app).append(Char.SP); 663 // create a list for processed option groups 664 final Collection<OptionGroup> processedGroups = new ArrayList<>(); 665 final List<Option> optList = new ArrayList<>(options.getOptions()); 666 if (getOptionComparator() != null) { 667 Collections.sort(optList, getOptionComparator()); 668 } 669 // iterate over the options 670 for (final Iterator<Option> it = optList.iterator(); it.hasNext();) { 671 // get the next Option 672 final Option option = it.next(); 673 // check if the option is part of an OptionGroup 674 final OptionGroup group = options.getOptionGroup(option); 675 // if the option is part of a group 676 if (group != null) { 677 // and if the group has not already been processed 678 if (!processedGroups.contains(group)) { 679 // add the group to the processed list 680 processedGroups.add(group); 681 // add the usage clause 682 appendOptionGroup(buff, group); 683 } 684 // otherwise the option was displayed in the group 685 // previously so ignore it. 686 } 687 // if the Option is not part of an OptionGroup 688 else { 689 appendOption(buff, option, option.isRequired()); 690 } 691 if (it.hasNext()) { 692 buff.append(Char.SP); 693 } 694 } 695 696 // call printWrapped 697 printWrapped(pw, width, buff.toString().indexOf(' ') + 1, buff.toString()); 698 } 699 700 /** 701 * Prints the specified text to the specified PrintWriter. 702 * 703 * @param pw The printWriter to write the help to 704 * @param width The number of characters to display per line 705 * @param nextLineTabStop The position on the next line for the first tab. 706 * @param text The text to be written to the PrintWriter 707 */ 708 public void printWrapped(final PrintWriter pw, final int width, final int nextLineTabStop, final String text) { 709 final StringBuffer sb = new StringBuffer(text.length()); 710 renderWrappedTextBlock(sb, width, nextLineTabStop, text); 711 pw.println(sb.toString()); 712 } 713 714 /** 715 * Prints the specified text to the specified PrintWriter. 716 * 717 * @param pw The printWriter to write the help to 718 * @param width The number of characters to display per line 719 * @param text The text to be written to the PrintWriter 720 */ 721 public void printWrapped(final PrintWriter pw, final int width, final String text) { 722 printWrapped(pw, width, 0, text); 723 } 724 725 /** 726 * Renders the specified Options and return the rendered Options in a StringBuffer. 727 * 728 * @param sb The StringBuffer to place the rendered Options into. 729 * @param width The number of characters to display per line 730 * @param options The command line Options 731 * @param leftPad the number of characters of padding to be prefixed to each line 732 * @param descPad the number of characters of padding to be prefixed to each description line 733 * 734 * @return the StringBuffer with the rendered Options contents. 735 */ 736 protected StringBuffer renderOptions(final StringBuffer sb, final int width, final Options options, final int leftPad, final int descPad) { 737 final String lpad = createPadding(leftPad); 738 final String dpad = createPadding(descPad); 739 // first create list containing only <lpad>-a,--aaa where 740 // -a is opt and --aaa is long opt; in parallel look for 741 // the longest opt string this list will be then used to 742 // sort options ascending 743 int max = 0; 744 final List<StringBuffer> prefixList = new ArrayList<>(); 745 final List<Option> optList = options.helpOptions(); 746 if (getOptionComparator() != null) { 747 Collections.sort(optList, getOptionComparator()); 748 } 749 for (final Option option : optList) { 750 final StringBuffer optBuf = new StringBuffer(); 751 if (option.getOpt() == null) { 752 optBuf.append(lpad).append(" ").append(getLongOptPrefix()).append(option.getLongOpt()); 753 } else { 754 optBuf.append(lpad).append(getOptPrefix()).append(option.getOpt()); 755 if (option.hasLongOpt()) { 756 optBuf.append(',').append(getLongOptPrefix()).append(option.getLongOpt()); 757 } 758 } 759 if (option.hasArg()) { 760 final String argName = option.getArgName(); 761 if (argName != null && argName.isEmpty()) { 762 // if the option has a blank argname 763 optBuf.append(' '); 764 } else { 765 optBuf.append(option.hasLongOpt() ? longOptSeparator : " "); 766 optBuf.append("<").append(argName != null ? option.getArgName() : getArgName()).append(">"); 767 } 768 } 769 prefixList.add(optBuf); 770 max = Math.max(optBuf.length(), max); 771 } 772 int x = 0; 773 for (final Iterator<Option> it = optList.iterator(); it.hasNext();) { 774 final Option option = it.next(); 775 final StringBuilder optBuf = new StringBuilder(prefixList.get(x++).toString()); 776 if (optBuf.length() < max) { 777 optBuf.append(createPadding(max - optBuf.length())); 778 } 779 optBuf.append(dpad); 780 final int nextLineTabStop = max + descPad; 781 if (showDeprecated && option.isDeprecated()) { 782 optBuf.append("[Deprecated]"); 783 } 784 if (option.getDescription() != null) { 785 if (showDeprecated && option.isDeprecated()) { 786 optBuf.append(' '); 787 } 788 optBuf.append(option.getDescription()); 789 } 790 renderWrappedText(sb, width, nextLineTabStop, optBuf.toString()); 791 if (it.hasNext()) { 792 sb.append(getNewLine()); 793 } 794 } 795 return sb; 796 } 797 798 /** 799 * Renders the specified text and return the rendered Options in a StringBuffer. 800 * 801 * @param sb The StringBuffer to place the rendered text into. 802 * @param width The number of characters to display per line 803 * @param nextLineTabStop The position on the next line for the first tab. 804 * @param text The text to be rendered. 805 * 806 * @return the StringBuffer with the rendered Options contents. 807 */ 808 protected StringBuffer renderWrappedText(final StringBuffer sb, final int width, final int nextLineTabStop, final String text) { 809 String render = text; 810 int nextLineTabStopPos = nextLineTabStop; 811 int pos = findWrapPos(render, width, 0); 812 if (pos == -1) { 813 sb.append(rtrim(render)); 814 return sb; 815 } 816 sb.append(rtrim(render.substring(0, pos))).append(getNewLine()); 817 if (nextLineTabStopPos >= width) { 818 // stops infinite loop happening 819 nextLineTabStopPos = 1; 820 } 821 // all following lines must be padded with nextLineTabStop space characters 822 final String padding = createPadding(nextLineTabStopPos); 823 while (true) { 824 render = padding + render.substring(pos).trim(); 825 pos = findWrapPos(render, width, 0); 826 if (pos == -1) { 827 sb.append(render); 828 return sb; 829 } 830 if (render.length() > width && pos == nextLineTabStopPos - 1) { 831 pos = width; 832 } 833 sb.append(rtrim(render.substring(0, pos))).append(getNewLine()); 834 } 835 } 836 837 /** 838 * Renders the specified text width a maximum width. This method differs from renderWrappedText by not removing leading 839 * spaces after a new line. 840 * 841 * @param sb The StringBuffer to place the rendered text into. 842 * @param width The number of characters to display per line 843 * @param nextLineTabStop The position on the next line for the first tab. 844 * @param text The text to be rendered. 845 */ 846 private Appendable renderWrappedTextBlock(final StringBuffer sb, final int width, final int nextLineTabStop, final String text) { 847 try { 848 final BufferedReader in = new BufferedReader(new StringReader(text)); 849 String line; 850 boolean firstLine = true; 851 while ((line = in.readLine()) != null) { 852 if (!firstLine) { 853 sb.append(getNewLine()); 854 } else { 855 firstLine = false; 856 } 857 renderWrappedText(sb, width, nextLineTabStop, line); 858 } 859 } catch (final IOException e) { // NOPMD 860 // cannot happen 861 } 862 return sb; 863 } 864 865 /** 866 * Removes the trailing whitespace from the specified String. 867 * 868 * @param s The String to remove the trailing padding from. 869 * @return The String of without the trailing padding 870 */ 871 protected String rtrim(final String s) { 872 if (s == null || s.isEmpty()) { 873 return s; 874 } 875 int pos = s.length(); 876 while (pos > 0 && Character.isWhitespace(s.charAt(pos - 1))) { 877 --pos; 878 } 879 return s.substring(0, pos); 880 } 881 882 /** 883 * Sets the 'argName'. 884 * 885 * @param name the new value of 'argName' 886 */ 887 public void setArgName(final String name) { 888 this.defaultArgName = name; 889 } 890 891 /** 892 * Sets the 'descPadding'. 893 * 894 * @param padding the new value of 'descPadding' 895 */ 896 public void setDescPadding(final int padding) { 897 this.defaultDescPad = padding; 898 } 899 900 /** 901 * Sets the 'leftPadding'. 902 * 903 * @param padding the new value of 'leftPadding' 904 */ 905 public void setLeftPadding(final int padding) { 906 this.defaultLeftPad = padding; 907 } 908 909 /** 910 * Sets the 'longOptPrefix'. 911 * 912 * @param prefix the new value of 'longOptPrefix' 913 */ 914 public void setLongOptPrefix(final String prefix) { 915 this.defaultLongOptPrefix = prefix; 916 } 917 918 /** 919 * Sets the separator displayed between a long option and its value. Ensure that the separator specified is supported by 920 * the parser used, typically ' ' or '='. 921 * 922 * @param longOptSeparator the separator, typically ' ' or '='. 923 * @since 1.3 924 */ 925 public void setLongOptSeparator(final String longOptSeparator) { 926 this.longOptSeparator = longOptSeparator; 927 } 928 929 /** 930 * Sets the 'newLine'. 931 * 932 * @param newline the new value of 'newLine' 933 */ 934 public void setNewLine(final String newline) { 935 this.defaultNewLine = newline; 936 } 937 938 /** 939 * Sets the comparator used to sort the options when they output in help text. Passing in a null comparator will keep the 940 * options in the order they were declared. 941 * 942 * @param comparator the {@link Comparator} to use for sorting the options 943 * @since 1.2 944 */ 945 public void setOptionComparator(final Comparator<Option> comparator) { 946 this.optionComparator = comparator; 947 } 948 949 /** 950 * Sets the 'optPrefix'. 951 * 952 * @param prefix the new value of 'optPrefix' 953 */ 954 public void setOptPrefix(final String prefix) { 955 this.defaultOptPrefix = prefix; 956 } 957 958 /** 959 * Sets the 'syntaxPrefix'. 960 * 961 * @param prefix the new value of 'syntaxPrefix' 962 */ 963 public void setSyntaxPrefix(final String prefix) { 964 this.defaultSyntaxPrefix = prefix; 965 } 966 967 /** 968 * Sets the 'width'. 969 * 970 * @param width the new value of 'width' 971 */ 972 public void setWidth(final int width) { 973 this.defaultWidth = width; 974 } 975 976}