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