diff --git a/spring-shell2-jcommander-adapter/src/main/java/org/springframework/shell2/jcommander/JCommanderParameterResolver.java b/spring-shell2-jcommander-adapter/src/main/java/org/springframework/shell2/jcommander/JCommanderParameterResolver.java index 526841cc..0afa6813 100644 --- a/spring-shell2-jcommander-adapter/src/main/java/org/springframework/shell2/jcommander/JCommanderParameterResolver.java +++ b/spring-shell2-jcommander-adapter/src/main/java/org/springframework/shell2/jcommander/JCommanderParameterResolver.java @@ -19,15 +19,19 @@ package org.springframework.shell2.jcommander; import static org.springframework.shell2.Utils.unCamelify; import java.lang.annotation.Annotation; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; import java.util.stream.Stream; import com.beust.jcommander.DynamicParameter; import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; +import com.beust.jcommander.ParameterException; import com.beust.jcommander.ParametersDelegate; import org.springframework.beans.BeanUtils; @@ -92,22 +96,43 @@ public class JCommanderParameterResolver implements ParameterResolver { @Override public Stream describe(MethodParameter parameter) { JCommander jCommander = createJCommander(parameter); - com.beust.jcommander.ParameterDescription mainParameter = jCommander.getMainParameter(); - return Stream.concat( - jCommander.getParameters().stream(), - mainParameter != null ? Stream.of(mainParameter) : Stream.empty() - ) + Stream jCommanderDescriptions = streamAllJCommanderDescriptions(jCommander); + return jCommanderDescriptions .map(j -> new ParameterDescription(parameter, unCamelify(j.getParameterized().getType().getSimpleName())) .keys(Arrays.asList(j.getParameter().names())) .help(j.getDescription()) - .mandatoryKey(!j.equals(mainParameter)) + .mandatoryKey(!j.equals(jCommander.getMainParameter())) // Not ideal as this does not take reverse-conversion into account, but just toString() .defaultValue(j.getDefault() == null ? "" : String.valueOf(j.getDefault())) ); } + /** + * Return all JCommander parameter descriptions, including the "main" parameter if present. + */ + private Stream streamAllJCommanderDescriptions(JCommander jCommander) { + return Stream.concat( + jCommander.getParameters().stream(), + jCommander.getMainParameter() != null ? Stream.of(jCommander.getMainParameter()) : Stream.empty() + ); + } + @Override public List complete(MethodParameter parameter, CompletionContext context) { - return null; + JCommander jCommander = createJCommander(parameter); + List words = context.getWords(); + try { + jCommander.parseWithoutValidation(words.toArray(new String[words.size()])); + } + catch (ParameterException ignored) { + // Exception here certainly means current buffer is not parseable in full. + // Better to bail out now. + return Collections.emptyList(); + } + return streamAllJCommanderDescriptions(jCommander) + .filter(p -> !p.isAssigned()) + .flatMap(p -> Arrays.stream(p.getParameter().names())) + .map(CompletionProposal::new) + .collect(Collectors.toList()); } } diff --git a/spring-shell2-jcommander-adapter/src/test/java/org/springframework/shell2/jcommander/JCommanderParameterResolverTest.java b/spring-shell2-jcommander-adapter/src/test/java/org/springframework/shell2/jcommander/JCommanderParameterResolverTest.java index fa33ebc6..09195055 100644 --- a/spring-shell2-jcommander-adapter/src/test/java/org/springframework/shell2/jcommander/JCommanderParameterResolverTest.java +++ b/spring-shell2-jcommander-adapter/src/test/java/org/springframework/shell2/jcommander/JCommanderParameterResolverTest.java @@ -25,9 +25,12 @@ import java.util.Arrays; import java.util.Collections; import java.util.stream.Stream; +import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.core.MethodParameter; +import org.springframework.shell2.CompletionContext; +import org.springframework.shell2.CompletionProposal; import org.springframework.shell2.ParameterDescription; import org.springframework.shell2.Utils; import org.springframework.util.ReflectionUtils; @@ -85,4 +88,26 @@ public class JCommanderParameterResolverTest { .help("rest"); assertThat(desciptions).contains(name, level, rest); } + + @Test + public void testCanComplete() { + MethodParameter methodParameter = Utils.createMethodParameter(COMMAND_METHOD, 0); + + CompletionContext context = new CompletionContext(Collections.emptyList(), 0, 0); + Stream proposals = resolver.complete(methodParameter, context).stream().map(CompletionProposal::value); + assertThat(proposals).containsExactly("--name", "-n", "-level"); + + context = new CompletionContext(Arrays.asList("-n", "foo"), 0, 0); + proposals = resolver.complete(methodParameter, context).stream().map(CompletionProposal::value); + assertThat(proposals).containsExactly("-level"); + } + + @Test + public void testCannotComplete() { + MethodParameter methodParameter = Utils.createMethodParameter(COMMAND_METHOD, 0); + + CompletionContext context = new CompletionContext(Arrays.asList("--name"), 0, 0); + Stream proposals = resolver.complete(methodParameter, context).stream().map(CompletionProposal::value); + assertThat(proposals).isEmpty(); + } }