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