    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}