001/*
002  Licensed to the Apache Software Foundation (ASF) under one or more
003  contributor license agreements.  See the NOTICE file distributed with
004  this work for additional information regarding copyright ownership.
005  The ASF licenses this file to You under the Apache License, Version 2.0
006  (the "License"); you may not use this file except in compliance with
007  the License.  You may obtain a copy of the License at
008
009      http://www.apache.org/licenses/LICENSE-2.0
010
011  Unless required by applicable law or agreed to in writing, software
012  distributed under the License is distributed on an "AS IS" BASIS,
013  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014  See the License for the specific language governing permissions and
015  limitations under the License.
016 */
017
018package org.apache.commons.cli;
019
020import java.io.BufferedReader;
021import java.io.IOException;
022import java.io.PrintWriter;
023import java.io.Serializable;
024import java.io.StringReader;
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.Comparator;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Objects;
033import java.util.function.Supplier;
034
035/**
036 * A formatter of help messages for command line options.
037 * <p>
038 * Example:
039 * </p>
040 * <pre>
041 * Options options = new Options();
042 * options.addOption(OptionBuilder.withLongOpt("file").withDescription("The file to be processed").hasArg().withArgName("FILE").isRequired().create('f'));
043 * options.addOption(OptionBuilder.withLongOpt("version").withDescription("Print the version of the application").create('v'));
044 * options.addOption(OptionBuilder.withLongOpt("help").create('h'));
045 *
046 * String header = "Do something useful with an input file\n\n";
047 * String footer = "\nPlease report issues at https://example.com/issues";
048 *
049 * HelpFormatter formatter = new HelpFormatter();
050 * formatter.printHelp("myapp", header, options, footer, true);
051 * </pre>
052 * <p>
053 * This produces the following output:
054 * </p>
055 * <pre>
056 * usage: myapp -f &lt;FILE&gt; [-h] [-v]
057 * Do something useful with an input file
058 *
059 *  -f,--file &lt;FILE&gt;   The file to be processed
060 *  -h,--help
061 *  -v,--version       Print the version of the application
062 *
063 * Please report issues at https://example.com/issues
064 * </pre>
065 */
066public class HelpFormatter {
067
068    /**
069     * Builds {@link HelpFormatter}.
070     *
071     * @since 1.7.0
072     */
073    public static final class Builder implements Supplier<HelpFormatter> {
074        // TODO All other instance HelpFormatter instance variables.
075        // Make HelpFormatter immutable for 2.0
076
077        /**
078         * Whether to show deprecated options.
079         */
080        private boolean showDeprecated;
081
082        /**
083         * The output PrintWriter, defaults to wrapping {@link System#out}.
084         */
085        private PrintWriter printStream = createDefaultPrintWriter();
086
087        @Override
088        public HelpFormatter get() {
089            return new HelpFormatter(showDeprecated, printStream);
090        }
091
092        /**
093         * Sets the output PrintWriter, defaults to wrapping {@link System#out}.
094         *
095         * @param printWriter the output PrintWriter, not null.
096         * @return this.
097         */
098        public Builder setPrintWriter(final PrintWriter printWriter) {
099            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}