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 */
017package org.apache.commons.cli.help;
018
019import java.util.Arrays;
020import java.util.function.BiFunction;
021import java.util.function.Function;
022import java.util.function.Supplier;
023
024import org.apache.commons.cli.DeprecatedAttributes;
025import org.apache.commons.cli.Option;
026
027/**
028 * The definition of how to display Option attributes.
029 *
030 * @since 1.10.0
031 */
032public final class OptionFormatter {
033
034    /**
035     * Builds instances of {@link OptionFormatter}.
036     */
037    public static final class Builder implements Supplier<OptionFormatter> {
038
039        /** The argument name delimiters */
040        private final String[] argNameDelimiters;
041
042        /** The default argument name */
043        private String defaultArgName;
044
045        /** The function to create the deprecated message for an option */
046        private Function<Option, String> deprecatedFormatFunction;
047
048        /** The long option prefix */
049        private String longOptPrefix;
050
051        /** The option prefix */
052        private String optPrefix;
053
054        /** The separator between long and short options */
055        private String optSeparator;
056
057        /** The separator between the opt and/or longOpt and the argument name */
058        private String optArgSeparator;
059
060        /** The delimiters surrounding optional {@link Option} instances. */
061        private final String[] optionalDelimiters;
062
063        /** A function to convert the {@link OptionFormatter} into an entry in the syntax description. */
064        private BiFunction<OptionFormatter, Boolean, String> syntaxFormatFunction;
065
066        /**
067         * Default constructor. Uses the defaults specified in {@link OptionFormatter}.
068         */
069        private Builder() {
070            argNameDelimiters = Arrays.copyOf(DEFAULT_ARG_NAME_DELIMITERS, 2);
071            defaultArgName = DEFAULT_ARG_NAME;
072            deprecatedFormatFunction = NO_DEPRECATED_FORMAT;
073            longOptPrefix = DEFAULT_LONG_OPT_PREFIX;
074            optPrefix = DEFAULT_OPT_PREFIX;
075            optSeparator = DEFAULT_OPT_SEPARATOR;
076            optArgSeparator = DEFAULT_OPT_ARG_SEPARATOR;
077            optionalDelimiters = Arrays.copyOf(DEFAULT_OPTIONAL_DELIMITERS, 2);
078        }
079
080        /**
081         * Constructor that takes the arguments from the supplied {@link OptionFormatter}
082         *
083         * @param optionFormatter The option formatter to provide values for the builder.
084         */
085        public Builder(final OptionFormatter optionFormatter) {
086            optionalDelimiters = Arrays.copyOf(optionFormatter.optionalDelimiters, 2);
087            argNameDelimiters = Arrays.copyOf(optionFormatter.argNameDelimiters, 2);
088            defaultArgName = optionFormatter.defaultArgName;
089            optPrefix = optionFormatter.optPrefix;
090            longOptPrefix = optionFormatter.longOptPrefix;
091            optSeparator = optionFormatter.optSeparator;
092            deprecatedFormatFunction = optionFormatter.deprecatedFormatFunction;
093            syntaxFormatFunction = optionFormatter.syntaxFormatFunction;
094        }
095
096        /**
097         * Build an OptionFormatter to format the specified option.
098         *
099         * @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 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 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 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 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(), "--");
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}