Skip to content

Commit 38acd79

Browse files
committed
Add unrecognised option support
- This commit modifies CommandParser to better track positional parameters which previously used to go there for non-recognised options. Now using relatively dump logic of just checking if first positional parameter starts with '-' which indicates it's a candidate for a new `UnrecognisedOptionException` which then would give user an error "Unrecognised option '--xxx'" for example. - Backport #601 - Backport #602 - Fixes #603 - Fixes #604
1 parent 800e249 commit 38acd79

File tree

3 files changed

+162
-2
lines changed

3 files changed

+162
-2
lines changed

spring-shell-core/src/main/java/org/springframework/shell/command/CommandParser.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,15 @@ public CommandParserResults parse(List<CommandOption> options, String[] args) {
227227
requiredOptions.remove(pr.option);
228228
}
229229
else {
230-
positional.addAll(pr.args);
230+
for (String arg : pr.args) {
231+
if (arg.startsWith("-")) {
232+
errors.add(UnrecognisedOptionException.of(String.format("Unrecognised option '%s'", arg),
233+
arg));
234+
}
235+
else {
236+
positional.add(arg);
237+
}
238+
}
231239
}
232240
if (pr.error != null) {
233241
errors.add(pr.error);
@@ -258,7 +266,9 @@ public CommandParserResults parse(List<CommandOption> options, String[] args) {
258266
}
259267
}
260268
}
261-
if (!oargs.isEmpty()) {
269+
// don't do anything if first arg looks like an option as if we are here
270+
// then we'd might remove wrong required option
271+
if (!oargs.isEmpty() && !oargs.get(0).startsWith("-")) {
262272
results.add(new DefaultCommandParserResult(o, oargs.stream().collect(Collectors.joining(" "))));
263273
requiredOptions.remove(o);
264274
}
@@ -516,4 +526,22 @@ public CommandOption getOption() {
516526
return option;
517527
}
518528
}
529+
530+
public static class UnrecognisedOptionException extends CommandParserException {
531+
532+
private String option;
533+
534+
public UnrecognisedOptionException(String message, String option) {
535+
super(message);
536+
this.option = option;
537+
}
538+
539+
public static UnrecognisedOptionException of(String message, String option) {
540+
return new UnrecognisedOptionException(message, option);
541+
}
542+
543+
public String getOption() {
544+
return option;
545+
}
546+
}
519547
}

spring-shell-core/src/test/java/org/springframework/shell/command/CommandParserTests.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,65 @@ public void testIntegerWithGivenValue() {
365365
assertThat(results.results().get(0).value()).isEqualTo(1);
366366
}
367367

368+
@Test
369+
public void testNotDefinedLongOptionWithoutOptions() {
370+
// gh-602
371+
List<CommandOption> options = Arrays.asList();
372+
String[] args = new String[]{"--arg1", "foo"};
373+
CommandParserResults results = parser.parse(options, args);
374+
assertThat(results.results()).hasSize(0);
375+
assertThat(results.errors()).hasSize(1);
376+
assertThat(results.positional()).hasSize(1);
377+
}
378+
379+
@Test
380+
public void testNotDefinedLongOptionWithOptionalOption() {
381+
// gh-602
382+
CommandOption option1 = longOption("arg1");
383+
List<CommandOption> options = Arrays.asList(option1);
384+
String[] args = new String[]{"--arg1", "bar", "--arg2", "foo"};
385+
CommandParserResults results = parser.parse(options, args);
386+
assertThat(results.results()).hasSize(1);
387+
assertThat(results.errors()).hasSize(1);
388+
assertThat(results.positional()).hasSize(1);
389+
}
390+
391+
@Test
392+
public void testNotDefinedLongOptionWithRequiredOption() {
393+
// gh-602
394+
CommandOption option1 = longOption("arg1", true);
395+
List<CommandOption> options = Arrays.asList(option1);
396+
String[] args = new String[]{"--arg2", "foo"};
397+
CommandParserResults results = parser.parse(options, args);
398+
assertThat(results.results()).hasSize(0);
399+
assertThat(results.errors()).hasSize(2);
400+
assertThat(results.positional()).hasSize(1);
401+
}
402+
403+
@Test
404+
public void testPositionDoesNotAffectRequiredErrorWithOtherErrors() {
405+
// gh-601
406+
CommandOption o1 = CommandOption.of(
407+
new String[] { "arg1" },
408+
null,
409+
null,
410+
null,
411+
true,
412+
null,
413+
0,
414+
1,
415+
1,
416+
null,
417+
null);
418+
419+
List<CommandOption> options = Arrays.asList(o1);
420+
String[] args = new String[]{"--arg2"};
421+
CommandParserResults results = parser.parse(options, args);
422+
assertThat(results.results()).hasSize(0);
423+
assertThat(results.errors()).hasSize(2);
424+
assertThat(results.positional()).hasSize(0);
425+
}
426+
368427
private static CommandOption longOption(String name) {
369428
return longOption(name, null);
370429
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.shell.samples.e2e;
17+
18+
import java.util.function.Supplier;
19+
20+
import org.springframework.context.annotation.Bean;
21+
import org.springframework.shell.command.CommandRegistration;
22+
import org.springframework.shell.standard.ShellComponent;
23+
import org.springframework.shell.standard.ShellMethod;
24+
import org.springframework.shell.standard.ShellOption;
25+
26+
@ShellComponent
27+
public class UnrecognisedOptionCommands extends BaseE2ECommands {
28+
29+
@ShellMethod(key = LEGACY_ANNO + "unrecognised-option-noother", group = GROUP)
30+
public String testUnrecognisedOptionNoOtherAnnotation(
31+
) {
32+
return "Hi";
33+
}
34+
35+
@Bean
36+
public CommandRegistration testUnrecognisedOptionNoOtherRegistration(Supplier<CommandRegistration.Builder> builder) {
37+
return builder.get()
38+
.command(REG, "unrecognised-option-noother")
39+
.group(GROUP)
40+
.withTarget()
41+
.function(ctx -> {
42+
return "Hi";
43+
})
44+
.and()
45+
.build();
46+
}
47+
48+
@ShellMethod(key = LEGACY_ANNO + "unrecognised-option-withrequired", group = GROUP)
49+
public String testUnrecognisedOptionWithRequiredAnnotation(
50+
@ShellOption(help = "Desc arg1") String arg1
51+
) {
52+
return "Hello " + arg1;
53+
}
54+
55+
@Bean
56+
public CommandRegistration testUnrecognisedOptionWithRequiredRegistration(Supplier<CommandRegistration.Builder> builder) {
57+
return builder.get()
58+
.command(REG, "unrecognised-option-withrequired")
59+
.group(GROUP)
60+
.withOption()
61+
.longNames("arg1")
62+
.description("Desc arg1")
63+
.required()
64+
.and()
65+
.withTarget()
66+
.function(ctx -> {
67+
String arg1 = ctx.getOptionValue("arg1");
68+
return "Hello " + arg1;
69+
})
70+
.and()
71+
.build();
72+
}
73+
}

0 commit comments

Comments
 (0)