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