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