    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.help;
    019
    020import java.util.ArrayList;
    021import java.util.Arrays;
    022import java.util.List;
    023
    024import org.apache.commons.cli.Option;
    025
    026/**
    027 * A default formatter implementation for standard usage.
    028 * <p>
    029 * Example:
    030 * </p>
    031 *
    032 * <pre>
    033 * Options options = new Options();
    034 * options.addOption(OptionBuilder.withLongOpt("file").withDescription("The file to be processed").hasArg().withArgName("FILE").isRequired().create('f'));
    035 * options.addOption(OptionBuilder.withLongOpt("version").withDescription("Print the version of the application").create('v'));
    036 * options.addOption(OptionBuilder.withLongOpt("help").create('h'));
    037 *
    038 * String header = "Do something useful with an input file";
    039 * String footer = "Please report issues at https://example.com/issues";
    040 *
    041 * HelpFormatter formatter = new HelpFormatter();
    042 * formatter.printHelp("myapp", header, options, footer, true);
    043 * </pre>
    044 * <p>
    045 * This produces the following output:
    046 * </p>
    047 *
    048 * <pre>
    049 *     {@code
    050 * usage: myapp -f <FILE> [-h] [-v]
    051 * Do something useful with an input file
    052 *
    053 *  -f,--file <FILE>   The file to be processed
    054 *  -h,--help
    055 *  -v,--version       Print the version of the application
    056 *
    057 * Please report issues at https://example.com/issues
    058 * }
    059 * </pre>
    060 *
    061 * @since 1.10.0
    062 */
    063public class HelpFormatter extends AbstractHelpFormatter {
    064
    065    /**
    066     * A builder for the HelpFormatter. Intended to make more complex uses of the HelpFormatter class easier. Default values are:
    067     * <ul>
    068     * <li>showSince = true</li>
    069     * <li>helpAppendable = a {@link TextHelpAppendable} writing to {@code System.out}</li>
    070     * <li>optionFormatter.Builder = the default {@link OptionFormatter.Builder}</li>
    071     * </ul>
    072     */
    073    public static class Builder extends AbstractHelpFormatter.Builder<Builder, HelpFormatter> {
    074
    075        /** If {@code true} show the "Since" column, otherwise ignore it. */
    076        private boolean showSince = true;
    077
    078        /**
    079         * Constructs a new instace.
    080         * <p>
    081         * Sets {@code showSince} to {@code true}.
    082         * </p>
    083         */
    084        protected Builder() {
    085            // empty
    086        }
    087
    088        @Override
    089        public HelpFormatter get() {
    090            return new HelpFormatter(this);
    091        }
    092
    093        /**
    094         * Sets the showSince flag.
    095         *
    096         * @param showSince the desired value of the showSince flag.
    097         * @return this instance.
    098         */
    099        public Builder setShowSince(final boolean showSince) {
    100            this.showSince = showSince;
    101            return this;
    102        }
    103    }
    104
    105    /**
    106     * Default number of characters per line: {@value}.
    107     */
    108    public static final int DEFAULT_WIDTH = 74;
    109
    110    /**
    111     * Default padding to the left of each line: {@value}.
    112     */
    113    public static final int DEFAULT_LEFT_PAD = 1;
    114
    115    /**
    116     * The default number of spaces between columns in the options table: {@value}.
    117     */
    118    public static final int DEFAULT_COLUMN_SPACING = 5;
    119
    120    /**
    121     * Constructs a new builder.
    122     *
    123     * @return a new builder.
    124     */
    125    public static Builder builder() {
    126        return new Builder();
    127    }
    128
    129    /** If {@code true} show the "Since" column, otherwise ignore it. */
    130    private final boolean showSince;
    131
    132    /**
    133     * Constructs the Help formatter.
    134     *
    135     * @param builder the Builder to build from.
    136     */
    137    protected HelpFormatter(final Builder builder) {
    138        super(builder);
    139        this.showSince = builder.showSince;
    140    }
    141
    142    /**
    143     * Gets the table definition for the options.
    144     *
    145     * @param options the collection of {@link Option} instances to create the table from.
    146     * @return A {@link TableDefinition} to display the options.
    147     */
    148    @Override
    149    public TableDefinition getTableDefinition(final Iterable<Option> options) {
    150        // set up the base TextStyle for the columns configured for the Option opt and arg values.
    151        final TextStyle.Builder builder = TextStyle.builder().setAlignment(TextStyle.Alignment.LEFT).setIndent(DEFAULT_LEFT_PAD).setScalable(false);
    152        final List<TextStyle> styles = new ArrayList<>();
    153        styles.add(builder.get());
    154        // set up showSince column
    155        builder.setScalable(true).setLeftPad(DEFAULT_COLUMN_SPACING);
    156        if (showSince) {
    157            builder.setAlignment(TextStyle.Alignment.CENTER);
    158            styles.add(builder.get());
    159        }
    160        // set up the description column.
    161        builder.setAlignment(TextStyle.Alignment.LEFT);
    162        styles.add(builder.get());
    163        // setup the rows for the table.
    164        final List<List<String>> rows = new ArrayList<>();
    165        final StringBuilder sb = new StringBuilder();
    166        for (final Option option : options) {
    167            final List<String> row = new ArrayList<>();
    168            // create an option formatter to correctly format the parts of the option
    169            final OptionFormatter formatter = getOptionFormatBuilder().build(option);
    170            sb.setLength(0);
    171            // append the opt values.
    172            sb.append(formatter.getBothOpt());
    173            // append the arg name if it exists.
    174            if (option.hasArg()) {
    175                sb.append(" ").append(formatter.getArgName());
    176            }
    177            row.add(sb.toString());
    178            // append the "since" value if desired.
    179            if (showSince) {
    180                row.add(formatter.getSince());
    181            }
    182            // add the option description
    183            row.add(formatter.getDescription());
    184            rows.add(row);
    185        }
    186        // return the TableDefinition with the proper column headers.
    187        return TableDefinition.from("", styles, showSince ? Arrays.asList("Options", "Since", "Description") : Arrays.asList("Options", "Description"), rows);
    188    }
    189}