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