Skip to content

Commit 5dee898

Browse files
committed
Fix Mixup of Option short and long name #1284
Signed-off-by: czpilar <david@czpilar.net>
1 parent 01aadb3 commit 5dee898

3 files changed

Lines changed: 105 additions & 10 deletions

File tree

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

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,17 @@
1515
*/
1616
package org.springframework.shell.core.command;
1717

18-
import java.io.PrintWriter;
19-
2018
import org.jspecify.annotations.Nullable;
21-
2219
import org.springframework.shell.core.InputReader;
2320

21+
import java.io.PrintWriter;
22+
import java.util.function.Predicate;
23+
2424
/**
2525
* Interface containing runtime information about the current command invocation.
2626
*
2727
* @author Mahmoud Ben Hassine
28+
* @author David Pilar
2829
* @since 4.0.0
2930
*/
3031
public record CommandContext(ParsedInput parsedInput, CommandRegistry commandRegistry, PrintWriter outputWriter,
@@ -36,11 +37,33 @@ public record CommandContext(ParsedInput parsedInput, CommandRegistry commandReg
3637
* @return the matching {@link CommandOption} or null if not found
3738
*/
3839
@Nullable public CommandOption getOptionByName(String optionName) {
39-
return this.parsedInput.options()
40-
.stream()
41-
.filter(option -> option.longName().equals(optionName) || option.shortName() == optionName.charAt(0))
42-
.findFirst()
43-
.orElse(null);
40+
CommandOption option = getOptionByLongName(optionName);
41+
if (option == null && optionName.length() == 1) {
42+
option = getOptionByShortName(optionName.charAt(0));
43+
}
44+
return option;
45+
}
46+
47+
/**
48+
* Retrieve a command option by its long name.
49+
* @param longName the long name of the option to retrieve
50+
* @return the matching {@link CommandOption} or null if not found
51+
*/
52+
@Nullable public CommandOption getOptionByLongName(String longName) {
53+
return getOptionByFilter(option -> longName.equals(option.longName()));
54+
}
55+
56+
/**
57+
* Retrieve a command option by its short name.
58+
* @param shortName the short name of the option to retrieve
59+
* @return the matching {@link CommandOption} or null if not found
60+
*/
61+
@Nullable public CommandOption getOptionByShortName(char shortName) {
62+
return getOptionByFilter(option -> option.shortName() == shortName);
63+
}
64+
65+
@Nullable private CommandOption getOptionByFilter(Predicate<CommandOption> filter) {
66+
return this.parsedInput.options().stream().filter(filter).findFirst().orElse(null);
4467
}
4568

4669
/**

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
* An adapter to adapt a method as a command.
4444
*
4545
* @author Mahmoud Ben Hassine
46+
* @author David Pilar
4647
* @since 4.0.0
4748
*/
4849
public class MethodInvokerCommandAdapter extends AbstractCommand {
@@ -141,8 +142,13 @@ private List<Object> prepareArguments(CommandContext commandContext) {
141142
+ parameters[i].getName() + "'");
142143
}
143144
boolean required = optionAnnotation.required();
144-
CommandOption commandOption = commandContext
145-
.getOptionByName(longName.isEmpty() ? String.valueOf(shortName) : longName);
145+
CommandOption commandOption = null;
146+
if (!longName.isEmpty()) {
147+
commandOption = commandContext.getOptionByLongName(longName);
148+
}
149+
if (commandOption == null && shortName != ' ') {
150+
commandOption = commandContext.getOptionByShortName(shortName);
151+
}
146152
if (commandOption != null) {
147153
String rawValue = commandOption.value();
148154
Class<?> parameterType = parameterTypes[i];
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package org.springframework.shell.core.command;
2+
3+
import org.junit.jupiter.api.BeforeEach;
4+
import org.junit.jupiter.params.ParameterizedTest;
5+
import org.junit.jupiter.params.provider.CsvSource;
6+
import org.springframework.shell.core.InputReader;
7+
8+
import java.io.PrintWriter;
9+
10+
import static org.junit.jupiter.api.Assertions.assertEquals;
11+
import static org.mockito.Mockito.mock;
12+
13+
class CommandContextTests {
14+
15+
private CommandContext context;
16+
17+
@BeforeEach
18+
void setUp() {
19+
ParsedInput parsedInput = ParsedInput.builder()
20+
.addOption(CommandOption.with().longName("aWrong").description("command1").build())
21+
.addOption(CommandOption.with().longName("intendedA").shortName('a').description("command2").build())
22+
.addOption(CommandOption.with().shortName('b').description("command3").build())
23+
.addOption(CommandOption.with().longName("x").description("command4").build())
24+
.addOption(CommandOption.with().shortName('x').description("command5").build())
25+
.addOption(CommandOption.with().shortName('y').description("command6").build())
26+
.addOption(CommandOption.with().longName("y").description("command7").build())
27+
.build();
28+
context = new CommandContext(parsedInput, mock(CommandRegistry.class), mock(PrintWriter.class),
29+
mock(InputReader.class));
30+
}
31+
32+
@ParameterizedTest
33+
@CsvSource({ "aWrong, command1", "intendedA, command2", "noSuchOption, null", "x, command4", "y, command7" })
34+
void testGetOptionByLongName(String longName, String description) {
35+
// when
36+
CommandOption commandOption = context.getOptionByLongName(longName);
37+
38+
// then
39+
String result = commandOption == null ? "null" : commandOption.description();
40+
assertEquals(description, result);
41+
}
42+
43+
@ParameterizedTest
44+
@CsvSource({ "a, command2", "b, command3", "n, null", "x, command5", "y, command6" })
45+
void testGetOptionByShortName(char shortName, String description) {
46+
// when
47+
CommandOption commandOption = context.getOptionByShortName(shortName);
48+
49+
// then
50+
String result = commandOption == null ? "null" : commandOption.description();
51+
assertEquals(description, result);
52+
}
53+
54+
@ParameterizedTest
55+
@CsvSource({ "aWrong, command1", "intendedA, command2", "noSuchOption, null", "a, command2", "b, command3",
56+
"n, null", "x, command4", "y, command7" })
57+
void testGetOptionByName(String optionName, String description) {
58+
// when
59+
CommandOption commandOption = context.getOptionByName(optionName);
60+
61+
// then
62+
String result = commandOption == null ? "null" : commandOption.description();
63+
assertEquals(description, result);
64+
}
65+
66+
}

0 commit comments

Comments
 (0)