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