Skip to content

Commit 15bec06

Browse files
committed
Improved completion by allowing configuration of whether option values should be completed
Signed-off-by: David Pilar <[email protected]>
1 parent 6b1e701 commit 15bec06

File tree

5 files changed

+77
-24
lines changed

5 files changed

+77
-24
lines changed

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
* @author Mahmoud Ben Hassine
2727
*/
2828
public record CommandOption(char shortName, @Nullable String longName, @Nullable String description,
29-
@Nullable Boolean required, @Nullable String defaultValue, @Nullable String value, Class<?> type) {
29+
@Nullable Boolean required, @Nullable String defaultValue, @Nullable String value, Class<?> type,
30+
boolean completion) {
3031

3132
public static Builder with() {
3233
return new Builder();
@@ -48,6 +49,8 @@ public static class Builder {
4849

4950
private Class<?> type = Object.class;
5051

52+
private boolean completion = true;
53+
5154
public Builder shortName(char shortName) {
5255
this.shortName = shortName;
5356
return this;
@@ -83,8 +86,13 @@ public Builder type(Class<?> type) {
8386
return this;
8487
}
8588

89+
public Builder completion(boolean completion) {
90+
this.completion = completion;
91+
return this;
92+
}
93+
8694
public CommandOption build() {
87-
return new CommandOption(shortName, longName, description, required, defaultValue, value, type);
95+
return new CommandOption(shortName, longName, description, required, defaultValue, value, type, completion);
8896
}
8997

9098
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,10 @@
6262
*/
6363
String defaultValue() default "";
6464

65+
/**
66+
* Indicates whether this option should be completed in the command completer.
67+
* @return true if the option should be completed, defaults to true.
68+
*/
69+
boolean completion() default true;
70+
6571
}

spring-shell-core/src/main/java/org/springframework/shell/core/command/annotation/support/CommandFactoryBean.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,15 @@ private List<CommandOption> getCommandOptions() {
126126
String description = optionAnnotation.description();
127127
boolean required = optionAnnotation.required();
128128
String defaultValue = optionAnnotation.defaultValue();
129+
boolean completion = optionAnnotation.completion();
129130
CommandOption commandOption = CommandOption.with()
130131
.shortName(shortName)
131132
.longName(longName)
132133
.description(description)
133134
.required(required)
134135
.defaultValue(defaultValue)
135136
.type(parameter.getType())
137+
.completion(completion)
136138
.build();
137139
commandOptions.add(commandOption);
138140
}

spring-shell-jline/src/main/java/org/springframework/shell/jline/CommandCompleter.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,14 @@ public void complete(LineReader reader, ParsedLine line, List<Candidate> candida
4747
// add option completions for the command
4848
for (CommandOption option : options) {
4949
boolean present = isOptionPresent(line, option);
50+
String separator = option.completion() ? "" : "=";
5051
if (StringUtils.hasLength(option.longName()) && !present) {
51-
candidates.add(new Candidate("--" + option.longName()));
52+
candidates.add(new Candidate("--" + option.longName() + separator, "--" + option.longName(),
53+
null, null, null, null, option.completion(), 0));
5254
}
5355
if (option.shortName() != ' ' && !present) {
54-
candidates.add(new Candidate("-" + option.shortName()));
56+
candidates.add(new Candidate("-" + option.shortName() + separator, "-" + option.shortName(),
57+
null, null, null, null, option.completion(), 0));
5558
}
5659
}
5760
}

spring-shell-jline/src/test/java/org/springframework/shell/jline/CommandCompleterTests.java

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ private List<String> toCandidateDisplayText(List<Candidate> candidates) {
8787
}
8888

8989
@ParameterizedTest
90-
@MethodSource("completeData")
91-
void testComplete(List<String> words, List<String> expectedValues) {
90+
@MethodSource("completeWithCompletionData")
91+
void testCompleteWithCompletion(List<String> words, List<String> expectedValues) {
9292
// given
9393
when(command.getOptions())
9494
.thenReturn(List.of(new CommandOption.Builder().longName("first").shortName('f').build(),
@@ -107,51 +107,85 @@ void testComplete(List<String> words, List<String> expectedValues) {
107107
assertEquals(expectedValues, toCandidateNames(candidates));
108108
}
109109

110-
static Stream<Arguments> completeData() {
110+
static Stream<Arguments> completeWithCompletionData() {
111+
return completeData("");
112+
}
113+
114+
@ParameterizedTest
115+
@MethodSource("completeWithoutCompletionData")
116+
void testCompleteWithoutCompletion(List<String> words, List<String> expectedValues) {
117+
// given
118+
when(command.getName()).thenReturn("hello");
119+
when(command.getOptions())
120+
.thenReturn(List.of(new CommandOption.Builder().longName("first").shortName('f').completion(false).build(),
121+
new CommandOption.Builder().longName("last").shortName('l').completion(false).build()));
122+
123+
List<Candidate> candidates = new ArrayList<>();
124+
ParsedLine line = mock(ParsedLine.class);
125+
when(line.words()).thenReturn(words);
126+
when(line.word()).thenReturn(words.get(words.size() - 1));
127+
when(line.line()).thenReturn(String.join(" ", words));
128+
129+
// when
130+
completer.complete(mock(LineReader.class), line, candidates);
131+
132+
// then
133+
assertEquals(expectedValues, toCandidateNames(candidates));
134+
}
135+
136+
static Stream<Arguments> completeWithoutCompletionData() {
137+
return completeData("=");
138+
}
139+
140+
static Stream<Arguments> completeData(String sep) {
111141
return Stream.of(Arguments.of(List.of(""), List.of("hello")), Arguments.of(List.of("he"), List.of("hello")),
112142
Arguments.of(List.of("he", ""), List.of()), Arguments.of(List.of("hello"), List.of("hello")),
113143

114-
Arguments.of(List.of("hello", ""), List.of("--first", "--last", "-f", "-l")),
115-
Arguments.of(List.of("hello", "--"), List.of("--first", "--last", "-f", "-l")),
116-
Arguments.of(List.of("hello", "-"), List.of("--first", "--last", "-f", "-l")),
117-
Arguments.of(List.of("hello", "--fi"), List.of("--first", "--last", "-f", "-l")),
118-
Arguments.of(List.of("hello", "--la"), List.of("--first", "--last", "-f", "-l")),
144+
Arguments.of(List.of("hello", ""), List.of("--first" + sep, "--last" + sep, "-f" + sep, "-l" + sep)),
145+
Arguments.of(List.of("hello", "--"), List.of("--first" + sep, "--last" + sep, "-f" + sep, "-l" + sep)),
146+
Arguments.of(List.of("hello", "-"), List.of("--first" + sep, "--last" + sep, "-f" + sep, "-l" + sep)),
147+
Arguments.of(List.of("hello", "--fi"),
148+
List.of("--first" + sep, "--last" + sep, "-f" + sep, "-l" + sep)),
149+
Arguments.of(List.of("hello", "--la"),
150+
List.of("--first" + sep, "--last" + sep, "-f" + sep, "-l" + sep)),
119151

120-
Arguments.of(List.of("hello", "-f"), List.of("--first", "--last", "-f", "-l")),
152+
Arguments.of(List.of("hello", "-f"), List.of("--first" + sep, "--last" + sep, "-f" + sep, "-l" + sep)),
121153
Arguments.of(List.of("hello", "-f="), List.of("-f=Mary", "-f=Paul", "-f=Peter")),
122154
Arguments.of(List.of("hello", "-f=Pe"), List.of("-f=Mary", "-f=Paul", "-f=Peter")),
123-
Arguments.of(List.of("hello", "-f=Pe", ""), List.of("--last", "-l")),
155+
Arguments.of(List.of("hello", "-f=Pe", ""), List.of("--last" + sep, "-l" + sep)),
124156

125-
Arguments.of(List.of("hello", "--first"), List.of("--first", "--last", "-f", "-l")),
157+
Arguments.of(List.of("hello", "--first"),
158+
List.of("--first" + sep, "--last" + sep, "-f" + sep, "-l" + sep)),
126159
Arguments.of(List.of("hello", "--first="), List.of("--first=Mary", "--first=Paul", "--first=Peter")),
127160
Arguments.of(List.of("hello", "--first=Pe"), List.of("--first=Mary", "--first=Paul", "--first=Peter")),
128-
Arguments.of(List.of("hello", "--first=Pe", ""), List.of("--last", "-l")),
161+
Arguments.of(List.of("hello", "--first=Pe", ""), List.of("--last" + sep, "-l" + sep)),
129162

130163
Arguments.of(List.of("hello", "-f", ""), List.of("Mary", "Paul", "Peter")),
131164
Arguments.of(List.of("hello", "--first", ""), List.of("Mary", "Paul", "Peter")),
132165

133166
Arguments.of(List.of("hello", "-f", "Pe"), List.of("Mary", "Paul", "Peter")),
134-
Arguments.of(List.of("hello", "-f", "Pe", ""), List.of("--last", "-l")),
167+
Arguments.of(List.of("hello", "-f", "Pe", ""), List.of("--last" + sep, "-l" + sep)),
135168
Arguments.of(List.of("hello", "--first", "Pe"), List.of("Mary", "Paul", "Peter")),
136-
Arguments.of(List.of("hello", "--first", "Pe", ""), List.of("--last", "-l")),
169+
Arguments.of(List.of("hello", "--first", "Pe", ""), List.of("--last" + sep, "-l" + sep)),
137170

138-
Arguments.of(List.of("hello", "-l"), List.of("--first", "--last", "-f", "-l")),
171+
Arguments.of(List.of("hello", "-l"), List.of("--first" + sep, "--last" + sep, "-f" + sep, "-l" + sep)),
139172
Arguments.of(List.of("hello", "-l="), List.of("-l=Chan", "-l=Noris")),
140173
Arguments.of(List.of("hello", "-l=No"), List.of("-l=Chan", "-l=Noris")),
141-
Arguments.of(List.of("hello", "-l=No", ""), List.of("--first", "-f")),
174+
Arguments.of(List.of("hello", "-l=No", ""), List.of("--first" + sep, "-f" + sep)),
142175

143-
Arguments.of(List.of("hello", "--last"), List.of("--first", "--last", "-f", "-l")),
176+
Arguments.of(List.of("hello", "--last"),
177+
List.of("--first" + sep, "--last" + sep, "-f" + sep, "-l" + sep)),
144178
Arguments.of(List.of("hello", "--last="), List.of("--last=Chan", "--last=Noris")),
145179
Arguments.of(List.of("hello", "--last=No"), List.of("--last=Chan", "--last=Noris")),
146-
Arguments.of(List.of("hello", "--last=No", ""), List.of("--first", "-f")),
180+
Arguments.of(List.of("hello", "--last=No", ""), List.of("--first" + sep, "-f" + sep)),
147181

148182
Arguments.of(List.of("hello", "-l", ""), List.of("Chan", "Noris")),
149183
Arguments.of(List.of("hello", "--last", ""), List.of("Chan", "Noris")),
150184

151185
Arguments.of(List.of("hello", "-l", "No"), List.of("Chan", "Noris")),
152-
Arguments.of(List.of("hello", "-l", "No", ""), List.of("--first", "-f")),
186+
Arguments.of(List.of("hello", "-l", "No", ""), List.of("--first" + sep, "-f" + sep)),
153187
Arguments.of(List.of("hello", "--last", "No"), List.of("Chan", "Noris")),
154-
Arguments.of(List.of("hello", "--last", "No", ""), List.of("--first", "-f")),
188+
Arguments.of(List.of("hello", "--last", "No", ""), List.of("--first" + sep, "-f" + sep)),
155189

156190
Arguments.of(List.of("hello", "--first", "Paul", "--last", "Noris", ""), List.of()),
157191
Arguments.of(List.of("hello", "--first", "Paul", "-l", "Noris", ""), List.of()),

0 commit comments

Comments
 (0)