• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

oracle / opengrok / #3715

30 Nov 2023 08:55AM UTC coverage: 75.937% (+9.8%) from 66.106%
#3715

push

web-flow
Refactoring to reduce sonar code smell fixes (#4485)

---------

Signed-off-by: Gino Augustine <ginoaugustine@gmail.com>
Co-authored-by: Vladimir Kotal <vlada@kotalovi.cz>

397 of 478 new or added lines in 51 files covered. (83.05%)

50 existing lines in 22 files now uncovered.

44494 of 58593 relevant lines covered (75.94%)

0.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.94
/opengrok-indexer/src/main/java/org/opengrok/indexer/util/OptionParser.java
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
 * <p>
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
 * <p>
47
 * Features
48
 * <p>
49
 *   o  An option can have multiple short names (-x) and multiple long
50
 *      names (--xyz). Thus, an option that displays a programs usage
51
 *      may be available as -h, -?, --help, --usage, and --about.
52
 * <p>
53
 *   o  An option may be specified as having no argument, an optional
54
 *      argument, or a required argument. Arguments may be validated
55
 *      against a regular expression pattern or a list of valid values.
56
 * <p>
57
 *   o  The argument specification and the code to handle it are
58
 *      written in the same place. The argument description may consist
59
 *      of one or more lines to be used when displaying usage summary.
60
 * <p>
61
 *   o  The option summary is produced without maintaining strings
62
 *      in a separate setting.
63
 * <p>
64
 *   o  Users are allowed to enter initial substrings for long option
65
 *      names as long as there is no ambiguity.
66
 * <p>
67
 *   o  Supports the ability to coerce command line arguments into objects.
68
 *      This class readily supports Boolean (yes/no,true/false,on/off),
69
 *      Float, Double, Integer, and String[] (strings separated by comma)
70
 *      objects. The programmer may define additional coercions of their own.
71
 *
72
 * @author Steven Haehn
73
 */
74
public class OptionParser {
75

76
    // Used to hold data type converters
77
    private static final Map<Class<?>, DataParser> converters = new HashMap<>();
1✔
78

79
    static class DataParser {
80
        Class<?> dataType;
81
        Function<String, Object> converter;
82

83
        DataParser(Class<?> cls, Function<String, Object> converter) {
1✔
84
            this.dataType = cls;
1✔
85
            this.converter = converter;
1✔
86
        }
1✔
87
    }
88

89
    // Supported internal data type converters.
90
    static {
91
        accept(Integer.class, Integer::parseInt);
1✔
92
        accept(Boolean.class, OptionParser::parseVerity);
1✔
93
        accept(Float.class, Float::parseFloat);
1✔
94
        accept(Double.class, Double::parseDouble);
1✔
95
        accept(String[].class, s -> s.split(","));
1✔
96
    }
97

98
    // Option object referenced by its name(s)
99
    private final Map<String, Option> options;
100

101
    // List of options in order of declaration
102
    private final List<Option> optionList;
103

104
    // Keeps track of separator elements placed in option summary
105
    private final List<Object> usageSummary;
106

107
    private boolean scanning = false;
1✔
108

109
    private String prologue;  // text emitted before option summary
110
    private String epilogue;  // text emitted after options summary
111

112
    public class Option {
113

114
        List<String> names;          // option names/aliases
115
        String argument;             // argument name for summary
116
        String value;                // user entered value for option
117
        Class<?> valueType;          // eg. Integer.class, other than String
118
        Pattern valuePattern;        // pattern used to accept value
119
        List<String> allowedValues;  // list of restricted values
120
        Boolean mandatory;           // true/false when argument present
121
        StringBuilder description;   // option description for summary
122
        Consumer<Object> action;     // code to execute when option encountered
123

124
        public Option() {
1✔
125
            names = new ArrayList<>();
1✔
126
        }
1✔
127

128
        void addOption(String option, String arg) throws IllegalArgumentException {
129
            addAlias(option);
1✔
130
            setArgument(arg);
1✔
131
        }
1✔
132

133
        void addAlias(String alias) throws IllegalArgumentException {
134
            names.add(alias);
1✔
135

136
            if (options.containsKey(alias)) {
1✔
137
                throw new IllegalArgumentException("** Programmer error! Option " + alias + " already defined");
1✔
138
            }
139

140
            options.put(alias, this);
1✔
141
        }
1✔
142

143
        void setAllowedValues(String[] allowed) {
144
            allowedValues = Arrays.asList(allowed);
1✔
145
        }
1✔
146

147
        void setValueType(Class<?> type) {
148
            valueType = type;
1✔
149
        }
1✔
150

151
        void setArgument(String arg) {
152
            argument = arg.trim();
1✔
153
            mandatory = !argument.startsWith("[");
1✔
154
        }
1✔
155

156
        void setPattern(String pattern) {
157
            valuePattern = Pattern.compile(pattern);
1✔
158
        }
1✔
159

160
        public static final int MAX_DESCRIPTION_LINE_LENGTH = 80;
161

162
        void addDescription(String description) {
163
            if (description.length() > MAX_DESCRIPTION_LINE_LENGTH) {
1✔
164
                throw new IllegalArgumentException(String.format("description line longer than %d characters: '%s'",
1✔
165
                        MAX_DESCRIPTION_LINE_LENGTH, description));
1✔
166
            }
167

168
            if (this.description == null) {
1✔
169
                this.description = new StringBuilder();
1✔
170
            }
171
            this.description.append(description);
1✔
172
            this.description.append("\n");
1✔
173
        }
1✔
174

175
        /**
176
         * Code to be activated when option encountered.
177
         *
178
         * @param action is the code that will be called when the
179
         * parser encounters the associated named option in its
180
         * argument list.
181
         */
182
        public void execute(Consumer<Object> action) {
183
            this.action = action;
1✔
184
        }
1✔
185

186
        String getUsage() {
187
            StringBuilder line = new StringBuilder();
1✔
188
            String separator = "";
1✔
189
            for (String name : names) {
1✔
190
                line.append(separator);
1✔
191
                line.append(name);
1✔
192
                separator = ", ";
1✔
193
            }
1✔
194

195
            if (argument != null) {
1✔
196
                line.append(' ');
×
197
                line.append(argument);
×
198
            }
199
            line.append("\n");
1✔
200
            if (description != null) {
1✔
201
                line.append("\t");
×
NEW
202
                line.append(description.toString().replace("\n", "\n\t"));
×
203
            }
204

205
            return line.toString();
1✔
206
        }
207
    }
208

209
    /**
210
     * Instantiate a new option parser
211
     *
212
     * This allows the programmer to create an empty option parser that can
213
     * be added to incrementally elsewhere in the program being built. For
214
     * example:
215
     *
216
     *   OptionParser parser = OptionParser();
217
     *     .
218
     *     .
219
     *   parser.prologue = "Usage: program [options] [file [...]]
220
     *
221
     *   parser.on("-?", "--help", "Display this usage.").execute( v -&gt; {
222
     *       parser.help();
223
     *   });
224
     */
225
    public OptionParser() {
1✔
226
        optionList = new ArrayList<>();
1✔
227
        options = new HashMap<>();
1✔
228
        usageSummary = new ArrayList<>();
1✔
229
    }
1✔
230

231
    // Allowable text values for Boolean.class, with case insensitivity.
232
    private static final Pattern VERITY = Pattern.compile("(?i)(true|yes|on)");
1✔
233
    private static final Pattern FALSEHOOD = Pattern.compile("(?i)(false|no|off)");
1✔
234

235
    private static Boolean parseVerity(String text) {
236
        Matcher m = VERITY.matcher(text);
1✔
237
        boolean veracity;
238

239
        if (m.matches()) {
1✔
240
            veracity = true;
1✔
241
        } else {
242
            m = FALSEHOOD.matcher(text);
1✔
243
            if (m.matches()) {
1✔
244
                veracity = false;
1✔
245
            } else {
246
                throw new IllegalArgumentException();
1✔
247
            }
248
        }
249
        return veracity;
1✔
250
    }
251

252
    /**
253
     * Supply parser with data conversion mechanism for option value.
254
     * The following is an example usage used internally:
255
     *
256
     *    accept(Integer.class, s -&gt; { return Integer.parseInt(s); });
257
     *
258
     * @param type is the internal data class to which an option
259
     * value should be converted.
260
     *
261
     * @param parser is the conversion code that will take the given
262
     * option value string and produce the named data type.
263
     */
264
    public static void accept(Class<?> type, Function<String, Object> parser) {
265
        converters.put(type, new DataParser(type, parser));
1✔
266
    }
1✔
267

268
    /**
269
     * Instantiate a new options parser and construct option actionable components.
270
     *
271
     * As an example:
272
     *
273
     * <code>
274
     *   OptionParser opts = OptionParser.execute(parser -&gt; {
275
     *
276
     *      parser.prologue =
277
     *          String.format("\nUsage: %s [options] [subDir1 [...]]\n", program);
278
     *
279
     *      parser.on("-?", "--help", "Display this usage.").execute( v -&gt; {
280
     *          parser.help();
281
     *      });
282
     *
283
     *      parser.epilogue = "That's all folks!";
284
     *   }
285
     * </code>
286
     *
287
     * @param parser consumer
288
     * @return OptionParser object
289
     */
290
    public static OptionParser execute(Consumer<OptionParser> parser) {
291
        OptionParser me = new OptionParser();
1✔
292
        parser.accept(me);
1✔
293
        return me;
1✔
294
    }
295

296
    /**
297
     * Provide a 'scanning' option parser.
298
     *
299
     * This type of parser only operates on the arguments for which it
300
     * is constructed. All other arguments passed to it are ignored.
301
     * That is, it won't raise any errors for unrecognizable input as
302
     * the normal option parser would.
303
     *
304
     * @param parser consumer
305
     * @return OptionParser object
306
     */
307
    public static OptionParser scan(Consumer<OptionParser> parser) {
308
        OptionParser me = new OptionParser();
1✔
309
        parser.accept(me);
1✔
310
        me.scanning = true;
1✔
311
        return me;
1✔
312
    }
313

314
    /**
315
     * Construct option recognition and description object
316
     *
317
     * This method is used to build the option object which holds
318
     * its recognition and validation criteria, description and
319
     * ultimately its data handler.
320
     *
321
     * The 'on' parameters consist of formatted strings which provide the
322
     * option names, whether or not the option takes on a value and,
323
     * if so, the option value type (mandatory/optional). The data type
324
     * of the option value may also be provided to allow the parser to
325
     * handle conversion from a string to an internally supported data type.
326
     *
327
     * Other parameters which may be provided are:
328
     *
329
     *  o String array of legal option values (eg. {"on","off"})
330
     *  o Regular expression pattern that option value must match
331
     *  o Multiple line description for the option.
332
     *
333
     * There are two forms of option names, short and long. The short
334
     * option names are a single character in length and are recognized
335
     * with a single "-" character to the left of the name (eg. -o).
336
     * The long option names hold more than a single character and are
337
     * recognized via "--" to the left of the name (eg. --option). The
338
     * syntax for specifying an option, whether it takes on a value or
339
     * not, and whether that value is mandatory or optional is as follows.
340
     * (Note, the use of OPT is an abbreviation for OPTIONAL.)
341
     *
342
     * Short name 'x':
343
     *    -x, -xVALUE, -x=VALUE, -x[OPT], -x[=OPT], -x PLACE
344
     *
345
     * The option has the short name 'x'. The first form has no value.
346
     * the next two require values, the next two indicate that the value
347
     * is optional (delineated by the '[' character). The last form
348
     * indicates that the option must have a value, but that it follows
349
     * the option indicator.
350
     *
351
     * Long name 'switch':
352
     *    --switch, --switch=VALUE, --switch=[OPT], --switch PLACE
353
     *
354
     * The option has the long name 'switch'. The first form indicates
355
     * it does not require a value, The second form indicates that the
356
     * option requires a value. The third form indicates that option may
357
     * or may not have value. The last form indicates that a value is
358
     * required, but that it follows the option indicator.
359
     *
360
     * Since an option may have multiple names (aliases), there is a
361
     * short hand for describing those which take on a value.
362
     *
363
     * Option value shorthand:  =VALUE, =[OPT]
364
     *
365
     * The first form indicates that the option value is required, the
366
     * second form indicates that the value is optional. For example
367
     * the following code says there is an option known by the aliases
368
     * -a, -b, and -c and that it needs a required value shown as N.
369
     *
370
     * <code>
371
     *     opt.on( "-a", "-b", "-c", "=N" )
372
     * </code>
373
     *
374
     * When an option takes on a value, 'on' may accept a regular expression
375
     * indicating what kind of values are acceptable. The regular expression
376
     * is indicated by surrounding the expression with '/' character. For
377
     * example, "/pattern/" indicates that the only value acceptable is the
378
     * word 'pattern'.
379
     *
380
     * Any string that does not start with a '-', '=', or '/' is used as a
381
     * description for the option in the summary. Multiple descriptions may
382
     * be given; they will be shown on additional lines.
383
     *
384
     * For programmers:  If a switch starts with 3 dashes (---) it will
385
     * be hidden from the usage summary and manual generation. It is meant
386
     * for unit testing access.
387
     *
388
     * @param args arguments
389
     * @return Option
390
     */
391
    public Option on(Object... args) {
392

393
        Option opt = new Option();
1✔
394

395
        // Once description starts, then no other option settings are eligible.
396
        boolean addedDescription = false;
1✔
397

398
        for (Object arg : args) {
1✔
399
            if (arg instanceof String) {
1✔
400
                String argument = (String) arg;
1✔
401
                if (addedDescription) {
1✔
402
                    opt.addDescription(argument);
1✔
403
                } else if (argument.startsWith("--")) {
1✔
404
                    // handle --switch --switch=ARG --switch=[OPT] --switch PLACE
405
                    String[] parts = argument.split("[ =]");
1✔
406

407
                    if (parts.length == 1) {
1✔
408
                        opt.addAlias(parts[0]);
1✔
409
                    } else {
410
                        opt.addOption(parts[0], parts[1]);
1✔
411
                    }
412
                } else if (argument.startsWith("-")) {
1✔
413
                    // handle -x -xARG -x=ARG -x[OPT] -x[=OPT] -x PLACE
414
                    String optName = argument.substring(0, 2);
1✔
415
                    String remainder = argument.substring(2);
1✔
416
                    opt.addOption(optName, remainder);
1✔
417

418
                } else if (argument.startsWith("=")) {
1✔
419
                    opt.setArgument(argument.substring(1));
1✔
420
                } else if (argument.startsWith("/")) {
1✔
421
                    // regular expression (sans '/'s)
422
                    opt.setPattern(argument.substring(1, argument.length() - 1));
1✔
423
                } else {
424
                    // this is description
425
                    opt.addDescription(argument);
1✔
426
                    addedDescription = true;
1✔
427
                }
428
            // This is indicator for a addOption of specific allowable option values
429
            } else if (arg instanceof String[]) {
1✔
430
                opt.setAllowedValues((String[]) arg);
1✔
431
            // This is indicator for option value data type
432
            // to which the parser will take and convert.
433
            } else if (arg instanceof Class) {
1✔
434
                opt.setValueType((Class<?>) arg);
1✔
435
            } else if (arg == null) {
×
436
                throw new IllegalArgumentException("arg is null");
×
437
            } else {
438
                throw new IllegalArgumentException("Invalid arg: " +
×
439
                        arg.getClass().getSimpleName() + " " + arg);
×
440
            }
441
        }
442

443
        // options starting with 3 dashes are to be hidden from usage.
444
        // (the idea here is to hide any unit test entries from general user)
445
        if (!opt.names.get(0).startsWith("---")) {
1✔
446
            optionList.add(opt);
1✔
447
            usageSummary.add(opt);
1✔
448
        }
449

450
        return opt;
1✔
451
    }
452

453
    private String argValue(String arg, boolean mandatory) {
454
        // Initially assume that the given argument is going
455
        // to be the option's value. Note that if the argument
456
        // is actually another option (starts with '-') then
457
        // there is no value available. If the option is required
458
        // to have a value, null is returned. If the option
459
        // does not require a value, an empty string is returned.
460
        String value = arg;
1✔
461
        boolean isOption = value.startsWith("-");
1✔
462

463
        if (mandatory) {
1✔
464
            if (isOption ) {
1✔
465
                value = null;
×
466
            }
467
        } else if (isOption) {
1✔
468
            value = "";
1✔
469
        }
470
        return value;
1✔
471
    }
472

473
    private String getOption(String arg, int index) throws ParseException {
474
        String option = null;
1✔
475

476
        if ( arg.equals("-")) {
1✔
477
            throw new ParseException("Stand alone '-' found in arguments, not allowed", index);
1✔
478
        }
479

480
        if (arg.startsWith("-")) {
1✔
481
            if (arg.startsWith("--")) {
1✔
482
                option = arg;                 // long name option (--longOption)
1✔
483
            } else if (arg.length() > 2) {
1✔
484
                option = arg.substring(0, 2); // short name option (-xValue)
1✔
485
            } else {
486
                option = arg;                 // short name option (-x)
1✔
487
            }
488
        }
489
        return option;
1✔
490
    }
491

492
    /**
493
     * Discover full name of partial option name.
494
     *
495
     * @param option is the initial substring of a long option name.
496
     * @param index into original argument list (only used by ParseException)
497
     * @return full name of given option substring, or null when not found.
498
     * @throws ParseException when more than one candidate name is found.
499
     */
500
    protected String candidate(String option, int index) throws ParseException {
501
        boolean found = options.containsKey(option);
1✔
502
        List<String> candidates = new ArrayList<>();
1✔
503
        String candidate = null;
1✔
504

505
        if (found) {
1✔
506
            candidate = option;
1✔
507
        } else {
508
            // Now check to see if initial substring was entered.
509
            for (String key: options.keySet()) {
1✔
510
                if (key.startsWith(option)) {
1✔
511
                    candidates.add(key);
1✔
512
                }
513
            }
1✔
514
            if (candidates.size() == 1 ) {
1✔
515
                candidate = candidates.get(0);
1✔
516
            } else if (candidates.size() > 1) {
1✔
517
                throw new ParseException(
1✔
518
                    "Ambiguous option " + option + " matches " + candidates, index);
519
            }
520
        }
521
        return candidate;
1✔
522
    }
523

524
    /**
525
     * Parse given set of arguments and activate handlers
526
     *
527
     * This code parses the given set of parameters looking for a described
528
     * set of options and activates the code segments associated with the
529
     * option.
530
     *
531
     * Parsing is discontinued when a lone "--" is encountered in the list of
532
     * arguments. If this is a normal non-scan parser, unrecognized options
533
     * will cause a parse exception. If this is a scan parser, unrecognized
534
     * options are ignored.
535
     *
536
     * @param args argument vector
537
     * @return non-option parameters, or all arguments after "--" encountered.
538
     * @throws ParseException parse exception
539
     */
540

541
    public String[] parse(String[] args) throws ParseException {
542
        int ii = 0;
1✔
543
        int optind = -1;
1✔
544
        String option;
545
        while (ii < args.length) {
1✔
546
            option = getOption(args[ii], ii);
1✔
547

548
            // When scanning for specific options...
549
            if (scanning && (option == null || (option = candidate(option, ii)) == null)) {
1✔
550
                optind = ++ii;  // skip over everything else
1✔
551
                continue;
1✔
552
            }
553

554
            if (option == null) {  // no more options? we be done.
1✔
555
                break;
×
556
            } else if (option.equals("--")) {  // parsing escape found? we be done.
1✔
557
                optind = ii + 1;
×
558
                break;
×
559
            } else {
560

561
                if ( !scanning ) {
1✔
562
                    String candidate = candidate(option, ii);
1✔
563
                    if (candidate != null) {
1✔
564
                        option = candidate;
1✔
565
                    } else {
566
                        throw new ParseException("Unknown option: " + option, ii);
1✔
567
                    }
568
                }
569
                Option opt = options.get(option);
1✔
570
                opt.value = null;
1✔
571

572
                if (option.length() == 2 && !option.equals(args[ii])) {  // catches -xValue
1✔
573
                    opt.value = args[ii].substring(2);
1✔
574
                }
575

576
                // No argument required?
577
                if (opt.argument == null || opt.argument.equals("")) {
1✔
578
                    if (opt.value != null) {
1✔
579
                        throw new ParseException("Option " + option + " does not use value.", ii);
×
580
                    }
581
                    opt.value = "";
1✔
582

583
                // Argument specified but value not yet acquired
584
                } else if (opt.value == null) {
1✔
585

586
                    ii++;   // next argument may hold argument value
1✔
587

588
                    // When option is last in list...
589
                    if (ii >= args.length) {
1✔
590
                        if (Boolean.FALSE.equals(opt.mandatory)) {
1✔
591
                            opt.value = "";  // indicate this option's value was optional
1✔
592
                        }
593
                    } else {
594

595
                        // Look at next argument for value
596
                        opt.value = argValue(args[ii], opt.mandatory);
1✔
597

598
                        if (opt.value != null && opt.value.equals("")) {
1✔
599
                            // encountered another option so this
600
                            // option's value was not required. Backup
601
                            // argument list index to handle so loop
602
                            // can re-examine this option.
603
                            ii--;
1✔
604
                        }
605
                    }
606
                }
607

608
                // If there is no value setting for the
609
                // option by now, throw a hissy fit.
610
                if (opt.value == null) {
1✔
611
                    throw new ParseException("Option " + option + " requires a value.", ii);
1✔
612
                }
613

614
                // Only specific values allowed?
615
                if (opt.allowedValues != null && !opt.allowedValues.contains(opt.value)) {
1✔
616
                    throw new ParseException(
1✔
617
                       "'" + opt.value +
618
                       "' is unknown value for option " + opt.names +
619
                       ". Must be one of " + opt.allowedValues, ii);
620
                }
621

622
                Object value = opt.value;
1✔
623

624
                // Should option argument match some pattern?
625
                if (opt.valuePattern != null) {
1✔
626
                    Matcher m = opt.valuePattern.matcher(opt.value);
1✔
627
                    if (!m.matches()) {
1✔
628
                        throw new ParseException(
1✔
629
                           "Value '" + opt.value + "' for option " + opt.names + opt.argument +
630
                           "\n does not match pattern " + opt.valuePattern, ii);
631
                    }
632

633
                // Handle special conversions of input
634
                // arguments before sending to action handler.
635
                } else if (opt.valueType != null) {
1✔
636

637
                    if (!converters.containsKey(opt.valueType)) {
1✔
638
                        throw new ParseException(
×
639
                            "No conversion handler for data type " + opt.valueType, ii);
640
                    }
641

642
                    try {
643
                        DataParser data = converters.get(opt.valueType);
1✔
644
                        value = data.converter.apply(opt.value);
1✔
645

646
                    } catch (Exception e) {
1✔
647
                        System.err.println("** " + e.getMessage());
1✔
648
                        throw new ParseException("Failed to parse (" + opt.value + ") as value of " + opt.names, ii);
1✔
649
                    }
1✔
650
                }
651

652
                if (opt.action != null) {
1✔
653
                    opt.action.accept(value); // 'do' assigned action
1✔
654
                }
655
                optind = ++ii;
1✔
656
            }
1✔
657
        }
658

659
        // Prepare to gather any remaining arguments
660
        // to send back to calling program.
661

662
        String[] remainingArgs = null;
1✔
663

664
        if (optind == -1) {
1✔
665
            remainingArgs = args;
×
666
        } else if (optind < args.length) {
1✔
667
            remainingArgs = Arrays.copyOfRange(args, optind, args.length);
×
668
        } else {
669
            remainingArgs = new String[0];  // all args used up, send back empty.
1✔
670
        }
671

672
        return remainingArgs;
1✔
673
    }
674

675
    private String getPrologue() {
676
        // Assign default prologue statement when none given.
677
        if (prologue == null) {
1✔
678
            prologue = "Usage: MyProgram [options]";
1✔
679
        }
680

681
        return prologue;
1✔
682
    }
683

684

685
    /**
686
     * Define the prologue to be presented before the options summary.
687
     * Example: Usage programName [options]
688
     * @param text that makes up the prologue.
689
     */
690
    public void setPrologue(String text) {
691
        prologue = text;
1✔
692
    }
1✔
693

694
    /**
695
     * Define the epilogue to be presented after the options summary.
696
     * @param text that makes up the epilogue.
697
     */
698
    public void setEpilogue(String text) {
699
        epilogue = text;
×
700
    }
×
701

702
    /**
703
     * Place text in option summary.
704
     * @param text to be inserted into option summary.
705
     *
706
     * Example usage:
707
     * <code>
708
     *  OptionParser opts = OptionParser.execute( parser -&gt; {
709
     *
710
     *    parser.prologue = String.format("Usage: %s [options] bubba smith", program);
711
     *    parser.separator("");
712
     *
713
     *    parser.on("-y value", "--why me", "This is a description").execute( v -&gt; {
714
     *        System.out.println("got " + v);
715
     *    });
716
     *
717
     *    parser.separator("  ----------------------------------------------");
718
     *    parser.separator("  Common Options:");
719
     *    ...
720
     *
721
     *    parser.separator("  ----------------------------------------------");
722
     *    parser.epilogue = "  That's all Folks!";
723
     * </code>
724
     */
725
    public void separator(String text) {
726
        usageSummary.add(text);
×
727
    }
×
728

729
    /**
730
     * Obtain option summary.
731
     * @param indent a string to be used as the option summary initial indent.
732
     * @return usage string
733
     */
734
    public String getUsage(String indent) {
735

736
        StringWriter wrt = new StringWriter();
1✔
737
        try (PrintWriter out = new PrintWriter(wrt)) {
1✔
738
            out.println(getPrologue());
1✔
739
            for (Object o : usageSummary) {
1✔
740
                // Need to be able to handle separator strings
741
                if (o instanceof String) {
1✔
742
                    out.println((String) o);
×
743
                } else {
744
                    out.println(indent + ((Option) o).getUsage());
1✔
745
                }
746
            }
1✔
747
            if (epilogue != null) {
1✔
748
                out.println(epilogue);
×
749
            }
750
            out.flush();
1✔
751
        }
752
        return wrt.toString();
1✔
753
    }
754

755
    /**
756
     * Obtain option summary.
757
     * @return option summary
758
     */
759
    public String getUsage() {
760
        return getUsage("  ");
1✔
761
    }
762

763
    /**
764
     * Print out option summary.
765
     */
766
    public void help() {
767
        System.out.println(getUsage());
×
768
    }
×
769

770
    /**
771
     * Print out option summary on provided output stream.
772
     * @param out print stream
773
     */
774
    public void help(PrintStream out) {
775
        out.println(getUsage());
×
776
    }
×
777

778
    protected List<Option> getOptionList() {
779
        return optionList;
1✔
780
    }
781
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc