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