Skip to content

Commit 281ffb6

Browse files
committed
Switch to enum
As not all combos make sense.
1 parent 46b169d commit 281ffb6

2 files changed

Lines changed: 129 additions & 24 deletions

File tree

src/main/java/org/apache/commons/cli/DefaultParser.java

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -168,25 +168,53 @@ static int indexOfEqual(final String token) {
168168
return token.indexOf(Char.EQUAL);
169169
}
170170

171+
/**
172+
* Enum representing possible actions that may be done when "non option" is discovered during parsing.
173+
*
174+
* @since 1.10.0
175+
*/
176+
public enum NonOptionAction {
177+
/**
178+
* Parsing continues and current token is ignored.
179+
*/
180+
IGNORE,
181+
/**
182+
* Parsing continues and current token is added to command line arguments.
183+
*/
184+
SKIP,
185+
/**
186+
* Parsing will stop and remaining tokens are added to command line arguments.
187+
* Equivalent of {@code stopAtNonOption = true}.
188+
*/
189+
STOP,
190+
/**
191+
* Parsing will abort and exception is thrown.
192+
* Equivalent of {@code stopAtNonOption = false}.
193+
*/
194+
THROW;
195+
}
196+
171197
/** The command-line instance. */
172198
protected CommandLine cmd;
173199

174200
/** The current options. */
175201
protected Options options;
176202

177203
/**
178-
* Flag indicating how unrecognized tokens are handled: {@code true} to stop the parsing and add the remaining
179-
* tokens to the args list. {@code false} add current token to the arg list and continue parsing.
204+
* Flag indicating how unrecognized tokens are handled. {@code true} to stop the parsing and add the remaining
205+
* tokens to the args list. {@code false} to throw an exception.
206+
*
207+
* @deprecated Use {@link #nonOptionAction} instead. This field is unused, and left for binary compatibility reasons.
180208
*/
209+
@Deprecated
181210
protected boolean stopAtNonOption;
182211

183212
/**
184-
* Flag indicating how unrecognized tokens are handled: {@code true} to abort parsing by throwing {@link ParseException}.
185-
* {@code false} to ignore it.
213+
* Action to happen when "non option" token is discovered.
186214
*
187215
* @since 1.10.0
188216
*/
189-
protected boolean throwAtNonOption = true;
217+
protected NonOptionAction nonOptionAction;
190218

