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