View Javadoc
1   /*
2     Licensed to the Apache Software Foundation (ASF) under one or more
3     contributor license agreements.  See the NOTICE file distributed with
4     this work for additional information regarding copyright ownership.
5     The ASF licenses this file to You under the Apache License, Version 2.0
6     (the "License"); you may not use this file except in compliance with
7     the License.  You may obtain a copy of the License at
8   
9         https://www.apache.org/licenses/LICENSE-2.0
10  
11    Unless required by applicable law or agreed to in writing, software
12    distributed under the License is distributed on an "AS IS" BASIS,
13    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14    See the License for the specific language governing permissions and
15    limitations under the License.
16   */
17  
18  package org.apache.commons.cli;
19  
20  import java.io.BufferedReader;
21  import java.io.IOException;
22  import java.io.PrintWriter;
23  import java.io.Serializable;
24  import java.io.StringReader;
25  import java.io.UncheckedIOException;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.Comparator;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Objects;
34  import java.util.function.Function;
35  import java.util.function.Supplier;
36  
37  import org.apache.commons.cli.help.AbstractHelpFormatter;
38  import org.apache.commons.cli.help.OptionFormatter;
39  
40  /**
41   * A formatter of help messages for command line options.
42   * <p>
43   * Example:
44   * </p>
45   * <pre>
46   * Options options = new Options();
47   * options.addOption(OptionBuilder.withLongOpt("file").withDescription("The file to be processed").hasArg().withArgName("FILE").isRequired().create('f'));
48   * options.addOption(OptionBuilder.withLongOpt("version").withDescription("Print the version of the application").create('v'));
49   * options.addOption(OptionBuilder.withLongOpt("help").create('h'));
50   *
51   * String header = "Do something useful with an input file\n\n";
52   * String footer = "\nPlease report issues at https://example.com/issues";
53   *
54   * HelpFormatter formatter = new HelpFormatter();
55   * formatter.printHelp("myapp", header, options, footer, true);
56   * </pre>
57   * <p>
58   * This produces the following output:
59   * </p>
60   * <pre>{@code
61   * usage: myapp -f <FILE> [-h] [-v]
62   * Do something useful with an input file
63   *
64   *  -f,--file <FILE>   The file to be processed
65   *  -h,--help
66   *  -v,--version       Print the version of the application
67   *
68   * Please report issues at https://example.com/issues
69   * }</pre>
70   * @deprecated Use {@link org.apache.commons.cli.help.HelpFormatter}.
71   */
72  @Deprecated
73  public class HelpFormatter {
74  
75      /**
76       * Builds {@link HelpFormatter}.
77       *
78       * @since 1.7.0
79       */
80      public static class Builder implements Supplier<HelpFormatter> {
81          // TODO All other instance HelpFormatter instance variables.
82          // Make HelpFormatter immutable for 2.0
83  
84          /**
85           * A function to convert a description (not null) and a deprecated Option (not null) to help description
86           */
87          private static final Function<Option, String> DEFAULT_DEPRECATED_FORMAT = o -> "[Deprecated] " + getDescription(o);
88  
89          /**
90           * Formatter for deprecated options.
91           */
92          private Function<Option, String> deprecatedFormatFunction = DEFAULT_DEPRECATED_FORMAT;
93  
94          /**
95           * The output PrintWriter, defaults to wrapping {@link System#out}.
96           */
97          private PrintWriter printStream = createDefaultPrintWriter();
98  
99          /** 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 }