Add help command
This commit is contained in:
7
pom.xml
7
pom.xml
@@ -59,6 +59,13 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<redirectTestOutputToFile>true</redirectTestOutputToFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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.springframework.boot.SpringApplication;
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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 java.util.HashMap;
|
||||
@@ -15,15 +31,15 @@ public class DefaultMethodTargetResolver implements MethodTargetResolver {
|
||||
|
||||
@Override
|
||||
public Map<String, MethodTarget> resolve(ApplicationContext applicationContext) {
|
||||
Map<String,MethodTarget> methodTargets = new HashMap<>();
|
||||
Map<String, MethodTarget> methodTargets = new HashMap<>();
|
||||
Map<String, Object> 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 == 1 && "".equals(keys[0])) {
|
||||
keys[0] = method.getName();
|
||||
if (keys.length == 0) {
|
||||
keys = new String[]{method.getName()};
|
||||
}
|
||||
for (String key : keys) {
|
||||
methodTargets.put(key, new MethodTarget(method, bean, shellMapping.help()));
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
/*
|
||||
* 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;
|
||||
@@ -23,25 +41,27 @@ import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
|
||||
/**
|
||||
* Default ParameterResolver implementation that supports the following features:<ul>
|
||||
* <li>named parameters (recognized because they start with some {@link ShellMethod#prefix()}</li>
|
||||
* <li>implicit named parameters (from the actual method parameter name)</li>
|
||||
* <li>positional parameters (in order, for all parameter values that were not resolved via named parameters)</li>
|
||||
* <li>default values (for all remaining parameters)</li>
|
||||
* <li>named parameters (recognized because they start with some {@link ShellMethod#prefix()}</li>
|
||||
* <li>implicit named parameters (from the actual method parameter name)</li>
|
||||
* <li>positional parameters (in order, for all parameter values that were not resolved via named parameters)</li>
|
||||
* <li>default values (for all remaining parameters)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>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}
|
||||
* 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).</p>
|
||||
*
|
||||
* <p>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
|
||||
* <p>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 <i>via</i> {@link ShellOption}
|
||||
* if needed.</p>
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Florent Biville
|
||||
*/
|
||||
class DefaultParameterResolver implements ParameterResolver {
|
||||
public class DefaultParameterResolver implements ParameterResolver {
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
@@ -63,7 +83,7 @@ class DefaultParameterResolver implements ParameterResolver {
|
||||
|
||||
@Override
|
||||
public Object resolve(MethodParameter methodParameter, List<String> words) {
|
||||
String prefix = methodParameter.getMethod().getAnnotation(ShellMethod.class).prefix();
|
||||
String prefix = prefixForMethod(methodParameter);
|
||||
|
||||
CacheKey cacheKey = new CacheKey(methodParameter.getMethod(), words);
|
||||
Map<Parameter, String> resolved = parameterCache.computeIfAbsent(cacheKey, (k) -> {
|
||||
@@ -113,11 +133,7 @@ class DefaultParameterResolver implements ParameterResolver {
|
||||
offset += arity;
|
||||
} // No more input. Try defaultValues
|
||||
else {
|
||||
Optional<String> defaultValue = Optional.empty();
|
||||
ShellOption option = parameter.getAnnotation(ShellOption.class);
|
||||
if (option != null && !ShellOption.NULL.equals(option.defaultValue())) {
|
||||
defaultValue = Optional.of(option.defaultValue());
|
||||
}
|
||||
Optional<String> defaultValue = defaultValueFor(parameter);
|
||||
String value = defaultValue.orElseThrow(() -> new RuntimeException(String.format("Ran out of input for " + keys)));
|
||||
result.put(parameter, value);
|
||||
}
|
||||
@@ -133,7 +149,72 @@ class DefaultParameterResolver implements ParameterResolver {
|
||||
});
|
||||
|
||||
String s = resolved.get(methodParameter.getMethod().getParameters()[methodParameter.getParameterIndex()]);
|
||||
return conversionService.convert(s, TypeDescriptor.valueOf(String.class), new TypeDescriptor(methodParameter));
|
||||
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<String> defaultValueFor(Parameter parameter) {
|
||||
Optional<String> 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<String> defaultValue = defaultValueFor(jlrParameter);
|
||||
if (defaultValue.isPresent()) {
|
||||
result.defaultValue(defaultValue.map(dv -> dv.equals(ShellOption.NULL) ? "<none>" : dv).get());
|
||||
}
|
||||
}
|
||||
List<String> 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<Foo>} 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");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,7 +248,7 @@ class DefaultParameterResolver implements ParameterResolver {
|
||||
* or from the actual parameter name.
|
||||
* @throws IllegalArgumentException if parameter names could not be extracted
|
||||
*/
|
||||
private Collection<String> getKeysForParameter(Method method, int index) {
|
||||
private List<String> getKeysForParameter(Method method, int index) {
|
||||
Parameter parameter = method.getParameters()[index];
|
||||
ShellOption option = parameter.getAnnotation(ShellOption.class);
|
||||
if (option != null && option.value().length > 0) {
|
||||
@@ -180,7 +261,7 @@ class DefaultParameterResolver implements ParameterResolver {
|
||||
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.singleton(parameterName);
|
||||
return Collections.singletonList(parameterName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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 java.lang.reflect.Parameter;
|
||||
@@ -7,6 +23,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.validation.ConstraintViolation;
|
||||
@@ -22,7 +39,9 @@ import org.jline.reader.ParsedLine;
|
||||
import org.jline.reader.impl.DefaultParser;
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.terminal.TerminalBuilder;
|
||||
import org.jline.utils.AttributedCharSequence;
|
||||
import org.jline.utils.AttributedString;
|
||||
import org.jline.utils.AttributedStringBuilder;
|
||||
import org.jline.utils.AttributedStyle;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -37,7 +56,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
* Created by ericbottard on 26/11/15.
|
||||
*/
|
||||
@Component
|
||||
public class JLineShell {
|
||||
public class JLineShell implements Shell {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
@@ -54,6 +73,11 @@ public class JLineShell {
|
||||
@Autowired
|
||||
private List<ParameterResolver> parameterResolvers = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public Map<String, MethodTarget> listCommands() {
|
||||
return methodTargets;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() throws Exception {
|
||||
for (MethodTargetResolver resolver : applicationContext.getBeansOfType(MethodTargetResolver.class).values()) {
|
||||
@@ -76,11 +100,19 @@ public class JLineShell {
|
||||
|
||||
@Override
|
||||
public AttributedString highlight(LineReader reader, String buffer) {
|
||||
if (buffer.length() < 4) {
|
||||
return new AttributedString(buffer, AttributedStyle.DEFAULT.blink().foreground(AttributedStyle.RED));
|
||||
int l = 0;
|
||||
String best = null;
|
||||
for (String command : methodTargets.keySet()) {
|
||||
if (buffer.startsWith(command) && command.length() > l) {
|
||||
l = command.length();
|
||||
best = command;
|
||||
}
|
||||
}
|
||||
if (best != null) {
|
||||
return new AttributedStringBuilder(buffer.length()).append(best, AttributedStyle.BOLD).append(buffer.substring(l)).toAttributedString();
|
||||
}
|
||||
else {
|
||||
return new AttributedString(buffer);
|
||||
return new AttributedString(buffer, AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -111,6 +143,8 @@ public class JLineShell {
|
||||
}
|
||||
|
||||
List<String> words = lineReader.getParsedLine().words();
|
||||
// TODO investigate trailing empty string in e.g. "help 'WTF 2'"
|
||||
words = words.stream().filter(w -> w.length() > 0).collect(Collectors.toList());
|
||||
if (methodTarget != null) {
|
||||
Parameter[] parameters = methodTarget.getMethod().getParameters();
|
||||
Object[] rawArgs = new Object[parameters.length];
|
||||
@@ -135,7 +169,9 @@ public class JLineShell {
|
||||
Set<ConstraintViolation<Object>> constraintViolations = executableValidator.validateParameters(methodTarget.getBean(),
|
||||
methodTarget.getMethod(),
|
||||
rawArgs);
|
||||
System.out.println(constraintViolations);
|
||||
if (constraintViolations.size() > 0) {
|
||||
System.out.println(constraintViolations);
|
||||
}
|
||||
|
||||
Object result = null;
|
||||
try {
|
||||
@@ -145,7 +181,12 @@ public class JLineShell {
|
||||
result = e;
|
||||
}
|
||||
|
||||
System.out.println(String.valueOf(result));
|
||||
if (result instanceof AttributedCharSequence) {
|
||||
System.out.println(((AttributedCharSequence) result).toAnsi(lineReader.getTerminal()));
|
||||
}
|
||||
else {
|
||||
System.out.println(String.valueOf(result));
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -1,7 +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;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 27/11/15.
|
||||
*/
|
||||
@@ -14,6 +32,9 @@ public class MethodTarget {
|
||||
private final String help;
|
||||
|
||||
public MethodTarget(Method method, Object bean, String help) {
|
||||
Assert.notNull(method, "Method cannot be null");
|
||||
Assert.notNull(bean, "Bean cannot be null");
|
||||
Assert.hasText(help, "Help cannot be blank");
|
||||
this.method = method;
|
||||
this.bean = bean;
|
||||
this.help = help;
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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 java.util.Map;
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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 java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
|
||||
/**
|
||||
* Encapsulates information about a shell invokable method parameter, so that it can be documented.
|
||||
*
|
||||
* <p>Instances of this class are constructed by {@link ParameterResolver#describe(MethodParameter)}.</p>
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class ParameterDescription {
|
||||
|
||||
/**
|
||||
* A string representation of the type of the parameter.
|
||||
*/
|
||||
private final String type;
|
||||
|
||||
/**
|
||||
* A string representation of the parameter, as it should appear in a parameter list.
|
||||
* If not provided, this is derived from the parameter type.
|
||||
*/
|
||||
private String formal;
|
||||
|
||||
/**
|
||||
* A string representation of the default value for the parameter, if any.
|
||||
*/
|
||||
private Optional<String> defaultValue = Optional.empty();
|
||||
|
||||
/**
|
||||
* The list of 'keys' that can be used to specify this parameter, if any.
|
||||
*/
|
||||
private List<String> keys;
|
||||
|
||||
/**
|
||||
* Depending on the {@link ParameterResolver}, whether keys are mandatory to identify this parameter.
|
||||
*/
|
||||
private boolean mandatoryKey = true;
|
||||
|
||||
/**
|
||||
* A short description of this parameter.
|
||||
*/
|
||||
private String help = "";
|
||||
|
||||
public ParameterDescription(String type) {
|
||||
this.type = type;
|
||||
this.formal = type;
|
||||
}
|
||||
|
||||
public static ParameterDescription ofType(String type) {
|
||||
return new ParameterDescription(Utils.unCamelify(type));
|
||||
}
|
||||
|
||||
public static ParameterDescription ofType(Class<?> type) {
|
||||
return ofType(type.getSimpleName());
|
||||
}
|
||||
|
||||
public ParameterDescription help(String help) {
|
||||
this.help = help;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean mandatoryKey() {
|
||||
return mandatoryKey;
|
||||
}
|
||||
|
||||
public List<String> keys() {
|
||||
return keys;
|
||||
}
|
||||
|
||||
public Optional<String> defaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public ParameterDescription defaultValue(String defaultValue) {
|
||||
this.defaultValue = Optional.of(defaultValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ParameterDescription keys(List<String> keys) {
|
||||
this.keys = keys;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ParameterDescription mandatoryKey(boolean mandatoryKey) {
|
||||
this.mandatoryKey = mandatoryKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String formal() {
|
||||
return formal;
|
||||
}
|
||||
|
||||
public String help() {
|
||||
return help;
|
||||
}
|
||||
|
||||
public ParameterDescription formal(String formal) {
|
||||
this.formal = formal;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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 java.util.List;
|
||||
@@ -11,6 +27,8 @@ public interface ParameterResolver {
|
||||
|
||||
boolean supports(MethodParameter parameter);
|
||||
|
||||
public Object resolve(MethodParameter methodParameter, List<String> words);
|
||||
Object resolve(MethodParameter methodParameter, List<String> words);
|
||||
|
||||
ParameterDescription describe(MethodParameter parameter);
|
||||
|
||||
}
|
||||
|
||||
29
src/main/java/org/springframework/shell2/Shell.java
Normal file
29
src/main/java/org/springframework/shell2/Shell.java
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 17/12/15.
|
||||
*/
|
||||
public interface Shell {
|
||||
|
||||
|
||||
public Map<String, MethodTarget> listCommands();
|
||||
|
||||
}
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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 java.lang.annotation.Documented;
|
||||
@@ -9,7 +25,13 @@ import java.lang.annotation.Target;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 27/11/15.
|
||||
* Indicates that an annotated class may contain shell methods (themselves annotated with {@link @ShellMethod}) that
|
||||
* is,
|
||||
* methods that may be invoked reflectively by the shell.
|
||||
*
|
||||
* <p>This annotation is a specialization of {@link Component}.</p>
|
||||
* @author Eric Bottard
|
||||
* @see Component
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@@ -17,5 +39,8 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public @interface ShellComponent {
|
||||
|
||||
String value();
|
||||
/**
|
||||
* Used to indicate a suggestion for a logical name for the component.
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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 java.lang.annotation.Documented;
|
||||
@@ -8,7 +24,6 @@ import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Used to mark a method as invokable via Spring Shell.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Florent Biville
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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 java.lang.annotation.Documented;
|
||||
@@ -16,6 +32,8 @@ public @interface ShellOption {
|
||||
|
||||
String NULL = "__NULL__";
|
||||
|
||||
String NONE = "__NONE__";
|
||||
|
||||
/**
|
||||
* The key(s) (without the {@link ShellMethod#prefix()}) by which this parameter can be referenced
|
||||
* when using named parameters. If none is specified, the actual method parameter name will be used.
|
||||
@@ -30,5 +48,10 @@ public @interface ShellOption {
|
||||
/**
|
||||
* The textual (pre-conversion) value to assign to this parameter if no value is provided by the user.
|
||||
*/
|
||||
String defaultValue() default NULL;
|
||||
String defaultValue() default NONE;
|
||||
|
||||
/**
|
||||
* Return a short description of the parameter.
|
||||
*/
|
||||
String help() default "";
|
||||
}
|
||||
|
||||
43
src/main/java/org/springframework/shell2/Utils.java
Normal file
43
src/main/java/org/springframework/shell2/Utils.java
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Some text utilities.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class Utils {
|
||||
|
||||
/**
|
||||
* Turn CamelCaseText into gnu-style-lowercase.
|
||||
*/
|
||||
public static String unCamelify(CharSequence original) {
|
||||
StringBuilder result = new StringBuilder(original.length());
|
||||
boolean wasLowercase = false;
|
||||
for (int i = 0; i < original.length(); i++) {
|
||||
char ch = original.charAt(i);
|
||||
if (Character.isUpperCase(ch) && wasLowercase) {
|
||||
result.append('-');
|
||||
}
|
||||
wasLowercase = Character.isLowerCase(ch);
|
||||
result.append(Character.toLowerCase(ch));
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
}
|
||||
218
src/main/java/org/springframework/shell2/commands/Help.java
Normal file
218
src/main/java/org/springframework/shell2/commands/Help.java
Normal file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* 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 static java.util.stream.Collectors.mapping;
|
||||
import static java.util.stream.Collectors.toCollection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jline.utils.AttributedStringBuilder;
|
||||
import org.jline.utils.AttributedStyle;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.shell2.MethodTarget;
|
||||
import org.springframework.shell2.ParameterDescription;
|
||||
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;
|
||||
|
||||
/**
|
||||
* A command to display help about all available commands.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@ShellComponent
|
||||
public class Help {
|
||||
|
||||
private final List<ParameterResolver> parameterResolvers;
|
||||
|
||||
private Shell shell;
|
||||
|
||||
@Autowired
|
||||
public Help(List<ParameterResolver> parameterResolvers) {
|
||||
this.parameterResolvers = parameterResolvers;
|
||||
}
|
||||
|
||||
@Autowired // ctor injection impossible b/c of circular dependency
|
||||
public void setShell(Shell shell) {
|
||||
this.shell = shell;
|
||||
}
|
||||
|
||||
@ShellMethod(help = "Display help about available commands.", prefix = "-")
|
||||
public CharSequence help(
|
||||
@ShellOption(defaultValue = ShellOption.NULL,
|
||||
value = {"C", "-command"},
|
||||
help = "The command to obtain help for.") String command) throws IOException {
|
||||
if (command == null) {
|
||||
return listCommands();
|
||||
}
|
||||
else {
|
||||
return documentCommand(command);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description of a specific command. Uses a layout inspired by *nix man pages.
|
||||
*/
|
||||
private CharSequence documentCommand(String command) {
|
||||
MethodTarget methodTarget = shell.listCommands().get(command);
|
||||
if (methodTarget == null) {
|
||||
throw new IllegalArgumentException("Unknown command '" + command + "'");
|
||||
}
|
||||
|
||||
// NAME
|
||||
AttributedStringBuilder result = new AttributedStringBuilder().append("\n\n");
|
||||
result.append("NAME", AttributedStyle.BOLD).append("\n\t");
|
||||
result.append(command).append(" - ").append(methodTarget.getHelp()).append("\n\n");
|
||||
|
||||
// SYNOPSYS
|
||||
result.append("SYNOPSYS", AttributedStyle.BOLD).append("\n\t");
|
||||
result.append(command, AttributedStyle.BOLD);
|
||||
result.append(" ");
|
||||
|
||||
List<ParameterDescription> parameterDescriptions = getParameterDescriptions(methodTarget);
|
||||
|
||||
for (ParameterDescription description : parameterDescriptions) {
|
||||
|
||||
if (description.defaultValue().isPresent()) {
|
||||
result.append("[");
|
||||
}
|
||||
if (!description.mandatoryKey()) {
|
||||
result.append("[");
|
||||
}
|
||||
result.append(description.keys().iterator().next(), AttributedStyle.BOLD);
|
||||
if (!description.mandatoryKey()) {
|
||||
result.append("]");
|
||||
if (!description.formal().isEmpty()) {
|
||||
result.append(" ");
|
||||
}
|
||||
}
|
||||
appendUnderlinedFormal(result, description);
|
||||
if (description.defaultValue().isPresent()) {
|
||||
result.append("]");
|
||||
}
|
||||
result.append(" ");
|
||||
}
|
||||
result.append("\n\n");
|
||||
|
||||
// OPTIONS
|
||||
result.append("OPTIONS", AttributedStyle.BOLD).append("\n");
|
||||
for (ParameterDescription description : parameterDescriptions) {
|
||||
result.append("\t").append(description.keys().stream().collect(Collectors.joining(" or ")), AttributedStyle.BOLD);
|
||||
if (description.formal().length() > 0) {
|
||||
result.append(" ");
|
||||
appendUnderlinedFormal(result, description);
|
||||
result.append("\n\t");
|
||||
}
|
||||
else if (description.keys().size() > 1) {
|
||||
result.append("\n\t");
|
||||
}
|
||||
result.append("\t");
|
||||
result.append(description.help());
|
||||
if (description.defaultValue().isPresent()) {
|
||||
result
|
||||
.append(" [Optional, default = ", AttributedStyle.BOLD)
|
||||
.append(description.defaultValue().get(), AttributedStyle.BOLD.italic())
|
||||
.append("]", AttributedStyle.BOLD);
|
||||
} else {
|
||||
result.append(" [Mandatory]", AttributedStyle.BOLD);
|
||||
}
|
||||
result.append("\n\n");
|
||||
}
|
||||
|
||||
// ALSO KNOWN AS
|
||||
Set<String> aliases = shell.listCommands().entrySet().stream()
|
||||
.filter(e -> e.getValue().equals(methodTarget))
|
||||
.map(e -> e.getKey())
|
||||
.filter(c -> !command.equals(c))
|
||||
.collect(toCollection(TreeSet::new));
|
||||
|
||||
if (!aliases.isEmpty()) {
|
||||
result.append("ALSO KNOWN AS", AttributedStyle.BOLD).append("\n");
|
||||
for (String alias : aliases) {
|
||||
result.append('\t').append(alias).append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
result.append("\n");
|
||||
return result;
|
||||
}
|
||||
|
||||
private CharSequence listCommands() {
|
||||
Map<String, Set<String>> groupedByMethodTarget = shell.listCommands().entrySet().stream()
|
||||
.collect(Collectors.groupingBy(e -> e.getValue().getHelp(), // Use help() as the grouping key
|
||||
mapping(Map.Entry::getKey, toCollection(TreeSet::new)))); // accumulate the command 'names' into a sorted set
|
||||
|
||||
// Then display commands, sorted alphabetically by their first alias
|
||||
AttributedStringBuilder result = new AttributedStringBuilder();
|
||||
result.append("AVAILABLE COMMANDS\n\n", AttributedStyle.BOLD);
|
||||
|
||||
groupedByMethodTarget.entrySet().stream()
|
||||
.sorted(sortByFirstElement())
|
||||
.forEach(e -> result.append("\t")
|
||||
.append(e.getValue().stream().collect(Collectors.joining(", ")), AttributedStyle.BOLD)
|
||||
.append(": ")
|
||||
.append(e.getKey())
|
||||
.append('\n')
|
||||
);
|
||||
return result.append("\n");
|
||||
}
|
||||
|
||||
private Comparator<Map.Entry<String, Set<String>>> sortByFirstElement() {
|
||||
return (e1, e2) -> e1.getValue().iterator().next().compareTo(e2.getValue().iterator().next());
|
||||
}
|
||||
|
||||
private void appendUnderlinedFormal(AttributedStringBuilder result, ParameterDescription description) {
|
||||
for (char c : description.formal().toCharArray()) {
|
||||
if (c != ' ') {
|
||||
result.append("" + c, AttributedStyle.DEFAULT.underline());
|
||||
}
|
||||
else {
|
||||
result.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<ParameterDescription> getParameterDescriptions(MethodTarget methodTarget) {
|
||||
Parameter[] parameters = methodTarget.getMethod().getParameters();
|
||||
List<ParameterDescription> parameterDescriptions = new ArrayList<>();
|
||||
for (int i = 0, parametersLength = parameters.length; i < parametersLength; i++) {
|
||||
for (ParameterResolver parameterResolver : parameterResolvers) {
|
||||
MethodParameter methodParameter = new MethodParameter(methodTarget.getMethod(), i);
|
||||
if (parameterResolver.supports(methodParameter)) {
|
||||
parameterDescriptions.add(parameterResolver.describe(methodParameter));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return parameterDescriptions;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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.lang.annotation.Annotation;
|
||||
@@ -13,6 +29,7 @@ import com.beust.jcommander.ParametersDelegate;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.shell2.ParameterDescription;
|
||||
import org.springframework.shell2.ParameterResolver;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
@@ -60,4 +77,8 @@ public class JCommanderParameterResolver implements ParameterResolver {
|
||||
return pojo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParameterDescription describe(MethodParameter parameter) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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.util.HashMap;
|
||||
@@ -12,7 +28,10 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 09/12/15.
|
||||
* A {@link MethodTargetResolver} that discovers methods annotated with {@link CliCommand} on beans
|
||||
* implementing the {@link CommandMarker} marker interface.
|
||||
* @author Eric Bottard
|
||||
* @author Florent Biville
|
||||
*/
|
||||
@Component
|
||||
public class LegacyMethodTargetResolver implements MethodTargetResolver {
|
||||
@@ -20,13 +39,13 @@ public class LegacyMethodTargetResolver implements MethodTargetResolver {
|
||||
@Override
|
||||
public Map<String, MethodTarget> resolve(ApplicationContext context) {
|
||||
Map<String, MethodTarget> methodTargets = new HashMap<>();
|
||||
Map<String, CommandMarker> beansOfType = context.getBeansOfType(CommandMarker.class);
|
||||
for (Object bean : beansOfType.values()) {
|
||||
Map<String, CommandMarker> beans = context.getBeansOfType(CommandMarker.class);
|
||||
for (Object bean : beans.values()) {
|
||||
Class<?> clazz = bean.getClass();
|
||||
ReflectionUtils.doWithMethods(clazz, method -> {
|
||||
CliCommand shellMapping = method.getAnnotation(CliCommand.class);
|
||||
for (String key : shellMapping.value()) {
|
||||
methodTargets.put(key, new MethodTarget(method, bean, shellMapping.help()));
|
||||
CliCommand cliCommand = method.getAnnotation(CliCommand.class);
|
||||
for (String key : cliCommand.value()) {
|
||||
methodTargets.put(key, new MethodTarget(method, bean, cliCommand.help()));
|
||||
}
|
||||
}, method -> method.getAnnotation(CliCommand.class) != null);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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.util.ArrayList;
|
||||
@@ -14,6 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.shell.core.Converter;
|
||||
import org.springframework.shell.core.annotation.CliOption;
|
||||
import org.springframework.shell2.ParameterDescription;
|
||||
import org.springframework.shell2.ParameterResolver;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -24,8 +41,6 @@ import org.springframework.util.Assert;
|
||||
@Component
|
||||
public class LegacyParameterResolver implements ParameterResolver {
|
||||
|
||||
public static final Object UNSPECIFIED = new Object();
|
||||
|
||||
@Autowired(required = false)
|
||||
private Collection<Converter<?>> converters = new ArrayList<>();
|
||||
|
||||
@@ -61,6 +76,11 @@ public class LegacyParameterResolver implements ParameterResolver {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParameterDescription describe(MethodParameter parameter) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private Map<String, String> parseOptions(List<String> words) {
|
||||
Map<String, String> values = new HashMap<>();
|
||||
for (int i = 0; i < words.size(); i++) {
|
||||
@@ -68,7 +88,7 @@ public class LegacyParameterResolver implements ParameterResolver {
|
||||
if (word.startsWith("--")) {
|
||||
String key = word.substring("--".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("--") ? words.get(++i) : null;
|
||||
String value = i < words.size() - 1 && !words.get(i + 1).startsWith("--") ? words.get(++i) : null;
|
||||
Assert.isTrue(!values.containsKey(key), String.format("Option --%s has already been set", key));
|
||||
values.put(key, value);
|
||||
} // Must be the 'anonymous' option
|
||||
|
||||
5
src/main/resources/logback.xml
Normal file
5
src/main/resources/logback.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<configuration>
|
||||
<!-- Disable most stdout logging by default -->
|
||||
<root level="ERROR">
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
|
||||
37
src/test/java/org/springframework/shell2/UtilsTest.java
Normal file
37
src/test/java/org/springframework/shell2/UtilsTest.java
Normal file
@@ -0,0 +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");
|
||||
}
|
||||
}
|
||||
157
src/test/java/org/springframework/shell2/commands/HelpTest.java
Normal file
157
src/test/java/org/springframework/shell2/commands/HelpTest.java
Normal file
@@ -0,0 +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<String, MethodTarget> 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() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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;
|
||||
@@ -37,7 +53,7 @@ public class LegacyCommands implements CommandMarker {
|
||||
return String.format(("Successfully registered module '%s:%s'"), type, name);
|
||||
}
|
||||
|
||||
@CliCommand("sum")
|
||||
@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;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
&
|
||||
&
|
||||
NAME&
|
||||
first-command - A rather extensive description of some command.&
|
||||
&
|
||||
SYNOPSYS&
|
||||
first-command [-r] [-f] [[-n] int] [-o] float float float &
|
||||
&
|
||||
OPTIONS&
|
||||
-r Whether to delete recursively [Mandatory]&
|
||||
&
|
||||
-f or --force&
|
||||
Do not ask for confirmation. YOLO [Mandatory]&
|
||||
&
|
||||
-n int&
|
||||
The answer to everything [Optional, default = 42]&
|
||||
&
|
||||
-o float float float&
|
||||
Some other parameters [Mandatory]&
|
||||
&
|
||||
ALSO KNOWN AS&
|
||||
1st-command&
|
||||
&
|
||||
@@ -0,0 +1,6 @@
|
||||
AVAILABLE COMMANDS&
|
||||
&
|
||||
1st-command, first-command: A rather extensive description of some command.&
|
||||
second-command, yet-another-command: The second command. This one is known under several aliases as well.&
|
||||
third-command: The last command.&
|
||||
&
|
||||
Reference in New Issue
Block a user