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}