    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 */
    017package org.apache.commons.cli.help;
    018
    019import java.io.IOException;
    020import java.util.ArrayList;
    021import java.util.Collection;
    022import java.util.Comparator;
    023import java.util.Iterator;
    024import java.util.List;
    025import java.util.Objects;
    026import java.util.function.Function;
    027import java.util.function.Supplier;
    028
    029import org.apache.commons.cli.Option;
    030import org.apache.commons.cli.OptionGroup;
    031import org.apache.commons.cli.Options;
    032
    033/**
    034 * Helps formatters provides the framework to link a {@link HelpAppendable} with a {@link OptionFormatter} and a default {@link TableDefinition} so to produce
    035 * standardized format help output.
    036 *
    037 * @since 1.10.0
    038 */
    039public abstract class AbstractHelpFormatter {
    040
    041    /**
    042     * Abstracts building instances for subclasses.
    043     * <ul>
    044     * <li>helpAppendable = a {@link TextHelpAppendable} writing to {@code System.out}</li>
    045     * <li>optionFormatter.Builder = the default {@link OptionFormatter.Builder}</li>
    046     * </ul>
    047     *
    048     * @param <B> The builder type.
    049     * @param <T> The type to build.
    050     */
    051    public abstract static class Builder<B extends Builder<B, T>, T extends AbstractHelpFormatter> implements Supplier<T> {
    052
    053        /** The comparator to sort lists of options */
    054        private Comparator<Option> comparator = DEFAULT_COMPARATOR;
    055
    056        /** The {@link HelpAppendable}. */
    057        private HelpAppendable helpAppendable = TextHelpAppendable.systemOut();
    058
    059        /** The {@link OptionFormatter.Builder} to use to format options in the table. */
    060        private OptionFormatter.Builder optionFormatBuilder = OptionFormatter.builder();
    061
    062        /** The string to separate option groups. */
    063        private String optionGroupSeparator = DEFAULT_OPTION_GROUP_SEPARATOR;
    064
    065        /**
    066         * Constructs a new instance.
    067         * <p>
    068         * Sets {@code showSince} to {@code true}.
    069         * </p>
    070         */
    071        protected Builder() {
    072            // empty
    073        }
    074
    075        /**
    076         * Returns this instance cast to {@code B}.
    077         *
    078         * @return {@code this} instance cast to {@code B}.
    079         */
    080        @SuppressWarnings("unchecked")
    081        protected B asThis() {
    082            return (B) this;
    083        }
    084
    085        /**
    086         * Gets the comparator to sort lists of options.
    087         *
    088         * @return the comparator to sort lists of options.
    089         */
    090        protected Comparator<Option> getComparator() {
    091            return comparator;
    092        }
    093
    094        /**
    095         * Gets {@link HelpAppendable}.
    096         *
    097         * @return the {@link HelpAppendable}.
    098         */
    099        protected HelpAppendable getHelpAppendable() {
    100            return helpAppendable;
    101        }
    102
    103        /**
    104         * Gets {@link OptionFormatter.Builder} to use to format options in the table.
    105         *
    106         * @return the {@link OptionFormatter.Builder} to use to format options in the table.
    107         */
    108        protected OptionFormatter.Builder getOptionFormatBuilder() {
    109            return optionFormatBuilder;
    110        }
    111
    112        /**
    113         * Gets string to separate option groups.
    114         *
    115         * @return the string to separate option groups.
    116         */
    117        protected String getOptionGroupSeparator() {
    118            return optionGroupSeparator;
    119        }
    120
    121        /**
    122         * Sets the comparator to use for sorting options. If set to {@code null} no sorting is performed.
    123         *
    124         * @param comparator The comparator to use for sorting options.
    125         * @return this
    126         */
    127        public B setComparator(final Comparator<Option> comparator) {
    128            this.comparator = comparator;
    129            return asThis();
    130        }
    131
    132        /**
    133         * Sets the {@link HelpAppendable}.
    134         *
    135         * @param helpAppendable the {@link HelpAppendable} to use.
    136         * @return this
    137         */
    138        public B setHelpAppendable(final HelpAppendable helpAppendable) {
    139            this.helpAppendable = helpAppendable != null ? helpAppendable : TextHelpAppendable.systemOut();
    140            return asThis();
    141        }
    142
    143        /**
    144         * Sets the {@link OptionFormatter.Builder}.
    145         *
    146         * @param optionFormatBuilder the {@link OptionFormatter.Builder} to use.
    147         * @return this
    148         */
    149        public B setOptionFormatBuilder(final OptionFormatter.Builder optionFormatBuilder) {
    150            this.optionFormatBuilder = optionFormatBuilder != null ? optionFormatBuilder : OptionFormatter.builder();
    151            return asThis();
    152        }
    153
    154        /**
    155         * Sets the OptionGroup separator. Normally " | " or something similar to denote that only one option may be chosen.
    156         *
    157         * @param optionGroupSeparator the string to separate option group elements with.
    158         * @return this
    159         */
    160        public B setOptionGroupSeparator(final String optionGroupSeparator) {
    161            this.optionGroupSeparator = Util.defaultValue(optionGroupSeparator, "");
    162            return asThis();
    163        }
    164
    165    }
    166
    167    /**
    168     * The default comparator for {@link Option} implementations.
    169     */
    170    public static final Comparator<Option> DEFAULT_COMPARATOR = (opt1, opt2) -> opt1.getKey().compareToIgnoreCase(opt2.getKey());
    171
    172    /**
    173     * The default separator between {@link OptionGroup} elements: {@value}.
    174     */
    175    public static final String DEFAULT_OPTION_GROUP_SEPARATOR = " | ";
    176
    177    /**
    178     * The string to display at the beginning of the usage statement: {@value}.
    179     */
    180    public static final String DEFAULT_SYNTAX_PREFIX = "usage: ";
    181
    182    /** The comparator for sorting {@link Option} collections */
    183    private final Comparator<Option> comparator;
    184    /**
    185     * The {@link HelpAppendable} that produces the final output.
    186     */
    187    private final HelpAppendable helpAppendable;
    188
    189    /**
    190     * The OptionFormatter.Builder used to display options within the help page.
    191     */
    192    private final OptionFormatter.Builder optionFormatBuilder;
    193
    194    /** The separator between {@link OptionGroup} components. */
    195    private final String optionGroupSeparator;
    196
    197    /**
    198     * The phrase printed before the syntax line.
    199     */
    200    private String syntaxPrefix = DEFAULT_SYNTAX_PREFIX;
    201
    202    /**
    203     * Constructs the base formatter.
    204     *
    205     * @param builder the builder.
    206     */
    207    protected AbstractHelpFormatter(final Builder<?, ?> builder) {
    208        this.helpAppendable = Objects.requireNonNull(builder.getHelpAppendable(), "helpAppendable");
    209        this.optionFormatBuilder = Objects.requireNonNull(builder.getOptionFormatBuilder(), "optionFormatBuilder");
    210        this.comparator = Objects.requireNonNull(builder.getComparator(), "comparator");
    211        this.optionGroupSeparator = Util.defaultValue(builder.getOptionGroupSeparator(), "");
    212    }
    213
    214    /**
    215     * Gets the comparator for sorting options.
    216     *
    217     * @return The comparator for sorting options.
    218     */
    219    protected Comparator<Option> getComparator() {
    220        return comparator;
    221    }
    222
    223    /**
    224     * Gets the help appendable.
    225     *
    226     * @return The help appendable.
    227     */
    228    protected HelpAppendable getHelpAppendable() {
    229        return helpAppendable;
    230    }
    231
    232    /**
    233     * Gets the option formatter builder.
    234     *
    235     * @return The option formatter builder.
    236     */
    237    protected OptionFormatter.Builder getOptionFormatBuilder() {
    238        return optionFormatBuilder;
    239    }
    240
    241    /**
    242     * Constructs an {@link OptionFormatter} for the specified {@link Option}.
    243     *
    244     * @param option The Option to format.
    245     * @return an {@link OptionFormatter} for the specified {@link Option}.
    246     */
    247    public final OptionFormatter getOptionFormatter(final Option option) {
    248        return optionFormatBuilder.build(option);
    249    }
    250
    251    /**
    252     * Gets the option group separator.
    253     *
    254     * @return The option group separator.
    255     */
    256    protected String getOptionGroupSeparator() {
    257        return optionGroupSeparator;
    258    }
    259
    260    /**
    261     * Gets the {@link HelpAppendable} associated with this help formatter.
    262     *
    263     * @return The {@link HelpAppendable} associated with this help formatter.
    264     */
    265    public final HelpAppendable getSerializer() {
    266        return helpAppendable;
    267    }
    268
    269    /**
    270     * Gets the currently set syntax prefix.
    271     *
    272     * @return The currently set syntax prefix.
    273     */
    274    public final String getSyntaxPrefix() {
    275        return syntaxPrefix;
    276    }
    277
    278    /**
    279     * Converts a collection of {@link Option}s into a {@link TableDefinition}.
    280     *
    281     * @param options The options to create a table for.
    282     * @return the TableDefinition.
    283     */
    284    protected abstract TableDefinition getTableDefinition(Iterable<Option> options);
    285
    286    /**
    287     * Prints the help for {@link Options} with the specified command line syntax.
    288     *
    289     * @param cmdLineSyntax the syntax for this application.
    290     * @param header        the banner to display at the beginning of the help.
    291     * @param options       the collection of {@link Option} objects to print.
    292     * @param footer        the banner to display at the end of the help.
    293     * @param autoUsage     whether to print an automatically generated usage statement.
    294     * @throws IOException If the output could not be written to the {@link HelpAppendable}.
    295     */
    296    public void printHelp(final String cmdLineSyntax, final String header, final Iterable<Option> options, final String footer, final boolean autoUsage)
    297            throws IOException {
    298        Options optionsObject = new Options();
    299        options.forEach(optionsObject::addOption);
    300        printHelp(cmdLineSyntax, header, optionsObject, footer, autoUsage);
    301    }
    302
    303    /**
    304     * Prints the help for a collection of {@link Option}s with the specified command line syntax.
    305     *
    306     * @param cmdLineSyntax the syntax for this application.
    307     * @param header        the banner to display at the beginning of the help.
    308     * @param options       the collection of {@link Option} objects to print.
    309     * @param footer        the banner to display at the end of the help.
    310     * @param autoUsage     whether to print an automatically generated usage statement.
    311     * @throws IOException If the output could not be written to the {@link HelpAppendable}.
    312     */
    313    public void printHelp(final String cmdLineSyntax, final String header, final Options options, final String footer, final boolean autoUsage)
    314            throws IOException {
    315        if (Util.isEmpty(cmdLineSyntax)) {
    316            throw new IllegalArgumentException("cmdLineSyntax not provided");
    317        }
    318        if (autoUsage) {
    319            helpAppendable.appendParagraphFormat("%s %s %s", syntaxPrefix, cmdLineSyntax, toSyntaxOptions(options));
    320        } else {
    321            helpAppendable.appendParagraphFormat("%s %s", syntaxPrefix, cmdLineSyntax);
    322        }
    323        if (!Util.isEmpty(header)) {
    324            helpAppendable.appendParagraph(header);
    325        }
    326        helpAppendable.appendTable(getTableDefinition(options.getOptions()));
    327        if (!Util.isEmpty(footer)) {
    328            helpAppendable.appendParagraph(footer);
    329        }
    330    }
    331
    332    /**
    333     * Prints the option table for a collection of {@link Option} objects to the {@link HelpAppendable}.
    334     *
    335     * @param options the collection of Option objects to print in the table.
    336     * @throws IOException If the output could not be written to the {@link HelpAppendable}.
    337     */
    338    public final void printOptions(final Iterable<Option> options) throws IOException {
    339        printOptions(getTableDefinition(options));
    340    }
    341
    342    /**
    343     * Prints the option table for the specified {@link Options} to the {@link HelpAppendable}.
    344     *
    345     * @param options the Options to print in the table.
    346     * @throws IOException If the output could not be written to the {@link HelpAppendable}.
    347     */
    348    public final void printOptions(final Options options) throws IOException {
    349        printOptions(options.getOptions());
    350    }
    351
    352    /**
    353     * Prints a {@link TableDefinition} to the {@link HelpAppendable}.
    354     *
    355     * @param tableDefinition the {@link TableDefinition} to print.
    356     * @throws IOException If the output could not be written to the {@link HelpAppendable}.
    357     */
    358    public final void printOptions(final TableDefinition tableDefinition) throws IOException {
    359        helpAppendable.appendTable(tableDefinition);
    360    }
    361
    362    /**
    363     * Sets the syntax prefix. This is the phrase that is printed before the syntax line.
    364     *
    365     * @param prefix the new value for the syntax prefix.
    366     */
    367    public final void setSyntaxPrefix(final String prefix) {
    368        this.syntaxPrefix = prefix;
    369    }
    370
    371    /**
    372     * Creates a new list of options ordered by the comparator.
    373     *
    374     * @param options the Options to sort.
    375     * @return a new list of options ordered by the comparator.
    376     */
    377    public List<Option> sort(final Iterable<Option> options) {
    378        final List<Option> result = new ArrayList<>();
    379        if (options != null) {
    380            options.forEach(result::add);
    381            result.sort(comparator);
    382        }
    383        return result;
    384    }
    385
    386    /**
    387     * Creates a new list of options ordered by the comparator.
    388     *
    389     * @param options the Options to sort.
    390     * @return a new list of options ordered by the comparator.
    391     */
    392    public List<Option> sort(final Options options) {
    393        return sort(options == null ? null : options.getOptions());
    394    }
    395
    396    /**
    397     * Formats the {@code argName} as an argument a defined in the enclosed {@link OptionFormatter.Builder}.
    398     *
    399     * @param argName the string to format as an argument.
    400     * @return the {@code argName} formatted as an argument.
    401     */
    402    public final String toArgName(final String argName) {
    403        return optionFormatBuilder.toArgName(argName);
    404    }
    405
    406    /**
    407     * Return the string representation of the options as used in the syntax display.
    408     * <p>
    409     *     This is probably not the method you want.  This method does not track the presence
    410     *     of option groups.  To display the option grouping use {@link #toSyntaxOptions(Options)} or
    411     *     {@link #toSyntaxOptions(OptionGroup)} for individual groups.
    412     * </p>
    413     * @param options The collection of {@link Option} instances to create the string representation for.
    414     * @return the string representation of the options as used in the syntax display.
    415     */
    416    public String toSyntaxOptions(final Iterable<Option> options) {
    417        return toSyntaxOptions(options, o -> null);
    418    }
    419
    420    /**
    421     * Return the string representation of the options as used in the syntax display.
    422     *
    423     * @param options The options to create the string representation for.
    424     * @param lookup  a function to determine if the Option is part of an OptionGroup that has already been processed.
    425     * @return the string representation of the options as used in the syntax display.
    426     */
    427    protected String toSyntaxOptions(final Iterable<Option> options, final Function<Option, OptionGroup> lookup) {
    428        // list of groups that have been processed.
    429        final Collection<OptionGroup> processedGroups = new ArrayList<>();
    430        final List<Option> optList = sort(options);
    431        final StringBuilder buff = new StringBuilder();
    432        String prefix = "";
    433        // iterate over the options
    434        for (final Option option : optList) {
    435            // get the next Option
    436            // check if the option is part of an OptionGroup
    437            final OptionGroup optionGroup = lookup.apply(option);
    438            // if the option is part of a group
    439            if (optionGroup != null) {
    440                // and if the group has not already been processed
    441                if (!processedGroups.contains(optionGroup)) {
    442                    // add the group to the processed list
    443                    processedGroups.add(optionGroup);
    444                    // add the usage clause
    445                    buff.append(prefix).append(toSyntaxOptions(optionGroup));
    446                    prefix = " ";
    447                }
    448                // otherwise the option was displayed in the group previously so ignore it.
    449            }
    450            // if the Option is not part of an OptionGroup
    451            else {
    452                buff.append(prefix).append(optionFormatBuilder.build(option).toSyntaxOption());
    453                prefix = " ";
    454            }
    455        }
    456        return buff.toString();
    457    }
    458
    459    /**
    460     * Return the string representation of the options as used in the syntax display.
    461     *
    462     * @param group The OptionGroup to create the string representation for.
    463     * @return the string representation of the options as used in the syntax display.
    464     */
    465    public String toSyntaxOptions(final OptionGroup group) {
    466        final StringBuilder buff = new StringBuilder();
    467        final List<Option> optList = sort(group.getOptions());
    468        OptionFormatter formatter = null;
    469        // for each option in the OptionGroup
    470        final Iterator<Option> iter = optList.iterator();
    471        while (iter.hasNext()) {
    472            formatter = optionFormatBuilder.build(iter.next());
    473            // whether the option is required or not is handled at group level
    474            buff.append(formatter.toSyntaxOption(true));
    475            if (iter.hasNext()) {
    476                buff.append(optionGroupSeparator);
    477            }
    478        }
    479        if (formatter != null) {
    480            return group.isRequired() ? buff.toString() : formatter.toOptional(buff.toString());
    481        }
    482        return ""; // there were no entries in the group.
    483    }
    484
    485    /**
    486     * Return the string representation of the options as used in the syntax display.
    487     *
    488     * @param options The {@link Options} to create the string representation for.
    489     * @return the string representation of the options as used in the syntax display.
    490     */
    491    public String toSyntaxOptions(final Options options) {
    492        return toSyntaxOptions(options.getOptions(), options::getOptionGroup);
    493    }
    494}