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