Coverage Summary for Class: CliArgs (co.rsk.cli)
Class |
Method, %
|
Line, %
|
CliArgs |
33.3%
(2/6)
|
69.2%
(9/13)
|
CliArgs$Parser |
0%
(0/9)
|
0%
(0/39)
|
Total |
13.3%
(2/15)
|
17.3%
(9/52)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2018 RSK Labs Ltd.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 package co.rsk.cli;
19
20 import java.util.*;
21 import java.util.stream.Collectors;
22
23 /**
24 * A simple representation of command line arguments, broken into "options", "flags" and "arguments".
25 */
26 public class CliArgs<O, F> {
27
28 private final List<String> arguments;
29 private final Map<O, String> options;
30 private final Set<F> flags;
31
32 private CliArgs(List<String> arguments, Map<O, String> options, Set<F> flags) {
33 this.arguments = Collections.unmodifiableList(arguments);
34 this.options = Collections.unmodifiableMap(options);
35 this.flags = Collections.unmodifiableSet(flags);
36 }
37
38 public static <O, F> CliArgs<O, F> empty() {
39 return new CliArgs<>(
40 Collections.emptyList(),
41 Collections.emptyMap(),
42 Collections.emptySet()
43 );
44 }
45
46 public List<String> getArguments() {
47 return arguments;
48 }
49
50 public Map<O, String> getOptions() {
51 return options;
52 }
53
54 public Set<F> getFlags() {
55 return flags;
56 }
57
58 /**
59 * Parses a {@code String[]} of command line arguments in order to populate a
60 * {@link CliArgs} object.
61 *
62 * <h3>Working with option arguments</h3>
63 * Option arguments must adhere to the exact syntax:
64 * <pre class="code">-optName optValue</pre>
65 * <pre class="code">--flagName</pre>
66 * That is, options must be prefixed with "{@code -}", and must specify a value,
67 * and flags must be prefixed with "{@code --}", and may not specify a value.
68 */
69 public static class Parser<O extends Enum<O> & OptionalizableCliArg, F extends Enum<F> & CliArg> {
70
71 private final EnumSet<O> options;
72 private final EnumSet<F> flags;
73
74 public Parser(Class<O> optionsClass, Class<F> flagsClass) {
75 this.options = EnumSet.allOf(optionsClass);
76 this.flags = EnumSet.allOf(flagsClass);
77 }
78
79 public CliArgs<O, F> parse(String[] args) {
80 List<String> arguments = new LinkedList<>();
81 Map<O, String> options = new HashMap<>();
82 Set<F> flags = new HashSet<>();
83
84 for (int i = 0; i < args.length; i++) {
85 switch (args[i].charAt(0)) {
86 case '-':
87 if (args[i].length() < 2) {
88 throw new IllegalArgumentException("You must provide an option name, e.g. -d");
89 }
90 if (args[i].charAt(1) == '-') {
91 if (args[i].length() < 3) {
92 throw new IllegalArgumentException("You must provide a flag name, e.g. --quiet");
93 }
94 flags.add(getFlagByName(args[i].substring(2, args[i].length())));
95 } else {
96 if (args.length - 1 == i) {
97 throw new IllegalArgumentException(
98 String.format("A value must be provided after the option -%s", args[i])
99 );
100 }
101 options.put(getOptionByName(args[i].substring(1, args[i].length())), args[i + 1]);
102 i++;
103 }
104 break;
105 default:
106 arguments.add(args[i]);
107 break;
108 }
109 }
110
111 Set<O> missingOptions = this.options.stream()
112 .filter(arg -> !arg.isOptional())
113 .collect(Collectors.toSet());
114 missingOptions.removeAll(options.keySet());
115 if (!missingOptions.isEmpty()) {
116 throw new IllegalArgumentException(
117 String.format("Missing configuration options: %s", missingOptions)
118 );
119 }
120
121 return new CliArgs<>(arguments, options, flags);
122 }
123
124 private F getFlagByName(String flagName) {
125 return flags.stream()
126 .filter(flag -> flag.getName().equals(flagName))
127 .findFirst()
128 .orElseThrow(
129 () -> new NoSuchElementException(String.format("--%s is not a valid flag", flagName))
130 );
131 }
132
133 private O getOptionByName(String optionName) {
134 return options.stream()
135 .filter(opt -> opt.getName().equals(optionName))
136 .findFirst()
137 .orElseThrow(
138 () -> new NoSuchElementException(String.format("-%s is not a valid option", optionName))
139 );
140 }
141 }
142 }