commandBeans = applicationContext.getBeansWithAnnotation(ShellComponent.class);
+ for (Object bean : commandBeans.values()) {
+ Class> clazz = bean.getClass();
+ ReflectionUtils.doWithMethods(clazz, method -> {
+ ShellMethod shellMapping = method.getAnnotation(ShellMethod.class);
+ String[] keys = shellMapping.value();
+ if (keys.length == 0) {
+ keys = new String[] {method.getName()};
+ }
+ for (String key : keys) {
+ methodTargets.put(key, new MethodTarget(method, bean, shellMapping.help()));
+ }
+ }, method -> method.getAnnotation(ShellMethod.class) != null);
+ }
+ return methodTargets;
+ }
+}
diff --git a/src/main/java/org/springframework/shell2/DefaultParameterResolver.java b/src/main/java/org/springframework/shell2/standard/StandardParameterResolver.java
similarity index 55%
rename from src/main/java/org/springframework/shell2/DefaultParameterResolver.java
rename to src/main/java/org/springframework/shell2/standard/StandardParameterResolver.java
index 4e776d0e..bbd1c296 100644
--- a/src/main/java/org/springframework/shell2/DefaultParameterResolver.java
+++ b/src/main/java/org/springframework/shell2/standard/StandardParameterResolver.java
@@ -1,307 +1,458 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.shell2;
-
-import static org.springframework.shell2.Utils.unCamelify;
-
-import java.lang.reflect.Method;
-import java.lang.reflect.Parameter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-import org.springframework.core.DefaultParameterNameDiscoverer;
-import org.springframework.core.MethodParameter;
-import org.springframework.core.convert.ConversionService;
-import org.springframework.core.convert.TypeDescriptor;
-import org.springframework.util.Assert;
-import org.springframework.util.ConcurrentReferenceHashMap;
-
-/**
- * Default ParameterResolver implementation that supports the following features:
- * - named parameters (recognized because they start with some {@link ShellMethod#prefix()}
- * - implicit named parameters (from the actual method parameter name)
- * - positional parameters (in order, for all parameter values that were not resolved via named parameters)
- * - default values (for all remaining parameters)
- *
- *
- * Method arguments can consume several words of input at once (driven by {@link ShellOption#arity()}, default 1).
- * If several words are consumed, they will be joined together as a comma separated value and passed to the {@link
- * ConversionService}
- * (which will typically return a List or array).
- *
- * Boolean parameters are by default expected to have an arity of 0, allowing invocations in the form {@code rm
- * --force --dir /foo}:
- * the presence of {@code --force} passes {@code true} as a parameter value, while its absence passes {@code false}.
- * Both
- * the default arity of 0 and the default value of {@code false} can be overridden via {@link ShellOption}
- * if needed.
- * @author Eric Bottard
- * @author Florent Biville
- */
-public class DefaultParameterResolver implements ParameterResolver {
-
- private final ConversionService conversionService;
-
- /**
- * A cache from method+input to String representation of actual parameter values.
- * Note that the converted result is not cached, to allow dynamic computation to happen at every invocation
- * if needed (e.g. if a remote service is involved).
- */
- private final Map> parameterCache = new ConcurrentReferenceHashMap<>();
-
- public DefaultParameterResolver(ConversionService conversionService) {
- this.conversionService = conversionService;
- }
-
- @Override
- public boolean supports(MethodParameter parameter) {
- return true;
- }
-
- @Override
- public Object resolve(MethodParameter methodParameter, List words) {
- String prefix = prefixForMethod(methodParameter);
-
- CacheKey cacheKey = new CacheKey(methodParameter.getMethod(), words);
- Map resolved = parameterCache.computeIfAbsent(cacheKey, (k) -> {
-
- Map result = new HashMap<>();
- Map namedParameters = new HashMap<>();
- List positionalValues = new ArrayList<>();
-
- // First, resolve all parameters passed by-name
- for (int i = 0; i < words.size(); i++) {
- String word = words.get(i);
- if (word.startsWith(prefix)) {
- String key = word.substring(prefix.length());
- Parameter parameter = lookupParameterForKey(methodParameter.getMethod(), key, prefix);
- int arity = getArity(parameter);
-
- String raw = words.subList(i + 1, i + 1 + arity).stream().collect(Collectors.joining(","));
- Assert.isTrue(!namedParameters.containsKey(key), String.format("Parameter for '%s' has already been specified", word));
- namedParameters.put(key, raw);
- result.put(parameter, raw);
- i += arity;
- if (arity == 0) {
- boolean defaultValue = booleanDefaultValue(parameter);
- // Boolean parameter has been specified. Use the opposite of the default value
- result.put(parameter, String.valueOf(!defaultValue));
- }
- } // store for later processing of positional params
- else {
- positionalValues.add(word);
- }
- }
-
- // Now have a second pass over params and treat them as positional
- int offset = 0;
- Parameter[] parameters = methodParameter.getMethod().getParameters();
- for (int i = 0, parametersLength = parameters.length; i < parametersLength; i++) {
- Parameter parameter = parameters[i];
- // Compute the intersection between possible keys for the param and what we've already seen for named params
- Collection keys = getKeysForParameter(methodParameter.getMethod(), i);
- Collection copy = new HashSet<>(keys);
- copy.retainAll(namedParameters.keySet());
- if (copy.isEmpty()) { // Was not set via a key (including aliases), must be positional
- int arity = getArity(parameter);
- if (offset < positionalValues.size() && (offset + arity) <= positionalValues.size()) {
- String raw = positionalValues.subList(offset, offset + arity).stream().collect(Collectors.joining(","));
- result.put(parameter, raw);
- offset += arity;
- } // No more input. Try defaultValues
- else {
- Optional defaultValue = defaultValueFor(parameter);
- String value = defaultValue.orElseThrow(() -> new RuntimeException(String.format("Ran out of input for " + keys)));
- result.put(parameter, value);
- }
- }
- else if (copy.size() > 1) {
- throw new IllegalArgumentException("Named parameter has been specified multiple times via " + prefix(copy, prefix));
- }
- }
-
- Assert.isTrue(offset == positionalValues.size(), "Too many arguments: the following could not be mapped to parameters: "
- + positionalValues.subList(offset, positionalValues.size()).stream().collect(Collectors.joining(" ", "'", "'")));
- return result;
- });
-
- String s = resolved.get(methodParameter.getMethod().getParameters()[methodParameter.getParameterIndex()]);
- if (ShellOption.NULL.equals(s)) {
- return null;
- }
- else {
- return conversionService.convert(s, TypeDescriptor.valueOf(String.class), new TypeDescriptor(methodParameter));
- }
- }
-
- private String prefixForMethod(MethodParameter methodParameter) {
- return methodParameter.getMethod().getAnnotation(ShellMethod.class).prefix();
- }
-
- private Optional defaultValueFor(Parameter parameter) {
- Optional defaultValue = Optional.empty();
- ShellOption option = parameter.getAnnotation(ShellOption.class);
- if (option != null && !ShellOption.NONE.equals(option.defaultValue())) {
- defaultValue = Optional.of(option.defaultValue());
- }
- return defaultValue;
- }
-
- @Override
- public ParameterDescription describe(MethodParameter parameter) {
- Parameter jlrParameter = parameter.getMethod().getParameters()[parameter.getParameterIndex()];
- int arity = getArity(jlrParameter);
- Class> type = parameter.getParameterType();
- ShellOption option = jlrParameter.getAnnotation(ShellOption.class);
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < arity; i++) {
- if (i > 0) {
- sb.append(" ");
- }
- sb.append(arity > 1 ? unCamelify(removeMultiplicityFromType(parameter).getSimpleName()) : unCamelify(type.getSimpleName()));
- }
- ParameterDescription result = ParameterDescription.ofType(type);
- result.formal(sb.toString());
- if (option != null) {
- result.help(option.help());
- Optional defaultValue = defaultValueFor(jlrParameter);
- if (defaultValue.isPresent()) {
- result.defaultValue(defaultValue.map(dv -> dv.equals(ShellOption.NULL) ? "" : dv).get());
- }
- }
- List rawKeys = getKeysForParameter(parameter.getMethod(), parameter.getParameterIndex());
- String prefix = prefixForMethod(parameter);
- result
- .keys(rawKeys.stream().map(k -> prefix + k).collect(Collectors.toList()))
- .mandatoryKey(false);
-
- return result;
- }
-
- /**
- * In case of {@code foo[] or Collection} and arity > 1, return the element type.
- */
- private Class> removeMultiplicityFromType(MethodParameter parameter) {
- Class> parameterType = parameter.getParameterType();
- if (parameterType.isArray()) {
- return parameterType.getComponentType();
- }
- else if (parameterType.isAssignableFrom(Collection.class)) {
- return parameter.getNestedParameterType();
- }
- else {
- throw new RuntimeException("For " + parameter + " (with arity > 1) expected an array/collection type");
- }
- }
-
- /**
- * Add the command prefix back to the list of keys that was used to invoke the method.
- */
- private String prefix(Collection keys, String prefix) {
- return keys.stream().map(k -> prefix + k).collect(Collectors.joining(", ", "'", "'"));
- }
-
- private boolean booleanDefaultValue(Parameter parameter) {
- ShellOption option = parameter.getAnnotation(ShellOption.class);
- if (option != null && !ShellOption.NULL.equals(option.defaultValue())) {
- return Boolean.parseBoolean(option.defaultValue());
- }
- return false;
- }
-
- /**
- * Return the arity of a given parameter. The default arity is 1, except for
- * booleans where arity is 0 (can be overridden back to 1 via an annotation)
- */
- private int getArity(Parameter parameter) {
- ShellOption option = parameter.getAnnotation(ShellOption.class);
- int inferred = (parameter.getType() == boolean.class || parameter.getType() == Boolean.class) ? 0 : 1;
- return option != null ? option.arity() : inferred;
- }
-
- /**
- * Return the key(s) the i-th parameter of the command method, resolved either from the {@link ShellOption}
- * annotation,
- * or from the actual parameter name.
- * @throws IllegalArgumentException if parameter names could not be extracted
- */
- private List getKeysForParameter(Method method, int index) {
- Parameter parameter = method.getParameters()[index];
- ShellOption option = parameter.getAnnotation(ShellOption.class);
- if (option != null && option.value().length > 0) {
- return Arrays.asList(option.value());
- }
- else {
- MethodParameter methodParameter = new MethodParameter(method, index);
- methodParameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
- String parameterName = methodParameter.getParameterName();
- Assert.notNull(parameterName, String.format(
- "Could not discover parameter name at index %d for %s, and option key(s) were not specified via %s annotation",
- index, method, ShellOption.class.getSimpleName()));
- return Collections.singletonList(parameterName);
- }
- }
-
- /**
- * Return the method parameter that should be bound to the given key.
- */
- private Parameter lookupParameterForKey(Method method, String key, String prefix) {
- Parameter[] parameters = method.getParameters();
- for (int i = 0, parametersLength = parameters.length; i < parametersLength; i++) {
- Parameter p = parameters[i];
- if (getKeysForParameter(method, i).contains(key)) {
- return p;
- }
- }
- throw new IllegalArgumentException(String.format("Could not look up parameter for '%s%s' in %s", prefix, key, method));
- }
-
- private static class CacheKey {
-
- private final Method method;
-
- private final List words;
-
- private CacheKey(Method method, List words) {
- this.method = method;
- this.words = words;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- CacheKey cacheKey = (CacheKey) o;
- return Objects.equals(method, cacheKey.method) &&
- Objects.equals(words, cacheKey.words);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(method, words);
- }
- }
-}
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.shell2.standard;
+
+import static org.springframework.shell2.Utils.unCamelify;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.shell2.CompletionContext;
+import org.springframework.shell2.CompletionProposal;
+import org.springframework.shell2.ParameterDescription;
+import org.springframework.shell2.ParameterMissingResolutionException;
+import org.springframework.shell2.ParameterResolver;
+import org.springframework.shell2.UnfinishedParameterResolutionException;
+import org.springframework.shell2.Utils;
+import org.springframework.util.Assert;
+import org.springframework.util.ConcurrentReferenceHashMap;
+
+/**
+ * Default ParameterResolver implementation that supports the following features:
+ * - named parameters (recognized because they start with some {@link ShellMethod#prefix()})
+ * - implicit named parameters (from the actual method parameter name)
+ * - positional parameters (in order, for all parameter values that were not resolved via named
+ * parameters)
+ * - default values (for all remaining parameters)
+ *
+ *
+ * Method arguments can consume several words of input at once (driven by {@link ShellOption#arity()}, default 1).
+ * If several words are consumed, they will be joined together as a comma separated value and passed to the {@link
+ * ConversionService}
+ * (which will typically return a List or array).
+ *
+ * Boolean parameters are by default expected to have an arity of 0, allowing invocations in the form {@code rm
+ * --force --dir /foo}:
+ * the presence of {@code --force} passes {@code true} as a parameter value, while its absence passes {@code false}.
+ * Both
+ * the default arity of 0 and the default value of {@code false} can be overridden via {@link ShellOption}
+ * if needed.
+ * @author Eric Bottard
+ * @author Florent Biville
+ */
+public class StandardParameterResolver implements ParameterResolver {
+
+ private final ConversionService conversionService;
+
+ private Collection valueProviders = new HashSet<>();
+
+ /**
+ * A cache from method+input to String representation of actual parameter values.
+ * Note that the converted result is not cached, to allow dynamic computation to happen at every invocation
+ * if needed (e.g. if a remote service is involved).
+ */
+ private final Map> parameterCache = new ConcurrentReferenceHashMap<>();
+
+ public StandardParameterResolver(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+ @Autowired(required = false)
+ public void setValueProviders(Collection valueProviders) {
+ this.valueProviders = valueProviders;
+ }
+
+ @Override
+ public boolean supports(MethodParameter parameter) {
+ return true;
+ }
+
+ @Override
+ public Object resolve(MethodParameter methodParameter, List words) {
+ String prefix = prefixForMethod(methodParameter);
+
+ CacheKey cacheKey = new CacheKey(methodParameter.getMethod(), words);
+ Map resolved = parameterCache.computeIfAbsent(cacheKey, (k) -> {
+
+ Map result = new HashMap<>();
+ Map namedParameters = new HashMap<>();
+ List positionalValues = new ArrayList<>();
+
+ Set possibleKeys = gatherAllPossibleKeys(methodParameter.getMethod());
+
+ // First, resolve all parameters passed by-name
+ for (int i = 0; i < words.size(); i++) {
+ String word = words.get(i);
+ if (possibleKeys.contains(word)) {
+ String key = word;
+ Parameter parameter = lookupParameterForKey(methodParameter.getMethod(), key, prefix);
+ int arity = getArity(parameter);
+
+ if (i + 1 + arity > words.size()) {
+ String input = words.subList(i, words.size()).stream().collect(Collectors.joining(" "));
+ throw new UnfinishedParameterResolutionException(describe(Utils.createMethodParameter(parameter)), input);
+ }
+ Assert.isTrue(i + 1 + arity <= words.size(), String.format("Not enough input for parameter '%s'", word));
+ String raw = words.subList(i + 1, i + 1 + arity).stream().collect(Collectors.joining(","));
+ Assert.isTrue(!namedParameters.containsKey(key), String.format("Parameter for '%s' has already been specified", word));
+ namedParameters.put(key, raw);
+ result.put(parameter, ParameterRawValue.explicit(raw, key));
+ i += arity;
+ if (arity == 0) {
+ boolean defaultValue = booleanDefaultValue(parameter);
+ // Boolean parameter has been specified. Use the opposite of the default value
+ result.put(parameter, ParameterRawValue.explicit(String.valueOf(!defaultValue), key));
+ }
+ } // store for later processing of positional params
+ else {
+ positionalValues.add(word);
+ }
+ }
+
+ // Now have a second pass over params and treat them as positional
+ int offset = 0;
+ Parameter[] parameters = methodParameter.getMethod().getParameters();
+ for (int i = 0, parametersLength = parameters.length; i < parametersLength; i++) {
+ Parameter parameter = parameters[i];
+ // Compute the intersection between possible keys for the param and what we've already seen for named params
+ Collection keys = getKeysForParameter(methodParameter.getMethod(), i).collect(Collectors.toSet());
+ Collection copy = new HashSet<>(keys);
+ copy.retainAll(namedParameters.keySet());
+ if (copy.isEmpty()) { // Was not set via a key (including aliases), must be positional
+ int arity = getArity(parameter);
+ if (arity > 0 && (offset + arity) <= positionalValues.size()) {
+ String raw = positionalValues.subList(offset, offset + arity).stream().collect(Collectors.joining(","));
+ result.put(parameter, ParameterRawValue.explicit(raw, null));
+ offset += arity;
+ } // No more input. Try defaultValues
+ else {
+ Optional defaultValue = defaultValueFor(parameter);
+ defaultValue.ifPresent(value -> result.put(parameter, ParameterRawValue.implicit(value, null)));
+ }
+ }
+ else if (copy.size() > 1) {
+ throw new IllegalArgumentException("Named parameter has been specified multiple times via " + quote(copy));
+ }
+ }
+
+ Assert.isTrue(offset == positionalValues.size(), "Too many arguments: the following could not be mapped to parameters: "
+ + positionalValues.subList(offset, positionalValues.size()).stream().collect(Collectors.joining(" ", "'", "'")));
+ return result;
+ });
+
+ Parameter param = methodParameter.getMethod().getParameters()[methodParameter.getParameterIndex()];
+ if (!resolved.containsKey(param)) {
+ throw new ParameterMissingResolutionException(describe(methodParameter));
+ }
+ String s = resolved.get(param).value;
+ if (ShellOption.NULL.equals(s)) {
+ return null;
+ }
+ else {
+ return conversionService.convert(s, TypeDescriptor.valueOf(String.class), new TypeDescriptor(methodParameter));
+ }
+ }
+
+ private Set gatherAllPossibleKeys(Method method) {
+ final String prefix = "--";
+ return Arrays.stream(method.getParameters())
+ .flatMap(p -> {
+ ShellOption option = p.getAnnotation(ShellOption.class);
+ if (option != null && option.value().length > 0) {
+ return Arrays.stream(option.value());
+ }
+ else {
+ return Stream.of(prefix + Utils.createMethodParameter(p).getParameterName());
+ }
+ }).collect(Collectors.toSet());
+ }
+
+ private String prefixForMethod(MethodParameter methodParameter) {
+ return methodParameter.getMethod().getAnnotation(ShellMethod.class).prefix();
+ }
+
+ private Optional defaultValueFor(Parameter parameter) {
+ Optional defaultValue = Optional.empty();
+ ShellOption option = parameter.getAnnotation(ShellOption.class);
+ if (option != null && !ShellOption.NONE.equals(option.defaultValue())) {
+ defaultValue = Optional.of(option.defaultValue());
+ }
+ else if (option == null && getArity(parameter) == 0) {
+ return Optional.of("false");
+ }
+ return defaultValue;
+ }
+
+ private boolean booleanDefaultValue(Parameter parameter) {
+ ShellOption option = parameter.getAnnotation(ShellOption.class);
+ if (option != null && !ShellOption.NULL.equals(option.defaultValue())) {
+ return Boolean.parseBoolean(option.defaultValue());
+ }
+ return false;
+ }
+
+ @Override
+ public ParameterDescription describe(MethodParameter parameter) {
+ Parameter jlrParameter = parameter.getMethod().getParameters()[parameter.getParameterIndex()];
+ int arity = getArity(jlrParameter);
+ Class> type = parameter.getParameterType();
+ ShellOption option = jlrParameter.getAnnotation(ShellOption.class);
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < arity; i++) {
+ if (i > 0) {
+ sb.append(" ");
+ }
+ sb.append(arity > 1 ? unCamelify(removeMultiplicityFromType(parameter).getSimpleName()) : unCamelify(type.getSimpleName()));
+ }
+ ParameterDescription result = ParameterDescription.outOf(parameter);
+ result.formal(sb.toString());
+ if (option != null) {
+ result.help(option.help());
+ Optional defaultValue = defaultValueFor(jlrParameter);
+ if (defaultValue.isPresent()) {
+ result.defaultValue(defaultValue.map(dv -> dv.equals(ShellOption.NULL) ? "" : dv).get());
+ }
+ }
+ result
+ .keys(getKeysForParameter(parameter.getMethod(), parameter.getParameterIndex())
+ .collect(Collectors.toList()))
+ .mandatoryKey(false);
+
+ return result;
+ }
+
+ @Override
+ public List complete(MethodParameter methodParameter, CompletionContext context) {
+ boolean set;
+ Exception unfinished = null;
+ // First try to see if this parameter has been set, even to some unfinished value
+ ParameterRawValue parameterRawValue = null;
+ try {
+ resolve(methodParameter, context.getWords());
+ CacheKey cacheKey = new CacheKey(methodParameter.getMethod(), context.getWords());
+ Parameter parameter = methodParameter.getMethod().getParameters()[methodParameter.getParameterIndex()];
+ parameterRawValue = parameterCache.get(cacheKey).get(parameter);
+ set = parameterRawValue.explicit;
+ }
+ catch (ParameterMissingResolutionException e) {
+ set = false;
+ }
+ catch (Exception e) {
+ unfinished = e;
+ set = false;
+ // Most likely what is already typed would fail resolution (eg type conversion failure)
+ // Exit early and let other parameters have a chance at being proposed
+ //return Collections.emptyList();
+ }
+
+ // There are 3 possible cases:
+ // 1) parameter not set at all
+ // 2) parameter set via its key, not enough input to consume a value
+ // 3) parameter set, and some value bound. But maybe that value is just a prefix to what the user actually wants
+ // 3.1) or maybe that value was resolved by position, but is a prefix of an actual valid key
+
+ if (!set) {
+ if (unfinished == null) { // case 1 above
+ return commandsThatStartWithContextPrefix(methodParameter, context);
+ } // case 2
+ else {
+ return valueCompletions(methodParameter, context);
+ }
+ }
+ else {
+ List result = new ArrayList<>();
+
+ String prefix = context.currentWordUpToCursor() != null ? context.currentWordUpToCursor() : "";
+ // TODO: should not look at last word only, but everything after what was used for key
+ // Case 3
+ result.addAll(valueCompletions(methodParameter, context));
+
+ if (parameterRawValue.positional()) {
+ // Case 3.1: There exists "--command foo" and user has typed "--comm" which (wrongly) got resolved as a positional param
+ result.addAll(commandsThatStartWithContextPrefix(methodParameter, context));
+ }
+ return result;
+ }
+ }
+
+ private List valueCompletions(MethodParameter methodParameter, CompletionContext completionContext) {
+ return valueProviders.stream()
+ .filter(vp -> vp.supports(methodParameter, completionContext))
+ .map(vp -> vp.complete(methodParameter, completionContext, null))
+ .findFirst().orElseGet(() -> Collections.emptyList());
+ }
+
+ private List commandsThatStartWithContextPrefix(MethodParameter methodParameter, CompletionContext context) {
+ String prefix = context.currentWordUpToCursor() != null ? context.currentWordUpToCursor() : "";
+ return describe(methodParameter).keys().stream()
+ .filter(k -> k.startsWith(prefix))
+ .map(v -> new CompletionProposal(v))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * In case of {@code foo[] or Collection} and arity > 1, return the element type.
+ */
+ private Class> removeMultiplicityFromType(MethodParameter parameter) {
+ Class> parameterType = parameter.getParameterType();
+ if (parameterType.isArray()) {
+ return parameterType.getComponentType();
+ }
+ else if (Collection.class.isAssignableFrom(parameterType)) {
+ return parameter.getNestedParameterType();
+ }
+ else {
+ throw new RuntimeException("For " + parameter + " (with arity > 1) expected an array/collection type");
+ }
+ }
+
+ /**
+ * Surrounds the parameter keys with quotes.
+ */
+ private String quote(Collection keys) {
+ return keys.stream().collect(Collectors.joining(", ", "'", "'"));
+ }
+
+ /**
+ * Return the arity of a given parameter. The default arity is 1, except for
+ * booleans where arity is 0 (can be overridden back to 1 via an annotation)
+ */
+ private int getArity(Parameter parameter) {
+ ShellOption option = parameter.getAnnotation(ShellOption.class);
+ int inferred = (parameter.getType() == boolean.class || parameter.getType() == Boolean.class) ? 0 : 1;
+ return option != null ? option.arity() : inferred;
+ }
+
+ /**
+ * Return the key(s) the i-th parameter of the command method, resolved either from the {@link ShellOption}
+ * annotation,
+ * or from the actual parameter name.
+ */
+ private Stream getKeysForParameter(Method method, int index) {
+ String prefix = "--";
+ Parameter p = method.getParameters()[index];
+ ShellOption option = p.getAnnotation(ShellOption.class);
+ if (option != null && option.value().length > 0) {
+ return Arrays.stream(option.value());
+ }
+ else {
+ return Stream.of(prefix + Utils.createMethodParameter(p).getParameterName());
+ }
+ }
+
+ /**
+ * Return the method parameter that should be bound to the given key.
+ */
+ private Parameter lookupParameterForKey(Method method, String key, String prefix) {
+ Parameter[] parameters = method.getParameters();
+ for (int i = 0, parametersLength = parameters.length; i < parametersLength; i++) {
+ Parameter p = parameters[i];
+ if (getKeysForParameter(method, i).anyMatch(k -> k.equals(key))) {
+ return p;
+ }
+ }
+ throw new IllegalArgumentException(String.format("Could not look up parameter for '%s%s' in %s", prefix, key, method));
+ }
+
+ private static class CacheKey {
+
+ private final Method method;
+
+ private final List words;
+
+ private CacheKey(Method method, List words) {
+ this.method = method;
+ this.words = words;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ CacheKey cacheKey = (CacheKey) o;
+ return Objects.equals(method, cacheKey.method) &&
+ Objects.equals(words, cacheKey.words);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(method, words);
+ }
+ }
+
+ private static class ParameterRawValue {
+
+ private CompletionContext context;
+
+ private int from;
+
+ private int to;
+
+ private Integer keyIndex;
+
+ /**
+ * The raw String value that got bound to a parameter.
+ */
+ private final String value;
+
+ /**
+ * If false, the value resolved is the result of applying defaults.
+ */
+ private final boolean explicit;
+
+ /**
+ * The key that was used to set the parameter, or null if resolution happened by position.
+ */
+ private final String key;
+
+ private ParameterRawValue(String value, boolean explicit, String key) {
+ this.value = value;
+ this.explicit = explicit;
+ this.key = key;
+ }
+
+ public static ParameterRawValue explicit(String value, String key) {
+ return new ParameterRawValue(value, true, key);
+ }
+
+ public static ParameterRawValue implicit(String value, String key) {
+ return new ParameterRawValue(value, false, key);
+ }
+
+ public boolean positional() {
+ return key == null;
+ }
+ }
+
+}
diff --git a/src/main/java/org/springframework/shell2/standard/ValueProvider.java b/src/main/java/org/springframework/shell2/standard/ValueProvider.java
new file mode 100644
index 00000000..8892c7f9
--- /dev/null
+++ b/src/main/java/org/springframework/shell2/standard/ValueProvider.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.shell2.standard;
+
+import java.util.List;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.shell2.CompletionContext;
+import org.springframework.shell2.CompletionProposal;
+
+/**
+ */
+public interface ValueProvider {
+
+ boolean supports(MethodParameter parameter, CompletionContext completionContext);
+
+ List complete(MethodParameter parameter, CompletionContext completionContext, String[] hints);
+}
diff --git a/src/main/java/org/springframework/shell2/standard/ValueProviderSupport.java b/src/main/java/org/springframework/shell2/standard/ValueProviderSupport.java
new file mode 100644
index 00000000..8ef322d7
--- /dev/null
+++ b/src/main/java/org/springframework/shell2/standard/ValueProviderSupport.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.shell2.standard;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.shell2.CompletionContext;
+
+/**
+ */
+public abstract class ValueProviderSupport implements ValueProvider {
+
+ @Override
+ public boolean supports(MethodParameter parameter, CompletionContext completionContext) {
+ ShellOption annotation = parameter.getParameterAnnotation(ShellOption.class);
+ if (annotation == null) {
+ return false;
+ }
+ return annotation.valueProvider().isAssignableFrom(this.getClass());
+ }
+}
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
index 6ff6a9a4..9a8bf719 100644
--- a/src/main/resources/logback.xml
+++ b/src/main/resources/logback.xml
@@ -1,5 +1,5 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/src/test/java/org/springframework/shell2/DefaultParameterResolverTest.java b/src/test/java/org/springframework/shell2/DefaultParameterResolverTest.java
deleted file mode 100644
index 1a9d5acc..00000000
--- a/src/test/java/org/springframework/shell2/DefaultParameterResolverTest.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.shell2;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.springframework.core.DefaultParameterNameDiscoverer;
-import org.springframework.core.MethodParameter;
-import org.springframework.core.convert.support.DefaultConversionService;
-
-import java.lang.reflect.Method;
-
-import static java.util.Arrays.asList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.springframework.util.ReflectionUtils.findMethod;
-
-/**
- * Unit tests for DefaultParameterResolver.
- *
- * @author Eric Bottard
- * @author Florent Biville
- */
-public class DefaultParameterResolverTest {
-
- @Rule
- public ExpectedException thrown = ExpectedException.none();
-
- private DefaultParameterResolver resolver = new DefaultParameterResolver(new DefaultConversionService());
-
- @Test
- public void testParses() throws Exception {
- Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class);
-
- assertThat(resolver.resolve(
- makeMethodParameter(method, 0),
- asList("--force --name --foo y".split(" "))
- )).isEqualTo(true);
- assertThat(resolver.resolve(
- makeMethodParameter(method, 1),
- asList("--force --name --foo y".split(" "))
- )).isEqualTo("--foo");
- assertThat(resolver.resolve(
- makeMethodParameter(method, 2),
- asList("--force --name --foo y".split(" "))
- )).isEqualTo("y");
- assertThat(resolver.resolve(
- makeMethodParameter(method, 3),
- asList("--force --name --foo y".split(" "))
- )).isEqualTo("last");
-
- }
-
- @Test
- public void testParameterSpecifiedTwiceViaDifferentAliases() throws Exception {
- Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class);
-
- thrown.expect(IllegalArgumentException.class);
- thrown.expectMessage("Named parameter has been specified multiple times via '--bar, --baz'");
-
- resolver.resolve(
- makeMethodParameter(method, 0),
- asList("--force --name --foo y --bar x --baz z".split(" "))
- );
- }
-
- @Test
- public void testParameterSpecifiedTwiceViaSameKey() throws Exception {
- Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class);
-
- thrown.expect(IllegalArgumentException.class);
- thrown.expectMessage("Parameter for '--baz' has already been specified");
-
- resolver.resolve(
- makeMethodParameter(method, 0),
- asList("--force --name --foo y --baz x --baz z".split(" "))
- );
- }
-
- @Test
- public void testUnknownParameter() throws Exception {
- Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class);
-
- thrown.expect(IllegalArgumentException.class);
- thrown.expectMessage("Could not look up parameter for '--unknown' in " + method);
-
- resolver.resolve(
- makeMethodParameter(method, 0),
- asList("--unknown --foo bar".split(" "))
- );
- }
-
- @Test
- public void testTooMuchInput() throws Exception {
- Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class);
-
- thrown.expect(IllegalArgumentException.class);
- thrown.expectMessage("the following could not be mapped to parameters: 'leftover'");
-
- resolver.resolve(
- makeMethodParameter(method, 0),
- asList("--foo hello --name bar --force --bar well leftover".split(" "))
- );
- }
-
- private MethodParameter makeMethodParameter(Method method, int parameterIndex) {
- MethodParameter methodParameter = new MethodParameter(method, parameterIndex);
- methodParameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
- return methodParameter;
- }
-
-
-}
diff --git a/src/test/java/org/springframework/shell2/UtilsTest.java b/src/test/java/org/springframework/shell2/UtilsTest.java
index 3a7018c9..4d8ef95a 100644
--- a/src/test/java/org/springframework/shell2/UtilsTest.java
+++ b/src/test/java/org/springframework/shell2/UtilsTest.java
@@ -1,37 +1,37 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.shell2;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.junit.Test;
-
-/**
- * Tests for {@link Utils}.
- *
- * @author Eric Bottard
- */
-public class UtilsTest {
-
- @Test
- public void testUnCamelify() throws Exception {
- assertThat(Utils.unCamelify("HelloWorld")).isEqualTo("hello-world");
- assertThat(Utils.unCamelify("helloWorld")).isEqualTo("hello-world");
- assertThat(Utils.unCamelify("helloWorldHowAreYou")).isEqualTo("hello-world-how-are-you");
- assertThat(Utils.unCamelify("URL")).isEqualTo("url");
- }
-}
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.shell2;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link Utils}.
+ *
+ * @author Eric Bottard
+ */
+public class UtilsTest {
+
+ @Test
+ public void testUnCamelify() throws Exception {
+ assertThat(Utils.unCamelify("HelloWorld")).isEqualTo("hello-world");
+ assertThat(Utils.unCamelify("helloWorld")).isEqualTo("hello-world");
+ assertThat(Utils.unCamelify("helloWorldHowAreYou")).isEqualTo("hello-world-how-are-you");
+ assertThat(Utils.unCamelify("URL")).isEqualTo("url");
+ }
+}
diff --git a/src/test/java/org/springframework/shell2/commands/HelpTest.java b/src/test/java/org/springframework/shell2/commands/HelpTest.java
index 3e929ee8..61e9ea23 100644
--- a/src/test/java/org/springframework/shell2/commands/HelpTest.java
+++ b/src/test/java/org/springframework/shell2/commands/HelpTest.java
@@ -1,157 +1,157 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.shell2.commands;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.lang.reflect.Method;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.assertj.core.api.Assertions;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.junit.runner.RunWith;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.convert.support.DefaultConversionService;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.shell2.DefaultParameterResolver;
-import org.springframework.shell2.MethodTarget;
-import org.springframework.shell2.ParameterResolver;
-import org.springframework.shell2.Shell;
-import org.springframework.shell2.ShellComponent;
-import org.springframework.shell2.ShellMethod;
-import org.springframework.shell2.ShellOption;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-import org.springframework.util.FileCopyUtils;
-import org.springframework.util.ReflectionUtils;
-
-/**
- * Tests for the {@link Help} command.
- *
- * @author Eric Bottard
- */
-@RunWith(SpringJUnit4ClassRunner.class)
-@ContextConfiguration(classes = HelpTest.Config.class)
-public class HelpTest {
-
- @Autowired
- private Help help;
-
- @Rule
- public TestName testName = new TestName();
-
- @Test
- public void testCommandHelp() throws Exception {
- CharSequence help = this.help.help("first-command").toString();
- Assertions.assertThat(help).isEqualTo(sample());
- }
-
- @Test
- public void testCommandList() throws Exception {
- String list = this.help.help(null).toString();
- Assertions.assertThat(list).isEqualTo(sample());
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testUnknownCommand() throws Exception {
- this.help.help("some unknown command");
- }
-
- private String sample() throws IOException {
- InputStream is = new ClassPathResource(HelpTest.class.getSimpleName() + "-" + testName.getMethodName() + ".txt", HelpTest.class).getInputStream();
- return FileCopyUtils.copyToString(new InputStreamReader(is, "UTF-8")).replace("&", "");
- }
-
- @Configuration
- static class Config {
-
- @Bean
- public Help help() {
- return new Help(Collections.singletonList(parameterResolver()));
- }
-
- @Bean
- public Shell shell() {
- return () -> {
- Map result = new HashMap<>();
- Method method = ReflectionUtils.findMethod(Commands.class, "firstCommand", boolean.class, boolean.class, int.class, float[].class);
- MethodTarget methodTarget = new MethodTarget(method, commands(), "A rather extensive description of some command.");
- result.put("first-command", methodTarget);
- result.put("1st-command", methodTarget);
-
- method = ReflectionUtils.findMethod(Commands.class, "secondCommand");
- methodTarget = new MethodTarget(method, commands(), "The second command. This one is known under several aliases as well.");
- result.put("second-command", methodTarget);
- result.put("yet-another-command", methodTarget);
-
- method = ReflectionUtils.findMethod(Commands.class, "thirdCommand");
- methodTarget = new MethodTarget(method, commands(), "The last command.");
- result.put("third-command", methodTarget);
-
- return result;
- };
- }
-
- @Bean
- public ParameterResolver parameterResolver() {
- return new DefaultParameterResolver(new DefaultConversionService());
- }
-
- @Bean
- public Object commands() {
- return new Commands();
- }
-
- }
-
- @ShellComponent
- static class Commands {
-
- @ShellMethod(prefix = "-")
- public void firstCommand(
- // Single key and arity = 0. Help displayed on same line
- @ShellOption(help = "Whether to delete recursively", arity = 0) boolean r,
- // Multiple keys and arity 0. Help displayed on next line
- @ShellOption(help = "Do not ask for confirmation. YOLO", arity = 0, value = {"f", "-force"}) boolean force,
- // Single key, arity >= 1. Help displayed on next line. Optional
- @ShellOption(help = "The answer to everything", defaultValue = "42") int n,
- // Single key, arity > 1.
- @ShellOption(help = "Some other parameters", arity = 3) float[] o
- ) {
-
- }
-
- @ShellMethod
- public void secondCommand() {
-
- }
-
- @ShellMethod
- public void thirdCommand() {
-
- }
-
- }
-}
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.shell2.commands;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.shell2.standard.StandardParameterResolver;
+import org.springframework.shell2.MethodTarget;
+import org.springframework.shell2.ParameterResolver;
+import org.springframework.shell2.Shell;
+import org.springframework.shell2.standard.ShellComponent;
+import org.springframework.shell2.standard.ShellMethod;
+import org.springframework.shell2.standard.ShellOption;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.util.FileCopyUtils;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * Tests for the {@link Help} command.
+ *
+ * @author Eric Bottard
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = HelpTest.Config.class)
+public class HelpTest {
+
+ @Autowired
+ private Help help;
+
+ @Rule
+ public TestName testName = new TestName();
+
+ @Test
+ public void testCommandHelp() throws Exception {
+ CharSequence help = this.help.help("first-command").toString();
+ Assertions.assertThat(help).isEqualTo(sample());
+ }
+
+ @Test
+ public void testCommandList() throws Exception {
+ String list = this.help.help(null).toString();
+ Assertions.assertThat(list).isEqualTo(sample());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testUnknownCommand() throws Exception {
+ this.help.help("some unknown command");
+ }
+
+ private String sample() throws IOException {
+ InputStream is = new ClassPathResource(HelpTest.class.getSimpleName() + "-" + testName.getMethodName() + ".txt", HelpTest.class).getInputStream();
+ return FileCopyUtils.copyToString(new InputStreamReader(is, "UTF-8")).replace("&", "");
+ }
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public Help help() {
+ return new Help(Collections.singletonList(parameterResolver()));
+ }
+
+ @Bean
+ public Shell shell() {
+ return () -> {
+ Map result = new HashMap<>();
+ Method method = ReflectionUtils.findMethod(Commands.class, "firstCommand", boolean.class, boolean.class, int.class, float[].class);
+ MethodTarget methodTarget = new MethodTarget(method, commands(), "A rather extensive description of some command.");
+ result.put("first-command", methodTarget);
+ result.put("1st-command", methodTarget);
+
+ method = ReflectionUtils.findMethod(Commands.class, "secondCommand");
+ methodTarget = new MethodTarget(method, commands(), "The second command. This one is known under several aliases as well.");
+ result.put("second-command", methodTarget);
+ result.put("yet-another-command", methodTarget);
+
+ method = ReflectionUtils.findMethod(Commands.class, "thirdCommand");
+ methodTarget = new MethodTarget(method, commands(), "The last command.");
+ result.put("third-command", methodTarget);
+
+ return result;
+ };
+ }
+
+ @Bean
+ public ParameterResolver parameterResolver() {
+ return new StandardParameterResolver(new DefaultConversionService());
+ }
+
+ @Bean
+ public Object commands() {
+ return new Commands();
+ }
+
+ }
+
+ @ShellComponent
+ static class Commands {
+
+ @ShellMethod(prefix = "--")
+ public void firstCommand(
+ // Single key and arity = 0. Help displayed on same line
+ @ShellOption(help = "Whether to delete recursively", arity = 0, value = "-r") boolean r,
+ // Multiple keys and arity 0. Help displayed on next line
+ @ShellOption(help = "Do not ask for confirmation. YOLO", arity = 0, value = {"-f", "--force"}) boolean force,
+ // Single key, arity >= 1. Help displayed on next line. Optional
+ @ShellOption(help = "The answer to everything", defaultValue = "42", value = "-n") int n,
+ // Single key, arity > 1.
+ @ShellOption(help = "Some other parameters", arity = 3, value = "-o") float[] o
+ ) {
+
+ }
+
+ @ShellMethod
+ public void secondCommand() {
+
+ }
+
+ @ShellMethod
+ public void thirdCommand() {
+
+ }
+
+ }
+}
diff --git a/src/test/java/org/springframework/shell2/jcommander/FieldCollins.java b/src/test/java/org/springframework/shell2/jcommander/FieldCollins.java
index a5243dd1..9a50492f 100644
--- a/src/test/java/org/springframework/shell2/jcommander/FieldCollins.java
+++ b/src/test/java/org/springframework/shell2/jcommander/FieldCollins.java
@@ -1,61 +1,61 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.shell2.jcommander;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import com.beust.jcommander.Parameter;
-
-/**
- * Created by ericbottard on 15/12/15.
- */
-public class FieldCollins {
-
- @Parameter(names = "--name")
- private String name;
-
- @Parameter(names = "-level")
- private int level;
-
- @Parameter(description = "rest")
- private List rest = new ArrayList<>();
-
- public List getRest() {
- return rest;
- }
-
- public void setRest(List rest) {
- this.rest = rest;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public int getLevel() {
- return level;
- }
-
- public void setLevel(int level) {
- this.level = level;
- }
-}
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.shell2.jcommander;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.beust.jcommander.Parameter;
+
+/**
+ * Created by ericbottard on 15/12/15.
+ */
+public class FieldCollins {
+
+ @Parameter(names = "--name")
+ private String name;
+
+ @Parameter(names = "-level")
+ private int level;
+
+ @Parameter(description = "rest")
+ private List rest = new ArrayList<>();
+
+ public List getRest() {
+ return rest;
+ }
+
+ public void setRest(List rest) {
+ this.rest = rest;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getLevel() {
+ return level;
+ }
+
+ public void setLevel(int level) {
+ this.level = level;
+ }
+}
diff --git a/src/test/java/org/springframework/shell2/jcommander/JCommanderParameterResolverTest.java b/src/test/java/org/springframework/shell2/jcommander/JCommanderParameterResolverTest.java
index e89d1411..37be2556 100644
--- a/src/test/java/org/springframework/shell2/jcommander/JCommanderParameterResolverTest.java
+++ b/src/test/java/org/springframework/shell2/jcommander/JCommanderParameterResolverTest.java
@@ -1,59 +1,61 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.shell2.jcommander;
-
-import org.junit.Test;
-import org.springframework.core.MethodParameter;
-import org.springframework.util.ReflectionUtils;
-
-import java.lang.reflect.Method;
-
-import static java.util.Arrays.asList;
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * Created by ericbottard on 15/12/15.
- */
-public class JCommanderParameterResolverTest {
-
- private static final Method COMMAND_METHOD = ReflectionUtils.findMethod(MyLordCommands.class, "genesis", FieldCollins.class);
-
- private JCommanderParameterResolver resolver = new JCommanderParameterResolver();
-
- @Test
- public void testSupportsJCommanderPojos() throws Exception {
- assertThat(resolver.supports(new MethodParameter(COMMAND_METHOD, 0))).isEqualTo(true);
- }
-
- @Test
- public void testDoesNotSupportsNonJCommanderPojos() throws Exception {
- Method method = ReflectionUtils.findMethod(MyLordCommands.class, "apocalypse", String.class);
-
- assertThat(resolver.supports(new MethodParameter(method, 0))).isFalse();
- }
-
- @Test
- public void testPojoValuesAreCorrectlySet() {
- MethodParameter methodParameter = new MethodParameter(COMMAND_METHOD, 0);
-
- FieldCollins resolved = (FieldCollins) resolver.resolve(methodParameter, asList("--name foo -level 2 something-else yet-something-else".split(" ")));
-
- assertThat(resolved.getName()).isEqualTo("foo");
- assertThat(resolved.getLevel()).isEqualTo(2);
- assertThat(resolved.getRest()).containsOnlyOnce("something-else", "yet-something-else");
- }
-}
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.shell2.jcommander;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.lang.reflect.Method;
+
+import org.junit.Test;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.shell2.Utils;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * Created by ericbottard on 15/12/15.
+ */
+public class JCommanderParameterResolverTest {
+
+ private static final Method COMMAND_METHOD = ReflectionUtils.findMethod(MyLordCommands.class, "genesis", FieldCollins.class);
+
+ private JCommanderParameterResolver resolver = new JCommanderParameterResolver();
+
+ @Test
+ public void testSupportsJCommanderPojos() throws Exception {
+ assertThat(resolver.supports(Utils.createMethodParameter(COMMAND_METHOD, 0))).isEqualTo(true);
+ }
+
+ @Test
+ public void testDoesNotSupportsNonJCommanderPojos() throws Exception {
+ Method method = ReflectionUtils.findMethod(MyLordCommands.class, "apocalypse", String.class);
+
+ assertThat(resolver.supports(Utils.createMethodParameter(method, 0))).isFalse();
+ }
+
+ @Test
+ public void testPojoValuesAreCorrectlySet() {
+ MethodParameter methodParameter = Utils.createMethodParameter(COMMAND_METHOD, 0);
+
+ FieldCollins resolved = (FieldCollins) resolver.resolve(methodParameter, asList("--name foo -level 2 something-else yet-something-else".split(" ")));
+
+ assertThat(resolved.getName()).isEqualTo("foo");
+ assertThat(resolved.getLevel()).isEqualTo(2);
+ assertThat(resolved.getRest()).containsOnlyOnce("something-else", "yet-something-else");
+ }
+}
diff --git a/src/test/java/org/springframework/shell2/jcommander/MyLordCommands.java b/src/test/java/org/springframework/shell2/jcommander/MyLordCommands.java
index d47bc86b..236af2d5 100644
--- a/src/test/java/org/springframework/shell2/jcommander/MyLordCommands.java
+++ b/src/test/java/org/springframework/shell2/jcommander/MyLordCommands.java
@@ -1,32 +1,32 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.shell2.jcommander;
-
-/**
- * Created by ericbottard on 15/12/15.
- */
-public class MyLordCommands {
-
- public void genesis(FieldCollins fieldCollins) {
-
- }
-
- public void apocalypse(String param) {
-
- }
-
-}
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.shell2.jcommander;
+
+/**
+ * Created by ericbottard on 15/12/15.
+ */
+public class MyLordCommands {
+
+ public void genesis(FieldCollins fieldCollins) {
+
+ }
+
+ public void apocalypse(String param) {
+
+ }
+
+}
diff --git a/src/test/java/org/springframework/shell2/legacy/ArtifactType.java b/src/test/java/org/springframework/shell2/legacy/ArtifactType.java
index e8c5849f..10891465 100644
--- a/src/test/java/org/springframework/shell2/legacy/ArtifactType.java
+++ b/src/test/java/org/springframework/shell2/legacy/ArtifactType.java
@@ -1,25 +1,25 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.shell2.legacy;
-
-/**
- * Created by ericbottard on 09/12/15.
- */
-public enum ArtifactType {
-
- source, processor, sink, task
-}
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.shell2.legacy;
+
+/**
+ * Created by ericbottard on 09/12/15.
+ */
+public enum ArtifactType {
+
+ source, processor, sink, task
+}
diff --git a/src/test/java/org/springframework/shell2/legacy/LegacyCommands.java b/src/test/java/org/springframework/shell2/legacy/LegacyCommands.java
index 39b3e5d9..aeb4f7c1 100644
--- a/src/test/java/org/springframework/shell2/legacy/LegacyCommands.java
+++ b/src/test/java/org/springframework/shell2/legacy/LegacyCommands.java
@@ -1,61 +1,61 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.shell2.legacy;
-
-import java.lang.reflect.Method;
-
-import org.springframework.shell.core.CommandMarker;
-import org.springframework.shell.core.annotation.CliCommand;
-import org.springframework.shell.core.annotation.CliOption;
-import org.springframework.util.ReflectionUtils;
-
-/**
- * Created by ericbottard on 09/12/15.
- */
-public class LegacyCommands implements CommandMarker {
-
- public static final Method REGISTER_METHOD = ReflectionUtils.findMethod(LegacyCommands.class, "register", String.class, ArtifactType.class, String.class, boolean.class);
- public static final Method SUM_METHOD = ReflectionUtils.findMethod(LegacyCommands.class, "sum", int.class, int.class);
-
- @CliCommand(value = "register module", help = "Register a new module")
- public String register(
- @CliOption(mandatory = true,
- key = {"", "name"},
- help = "the name for the registered module")
- String name,
- @CliOption(mandatory = true,
- key = {"type"},
- help = "the type for the registered module")
- ArtifactType type,
- @CliOption(mandatory = true,
- key = {"coordinates", "coords"},
- help = "coordinates to the module archive")
- String coordinates,
- @CliOption(key = "force",
- help = "force update if module already exists (only if not in use)",
- specifiedDefaultValue = "true",
- unspecifiedDefaultValue = "false")
- boolean force) {
- return String.format(("Successfully registered module '%s:%s'"), type, name);
- }
-
- @CliCommand(value = "sum", help = "adds two numbers")
- public int sum(@CliOption(key = "v1", unspecifiedDefaultValue = "38") int a, @CliOption(key = "v2", specifiedDefaultValue = "42") int b) {
- return a + b;
- }
-
-}
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.shell2.legacy;
+
+import java.lang.reflect.Method;
+
+import org.springframework.shell.core.CommandMarker;
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * Created by ericbottard on 09/12/15.
+ */
+public class LegacyCommands implements CommandMarker {
+
+ public static final Method REGISTER_METHOD = ReflectionUtils.findMethod(LegacyCommands.class, "register", String.class, ArtifactType.class, String.class, boolean.class);
+ public static final Method SUM_METHOD = ReflectionUtils.findMethod(LegacyCommands.class, "sum", int.class, int.class);
+
+ @CliCommand(value = "register module", help = "Register a new module")
+ public String register(
+ @CliOption(mandatory = true,
+ key = {"", "name"},
+ help = "the name for the registered module")
+ String name,
+ @CliOption(mandatory = true,
+ key = {"type"},
+ help = "the type for the registered module")
+ ArtifactType type,
+ @CliOption(mandatory = true,
+ key = {"coordinates", "coords"},
+ help = "coordinates to the module archive")
+ String coordinates,
+ @CliOption(key = "force",
+ help = "force update if module already exists (only if not in use)",
+ specifiedDefaultValue = "true",
+ unspecifiedDefaultValue = "false")
+ boolean force) {
+ return String.format(("Successfully registered module '%s:%s'"), type, name);
+ }
+
+ @CliCommand(value = "sum", help = "adds two numbers")
+ public int sum(@CliOption(key = "v1", unspecifiedDefaultValue = "38") int a, @CliOption(key = "v2", specifiedDefaultValue = "42") int b) {
+ return a + b;
+ }
+
+}
diff --git a/src/test/java/org/springframework/shell2/legacy/LegacyMethodTargetResolverTest.java b/src/test/java/org/springframework/shell2/legacy/LegacyMethodTargetResolverTest.java
index 907e0c25..42dec593 100644
--- a/src/test/java/org/springframework/shell2/legacy/LegacyMethodTargetResolverTest.java
+++ b/src/test/java/org/springframework/shell2/legacy/LegacyMethodTargetResolverTest.java
@@ -1,76 +1,76 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.shell2.legacy;
-
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.shell2.MethodTarget;
-import org.springframework.shell2.MethodTargetResolver;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-
-import java.util.Map;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.data.MapEntry.entry;
-
-/**
- * Created by ericbottard on 09/12/15.
- */
-@RunWith(SpringJUnit4ClassRunner.class)
-@ContextConfiguration(classes = LegacyMethodTargetResolverTest.Config.class)
-public class LegacyMethodTargetResolverTest {
-
- @Autowired
- private ApplicationContext applicationContext;
-
- @Autowired
- private LegacyCommands legacyCommands;
-
- @Autowired
- private MethodTargetResolver resolver;
-
- @Test
- public void findsMethodsAnnotatedWithCliCommand() throws Exception {
- Map targets = resolver.resolve(applicationContext);
-
- assertThat(targets).contains(entry(
- "register module",
- new MethodTarget(LegacyCommands.REGISTER_METHOD, legacyCommands, "Register a new module" )
- ));
- }
-
- @Configuration
- static class Config {
-
- @Bean
- public LegacyCommands legacyCommands() {
- return new LegacyCommands();
- }
-
- @Bean
- public MethodTargetResolver methodTargetResolver() {
- return new LegacyMethodTargetResolver();
- }
- }
-
-}
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.shell2.legacy;
+
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.shell2.MethodTarget;
+import org.springframework.shell2.MethodTargetResolver;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.data.MapEntry.entry;
+
+/**
+ * Created by ericbottard on 09/12/15.
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = LegacyMethodTargetResolverTest.Config.class)
+public class LegacyMethodTargetResolverTest {
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @Autowired
+ private LegacyCommands legacyCommands;
+
+ @Autowired
+ private MethodTargetResolver resolver;
+
+ @Test
+ public void findsMethodsAnnotatedWithCliCommand() throws Exception {
+ Map targets = resolver.resolve(applicationContext);
+
+ assertThat(targets).contains(entry(
+ "register module",
+ new MethodTarget(LegacyCommands.REGISTER_METHOD, legacyCommands, "Register a new module" )
+ ));
+ }
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public LegacyCommands legacyCommands() {
+ return new LegacyCommands();
+ }
+
+ @Bean
+ public MethodTargetResolver methodTargetResolver() {
+ return new LegacyMethodTargetResolver();
+ }
+ }
+
+}
diff --git a/src/test/java/org/springframework/shell2/legacy/LegacyParameterResolverTest.java b/src/test/java/org/springframework/shell2/legacy/LegacyParameterResolverTest.java
index 210a091d..95f912b5 100644
--- a/src/test/java/org/springframework/shell2/legacy/LegacyParameterResolverTest.java
+++ b/src/test/java/org/springframework/shell2/legacy/LegacyParameterResolverTest.java
@@ -1,191 +1,184 @@
-/*
- * Copyright 2015 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.shell2.legacy;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.DefaultParameterNameDiscoverer;
-import org.springframework.core.MethodParameter;
-import org.springframework.shell.converters.BooleanConverter;
-import org.springframework.shell.converters.EnumConverter;
-import org.springframework.shell.converters.StringConverter;
-import org.springframework.shell.core.Converter;
-import org.springframework.shell2.ParameterResolver;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-
-import java.lang.reflect.Method;
-
-import static java.util.Arrays.asList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.springframework.shell2.legacy.LegacyCommands.REGISTER_METHOD;
-
-@RunWith(SpringJUnit4ClassRunner.class)
-@ContextConfiguration(classes = LegacyParameterResolverTest.Config.class)
-public class LegacyParameterResolverTest {
-
- private static final int NAME_OR_ANONYMOUS = 0;
- private static final int TYPE = 1;
- private static final int COORDINATES = 2;
- private static final int FORCE = 3;
-
- @Autowired
- ParameterResolver parameterResolver;
-
- @Rule
- public ExpectedException thrown = ExpectedException.none();
-
- @Test
- public void supportsParameterAnnotatedWithCliOption() throws Exception {
- MethodParameter methodParameter = buildMethodParameter(REGISTER_METHOD, NAME_OR_ANONYMOUS);
-
- boolean result = parameterResolver.supports(methodParameter);
-
- assertThat(result).isTrue();
- }
-
- @Test
- public void resolvesParameterAnnotatedWithCliOption() throws Exception {
- MethodParameter methodParameter = buildMethodParameter(REGISTER_METHOD, NAME_OR_ANONYMOUS);
-
- Object result = resolve(methodParameter, "--foo bar --name baz --qix bux");
-
- assertThat(result).isEqualTo("baz");
- }
-
- @Test
- public void resolvesAnonymousParameterAnnotatedWithCliOption() throws Exception {
- MethodParameter methodParameter = buildMethodParameter(REGISTER_METHOD, NAME_OR_ANONYMOUS);
-
- Object result = resolve(methodParameter, "--foo bar baz --qix bux");
- assertThat(result).isEqualTo("baz");
-
- // As first param
- result = resolve(methodParameter, "baz --foo bar --qix bux");
- assertThat(result).isEqualTo("baz");
- }
-
- @Test
- public void usesLegacyConverters() throws Exception {
- MethodParameter methodParameter = buildMethodParameter(REGISTER_METHOD, TYPE);
-
- Object result = resolve(methodParameter, "--foo bar --name baz --qix bux --type processor");
-
- assertThat(result).isSameAs(ArtifactType.processor);
- }
-
- @Test
- public void testUnspecifiedDefaultValue() throws Exception {
- MethodParameter methodParameter = buildMethodParameter(REGISTER_METHOD, FORCE);
-
- Object result = resolve(methodParameter, "--foo bar --name baz --qix bux");
-
- assertThat(result).isEqualTo(false);
- }
-
- @Test
- public void testSpecifiedDefaultValue() throws Exception {
- MethodParameter methodParameter = buildMethodParameter(REGISTER_METHOD, FORCE);
-
- assertThat(resolve(methodParameter, "--force --foo bar --name baz --qix bux")).isEqualTo(true);
- assertThat(resolve(methodParameter, "--foo bar --name baz --qix bux --force")).isEqualTo(true);
- }
-
- @Test
- public void testParameterNotFound() throws Exception {
- MethodParameter methodParameter = buildMethodParameter(REGISTER_METHOD, COORDINATES);
-
- thrown.expect(IllegalArgumentException.class);
- thrown.expectMessage("Could not find parameter values for [--coordinates, --coords] in [--force, --foo, bar, --name, baz, --qix, bux]");
- resolve(methodParameter, "--force --foo bar --name baz --qix bux");
- }
-
- @Test
- public void testParameterFoundWithSameNameTooManyTimes() throws Exception {
- MethodParameter methodParameter = buildMethodParameter(REGISTER_METHOD, COORDINATES);
-
- thrown.expect(IllegalArgumentException.class);
- thrown.expectMessage("Option --coordinates has already been set");
- resolve(methodParameter, "--force --coordinates bar --coordinates baz --qix bux");
- }
-
- @Test
- public void testNoConverterFound() throws Exception {
- MethodParameter methodParameter = buildMethodParameter(LegacyCommands.SUM_METHOD, 0);
-
- thrown.expect(IllegalStateException.class);
- thrown.expectMessage("No converter found for --v1 from '1' to type int");
- resolve(methodParameter, "--v1 1 --v2 2");
- }
-
- @Test
- public void testNoConverterFoundForUnspecifiedValue() throws Exception {
- MethodParameter methodParameter = buildMethodParameter(LegacyCommands.SUM_METHOD, 0);
-
- thrown.expect(IllegalStateException.class);
- thrown.expectMessage("No converter found for --v1 from '38' to type int");
- resolve(methodParameter, "--v2 2");
- }
-
- @Test
- public void testNoConverterFoundForSpecifiedValue() throws Exception {
- MethodParameter methodParameter = buildMethodParameter(LegacyCommands.SUM_METHOD, 1);
-
- thrown.expect(IllegalStateException.class);
- thrown.expectMessage("No converter found for --v2 from '42' to type int");
- resolve(methodParameter, "--v1 1 --v2");
- }
-
- private MethodParameter buildMethodParameter(Method method, int index) {
- MethodParameter methodParameter = new MethodParameter(method, index);
- methodParameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
- return methodParameter;
- }
-
- private Object resolve(MethodParameter methodParameter, String command) {
- return parameterResolver.resolve(methodParameter, asList(command.split(" ")));
- }
-
- @Configuration
- static class Config {
-
- @Bean
- public Converter stringConverter() {
- return new StringConverter();
- }
-
- @Bean
- public Converter booleanConverter() {
- return new BooleanConverter();
- }
-
- @Bean
- public Converter> enumConverter() {
- return new EnumConverter();
- }
-
- @Bean
- public ParameterResolver parameterResolver() {
- return new LegacyParameterResolver();
- }
- }
-}
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.shell2.legacy;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.shell2.legacy.LegacyCommands.REGISTER_METHOD;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.MethodParameter;
+import org.springframework.shell.converters.BooleanConverter;
+import org.springframework.shell.converters.EnumConverter;
+import org.springframework.shell.converters.StringConverter;
+import org.springframework.shell.core.Converter;
+import org.springframework.shell2.ParameterResolver;
+import org.springframework.shell2.Utils;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = LegacyParameterResolverTest.Config.class)
+public class LegacyParameterResolverTest {
+
+ private static final int NAME_OR_ANONYMOUS = 0;
+ private static final int TYPE = 1;
+ private static final int COORDINATES = 2;
+ private static final int FORCE = 3;
+
+ @Autowired
+ ParameterResolver parameterResolver;
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void supportsParameterAnnotatedWithCliOption() throws Exception {
+ MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, NAME_OR_ANONYMOUS);
+
+ boolean result = parameterResolver.supports(methodParameter);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void resolvesParameterAnnotatedWithCliOption() throws Exception {
+ MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, NAME_OR_ANONYMOUS);
+
+ Object result = resolve(methodParameter, "--foo bar --name baz --qix bux");
+
+ assertThat(result).isEqualTo("baz");
+ }
+
+ @Test
+ public void resolvesAnonymousParameterAnnotatedWithCliOption() throws Exception {
+ MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, NAME_OR_ANONYMOUS);
+
+ Object result = resolve(methodParameter, "--foo bar baz --qix bux");
+ assertThat(result).isEqualTo("baz");
+
+ // As first param
+ result = resolve(methodParameter, "baz --foo bar --qix bux");
+ assertThat(result).isEqualTo("baz");
+ }
+
+ @Test
+ public void usesLegacyConverters() throws Exception {
+ MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, TYPE);
+
+ Object result = resolve(methodParameter, "--foo bar --name baz --qix bux --type processor");
+
+ assertThat(result).isSameAs(ArtifactType.processor);
+ }
+
+ @Test
+ public void testUnspecifiedDefaultValue() throws Exception {
+ MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, FORCE);
+
+ Object result = resolve(methodParameter, "--foo bar --name baz --qix bux");
+
+ assertThat(result).isEqualTo(false);
+ }
+
+ @Test
+ public void testSpecifiedDefaultValue() throws Exception {
+ MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, FORCE);
+
+ assertThat(resolve(methodParameter, "--force --foo bar --name baz --qix bux")).isEqualTo(true);
+ assertThat(resolve(methodParameter, "--foo bar --name baz --qix bux --force")).isEqualTo(true);
+ }
+
+ @Test
+ public void testParameterNotFound() throws Exception {
+ MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, COORDINATES);
+
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Could not find parameter values for [--coordinates, --coords] in [--force, --foo, bar, --name, baz, --qix, bux]");
+ resolve(methodParameter, "--force --foo bar --name baz --qix bux");
+ }
+
+ @Test
+ public void testParameterFoundWithSameNameTooManyTimes() throws Exception {
+ MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, COORDINATES);
+
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Option --coordinates has already been set");
+ resolve(methodParameter, "--force --coordinates bar --coordinates baz --qix bux");
+ }
+
+ @Test
+ public void testNoConverterFound() throws Exception {
+ MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.SUM_METHOD, 0);
+
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("No converter found for --v1 from '1' to type int");
+ resolve(methodParameter, "--v1 1 --v2 2");
+ }
+
+ @Test
+ public void testNoConverterFoundForUnspecifiedValue() throws Exception {
+ MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.SUM_METHOD, 0);
+
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("No converter found for --v1 from '38' to type int");
+ resolve(methodParameter, "--v2 2");
+ }
+
+ @Test
+ public void testNoConverterFoundForSpecifiedValue() throws Exception {
+ MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.SUM_METHOD, 1);
+
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("No converter found for --v2 from '42' to type int");
+ resolve(methodParameter, "--v1 1 --v2");
+ }
+
+ private Object resolve(MethodParameter methodParameter, String command) {
+ return parameterResolver.resolve(methodParameter, asList(command.split(" ")));
+ }
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public Converter stringConverter() {
+ return new StringConverter();
+ }
+
+ @Bean
+ public Converter booleanConverter() {
+ return new BooleanConverter();
+ }
+
+ @Bean
+ public Converter> enumConverter() {
+ return new EnumConverter();
+ }
+
+ @Bean
+ public ParameterResolver parameterResolver() {
+ return new LegacyParameterResolver();
+ }
+ }
+}
diff --git a/src/test/java/org/springframework/shell2/standard/Remote.java b/src/test/java/org/springframework/shell2/standard/Remote.java
new file mode 100644
index 00000000..0f7b3025
--- /dev/null
+++ b/src/test/java/org/springframework/shell2/standard/Remote.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.shell2.standard;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.shell2.CompletionContext;
+import org.springframework.shell2.CompletionProposal;
+
+/**
+ * An example commands class.
+ *
+ * @author Eric Bottard
+ * @author Florent Biville
+ */
+public class Remote {
+
+ /**
+ * A command method that showcases
+ * - default handling for booleans (force)
+ * - default parameter name discovery (name)
+ * - default value supplying (foo and bar)
+ *
+ */
+ @ShellMethod(help = "switch channels")
+ public void zap(boolean force,
+ String name,
+ @ShellOption(defaultValue="defoolt") String foo,
+ @ShellOption(value = {"--bar", "--baz"}, defaultValue = "last") String bar) {
+
+ }
+
+ @ShellMethod(help = "bye bye")
+ public void shutdown(@ShellOption Delay delay) {
+
+ }
+
+ @ShellMethod(help = "add 3 numbers together")
+ public void add(@ShellOption(arity = 3, valueProvider = NumberValueProvider.class) List numbers) {
+
+ }
+
+ public enum Delay {
+ small, medium, big;
+ }
+
+
+ public static class NumberValueProvider extends ValueProviderSupport {
+
+ @Override
+ public List complete(MethodParameter parameter, CompletionContext completionContext, String[] hints) {
+ String prefix = completionContext.currentWord() != null ? completionContext.currentWord() : "";
+ return Arrays.asList("12", "42", "7").stream()
+ .filter(n -> n.startsWith(prefix))
+ .map(n -> new CompletionProposal(n))
+ .collect(Collectors.toList());
+ }
+ }
+}
diff --git a/src/test/java/org/springframework/shell2/standard/StandardParameterResolverTest.java b/src/test/java/org/springframework/shell2/standard/StandardParameterResolverTest.java
new file mode 100644
index 00000000..8abbb811
--- /dev/null
+++ b/src/test/java/org/springframework/shell2/standard/StandardParameterResolverTest.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.shell2.standard;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.util.ReflectionUtils.findMethod;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.jline.reader.ParsedLine;
+import org.jline.reader.impl.DefaultParser;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.shell2.CompletionContext;
+import org.springframework.shell2.CompletionProposal;
+import org.springframework.shell2.ParameterMissingResolutionException;
+import org.springframework.shell2.UnfinishedParameterResolutionException;
+import org.springframework.shell2.Utils;
+
+/**
+ * Unit tests for DefaultParameterResolver.
+ * @author Eric Bottard
+ * @author Florent Biville
+ */
+public class StandardParameterResolverTest {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ private StandardParameterResolver resolver = new StandardParameterResolver(new DefaultConversionService());
+
+ // Tests for resolution
+
+ @Test
+ public void testParses() throws Exception {
+ Method method = findMethod(org.springframework.shell2.standard.Remote.class, "zap", boolean.class, String.class, String.class, String.class);
+
+ assertThat(resolver.resolve(
+ Utils.createMethodParameter(method, 0),
+ asList("--force --name --foo y".split(" "))
+ )).isEqualTo(true);
+ assertThat(resolver.resolve(
+ Utils.createMethodParameter(method, 1),
+ asList("--force --name --foo y".split(" "))
+ )).isEqualTo("--foo");
+ assertThat(resolver.resolve(
+ Utils.createMethodParameter(method, 2),
+ asList("--force --name --foo y".split(" "))
+ )).isEqualTo("y");
+ assertThat(resolver.resolve(
+ Utils.createMethodParameter(method, 3),
+ asList("--force --name --foo y".split(" "))
+ )).isEqualTo("last");
+
+ }
+
+ @Test
+ public void testParameterSpecifiedTwiceViaDifferentAliases() throws Exception {
+ Method method = findMethod(org.springframework.shell2.standard.Remote.class, "zap", boolean.class, String.class, String.class, String.class);
+
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Named parameter has been specified multiple times via '--bar, --baz'");
+
+ resolver.resolve(
+ Utils.createMethodParameter(method, 0),
+ asList("--force --name --foo y --bar x --baz z".split(" "))
+ );
+ }
+
+ @Test
+ public void testParameterSpecifiedTwiceViaSameKey() throws Exception {
+ Method method = findMethod(org.springframework.shell2.standard.Remote.class, "zap", boolean.class, String.class, String.class, String.class);
+
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Parameter for '--baz' has already been specified");
+
+ resolver.resolve(
+ Utils.createMethodParameter(method, 0),
+ asList("--force --name --foo y --baz x --baz z".split(" "))
+ );
+ }
+
+ @Test
+ public void testTooMuchInput() throws Exception {
+ Method method = findMethod(org.springframework.shell2.standard.Remote.class, "zap", boolean.class, String.class, String.class, String.class);
+
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("the following could not be mapped to parameters: 'leftover'");
+
+ resolver.resolve(
+ Utils.createMethodParameter(method, 0),
+ asList("--foo hello --name bar --force --bar well leftover".split(" "))
+ );
+ }
+
+ @Test
+ public void testIncompleteCommandResolution() throws Exception {
+ Method method = findMethod(org.springframework.shell2.standard.Remote.class, "shutdown", org.springframework.shell2.standard.Remote.Delay.class);
+
+ thrown.expect(UnfinishedParameterResolutionException.class);
+ thrown.expectMessage("Error trying to resolve '--delay delay' using [--delay]");
+
+ resolver.resolve(
+ Utils.createMethodParameter(method, 0),
+ asList("--delay".split(" "))
+ );
+ }
+
+ @Test
+ public void testIncompleteCommandResolutionBigArity() throws Exception {
+ Method method = findMethod(org.springframework.shell2.standard.Remote.class, "add", List.class);
+
+ thrown.expect(UnfinishedParameterResolutionException.class);
+ thrown.expectMessage("Error trying to resolve '--numbers list list list' using [--numbers 1 2]");
+
+ resolver.resolve(
+ Utils.createMethodParameter(method, 0),
+ asList("--numbers 1 2".split(" "))
+ );
+ }
+
+ @Test
+ public void testUnresolvableArg() throws Exception {
+ Method method = findMethod(org.springframework.shell2.standard.Remote.class, "zap", boolean.class, String.class, String.class, String.class);
+
+ thrown.expect(ParameterMissingResolutionException.class);
+ thrown.expectMessage("Parameter '--name string' should be specified");
+
+ resolver.resolve(
+ Utils.createMethodParameter(method, 1),
+ asList("--foo hello --force --bar well".split(" "))
+ );
+ }
+
+ // Tests for completion
+
+ @Test
+ public void testParameterKeyNotYetSetAppearsInProposals() {
+ Method method = findMethod(org.springframework.shell2.standard.Remote.class, "zap", boolean.class, String.class, String.class, String.class);
+ List completions = resolver.complete(
+ Utils.createMethodParameter(method, 1),
+ contextFor("")
+ ).stream().map(CompletionProposal::value).collect(Collectors.toList());
+ assertThat(completions).contains("--name");
+ completions = resolver.complete(
+ Utils.createMethodParameter(method, 1),
+ contextFor("--force ")
+ ).stream().map(CompletionProposal::value).collect(Collectors.toList());
+ assertThat(completions).contains("--name");
+ }
+
+ @Test
+ public void testParameterKeyNotFullySpecified() {
+ Method method = findMethod(org.springframework.shell2.standard.Remote.class, "zap", boolean.class, String.class, String.class, String.class);
+ List completions = resolver.complete(
+ Utils.createMethodParameter(method, 1),
+ contextFor("--na")
+ ).stream().map(CompletionProposal::value).collect(Collectors.toList());
+ assertThat(completions).contains("--name");
+ completions = resolver.complete(
+ Utils.createMethodParameter(method, 1),
+ contextFor("--force --na")
+ ).stream().map(CompletionProposal::value).collect(Collectors.toList());
+ assertThat(completions).contains("--name");
+ }
+
+ @Test
+ public void testNoMoreAvailableParameters() {
+ Method method = findMethod(org.springframework.shell2.standard.Remote.class, "zap", boolean.class, String.class, String.class, String.class);
+ List completions = resolver.complete(
+ Utils.createMethodParameter(method, 2), // trying to complete --foo
+ contextFor("--name ") // but input is currently focused on --name
+ ).stream().map(CompletionProposal::value).collect(Collectors.toList());
+ System.out.println(completions);
+// assertThat(completions).isEmpty();
+ }
+
+ @Test
+ public void testNotTheRightTimeToCompleteThatParameter() {
+ Method method = findMethod(org.springframework.shell2.standard.Remote.class, "shutdown", org.springframework.shell2.standard.Remote.Delay.class);
+ List completions = resolver.complete(
+ Utils.createMethodParameter(method, 0),
+ contextFor("--delay 323")
+ ).stream().map(CompletionProposal::value).collect(Collectors.toList());
+ assertThat(completions).isEmpty();
+ }
+
+ @Test
+ public void testValueCompletionWithNonDefaultArity() {
+
+ resolver.setValueProviders(Arrays.asList(new Remote.NumberValueProvider()));
+
+ Method method = findMethod(org.springframework.shell2.standard.Remote.class, "add", List.class);
+ List completions = resolver.complete(
+ Utils.createMethodParameter(method, 0),
+ contextFor("--numbers ")
+ ).stream().map(CompletionProposal::value).collect(Collectors.toList());
+ assertThat(completions).contains("12");
+
+ completions = resolver.complete(
+ Utils.createMethodParameter(method, 0),
+ contextFor("--numbers 42 ")
+ ).stream().map(CompletionProposal::value).collect(Collectors.toList());
+ assertThat(completions).contains("12");
+
+ completions = resolver.complete(
+ Utils.createMethodParameter(method, 0),
+ contextFor("--numbers 42 34 ")
+ ).stream().map(CompletionProposal::value).collect(Collectors.toList());
+ assertThat(completions).contains("12");
+
+ completions = resolver.complete(
+ Utils.createMethodParameter(method, 0),
+ contextFor("--numbers 42 34 66 ")
+ ).stream().map(CompletionProposal::value).collect(Collectors.toList());
+ assertThat(completions).isEmpty();
+ }
+
+
+
+ private CompletionContext contextFor(String input) {
+ DefaultParser defaultParser = new DefaultParser();
+ ParsedLine parsed = defaultParser.parse(input, input.length());
+ List words = parsed.words().stream().filter(w -> w.length() > 0).collect(Collectors.toList());
+
+ return new CompletionContext(words, parsed.wordIndex(), parsed.wordCursor());
+ }
+}