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 package org.apache.commons.cli.help;
18
19 import java.util.Arrays;
20 import java.util.function.BiFunction;
21 import java.util.function.Function;
22 import java.util.function.Supplier;
23
24 import org.apache.commons.cli.DeprecatedAttributes;
25 import org.apache.commons.cli.Option;
26
27 /**
28 * The definition of how to display Option attributes.
29 *
30 * @since 1.10.0
31 */
32 public final class OptionFormatter {
33
34 /**
35 * Builds instances of {@link OptionFormatter}.
36 */
37 public static final class Builder implements Supplier<OptionFormatter> {
38
39 /** The argument name delimiters */
40 private final String[] argNameDelimiters;
41
42 /** The default argument name */
43 private String defaultArgName;
44
45 /** The function to create the deprecated message for an option */
46 private Function<Option, String> deprecatedFormatFunction;
47
48 /** The long option prefix */
49 private String longOptPrefix;
50
51 /** The option prefix */
52 private String optPrefix;
53
54 /** The separator between long and short options */
55 private String optSeparator;
56
57 /** The separator between the opt and/or longOpt and the argument name */
58 private String optArgSeparator;
59
60 /** The delimiters surrounding optional {@link Option} instances. */
61 private final String[] optionalDelimiters;
62
63 /** A function to convert the {@link OptionFormatter} into an entry in the syntax description. */
64 private BiFunction<OptionFormatter, Boolean, String> syntaxFormatFunction;
65
66 /**
67 * Default constructor. Uses the defaults specified in {@link OptionFormatter}.
68 */
69 private Builder() {
70 argNameDelimiters = Arrays.copyOf(DEFAULT_ARG_NAME_DELIMITERS, 2);
71 defaultArgName = DEFAULT_ARG_NAME;
72 deprecatedFormatFunction = NO_DEPRECATED_FORMAT;
73 longOptPrefix = DEFAULT_LONG_OPT_PREFIX;
74 optPrefix = DEFAULT_OPT_PREFIX;
75 optSeparator = DEFAULT_OPT_SEPARATOR;
76 optArgSeparator = DEFAULT_OPT_ARG_SEPARATOR;
77 optionalDelimiters = Arrays.copyOf(DEFAULT_OPTIONAL_DELIMITERS, 2);
78 }
79
80 /**
81 * Constructor that takes the arguments from the supplied {@link OptionFormatter}
82 *
83 * @param optionFormatter The option formatter to provide values for the builder.
84 */
85 public Builder(final OptionFormatter optionFormatter) {
86 optionalDelimiters = Arrays.copyOf(optionFormatter.optionalDelimiters, 2);
87 argNameDelimiters = Arrays.copyOf(optionFormatter.argNameDelimiters, 2);
88 defaultArgName = optionFormatter.defaultArgName;
89 optPrefix = optionFormatter.optPrefix;
90 longOptPrefix = optionFormatter.longOptPrefix;
91 optSeparator = optionFormatter.optSeparator;
92 deprecatedFormatFunction = optionFormatter.deprecatedFormatFunction;
93 syntaxFormatFunction = optionFormatter.syntaxFormatFunction;
94 }
95
96 /**
97 * Build an OptionFormatter to format the specified option.
98 *
99 * @param option The Option to format.
100 * @return An OptionFormatter to format the specified option.
101 */
102 public OptionFormatter build(final Option option) {
103 return new OptionFormatter(option, this);
104 }
105
106 @Override
107 public OptionFormatter get() {
108 // TODO Auto-generated method stub
109 return null;
110 }
111
112 /**
113 * Specifies the starting and ending argument name delimiters for {@link Option} instances.
114 *
115 * @param begin the beginning delimiter.
116 * @param end the ending delimiter.
117 * @return {@code this} instance.
118 */
119 public Builder setArgumentNameDelimiters(final String begin, final String end) {
120 this.argNameDelimiters[0] = Util.defaultValue(begin, "");
121 this.argNameDelimiters[1] = Util.defaultValue(end, "");
122 return this;
123 }
124
125 /**
126 * Sets the default argument name.
127 *
128 * @param name the new value of default argument name.
129 * @return this
130 */
131 public Builder setDefaultArgName(final String name) {
132 this.defaultArgName = Util.defaultValue(name, DEFAULT_ARG_NAME);
133 return this;
134 }
135
136 /**
137 * Specifies the function to construct the deprecated massage for the Option. Should include the description text if desired.
138 *
139 * @param deprecatedFormatFunction the function to specify the deprecated message for the option.
140 * @return {@code this} instance.
141 */
142 public Builder setDeprecatedFormatFunction(final Function<Option, String> deprecatedFormatFunction) {
143 this.deprecatedFormatFunction = deprecatedFormatFunction;
144 return this;
145 }
146
147 /**
148 * Sets the long option prefix.
149 *
150 * @param prefix prefix for long options.
151 * @return this
152 */
153 public Builder setLongOptPrefix(final String prefix) {
154 this.longOptPrefix = Util.defaultValue(prefix, "");
155 return this;
156 }
157
158 /**
159 * Sets the separator displayed between a options and the argument name. Typically ' ' or '='.
160 *
161 * @param optArgSeparator the separator.
162 * @return this
163 * @since 1.3
164 */
165 public Builder setOptArgSeparator(final String optArgSeparator) {
166 this.optArgSeparator = Util.defaultValue(optArgSeparator, "");
167 return this;
168 }
169
170 /**
171 * Specifies the starting and ending delimiters for optional {@link Option} instances.
172 *
173 * @param begin the beginning delimiter.
174 * @param end the ending delimiter.
175 * @return {@code this} instance.
176 */
177 public Builder setOptionalDelimiters(final String begin, final String end) {
178 this.optionalDelimiters[0] = Util.defaultValue(begin, "");
179 this.optionalDelimiters[1] = Util.defaultValue(end, "");
180 return this;
181 }
182
183 /**
184 * Specifies the short option prefix.
185 *
186 * @param optPrefix the prefix for short options.
187 * @return {@code this} instance.
188 */
189 public Builder setOptPrefix(final String optPrefix) {
190 this.optPrefix = Util.defaultValue(optPrefix, "");
191 return this;
192 }
193
194 /**
195 * Sets the separator displayed between a long option and short options. Typically ',' or ' '.
196 *
197 * @param optSeparator the separator.
198 * @return this
199 * @since 1.3
200 */
201 public Builder setOptSeparator(final String optSeparator) {
202 this.optSeparator = Util.defaultValue(optSeparator, "");
203 return this;
204 }
205
206 /**
207 * Specifies the function to convert an {@link OptionFormatter} into the syntax format for the option.
208 *
209 * @param syntaxFormatFunction The function to convert an {@link OptionFormatter} into the syntax format for the option.
210 * @return this
211 */
212 public Builder setSyntaxFormatFunction(final BiFunction<OptionFormatter, Boolean, String> syntaxFormatFunction) {
213 this.syntaxFormatFunction = syntaxFormatFunction;
214 return this;
215 }
216
217 /**
218 * A helper method to format any string as an argument name based on this builder.
219 *
220 * @param argName the name of the argument.
221 * @return the formatted argument.
222 */
223 public String toArgName(final String argName) {
224 return argNameDelimiters[0] + Util.defaultValue(argName, "") + argNameDelimiters[1];
225 }
226 }
227
228 /** The default delimiters for optional arguments */
229 private static final String[] DEFAULT_OPTIONAL_DELIMITERS = { "[", "]" };
230
231 /** The default delimiters for an argument name */
232 private static final String[] DEFAULT_ARG_NAME_DELIMITERS = { "<", ">" };
233
234 /**
235 * The default argument name: {@value}.
236 */
237 public static final String DEFAULT_ARG_NAME = "arg";
238
239 /**
240 * A function to display a deprecated option with the "[Deprecated]" prefix.
241 */
242 public static final Function<Option, String> SIMPLE_DEPRECATED_FORMAT = o -> "[Deprecated] " + Util.defaultValue(o.getDescription(), "");
243
244 /**
245 * A function to display a deprecated option with a "Deprecated" prefix that displays all deprecation information.
246 */
247 public static final Function<Option, String> COMPLEX_DEPRECATED_FORMAT = o -> {
248 final StringBuilder sb = new StringBuilder("[Deprecated");
249 final DeprecatedAttributes attr = o.getDeprecated();
250 if (attr.isForRemoval()) {
251 sb.append(" for removal");
252 }
253 if (!Util.isEmpty(attr.getSince())) {
254 sb.append(" since ").append(attr.getSince());
255 }
256 if (!Util.isEmpty(attr.getDescription())) {
257 sb.append(". ").append(attr.getDescription());
258 }
259 sb.append("]");
260 if (!Util.isEmpty(o.getDescription())) {
261 sb.append(" ").append(o.getDescription());
262 }
263 return sb.toString();
264 };
265
266 /**
267 * A function to display a deprecated option with the "[Deprecated]" prefix.
268 */
269 public static final Function<Option, String> NO_DEPRECATED_FORMAT = o -> Util.defaultValue(o.getDescription(), "");
270
271 /**
272 * The string to display at the beginning of the usage statement: {@value}.
273 */
274 public static final String DEFAULT_SYNTAX_PREFIX = "usage: ";
275
276 /**
277 * Default prefix for short options: {@value}.
278 */
279 public static final String DEFAULT_OPT_PREFIX = "-";
280
281 /**
282 * Default prefix for long options: {@value}.
283 */
284 public static final String DEFAULT_LONG_OPT_PREFIX = "--";
285
286 /**
287 * The default separator between options: {@value}.
288 */
289 public static final String DEFAULT_OPT_SEPARATOR = ", ";
290
291 /**
292 * The default separator between the opt and/or longOpt and the argument name: {@value}.
293 */
294 public static final String DEFAULT_OPT_ARG_SEPARATOR = " ";
295
296 /**
297 * Creates a new builder.
298 *
299 * @return a new builder.
300 */
301 public static Builder builder() {
302 return new Builder();
303 }
304
305 /**
306 * Construct the {@link OptionFormatter} from an {@link Option} using the default {@link OptionFormatter.Builder}.
307 *
308 * @param option the option to format.
309 * @return an OptionFormatter for the specified @{code option}.
310 */
311 public static OptionFormatter from(final Option option) {
312 return new Builder().build(option);
313 }
314
315 /**
316 * The delimiters around argument names.
317 */
318 private final String[] argNameDelimiters;
319
320 /** The default argument name. */
321 private final String defaultArgName;
322
323 /** The function to display the deprecated option message. */
324 private final Function<Option, String> deprecatedFormatFunction;
325
326 /** The prefix for the long option text. */
327 private final String longOptPrefix;
328
329 /** The prefix for the short option text. */
330 private final String optPrefix;
331
332 /** The separator between the options. */
333 private final String optSeparator;
334
335 /** the separator between the opt and/or longOpt and the argument name. */
336 private final String optArgSeparator;
337
338 /** The delimiters for optional {@link Option}s. */
339 private final String[] optionalDelimiters;
340
341 /** The method to convert an Option formatter into a syntax notation. */
342 private final BiFunction<OptionFormatter, Boolean, String> syntaxFormatFunction;
343
344 /** The {@link Option} being formatted. */
345 private final Option option;
346
347 /**
348 * An OptionFormatter applies formatting options to various {@link Option} attributes for textual display.
349 *
350 * @param option the Option to apply formatting to.
351 * @param builder The Builder that specifies the various formatting options.
352 */
353 private OptionFormatter(final Option option, final Builder builder) {
354 this.optionalDelimiters = builder.optionalDelimiters;
355 this.argNameDelimiters = builder.argNameDelimiters;
356 this.defaultArgName = builder.defaultArgName;
357 this.optPrefix = builder.optPrefix;
358 this.longOptPrefix = builder.longOptPrefix;
359 this.optSeparator = builder.optSeparator;
360 this.optArgSeparator = builder.optArgSeparator;
361 this.deprecatedFormatFunction = builder.deprecatedFormatFunction;
362 this.option = option;
363 this.syntaxFormatFunction = builder.syntaxFormatFunction != null ? builder.syntaxFormatFunction : (o, required) -> {
364 final StringBuilder buff = new StringBuilder();
365 final String argName = o.getArgName();
366 buff.append(Util.defaultValue(o.getOpt(), o.getLongOpt()));
367 if (!Util.isEmpty(argName)) {
368 buff.append(optArgSeparator).append(argName);
369 }
370 final boolean requiredFlg = required == null ? o.isRequired() : required;
371 return requiredFlg ? buff.toString() : o.toOptional(buff.toString());
372 };
373 }
374
375 /**
376 * Gets the argument name wrapped in the argument name delimiters.
377 * <ul>
378 * <li>If option has no arguments an empty string is returned</li>
379 * <li>If the argument name is not set the default argument name is used.</li>
380 * </ul>
381 *
382 * @return The argument name wrapped in the argument name delimiters or an empty string.
383 */
384 public String getArgName() {
385 return option.hasArg() ? argNameDelimiters[0] + Util.defaultValue(option.getArgName(), defaultArgName) + argNameDelimiters[1] : "";
386 }
387
388 /**
389 * Gets both options separated by the specified option separator. Correctly handles the case where one option is not specified.
390 *
391 * @return The one or both of the short and/or long Opt with the associate prefixes.
392 */
393 public String getBothOpt() {
394 final String lOpt = getLongOpt();
395
396 final StringBuilder sb = new StringBuilder(getOpt());
397 if (sb.length() > 0 && !Util.isEmpty(lOpt)) {
398 sb.append(optSeparator);
399 }
400 // sb will not be empty as Option requries at least one of opt or longOpt.
401 return sb.append(getLongOpt()).toString();
402 }
403
404 /**
405 * Gets the description for the option. This will include any deprecation notices if the deprecated format function has been set.
406 *
407 * @return The Description from the option or an empty string is no description was provided and the option is not deprecated.
408 */
409 public String getDescription() {
410 return option.isDeprecated() ? deprecatedFormatFunction.apply(option) : Util.defaultValue(option.getDescription(), "");
411 }
412
413 /**
414 * Gets the long Opt from the @{link Option} with the associate prefix.
415 *
416 * @return The long Opt from the @{link Option} with the associate prefix or an empty string.
417 */
418 public String getLongOpt() {
419 return Util.isEmpty(option.getLongOpt()) ? "" : longOptPrefix + option.getLongOpt();
420 }
421
422 /**
423 * Gets the Opt from the @{link Option} with the associate prefix.
424 *
425 * @return The Opt from the @{link Option} with the associate prefix or an empty string.
426 */
427 public String getOpt() {
428 return Util.isEmpty(option.getOpt()) ? "" : optPrefix + option.getOpt();
429 }
430
431 /**
432 * Gets the "since" value from the Option.
433 *
434 * @return The since valeu from the option or "--" if no since value was set.
435 */
436 public String getSince() {
437 return Util.defaultValue(option.getSince(), DEFAULT_LONG_OPT_PREFIX);
438 }
439
440 /**
441 * Gets the required flag from the enclosed {@link Option}.
442 *
443 * @return The required flag from the enclosed {@link Option}.
444 */
445 public boolean isRequired() {
446 return option.isRequired();
447 }
448
449 /**
450 * Wraps the provided text in the optional delimiters.
451 *
452 * @param text the text to wrap.
453 * @return The text wrapped in the optional delimiters or an eppty string of the text is null or an empty string.
454 */
455 public String toOptional(final String text) {
456 if (Util.isEmpty(text)) {
457 return "";
458 }
459 return optionalDelimiters[0] + text + optionalDelimiters[1];
460 }
461
462 /**
463 * Gets the syntax format for this option.
464 *
465 * @return the syntax format for this option as specified by the syntaxFormatFunction.
466 */
467 public String toSyntaxOption() {
468 return toSyntaxOption(isRequired());
469 }
470
471 /**
472 * Gets the syntax format for this option.
473 *
474 * @param isRequired if {@code true} the options is printed as a required option, otherwise it is optional.
475 * @return the syntax format for this option as specified by the syntaxFormatFunction.
476 */
477 public String toSyntaxOption(final boolean isRequired) {
478 return syntaxFormatFunction.apply(this, isRequired);
479 }
480 }