Skip to content

Commit 32f77c1

Browse files
committed
Rework completion interfaces
- Use same interface type in a generic interactive completions in a method level and option value level. - Change CompletionResolver to have same function signature as with options and use CompletionContext to keep relevant information. - Fixes #449
1 parent 2520e06 commit 32f77c1

File tree

9 files changed

+57
-47
lines changed

9 files changed

+57
-47
lines changed

spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ParameterResolverAutoConfiguration.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
/*
2+
* Copyright 2022 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+
*/
116
package org.springframework.shell.boot;
217

318
import java.util.ArrayList;
@@ -11,7 +26,7 @@
1126
import org.springframework.shell.command.CommandContextMethodArgumentResolver;
1227
import org.springframework.shell.command.CommandExecution.CommandExecutionHandlerMethodArgumentResolvers;
1328
import org.springframework.shell.completion.CompletionResolver;
14-
import org.springframework.shell.completion.DefaultCompletionResolver;
29+
import org.springframework.shell.completion.RegistrationOptionsCompletionResolver;
1530
import org.springframework.shell.config.ShellConversionServiceSupplier;
1631
import org.springframework.shell.standard.ShellOptionMethodArgumentResolver;
1732

@@ -20,7 +35,7 @@ public class ParameterResolverAutoConfiguration {
2035

2136
@Bean
2237
public CompletionResolver defaultCompletionResolver() {
23-
return new DefaultCompletionResolver();
38+
return new RegistrationOptionsCompletionResolver();
2439
}
2540

2641
@Bean

spring-shell-core/src/main/java/org/springframework/shell/CompletionContext.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,17 @@ public CompletionContext drop(int nbWords) {
105105
position, commandRegistration, commandOption);
106106
}
107107

108+
/**
109+
* Return a copy of this context with given command option.
110+
*/
108111
public CompletionContext commandOption(CommandOption commandOption) {
109112
return new CompletionContext(words, wordIndex, position, commandRegistration, commandOption);
110113
}
114+
115+
/**
116+
* Return a copy of this context with given command registration.
117+
*/
118+
public CompletionContext commandRegistration(CommandRegistration commandRegistration) {
119+
return new CompletionContext(words, wordIndex, position, commandRegistration, commandOption);
120+
}
111121
}

