Skip to content

Commit e9a72ea

Browse files
authored
CLI-339: Help formatter extension (#314)
* initial checkin * fixed up for Rat processing * fixed checkstyle and some javadoc * fixed javadoc errors * added more tests and fixed some errors * added tests and fixed errors * checkstyle and jacoco fixes * fixed javadoc and pmd errors * fixed javadoc error * renamed Serializer to HelpWriter * changes as per review * Deprecated HelpFormatter and ensured replacement methods were available * Fixed javadoc issues * updated as per review * Partial changes as per review HelpWriter naming chagnes Changed scaling to boolean scalable fixed weird license comment formatting. * Added java bug reference * changed 'asX' to 'toX' * Split Util into Util and halp/Util and make package private
1 parent 7507916 commit e9a72ea

22 files changed

Lines changed: 4162 additions & 0 deletions

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@
6363
<version>2.17.0</version>
6464
<scope>test</scope>
6565
</dependency>
66+
<dependency>
67+
<groupId>org.apache.commons</groupId>
68+
<artifactId>commons-text</artifactId>
69+
<version>1.12.0</version>
70+
<scope>test</scope>
71+
</dependency>
6672
<dependency>
6773
<groupId>org.mockito</groupId>
6874
<artifactId>mockito-core</artifactId>

src/main/java/org/apache/commons/cli/HelpFormatter.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ Licensed to the Apache Software Foundation (ASF) under one or more
6464
*
6565
* Please report issues at https://example.com/issues
6666
* </pre>
67+
* @deprecated Use {@link org.apache.commons.cli.help.HelpFormatter}
6768
*/
69+
@Deprecated
6870
public class HelpFormatter {
6971

7072
/**
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
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+
http://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+
package org.apache.commons.cli.help;
18+
19+
import static java.lang.String.format;
20+
21+
import java.io.IOException;
22+
import java.util.ArrayList;
23+
import java.util.Collection;
24+
import java.util.Comparator;
25+
import java.util.Iterator;
26+
import java.util.List;
27+
import java.util.Objects;
28+
import java.util.function.Function;
29+
30+
import org.apache.commons.cli.Option;
31+
import org.apache.commons.cli.OptionGroup;
32+
import org.apache.commons.cli.Options;
33+
34+
/**
35+
* The class for help formatters provides the framework to link the {@link HelpWriter} with the {@link OptionFormatter}
36+
* and a default {@link TableDefinition} so to produce a standard format help page.
37+
* @since 1.10.0
38+
*/
39+
public abstract class AbstractHelpFormatter {
40+
41+
/** The string to display at the beginning of the usage statement */
42+
public static final String DEFAULT_SYNTAX_PREFIX = "usage: ";
43+
44+
/**
45+
* The default separator between {@link OptionGroup} elements.
46+
*/
47+
public static final String DEFAULT_OPTION_GROUP_SEPARATOR = " | ";
48+
49+
/**
50+
* The default comparator for {@link Option} implementations.
51+
*/
52+
public static final Comparator<Option> DEFAULT_COMPARATOR = (opt1, opt2) -> opt1.getKey().compareToIgnoreCase(opt2.getKey());
53+
54+
/**
55+
* The {@link HelpWriter} that produces the final output.
56+
*/
57+
protected final HelpWriter helpWriter;
58+
/**
59+
* The OptionFormatter.Builder used to display options within the help page
60+
*/
61+
protected final OptionFormatter.Builder optionFormatBuilder;
62+
/**
63+
* The phrase printed before the syntax line.
64+
*/
65+
protected String syntaxPrefix = DEFAULT_SYNTAX_PREFIX;
66+
67+
/** The comparator for sorting {@link Option} collections */
68+
protected Comparator<Option> comparator;
69+
70+
/** The separator between {@link OptionGroup} components.*/
71+
protected final String optionGroupSeparator;
72+
73+
74+
/**
75+
* Constructs the base formatter.
76+
* @param helpWriter the helpWriter to output with
77+
* @param optionFormatBuilder the builder of {@link OptionFormatter} to format options for display.
78+
* @param comparator The comparator to use for sorting options.
79+
* @param optionGroupSeparator the string to separate option groups.
80+
*/
81+
protected AbstractHelpFormatter(final HelpWriter helpWriter, final OptionFormatter.Builder optionFormatBuilder,
82+
final Comparator<Option> comparator,
83+
final String optionGroupSeparator) {
84+
this.helpWriter = Objects.requireNonNull(helpWriter, "helpWriter");
85+
this.optionFormatBuilder = Objects.requireNonNull(optionFormatBuilder, "optionFormatBuilder");
86+
this.comparator = Objects.requireNonNull(comparator, "comparator");
87+
this.optionGroupSeparator = Util.defaultValue(optionGroupSeparator, "");
88+
}
89+
90+
/**
91+
* Converts a collection of {@link Option}s into a {@link TableDefinition}.
92+
* @param options The options to create a table for.
93+
* @return the TableDefinition.
94+
*/
95+
protected abstract TableDefinition getTableDefinition(Iterable<Option> options);
96+
97+
/**
98+
* Sets the syntax prefix. This is the phrase that is printed before the syntax line.
99+
*
100+
* @param prefix the new value for the syntax prefix.
101+
*/
102+
public final void setSyntaxPrefix(final String prefix) {
103+
this.syntaxPrefix = prefix;
104+
}
105+
106+
/**
107+
* Gets the currently set syntax prefix.
108+
* @return The currently set syntax prefix.
109+
*/
110+
public final String getSyntaxPrefix() {
111+
return syntaxPrefix;
112+
}
113+
114+
/**
115+
* Gets the {@link HelpWriter} associated with this help formatter.
116+
* @return The {@link HelpWriter} associated with this help formatter.
117+
*/
118+
public final HelpWriter getSerializer() {
119+
return helpWriter;
120+
}
121+
122+
/**
123+
* Constructs an {@link OptionFormatter} for the specified {@link Option}.
124+
* @param option The Option to format.
125+
* @return an {@link OptionFormatter} for the specified {@link Option}.
126+
*/
127+
public final OptionFormatter getOptionFormatter(final Option option) {
128+
return optionFormatBuilder.build(option);
129+
}
130+
131+
/**
132+
* Gets the comparator used by this HelpFormatter.
133+
* @return The comparator used by this HelpFormatter.
134+
*/
135+
public Comparator<Option> getComparator() {
136+
return comparator;
137+
}
138+
139+
/**
140+
* Prints the help for {@link Options} with the specified command line syntax.
141+
*
142+
* @param cmdLineSyntax the syntax for this application
143+
* @param header the banner to display at the beginning of the help
144+
* @param options the {@link Options} to print
145+
* @param footer the banner to display at the end of the help
146+
* @param autoUsage whether to print an automatically generated usage statement
147+
* @throws IOException If the output could not be written to the {@link HelpWriter}
148+
*/
149+
public final void printHelp(final String cmdLineSyntax, final String header, final Options options,
150+
final String footer, final boolean autoUsage) throws IOException {
151+
printHelp(cmdLineSyntax, header, options.getOptions(), footer, autoUsage);
152+
}
153+
154+
/**
155+
* Prints the help for a collection of {@link Option}s with the specified command line syntax.
156+
*
157+
* @param cmdLineSyntax the syntax for this application
158+
* @param header the banner to display at the beginning of the help
159+
* @param options the collection of {@link Option} objects to print.
160+
* @param footer the banner to display at the end of the help
161+
* @param autoUsage whether to print an automatically generated usage statement
162+
* @throws IOException If the output could not be written to the {@link HelpWriter}
163+
*/
164+
public void printHelp(final String cmdLineSyntax, final String header, final Iterable<Option> options,
165+
final String footer, final boolean autoUsage) throws IOException {
166+
if (Util.isEmpty(cmdLineSyntax)) {
167+
throw new IllegalArgumentException("cmdLineSyntax not provided");
168+
}
169+
170+
if (autoUsage) {
171+
helpWriter.appendParagraph(format("%s %s %s", syntaxPrefix, cmdLineSyntax, toSyntaxOptions(options)));
172+
} else {
173+
helpWriter.appendParagraph(format("%s %s", syntaxPrefix, cmdLineSyntax));
174+
}
175+
176+
if (!Util.isEmpty(header)) {
177+
helpWriter.appendParagraph(header);
178+
}
179+
180+
helpWriter.appendTable(getTableDefinition(options));
181+
182+
if (!Util.isEmpty(footer)) {
183+
helpWriter.appendParagraph(footer);
184+
}
185+
}
186+
187+
/**
188+
* Prints the option table for the specified {@link Options} to the {@link HelpWriter}.
189+
* @param options the Options to print in the table.
190+
* @throws IOException If the output could not be written to the {@link HelpWriter}
191+
*/
192+
public final void printOptions(final Options options) throws IOException {
193+
printOptions(options.getOptions());
194+
}
195+
196+
/**
197+
* Prints the option table for a collection of {@link Option} objects to the {@link HelpWriter}.
198+
* @param options the collection of Option objects to print in the table.
199+
* @throws IOException If the output could not be written to the {@link HelpWriter}
200+
*/
201+
public final void printOptions(final Iterable<Option> options) throws IOException {
202+
printOptions(getTableDefinition(options));
203+
}
204+
/**
205+
* Prints a {@link TableDefinition} to the {@link HelpWriter}.
206+
* @param tableDefinition the {@link TableDefinition} to print.
207+
* @throws IOException If the output could not be written to the {@link HelpWriter}
208+
*/
209+
public final void printOptions(final TableDefinition tableDefinition) throws IOException {
210+
helpWriter.appendTable(tableDefinition);
211+
}
212+
213+
/**
214+
* Formats the {@code argName} as an argument a defined in the enclosed {@link OptionFormatter.Builder}
215+
* @param argName the string to format as an argument.
216+
* @return the {@code argName} formatted as an argument.
217+
*/
218+
public final String asArgName(final String argName) {
219+
return optionFormatBuilder.toArgName(argName);
220+
}
221+
222+
/**
223+
* Return the string representation of the options as used in the syntax display.
224+
* @param options The {@link Options} to create the string representation for.
225+
* @return the string representation of the options as used in the syntax display.
226+
*/
227+
public String toSyntaxOptions(final Options options) {
228+
return toSyntaxOptions(options.getOptions(), options::getOptionGroup);
229+
}
230+
231+
/**
232+
* Return the string representation of the options as used in the syntax display.
233+
* @param options The collection of {@link Option} instances to create the string representation for.
234+
* @return the string representation of the options as used in the syntax display.
235+
*/
236+
public String toSyntaxOptions(final Iterable<Option> options) {
237+
return toSyntaxOptions(options, o -> null);
238+
}
239+
240+
/**
241+
* Creates a new list of options ordered by the comparator.
242+
* @param options the Options to sort.
243+
* @return a new list of options ordered by the comparator.
244+
*/
245+
public List<Option> sort(final Options options) {
246+
return sort(options == null ? null : options.getOptions());
247+
}
248+
249+
/**
250+
* Creates a new list of options ordered by the comparator.
251+
* @param options the Options to sort.
252+
* @return a new list of options ordered by the comparator.
253+
*/
254+
public List<Option> sort(final Iterable<Option> options) {
255+
List<Option> result = new ArrayList<>();
256+
if (options != null) {
257+
options.forEach(result::add);
258+
result.sort(comparator);
259+
}
260+
return result;
261+
}
262+
263+
/**
264+
* Return the string representation of the options as used in the syntax display.
265+
* @param options The options to create the string representation for.
266+
* @param lookup a function to determine if the Option is part of an OptionGroup that has already been processed.
267+
* @return the string representation of the options as used in the syntax display.
268+
*/
269+
protected String toSyntaxOptions(final Iterable<Option> options,
270+
final Function<Option, OptionGroup> lookup) {
271+
// list of groups that have been processed.
272+
final Collection<OptionGroup> processedGroups = new ArrayList<>();
273+
final List<Option> optList = sort(options);
274+
StringBuilder buff = new StringBuilder();
275+
String pfx = "";
276+
// iterate over the options
277+
for (final Option option : optList) {
278+
// get the next Option
279+
// check if the option is part of an OptionGroup
280+
final OptionGroup group = lookup.apply(option);
281+
// if the option is part of a group
282+
if (group != null) {
283+
// and if the group has not already been processed
284+
if (!processedGroups.contains(group)) {
285+
// add the group to the processed list
286+
processedGroups.add(group);
287+
// add the usage clause
288+
buff.append(pfx).append(toSyntaxOptions(group));
289+
pfx = " ";
290+
}
291+
// otherwise the option was displayed in the group previously so ignore it.
292+
}
293+
// if the Option is not part of an OptionGroup
294+
else {
295+
buff.append(pfx).append(optionFormatBuilder.build(option).toSyntaxOption());
296+
pfx = " ";
297+
}
298+
}
299+
return buff.toString();
300+
}
301+
302+
/**
303+
* Return the string representation of the options as used in the syntax display.
304+
* @param group The OptionGroup to create the string representation for.
305+
* @return the string representation of the options as used in the syntax display.
306+
*/
307+
public String toSyntaxOptions(final OptionGroup group) {
308+
StringBuilder buff = new StringBuilder();
309+
final List<Option> optList = sort(group.getOptions());
310+
OptionFormatter formatter = null;
311+
// for each option in the OptionGroup
312+
Iterator<Option> iter = optList.iterator();
313+
while (iter.hasNext()) {
314+
formatter = optionFormatBuilder.build(iter.next());
315+
// whether the option is required or not is handled at group level
316+
buff.append(formatter.toSyntaxOption(true));
317+
318+
if (iter.hasNext()) {
319+
buff.append(optionGroupSeparator);
320+
}
321+
}
322+
if (formatter != null) {
323+
return group.isRequired() ? buff.toString() : formatter.toOptional(buff.toString());
324+
}
325+
return ""; // there were no entries in the group.
326+
}
327+
}

0 commit comments

Comments
 (0)