View Javadoc
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 }