spring-shell-core/src/main/java/org/springframework/shell/Shell.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,11 +277,12 @@ public List<CompletionProposal> complete(CompletionContext context) {
277277

278278
String best = findLongestCommand(prefix);
279279
if (best != null) {
280-
CompletionContext argsContext = context.drop(best.split(" ").length);
280+
context = context.drop(best.split(" ").length);
281281
CommandRegistration registration = commandRegistry.getRegistrations().get(best);
282+
CompletionContext argsContext = context.commandRegistration(registration);
282283

283284
for (CompletionResolver resolver : completionResolvers) {
284-
List<CompletionProposal> resolved = resolver.resolve(registration, argsContext);
285+
List<CompletionProposal> resolved = resolver.apply(argsContext);
285286
candidates.addAll(resolved);
286287
}
287288

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

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

18-
import java.util.List;
19-
import java.util.function.Function;
20-
2118
import org.springframework.core.ResolvableType;
22-
import org.springframework.shell.CompletionContext;
23-
import org.springframework.shell.CompletionProposal;
19+
import org.springframework.shell.completion.CompletionResolver;
2420

2521
/**
2622
* Interface representing an option in a command.
@@ -104,7 +100,7 @@ public interface CommandOption {
104100
*
105101
* @return the completion function
106102
*/
107-
Function<CompletionContext, List<CompletionProposal>> getCompletion();
103+
CompletionResolver getCompletion();
108104

109105
/**
110106
* Gets an instance of a default {@link CommandOption}.
@@ -150,7 +146,7 @@ public static CommandOption of(String[] longNames, Character[] shortNames, Strin
150146
*/
151147
public static CommandOption of(String[] longNames, Character[] shortNames, String description,
152148
ResolvableType type, boolean required, String defaultValue, Integer position, Integer arityMin,
153-
Integer arityMax, String label, Function<CompletionContext, List<CompletionProposal>> completion) {
149+
Integer arityMax, String label, CompletionResolver completion) {
154150
return new DefaultCommandOption(longNames, shortNames, description, type, required, defaultValue, position,
155151
arityMin, arityMax, label, completion);
156152
}
@@ -170,12 +166,12 @@ public static class DefaultCommandOption implements CommandOption {
170166
private int arityMin;
171167
private int arityMax;
172168
private String label;
173-
private Function<CompletionContext, List<CompletionProposal>> completion;
169+
private CompletionResolver completion;
174170

175171
public DefaultCommandOption(String[] longNames, Character[] shortNames, String description,
176172
ResolvableType type, boolean required, String defaultValue, Integer position,
177173
Integer arityMin, Integer arityMax, String label,
178-
Function<CompletionContext, List<CompletionProposal>> completion) {
174+
CompletionResolver completion) {
179175
this.longNames = longNames != null ? longNames : new String[0];
180176
this.shortNames = shortNames != null ? shortNames : new Character[0];
181177
this.description = description;
@@ -240,7 +236,7 @@ public String getLabel() {
240236
}
241237

242238
@Override
243-
public Function<CompletionContext, List<CompletionProposal>> getCompletion() {
239+
public CompletionResolver getCompletion() {
244240
return completion;
245241
}
246242
}

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@
2929
import org.springframework.core.ResolvableType;
3030
import org.springframework.lang.Nullable;
3131
import org.springframework.shell.Availability;
32-
import org.springframework.shell.CompletionContext;
33-
import org.springframework.shell.CompletionProposal;
32+
import org.springframework.shell.completion.CompletionResolver;
3433
import org.springframework.shell.context.InteractionMode;
3534
import org.springframework.util.Assert;
3635
import org.springframework.util.ObjectUtils;
@@ -216,7 +215,7 @@ public interface OptionSpec {
216215
* @param completion the completion function
217216
* @return option spec for chaining
218217
*/
219-
OptionSpec completion(Function<CompletionContext, List<CompletionProposal>> completion);
218+
OptionSpec completion(CompletionResolver completion);
220219

221220
/**
222221
* Return a builder for chaining.
@@ -538,7 +537,7 @@ static class DefaultOptionSpec implements OptionSpec {
538537
private Integer arityMin;
539538
private Integer arityMax;
540539
private String label;
541-
private Function<CompletionContext, List<CompletionProposal>> completion;
540+
private CompletionResolver completion;
542541

543542
DefaultOptionSpec(BaseBuilder builder) {
544543
this.builder = builder;
@@ -638,7 +637,7 @@ public OptionSpec label(String label) {
638637
}
639638

640639
@Override
641-
public OptionSpec completion(Function<CompletionContext, List<CompletionProposal>> completion) {
640+
public OptionSpec completion(CompletionResolver completion) {
642641
this.completion = completion;
643642
return this;
644643
}
@@ -688,7 +687,7 @@ public String getLabel() {
688687
return label;
689688
}
690689

691-
public Function<CompletionContext, List<CompletionProposal>> getCompletion() {
690+
public CompletionResolver getCompletion() {
692691
return completion;
693692
}
694693
}

spring-shell-core/src/main/java/org/springframework/shell/completion/CompletionResolver.java

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,16 @@
1616
package org.springframework.shell.completion;
1717

1818
import java.util.List;
19+
import java.util.function.Function;
1920

2021
import org.springframework.shell.CompletionContext;
2122
import org.springframework.shell.CompletionProposal;
22-
import org.springframework.shell.command.CommandRegistration;
2323

2424
/**
2525
* Interface resolving completion proposals.
2626
*
2727
* @author Janne Valkealahti
2828
*/
29-
public interface CompletionResolver {
30-
31-
/**
32-
* Resolve completions.
33-
*
34-
* @param registration the command registration
35-
* @param context the completion context
36-
* @return list of resolved completions
37-
*/
38-
List<CompletionProposal> resolve(CommandRegistration registration, CompletionContext context);
29+
@FunctionalInterface
30+
public interface CompletionResolver extends Function<CompletionContext, List<CompletionProposal>> {
3931
}

spring-shell-core/src/main/java/org/springframework/shell/completion/DefaultCompletionResolver.java renamed to spring-shell-core/src/main/java/org/springframework/shell/completion/RegistrationOptionsCompletionResolver.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,31 @@
1616
package org.springframework.shell.completion;
1717

1818
import java.util.ArrayList;
19+
import java.util.Collections;
1920
import java.util.List;
2021
import java.util.stream.Stream;
2122

2223
import org.springframework.shell.CompletionContext;
2324
import org.springframework.shell.CompletionProposal;
24-
import org.springframework.shell.command.CommandRegistration;
2525

2626
/**
2727
* Default implementation of a {@link CompletionResolver}.
2828
*
2929
* @author Janne Valkealahti
3030
*/
31-
public class DefaultCompletionResolver implements CompletionResolver {
31+
public class RegistrationOptionsCompletionResolver implements CompletionResolver {
3232

3333
@Override
34-
public List<CompletionProposal> resolve(CommandRegistration registration, CompletionContext context) {
34+
public List<CompletionProposal> apply(CompletionContext context) {
35+
if (context.getCommandRegistration() == null) {
36+
return Collections.emptyList();
37+
}
3538
List<CompletionProposal> candidates = new ArrayList<>();
36-
registration.getOptions().stream()
39+
context.getCommandRegistration().getOptions().stream()
3740
.flatMap(o -> Stream.of(o.getLongNames()))
3841
.map(ln -> new CompletionProposal("--" + ln))
3942
.forEach(candidates::add);
40-
registration.getOptions().stream()
43+
context.getCommandRegistration().getOptions().stream()
4144
.flatMap(o -> Stream.of(o.getShortNames()))
4245
.map(ln -> new CompletionProposal("-" + ln))
4346
.forEach(candidates::add);

spring-shell-core/src/test/java/org/springframework/shell/ShellTests.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
import org.springframework.shell.command.CommandCatalog;
3232
import org.springframework.shell.command.CommandRegistration;
33-
import org.springframework.shell.completion.CompletionResolver;
33+
import org.springframework.shell.completion.RegistrationOptionsCompletionResolver;
3434

3535
import static org.assertj.core.api.Assertions.assertThat;
3636
import static org.assertj.core.api.Assertions.fail;
@@ -56,17 +56,14 @@ public class ShellTests {
5656
@Mock
5757
CommandCatalog commandRegistry;
5858

59-
@Mock
60-
private CompletionResolver completionResolver;
61-
6259
@InjectMocks
6360
private Shell shell;
6461

6562
private boolean invoked;
6663

6764
@BeforeEach
6865
public void setUp() {
69-
shell.setCompletionResolvers(Arrays.asList(completionResolver));
66+
shell.setCompletionResolvers(Arrays.asList(new RegistrationOptionsCompletionResolver()));
7067
}
7168

7269
@Test
@@ -272,7 +269,6 @@ private String failing() {
272269

273270
@Test
274271
public void completionArgWithMethod() throws Exception {
275-
when(completionResolver.resolve(any(), any())).thenReturn(Arrays.asList(new CompletionProposal("--arg1")));
276272
CommandRegistration registration1 = CommandRegistration.builder()
277273
.command("hello world")
278274
.withTarget()
@@ -294,7 +290,6 @@ public void completionArgWithMethod() throws Exception {
294290

295291
@Test
296292
public void completionArgWithFunction() throws Exception {
297-
when(completionResolver.resolve(any(), any())).thenReturn(Arrays.asList(new CompletionProposal("--arg1")));
298293
CommandRegistration registration1 = CommandRegistration.builder()
299294
.command("hello world")
300295
.withTarget()

spring-shell-standard/src/main/java/org/springframework/shell/standard/StandardMethodTargetRegistrar.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.util.List;
2424
import java.util.Map;
2525
import java.util.Set;
26-
import java.util.function.Function;
2726
import java.util.function.Supplier;
2827
import java.util.stream.Collectors;
2928

@@ -37,14 +36,14 @@
3736
import org.springframework.core.annotation.AnnotationUtils;
3837
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
3938
import org.springframework.shell.Availability;
40-
import org.springframework.shell.CompletionContext;
4139
import org.springframework.shell.CompletionProposal;
4240
import org.springframework.shell.MethodTargetRegistrar;
4341
import org.springframework.shell.Utils;
4442
import org.springframework.shell.command.CommandCatalog;
4543
import org.springframework.shell.command.CommandRegistration;
4644
import org.springframework.shell.command.CommandRegistration.Builder;
4745
import org.springframework.shell.command.CommandRegistration.OptionSpec;
46+
import org.springframework.shell.completion.CompletionResolver;
4847
import org.springframework.shell.standard.ShellOption.NoValueProvider;
4948
import org.springframework.util.Assert;
5049
import org.springframework.util.ClassUtils;
@@ -149,12 +148,12 @@ else if (o.length() == stripped.length() + 1 && stripped.length() == 1) {
149148
optionSpec.required();
150149
}
151150
if (!ClassUtils.isAssignable(NoValueProvider.class, so.valueProvider())) {
152-
Function<CompletionContext, List<CompletionProposal>> completionFunction = ctx -> {
151+
CompletionResolver completionResolver = ctx -> {
153152
ValueProvider valueProviderBean = this.applicationContext.getBean(so.valueProvider());
154153
List<CompletionProposal> complete = valueProviderBean.complete(ctx);
155154
return complete;
156155
};
157-
optionSpec.completion(completionFunction);
156+
optionSpec.completion(completionResolver);
158157
}
159158
}
160159
}

0 commit comments

Comments
 (0)