Change to boot 2.x
- disable PropertyPlaceholderAutoConfiguration in NoAutoConf - Remove shell1 deps - Update README
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
= Spring Shell 2
|
||||
= Spring Shell 3
|
||||
|
||||
Spring Shell 2 is an ongoing effort to completely rewrite
|
||||
https://github.com/spring-projects/spring-shell[Spring Shell] from scratch, making it more modular
|
||||
while maintaining some level of backward compatibility with your existing commands.
|
||||
Spring Shell 3 is a work to solely depend on Spring Boot 2.x and not trying to keep
|
||||
any backward compatibility with older Spring Shell 1.x nor Spring Boot 1.x.
|
||||
|
||||
== Building
|
||||
```
|
||||
|
||||
15
pom.xml
15
pom.xml
@@ -11,7 +11,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>1.5.8.RELEASE</version>
|
||||
<version>2.0.6.RELEASE</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
<scm>
|
||||
@@ -26,7 +26,6 @@
|
||||
<assertj.version>3.8.0</assertj.version>
|
||||
<java.version>1.8</java.version>
|
||||
<jcommander.version>1.48</jcommander.version>
|
||||
<shell1.version>1.2.0.RELEASE</shell1.version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
@@ -35,7 +34,6 @@
|
||||
<module>spring-shell-standard</module>
|
||||
<module>spring-shell-standard-commands</module>
|
||||
<module>spring-shell-jcommander-adapter</module>
|
||||
<module>spring-shell-shell1-adapter</module>
|
||||
<module>spring-shell-starter</module>
|
||||
<module>spring-shell-table</module>
|
||||
<module>spring-shell-docs</module>
|
||||
@@ -64,11 +62,6 @@
|
||||
<artifactId>spring-shell-standard-commands</artifactId>
|
||||
<version>3.0.0.BUILD-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.shell</groupId>
|
||||
<artifactId>spring-shell-shell1-adapter</artifactId>
|
||||
<version>3.0.0.BUILD-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.shell</groupId>
|
||||
<artifactId>spring-shell-jcommander-adapter</artifactId>
|
||||
@@ -108,12 +101,6 @@
|
||||
<version>${assertj.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- The following dependency is Spring Shell 1. Note the spring-shell artifactId is not used by Shell 2-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.shell</groupId>
|
||||
<artifactId>spring-shell</artifactId>
|
||||
<version>${shell1.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
@@ -37,16 +37,6 @@
|
||||
<artifactId>spring-shell-standard-commands</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.shell</groupId>
|
||||
<artifactId>spring-shell-shell1-adapter</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.shell</groupId>
|
||||
<artifactId>spring-shell</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.shell</groupId>
|
||||
<artifactId>spring-shell-jcommander-adapter</artifactId>
|
||||
|
||||
@@ -17,14 +17,12 @@
|
||||
package org.springframework.shell.samples.noautoconf;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.shell.SpringShellAutoConfiguration;
|
||||
import org.springframework.shell.jcommander.JCommanderParameterResolverAutoConfiguration;
|
||||
import org.springframework.shell.jline.JLineShellAutoConfiguration;
|
||||
import org.springframework.shell.legacy.LegacyAdapterAutoConfiguration;
|
||||
import org.springframework.shell.samples.jcommander.JCommanderCommands;
|
||||
import org.springframework.shell.samples.legacy.LegacyCommands;
|
||||
import org.springframework.shell.samples.standard.Commands;
|
||||
@@ -46,12 +44,11 @@ import org.springframework.shell.standard.commands.StandardCommandsAutoConfigura
|
||||
JLineShellAutoConfiguration.class,
|
||||
// Various Resolvers
|
||||
JCommanderParameterResolverAutoConfiguration.class,
|
||||
LegacyAdapterAutoConfiguration.class,
|
||||
StandardAPIAutoConfiguration.class,
|
||||
// Built-In Commands
|
||||
StandardCommandsAutoConfiguration.class,
|
||||
// Allows ${} support
|
||||
PropertyPlaceholderAutoConfiguration.class,
|
||||
//PropertyPlaceholderAutoConfiguration.class,
|
||||
// Sample Commands
|
||||
JCommanderCommands.class,
|
||||
LegacyCommands.class,
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-shell-shell1-adapter</artifactId>
|
||||
<name>Spring Shell 1 Adapter</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.shell</groupId>
|
||||
<artifactId>spring-shell-parent</artifactId>
|
||||
<version>3.0.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<description>Adapter classes to enable Shell 1 type annotations via Spring Shell 2</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.shell</groupId>
|
||||
<artifactId>spring-shell-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- The following dependency is Spring Shell 1. Note the spring-shell artifactId is not used by Shell 2-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.shell</groupId>
|
||||
<artifactId>spring-shell</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.shell</groupId>
|
||||
<artifactId>spring-shell-core-test-support</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 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.shell.legacy;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.shell.converters.ArrayConverter;
|
||||
import org.springframework.shell.converters.AvailableCommandsConverter;
|
||||
import org.springframework.shell.converters.FileConverter;
|
||||
import org.springframework.shell.converters.SimpleFileConverter;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Main configuration class for the Shell 2 - Shell 1 adapter.
|
||||
*
|
||||
* @author Camilo Gonzalez
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(CliCommand.class)
|
||||
@ComponentScan(basePackageClasses = {ArrayConverter.class}, excludeFilters = @ComponentScan.Filter(
|
||||
type = FilterType.ASSIGNABLE_TYPE,
|
||||
value = {AvailableCommandsConverter.class, SimpleFileConverter.class}))
|
||||
public class LegacyAdapterAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public LegacyMethodTargetRegistrar legacyMethodTargetResolver() {
|
||||
return new LegacyMethodTargetRegistrar();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LegacyParameterResolver legacyParameterResolver() {
|
||||
return new LegacyParameterResolver();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FileConverter fileConverter() {
|
||||
return new FileConverter() {
|
||||
@Override
|
||||
protected File getWorkingDirectory() {
|
||||
return new File(".");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2017 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.shell.legacy;
|
||||
|
||||
import static org.springframework.util.StringUtils.collectionToDelimitedString;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.shell.Availability;
|
||||
import org.springframework.shell.ConfigurableCommandRegistry;
|
||||
import org.springframework.shell.core.CommandMarker;
|
||||
import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
import org.springframework.shell.MethodTarget;
|
||||
import org.springframework.shell.MethodTargetRegistrar;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* A {@link MethodTargetRegistrar} that discovers methods annotated with {@link CliCommand} on beans
|
||||
* implementing the {@link CommandMarker} marker interface.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Florent Biville
|
||||
* @author Camilo Gonzalez
|
||||
*/
|
||||
@Component
|
||||
public class LegacyMethodTargetRegistrar implements MethodTargetRegistrar {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private Map<String, MethodTarget> commands = new TreeMap<>();
|
||||
|
||||
@Override
|
||||
public void register(ConfigurableCommandRegistry registry) {
|
||||
Map<String, CommandMarker> beans = applicationContext.getBeansOfType(CommandMarker.class);
|
||||
for (Object bean : beans.values()) {
|
||||
Class<?> clazz = bean.getClass();
|
||||
ReflectionUtils.doWithMethods(clazz, method -> {
|
||||
CliCommand cliCommand = method.getAnnotation(CliCommand.class);
|
||||
for (String key : cliCommand.value()) {
|
||||
Supplier<Availability> availabilityIndicator = bridgeAvailabilityIndicator(key, bean);
|
||||
MethodTarget target = new MethodTarget(method, bean, cliCommand.help(), availabilityIndicator);
|
||||
registry.register(key, target);
|
||||
commands.put(key, target);
|
||||
}
|
||||
}, method -> method.getAnnotation(CliCommand.class) != null);
|
||||
}
|
||||
}
|
||||
|
||||
private Supplier<Availability> bridgeAvailabilityIndicator(String commandKey, Object bean) {
|
||||
Class<?> clazz = bean.getClass();
|
||||
Set<Method> candidates = new HashSet<>();
|
||||
|
||||
ReflectionUtils.doWithMethods(clazz, candidates::add,
|
||||
method -> method.getAnnotation(CliAvailabilityIndicator.class) != null
|
||||
&& Arrays.asList(method.getAnnotation(CliAvailabilityIndicator.class).value()).contains(commandKey));
|
||||
|
||||
switch (candidates.size()) {
|
||||
case 0:
|
||||
return null;
|
||||
case 1:
|
||||
return () -> {
|
||||
boolean available = (Boolean) ReflectionUtils.invokeMethod(candidates.iterator().next(), bean);
|
||||
return available ? Availability.available() : Availability.unavailable("[Unknown reason]");
|
||||
};
|
||||
default:
|
||||
throw new IllegalStateException("Looks like there are several @" + CliAvailabilityIndicator.class.getSimpleName()
|
||||
+ " for '" + commandKey + "'. Found " + candidates);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + " contributing "
|
||||
+ collectionToDelimitedString(commands.keySet(), ", ", "[", "]");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,301 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2017 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.shell.legacy;
|
||||
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
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.shell.core.Completion;
|
||||
import org.springframework.shell.core.Converter;
|
||||
import org.springframework.shell.core.MethodTarget;
|
||||
import org.springframework.shell.core.annotation.CliOption;
|
||||
import org.springframework.shell.CompletionContext;
|
||||
import org.springframework.shell.CompletionProposal;
|
||||
import org.springframework.shell.ParameterDescription;
|
||||
import org.springframework.shell.ParameterResolver;
|
||||
import org.springframework.shell.ValueResult;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.ValidatorFactory;
|
||||
import javax.validation.metadata.MethodDescriptor;
|
||||
import javax.validation.metadata.ParameterDescriptor;
|
||||
|
||||
/**
|
||||
* Resolves parameters by looking at the {@link CliOption} annotation and acting
|
||||
* accordingly.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Camilo Gonzalez
|
||||
*/
|
||||
@Component
|
||||
public class LegacyParameterResolver implements ParameterResolver {
|
||||
|
||||
private static final String CLI_OPTION_NULL = "__NULL__";
|
||||
|
||||
/**
|
||||
* Prefix used by Spring Shell 1 for the argument keys (<em>e.g.</em> command --key
|
||||
* value).
|
||||
*/
|
||||
private static final String CLI_PREFIX = "--";
|
||||
|
||||
@Autowired(required = false)
|
||||
private Collection<Converter<?>> converters = new ArrayList<>();
|
||||
|
||||
private Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setValidatorFactory(ValidatorFactory validatorFactory) {
|
||||
this.validator = validatorFactory.getValidator();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter parameter) {
|
||||
return parameter.hasParameterAnnotation(CliOption.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueResult resolve(MethodParameter methodParameter, List<String> words) {
|
||||
Optional<Converter<?>> converter = findOptionalConverter(methodParameter);
|
||||
CliOption cliOption = methodParameter.getParameterAnnotation(CliOption.class);
|
||||
|
||||
Map<String, ParseResult> values = parseOptions(words);
|
||||
Map<String, ValueResult> seenValues = convertValues(values, methodParameter, converter);
|
||||
switch (seenValues.size()) {
|
||||
case 0:
|
||||
if (!cliOption.mandatory()) {
|
||||
String value = cliOption.unspecifiedDefaultValue();
|
||||
Object resolvedValue = converter
|
||||
.orElseThrow(noConverterFound(cliOption.key()[0], value, methodParameter.getParameterType()))
|
||||
.convertFromText(value, methodParameter.getParameterType(), cliOption.optionContext());
|
||||
|
||||
return new ValueResult(methodParameter, resolvedValue);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Could not find parameter values for "
|
||||
+ prettifyKeys(Arrays.asList(cliOption.key())) + " in " + words);
|
||||
}
|
||||
case 1:
|
||||
return seenValues.values().iterator().next();
|
||||
default:
|
||||
throw new RuntimeException("Option has been set multiple times via " + prettifyKeys(seenValues.keySet()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe find a Shell 1 Converter that applies to the given {@literal methodParameter}.
|
||||
*/
|
||||
private Optional<Converter<?>> findOptionalConverter(MethodParameter methodParameter) {
|
||||
CliOption cliOption = methodParameter.getParameterAnnotation(CliOption.class);
|
||||
return converters.stream()
|
||||
.filter(c -> c.supports(methodParameter.getParameterType(), cliOption.optionContext()))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ParameterDescription> describe(MethodParameter parameter) {
|
||||
Parameter jlrParameter = parameter.getMethod().getParameters()[parameter.getParameterIndex()];
|
||||
CliOption option = jlrParameter.getAnnotation(CliOption.class);
|
||||
ParameterDescription result = ParameterDescription.outOf(parameter);
|
||||
result.help(option.help());
|
||||
List<String> keys = Arrays.asList(option.key());
|
||||
result.keys(notDefaultCommandKeys(parameter));
|
||||
if (!option.mandatory()) {
|
||||
result.defaultValue(CLI_OPTION_NULL.equals(option.unspecifiedDefaultValue()) ? "null"
|
||||
: option.unspecifiedDefaultValue());
|
||||
}
|
||||
if (!CLI_OPTION_NULL.equals(option.specifiedDefaultValue())) {
|
||||
result.whenFlag(option.specifiedDefaultValue());
|
||||
}
|
||||
boolean containsEmptyKey = keys.contains("");
|
||||
result.mandatoryKey(!containsEmptyKey);
|
||||
|
||||
MethodDescriptor constraintsForMethod = validator.getConstraintsForClass(parameter.getDeclaringClass())
|
||||
.getConstraintsForMethod(parameter.getMethod().getName(), parameter.getMethod().getParameterTypes());
|
||||
if (constraintsForMethod != null) {
|
||||
ParameterDescriptor constraintsDescriptor = constraintsForMethod
|
||||
.getParameterDescriptors().get(parameter.getParameterIndex());
|
||||
result.elementDescriptor(constraintsDescriptor);
|
||||
}
|
||||
|
||||
return Stream.of(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of keys (with their "--" prefix) that can be used to set the given
|
||||
* parameter. If the parameter supports the empty key, this is not part of the result.
|
||||
*/
|
||||
private List<String> notDefaultCommandKeys(MethodParameter parameter) {
|
||||
Parameter jlrParameter = parameter.getMethod().getParameters()[parameter.getParameterIndex()];
|
||||
CliOption option = jlrParameter.getAnnotation(CliOption.class);
|
||||
return Arrays.stream(option.key())
|
||||
.filter(key -> !key.isEmpty())
|
||||
.map(key -> CLI_PREFIX + key)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CompletionProposal> complete(MethodParameter parameter, CompletionContext context) {
|
||||
String nextToLast = null;
|
||||
String last;
|
||||
if (context.getWords().size() >= 2) {
|
||||
nextToLast = context.getWords().get(context.getWords().size() - 2);
|
||||
}
|
||||
if (context.getWords().size() >= 1) {
|
||||
last = context.getWords().get(context.getWords().size() - 1);
|
||||
}
|
||||
else {
|
||||
last = null;
|
||||
}
|
||||
List<String> commandKeys = notDefaultCommandKeys(parameter);
|
||||
if (nextToLast != null) {
|
||||
if (commandKeys.contains(nextToLast)) {
|
||||
// nextToLast is our key, last is our (possibly unfinished) value
|
||||
if (findOptionalConverter(parameter).isPresent()) {
|
||||
ArrayList<Completion> legacyProposals = new ArrayList<>();
|
||||
findOptionalConverter(parameter).get().getAllPossibleValues(
|
||||
legacyProposals,
|
||||
parameter.getParameterType(),
|
||||
last,
|
||||
parameter.getParameterAnnotation(CliOption.class).optionContext(),
|
||||
craftMethodTarget()
|
||||
);
|
||||
return legacyProposals.stream()
|
||||
.filter(lp -> lp.getValue().startsWith(last))
|
||||
.map(this::toCompletionProposal)
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
} // nextToLast looks like a key, but not for this parameter
|
||||
else if (nextToLast.startsWith(CLI_PREFIX)) {
|
||||
// Not for this parameter
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
// Fallthrough: nextToLast is the value to another parameter
|
||||
// and last (possibly the empty string) could be our key
|
||||
if (last != null) {
|
||||
return commandKeys.stream()
|
||||
.filter(k -> k.startsWith(last))
|
||||
.map(CompletionProposal::new)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
// Invoked completion just after the command (without a space): my-command<TAB>
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a Shell 1 Completion into a CompletionProposal.
|
||||
*/
|
||||
private CompletionProposal toCompletionProposal(Completion c) {
|
||||
return new CompletionProposal(c.getValue())
|
||||
.displayText(c.getFormattedValue())
|
||||
.category(c.getHeading());
|
||||
}
|
||||
|
||||
// TODO pass invokable method in the completion context. Rarely used by converters, so ok for now
|
||||
private MethodTarget craftMethodTarget() {
|
||||
return new MethodTarget(ReflectionUtils.findMethod(Object.class, "toString"), "foo");
|
||||
}
|
||||
|
||||
private Map<String, ParseResult> parseOptions(List<String> words) {
|
||||
Map<String, ParseResult> values = new HashMap<>();
|
||||
for (int i = 0; i < words.size(); i++) {
|
||||
int from = i;
|
||||
String word = words.get(i);
|
||||
if (word.startsWith(CLI_PREFIX)) {
|
||||
String key = word.substring(CLI_PREFIX.length());
|
||||
// If next word doesn't exist or starts with '--', this is an unary option. Store null
|
||||
String value = i < words.size() - 1 && !words.get(i + 1).startsWith(CLI_PREFIX) ? words.get(++i) : null;
|
||||
Assert.isTrue(!values.containsKey(key),
|
||||
String.format("Option %s%s has already been set", CLI_PREFIX, key));
|
||||
values.put(key, new ParseResult(value, from));
|
||||
} // Must be the 'anonymous' option
|
||||
else {
|
||||
Assert.isTrue(!values.containsKey(""), "Anonymous option has already been set");
|
||||
values.put("", new ParseResult(word, from));
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private Map<String, ValueResult> convertValues(Map<String, ParseResult> values, MethodParameter methodParameter,
|
||||
Optional<Converter<?>> converter) {
|
||||
Map<String, ValueResult> seenValues = new HashMap<>();
|
||||
CliOption option = methodParameter.getParameterAnnotation(CliOption.class);
|
||||
for (String key : option.key()) {
|
||||
if (values.containsKey(key)) {
|
||||
ParseResult parseResult = values.get(key);
|
||||
String value = parseResult.value;
|
||||
if (value == null && !CLI_OPTION_NULL.equals(option.specifiedDefaultValue())) {
|
||||
value = option.specifiedDefaultValue();
|
||||
}
|
||||
Class<?> parameterType = methodParameter.getParameterType();
|
||||
Object resolvedValue = converter
|
||||
.orElseThrow(noConverterFound(key, value, parameterType))
|
||||
.convertFromText(value, parameterType, option.optionContext());
|
||||
int from = parseResult.from;
|
||||
int to = key.isEmpty() || parseResult.value == null ? from : from + 1;
|
||||
BitSet wordsUsed = new BitSet();
|
||||
wordsUsed.set(from, to + 1);
|
||||
BitSet wordsUsedForValues = new BitSet();
|
||||
if (parseResult.value != null) {
|
||||
wordsUsedForValues.set(to);
|
||||
}
|
||||
seenValues.put(key, new ValueResult(methodParameter, resolvedValue, wordsUsed, wordsUsedForValues));
|
||||
}
|
||||
}
|
||||
return seenValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of possible keys for an option, suitable for displaying in an error
|
||||
* message.
|
||||
*/
|
||||
private String prettifyKeys(Collection<String> keys) {
|
||||
return keys.stream().map(s -> "".equals(s) ? "<anonymous>" : CLI_PREFIX + s)
|
||||
.collect(Collectors.joining(", ", "[", "]"));
|
||||
}
|
||||
|
||||
private Supplier<IllegalStateException> noConverterFound(String key, String value, Class<?> parameterType) {
|
||||
return () -> new IllegalStateException(
|
||||
"No converter found for " + CLI_PREFIX + key + " from '" + value + "' to type " + parameterType);
|
||||
}
|
||||
|
||||
private static class ParseResult {
|
||||
private final String value;
|
||||
|
||||
private final Integer from;
|
||||
|
||||
public ParseResult(String value, Integer from) {
|
||||
this.value = value;
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides integration with Spring Shell 1.
|
||||
*/
|
||||
package org.springframework.shell.legacy;
|
||||
@@ -1,2 +0,0 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.shell.legacy.LegacyAdapterAutoConfiguration
|
||||
@@ -1,27 +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.shell.legacy;
|
||||
|
||||
/**
|
||||
* An example enum used for testing argument passing of Shell 1 commands.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public enum ArtifactType {
|
||||
|
||||
source, processor, sink, task
|
||||
}
|
||||
@@ -1,81 +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.shell.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;
|
||||
|
||||
/**
|
||||
* A set of Shell 1 commands used for tests.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
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);
|
||||
public static final Method LEGACY_ECHO_METHOD = ReflectionUtils.findMethod(LegacyCommands.class, "legacyEcho", String.class);
|
||||
public static final Method SOME_METHOD = ReflectionUtils.findMethod(LegacyCommands.class, "someMethod", String.class, boolean.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"},
|
||||
optionContext = "disable-string-converter",
|
||||
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;
|
||||
}
|
||||
|
||||
@CliCommand(value = "legacy-echo", help = "Echoes a message")
|
||||
public String legacyEcho(@CliOption(key = "", mandatory = true) String message) {
|
||||
return message;
|
||||
}
|
||||
|
||||
@CliCommand(value = "someMethod", help = "Method used for testing purposes")
|
||||
public String someMethod(
|
||||
@CliOption(key = "key", mandatory = false, help = "The optional parameter") String parameter,
|
||||
@CliOption(key = "option", help = "an option", specifiedDefaultValue = "true", unspecifiedDefaultValue = "false") boolean option) {
|
||||
return parameter + ", " + option;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,78 +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.shell.legacy;
|
||||
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.data.MapEntry.entry;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
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.shell.Command;
|
||||
import org.springframework.shell.ConfigurableCommandRegistry;
|
||||
import org.springframework.shell.MethodTarget;
|
||||
import org.springframework.shell.MethodTargetRegistrar;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link LegacyMethodTargetRegistrar}.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = LegacyMethodTargetRegistrarTest.Config.class)
|
||||
public class LegacyMethodTargetRegistrarTest {
|
||||
|
||||
@Autowired
|
||||
private LegacyCommands legacyCommands;
|
||||
|
||||
@Autowired
|
||||
private MethodTargetRegistrar resolver;
|
||||
|
||||
@Test
|
||||
public void findsMethodsAnnotatedWithCliCommand() throws Exception {
|
||||
ConfigurableCommandRegistry registry = new ConfigurableCommandRegistry();
|
||||
resolver.register(registry);
|
||||
Map<String, MethodTarget> targets = registry.listCommands();
|
||||
|
||||
assertThat(targets).contains(entry(
|
||||
"register module",
|
||||
MethodTarget.of("register", legacyCommands, new Command.Help("Register a new module"))
|
||||
));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public LegacyCommands legacyCommands() {
|
||||
return new LegacyCommands();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MethodTargetRegistrar methodTargetResolver() {
|
||||
return new LegacyMethodTargetRegistrar();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,355 +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.shell.legacy;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.shell.ValueResultAsserts.assertThat;
|
||||
import static org.springframework.shell.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.*;
|
||||
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.shell.core.annotation.CliOption;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Tests for {@link LegacyParameterResolver}.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@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);
|
||||
|
||||
assertThat(resolve(methodParameter, "--foo bar --name baz --qix bux")).hasValue("baz").usesWords(2, 3)
|
||||
.usesWordsForValue(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesAnonymousParameterAnnotatedWithCliOption() throws Exception {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, NAME_OR_ANONYMOUS);
|
||||
|
||||
assertThat(resolve(methodParameter, "--foo bar baz --qix bux")).hasValue("baz").usesWords(2)
|
||||
.usesWordsForValue(2);
|
||||
|
||||
assertThat(resolve(methodParameter, "baz --foo bar --qix bux")).hasValue("baz").usesWords(0)
|
||||
.usesWordsForValue(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usesLegacyConverters() throws Exception {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, TYPE);
|
||||
|
||||
assertThat(resolve(methodParameter, "--foo bar --name baz --qix bux --type processor"))
|
||||
.hasValue(ArtifactType.processor).usesWords(6, 7).usesWordsForValue(7);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnspecifiedDefaultValue() throws Exception {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, FORCE);
|
||||
|
||||
assertThat(resolve(methodParameter, "--foo bar --name baz --qix bux"))
|
||||
.hasValue(false).notUsesWords().notUsesWordsForValue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpecifiedDefaultValue() throws Exception {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, FORCE);
|
||||
|
||||
assertThat(resolve(methodParameter, "--force --foo bar --name baz --qix bux"))
|
||||
.hasValue(true).usesWords(0).notUsesWordsForValue();
|
||||
|
||||
assertThat(resolve(methodParameter, "--foo bar --name baz --qix bux --force"))
|
||||
.hasValue(true).usesWords(6).notUsesWordsForValue();
|
||||
}
|
||||
|
||||
@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");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDescribeBothDefaultsNotDeclared() {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.REGISTER_METHOD, 1);
|
||||
|
||||
ParameterDescription description = parameterResolver.describe(methodParameter).findFirst().get();
|
||||
|
||||
assertThat(description.keys()).containsExactly("--type");
|
||||
assertThat(description.formal()).isEqualTo(Utils.unCamelify(ArtifactType.class.getSimpleName()));
|
||||
assertThat(description.defaultValue().isPresent()).isFalse();
|
||||
assertThat(description.mandatoryKey()).isTrue();
|
||||
|
||||
String expectedHelp = methodParameter.getParameterAnnotation(CliOption.class).help();
|
||||
assertThat(description.help()).isEqualTo(expectedHelp);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDescribeBothDefaultsDeclared() {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.SOME_METHOD, 1);
|
||||
|
||||
ParameterDescription description = parameterResolver.describe(methodParameter).findFirst().get();
|
||||
|
||||
assertThat(description.keys()).containsExactly("--option");
|
||||
assertThat(description.formal()).isEqualTo(boolean.class.getName());
|
||||
assertThat(description.defaultValue().get()).isEqualTo("false");
|
||||
assertThat(description.defaultValueWhenFlag().get()).isEqualTo("true");
|
||||
assertThat(description.mandatoryKey()).isTrue();
|
||||
|
||||
String expectedHelp = methodParameter.getParameterAnnotation(CliOption.class).help();
|
||||
assertThat(description.help()).isEqualTo(expectedHelp);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDescribeOnlySpecifiedDefaultDeclared() {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.SUM_METHOD, 1);
|
||||
|
||||
ParameterDescription description = parameterResolver.describe(methodParameter).findFirst().get();
|
||||
|
||||
assertThat(description.keys()).containsExactly("--v2");
|
||||
assertThat(description.formal()).isEqualTo(int.class.getName());
|
||||
assertThat(description.defaultValue().get()).isEqualTo("null");
|
||||
assertThat(description.defaultValueWhenFlag().get()).isEqualTo("42");
|
||||
assertThat(description.mandatoryKey()).isTrue();
|
||||
|
||||
String expectedHelp = methodParameter.getParameterAnnotation(CliOption.class).help();
|
||||
assertThat(description.help()).isEqualTo(expectedHelp);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDescribeOnlyUnspecifiedDefaultDeclared() {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.SUM_METHOD, 0);
|
||||
|
||||
ParameterDescription description = parameterResolver.describe(methodParameter).findFirst().get();
|
||||
|
||||
assertThat(description.keys()).containsExactly("--v1");
|
||||
assertThat(description.formal()).isEqualTo(int.class.getName());
|
||||
assertThat(description.defaultValue().get()).isEqualTo("38");
|
||||
assertThat(description.mandatoryKey()).isTrue();
|
||||
|
||||
String expectedHelp = methodParameter.getParameterAnnotation(CliOption.class).help();
|
||||
assertThat(description.help()).isEqualTo(expectedHelp);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDescribeDefaultKey() {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.LEGACY_ECHO_METHOD, 0);
|
||||
|
||||
ParameterDescription description = parameterResolver.describe(methodParameter).findFirst().get();
|
||||
|
||||
assertThat(description.keys()).isEmpty();
|
||||
assertThat(description.formal()).isEqualTo(Utils.unCamelify(String.class.getSimpleName()));
|
||||
assertThat(description.defaultValue().isPresent()).isFalse();
|
||||
assertThat(description.mandatoryKey()).isFalse();
|
||||
|
||||
String expectedHelp = methodParameter.getParameterAnnotation(CliOption.class).help();
|
||||
assertThat(description.help()).isEqualTo(expectedHelp);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDescribeNonMandatoryNoDefaults() {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.SOME_METHOD, 0);
|
||||
|
||||
ParameterDescription description = parameterResolver.describe(methodParameter).findFirst().get();
|
||||
|
||||
assertThat(description.keys()).containsExactly("--key");
|
||||
assertThat(description.formal()).isEqualTo(Utils.unCamelify(String.class.getSimpleName()));
|
||||
assertThat(description.defaultValue().get()).isEqualTo("null");
|
||||
assertThat(description.mandatoryKey()).isTrue();
|
||||
|
||||
String expectedHelp = methodParameter.getParameterAnnotation(CliOption.class).help();
|
||||
assertThat(description.help()).isEqualTo(expectedHelp);
|
||||
}
|
||||
|
||||
private ValueResult resolve(MethodParameter methodParameter, String command) {
|
||||
ValueResult result = parameterResolver.resolve(methodParameter, asList(command.split(" ")));
|
||||
return result;
|
||||
}
|
||||
|
||||
// ======================== Completion Tests ==========================
|
||||
|
||||
@Test
|
||||
public void testNoCompletionJustAfterCommandWithNoSpace() {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, NAME_OR_ANONYMOUS);
|
||||
|
||||
List<CompletionProposal> proposals = parameterResolver.complete(methodParameter, new CompletionContext(Collections.emptyList(), -1, 0));
|
||||
assertThat(proposals).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllCommandKeysJustAfterCommand() {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, COORDINATES);
|
||||
|
||||
List<CompletionProposal> proposals = parameterResolver.complete(methodParameter, new CompletionContext(Collections.singletonList(""), 0, 0));
|
||||
assertThat(valuesOf(proposals)).contains("--coords", "--coordinates");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllCommandKeysWhenStarted() {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, COORDINATES);
|
||||
|
||||
List<CompletionProposal> proposals = parameterResolver.complete(methodParameter, new CompletionContext(Collections.singletonList("--co"), 0, 4));
|
||||
assertThat(valuesOf(proposals)).contains("--coords", "--coordinates");
|
||||
|
||||
proposals = parameterResolver.complete(methodParameter, new CompletionContext(Arrays.asList("--name", "foo", "--co"), 2, 4));
|
||||
assertThat(valuesOf(proposals)).contains("--coords", "--coordinates");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoCompletionsWhenAnotherParameterDetected() {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, COORDINATES);
|
||||
|
||||
List<CompletionProposal> proposals = parameterResolver.complete(methodParameter, new CompletionContext(Arrays.asList("--name", ""), 1, 0));
|
||||
assertThat(valuesOf(proposals)).isEmpty();
|
||||
|
||||
proposals = parameterResolver.complete(methodParameter, new CompletionContext(Arrays.asList("--name", "foo"), 1, 3));
|
||||
assertThat(valuesOf(proposals)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValueCompletionsWhenConverterAvailable() {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, FORCE);
|
||||
|
||||
List<CompletionProposal> proposals = parameterResolver.complete(methodParameter, new CompletionContext(Arrays.asList("--force", ""), 1, 0));
|
||||
assertThat(valuesOf(proposals)).contains("true", "false");
|
||||
|
||||
proposals = parameterResolver.complete(methodParameter, new CompletionContext(Arrays.asList("--force", "fa"), 1, 2));
|
||||
assertThat(valuesOf(proposals)).contains("false").doesNotContain("true");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoValueCompletionsWhenNoConverterAvailable() {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, COORDINATES);
|
||||
|
||||
List<CompletionProposal> proposals = parameterResolver.complete(methodParameter, new CompletionContext(Arrays.asList("--coords", ""), 1, 0));
|
||||
assertThat(valuesOf(proposals)).isEmpty();
|
||||
|
||||
proposals = parameterResolver.complete(methodParameter, new CompletionContext(Arrays.asList("--coords", "foo"), 1, 3));
|
||||
assertThat(valuesOf(proposals)).isEmpty();
|
||||
|
||||
}
|
||||
|
||||
private List<String> valuesOf(List<CompletionProposal> proposals) {
|
||||
return proposals.stream().map(CompletionProposal::value).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public Converter<String> stringConverter() {
|
||||
return new StringConverter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Converter<Boolean> booleanConverter() {
|
||||
return new BooleanConverter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Converter<Enum<?>> enumConverter() {
|
||||
return new EnumConverter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ParameterResolver parameterResolver() {
|
||||
return new LegacyParameterResolver();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,10 +27,6 @@
|
||||
<groupId>org.springframework.shell</groupId>
|
||||
<artifactId>spring-shell-standard-commands</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.shell</groupId>
|
||||
<artifactId>spring-shell-shell1-adapter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.shell</groupId>
|
||||
<artifactId>spring-shell-jcommander-adapter</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user