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