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