1 /*
2 Licensed to the Apache Software Foundation (ASF) under one or more
3 contributor license agreements. See the NOTICE file distributed with
4 this work for additional information regarding copyright ownership.
5 The ASF licenses this file to You under the Apache License, Version 2.0
6 (the "License"); you may not use this file except in compliance with
7 the License. You may obtain a copy of the License at
8
9 https://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16 */
17
18 package org.apache.commons.cli.help;
19
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.List;
23
24 import org.apache.commons.cli.Option;
25
26 /**
27 * A default formatter implementation for standard usage.
28 * <p>
29 * Example:
30 * </p>
31 *
32 * <pre>
33 * Options options = new Options();
34 * options.addOption(OptionBuilder.withLongOpt("file").withDescription("The file to be processed").hasArg().withArgName("FILE").isRequired().create('f'));
35 * options.addOption(OptionBuilder.withLongOpt("version").withDescription("Print the version of the application").create('v'));
36 * options.addOption(OptionBuilder.withLongOpt("help").create('h'));
37 *
38 * String header = "Do something useful with an input file";
39 * String footer = "Please report issues at https://example.com/issues";
40 *
41 * HelpFormatter formatter = new HelpFormatter();
42 * formatter.printHelp("myapp", header, options, footer, true);
43 * </pre>
44 * <p>
45 * This produces the following output:
46 * </p>
47 *
48 * <pre>
49 * {@code
50 * usage: myapp -f <FILE> [-h] [-v]
51 * Do something useful with an input file
52 *
53 * -f,--file <FILE> The file to be processed
54 * -h,--help
55 * -v,--version Print the version of the application
56 *
57 * Please report issues at https://example.com/issues
58 * }
59 * </pre>
60 *
61 * @since 1.10.0
62 */
63 public class HelpFormatter extends AbstractHelpFormatter {
64
65 /**
66 * A builder for the HelpFormatter. Intended to make more complex uses of the HelpFormatter class easier. Default values are:
67 * <ul>
68 * <li>showSince = true</li>
69 * <li>helpAppendable = a {@link TextHelpAppendable} writing to {@code System.out}</li>
70 * <li>optionFormatter.Builder = the default {@link OptionFormatter.Builder}</li>
71 * </ul>
72 */
73 public static class Builder extends AbstractHelpFormatter.Builder<Builder, HelpFormatter> {
74
75 /** If {@code true} show the "Since" column, otherwise ignore it. */
76 private boolean showSince = true;
77
78 /**
79 * Constructs a new instace.
80 * <p>
81 * Sets {@code showSince} to {@code true}.
82 * </p>
83 */
84 protected Builder() {
85 // empty
86 }
87
88 @Override
89 public HelpFormatter get() {
90 return new HelpFormatter(this);
91 }
92
93 /**
94 * Sets the showSince flag.
95 *
96 * @param showSince the desired value of the showSince flag.
97 * @return {@code this} instance.
98 */
99 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 options.forEach(option -> {
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 }