191219
/** The token currently processed. */
192220
protected String currentToken;
@@ -364,7 +392,7 @@ protected void handleConcatenatedOptions(final String token) throws ParseExcepti
364392
for (int i = 1; i < token.length(); i++) {
365393
final String ch = String.valueOf(token.charAt(i));
366394
if (!options.hasOption(ch)) {
367-
handleUnknownToken(stopAtNonOption && i > 1 ? token.substring(i) : token);
395+
handleUnknownToken(nonOptionAction == NonOptionAction.STOP && i > 1 ? token.substring(i) : token);
368396
break;
369397
}
370398
handleOption(options.getOption(ch));
@@ -594,11 +622,13 @@ private void handleToken(final String token) throws ParseException {
594622
* @since 1.10.0
595623
*/
596624
protected void handleUnknownToken(final String token) throws ParseException {
597-
if (token.startsWith("-") && token.length() > 1 && throwAtNonOption) {
625+
if (token.startsWith("-") && token.length() > 1 && nonOptionAction == NonOptionAction.THROW) {
598626
throw new UnrecognizedOptionException("Unrecognized option: " + token, token);
599627
}
600-
addArg(token);
601-
if (stopAtNonOption) {
628+
if (!token.startsWith("-") || token.equals("-") || token.length() > 1 && nonOptionAction != NonOptionAction.IGNORE) {
629+
addArg(token);
630+
}
631+
if (nonOptionAction == NonOptionAction.STOP) {
602632
skipParsing = true;
603633
}
604634
}
@@ -702,7 +732,7 @@ public CommandLine parse(final Options options, final String[] arguments) throws
702732
}
703733

704734
/**
705-
* @see #parse(Options, String[], Properties, boolean, boolean)
735+
* @see #parse(Options, String[], Properties, NonOptionAction)
706736
*/
707737
@Override
708738
public CommandLine parse(final Options options, final String[] arguments, final boolean stopAtNonOption) throws ParseException {
@@ -734,11 +764,11 @@ public CommandLine parse(final Options options, final String[] arguments, final
734764
*
735765
* @return the list of atomic option and value tokens
736766
* @throws ParseException if there are any problems encountered while parsing the command line tokens.
737-
* @see #parse(Options, String[], Properties, boolean, boolean)
767+
* @see #parse(Options, String[], Properties, NonOptionAction)
738768
*/
739769
public CommandLine parse(final Options options, final String[] arguments, final Properties properties, final boolean stopAtNonOption)
740770
throws ParseException {
741-
return parse(options, arguments, properties, stopAtNonOption, !stopAtNonOption);
771+
return parse(options, arguments, properties, stopAtNonOption ? NonOptionAction.STOP : NonOptionAction.THROW);
742772
}
743773

744774
/**
@@ -747,18 +777,16 @@ public CommandLine parse(final Options options, final String[] arguments, final
747777
* @param options the specified Options
748778
* @param arguments the command line arguments
749779
* @param properties command line option name-value pairs
750-
* @param stopAtNonOption see {@link #stopAtNonOption}.
751-
* @param throwAtNonOption see {@link #throwAtNonOption}.
780+
* @param nonOptionAction see {@link NonOptionAction}.
752781
*
753782
* @return the list of atomic option and value tokens
754783
* @throws ParseException if there are any problems encountered while parsing the command line tokens.
755784
* @since 1.10.0
756785
*/
757-
public CommandLine parse(final Options options, final String[] arguments, final Properties properties, final boolean stopAtNonOption,
758-
final boolean throwAtNonOption) throws ParseException {
786+
public CommandLine parse(final Options options, final String[] arguments, final Properties properties, final NonOptionAction nonOptionAction)
787+
throws ParseException {
759788
this.options = options;
760-
this.stopAtNonOption = stopAtNonOption;
761-
this.throwAtNonOption = throwAtNonOption;
789+
this.nonOptionAction = nonOptionAction;
762790
skipParsing = false;
763791
currentOption = null;
764792
expectedOpts = new ArrayList<>(options.getRequiredOptions());

src/test/java/org/apache/commons/cli/DefaultParserTest.java

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ public void setUp() {
159159
}
160160

161161
@Test
162-
void chainingParsersHappyPath() throws ParseException {
162+
void chainingParsersSkipHappyPath() throws ParseException {
163163
Option a = Option.builder().option("a").longOpt("first-letter").build();
164164
Option b = Option.builder().option("b").longOpt("second-letter").build();
165165
Option c = Option.builder().option("c").longOpt("third-letter").build();
@@ -178,7 +178,7 @@ void chainingParsersHappyPath() throws ParseException {
178178

179179
DefaultParser parser = new DefaultParser();
180180

181-
CommandLine baseCommandLine = parser.parse(baseOptions, args, null, false, false);
181+
CommandLine baseCommandLine = parser.parse(baseOptions, args, null, DefaultParser.NonOptionAction.SKIP);
182182
assertEquals(2, baseCommandLine.getOptions().length);
183183
assertEquals(4, baseCommandLine.getArgs().length);
184184
assertTrue(baseCommandLine.hasOption("a"));
@@ -192,7 +192,7 @@ void chainingParsersHappyPath() throws ParseException {
192192
assertTrue(baseCommandLine.getArgList().contains("arg1"));
193193
assertTrue(baseCommandLine.getArgList().contains("arg2"));
194194

195-
CommandLine specificCommandLine = parser.parse(specificOptions, args, null, false, true);
195+
CommandLine specificCommandLine = parser.parse(specificOptions, args, null, DefaultParser.NonOptionAction.THROW);
196196
assertEquals(4, specificCommandLine.getOptions().length);
197197
assertEquals(2, specificCommandLine.getArgs().length);
198198
assertTrue(specificCommandLine.hasOption("a"));
@@ -208,7 +208,7 @@ void chainingParsersHappyPath() throws ParseException {
208208
}
209209

210210
@Test
211-
void chainingParsersNonHappyPath() throws ParseException {
211+
void chainingParsersSkipNonHappyPath() throws ParseException {
212212
Option a = Option.builder().option("a").longOpt("first-letter").build();
213213
Option b = Option.builder().option("b").longOpt("second-letter").build();
214214
Option c = Option.builder().option("c").longOpt("third-letter").build();
@@ -225,11 +225,88 @@ void chainingParsersNonHappyPath() throws ParseException {
225225

226226
DefaultParser parser = new DefaultParser();
227227

228-
CommandLine baseCommandLine = parser.parse(baseOptions, args, null, false, false);
228+
CommandLine baseCommandLine = parser.parse(baseOptions, args, null, DefaultParser.NonOptionAction.SKIP);
229229
assertEquals(2, baseCommandLine.getOptions().length);
230230
assertEquals(4, baseCommandLine.getArgs().length);
231231

232-
UnrecognizedOptionException e = assertThrows(UnrecognizedOptionException.class, () -> parser.parse(specificOptions, args, null, false, true));
232+
UnrecognizedOptionException e = assertThrows(UnrecognizedOptionException.class,
233+
() -> parser.parse(specificOptions, args, null, DefaultParser.NonOptionAction.THROW));
234+
assertTrue(e.getMessage().contains("-d"));
235+
}
236+
237+
@Test
238+
void chainingParsersIgnoreHappyPath() throws ParseException {
239+
Option a = Option.builder().option("a").longOpt("first-letter").build();
240+
Option b = Option.builder().option("b").longOpt("second-letter").build();
241+
Option c = Option.builder().option("c").longOpt("third-letter").build();
242+
Option d = Option.builder().option("d").longOpt("fourth-letter").build();
243+
244+
Options baseOptions = new Options();
245+
baseOptions.addOption(a);
246+
baseOptions.addOption(b);
247+
Options specificOptions = new Options();
248+
specificOptions.addOption(a);
249+
specificOptions.addOption(b);
250+
specificOptions.addOption(c);
251+
specificOptions.addOption(d);
252+
253+
String[] args = {"-a", "-b", "-c", "-d", "arg1", "arg2"};
254+
255+
DefaultParser parser = new DefaultParser();
256+
257+
CommandLine baseCommandLine = parser.parse(baseOptions, args, null, DefaultParser.NonOptionAction.IGNORE);
258+
assertEquals(2, baseCommandLine.getOptions().length);
259+
assertEquals(2, baseCommandLine.getArgs().length);
260+
assertTrue(baseCommandLine.hasOption("a"));
261+
assertTrue(baseCommandLine.hasOption("b"));
262+
assertFalse(baseCommandLine.hasOption("c"));
263+
assertFalse(baseCommandLine.hasOption("d"));
264+
assertFalse(baseCommandLine.getArgList().contains("-a"));
265+
assertFalse(baseCommandLine.getArgList().contains("-b"));
266+
assertFalse(baseCommandLine.getArgList().contains("-c"));
267+
assertFalse(baseCommandLine.getArgList().contains("-d"));
268+
assertTrue(baseCommandLine.getArgList().contains("arg1"));
269+
assertTrue(baseCommandLine.getArgList().contains("arg2"));
270+
271+
CommandLine specificCommandLine = parser.parse(specificOptions, args, null, DefaultParser.NonOptionAction.THROW);
272+
assertEquals(4, specificCommandLine.getOptions().length);
273+
assertEquals(2, specificCommandLine.getArgs().length);
274+
assertTrue(specificCommandLine.hasOption("a"));
275+
assertTrue(specificCommandLine.hasOption("b"));
276+
assertTrue(specificCommandLine.hasOption("c"));
277+
assertTrue(specificCommandLine.hasOption("d"));
278+
assertFalse(specificCommandLine.getArgList().contains("-a"));
279+
assertFalse(specificCommandLine.getArgList().contains("-b"));
280+
assertFalse(specificCommandLine.getArgList().contains("-c"));
281+
assertFalse(specificCommandLine.getArgList().contains("-d"));
282+
assertTrue(specificCommandLine.getArgList().contains("arg1"));
283+
assertTrue(specificCommandLine.getArgList().contains("arg2"));
284+
}
285+
286+
@Test
287+
void chainingParsersIgnoreNonHappyPath() throws ParseException {
288+
Option a = Option.builder().option("a").longOpt("first-letter").build();
289+
Option b = Option.builder().option("b").longOpt("second-letter").build();
290+
Option c = Option.builder().option("c").longOpt("third-letter").build();
291+
292+
Options baseOptions = new Options();
293+
baseOptions.addOption(a);
294+
baseOptions.addOption(b);
295+
Options specificOptions = new Options();
296+
specificOptions.addOption(a);
297+
specificOptions.addOption(b);
298+
specificOptions.addOption(c);
299+
300+
String[] args = {"-a", "-b", "-c", "-d", "arg1", "arg2"}; // -d is rogue option
301+
302+
DefaultParser parser = new DefaultParser();
303+
304+
CommandLine baseCommandLine = parser.parse(baseOptions, args, null, DefaultParser.NonOptionAction.IGNORE);
305+
assertEquals(2, baseCommandLine.getOptions().length);
306+
assertEquals(2, baseCommandLine.getArgs().length);
307+
308+
UnrecognizedOptionException e = assertThrows(UnrecognizedOptionException.class,
309+
() -> parser.parse(specificOptions, args, null, DefaultParser.NonOptionAction.THROW));
233310
assertTrue(e.getMessage().contains("-d"));
234311
}
235312

0 commit comments

Comments
 (0)