xref: /OpenGrok/opengrok-indexer/src/main/java/org/opengrok/indexer/util/OptionParser.java (revision 607bcff587eb71fa94e8d6155f7e6119d366d7d5)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * See LICENSE.txt included in this distribution for the specific
9  * language governing permissions and limitations under the License.
10  *
11  * When distributing Covered Code, include this CDDL HEADER in each
12  * file and include the License file at LICENSE.txt.
13  * If applicable, add the following below this CDDL HEADER, with the
14  * fields enclosed by brackets "[]" replaced with your own identifying
15  * information: Portions Copyright [yyyy] [name of copyright owner]
16  *
17  * CDDL HEADER END
18  */
19 
20 /*
21  * Portions Copyright (c) 2017, Steven Haehn.
22  * Portions Copyright (c) 2019, Chris Fraire <cfraire@me.com>.
23  */
24 package org.opengrok.indexer.util;
25 
26 import java.io.PrintWriter;
27 import java.io.StringWriter;
28 import java.io.PrintStream;
29 import java.text.ParseException;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.function.Consumer;
33 import java.util.function.Function;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.HashMap;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
39 
40 /**
41  * OptionParser is a class for command-line option analysis.
42  *
43  * Now that Java 8 has the crucial Lambda and Consumer interfaces, we can
44  * implement a more powerful program option parsing mechanism, ala ruby
45  * OptionParser style.
46  *
47  * Features
48  *   o  An option can have multiple short names (-x) and multiple long
49  *      names (--xyz). Thus, an option that displays a programs usage
50  *      may be available as -h, -?, --help, --usage, and --about.
51  *
52  *   o  An option may be specified as having no argument, an optional
53  *      argument, or a required argument. Arguments may be validated
54  *      against a regular expression pattern or a list of valid values.
55  *
56  *   o  The argument specification and the code to handle it are
57  *      written in the same place. The argument description may consist
58  *      of one or more lines to be used when displaying usage summary.
59  *
60  *   o  The option summary is produced without maintaining strings
61  *      in a separate setting.
62  *
63  *   o  Users are allowed to enter initial substrings for long option
64  *      names as long as there is no ambiguity.
65  *
66  *   o  Supports the ability to coerce command line arguments into objects.
67  *      This class readily supports Boolean (yes/no,true/false,on/off),
68  *      Float, Double, Integer, and String[] (strings separated by comma)
69  *      objects. The programmer may define additional coercions of their own.
70  *
71  * @author Steven Haehn
72  */
73 public class OptionParser {
74 
75     // Used to hold data type converters
76     private static final Map<Class<?>, DataParser> converters = new HashMap<>();
77 
78     static class DataParser {
79         Class<?> dataType;
80         Function<String, Object> converter;
81 
DataParser(Class<?> cls, Function<String, Object> converter)82         DataParser(Class<?> cls, Function<String, Object> converter) {
83             this.dataType = cls;
84             this.converter = converter;
85         }
86     }
87 
88     // Supported internal data type converters.
89     static {
accept(Integer.class, Integer::parseInt)90         accept(Integer.class, Integer::parseInt);
accept(Boolean.class, OptionParser::parseVerity)91         accept(Boolean.class, OptionParser::parseVerity);
accept(Float.class, Float::parseFloat)92         accept(Float.class, Float::parseFloat);
accept(Double.class, Double::parseDouble)93         accept(Double.class, Double::parseDouble);
accept(String[].class, s -> s.split(","))94         accept(String[].class, s -> s.split(","));
95     }
96 
97     // Option object referenced by its name(s)
98     private final Map<String, Option> options;
99 
100     // List of options in order of declaration
101     private final List<Option> optionList;
102 
103     // Keeps track of separator elements placed in option summary
104     private final List<Object> usageSummary;
105 
106     private boolean scanning = false;
107 
108     private String prologue;  // text emitted before option summary
109     private String epilogue;  // text emitted after options summary
110 
111     public class Option {
112 
113         List<String> names;          // option names/aliases
114         String argument;             // argument name for summary
115         String value;                // user entered value for option
116         Class<?> valueType;          // eg. Integer.class, other than String
117         Pattern valuePattern;        // pattern used to accept value
118         List<String> allowedValues;  // list of restricted values
119         Boolean mandatory;           // true/false when argument present
120         StringBuilder description;   // option description for summary
121         Consumer<Object> action;     // code to execute when option encountered
122 
Option()123         public Option() {
124             names = new ArrayList<>();
125         }
126 
addOption(String option, String arg)127         void addOption(String option, String arg) throws IllegalArgumentException {
128             addAlias(option);
129             setArgument(arg);
130         }
131 
addAlias(String alias)132         void addAlias(String alias) throws IllegalArgumentException {
133             names.add(alias);
134 
135             if (options.containsKey(alias)) {
136                 throw new IllegalArgumentException("** Programmer error! Option " + alias + " already defined");
137             }
138 
139             options.put(alias, this);
140         }
141 
setAllowedValues(String[] allowed)142         void setAllowedValues(String[] allowed) {
143             allowedValues = Arrays.asList(allowed);
144         }
145 
setValueType(Class<?> type)146         void setValueType(Class<?> type) {
147             valueType = type;
148         }
149 
setArgument(String arg)150         void setArgument(String arg) {
151             argument = arg.trim();
152             mandatory = !argument.startsWith("[");
153         }
154 
setPattern(String pattern)155         void setPattern(String pattern) {
156             valuePattern = Pattern.compile(pattern);
157         }
158 
159         public static final int MAX_DESCRIPTION_LINE_LENGTH = 80;
160 
addDescription(String description)161         void addDescription(String description) {
162             if (description.length() > MAX_DESCRIPTION_LINE_LENGTH) {
163                 throw new IllegalArgumentException(String.format("description line longer than %d characters: '%s'",
164                         MAX_DESCRIPTION_LINE_LENGTH, description));
165             }
166 
167             if (this.description == null) {
168                 this.description = new StringBuilder();
169             }
170             this.description.append(description);
171             this.description.append("\n");
172         }
173 
174         /**
175          * Code to be activated when option encountered.
176          *
177          * @param action is the code that will be called when the
178          * parser encounters the associated named option in its
179          * argument list.
180          */
execute(Consumer<Object> action)181         public void execute(Consumer<Object> action) {
182             this.action = action;
183         }
184 
getUsage()185         String getUsage() {
186             StringBuilder line = new StringBuilder();
187             String separator = "";
188             for (String name : names) {
189                 line.append(separator);
190                 line.append(name);
191                 separator = ", ";
192             }
193 
194             if (argument != null) {
195                 line.append(' ');
196                 line.append(argument);
197             }
198             line.append("\n");
199             if (description != null) {
200                 line.append("\t");
201                 line.append(description.toString().replaceAll("\\n", "\n\t"));
202             }
203 
204             return line.toString();
205         }
206     }
207 
208     /**
209      * Instantiate a new option parser
210      *
211      * This allows the programmer to create an empty option parser that can
212      * be added to incrementally elsewhere in the program being built. For
213      * example:
214      *
215      *   OptionParser parser = OptionParser();
216      *     .
217      *     .
218      *   parser.prologue = "Usage: program [options] [file [...]]
219      *
220      *   parser.on("-?", "--help", "Display this usage.").execute( v -&gt; {
221      *       parser.help();
222      *   });
223      */
OptionParser()224     public OptionParser() {
225         optionList = new ArrayList<>();
226         options = new HashMap<>();
227         usageSummary = new ArrayList<>();
228     }
229 
230     // Allowable text values for Boolean.class, with case insensitivity.
231     private static final Pattern VERITY = Pattern.compile("(?i)(true|yes|on)");
232     private static final Pattern FALSEHOOD = Pattern.compile("(?i)(false|no|off)");
233 
parseVerity(String text)234     private static Boolean parseVerity(String text) {
235         Matcher m = VERITY.matcher(text);
236         boolean veracity;
237 
238         if (m.matches()) {
239             veracity = true;
240         } else {
241             m = FALSEHOOD.matcher(text);
242             if (m.matches()) {
243                 veracity = false;
244             } else {
245                 throw new IllegalArgumentException();
246             }
247         }
248         return veracity;
249     }
250 
251     /**
252      * Supply parser with data conversion mechanism for option value.
253      * The following is an example usage used internally:
254      *
255      *    accept(Integer.class, s -&gt; { return Integer.parseInt(s); });
256      *
257      * @param type is the internal data class to which an option
258      * value should be converted.
259      *
260      * @param parser is the conversion code that will take the given
261      * option value string and produce the named data type.
262      */
accept(Class<?> type, Function<String, Object> parser)263     public static void accept(Class<?> type, Function<String, Object> parser) {
264         converters.put(type, new DataParser(type, parser));
265     }
266 
267     /**
268      * Instantiate a new options parser and construct option actionable components.
269      *
270      * As an example:
271      *
272      * <code>
273      *   OptionParser opts = OptionParser.execute(parser -&gt; {
274      *
275      *      parser.prologue =
276      *          String.format("\nUsage: %s [options] [subDir1 [...]]\n", program);
277      *
278      *      parser.on("-?", "--help", "Display this usage.").execute( v -&gt; {
279      *          parser.help();
280      *      });
281      *
282      *      parser.epilogue = "That's all folks!";
283      *   }
284      * </code>
285      *
286      * @param parser consumer
287      * @return OptionParser object
288      */
execute(Consumer<OptionParser> parser)289     public static OptionParser execute(Consumer<OptionParser> parser) {
290         OptionParser me = new OptionParser();
291         parser.accept(me);
292         return me;
293     }
294 
295     /**
296      * Provide a 'scanning' option parser.
297      *
298      * This type of parser only operates on the arguments for which it
299      * is constructed. All other arguments passed to it are ignored.
300      * That is, it won't raise any errors for unrecognizable input as
301      * the normal option parser would.
302      *
303      * @param parser consumer
304      * @return OptionParser object
305      */
scan(Consumer<OptionParser> parser)306     public static OptionParser scan(Consumer<OptionParser> parser) {
307         OptionParser me = new OptionParser();
308         parser.accept(me);
309         me.scanning = true;
310         return me;
311     }
312 
313     /**
314      * Construct option recognition and description object
315      *
316      * This method is used to build the option object which holds
317      * its recognition and validation criteria, description and
318      * ultimately its data handler.
319      *
320      * The 'on' parameters consist of formatted strings which provide the
321      * option names, whether or not the option takes on a value and,
322      * if so, the option value type (mandatory/optional). The data type
323      * of the option value may also be provided to allow the parser to
324      * handle conversion from a string to an internally supported data type.
325      *
326      * Other parameters which may be provided are:
327      *
328      *  o String array of legal option values (eg. {"on","off"})
329      *  o Regular expression pattern that option value must match
330      *  o Multiple line description for the option.
331      *
332      * There are two forms of option names, short and long. The short
333      * option names are a single character in length and are recognized
334      * with a single "-" character to the left of the name (eg. -o).
335      * The long option names hold more than a single character and are
336      * recognized via "--" to the left of the name (eg. --option). The
337      * syntax for specifying an option, whether it takes on a value or
338      * not, and whether that value is mandatory or optional is as follows.
339      * (Note, the use of OPT is an abbreviation for OPTIONAL.)
340      *
341      * Short name 'x':
342      *    -x, -xVALUE, -x=VALUE, -x[OPT], -x[=OPT], -x PLACE
343      *
344      * The option has the short name 'x'. The first form has no value.
345      * the next two require values, the next two indicate that the value
346      * is optional (delineated by the '[' character). The last form
347      * indicates that the option must have a value, but that it follows
348      * the option indicator.
349      *
350      * Long name 'switch':
351      *    --switch, --switch=VALUE, --switch=[OPT], --switch PLACE
352      *
353      * The option has the long name 'switch'. The first form indicates
354      * it does not require a value, The second form indicates that the
355      * option requires a value. The third form indicates that option may
356      * or may not have value. The last form indicates that a value is
357      * required, but that it follows the option indicator.
358      *
359      * Since an option may have multiple names (aliases), there is a
360      * short hand for describing those which take on a value.
361      *
362      * Option value shorthand:  =VALUE, =[OPT]
363      *
364      * The first form indicates that the option value is required, the
365      * second form indicates that the value is optional. For example
366      * the following code says there is an option known by the aliases
367      * -a, -b, and -c and that it needs a required value shown as N.
368      *
369      * <code>
370      *     opt.on( "-a", "-b", "-c", "=N" )
371      * </code>
372      *
373      * When an option takes on a value, 'on' may accept a regular expression
374      * indicating what kind of values are acceptable. The regular expression
375      * is indicated by surrounding the expression with '/' character. For
376      * example, "/pattern/" indicates that the only value acceptable is the
377      * word 'pattern'.
378      *
379      * Any string that does not start with a '-', '=', or '/' is used as a
380      * description for the option in the summary. Multiple descriptions may
381      * be given; they will be shown on additional lines.
382      *
383      * For programmers:  If a switch starts with 3 dashes (---) it will
384      * be hidden from the usage summary and manual generation. It is meant
385      * for unit testing access.
386      *
387      * @param args arguments
388      * @return Option
389      */
on(Object... args)390     public Option on(Object... args) {
391 
392         Option opt = new Option();
393 
394         // Once description starts, then no other option settings are eligible.
395         boolean addedDescription = false;
396 
397         for (Object arg : args) {
398             if (arg instanceof String) {
399                 String argument = (String) arg;
400                 if (addedDescription) {
401                     opt.addDescription(argument);
402                 } else if (argument.startsWith("--")) {
403                     // handle --switch --switch=ARG --switch=[OPT] --switch PLACE
404                     String[] parts = argument.split("[ =]");
405 
406                     if (parts.length == 1) {
407                         opt.addAlias(parts[0]);
408                     } else {
409                         opt.addOption(parts[0], parts[1]);
410                     }
411                 } else if (argument.startsWith("-")) {
412                     // handle -x -xARG -x=ARG -x[OPT] -x[=OPT] -x PLACE
413                     String optName = argument.substring(0, 2);
414                     String remainder = argument.substring(2);
415                     opt.addOption(optName, remainder);
416 
417                 } else if (argument.startsWith("=")) {
418                     opt.setArgument(argument.substring(1));
419                 } else if (argument.startsWith("/")) {
420                     // regular expression (sans '/'s)
421                     opt.setPattern(argument.substring(1, argument.length() - 1));
422                 } else {
423                     // this is description
424                     opt.addDescription(argument);
425                     addedDescription = true;
426                 }
427             // This is indicator for a addOption of specific allowable option values
428             } else if (arg instanceof String[]) {
429                 opt.setAllowedValues((String[]) arg);
430             // This is indicator for option value data type
431             // to which the parser will take and convert.
432             } else if (arg instanceof Class) {
433                 opt.setValueType((Class<?>) arg);
434             } else if (arg == null) {
435                 throw new IllegalArgumentException("arg is null");
436             } else {
437                 throw new IllegalArgumentException("Invalid arg: " +
438                         arg.getClass().getSimpleName() + " " + arg);
439             }
440         }
441 
442         // options starting with 3 dashes are to be hidden from usage.
443         // (the idea here is to hide any unit test entries from general user)
444         if (!opt.names.get(0).startsWith("---")) {
445             optionList.add(opt);
446             usageSummary.add(opt);
447         }
448 
449         return opt;
450     }
451 
argValue(String arg, boolean mandatory)452     private String argValue(String arg, boolean mandatory) {
453         // Initially assume that the given argument is going
454         // to be the option's value. Note that if the argument
455         // is actually another option (starts with '-') then
456         // there is no value available. If the option is required
457         // to have a value, null is returned. If the option
458         // does not require a value, an empty string is returned.
459         String value = arg;
460         boolean isOption = value.startsWith("-");
461 
462         if (mandatory) {
463             if (isOption ) {
464                 value = null;
465             }
466         } else if (isOption) {
467             value = "";
468         }
469         return value;
470     }
471 
getOption(String arg, int index)472     private String getOption(String arg, int index) throws ParseException {
473         String option = null;
474 
475         if ( arg.equals("-")) {
476             throw new ParseException("Stand alone '-' found in arguments, not allowed", index);
477         }
478 
479         if (arg.startsWith("-")) {
480             if (arg.startsWith("--")) {
481                 option = arg;                 // long name option (--longOption)
482             } else if (arg.length() > 2) {
483                 option = arg.substring(0, 2); // short name option (-xValue)
484             } else {
485                 option = arg;                 // short name option (-x)
486             }
487         }
488         return option;
489     }
490 
491     /**
492      * Discover full name of partial option name.
493      *
494      * @param option is the initial substring of a long option name.
495      * @param index into original argument list (only used by ParseException)
496      * @return full name of given option substring, or null when not found.
497      * @throws ParseException when more than one candidate name is found.
498      */
candidate(String option, int index)499     protected String candidate(String option, int index) throws ParseException {
500         boolean found = options.containsKey(option);
501         List<String> candidates = new ArrayList<>();
502         String candidate = null;
503 
504         if (found) {
505             candidate = option;
506         } else {
507             // Now check to see if initial substring was entered.
508             for (String key: options.keySet()) {
509                 if (key.startsWith(option)) {
510                     candidates.add(key);
511                 }
512             }
513             if (candidates.size() == 1 ) {
514                 candidate = candidates.get(0);
515             } else if (candidates.size() > 1) {
516                 throw new ParseException(
517                     "Ambiguous option " + option + " matches " + candidates, index);
518             }
519         }
520         return candidate;
521     }
522 
523     /**
524      * Parse given set of arguments and activate handlers
525      *
526      * This code parses the given set of parameters looking for a described
527      * set of options and activates the code segments associated with the
528      * option.
529      *
530      * Parsing is discontinued when a lone "--" is encountered in the list of
531      * arguments. If this is a normal non-scan parser, unrecognized options
532      * will cause a parse exception. If this is a scan parser, unrecognized
533      * options are ignored.
534      *
535      * @param args argument vector
536      * @return non-option parameters, or all arguments after "--" encountered.
537      * @throws ParseException parse exception
538      */
539 
parse(String[] args)540     public String[] parse(String[] args) throws ParseException {
541         int ii = 0;
542         int optind = -1;
543         String option;
544         while (ii < args.length) {
545             option = getOption(args[ii], ii);
546 
547             // When scanning for specific options...
548             if (scanning) {
549                 if (option == null || (option = candidate(option, ii)) == null) {
550                     optind = ++ii;  // skip over everything else
551                     continue;
552                 }
553             }
554 
555             if (option == null) {  // no more options? we be done.
556                 break;
557             } else if (option.equals("--")) {  // parsing escape found? we be done.
558                 optind = ii + 1;
559                 break;
560             } else {
561 
562                 if ( !scanning ) {
563                     String candidate = candidate(option, ii);
564                     if (candidate != null) {
565                         option = candidate;
566                     } else {
567                         throw new ParseException("Unknown option: " + option, ii);
568                     }
569                 }
570                 Option opt = options.get(option);
571                 opt.value = null;
572 
573                 if (option.length() == 2 && !option.equals(args[ii])) {  // catches -xValue
574                     opt.value = args[ii].substring(2);
575                 }
576 
577                 // No argument required?
578                 if (opt.argument == null || opt.argument.equals("")) {
579                     if (opt.value != null) {
580                         throw new ParseException("Option " + option + " does not use value.", ii);
581                     }
582                     opt.value = "";
583 
584                 // Argument specified but value not yet acquired
585                 } else if (opt.value == null) {
586 
587                     ii++;   // next argument may hold argument value
588 
589                     // When option is last in list...
590                     if (ii >= args.length) {
591                         if (!opt.mandatory) {
592                             opt.value = "";  // indicate this option's value was optional
593                         }
594                     } else {
595 
596                         // Look at next argument for value
597                         opt.value = argValue(args[ii], opt.mandatory);
598 
599                         if (opt.value != null && opt.value.equals("")) {
600                             // encountered another option so this
601                             // option's value was not required. Backup
602                             // argument list index to handle so loop
603                             // can re-examine this option.
604                             ii--;
605                         }
606                     }
607                 }
608 
609                 // If there is no value setting for the
610                 // option by now, throw a hissy fit.
611                 if (opt.value == null) {
612                     throw new ParseException("Option " + option + " requires a value.", ii);
613                 }
614 
615                 // Only specific values allowed?
616                 if (opt.allowedValues != null) {
617                     if (!opt.allowedValues.contains(opt.value)) {
618                         throw new ParseException(
619                            "'" + opt.value +
620                            "' is unknown value for option " + opt.names +
621                            ". Must be one of " + opt.allowedValues, ii);
622                     }
623                 }
624 
625                 Object value = opt.value;
626 
627                 // Should option argument match some pattern?
628                 if (opt.valuePattern != null) {
629                     Matcher m = opt.valuePattern.matcher(opt.value);
630                     if (!m.matches()) {
631                         throw new ParseException(
632                            "Value '" + opt.value + "' for option " + opt.names + opt.argument +
633                            "\n does not match pattern " + opt.valuePattern, ii);
634                     }
635 
636                 // Handle special conversions of input
637                 // arguments before sending to action handler.
638                 } else if (opt.valueType != null) {
639 
640                     if (!converters.containsKey(opt.valueType)) {
641                         throw new ParseException(
642                             "No conversion handler for data type " + opt.valueType, ii);
643                     }
644 
645                     try {
646                         DataParser data = converters.get(opt.valueType);
647                         value = data.converter.apply(opt.value);
648 
649                     } catch (Exception e) {
650                         System.err.println("** " + e.getMessage());
651                         throw new ParseException("Failed to parse (" + opt.value + ") as value of " + opt.names, ii);
652                     }
653                 }
654 
655                 if (opt.action != null) {
656                     opt.action.accept(value); // 'do' assigned action
657                 }
658                 optind = ++ii;
659             }
660         }
661 
662         // Prepare to gather any remaining arguments
663         // to send back to calling program.
664 
665         String[] remainingArgs = null;
666 
667         if (optind == -1) {
668             remainingArgs = args;
669         } else if (optind < args.length) {
670             remainingArgs = Arrays.copyOfRange(args, optind, args.length);
671         } else {
672             remainingArgs = new String[0];  // all args used up, send back empty.
673         }
674 
675         return remainingArgs;
676     }
677 
getPrologue()678     private String getPrologue() {
679         // Assign default prologue statement when none given.
680         if (prologue == null) {
681             prologue = "Usage: MyProgram [options]";
682         }
683 
684         return prologue;
685     }
686 
687 
688     /**
689      * Define the prologue to be presented before the options summary.
690      * Example: Usage programName [options]
691      * @param text that makes up the prologue.
692      */
setPrologue(String text)693     public void setPrologue(String text) {
694         prologue = text;
695     }
696 
697     /**
698      * Define the epilogue to be presented after the options summary.
699      * @param text that makes up the epilogue.
700      */
setEpilogue(String text)701     public void setEpilogue(String text) {
702         epilogue = text;
703     }
704 
705     /**
706      * Place text in option summary.
707      * @param text to be inserted into option summary.
708      *
709      * Example usage:
710      * <code>
711      *  OptionParser opts = OptionParser.execute( parser -&gt; {
712      *
713      *    parser.prologue = String.format("Usage: %s [options] bubba smith", program);
714      *    parser.separator("");
715      *
716      *    parser.on("-y value", "--why me", "This is a description").execute( v -&gt; {
717      *        System.out.println("got " + v);
718      *    });
719      *
720      *    parser.separator("  ----------------------------------------------");
721      *    parser.separator("  Common Options:");
722      *    ...
723      *
724      *    parser.separator("  ----------------------------------------------");
725      *    parser.epilogue = "  That's all Folks!";
726      * </code>
727      */
separator(String text)728     public void separator(String text) {
729         usageSummary.add(text);
730     }
731 
732     /**
733      * Obtain option summary.
734      * @param indent a string to be used as the option summary initial indent.
735      * @return usage string
736      */
getUsage(String indent)737     public String getUsage(String indent) {
738 
739         StringWriter wrt = new StringWriter();
740         try (PrintWriter out = new PrintWriter(wrt)) {
741             out.println(getPrologue());
742             for (Object o : usageSummary) {
743                 // Need to be able to handle separator strings
744                 if (o instanceof String) {
745                     out.println((String) o);
746                 } else {
747                     out.println(indent + ((Option) o).getUsage());
748                 }
749             }
750             if (epilogue != null) {
751                 out.println(epilogue);
752             }
753             out.flush();
754         }
755         return wrt.toString();
756     }
757 
758     /**
759      * Obtain option summary.
760      * @return option summary
761      */
getUsage()762     public String getUsage() {
763         return getUsage("  ");
764     }
765 
766     /**
767      * Print out option summary.
768      */
help()769     public void help() {
770         System.out.println(getUsage());
771     }
772 
773     /**
774      * Print out option summary on provided output stream.
775      * @param out print stream
776      */
help(PrintStream out)777     public void help(PrintStream out) {
778         out.println(getUsage());
779     }
780 
getOptionList()781     protected List<Option> getOptionList() {
782         return optionList;
783     }
784 }
785