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