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      http://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 */
017
018package org.apache.commons.cli;
019
020import java.io.BufferedReader;
021import java.io.IOException;
022import java.io.PrintWriter;
023import java.io.Serializable;
024import java.io.StringReader;
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.Comparator;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Objects;
033import java.util.function.Function;
034import java.util.function.Supplier;
035
036/**
037 * A formatter of help messages for command line options.
038 * <p>
039 * Example:
040 * </p>
041 * <pre>
042 * Options options = new Options();
043 * options.addOption(OptionBuilder.withLongOpt("file").withDescription("The file to be processed").hasArg().withArgName("FILE").isRequired().create('f'));
044 * options.addOption(OptionBuilder.withLongOpt("version").withDescription("Print the version of the application").create('v'));
045 * options.addOption(OptionBuilder.withLongOpt("help").create('h'));
046 *
047 * String header = "Do something useful with an input file\n\n";
048 * String footer = "\nPlease report issues at https://example.com/issues";
049 *
050 * HelpFormatter formatter = new HelpFormatter();
051 * formatter.printHelp("myapp", header, options, footer, true);
052 * </pre>
053 * <p>
054 * This produces the following output:
055 * </p>
056 * <pre>
057 * usage: myapp -f &lt;FILE&gt; [-h] [-v]
058 * Do something useful with an input file
059 *
060 *  -f,--file &lt;FILE&gt;   The file to be processed
061 *  -h,--help
062 *  -v,--version       Print the version of the application
063 *
064 * Please report issues at https://example.com/issues
065 * </pre>
066 */
067public class HelpFormatter {
068
069    /**
070     * Builds {@link HelpFormatter}.
071     *
072     * @since 1.7.0
073     */
074    public static final class Builder implements Supplier<HelpFormatter> {
075        // TODO All other instance HelpFormatter instance variables.
076        // Make HelpFormatter immutable for 2.0
077
078        /**
079         * A function to convert a description (not null) and a deprecated Option (not null) to help description
080         */
081        private static final Function<Option, String> DEFAULT_DEPRECATED_FORMAT = o -> "[Deprecated] " + getDescription(o);
082
083        /**
084         * Formatter for deprecated options.
085         */
086        private Function<Option, String> deprecatedFormatFunc = DEFAULT_DEPRECATED_FORMAT;
087
088        /**
089         * The output PrintWriter, defaults to wrapping {@link System#out}.
090         */
091        private PrintWriter printStream = createDefaultPrintWriter();
092
093        @Override
094        public HelpFormatter get() {
095            return new HelpFormatter(deprecatedFormatFunc, printStream);
096        }
097
098        /**
099         * Sets the output PrintWriter, defaults to wrapping {@link System#out}.
100         *
101         * @param printWriter the output PrintWriter, not null.
102         * @return {@code this} instance.
103         */
104        public Builder setPrintWriter(final PrintWriter printWriter) {
105            this.printStream = Objects.requireNonNull(printWriter, "printWriter");
106            return this;
107        }
108
109        /**
110         * Sets whether to show deprecated options.
111         *
112         * @param useDefaultFormat if {@code true} use the default format, otherwise clear the formatter.
113         * @return {@code this} instance.
114         */
115        public Builder setShowDeprecated(final boolean useDefaultFormat) {
116            return setShowDeprecated(useDefaultFormat ? DEFAULT_DEPRECATED_FORMAT : null);
117        }
118
119        /**
120         * Sets whether to show deprecated options.
121         *
122         * @param showDeprecatedFunc Specify the format for the deprecated options.
123         * @return {@code this} instance.
124         * @since 1.8.0
125         */
126        public Builder setShowDeprecated(final Function<Option, String> showDeprecatedFunc) {
127            this.deprecatedFormatFunc = showDeprecatedFunc;
128            return this;
129        }
130    }
131
132    /**
133     * This class implements the {@code Comparator} interface for comparing Options.
134     */
135    private static final class OptionComparator implements Comparator<Option>, Serializable {
136
137        /** The serial version UID. */
138        private static final long serialVersionUID = 5305467873966684014L;
139
140        /**
141         * Compares its two arguments for order. Returns a negative integer, zero, or a positive integer as the first argument
142         * is less than, equal to, or greater than the second.
143         *
144         * @param opt1 The first Option to be compared.
145         * @param opt2 The second Option to be compared.
146         * @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than
147         *         the second.
148         */
149        @Override
150        public int compare(final Option opt1, final Option opt2) {
151            return opt1.getKey().compareToIgnoreCase(opt2.getKey());
152        }
153    }
154    /** Default number of characters per line */
155    public static final int DEFAULT_WIDTH = 74;
156
157    /** Default padding to the left of each line */
158    public static final int DEFAULT_LEFT_PAD = 1;
159
160    /** Number of space characters to be prefixed to each description line */
161    public static final int DEFAULT_DESC_PAD = 3;
162
163    /** The string to display at the beginning of the usage statement */
164    public static final String DEFAULT_SYNTAX_PREFIX = "usage: ";
165
166    /** Default prefix for shortOpts */
167    public static final String DEFAULT_OPT_PREFIX = "-";
168
169    /** Default prefix for long Option */
170    public static final String DEFAULT_LONG_OPT_PREFIX = "--";
171
172    /**
173     * Default separator displayed between a long Option and its value
174     *
175     * @since 1.3
176     */
177    public static final String DEFAULT_LONG_OPT_SEPARATOR = " ";
178
179    /** Default name for an argument */
180    public static final String DEFAULT_ARG_NAME = "arg";
181
182    /**
183     * Creates a new builder.
184     *
185     * @return a new builder.
186     * @since 1.7.0
187     */
188    public static Builder builder() {
189        return new Builder();
190    }
191
192    private static PrintWriter createDefaultPrintWriter() {
193        return new PrintWriter(System.out);
194    }
195
196    /**
197     * Gets the option description or an empty string if the description is {@code null}.
198     * @param option The option to get the description from.
199     * @return the option description or an empty string if the description is {@code null}.
200     * @since 1.8.0
201     */
202    public static String getDescription(final Option option) {
203        final String desc = option.getDescription();
204        return desc == null ? "" : desc;
205    }
206
207    /**
208     * Number of characters per line
209     *
210     * @deprecated Scope will be made private for next major version - use get/setWidth methods instead.
211     */
212    @Deprecated
213    public int defaultWidth = DEFAULT_WIDTH;
214
215    /**
216     * Amount of padding to the left of each line
217     *
218     * @deprecated Scope will be made private for next major version - use get/setLeftPadding methods instead.
219     */
220    @Deprecated
221    public int defaultLeftPad = DEFAULT_LEFT_PAD;
222
223    /**
224     * The number of characters of padding to be prefixed to each description line
225     *
226     * @deprecated Scope will be made private for next major version - use get/setDescPadding methods instead.
227     */
228    @Deprecated
229    public int defaultDescPad = DEFAULT_DESC_PAD;
230
231    /**
232     * The string to display at the beginning of the usage statement
233     *
234     * @deprecated Scope will be made private for next major version - use get/setSyntaxPrefix methods instead.
235     */
236    @Deprecated
237    public String defaultSyntaxPrefix = DEFAULT_SYNTAX_PREFIX;
238
239    /**
240     * The new line string
241     *
242     * @deprecated Scope will be made private for next major version - use get/setNewLine methods instead.
243     */
244    @Deprecated
245    public String defaultNewLine = System.lineSeparator();
246
247    /**
248     * The shortOpt prefix
249     *
250     * @deprecated Scope will be made private for next major version - use get/setOptPrefix methods instead.
251     */
252    @Deprecated
253    public String defaultOptPrefix = DEFAULT_OPT_PREFIX;
254
255    /**
256     * The long Opt prefix
257     *
258     * @deprecated Scope will be made private for next major version - use get/setLongOptPrefix methods instead.
259     */
260    @Deprecated
261    public String defaultLongOptPrefix = DEFAULT_LONG_OPT_PREFIX;
262
263    /**
264     * The name of the argument
265     *
266     * @deprecated Scope will be made private for next major version - use get/setArgName methods instead.
267     */
268    @Deprecated
269    public String defaultArgName = DEFAULT_ARG_NAME;
270
271    /**
272     * Comparator used to sort the options when they output in help text
273     *
274     * Defaults to case-insensitive alphabetical sorting by option key
275     */
276    protected Comparator<Option> optionComparator = new OptionComparator();
277
278    /**
279     * Function to format the description for a deprecated option.
280     */
281    private final Function<Option, String> deprecatedFormatFunc;
282
283    /**
284     * Where to print help.
285     */
286    private final PrintWriter printWriter;
287
288    /**
289     * The separator displayed between the long option and its value.
290     */
291    private String longOptSeparator = DEFAULT_LONG_OPT_SEPARATOR;
292
293    /**
294     * Constructs a new instance.
295     */
296    public HelpFormatter() {
297        this(null, createDefaultPrintWriter());
298    }
299
300    /**
301     * Constructs a new instance.
302     * @param printStream TODO
303     */
304    private HelpFormatter(final Function<Option, String> deprecatedFormatFunc, final PrintWriter printStream) {
305        // TODO All other instance HelpFormatter instance variables.
306        // Make HelpFormatter immutable for 2.0
307        this.deprecatedFormatFunc = deprecatedFormatFunc;
308        this.printWriter = printStream;
309    }
310
311    /**
312     * Appends the usage clause for an Option to a StringBuffer.
313     *
314     * @param buff the StringBuffer to append to
315     * @param option the Option to append
316     * @param required whether the Option is required or not
317     */
318    private void appendOption(final StringBuffer buff, final Option option, final boolean required) {
319        if (!required) {
320            buff.append("[");
321        }
322        if (option.getOpt() != null) {
323            buff.append("-").append(option.getOpt());
324        } else {
325            buff.append("--").append(option.getLongOpt());
326        }
327        // if the Option has a value and a non blank argname
328        if (option.hasArg() && (option.getArgName() == null || !option.getArgName().isEmpty())) {
329            buff.append(option.getOpt() == null ? longOptSeparator : " ");
330            buff.append("<").append(option.getArgName() != null ? option.getArgName() : getArgName()).append(">");
331        }
332        // if the Option is not a required option
333        if (!required) {
334            buff.append("]");
335        }
336    }
337
338    /**
339     * Appends the usage clause for an OptionGroup to a StringBuffer. The clause is wrapped in square brackets if the group
340     * is required. The display of the options is handled by appendOption
341     *
342     * @param buff the StringBuffer to append to
343     * @param group the group to append
344     * @see #appendOption(StringBuffer,Option,boolean)
345     */
346    private void appendOptionGroup(final StringBuffer buff, final OptionGroup group) {
347        if (!group.isRequired()) {
348            buff.append("[");
349        }
350
351        final List<Option> optList = new ArrayList<>(group.getOptions());
352        if (getOptionComparator() != null) {
353            Collections.sort(optList, getOptionComparator());
354        }
355        // for each option in the OptionGroup
356        for (final Iterator<Option> it = optList.iterator(); it.hasNext();) {
357            // whether the option is required or not is handled at group level
358            appendOption(buff, it.next(), true);
359
360            if (it.hasNext()) {
361                buff.append(" | ");
362            }
363        }
364
365        if (!group.isRequired()) {
366            buff.append("]");
367        }
368    }
369
370    /**
371     * Creates a String of padding of length {@code len}.
372     *
373     * @param len The length of the String of padding to create.
374     *
375     * @return The String of padding
376     */
377    protected String createPadding(final int len) {
378        final char[] padding = new char[len];
379        Arrays.fill(padding, ' ');
380        return new String(padding);
381    }
382
383    /**
384     * Finds the next text wrap position after {@code startPos} for the text in {@code text} with the column width
385     * {@code width}. The wrap point is the last position before startPos+width having a whitespace character (space,
386     * \n, \r). If there is no whitespace character before startPos+width, it will return startPos+width.
387     *
388     * @param text The text being searched for the wrap position
389     * @param width width of the wrapped text
390     * @param startPos position from which to start the lookup whitespace character
391     * @return position on which the text must be wrapped or -1 if the wrap position is at the end of the text
392     */
393    protected int findWrapPos(final String text, final int width, final int startPos) {
394        // the line ends before the max wrap pos or a new line char found
395        int pos = text.indexOf(Char.LF, startPos);
396        if (pos != -1 && pos <= width) {
397            return pos + 1;
398        }
399        pos = text.indexOf(Char.TAB, startPos);
400        if (pos != -1 && pos <= width) {
401            return pos + 1;
402        }
403        if (startPos + width >= text.length()) {
404            return -1;
405        }
406        // look for the last whitespace character before startPos+width
407        for (pos = startPos + width; pos >= startPos; --pos) {
408            final char c = text.charAt(pos);
409            if (c == Char.SP || c == Char.LF || c == Char.CR) {
410                break;
411            }
412        }
413        // if we found it - just return
414        if (pos > startPos) {
415            return pos;
416        }
417        // if we didn't find one, simply chop at startPos+width
418        pos = startPos + width;
419
420        return pos == text.length() ? -1 : pos;
421    }
422
423    /**
424     * Gets the 'argName'.
425     *
426     * @return the 'argName'
427     */
428    public String getArgName() {
429        return defaultArgName;
430    }
431
432    /**
433     * Gets the 'descPadding'.
434     *
435     * @return the 'descPadding'
436     */
437    public int getDescPadding() {
438        return defaultDescPad;
439    }
440
441    /**
442     * Gets the 'leftPadding'.
443     *
444     * @return the 'leftPadding'
445     */
446    public int getLeftPadding() {
447        return defaultLeftPad;
448    }
449
450    /**
451     * Gets the 'longOptPrefix'.
452     *
453     * @return the 'longOptPrefix'
454     */
455    public String getLongOptPrefix() {
456        return defaultLongOptPrefix;
457    }
458
459    /**
460     * Gets the separator displayed between a long option and its value.
461     *
462     * @return the separator
463     * @since 1.3
464     */
465    public String getLongOptSeparator() {
466        return longOptSeparator;
467    }
468
469    /**
470     * Gets the 'newLine'.
471     *
472     * @return the 'newLine'
473     */
474    public String getNewLine() {
475        return defaultNewLine;
476    }
477
478    /**
479     * Comparator used to sort the options when they output in help text. Defaults to case-insensitive alphabetical sorting
480     * by option key.
481     *
482     * @return the {@link Comparator} currently in use to sort the options
483     * @since 1.2
484     */
485    public Comparator<Option> getOptionComparator() {
486        return optionComparator;
487    }
488
489    /**
490     * Gets the 'optPrefix'.
491     *
492     * @return the 'optPrefix'
493     */
494    public String getOptPrefix() {
495        return defaultOptPrefix;
496    }
497
498    /**
499     * Gets the 'syntaxPrefix'.
500     *
501     * @return the 'syntaxPrefix'
502     */
503    public String getSyntaxPrefix() {
504        return defaultSyntaxPrefix;
505    }
506
507    /**
508     * Gets the 'width'.
509     *
510     * @return the 'width'
511     */
512    public int getWidth() {
513        return defaultWidth;
514    }
515
516    /**
517     * Prints the help for {@code options} with the specified command line syntax. This method prints help information
518     * to  {@link System#out}  by default.
519     *
520     * @param width the number of characters to be displayed on each line
521     * @param cmdLineSyntax the syntax for this application
522     * @param header the banner to display at the beginning of the help
523     * @param options the Options instance
524     * @param footer the banner to display at the end of the help
525     */
526    public void printHelp(final int width, final String cmdLineSyntax, final String header, final Options options, final String footer) {
527        printHelp(width, cmdLineSyntax, header, options, footer, false);
528    }
529
530    /**
531     * Prints the help for {@code options} with the specified command line syntax. This method prints help information
532     * to {@link System#out} by default.
533     *
534     * @param width the number of characters to be displayed on each line
535     * @param cmdLineSyntax the syntax for this application
536     * @param header the banner to display at the beginning of the help
537     * @param options the Options instance
538     * @param footer the banner to display at the end of the help
539     * @param autoUsage whether to print an automatically generated usage statement
540     */
541    public void printHelp(final int width, final String cmdLineSyntax, final String header, final Options options, final String footer,
542        final boolean autoUsage) {
543        final PrintWriter pw = new PrintWriter(printWriter);
544        printHelp(pw, width, cmdLineSyntax, header, options, getLeftPadding(), getDescPadding(), footer, autoUsage);
545        pw.flush();
546    }
547
548    /**
549     * Prints the help for {@code options} with the specified command line syntax.
550     *
551     * @param pw the writer to which the help will be written
552     * @param width the number of characters to be displayed on each line
553     * @param cmdLineSyntax the syntax for this application
554     * @param header the banner to display at the beginning of the help
555     * @param options the Options instance
556     * @param leftPad the number of characters of padding to be prefixed to each line
557     * @param descPad the number of characters of padding to be prefixed to each description line
558     * @param footer the banner to display at the end of the help
559     *
560     * @throws IllegalStateException if there is no room to print a line
561     */
562    public void printHelp(final PrintWriter pw, final int width, final String cmdLineSyntax, final String header, final Options options, final int leftPad,
563        final int descPad, final String footer) {
564        printHelp(pw, width, cmdLineSyntax, header, options, leftPad, descPad, footer, false);
565    }
566
567    /**
568     * Prints the help for {@code options} with the specified command line syntax.
569     *
570     * @param pw the writer to which the help will be written
571     * @param width the number of characters to be displayed on each line
572     * @param cmdLineSyntax the syntax for this application
573     * @param header the banner to display at the beginning of the help
574     * @param options the Options instance
575     * @param leftPad the number of characters of padding to be prefixed to each line
576     * @param descPad the number of characters of padding to be prefixed to each description line
577     * @param footer the banner to display at the end of the help
578     * @param autoUsage whether to print an automatically generated usage statement
579     * @throws IllegalStateException if there is no room to print a line
580     */
581    public void printHelp(final PrintWriter pw, final int width, final String cmdLineSyntax, final String header, final Options options, final int leftPad,
582        final int descPad, final String footer, final boolean autoUsage) {
583        if (Util.isEmpty(cmdLineSyntax)) {
584            throw new IllegalArgumentException("cmdLineSyntax not provided");
585        }
586        if (autoUsage) {
587            printUsage(pw, width, cmdLineSyntax, options);
588        } else {
589            printUsage(pw, width, cmdLineSyntax);
590        }
591        if (header != null && !header.isEmpty()) {
592            printWrapped(pw, width, header);
593        }
594        printOptions(pw, width, options, leftPad, descPad);
595        if (footer != null && !footer.isEmpty()) {
596            printWrapped(pw, width, footer);
597        }
598    }
599
600    /**
601     * Prints the help for {@code options} with the specified command line syntax. This method prints help information
602     * to {@link System#out} by default.
603     *
604     * @param cmdLineSyntax the syntax for this application
605     * @param options the Options instance
606     */
607    public void printHelp(final String cmdLineSyntax, final Options options) {
608        printHelp(getWidth(), cmdLineSyntax, null, options, null, false);
609    }
610
611    /**
612     * Prints the help for {@code options} with the specified command line syntax. This method prints help information
613     * to {@link System#out} by default.
614     *
615     * @param cmdLineSyntax the syntax for this application
616     * @param options the Options instance
617     * @param autoUsage whether to print an automatically generated usage statement
618     */
619    public void printHelp(final String cmdLineSyntax, final Options options, final boolean autoUsage) {
620        printHelp(getWidth(), cmdLineSyntax, null, options, null, autoUsage);
621    }
622
623    /**
624     * Prints the help for {@code options} with the specified command line syntax. This method prints help information
625     * to {@link System#out} by default.
626     *
627     * @param cmdLineSyntax the syntax for this application
628     * @param header the banner to display at the beginning of the help
629     * @param options the Options instance
630     * @param footer the banner to display at the end of the help
631     */
632    public void printHelp(final String cmdLineSyntax, final String header, final Options options, final String footer) {
633        printHelp(cmdLineSyntax, header, options, footer, false);
634    }
635
636    /**
637     * Prints the help for {@code options} with the specified command line syntax. This method prints help information
638     * to {@link System#out} by default.
639     *
640     * @param cmdLineSyntax the syntax for this application
641     * @param header the banner to display at the beginning of the help
642     * @param options the Options instance
643     * @param footer the banner to display at the end of the help
644     * @param autoUsage whether to print an automatically generated usage statement
645     */
646    public void printHelp(final String cmdLineSyntax, final String header, final Options options, final String footer, final boolean autoUsage) {
647        printHelp(getWidth(), cmdLineSyntax, header, options, footer, autoUsage);
648    }
649
650    /**
651     * Prints the help for the specified Options to the specified writer, using the specified width, left padding and
652     * description padding.
653     *
654     * @param pw The printWriter to write the help to
655     * @param width The number of characters to display per line
656     * @param options The command line Options
657     * @param leftPad the number of characters of padding to be prefixed to each line
658     * @param descPad the number of characters of padding to be prefixed to each description line
659     */
660    public void printOptions(final PrintWriter pw, final int width, final Options options, final int leftPad, final int descPad) {
661        final StringBuffer sb = new StringBuffer();
662        renderOptions(sb, width, options, leftPad, descPad);
663        pw.println(sb.toString());
664    }
665
666    /**
667     * Prints the cmdLineSyntax to the specified writer, using the specified width.
668     *
669     * @param pw The printWriter to write the help to
670     * @param width The number of characters per line for the usage statement.
671     * @param cmdLineSyntax The usage statement.
672     */
673    public void printUsage(final PrintWriter pw, final int width, final String cmdLineSyntax) {
674        final int argPos = cmdLineSyntax.indexOf(' ') + 1;
675        printWrapped(pw, width, getSyntaxPrefix().length() + argPos, getSyntaxPrefix() + cmdLineSyntax);
676    }
677
678    /**
679     * Prints the usage statement for the specified application.
680     *
681     * @param pw The PrintWriter to print the usage statement
682     * @param width The number of characters to display per line
683     * @param app The application name
684     * @param options The command line Options
685     */
686    public void printUsage(final PrintWriter pw, final int width, final String app, final Options options) {
687        // initialize the string buffer
688        final StringBuffer buff = new StringBuffer(getSyntaxPrefix()).append(app).append(Char.SP);
689        // create a list for processed option groups
690        final Collection<OptionGroup> processedGroups = new ArrayList<>();
691        final List<Option> optList = new ArrayList<>(options.getOptions());
692        if (getOptionComparator() != null) {
693            Collections.sort(optList, getOptionComparator());
694        }
695        // iterate over the options
696        for (final Iterator<Option> it = optList.iterator(); it.hasNext();) {
697            // get the next Option
698            final Option option = it.next();
699            // check if the option is part of an OptionGroup
700            final OptionGroup group = options.getOptionGroup(option);
701            // if the option is part of a group
702            if (group != null) {
703                // and if the group has not already been processed
704                if (!processedGroups.contains(group)) {
705                    // add the group to the processed list
706                    processedGroups.add(group);
707                    // add the usage clause
708                    appendOptionGroup(buff, group);
709                }
710                // otherwise the option was displayed in the group
711                // previously so ignore it.
712            }
713            // if the Option is not part of an OptionGroup
714            else {
715                appendOption(buff, option, option.isRequired());
716            }
717            if (it.hasNext()) {
718                buff.append(Char.SP);
719            }
720        }
721
722        // call printWrapped
723        printWrapped(pw, width, buff.toString().indexOf(' ') + 1, buff.toString());
724    }
725
726    /**
727     * Prints the specified text to the specified PrintWriter.
728     *
729     * @param pw The printWriter to write the help to
730     * @param width The number of characters to display per line
731     * @param nextLineTabStop The position on the next line for the first tab.
732     * @param text The text to be written to the PrintWriter
733     */
734    public void printWrapped(final PrintWriter pw, final int width, final int nextLineTabStop, final String text) {
735        final StringBuffer sb = new StringBuffer(text.length());
736        renderWrappedTextBlock(sb, width, nextLineTabStop, text);
737        pw.println(sb.toString());
738    }
739
740    /**
741     * Prints the specified text to the specified PrintWriter.
742     *
743     * @param pw The printWriter to write the help to
744     * @param width The number of characters to display per line
745     * @param text The text to be written to the PrintWriter
746     */
747    public void printWrapped(final PrintWriter pw, final int width, final String text) {
748        printWrapped(pw, width, 0, text);
749    }
750
751    /**
752     * Renders the specified Options and return the rendered Options in a StringBuffer.
753     *
754     * @param sb The StringBuffer to place the rendered Options into.
755     * @param width The number of characters to display per line
756     * @param options The command line Options
757     * @param leftPad the number of characters of padding to be prefixed to each line
758     * @param descPad the number of characters of padding to be prefixed to each description line
759     *
760     * @return the StringBuffer with the rendered Options contents.
761     */
762    protected StringBuffer renderOptions(final StringBuffer sb, final int width, final Options options, final int leftPad, final int descPad) {
763        final String lpad = createPadding(leftPad);
764        final String dpad = createPadding(descPad);
765        // first create list containing only <lpad>-a,--aaa where
766        // -a is opt and --aaa is long opt; in parallel look for
767        // the longest opt string this list will be then used to
768        // sort options ascending
769        int max = 0;
770        final List<StringBuffer> prefixList = new ArrayList<>();
771        final List<Option> optList = options.helpOptions();
772        if (getOptionComparator() != null) {
773            Collections.sort(optList, getOptionComparator());
774        }
775        for (final Option option : optList) {
776            final StringBuffer optBuf = new StringBuffer();
777            if (option.getOpt() == null) {
778                optBuf.append(lpad).append("   ").append(getLongOptPrefix()).append(option.getLongOpt());
779            } else {
780                optBuf.append(lpad).append(getOptPrefix()).append(option.getOpt());
781                if (option.hasLongOpt()) {
782                    optBuf.append(',').append(getLongOptPrefix()).append(option.getLongOpt());
783                }
784            }
785            if (option.hasArg()) {
786                final String argName = option.getArgName();
787                if (argName != null && argName.isEmpty()) {
788                    // if the option has a blank argname
789                    optBuf.append(' ');
790                } else {
791                    optBuf.append(option.hasLongOpt() ? longOptSeparator : " ");
792                    optBuf.append("<").append(argName != null ? option.getArgName() : getArgName()).append(">");
793                }
794            }
795            prefixList.add(optBuf);
796            max = Math.max(optBuf.length(), max);
797        }
798        int x = 0;
799        for (final Iterator<Option> it = optList.iterator(); it.hasNext();) {
800            final Option option = it.next();
801            final StringBuilder optBuf = new StringBuilder(prefixList.get(x++).toString());
802            if (optBuf.length() < max) {
803                optBuf.append(createPadding(max - optBuf.length()));
804            }
805            optBuf.append(dpad);
806            final int nextLineTabStop = max + descPad;
807            if (deprecatedFormatFunc != null && option.isDeprecated()) {
808                optBuf.append(deprecatedFormatFunc.apply(option).trim());
809            } else if (option.getDescription() != null) {
810                optBuf.append(option.getDescription());
811            }
812            renderWrappedText(sb, width, nextLineTabStop, optBuf.toString());
813            if (it.hasNext()) {
814                sb.append(getNewLine());
815            }
816        }
817        return sb;
818    }
819
820    /**
821     * Renders the specified text and return the rendered Options in a StringBuffer.
822     *
823     * @param sb The StringBuffer to place the rendered text into.
824     * @param width The number of characters to display per line
825     * @param nextLineTabStop The position on the next line for the first tab.
826     * @param text The text to be rendered.
827     *
828     * @return the StringBuffer with the rendered Options contents.
829     */
830    protected StringBuffer renderWrappedText(final StringBuffer sb, final int width, final int nextLineTabStop, final String text) {
831        String render = text;
832        int nextLineTabStopPos = nextLineTabStop;
833        int pos = findWrapPos(render, width, 0);
834        if (pos == -1) {
835            sb.append(rtrim(render));
836            return sb;
837        }
838        sb.append(rtrim(render.substring(0, pos))).append(getNewLine());
839        if (nextLineTabStopPos >= width) {
840            // stops infinite loop happening
841            nextLineTabStopPos = 1;
842        }
843        // all following lines must be padded with nextLineTabStop space characters
844        final String padding = createPadding(nextLineTabStopPos);
845        while (true) {
846            render = padding + render.substring(pos).trim();
847            pos = findWrapPos(render, width, 0);
848            if (pos == -1) {
849                sb.append(render);
850                return sb;
851            }
852            if (render.length() > width && pos == nextLineTabStopPos - 1) {
853                pos = width;
854            }
855            sb.append(rtrim(render.substring(0, pos))).append(getNewLine());
856        }
857    }
858
859    /**
860     * Renders the specified text width a maximum width. This method differs from renderWrappedText by not removing leading
861     * spaces after a new line.
862     *
863     * @param sb The StringBuffer to place the rendered text into.
864     * @param width The number of characters to display per line
865     * @param nextLineTabStop The position on the next line for the first tab.
866     * @param text The text to be rendered.
867     */
868    private Appendable renderWrappedTextBlock(final StringBuffer sb, final int width, final int nextLineTabStop, final String text) {
869        try {
870            final BufferedReader in = new BufferedReader(new StringReader(text));
871            String line;
872            boolean firstLine = true;
873            while ((line = in.readLine()) != null) {
874                if (!firstLine) {
875                    sb.append(getNewLine());
876                } else {
877                    firstLine = false;
878                }
879                renderWrappedText(sb, width, nextLineTabStop, line);
880            }
881        } catch (final IOException e) { // NOPMD
882            // cannot happen
883        }
884        return sb;
885    }
886
887    /**
888     * Removes the trailing whitespace from the specified String.
889     *
890     * @param s The String to remove the trailing padding from.
891     * @return The String of without the trailing padding
892     */
893    protected String rtrim(final String s) {
894        if (Util.isEmpty(s)) {
895            return s;
896        }
897        int pos = s.length();
898        while (pos > 0 && Character.isWhitespace(s.charAt(pos - 1))) {
899            --pos;
900        }
901        return s.substring(0, pos);
902    }
903
904    /**
905     * Sets the 'argName'.
906     *
907     * @param name the new value of 'argName'
908     */
909    public void setArgName(final String name) {
910        this.defaultArgName = name;
911    }
912
913    /**
914     * Sets the 'descPadding'.
915     *
916     * @param padding the new value of 'descPadding'
917     */
918    public void setDescPadding(final int padding) {
919        this.defaultDescPad = padding;
920    }
921
922    /**
923     * Sets the 'leftPadding'.
924     *
925     * @param padding the new value of 'leftPadding'
926     */
927    public void setLeftPadding(final int padding) {
928        this.defaultLeftPad = padding;
929    }
930
931    /**
932     * Sets the 'longOptPrefix'.
933     *
934     * @param prefix the new value of 'longOptPrefix'
935     */
936    public void setLongOptPrefix(final String prefix) {
937        this.defaultLongOptPrefix = prefix;
938    }
939
940    /**
941     * Sets the separator displayed between a long option and its value. Ensure that the separator specified is supported by
942     * the parser used, typically ' ' or '='.
943     *
944     * @param longOptSeparator the separator, typically ' ' or '='.
945     * @since 1.3
946     */
947    public void setLongOptSeparator(final String longOptSeparator) {
948        this.longOptSeparator = longOptSeparator;
949    }
950
951    /**
952     * Sets the 'newLine'.
953     *
954     * @param newline the new value of 'newLine'
955     */
956    public void setNewLine(final String newline) {
957        this.defaultNewLine = newline;
958    }
959
960    /**
961     * Sets the comparator used to sort the options when they output in help text. Passing in a null comparator will keep the
962     * options in the order they were declared.
963     *
964     * @param comparator the {@link Comparator} to use for sorting the options
965     * @since 1.2
966     */
967    public void setOptionComparator(final Comparator<Option> comparator) {
968        this.optionComparator = comparator;
969    }
970
971    /**
972     * Sets the 'optPrefix'.
973     *
974     * @param prefix the new value of 'optPrefix'
975     */
976    public void setOptPrefix(final String prefix) {
977        this.defaultOptPrefix = prefix;
978    }
979
980    /**
981     * Sets the 'syntaxPrefix'.
982     *
983     * @param prefix the new value of 'syntaxPrefix'
984     */
985    public void setSyntaxPrefix(final String prefix) {
986        this.defaultSyntaxPrefix = prefix;
987    }
988
989    /**
990     * Sets the 'width'.
991     *
992     * @param width the new value of 'width'
993     */
994    public void setWidth(final int width) {
995        this.defaultWidth = width;
996    }
997
998}