WIP on Completer
This commit is contained in:
5
pom.xml
5
pom.xml
@@ -30,14 +30,15 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jline</groupId>
|
||||
<groupId>org.jline</groupId>
|
||||
<artifactId>jline</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
<version>3.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.shell</groupId>
|
||||
<artifactId>spring-shell</artifactId>
|
||||
<version>1.2.0.BUILD-SNAPSHOT</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.beust</groupId>
|
||||
|
||||
@@ -32,6 +32,8 @@ import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.shell.converters.ArrayConverter;
|
||||
import org.springframework.shell.converters.AvailableCommandsConverter;
|
||||
import org.springframework.shell.converters.SimpleFileConverter;
|
||||
import org.springframework.shell2.standard.EnumValueProvider;
|
||||
import org.springframework.shell2.standard.StandardParameterResolver;
|
||||
|
||||
/**
|
||||
*/
|
||||
@@ -53,7 +55,7 @@ public class Bootstrap {
|
||||
|
||||
@Bean
|
||||
public ParameterResolver parameterResolver(ConversionService conversionService) {
|
||||
return new DefaultParameterResolver(conversionService);
|
||||
return new StandardParameterResolver(conversionService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -61,4 +63,8 @@ public class Bootstrap {
|
||||
return TerminalBuilder.builder().build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EnumValueProvider enumValueProvider() {
|
||||
return new EnumValueProvider();
|
||||
}
|
||||
}
|
||||
|
||||
47
src/main/java/org/springframework/shell2/Commands.java
Normal file
47
src/main/java/org/springframework/shell2/Commands.java
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.shell2.standard.ShellComponent;
|
||||
import org.springframework.shell2.standard.ShellMethod;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 27/12/15.
|
||||
*/
|
||||
@ShellComponent("")
|
||||
public class Commands {
|
||||
|
||||
@ShellMethod(help = "it's cool")
|
||||
public void foo(String bar) {
|
||||
|
||||
}
|
||||
|
||||
@ShellMethod(help = "it's better")
|
||||
public void foobar() {
|
||||
|
||||
}
|
||||
|
||||
@ShellMethod(help = "something else")
|
||||
public void somethingElse() {
|
||||
|
||||
}
|
||||
|
||||
@ShellMethod(help = "add stuff")
|
||||
public int add(int ahbahdisdonc, int b, int c) {
|
||||
return ahbahdisdonc + b + c;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Represents the buffer context in which completion was triggered.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class CompletionContext {
|
||||
|
||||
private final List<String> words;
|
||||
|
||||
private final int wordIndex;
|
||||
|
||||
private final int position;
|
||||
|
||||
|
||||
public CompletionContext(List<String> words, int wordIndex, int position) {
|
||||
this.words = words;
|
||||
this.wordIndex = wordIndex;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public List<String> getWords() {
|
||||
return words;
|
||||
}
|
||||
|
||||
public int getWordIndex() {
|
||||
return wordIndex;
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public String upToCursor() {
|
||||
String start = words.subList(0, wordIndex).stream().collect(Collectors.joining(" "));
|
||||
if (wordIndex < words.size()) {
|
||||
start += " " + currentWord().substring(0, position);
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the whole word the cursor is in, or {@code null} if the cursor is past the last word.
|
||||
*/
|
||||
public String currentWord() {
|
||||
return wordIndex < words.size() ? words.get(wordIndex) : null;
|
||||
}
|
||||
|
||||
public String currentWordUpToCursor() {
|
||||
String currentWord = currentWord();
|
||||
return currentWord != null ? currentWord.substring(0, getPosition()) : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2;
|
||||
|
||||
/**
|
||||
* Represents a proposal for TAB completion, made not only of the text to append, but also metadata about the proposal.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class CompletionProposal {
|
||||
|
||||
/**
|
||||
* The actual value of the proposal.
|
||||
*/
|
||||
private String value;
|
||||
|
||||
/**
|
||||
* The text displayed while the proposal is being considered.
|
||||
*/
|
||||
private String displayText;
|
||||
|
||||
/**
|
||||
* The description for the proposal.
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* The category of the proposal, which may be used to group proposals together.
|
||||
*/
|
||||
private String category;
|
||||
|
||||
public CompletionProposal(String value) {
|
||||
this.value = this.displayText = value;
|
||||
}
|
||||
|
||||
public String value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void value(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String displayText() {
|
||||
return displayText;
|
||||
}
|
||||
|
||||
public void displayText(String displayText) {
|
||||
this.displayText = displayText;
|
||||
}
|
||||
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void description(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String category() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void category(String category) {
|
||||
this.category = category;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,39 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* This exception, when thrown and caught, will ask the shell to gracefully shutdown.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class ExitRequest extends RuntimeException {
|
||||
|
||||
private final int code;
|
||||
|
||||
public ExitRequest() {
|
||||
this(0);
|
||||
}
|
||||
|
||||
public ExitRequest(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int status() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2;
|
||||
|
||||
/**
|
||||
* This exception, when thrown and caught, will ask the shell to gracefully shutdown.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class ExitRequest extends RuntimeException {
|
||||
|
||||
private final int code;
|
||||
|
||||
public ExitRequest() {
|
||||
this(0);
|
||||
}
|
||||
|
||||
public ExitRequest(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int status() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,199 +1,285 @@
|
||||
/*
|
||||
* 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.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
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;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.executable.ExecutableValidator;
|
||||
|
||||
import org.jline.reader.Candidate;
|
||||
import org.jline.reader.Completer;
|
||||
import org.jline.reader.Highlighter;
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.LineReaderBuilder;
|
||||
import org.jline.reader.ParsedLine;
|
||||
import org.jline.reader.impl.DefaultParser;
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.utils.AttributedString;
|
||||
import org.jline.utils.AttributedStringBuilder;
|
||||
import org.jline.utils.AttributedStyle;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.shell2.result.ResultHandlers;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 26/11/15.
|
||||
*/
|
||||
@Component
|
||||
public class JLineShell implements Shell {
|
||||
|
||||
@Autowired
|
||||
private ResultHandlers resultHandlers;
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private Map<String, MethodTarget> methodTargets = new HashMap<>();
|
||||
|
||||
private LineReader lineReader;
|
||||
|
||||
@Autowired
|
||||
private Terminal terminal;
|
||||
|
||||
@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()) {
|
||||
methodTargets.putAll(resolver.resolve(applicationContext));
|
||||
}
|
||||
|
||||
LineReaderBuilder lineReaderBuilder = LineReaderBuilder.builder()
|
||||
.terminal(terminal)
|
||||
.appName("Foo")
|
||||
.completer(new Completer() {
|
||||
|
||||
@Override
|
||||
public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates) {
|
||||
candidates.add(new Candidate("value", "displayed value", "v", "the description", null, null, false));
|
||||
candidates.add(new Candidate("value1", "displayed value1", "v", "the description of v1", null, null, false));
|
||||
}
|
||||
})
|
||||
.highlighter(new Highlighter() {
|
||||
|
||||
@Override
|
||||
public AttributedString highlight(LineReader reader, String buffer) {
|
||||
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, AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));
|
||||
}
|
||||
}
|
||||
})
|
||||
.parser(new DefaultParser());
|
||||
|
||||
lineReader = lineReaderBuilder.build();
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void run() throws IOException {
|
||||
while (true) {
|
||||
lineReader.readLine("shell:>");
|
||||
String separator = "";
|
||||
StringBuilder candidateCommand = new StringBuilder();
|
||||
MethodTarget methodTarget = null;
|
||||
int c = 0;
|
||||
int wordsUsedForCommandKey = 0;
|
||||
for (String word : lineReader.getParsedLine().words()) {
|
||||
c++;
|
||||
candidateCommand.append(separator).append(word);
|
||||
MethodTarget t = methodTargets.get(candidateCommand.toString());
|
||||
if (t != null) {
|
||||
methodTarget = t;
|
||||
wordsUsedForCommandKey = c;
|
||||
}
|
||||
separator = " ";
|
||||
}
|
||||
|
||||
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];
|
||||
Object unresolved = new Object();
|
||||
Arrays.fill(rawArgs, unresolved);
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
MethodParameter methodParameter = new MethodParameter(methodTarget.getMethod(), i);
|
||||
methodParameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
|
||||
for (ParameterResolver resolver : parameterResolvers) {
|
||||
if (resolver.supports(methodParameter)) {
|
||||
rawArgs[i] = resolver.resolve(methodParameter, words.subList(wordsUsedForCommandKey, words.size()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rawArgs[i] == unresolved) {
|
||||
throw new IllegalStateException("Could not resolve " + methodParameter);
|
||||
}
|
||||
}
|
||||
|
||||
ExecutableValidator executableValidator = Validation
|
||||
.buildDefaultValidatorFactory().getValidator().forExecutables();
|
||||
Set<ConstraintViolation<Object>> constraintViolations = executableValidator.validateParameters(methodTarget.getBean(),
|
||||
methodTarget.getMethod(),
|
||||
rawArgs);
|
||||
if (constraintViolations.size() > 0) {
|
||||
System.out.println(constraintViolations);
|
||||
}
|
||||
|
||||
Object result = null;
|
||||
try {
|
||||
result = ReflectionUtils.invokeMethod(methodTarget.getMethod(), methodTarget.getBean(), rawArgs);
|
||||
}
|
||||
catch (ExitRequest e) {
|
||||
if (applicationContext instanceof Closeable) {
|
||||
((Closeable)applicationContext).close();
|
||||
System.exit(e.status());
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
result = e;
|
||||
}
|
||||
|
||||
resultHandlers.handleResult(result);
|
||||
|
||||
}
|
||||
else {
|
||||
System.out.println("No command found for " + words);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
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;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.executable.ExecutableValidator;
|
||||
|
||||
import org.jline.reader.Candidate;
|
||||
import org.jline.reader.Completer;
|
||||
import org.jline.reader.Highlighter;
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.LineReaderBuilder;
|
||||
import org.jline.reader.ParsedLine;
|
||||
import org.jline.reader.impl.DefaultParser;
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.utils.AttributedString;
|
||||
import org.jline.utils.AttributedStringBuilder;
|
||||
import org.jline.utils.AttributedStyle;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.shell2.result.ResultHandlers;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 26/11/15.
|
||||
*/
|
||||
@Component
|
||||
public class JLineShell implements Shell {
|
||||
|
||||
@Autowired
|
||||
private ResultHandlers resultHandlers;
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private Map<String, MethodTarget> methodTargets = new HashMap<>();
|
||||
|
||||
private LineReader lineReader;
|
||||
|
||||
@Autowired
|
||||
private Terminal terminal;
|
||||
|
||||
@Autowired
|
||||
private List<ParameterResolver> parameterResolvers = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Marker object to distinguish unresolved arguments from {@code null}, which is a valid value.
|
||||
*/
|
||||
private static final Object UNRESOLVED = new Object();
|
||||
|
||||
@Override
|
||||
public Map<String, MethodTarget> listCommands() {
|
||||
return methodTargets;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() throws Exception {
|
||||
for (MethodTargetResolver resolver : applicationContext.getBeansOfType(MethodTargetResolver.class).values()) {
|
||||
methodTargets.putAll(resolver.resolve(applicationContext));
|
||||
}
|
||||
|
||||
LineReaderBuilder lineReaderBuilder = LineReaderBuilder.builder()
|
||||
.terminal(terminal)
|
||||
.appName("Foo")
|
||||
.completer(new CompleterAdapter())
|
||||
.highlighter(new Highlighter() {
|
||||
|
||||
@Override
|
||||
public AttributedString highlight(LineReader reader, String buffer) {
|
||||
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, AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));
|
||||
}
|
||||
}
|
||||
})
|
||||
.parser(new DefaultParser());
|
||||
|
||||
lineReader = lineReaderBuilder.build();
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void run() throws IOException {
|
||||
while (true) {
|
||||
lineReader.readLine(new AttributedString("shell:>", AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW)).toAnsi(terminal));
|
||||
String separator = "";
|
||||
StringBuilder candidateCommand = new StringBuilder();
|
||||
MethodTarget methodTarget = null;
|
||||
int c = 0;
|
||||
int wordsUsedForCommandKey = 0;
|
||||
for (String word : lineReader.getParsedLine().words()) {
|
||||
c++;
|
||||
candidateCommand.append(separator).append(word);
|
||||
MethodTarget t = methodTargets.get(candidateCommand.toString());
|
||||
if (t != null) {
|
||||
methodTarget = t;
|
||||
wordsUsedForCommandKey = c;
|
||||
}
|
||||
separator = " ";
|
||||
}
|
||||
|
||||
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) {
|
||||
List<String> wordsForArgs = words.subList(wordsUsedForCommandKey, words.size());
|
||||
Method method = methodTarget.getMethod();
|
||||
|
||||
|
||||
Object result = null;
|
||||
try {
|
||||
Object[] args = resolveArgs(method, wordsForArgs);
|
||||
validateArgs(args, methodTarget);
|
||||
result = ReflectionUtils.invokeMethod(method, methodTarget.getBean(), args);
|
||||
}
|
||||
catch (ExitRequest e) {
|
||||
if (applicationContext instanceof Closeable) {
|
||||
((Closeable) applicationContext).close();
|
||||
System.exit(e.status());
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
result = e;
|
||||
}
|
||||
|
||||
resultHandlers.handleResult(result);
|
||||
|
||||
}
|
||||
else {
|
||||
System.out.println("No command found for " + words);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateArgs(Object[] args, MethodTarget methodTarget) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i] == UNRESOLVED) {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(methodTarget.getMethod(), i);
|
||||
throw new IllegalStateException("Could not resolve " + methodParameter);
|
||||
}
|
||||
}
|
||||
ExecutableValidator executableValidator = Validation
|
||||
.buildDefaultValidatorFactory().getValidator().forExecutables();
|
||||
Set<ConstraintViolation<Object>> constraintViolations = executableValidator.validateParameters(methodTarget.getBean(),
|
||||
methodTarget.getMethod(),
|
||||
args);
|
||||
if (constraintViolations.size() > 0) {
|
||||
System.out.println(constraintViolations);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use all known {@link ParameterResolver}s to try to compute a value for each parameter of the method to
|
||||
* invoke.
|
||||
* @param method the method for which parameters should be computed
|
||||
* @param wordsForArgs the list of 'words' that should be converted to parameter values.
|
||||
* May include markers for passing parameters 'by name'
|
||||
* @return an array containing resolved parameter values, or {@link #UNRESOLVED} for parameters that could not be
|
||||
* resolved
|
||||
*/
|
||||
private Object[] resolveArgs(Method method, List<String> wordsForArgs) {
|
||||
Parameter[] parameters = method.getParameters();
|
||||
Object[] args = new Object[parameters.length];
|
||||
Arrays.fill(args, UNRESOLVED);
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(method, i);
|
||||
args[i] = findResolver(methodParameter).resolve(methodParameter, wordsForArgs);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
private ParameterResolver findResolver(MethodParameter parameter) {
|
||||
return parameterResolvers.stream()
|
||||
.filter(resolver -> resolver.supports(parameter))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new RuntimeException("resolver not found"));
|
||||
}
|
||||
|
||||
/**
|
||||
* A bridge between JLine's {@link Completer} contract and our own.
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
private class CompleterAdapter implements Completer {
|
||||
|
||||
@Override
|
||||
public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates) {
|
||||
String prefix = reader.getBuffer().upToCursor();
|
||||
|
||||
String best = methodTargets.keySet().stream()
|
||||
.filter(c -> prefix.startsWith(c))
|
||||
.reduce("", (c1, c2) -> c1.length() > c2.length() ? c1 : c2);
|
||||
if (best.equals("")) { // no command found
|
||||
List<Candidate> result = commandsStartingWith(prefix);
|
||||
candidates.addAll(result);
|
||||
return;
|
||||
} // trying to complete args for command <best>,
|
||||
// or trying to complete command whose name starts with <best> (which also happens to be a command)
|
||||
else if (prefix.equals(best)) {
|
||||
candidates.addAll(commandsStartingWith(best));
|
||||
return;
|
||||
} // valid command (<best>) followed by a suffix (but not necessarily [<space> args*])
|
||||
else if (!prefix.startsWith(best + " ")) {
|
||||
// must be an invalid command, can't do anything
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to complete arguments
|
||||
MethodTarget methodTarget = methodTargets.get(best);
|
||||
List<String> words = line.words();
|
||||
int noOfWordsInCommand = best.split(" ").length;
|
||||
List<String> rest = words.subList(noOfWordsInCommand, words.size())
|
||||
.stream()
|
||||
.filter(w -> !w.isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
CompletionContext context = new CompletionContext(rest, line.wordIndex() - noOfWordsInCommand, line.wordCursor());
|
||||
Method method = methodTarget.getMethod();
|
||||
for (int i = 0; i < method.getParameterCount(); i++) {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(method, i);
|
||||
ParameterResolver resolver = findResolver(methodParameter);
|
||||
resolver.complete(methodParameter, context)
|
||||
.stream()
|
||||
.map(completion -> new Candidate(
|
||||
completion.value(),
|
||||
completion.displayText(),
|
||||
"Comp for parameter " + resolver.describe(methodParameter).toString(),
|
||||
resolver.describe(methodParameter).help(),
|
||||
null, null, true)
|
||||
)
|
||||
.forEach(candidates::add);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Candidate> commandsStartingWith(String prefix) {
|
||||
return methodTargets.entrySet().stream()
|
||||
.filter(e -> e.getKey().startsWith(prefix)) // find commands that start with our buffer prefix
|
||||
.map(e -> toCandidate(e.getKey(), e.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Candidate toCandidate(String command, MethodTarget methodTarget) {
|
||||
return new Candidate(command, command, "Available commands", methodTarget.getHelp(), null, null, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +1,75 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
public class MethodTarget {
|
||||
|
||||
private final Method method;
|
||||
|
||||
private final Object bean;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public Object getBean() {
|
||||
return bean;
|
||||
}
|
||||
|
||||
public String getHelp() {
|
||||
return help;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
MethodTarget that = (MethodTarget) o;
|
||||
|
||||
if (!method.equals(that.method)) return false;
|
||||
if (!bean.equals(that.bean)) return false;
|
||||
return help.equals(that.help);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = method.hashCode();
|
||||
result = 31 * result + bean.hashCode();
|
||||
result = 31 * result + help.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
public class MethodTarget {
|
||||
|
||||
private final Method method;
|
||||
|
||||
private final Object bean;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public Object getBean() {
|
||||
return bean;
|
||||
}
|
||||
|
||||
public String getHelp() {
|
||||
return help;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
MethodTarget that = (MethodTarget) o;
|
||||
|
||||
if (!method.equals(that.method)) return false;
|
||||
if (!bean.equals(that.bean)) return false;
|
||||
return help.equals(that.help);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = method.hashCode();
|
||||
result = 31 * result + bean.hashCode();
|
||||
result = 31 * result + help.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 09/12/15.
|
||||
*/
|
||||
public interface MethodTargetResolver {
|
||||
|
||||
public Map<String, MethodTarget> resolve(ApplicationContext context);
|
||||
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 09/12/15.
|
||||
*/
|
||||
public interface MethodTargetResolver {
|
||||
|
||||
public Map<String, MethodTarget> resolve(ApplicationContext context);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,125 +1,137 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
* The original method parameter this is describing.
|
||||
*/
|
||||
private final MethodParameter parameter;
|
||||
|
||||
/**
|
||||
* 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(MethodParameter parameter, String type) {
|
||||
this.parameter = parameter;
|
||||
this.type = type;
|
||||
this.formal = type;
|
||||
}
|
||||
|
||||
public static ParameterDescription outOf(MethodParameter parameter) {
|
||||
Class<?> type = parameter.getParameterType();
|
||||
return new ParameterDescription(parameter, Utils.unCamelify(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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s %s", keys().iterator().next(), formal());
|
||||
}
|
||||
|
||||
public MethodParameter parameter() {
|
||||
return parameter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,40 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* An example commands class.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Florent Biville
|
||||
*/
|
||||
public class Remote {
|
||||
|
||||
/**
|
||||
* A command method that showcases<ul>
|
||||
* <li>default handling for booleans (force)</li>
|
||||
* <li>default parameter name discovery (name)</li>
|
||||
* <li>default value supplying (foo and bar)</li>
|
||||
* </ul>
|
||||
*/
|
||||
@ShellMethod
|
||||
public void zap(boolean force,
|
||||
String name,
|
||||
@ShellOption(defaultValue="defoolt") String foo,
|
||||
@ShellOption(value = {"bar", "baz"}, defaultValue = "last") String bar) {
|
||||
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Thrown by a {@link ParameterResolver} when a parameter that should have been set has been left out altogether.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class ParameterMissingResolutionException extends RuntimeException {
|
||||
|
||||
private final ParameterDescription parameterDescription;
|
||||
|
||||
public ParameterMissingResolutionException(ParameterDescription parameterDescription) {
|
||||
this.parameterDescription = parameterDescription;
|
||||
}
|
||||
|
||||
public ParameterDescription getParameterDescription() {
|
||||
return parameterDescription;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return String.format("Parameter '%s' should be specified", parameterDescription);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,36 @@
|
||||
/*
|
||||
* 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 org.springframework.core.MethodParameter;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 27/11/15.
|
||||
*/
|
||||
public interface ParameterResolver {
|
||||
|
||||
boolean supports(MethodParameter parameter);
|
||||
|
||||
Object resolve(MethodParameter methodParameter, List<String> words);
|
||||
|
||||
ParameterDescription describe(MethodParameter parameter);
|
||||
|
||||
}
|
||||
/*
|
||||
* 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 org.springframework.core.MethodParameter;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 27/11/15.
|
||||
*/
|
||||
public interface ParameterResolver {
|
||||
|
||||
boolean supports(MethodParameter parameter);
|
||||
|
||||
Object resolve(MethodParameter methodParameter, List<String> words);
|
||||
|
||||
ParameterDescription describe(MethodParameter parameter);
|
||||
|
||||
List<CompletionProposal> complete(MethodParameter parameter, CompletionContext context);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,29 +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();
|
||||
|
||||
}
|
||||
/*
|
||||
* 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();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2;
|
||||
|
||||
/**
|
||||
* Thrown during parameter resolution, when a parameter has been identified, but could not be correctly resolved.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class UnfinishedParameterResolutionException extends RuntimeException {
|
||||
|
||||
private final ParameterDescription parameterDescription;
|
||||
|
||||
private final CharSequence input;
|
||||
|
||||
public UnfinishedParameterResolutionException(ParameterDescription parameterDescription, CharSequence input) {
|
||||
this.parameterDescription = parameterDescription;
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
public ParameterDescription getParameterDescription() {
|
||||
return parameterDescription;
|
||||
}
|
||||
|
||||
public CharSequence getInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return String.format("Error trying to resolve '%s' using [%s]", parameterDescription, input);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,79 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* 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.Constructor;
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from JDK {@link Parameter} to Spring {@link MethodParameter}.
|
||||
*/
|
||||
public static MethodParameter createMethodParameter(Parameter parameter) {
|
||||
Parameter[] parameters = parameter.getDeclaringExecutable().getParameters();
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
if (parameters[i].equals(parameter)) {
|
||||
return createMethodParameter(parameter.getDeclaringExecutable(), i);
|
||||
}
|
||||
}
|
||||
throw new AssertionError("Can't happen");
|
||||
}
|
||||
/**
|
||||
* Return a properly initialized MethodParameter for the given executable and index.
|
||||
*/
|
||||
public static MethodParameter createMethodParameter(Executable executable, int i) {
|
||||
MethodParameter methodParameter;
|
||||
if (executable instanceof Method) {
|
||||
methodParameter = new MethodParameter((Method) executable, i);
|
||||
} else if (executable instanceof Constructor){
|
||||
methodParameter = new MethodParameter((Constructor) executable, i);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported Executable: " + executable);
|
||||
}
|
||||
methodParameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
|
||||
return methodParameter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 org.jline.terminal.Terminal;
|
||||
import org.jline.utils.InfoCmp;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.shell2.standard.ShellComponent;
|
||||
import org.springframework.shell2.standard.ShellMethod;
|
||||
|
||||
/**
|
||||
* ANSI console related commands.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@ShellComponent
|
||||
public class Console {
|
||||
|
||||
@Autowired
|
||||
private Terminal terminal;
|
||||
|
||||
@ShellMethod(help = "Clear the shell screen.")
|
||||
public void clear() {
|
||||
terminal.puts(InfoCmp.Capability.clear_screen);
|
||||
}
|
||||
}
|
||||
@@ -1,218 +1,221 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* 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.standard.ShellComponent;
|
||||
import org.springframework.shell2.standard.ShellMethod;
|
||||
import org.springframework.shell2.standard.ShellOption;
|
||||
import org.springframework.shell2.Utils;
|
||||
|
||||
/**
|
||||
* 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
|
||||
if (!parameterDescriptions.isEmpty()) {
|
||||
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 = Utils.createMethodParameter(methodTarget.getMethod(), i);
|
||||
if (parameterResolver.supports(methodParameter)) {
|
||||
parameterDescriptions.add(parameterResolver.describe(methodParameter));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return parameterDescriptions;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
/*
|
||||
* 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 org.springframework.shell2.ExitRequest;
|
||||
import org.springframework.shell2.ShellComponent;
|
||||
import org.springframework.shell2.ShellMethod;
|
||||
|
||||
/**
|
||||
* A command that terminates the running shell.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@ShellComponent("")
|
||||
public class Quit {
|
||||
|
||||
@ShellMethod(help = "Exit the shell")
|
||||
public void quit() {
|
||||
throw new ExitRequest();
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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 org.springframework.shell2.ExitRequest;
|
||||
import org.springframework.shell2.standard.ShellComponent;
|
||||
import org.springframework.shell2.standard.ShellMethod;
|
||||
|
||||
/**
|
||||
* A command that terminates the running shell.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@ShellComponent
|
||||
public class Quit {
|
||||
|
||||
@ShellMethod(help = "Exit the shell.", value = {"quit", "exit"})
|
||||
public void quit() {
|
||||
throw new ExitRequest();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,84 +1,91 @@
|
||||
/*
|
||||
* 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;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.beust.jcommander.DynamicParameter;
|
||||
import com.beust.jcommander.JCommander;
|
||||
import com.beust.jcommander.Parameter;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 15/12/15.
|
||||
*/
|
||||
public class JCommanderParameterResolver implements ParameterResolver {
|
||||
|
||||
private static final Collection<Class<? extends Annotation>> JCOMMANDER_ANNOTATIONS =
|
||||
Arrays.asList(Parameter.class, DynamicParameter.class, ParametersDelegate.class);
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter parameter) {
|
||||
AtomicBoolean isSupported = new AtomicBoolean(false);
|
||||
|
||||
Class<?> parameterType = parameter.getParameterType();
|
||||
ReflectionUtils.doWithFields(parameterType, field -> {
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
boolean hasAnnotation = Arrays.asList(field.getAnnotations())
|
||||
.stream()
|
||||
.map(Annotation::annotationType)
|
||||
.anyMatch(JCOMMANDER_ANNOTATIONS::contains);
|
||||
isSupported.compareAndSet(false, hasAnnotation);
|
||||
|
||||
});
|
||||
|
||||
ReflectionUtils.doWithMethods(parameterType, method -> {
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
boolean hasAnnotation = Arrays.asList(method.getAnnotations())
|
||||
.stream()
|
||||
.map(Annotation::annotationType)
|
||||
.anyMatch(Parameter.class::equals);
|
||||
isSupported.compareAndSet(false, hasAnnotation);
|
||||
});
|
||||
return isSupported.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolve(MethodParameter methodParameter, List<String> words) {
|
||||
Object pojo = BeanUtils.instantiateClass(methodParameter.getParameterType());
|
||||
JCommander jCommander = new JCommander();
|
||||
jCommander.addObject(pojo);
|
||||
jCommander.setAcceptUnknownOptions(true);
|
||||
jCommander.parse(words.toArray(new String[words.size()]));
|
||||
return pojo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParameterDescription describe(MethodParameter parameter) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.beust.jcommander.DynamicParameter;
|
||||
import com.beust.jcommander.JCommander;
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.ParametersDelegate;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.shell2.CompletionContext;
|
||||
import org.springframework.shell2.CompletionProposal;
|
||||
import org.springframework.shell2.ParameterDescription;
|
||||
import org.springframework.shell2.ParameterResolver;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 15/12/15.
|
||||
*/
|
||||
public class JCommanderParameterResolver implements ParameterResolver {
|
||||
|
||||
private static final Collection<Class<? extends Annotation>> JCOMMANDER_ANNOTATIONS =
|
||||
Arrays.asList(Parameter.class, DynamicParameter.class, ParametersDelegate.class);
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter parameter) {
|
||||
AtomicBoolean isSupported = new AtomicBoolean(false);
|
||||
|
||||
Class<?> parameterType = parameter.getParameterType();
|
||||
ReflectionUtils.doWithFields(parameterType, field -> {
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
boolean hasAnnotation = Arrays.asList(field.getAnnotations())
|
||||
.stream()
|
||||
.map(Annotation::annotationType)
|
||||
.anyMatch(JCOMMANDER_ANNOTATIONS::contains);
|
||||
isSupported.compareAndSet(false, hasAnnotation);
|
||||
|
||||
});
|
||||
|
||||
ReflectionUtils.doWithMethods(parameterType, method -> {
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
boolean hasAnnotation = Arrays.asList(method.getAnnotations())
|
||||
.stream()
|
||||
.map(Annotation::annotationType)
|
||||
.anyMatch(Parameter.class::equals);
|
||||
isSupported.compareAndSet(false, hasAnnotation);
|
||||
});
|
||||
return isSupported.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolve(MethodParameter methodParameter, List<String> words) {
|
||||
Object pojo = BeanUtils.instantiateClass(methodParameter.getParameterType());
|
||||
JCommander jCommander = new JCommander();
|
||||
jCommander.addObject(pojo);
|
||||
jCommander.setAcceptUnknownOptions(true);
|
||||
jCommander.parse(words.toArray(new String[words.size()]));
|
||||
return pojo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParameterDescription describe(MethodParameter parameter) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CompletionProposal> complete(MethodParameter parameter, CompletionContext context) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
/*
|
||||
* 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;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.shell.core.CommandMarker;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
import org.springframework.shell2.MethodTarget;
|
||||
import org.springframework.shell2.MethodTargetResolver;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
@Override
|
||||
public Map<String, MethodTarget> resolve(ApplicationContext context) {
|
||||
Map<String, MethodTarget> methodTargets = new HashMap<>();
|
||||
Map<String, CommandMarker> beans = context.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()) {
|
||||
methodTargets.put(key, new MethodTarget(method, bean, cliCommand.help()));
|
||||
}
|
||||
}, method -> method.getAnnotation(CliCommand.class) != null);
|
||||
}
|
||||
return methodTargets;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.shell.core.CommandMarker;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
import org.springframework.shell2.MethodTarget;
|
||||
import org.springframework.shell2.MethodTargetResolver;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
@Override
|
||||
public Map<String, MethodTarget> resolve(ApplicationContext context) {
|
||||
Map<String, MethodTarget> methodTargets = new HashMap<>();
|
||||
Map<String, CommandMarker> beans = context.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()) {
|
||||
methodTargets.put(key, new MethodTarget(method, bean, cliCommand.help()));
|
||||
}
|
||||
}, method -> method.getAnnotation(CliCommand.class) != null);
|
||||
}
|
||||
return methodTargets;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,132 +1,139 @@
|
||||
/*
|
||||
* 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;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 09/12/15.
|
||||
*/
|
||||
@Component
|
||||
public class LegacyParameterResolver implements ParameterResolver {
|
||||
|
||||
@Autowired(required = false)
|
||||
private Collection<Converter<?>> converters = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter parameter) {
|
||||
return parameter.hasParameterAnnotation(CliOption.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolve(MethodParameter methodParameter, List<String> words) {
|
||||
CliOption cliOption = methodParameter.getParameterAnnotation(CliOption.class);
|
||||
Optional<Converter<?>> converter = converters.stream()
|
||||
.filter(c -> c.supports(methodParameter.getParameterType(), cliOption.optionContext()))
|
||||
.findFirst();
|
||||
|
||||
Map<String, String> values = parseOptions(words);
|
||||
Map<String, Object> seenValues = convertValues(values, methodParameter, converter);
|
||||
switch (seenValues.size()) {
|
||||
case 0:
|
||||
if (!cliOption.mandatory()) {
|
||||
String value = cliOption.unspecifiedDefaultValue();
|
||||
return converter
|
||||
.orElseThrow(noConverterFound(cliOption.key()[0], value, methodParameter.getParameterType()))
|
||||
.convertFromText(value, methodParameter.getParameterType(), cliOption.optionContext());
|
||||
}
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
@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++) {
|
||||
String word = words.get(i);
|
||||
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;
|
||||
Assert.isTrue(!values.containsKey(key), String.format("Option --%s has already been set", key));
|
||||
values.put(key, value);
|
||||
} // Must be the 'anonymous' option
|
||||
else {
|
||||
Assert.isTrue(!values.containsKey(""), "Anonymous option has already been set");
|
||||
values.put("", word);
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private Map<String, Object> convertValues(Map<String, String> values, MethodParameter methodParameter, Optional<Converter<?>> converter) {
|
||||
Map<String, Object> seenValues = new HashMap<>();
|
||||
CliOption option = methodParameter.getParameterAnnotation(CliOption.class);
|
||||
for (String key : option.key()) {
|
||||
if (values.containsKey(key)) {
|
||||
String value = values.get(key);
|
||||
if (value == null && !"__NULL__".equals(option.specifiedDefaultValue())) {
|
||||
value = option.specifiedDefaultValue();
|
||||
}
|
||||
Class<?> parameterType = methodParameter.getParameterType();
|
||||
seenValues.put(key, converter
|
||||
.orElseThrow(noConverterFound(key, value, parameterType))
|
||||
.convertFromText(value, parameterType, option.optionContext()));
|
||||
}
|
||||
}
|
||||
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>" : "--" + s).collect(Collectors.joining(", ", "[", "]"));
|
||||
}
|
||||
|
||||
private Supplier<IllegalStateException> noConverterFound(String key, String value, Class<?> parameterType) {
|
||||
return () -> new IllegalStateException("No converter found for --" + key + " from '" + value + "' to type " + parameterType);
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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.CompletionContext;
|
||||
import org.springframework.shell2.CompletionProposal;
|
||||
import org.springframework.shell2.ParameterDescription;
|
||||
import org.springframework.shell2.ParameterResolver;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 09/12/15.
|
||||
*/
|
||||
@Component
|
||||
public class LegacyParameterResolver implements ParameterResolver {
|
||||
|
||||
@Autowired(required = false)
|
||||
private Collection<Converter<?>> converters = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter parameter) {
|
||||
return parameter.hasParameterAnnotation(CliOption.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolve(MethodParameter methodParameter, List<String> words) {
|
||||
CliOption cliOption = methodParameter.getParameterAnnotation(CliOption.class);
|
||||
Optional<Converter<?>> converter = converters.stream()
|
||||
.filter(c -> c.supports(methodParameter.getParameterType(), cliOption.optionContext()))
|
||||
.findFirst();
|
||||
|
||||
Map<String, String> values = parseOptions(words);
|
||||
Map<String, Object> seenValues = convertValues(values, methodParameter, converter);
|
||||
switch (seenValues.size()) {
|
||||
case 0:
|
||||
if (!cliOption.mandatory()) {
|
||||
String value = cliOption.unspecifiedDefaultValue();
|
||||
return converter
|
||||
.orElseThrow(noConverterFound(cliOption.key()[0], value, methodParameter.getParameterType()))
|
||||
.convertFromText(value, methodParameter.getParameterType(), cliOption.optionContext());
|
||||
}
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParameterDescription describe(MethodParameter parameter) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CompletionProposal> complete(MethodParameter parameter, CompletionContext context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Map<String, String> parseOptions(List<String> words) {
|
||||
Map<String, String> values = new HashMap<>();
|
||||
for (int i = 0; i < words.size(); i++) {
|
||||
String word = words.get(i);
|
||||
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;
|
||||
Assert.isTrue(!values.containsKey(key), String.format("Option --%s has already been set", key));
|
||||
values.put(key, value);
|
||||
} // Must be the 'anonymous' option
|
||||
else {
|
||||
Assert.isTrue(!values.containsKey(""), "Anonymous option has already been set");
|
||||
values.put("", word);
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private Map<String, Object> convertValues(Map<String, String> values, MethodParameter methodParameter, Optional<Converter<?>> converter) {
|
||||
Map<String, Object> seenValues = new HashMap<>();
|
||||
CliOption option = methodParameter.getParameterAnnotation(CliOption.class);
|
||||
for (String key : option.key()) {
|
||||
if (values.containsKey(key)) {
|
||||
String value = values.get(key);
|
||||
if (value == null && !"__NULL__".equals(option.specifiedDefaultValue())) {
|
||||
value = option.specifiedDefaultValue();
|
||||
}
|
||||
Class<?> parameterType = methodParameter.getParameterType();
|
||||
seenValues.put(key, converter
|
||||
.orElseThrow(noConverterFound(key, value, parameterType))
|
||||
.convertFromText(value, parameterType, option.optionContext()));
|
||||
}
|
||||
}
|
||||
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>" : "--" + s).collect(Collectors.joining(", ", "[", "]"));
|
||||
}
|
||||
|
||||
private Supplier<IllegalStateException> noConverterFound(String key, String value, Class<?> parameterType) {
|
||||
return () -> new IllegalStateException("No converter found for --" + key + " from '" + value + "' to type " + parameterType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
/*
|
||||
* 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.result;
|
||||
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.utils.AttributedCharSequence;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* A {@link ResultHandler} that knows how to render JLine's {@link AttributedCharSequence}.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@Component
|
||||
public class AttributedCharSequenceResultHandler implements ResultHandler<AttributedCharSequence> {
|
||||
|
||||
private final Terminal terminal;
|
||||
|
||||
@Autowired
|
||||
public AttributedCharSequenceResultHandler(Terminal terminal) {
|
||||
this.terminal = terminal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResult(AttributedCharSequence result) {
|
||||
System.out.println(result.toAnsi(terminal));
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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.result;
|
||||
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.utils.AttributedCharSequence;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* A {@link ResultHandler} that knows how to render JLine's {@link AttributedCharSequence}.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@Component
|
||||
public class AttributedCharSequenceResultHandler implements ResultHandler<AttributedCharSequence> {
|
||||
|
||||
private final Terminal terminal;
|
||||
|
||||
@Autowired
|
||||
public AttributedCharSequenceResultHandler(Terminal terminal) {
|
||||
this.terminal = terminal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResult(AttributedCharSequence result) {
|
||||
System.out.println(result.toAnsi(terminal));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
/*
|
||||
* 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.result;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* A simple {@link ResultHandler} that deals with Objects (hence comes as a last resort)
|
||||
* and prints the {@link Object#toString()} value of results to standard out.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@Component
|
||||
public class DefaultResultHandler implements ResultHandler<Object> {
|
||||
|
||||
@Override
|
||||
public void handleResult(Object result) {
|
||||
System.out.println(String.valueOf(result));
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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.result;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* A simple {@link ResultHandler} that deals with Objects (hence comes as a last resort)
|
||||
* and prints the {@link Object#toString()} value of results to standard out.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@Component
|
||||
public class DefaultResultHandler implements ResultHandler<Object> {
|
||||
|
||||
@Override
|
||||
public void handleResult(Object result) {
|
||||
System.out.println(String.valueOf(result));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
/*
|
||||
* 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.result;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* A {@link ResultHandler} that deals with {@link Iterable}s and delegates to
|
||||
* {@link ResultHandlers} for each element in turn.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@Component
|
||||
public class IterableResultHandler implements ResultHandler<Iterable> {
|
||||
|
||||
private ResultHandlers resultHandlers;
|
||||
|
||||
@Autowired
|
||||
public void setResultHandlers(ResultHandlers resultHandlers) {
|
||||
this.resultHandlers = resultHandlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResult(Iterable result) {
|
||||
for (Object o : result) {
|
||||
resultHandlers.handleResult(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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.result;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* A {@link ResultHandler} that deals with {@link Iterable}s and delegates to
|
||||
* {@link ResultHandlers} for each element in turn.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@Component
|
||||
public class IterableResultHandler implements ResultHandler<Iterable> {
|
||||
|
||||
private ResultHandlers resultHandlers;
|
||||
|
||||
@Autowired
|
||||
public void setResultHandlers(ResultHandlers resultHandlers) {
|
||||
this.resultHandlers = resultHandlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResult(Iterable result) {
|
||||
for (Object o : result) {
|
||||
resultHandlers.handleResult(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.result;
|
||||
|
||||
/**
|
||||
* Implementations know how to deal with results of method invocations, whether normal results or exceptions thrown.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public interface ResultHandler<T> {
|
||||
|
||||
/**
|
||||
* Deal with some method execution result, whether it was the normal return value, or some kind
|
||||
* of {@link Throwable}.
|
||||
*/
|
||||
void handleResult(T result);
|
||||
|
||||
}
|
||||
/*
|
||||
* 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.result;
|
||||
|
||||
/**
|
||||
* Implementations know how to deal with results of method invocations, whether normal results or exceptions thrown.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public interface ResultHandler<T> {
|
||||
|
||||
/**
|
||||
* Deal with some method execution result, whether it was the normal return value, or some kind
|
||||
* of {@link Throwable}.
|
||||
*/
|
||||
void handleResult(T result);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,73 +1,73 @@
|
||||
/*
|
||||
* 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.result;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* A unique entry point delegating to the most appropriate {@link ResultHandler},
|
||||
* according to the type of result to handle.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@Component
|
||||
public class ResultHandlers {
|
||||
|
||||
private Map<Class<?>, ResultHandler<?>> resultHandlers = new HashMap<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void handleResult(Object result) {
|
||||
if (result == null) { // void methods
|
||||
return;
|
||||
}
|
||||
Class<?> clazz = result.getClass();
|
||||
ResultHandler handler = getResultHandler(clazz);
|
||||
handler.handleResult(result);
|
||||
}
|
||||
|
||||
private ResultHandler getResultHandler(Class<?> clazz) {
|
||||
ResultHandler handler = resultHandlers.get(clazz);
|
||||
if (handler != null) {
|
||||
return handler;
|
||||
}
|
||||
else {
|
||||
for (Class type : clazz.getInterfaces()) {
|
||||
handler = getResultHandler(type);
|
||||
if (handler != null) {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
return clazz.getSuperclass() != null ? getResultHandler(clazz.getSuperclass()) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setResultHandlers(Set<ResultHandler<?>> resultHandlers) {
|
||||
for (ResultHandler<?> resultHandler : resultHandlers) {
|
||||
Type type = ((ParameterizedType) resultHandler.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0];
|
||||
this.resultHandlers.put((Class<?>) type, resultHandler);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* 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.result;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* A unique entry point delegating to the most appropriate {@link ResultHandler},
|
||||
* according to the type of result to handle.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@Component
|
||||
public class ResultHandlers {
|
||||
|
||||
private Map<Class<?>, ResultHandler<?>> resultHandlers = new HashMap<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void handleResult(Object result) {
|
||||
if (result == null) { // void methods
|
||||
return;
|
||||
}
|
||||
Class<?> clazz = result.getClass();
|
||||
ResultHandler handler = getResultHandler(clazz);
|
||||
handler.handleResult(result);
|
||||
}
|
||||
|
||||
private ResultHandler getResultHandler(Class<?> clazz) {
|
||||
ResultHandler handler = resultHandlers.get(clazz);
|
||||
if (handler != null) {
|
||||
return handler;
|
||||
}
|
||||
else {
|
||||
for (Class type : clazz.getInterfaces()) {
|
||||
handler = getResultHandler(type);
|
||||
if (handler != null) {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
return clazz.getSuperclass() != null ? getResultHandler(clazz.getSuperclass()) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setResultHandlers(Set<ResultHandler<?>> resultHandlers) {
|
||||
for (ResultHandler<?> resultHandler : resultHandlers) {
|
||||
Type type = ((ParameterizedType) resultHandler.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0];
|
||||
this.resultHandlers.put((Class<?>) type, resultHandler);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.result;
|
||||
|
||||
import org.jline.utils.AttributedString;
|
||||
import org.jline.utils.AttributedStyle;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* A {@link ResultHandler} that prints thrown exceptions messages in red.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@Component
|
||||
public class ThrowableResultHandler implements ResultHandler<Throwable> {
|
||||
|
||||
@Override
|
||||
public void handleResult(Throwable result) {
|
||||
System.out.println(new AttributedString(result.getMessage(),
|
||||
AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)).toAnsi());
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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.result;
|
||||
|
||||
import org.jline.utils.AttributedString;
|
||||
import org.jline.utils.AttributedStyle;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* A {@link ResultHandler} that prints thrown exceptions messages in red.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@Component
|
||||
public class ThrowableResultHandler implements ResultHandler<Throwable> {
|
||||
|
||||
@Override
|
||||
public void handleResult(Throwable result) {
|
||||
System.out.println(new AttributedString(result.toString(),
|
||||
AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)).toAnsi());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.standard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.shell2.CompletionContext;
|
||||
import org.springframework.shell2.CompletionProposal;
|
||||
|
||||
/**
|
||||
* A {@link ValueProvider} that knows how to complete values for {@link Enum} typed parameters.
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class EnumValueProvider implements ValueProvider {
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter parameter, CompletionContext completionContext) {
|
||||
return Enum.class.isAssignableFrom(parameter.getParameterType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CompletionProposal> complete(MethodParameter parameter, CompletionContext completionContext, String[] hints) {
|
||||
List<CompletionProposal> result = new ArrayList<>();
|
||||
for (Object v : parameter.getParameterType().getEnumConstants()) {
|
||||
Enum e = (Enum) v;
|
||||
String prefix = completionContext.currentWordUpToCursor();
|
||||
if (prefix == null) {
|
||||
prefix = "";
|
||||
}
|
||||
if (e.name().startsWith(prefix)) {
|
||||
result.add(new CompletionProposal(e.name()));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,47 @@
|
||||
/*
|
||||
* 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;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
@Documented
|
||||
@Component
|
||||
public @interface ShellComponent {
|
||||
|
||||
/**
|
||||
* Used to indicate a suggestion for a logical name for the component.
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.standard;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
@Documented
|
||||
@Component
|
||||
public @interface ShellComponent {
|
||||
|
||||
/**
|
||||
* Used to indicate a suggestion for a logical name for the component.
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
@@ -1,52 +1,53 @@
|
||||
/*
|
||||
* 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;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Used to mark a method as invokable via Spring Shell.
|
||||
* @author Eric Bottard
|
||||
* @author Florent Biville
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
@Documented
|
||||
public @interface ShellMethod {
|
||||
|
||||
/**
|
||||
* The name(s) by which this method can be invoked via Spring Shell. If not specified, the actual method name
|
||||
* will be used (turning camelCase humps into "-").
|
||||
*/
|
||||
String[] value() default {};
|
||||
|
||||
/**
|
||||
* A description for the command. Should not contain any formatting (e.g. html) characters and would typically
|
||||
* start with a capital letter and end with a dot.
|
||||
*/
|
||||
String help() default "";
|
||||
|
||||
/**
|
||||
* The prefix to use for assigning parameters by name.
|
||||
*/
|
||||
String prefix() default "--";
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.standard;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Used to mark a method as invokable via Spring Shell.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Florent Biville
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
@Documented
|
||||
public @interface ShellMethod {
|
||||
|
||||
/**
|
||||
* The name(s) by which this method can be invoked via Spring Shell. If not specified, the actual method name
|
||||
* will be used (turning camelCase humps into "-").
|
||||
*/
|
||||
String[] value() default {};
|
||||
|
||||
/**
|
||||
* A description for the command. Should not contain any formatting (e.g. html) characters and would typically
|
||||
* start with a capital letter and end with a dot.
|
||||
*/
|
||||
String help() default "";
|
||||
|
||||
/**
|
||||
* The prefix to use for assigning parameters by name.
|
||||
*/
|
||||
String prefix() default "--";
|
||||
|
||||
}
|
||||
@@ -1,57 +1,66 @@
|
||||
/*
|
||||
* 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;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Used to customize handling of a {@link ShellMethod} parameters.
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.PARAMETER)
|
||||
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.
|
||||
*/
|
||||
String[] value() default {};
|
||||
|
||||
/**
|
||||
* Return the number of input "words" this parameter consumes.
|
||||
*/
|
||||
int arity() default 1;
|
||||
|
||||
/**
|
||||
* The textual (pre-conversion) value to assign to this parameter if no value is provided by the user.
|
||||
*/
|
||||
String defaultValue() default NONE;
|
||||
|
||||
/**
|
||||
* Return a short description of the parameter.
|
||||
*/
|
||||
String help() default "";
|
||||
}
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.standard;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Used to customize handling of a {@link ShellMethod} parameter.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Florent Biville
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.PARAMETER)
|
||||
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.
|
||||
*/
|
||||
String[] value() default {};
|
||||
|
||||
/**
|
||||
* Return the number of input "words" this parameter consumes.
|
||||
*/
|
||||
int arity() default 1;
|
||||
|
||||
/**
|
||||
* The textual (pre-conversion) value to assign to this parameter if no value is provided by the user.
|
||||
*/
|
||||
String defaultValue() default NONE;
|
||||
|
||||
/**
|
||||
* Return a short description of the parameter.
|
||||
*/
|
||||
String help() default "";
|
||||
|
||||
Class<? extends ValueProvider> valueProvider() default NoValueProvider.class;
|
||||
|
||||
interface NoValueProvider extends ValueProvider {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,51 +1,57 @@
|
||||
/*
|
||||
* 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;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 09/12/15.
|
||||
*/
|
||||
@Component
|
||||
public class DefaultMethodTargetResolver implements MethodTargetResolver {
|
||||
|
||||
@Override
|
||||
public Map<String, MethodTarget> resolve(ApplicationContext applicationContext) {
|
||||
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 == 0) {
|
||||
keys = new String[] {method.getName()};
|
||||
}
|
||||
for (String key : keys) {
|
||||
methodTargets.put(key, new MethodTarget(method, bean, shellMapping.help()));
|
||||
}
|
||||
}, method -> method.getAnnotation(ShellMethod.class) != null);
|
||||
}
|
||||
return methodTargets;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.standard;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.shell2.MethodTarget;
|
||||
import org.springframework.shell2.MethodTargetResolver;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* The standard implementation of {@link MethodTargetResolver} for new shell applications,
|
||||
* resolves methods annotated with {@link ShellMethod} on {@link ShellComponent} beans.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Florent Biville
|
||||
*/
|
||||
@Component
|
||||
public class StandardMethodTargetResolver implements MethodTargetResolver {
|
||||
|
||||
@Override
|
||||
public Map<String, MethodTarget> resolve(ApplicationContext applicationContext) {
|
||||
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 == 0) {
|
||||
keys = new String[] {method.getName()};
|
||||
}
|
||||
for (String key : keys) {
|
||||
methodTargets.put(key, new MethodTarget(method, bean, shellMapping.help()));
|
||||
}
|
||||
}, method -> method.getAnnotation(ShellMethod.class) != null);
|
||||
}
|
||||
return methodTargets;
|
||||
}
|
||||
}
|
||||
@@ -1,307 +1,458 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2;
|
||||
|
||||
import static org.springframework.shell2.Utils.unCamelify;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
|
||||
/**
|
||||
* Default ParameterResolver implementation that supports the following features:<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>
|
||||
* </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}
|
||||
* (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
|
||||
* 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
|
||||
*/
|
||||
public class DefaultParameterResolver implements ParameterResolver {
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
/**
|
||||
* A cache from method+input to String representation of actual parameter values.
|
||||
* Note that the converted result is not cached, to allow dynamic computation to happen at every invocation
|
||||
* if needed (e.g. if a remote service is involved).
|
||||
*/
|
||||
private final Map<CacheKey, Map<Parameter, String>> parameterCache = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
public DefaultParameterResolver(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter parameter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolve(MethodParameter methodParameter, List<String> words) {
|
||||
String prefix = prefixForMethod(methodParameter);
|
||||
|
||||
CacheKey cacheKey = new CacheKey(methodParameter.getMethod(), words);
|
||||
Map<Parameter, String> resolved = parameterCache.computeIfAbsent(cacheKey, (k) -> {
|
||||
|
||||
Map<Parameter, String> result = new HashMap<>();
|
||||
Map<String, String> namedParameters = new HashMap<>();
|
||||
List<String> positionalValues = new ArrayList<>();
|
||||
|
||||
// First, resolve all parameters passed by-name
|
||||
for (int i = 0; i < words.size(); i++) {
|
||||
String word = words.get(i);
|
||||
if (word.startsWith(prefix)) {
|
||||
String key = word.substring(prefix.length());
|
||||
Parameter parameter = lookupParameterForKey(methodParameter.getMethod(), key, prefix);
|
||||
int arity = getArity(parameter);
|
||||
|
||||
String raw = words.subList(i + 1, i + 1 + arity).stream().collect(Collectors.joining(","));
|
||||
Assert.isTrue(!namedParameters.containsKey(key), String.format("Parameter for '%s' has already been specified", word));
|
||||
namedParameters.put(key, raw);
|
||||
result.put(parameter, raw);
|
||||
i += arity;
|
||||
if (arity == 0) {
|
||||
boolean defaultValue = booleanDefaultValue(parameter);
|
||||
// Boolean parameter has been specified. Use the opposite of the default value
|
||||
result.put(parameter, String.valueOf(!defaultValue));
|
||||
}
|
||||
} // store for later processing of positional params
|
||||
else {
|
||||
positionalValues.add(word);
|
||||
}
|
||||
}
|
||||
|
||||
// Now have a second pass over params and treat them as positional
|
||||
int offset = 0;
|
||||
Parameter[] parameters = methodParameter.getMethod().getParameters();
|
||||
for (int i = 0, parametersLength = parameters.length; i < parametersLength; i++) {
|
||||
Parameter parameter = parameters[i];
|
||||
// Compute the intersection between possible keys for the param and what we've already seen for named params
|
||||
Collection<String> keys = getKeysForParameter(methodParameter.getMethod(), i);
|
||||
Collection<String> copy = new HashSet<>(keys);
|
||||
copy.retainAll(namedParameters.keySet());
|
||||
if (copy.isEmpty()) { // Was not set via a key (including aliases), must be positional
|
||||
int arity = getArity(parameter);
|
||||
if (offset < positionalValues.size() && (offset + arity) <= positionalValues.size()) {
|
||||
String raw = positionalValues.subList(offset, offset + arity).stream().collect(Collectors.joining(","));
|
||||
result.put(parameter, raw);
|
||||
offset += arity;
|
||||
} // No more input. Try defaultValues
|
||||
else {
|
||||
Optional<String> defaultValue = defaultValueFor(parameter);
|
||||
String value = defaultValue.orElseThrow(() -> new RuntimeException(String.format("Ran out of input for " + keys)));
|
||||
result.put(parameter, value);
|
||||
}
|
||||
}
|
||||
else if (copy.size() > 1) {
|
||||
throw new IllegalArgumentException("Named parameter has been specified multiple times via " + prefix(copy, prefix));
|
||||
}
|
||||
}
|
||||
|
||||
Assert.isTrue(offset == positionalValues.size(), "Too many arguments: the following could not be mapped to parameters: "
|
||||
+ positionalValues.subList(offset, positionalValues.size()).stream().collect(Collectors.joining(" ", "'", "'")));
|
||||
return result;
|
||||
});
|
||||
|
||||
String s = resolved.get(methodParameter.getMethod().getParameters()[methodParameter.getParameterIndex()]);
|
||||
if (ShellOption.NULL.equals(s)) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return conversionService.convert(s, TypeDescriptor.valueOf(String.class), new TypeDescriptor(methodParameter));
|
||||
}
|
||||
}
|
||||
|
||||
private String prefixForMethod(MethodParameter methodParameter) {
|
||||
return methodParameter.getMethod().getAnnotation(ShellMethod.class).prefix();
|
||||
}
|
||||
|
||||
private Optional<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");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the command prefix back to the list of keys that was used to invoke the method.
|
||||
*/
|
||||
private String prefix(Collection<String> keys, String prefix) {
|
||||
return keys.stream().map(k -> prefix + k).collect(Collectors.joining(", ", "'", "'"));
|
||||
}
|
||||
|
||||
private boolean booleanDefaultValue(Parameter parameter) {
|
||||
ShellOption option = parameter.getAnnotation(ShellOption.class);
|
||||
if (option != null && !ShellOption.NULL.equals(option.defaultValue())) {
|
||||
return Boolean.parseBoolean(option.defaultValue());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the arity of a given parameter. The default arity is 1, except for
|
||||
* booleans where arity is 0 (can be overridden back to 1 via an annotation)
|
||||
*/
|
||||
private int getArity(Parameter parameter) {
|
||||
ShellOption option = parameter.getAnnotation(ShellOption.class);
|
||||
int inferred = (parameter.getType() == boolean.class || parameter.getType() == Boolean.class) ? 0 : 1;
|
||||
return option != null ? option.arity() : inferred;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key(s) the i-th parameter of the command method, resolved either from the {@link ShellOption}
|
||||
* annotation,
|
||||
* or from the actual parameter name.
|
||||
* @throws IllegalArgumentException if parameter names could not be extracted
|
||||
*/
|
||||
private List<String> getKeysForParameter(Method method, int index) {
|
||||
Parameter parameter = method.getParameters()[index];
|
||||
ShellOption option = parameter.getAnnotation(ShellOption.class);
|
||||
if (option != null && option.value().length > 0) {
|
||||
return Arrays.asList(option.value());
|
||||
}
|
||||
else {
|
||||
MethodParameter methodParameter = new MethodParameter(method, index);
|
||||
methodParameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
|
||||
String parameterName = methodParameter.getParameterName();
|
||||
Assert.notNull(parameterName, String.format(
|
||||
"Could not discover parameter name at index %d for %s, and option key(s) were not specified via %s annotation",
|
||||
index, method, ShellOption.class.getSimpleName()));
|
||||
return Collections.singletonList(parameterName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the method parameter that should be bound to the given key.
|
||||
*/
|
||||
private Parameter lookupParameterForKey(Method method, String key, String prefix) {
|
||||
Parameter[] parameters = method.getParameters();
|
||||
for (int i = 0, parametersLength = parameters.length; i < parametersLength; i++) {
|
||||
Parameter p = parameters[i];
|
||||
if (getKeysForParameter(method, i).contains(key)) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(String.format("Could not look up parameter for '%s%s' in %s", prefix, key, method));
|
||||
}
|
||||
|
||||
private static class CacheKey {
|
||||
|
||||
private final Method method;
|
||||
|
||||
private final List<String> words;
|
||||
|
||||
private CacheKey(Method method, List<String> words) {
|
||||
this.method = method;
|
||||
this.words = words;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
CacheKey cacheKey = (CacheKey) o;
|
||||
return Objects.equals(method, cacheKey.method) &&
|
||||
Objects.equals(words, cacheKey.words);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(method, words);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.standard;
|
||||
|
||||
import static org.springframework.shell2.Utils.unCamelify;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.shell2.CompletionContext;
|
||||
import org.springframework.shell2.CompletionProposal;
|
||||
import org.springframework.shell2.ParameterDescription;
|
||||
import org.springframework.shell2.ParameterMissingResolutionException;
|
||||
import org.springframework.shell2.ParameterResolver;
|
||||
import org.springframework.shell2.UnfinishedParameterResolutionException;
|
||||
import org.springframework.shell2.Utils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
|
||||
/**
|
||||
* Default ParameterResolver implementation that supports the following features:<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 <i>via</i> 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}
|
||||
* (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
|
||||
* 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
|
||||
*/
|
||||
public class StandardParameterResolver implements ParameterResolver {
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
private Collection<ValueProvider> valueProviders = new HashSet<>();
|
||||
|
||||
/**
|
||||
* A cache from method+input to String representation of actual parameter values.
|
||||
* Note that the converted result is not cached, to allow dynamic computation to happen at every invocation
|
||||
* if needed (e.g. if a remote service is involved).
|
||||
*/
|
||||
private final Map<CacheKey, Map<Parameter, ParameterRawValue>> parameterCache = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
public StandardParameterResolver(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setValueProviders(Collection<ValueProvider> valueProviders) {
|
||||
this.valueProviders = valueProviders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter parameter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolve(MethodParameter methodParameter, List<String> words) {
|
||||
String prefix = prefixForMethod(methodParameter);
|
||||
|
||||
CacheKey cacheKey = new CacheKey(methodParameter.getMethod(), words);
|
||||
Map<Parameter, ParameterRawValue> resolved = parameterCache.computeIfAbsent(cacheKey, (k) -> {
|
||||
|
||||
Map<Parameter, ParameterRawValue> result = new HashMap<>();
|
||||
Map<String, String> namedParameters = new HashMap<>();
|
||||
List<String> positionalValues = new ArrayList<>();
|
||||
|
||||
Set<String> possibleKeys = gatherAllPossibleKeys(methodParameter.getMethod());
|
||||
|
||||
// First, resolve all parameters passed by-name
|
||||
for (int i = 0; i < words.size(); i++) {
|
||||
String word = words.get(i);
|
||||
if (possibleKeys.contains(word)) {
|
||||
String key = word;
|
||||
Parameter parameter = lookupParameterForKey(methodParameter.getMethod(), key, prefix);
|
||||
int arity = getArity(parameter);
|
||||
|
||||
if (i + 1 + arity > words.size()) {
|
||||
String input = words.subList(i, words.size()).stream().collect(Collectors.joining(" "));
|
||||
throw new UnfinishedParameterResolutionException(describe(Utils.createMethodParameter(parameter)), input);
|
||||
}
|
||||
Assert.isTrue(i + 1 + arity <= words.size(), String.format("Not enough input for parameter '%s'", word));
|
||||
String raw = words.subList(i + 1, i + 1 + arity).stream().collect(Collectors.joining(","));
|
||||
Assert.isTrue(!namedParameters.containsKey(key), String.format("Parameter for '%s' has already been specified", word));
|
||||
namedParameters.put(key, raw);
|
||||
result.put(parameter, ParameterRawValue.explicit(raw, key));
|
||||
i += arity;
|
||||
if (arity == 0) {
|
||||
boolean defaultValue = booleanDefaultValue(parameter);
|
||||
// Boolean parameter has been specified. Use the opposite of the default value
|
||||
result.put(parameter, ParameterRawValue.explicit(String.valueOf(!defaultValue), key));
|
||||
}
|
||||
} // store for later processing of positional params
|
||||
else {
|
||||
positionalValues.add(word);
|
||||
}
|
||||
}
|
||||
|
||||
// Now have a second pass over params and treat them as positional
|
||||
int offset = 0;
|
||||
Parameter[] parameters = methodParameter.getMethod().getParameters();
|
||||
for (int i = 0, parametersLength = parameters.length; i < parametersLength; i++) {
|
||||
Parameter parameter = parameters[i];
|
||||
// Compute the intersection between possible keys for the param and what we've already seen for named params
|
||||
Collection<String> keys = getKeysForParameter(methodParameter.getMethod(), i).collect(Collectors.toSet());
|
||||
Collection<String> copy = new HashSet<>(keys);
|
||||
copy.retainAll(namedParameters.keySet());
|
||||
if (copy.isEmpty()) { // Was not set via a key (including aliases), must be positional
|
||||
int arity = getArity(parameter);
|
||||
if (arity > 0 && (offset + arity) <= positionalValues.size()) {
|
||||
String raw = positionalValues.subList(offset, offset + arity).stream().collect(Collectors.joining(","));
|
||||
result.put(parameter, ParameterRawValue.explicit(raw, null));
|
||||
offset += arity;
|
||||
} // No more input. Try defaultValues
|
||||
else {
|
||||
Optional<String> defaultValue = defaultValueFor(parameter);
|
||||
defaultValue.ifPresent(value -> result.put(parameter, ParameterRawValue.implicit(value, null)));
|
||||
}
|
||||
}
|
||||
else if (copy.size() > 1) {
|
||||
throw new IllegalArgumentException("Named parameter has been specified multiple times via " + quote(copy));
|
||||
}
|
||||
}
|
||||
|
||||
Assert.isTrue(offset == positionalValues.size(), "Too many arguments: the following could not be mapped to parameters: "
|
||||
+ positionalValues.subList(offset, positionalValues.size()).stream().collect(Collectors.joining(" ", "'", "'")));
|
||||
return result;
|
||||
});
|
||||
|
||||
Parameter param = methodParameter.getMethod().getParameters()[methodParameter.getParameterIndex()];
|
||||
if (!resolved.containsKey(param)) {
|
||||
throw new ParameterMissingResolutionException(describe(methodParameter));
|
||||
}
|
||||
String s = resolved.get(param).value;
|
||||
if (ShellOption.NULL.equals(s)) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return conversionService.convert(s, TypeDescriptor.valueOf(String.class), new TypeDescriptor(methodParameter));
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> gatherAllPossibleKeys(Method method) {
|
||||
final String prefix = "--";
|
||||
return Arrays.stream(method.getParameters())
|
||||
.flatMap(p -> {
|
||||
ShellOption option = p.getAnnotation(ShellOption.class);
|
||||
if (option != null && option.value().length > 0) {
|
||||
return Arrays.stream(option.value());
|
||||
}
|
||||
else {
|
||||
return Stream.of(prefix + Utils.createMethodParameter(p).getParameterName());
|
||||
}
|
||||
}).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private String prefixForMethod(MethodParameter methodParameter) {
|
||||
return methodParameter.getMethod().getAnnotation(ShellMethod.class).prefix();
|
||||
}
|
||||
|
||||
private Optional<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());
|
||||
}
|
||||
else if (option == null && getArity(parameter) == 0) {
|
||||
return Optional.of("false");
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private boolean booleanDefaultValue(Parameter parameter) {
|
||||
ShellOption option = parameter.getAnnotation(ShellOption.class);
|
||||
if (option != null && !ShellOption.NULL.equals(option.defaultValue())) {
|
||||
return Boolean.parseBoolean(option.defaultValue());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParameterDescription describe(MethodParameter parameter) {
|
||||
Parameter jlrParameter = parameter.getMethod().getParameters()[parameter.getParameterIndex()];
|
||||
int arity = getArity(jlrParameter);
|
||||
Class<?> type = parameter.getParameterType();
|
||||
ShellOption option = jlrParameter.getAnnotation(ShellOption.class);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < arity; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append(arity > 1 ? unCamelify(removeMultiplicityFromType(parameter).getSimpleName()) : unCamelify(type.getSimpleName()));
|
||||
}
|
||||
ParameterDescription result = ParameterDescription.outOf(parameter);
|
||||
result.formal(sb.toString());
|
||||
if (option != null) {
|
||||
result.help(option.help());
|
||||
Optional<String> defaultValue = defaultValueFor(jlrParameter);
|
||||
if (defaultValue.isPresent()) {
|
||||
result.defaultValue(defaultValue.map(dv -> dv.equals(ShellOption.NULL) ? "<none>" : dv).get());
|
||||
}
|
||||
}
|
||||
result
|
||||
.keys(getKeysForParameter(parameter.getMethod(), parameter.getParameterIndex())
|
||||
.collect(Collectors.toList()))
|
||||
.mandatoryKey(false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CompletionProposal> complete(MethodParameter methodParameter, CompletionContext context) {
|
||||
boolean set;
|
||||
Exception unfinished = null;
|
||||
// First try to see if this parameter has been set, even to some unfinished value
|
||||
ParameterRawValue parameterRawValue = null;
|
||||
try {
|
||||
resolve(methodParameter, context.getWords());
|
||||
CacheKey cacheKey = new CacheKey(methodParameter.getMethod(), context.getWords());
|
||||
Parameter parameter = methodParameter.getMethod().getParameters()[methodParameter.getParameterIndex()];
|
||||
parameterRawValue = parameterCache.get(cacheKey).get(parameter);
|
||||
set = parameterRawValue.explicit;
|
||||
}
|
||||
catch (ParameterMissingResolutionException e) {
|
||||
set = false;
|
||||
}
|
||||
catch (Exception e) {
|
||||
unfinished = e;
|
||||
set = false;
|
||||
// Most likely what is already typed would fail resolution (eg type conversion failure)
|
||||
// Exit early and let other parameters have a chance at being proposed
|
||||
//return Collections.emptyList();
|
||||
}
|
||||
|
||||
// There are 3 possible cases:
|
||||
// 1) parameter not set at all
|
||||
// 2) parameter set via its key, not enough input to consume a value
|
||||
// 3) parameter set, and some value bound. But maybe that value is just a prefix to what the user actually wants
|
||||
// 3.1) or maybe that value was resolved by position, but is a prefix of an actual valid key
|
||||
|
||||
if (!set) {
|
||||
if (unfinished == null) { // case 1 above
|
||||
return commandsThatStartWithContextPrefix(methodParameter, context);
|
||||
} // case 2
|
||||
else {
|
||||
return valueCompletions(methodParameter, context);
|
||||
}
|
||||
}
|
||||
else {
|
||||
List<CompletionProposal> result = new ArrayList<>();
|
||||
|
||||
String prefix = context.currentWordUpToCursor() != null ? context.currentWordUpToCursor() : "";
|
||||
// TODO: should not look at last word only, but everything after what was used for key
|
||||
// Case 3
|
||||
result.addAll(valueCompletions(methodParameter, context));
|
||||
|
||||
if (parameterRawValue.positional()) {
|
||||
// Case 3.1: There exists "--command foo" and user has typed "--comm" which (wrongly) got resolved as a positional param
|
||||
result.addAll(commandsThatStartWithContextPrefix(methodParameter, context));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private List<CompletionProposal> valueCompletions(MethodParameter methodParameter, CompletionContext completionContext) {
|
||||
return valueProviders.stream()
|
||||
.filter(vp -> vp.supports(methodParameter, completionContext))
|
||||
.map(vp -> vp.complete(methodParameter, completionContext, null))
|
||||
.findFirst().orElseGet(() -> Collections.emptyList());
|
||||
}
|
||||
|
||||
private List<CompletionProposal> commandsThatStartWithContextPrefix(MethodParameter methodParameter, CompletionContext context) {
|
||||
String prefix = context.currentWordUpToCursor() != null ? context.currentWordUpToCursor() : "";
|
||||
return describe(methodParameter).keys().stream()
|
||||
.filter(k -> k.startsWith(prefix))
|
||||
.map(v -> new CompletionProposal(v))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* In case of {@code foo[] or Collection<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 (Collection.class.isAssignableFrom(parameterType)) {
|
||||
return parameter.getNestedParameterType();
|
||||
}
|
||||
else {
|
||||
throw new RuntimeException("For " + parameter + " (with arity > 1) expected an array/collection type");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Surrounds the parameter keys with quotes.
|
||||
*/
|
||||
private String quote(Collection<String> keys) {
|
||||
return keys.stream().collect(Collectors.joining(", ", "'", "'"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the arity of a given parameter. The default arity is 1, except for
|
||||
* booleans where arity is 0 (can be overridden back to 1 via an annotation)
|
||||
*/
|
||||
private int getArity(Parameter parameter) {
|
||||
ShellOption option = parameter.getAnnotation(ShellOption.class);
|
||||
int inferred = (parameter.getType() == boolean.class || parameter.getType() == Boolean.class) ? 0 : 1;
|
||||
return option != null ? option.arity() : inferred;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key(s) the i-th parameter of the command method, resolved either from the {@link ShellOption}
|
||||
* annotation,
|
||||
* or from the actual parameter name.
|
||||
*/
|
||||
private Stream<String> getKeysForParameter(Method method, int index) {
|
||||
String prefix = "--";
|
||||
Parameter p = method.getParameters()[index];
|
||||
ShellOption option = p.getAnnotation(ShellOption.class);
|
||||
if (option != null && option.value().length > 0) {
|
||||
return Arrays.stream(option.value());
|
||||
}
|
||||
else {
|
||||
return Stream.of(prefix + Utils.createMethodParameter(p).getParameterName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the method parameter that should be bound to the given key.
|
||||
*/
|
||||
private Parameter lookupParameterForKey(Method method, String key, String prefix) {
|
||||
Parameter[] parameters = method.getParameters();
|
||||
for (int i = 0, parametersLength = parameters.length; i < parametersLength; i++) {
|
||||
Parameter p = parameters[i];
|
||||
if (getKeysForParameter(method, i).anyMatch(k -> k.equals(key))) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(String.format("Could not look up parameter for '%s%s' in %s", prefix, key, method));
|
||||
}
|
||||
|
||||
private static class CacheKey {
|
||||
|
||||
private final Method method;
|
||||
|
||||
private final List<String> words;
|
||||
|
||||
private CacheKey(Method method, List<String> words) {
|
||||
this.method = method;
|
||||
this.words = words;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
CacheKey cacheKey = (CacheKey) o;
|
||||
return Objects.equals(method, cacheKey.method) &&
|
||||
Objects.equals(words, cacheKey.words);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(method, words);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ParameterRawValue {
|
||||
|
||||
private CompletionContext context;
|
||||
|
||||
private int from;
|
||||
|
||||
private int to;
|
||||
|
||||
private Integer keyIndex;
|
||||
|
||||
/**
|
||||
* The raw String value that got bound to a parameter.
|
||||
*/
|
||||
private final String value;
|
||||
|
||||
/**
|
||||
* If false, the value resolved is the result of applying defaults.
|
||||
*/
|
||||
private final boolean explicit;
|
||||
|
||||
/**
|
||||
* The key that was used to set the parameter, or null if resolution happened by position.
|
||||
*/
|
||||
private final String key;
|
||||
|
||||
private ParameterRawValue(String value, boolean explicit, String key) {
|
||||
this.value = value;
|
||||
this.explicit = explicit;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public static ParameterRawValue explicit(String value, String key) {
|
||||
return new ParameterRawValue(value, true, key);
|
||||
}
|
||||
|
||||
public static ParameterRawValue implicit(String value, String key) {
|
||||
return new ParameterRawValue(value, false, key);
|
||||
}
|
||||
|
||||
public boolean positional() {
|
||||
return key == null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.standard;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.shell2.CompletionContext;
|
||||
import org.springframework.shell2.CompletionProposal;
|
||||
|
||||
/**
|
||||
*/
|
||||
public interface ValueProvider {
|
||||
|
||||
boolean supports(MethodParameter parameter, CompletionContext completionContext);
|
||||
|
||||
List<CompletionProposal> complete(MethodParameter parameter, CompletionContext completionContext, String[] hints);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.standard;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.shell2.CompletionContext;
|
||||
|
||||
/**
|
||||
*/
|
||||
public abstract class ValueProviderSupport implements ValueProvider {
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter parameter, CompletionContext completionContext) {
|
||||
ShellOption annotation = parameter.getParameterAnnotation(ShellOption.class);
|
||||
if (annotation == null) {
|
||||
return false;
|
||||
}
|
||||
return annotation.valueProvider().isAssignableFrom(this.getClass());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<configuration>
|
||||
<!-- Disable most stdout logging by default -->
|
||||
<root level="ERROR">
|
||||
</root>
|
||||
</configuration>
|
||||
<configuration>
|
||||
<!-- Disable most stdout logging by default -->
|
||||
<root level="ERROR">
|
||||
</root>
|
||||
</configuration>
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.util.ReflectionUtils.findMethod;
|
||||
|
||||
/**
|
||||
* Unit tests for DefaultParameterResolver.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Florent Biville
|
||||
*/
|
||||
public class DefaultParameterResolverTest {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private DefaultParameterResolver resolver = new DefaultParameterResolver(new DefaultConversionService());
|
||||
|
||||
@Test
|
||||
public void testParses() throws Exception {
|
||||
Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class);
|
||||
|
||||
assertThat(resolver.resolve(
|
||||
makeMethodParameter(method, 0),
|
||||
asList("--force --name --foo y".split(" "))
|
||||
)).isEqualTo(true);
|
||||
assertThat(resolver.resolve(
|
||||
makeMethodParameter(method, 1),
|
||||
asList("--force --name --foo y".split(" "))
|
||||
)).isEqualTo("--foo");
|
||||
assertThat(resolver.resolve(
|
||||
makeMethodParameter(method, 2),
|
||||
asList("--force --name --foo y".split(" "))
|
||||
)).isEqualTo("y");
|
||||
assertThat(resolver.resolve(
|
||||
makeMethodParameter(method, 3),
|
||||
asList("--force --name --foo y".split(" "))
|
||||
)).isEqualTo("last");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParameterSpecifiedTwiceViaDifferentAliases() throws Exception {
|
||||
Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class);
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Named parameter has been specified multiple times via '--bar, --baz'");
|
||||
|
||||
resolver.resolve(
|
||||
makeMethodParameter(method, 0),
|
||||
asList("--force --name --foo y --bar x --baz z".split(" "))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParameterSpecifiedTwiceViaSameKey() throws Exception {
|
||||
Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class);
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Parameter for '--baz' has already been specified");
|
||||
|
||||
resolver.resolve(
|
||||
makeMethodParameter(method, 0),
|
||||
asList("--force --name --foo y --baz x --baz z".split(" "))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnknownParameter() throws Exception {
|
||||
Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class);
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Could not look up parameter for '--unknown' in " + method);
|
||||
|
||||
resolver.resolve(
|
||||
makeMethodParameter(method, 0),
|
||||
asList("--unknown --foo bar".split(" "))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTooMuchInput() throws Exception {
|
||||
Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class);
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("the following could not be mapped to parameters: 'leftover'");
|
||||
|
||||
resolver.resolve(
|
||||
makeMethodParameter(method, 0),
|
||||
asList("--foo hello --name bar --force --bar well leftover".split(" "))
|
||||
);
|
||||
}
|
||||
|
||||
private MethodParameter makeMethodParameter(Method method, int parameterIndex) {
|
||||
MethodParameter methodParameter = new MethodParameter(method, parameterIndex);
|
||||
methodParameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
|
||||
return methodParameter;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,37 +1,37 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests for {@link Utils}.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class UtilsTest {
|
||||
|
||||
@Test
|
||||
public void testUnCamelify() throws Exception {
|
||||
assertThat(Utils.unCamelify("HelloWorld")).isEqualTo("hello-world");
|
||||
assertThat(Utils.unCamelify("helloWorld")).isEqualTo("hello-world");
|
||||
assertThat(Utils.unCamelify("helloWorldHowAreYou")).isEqualTo("hello-world-how-are-you");
|
||||
assertThat(Utils.unCamelify("URL")).isEqualTo("url");
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests for {@link Utils}.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class UtilsTest {
|
||||
|
||||
@Test
|
||||
public void testUnCamelify() throws Exception {
|
||||
assertThat(Utils.unCamelify("HelloWorld")).isEqualTo("hello-world");
|
||||
assertThat(Utils.unCamelify("helloWorld")).isEqualTo("hello-world");
|
||||
assertThat(Utils.unCamelify("helloWorldHowAreYou")).isEqualTo("hello-world-how-are-you");
|
||||
assertThat(Utils.unCamelify("URL")).isEqualTo("url");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,157 +1,157 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.commands;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestName;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.shell2.DefaultParameterResolver;
|
||||
import org.springframework.shell2.MethodTarget;
|
||||
import org.springframework.shell2.ParameterResolver;
|
||||
import org.springframework.shell2.Shell;
|
||||
import org.springframework.shell2.ShellComponent;
|
||||
import org.springframework.shell2.ShellMethod;
|
||||
import org.springframework.shell2.ShellOption;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Tests for the {@link Help} command.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = HelpTest.Config.class)
|
||||
public class HelpTest {
|
||||
|
||||
@Autowired
|
||||
private Help help;
|
||||
|
||||
@Rule
|
||||
public TestName testName = new TestName();
|
||||
|
||||
@Test
|
||||
public void testCommandHelp() throws Exception {
|
||||
CharSequence help = this.help.help("first-command").toString();
|
||||
Assertions.assertThat(help).isEqualTo(sample());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommandList() throws Exception {
|
||||
String list = this.help.help(null).toString();
|
||||
Assertions.assertThat(list).isEqualTo(sample());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testUnknownCommand() throws Exception {
|
||||
this.help.help("some unknown command");
|
||||
}
|
||||
|
||||
private String sample() throws IOException {
|
||||
InputStream is = new ClassPathResource(HelpTest.class.getSimpleName() + "-" + testName.getMethodName() + ".txt", HelpTest.class).getInputStream();
|
||||
return FileCopyUtils.copyToString(new InputStreamReader(is, "UTF-8")).replace("&", "");
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public Help help() {
|
||||
return new Help(Collections.singletonList(parameterResolver()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Shell shell() {
|
||||
return () -> {
|
||||
Map<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() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.commands;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestName;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.shell2.standard.StandardParameterResolver;
|
||||
import org.springframework.shell2.MethodTarget;
|
||||
import org.springframework.shell2.ParameterResolver;
|
||||
import org.springframework.shell2.Shell;
|
||||
import org.springframework.shell2.standard.ShellComponent;
|
||||
import org.springframework.shell2.standard.ShellMethod;
|
||||
import org.springframework.shell2.standard.ShellOption;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Tests for the {@link Help} command.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = HelpTest.Config.class)
|
||||
public class HelpTest {
|
||||
|
||||
@Autowired
|
||||
private Help help;
|
||||
|
||||
@Rule
|
||||
public TestName testName = new TestName();
|
||||
|
||||
@Test
|
||||
public void testCommandHelp() throws Exception {
|
||||
CharSequence help = this.help.help("first-command").toString();
|
||||
Assertions.assertThat(help).isEqualTo(sample());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommandList() throws Exception {
|
||||
String list = this.help.help(null).toString();
|
||||
Assertions.assertThat(list).isEqualTo(sample());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testUnknownCommand() throws Exception {
|
||||
this.help.help("some unknown command");
|
||||
}
|
||||
|
||||
private String sample() throws IOException {
|
||||
InputStream is = new ClassPathResource(HelpTest.class.getSimpleName() + "-" + testName.getMethodName() + ".txt", HelpTest.class).getInputStream();
|
||||
return FileCopyUtils.copyToString(new InputStreamReader(is, "UTF-8")).replace("&", "");
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public Help help() {
|
||||
return new Help(Collections.singletonList(parameterResolver()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Shell shell() {
|
||||
return () -> {
|
||||
Map<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 StandardParameterResolver(new DefaultConversionService());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Object commands() {
|
||||
return new Commands();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ShellComponent
|
||||
static class Commands {
|
||||
|
||||
@ShellMethod(prefix = "--")
|
||||
public void firstCommand(
|
||||
// Single key and arity = 0. Help displayed on same line
|
||||
@ShellOption(help = "Whether to delete recursively", arity = 0, value = "-r") boolean r,
|
||||
// Multiple keys and arity 0. Help displayed on next line
|
||||
@ShellOption(help = "Do not ask for confirmation. YOLO", arity = 0, value = {"-f", "--force"}) boolean force,
|
||||
// Single key, arity >= 1. Help displayed on next line. Optional
|
||||
@ShellOption(help = "The answer to everything", defaultValue = "42", value = "-n") int n,
|
||||
// Single key, arity > 1.
|
||||
@ShellOption(help = "Some other parameters", arity = 3, value = "-o") float[] o
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
@ShellMethod
|
||||
public void secondCommand() {
|
||||
|
||||
}
|
||||
|
||||
@ShellMethod
|
||||
public void thirdCommand() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.jcommander;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 15/12/15.
|
||||
*/
|
||||
public class FieldCollins {
|
||||
|
||||
@Parameter(names = "--name")
|
||||
private String name;
|
||||
|
||||
@Parameter(names = "-level")
|
||||
private int level;
|
||||
|
||||
@Parameter(description = "rest")
|
||||
private List<String> rest = new ArrayList<>();
|
||||
|
||||
public List<String> getRest() {
|
||||
return rest;
|
||||
}
|
||||
|
||||
public void setRest(List<String> rest) {
|
||||
this.rest = rest;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
public void setLevel(int level) {
|
||||
this.level = level;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.jcommander;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 15/12/15.
|
||||
*/
|
||||
public class FieldCollins {
|
||||
|
||||
@Parameter(names = "--name")
|
||||
private String name;
|
||||
|
||||
@Parameter(names = "-level")
|
||||
private int level;
|
||||
|
||||
@Parameter(description = "rest")
|
||||
private List<String> rest = new ArrayList<>();
|
||||
|
||||
public List<String> getRest() {
|
||||
return rest;
|
||||
}
|
||||
|
||||
public void setRest(List<String> rest) {
|
||||
this.rest = rest;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
public void setLevel(int level) {
|
||||
this.level = level;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +1,61 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.jcommander;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 15/12/15.
|
||||
*/
|
||||
public class JCommanderParameterResolverTest {
|
||||
|
||||
private static final Method COMMAND_METHOD = ReflectionUtils.findMethod(MyLordCommands.class, "genesis", FieldCollins.class);
|
||||
|
||||
private JCommanderParameterResolver resolver = new JCommanderParameterResolver();
|
||||
|
||||
@Test
|
||||
public void testSupportsJCommanderPojos() throws Exception {
|
||||
assertThat(resolver.supports(new MethodParameter(COMMAND_METHOD, 0))).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotSupportsNonJCommanderPojos() throws Exception {
|
||||
Method method = ReflectionUtils.findMethod(MyLordCommands.class, "apocalypse", String.class);
|
||||
|
||||
assertThat(resolver.supports(new MethodParameter(method, 0))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPojoValuesAreCorrectlySet() {
|
||||
MethodParameter methodParameter = new MethodParameter(COMMAND_METHOD, 0);
|
||||
|
||||
FieldCollins resolved = (FieldCollins) resolver.resolve(methodParameter, asList("--name foo -level 2 something-else yet-something-else".split(" ")));
|
||||
|
||||
assertThat(resolved.getName()).isEqualTo("foo");
|
||||
assertThat(resolved.getLevel()).isEqualTo(2);
|
||||
assertThat(resolved.getRest()).containsOnlyOnce("something-else", "yet-something-else");
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.jcommander;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.shell2.Utils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 15/12/15.
|
||||
*/
|
||||
public class JCommanderParameterResolverTest {
|
||||
|
||||
private static final Method COMMAND_METHOD = ReflectionUtils.findMethod(MyLordCommands.class, "genesis", FieldCollins.class);
|
||||
|
||||
private JCommanderParameterResolver resolver = new JCommanderParameterResolver();
|
||||
|
||||
@Test
|
||||
public void testSupportsJCommanderPojos() throws Exception {
|
||||
assertThat(resolver.supports(Utils.createMethodParameter(COMMAND_METHOD, 0))).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotSupportsNonJCommanderPojos() throws Exception {
|
||||
Method method = ReflectionUtils.findMethod(MyLordCommands.class, "apocalypse", String.class);
|
||||
|
||||
assertThat(resolver.supports(Utils.createMethodParameter(method, 0))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPojoValuesAreCorrectlySet() {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(COMMAND_METHOD, 0);
|
||||
|
||||
FieldCollins resolved = (FieldCollins) resolver.resolve(methodParameter, asList("--name foo -level 2 something-else yet-something-else".split(" ")));
|
||||
|
||||
assertThat(resolved.getName()).isEqualTo("foo");
|
||||
assertThat(resolved.getLevel()).isEqualTo(2);
|
||||
assertThat(resolved.getRest()).containsOnlyOnce("something-else", "yet-something-else");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.jcommander;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 15/12/15.
|
||||
*/
|
||||
public class MyLordCommands {
|
||||
|
||||
public void genesis(FieldCollins fieldCollins) {
|
||||
|
||||
}
|
||||
|
||||
public void apocalypse(String param) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.jcommander;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 15/12/15.
|
||||
*/
|
||||
public class MyLordCommands {
|
||||
|
||||
public void genesis(FieldCollins fieldCollins) {
|
||||
|
||||
}
|
||||
|
||||
public void apocalypse(String param) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.legacy;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 09/12/15.
|
||||
*/
|
||||
public enum ArtifactType {
|
||||
|
||||
source, processor, sink, task
|
||||
}
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.legacy;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 09/12/15.
|
||||
*/
|
||||
public enum ArtifactType {
|
||||
|
||||
source, processor, sink, task
|
||||
}
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.legacy;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.shell.core.CommandMarker;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
import org.springframework.shell.core.annotation.CliOption;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 09/12/15.
|
||||
*/
|
||||
public class LegacyCommands implements CommandMarker {
|
||||
|
||||
public static final Method REGISTER_METHOD = ReflectionUtils.findMethod(LegacyCommands.class, "register", String.class, ArtifactType.class, String.class, boolean.class);
|
||||
public static final Method SUM_METHOD = ReflectionUtils.findMethod(LegacyCommands.class, "sum", int.class, int.class);
|
||||
|
||||
@CliCommand(value = "register module", help = "Register a new module")
|
||||
public String register(
|
||||
@CliOption(mandatory = true,
|
||||
key = {"", "name"},
|
||||
help = "the name for the registered module")
|
||||
String name,
|
||||
@CliOption(mandatory = true,
|
||||
key = {"type"},
|
||||
help = "the type for the registered module")
|
||||
ArtifactType type,
|
||||
@CliOption(mandatory = true,
|
||||
key = {"coordinates", "coords"},
|
||||
help = "coordinates to the module archive")
|
||||
String coordinates,
|
||||
@CliOption(key = "force",
|
||||
help = "force update if module already exists (only if not in use)",
|
||||
specifiedDefaultValue = "true",
|
||||
unspecifiedDefaultValue = "false")
|
||||
boolean force) {
|
||||
return String.format(("Successfully registered module '%s:%s'"), type, name);
|
||||
}
|
||||
|
||||
@CliCommand(value = "sum", help = "adds two numbers")
|
||||
public int sum(@CliOption(key = "v1", unspecifiedDefaultValue = "38") int a, @CliOption(key = "v2", specifiedDefaultValue = "42") int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.legacy;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.shell.core.CommandMarker;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
import org.springframework.shell.core.annotation.CliOption;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 09/12/15.
|
||||
*/
|
||||
public class LegacyCommands implements CommandMarker {
|
||||
|
||||
public static final Method REGISTER_METHOD = ReflectionUtils.findMethod(LegacyCommands.class, "register", String.class, ArtifactType.class, String.class, boolean.class);
|
||||
public static final Method SUM_METHOD = ReflectionUtils.findMethod(LegacyCommands.class, "sum", int.class, int.class);
|
||||
|
||||
@CliCommand(value = "register module", help = "Register a new module")
|
||||
public String register(
|
||||
@CliOption(mandatory = true,
|
||||
key = {"", "name"},
|
||||
help = "the name for the registered module")
|
||||
String name,
|
||||
@CliOption(mandatory = true,
|
||||
key = {"type"},
|
||||
help = "the type for the registered module")
|
||||
ArtifactType type,
|
||||
@CliOption(mandatory = true,
|
||||
key = {"coordinates", "coords"},
|
||||
help = "coordinates to the module archive")
|
||||
String coordinates,
|
||||
@CliOption(key = "force",
|
||||
help = "force update if module already exists (only if not in use)",
|
||||
specifiedDefaultValue = "true",
|
||||
unspecifiedDefaultValue = "false")
|
||||
boolean force) {
|
||||
return String.format(("Successfully registered module '%s:%s'"), type, name);
|
||||
}
|
||||
|
||||
@CliCommand(value = "sum", help = "adds two numbers")
|
||||
public int sum(@CliOption(key = "v1", unspecifiedDefaultValue = "38") int a, @CliOption(key = "v2", specifiedDefaultValue = "42") int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,76 +1,76 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.legacy;
|
||||
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.shell2.MethodTarget;
|
||||
import org.springframework.shell2.MethodTargetResolver;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.data.MapEntry.entry;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 09/12/15.
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = LegacyMethodTargetResolverTest.Config.class)
|
||||
public class LegacyMethodTargetResolverTest {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Autowired
|
||||
private LegacyCommands legacyCommands;
|
||||
|
||||
@Autowired
|
||||
private MethodTargetResolver resolver;
|
||||
|
||||
@Test
|
||||
public void findsMethodsAnnotatedWithCliCommand() throws Exception {
|
||||
Map<String, MethodTarget> targets = resolver.resolve(applicationContext);
|
||||
|
||||
assertThat(targets).contains(entry(
|
||||
"register module",
|
||||
new MethodTarget(LegacyCommands.REGISTER_METHOD, legacyCommands, "Register a new module" )
|
||||
));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public LegacyCommands legacyCommands() {
|
||||
return new LegacyCommands();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MethodTargetResolver methodTargetResolver() {
|
||||
return new LegacyMethodTargetResolver();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.legacy;
|
||||
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.shell2.MethodTarget;
|
||||
import org.springframework.shell2.MethodTargetResolver;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.data.MapEntry.entry;
|
||||
|
||||
/**
|
||||
* Created by ericbottard on 09/12/15.
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = LegacyMethodTargetResolverTest.Config.class)
|
||||
public class LegacyMethodTargetResolverTest {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Autowired
|
||||
private LegacyCommands legacyCommands;
|
||||
|
||||
@Autowired
|
||||
private MethodTargetResolver resolver;
|
||||
|
||||
@Test
|
||||
public void findsMethodsAnnotatedWithCliCommand() throws Exception {
|
||||
Map<String, MethodTarget> targets = resolver.resolve(applicationContext);
|
||||
|
||||
assertThat(targets).contains(entry(
|
||||
"register module",
|
||||
new MethodTarget(LegacyCommands.REGISTER_METHOD, legacyCommands, "Register a new module" )
|
||||
));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public LegacyCommands legacyCommands() {
|
||||
return new LegacyCommands();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MethodTargetResolver methodTargetResolver() {
|
||||
return new LegacyMethodTargetResolver();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,191 +1,184 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.legacy;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.shell.converters.BooleanConverter;
|
||||
import org.springframework.shell.converters.EnumConverter;
|
||||
import org.springframework.shell.converters.StringConverter;
|
||||
import org.springframework.shell.core.Converter;
|
||||
import org.springframework.shell2.ParameterResolver;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.shell2.legacy.LegacyCommands.REGISTER_METHOD;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = LegacyParameterResolverTest.Config.class)
|
||||
public class LegacyParameterResolverTest {
|
||||
|
||||
private static final int NAME_OR_ANONYMOUS = 0;
|
||||
private static final int TYPE = 1;
|
||||
private static final int COORDINATES = 2;
|
||||
private static final int FORCE = 3;
|
||||
|
||||
@Autowired
|
||||
ParameterResolver parameterResolver;
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void supportsParameterAnnotatedWithCliOption() throws Exception {
|
||||
MethodParameter methodParameter = buildMethodParameter(REGISTER_METHOD, NAME_OR_ANONYMOUS);
|
||||
|
||||
boolean result = parameterResolver.supports(methodParameter);
|
||||
|
||||
assertThat(result).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesParameterAnnotatedWithCliOption() throws Exception {
|
||||
MethodParameter methodParameter = buildMethodParameter(REGISTER_METHOD, NAME_OR_ANONYMOUS);
|
||||
|
||||
Object result = resolve(methodParameter, "--foo bar --name baz --qix bux");
|
||||
|
||||
assertThat(result).isEqualTo("baz");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesAnonymousParameterAnnotatedWithCliOption() throws Exception {
|
||||
MethodParameter methodParameter = buildMethodParameter(REGISTER_METHOD, NAME_OR_ANONYMOUS);
|
||||
|
||||
Object result = resolve(methodParameter, "--foo bar baz --qix bux");
|
||||
assertThat(result).isEqualTo("baz");
|
||||
|
||||
// As first param
|
||||
result = resolve(methodParameter, "baz --foo bar --qix bux");
|
||||
assertThat(result).isEqualTo("baz");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usesLegacyConverters() throws Exception {
|
||||
MethodParameter methodParameter = buildMethodParameter(REGISTER_METHOD, TYPE);
|
||||
|
||||
Object result = resolve(methodParameter, "--foo bar --name baz --qix bux --type processor");
|
||||
|
||||
assertThat(result).isSameAs(ArtifactType.processor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnspecifiedDefaultValue() throws Exception {
|
||||
MethodParameter methodParameter = buildMethodParameter(REGISTER_METHOD, FORCE);
|
||||
|
||||
Object result = resolve(methodParameter, "--foo bar --name baz --qix bux");
|
||||
|
||||
assertThat(result).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpecifiedDefaultValue() throws Exception {
|
||||
MethodParameter methodParameter = buildMethodParameter(REGISTER_METHOD, FORCE);
|
||||
|
||||
assertThat(resolve(methodParameter, "--force --foo bar --name baz --qix bux")).isEqualTo(true);
|
||||
assertThat(resolve(methodParameter, "--foo bar --name baz --qix bux --force")).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParameterNotFound() throws Exception {
|
||||
MethodParameter methodParameter = buildMethodParameter(REGISTER_METHOD, COORDINATES);
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Could not find parameter values for [--coordinates, --coords] in [--force, --foo, bar, --name, baz, --qix, bux]");
|
||||
resolve(methodParameter, "--force --foo bar --name baz --qix bux");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParameterFoundWithSameNameTooManyTimes() throws Exception {
|
||||
MethodParameter methodParameter = buildMethodParameter(REGISTER_METHOD, COORDINATES);
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Option --coordinates has already been set");
|
||||
resolve(methodParameter, "--force --coordinates bar --coordinates baz --qix bux");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoConverterFound() throws Exception {
|
||||
MethodParameter methodParameter = buildMethodParameter(LegacyCommands.SUM_METHOD, 0);
|
||||
|
||||
thrown.expect(IllegalStateException.class);
|
||||
thrown.expectMessage("No converter found for --v1 from '1' to type int");
|
||||
resolve(methodParameter, "--v1 1 --v2 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoConverterFoundForUnspecifiedValue() throws Exception {
|
||||
MethodParameter methodParameter = buildMethodParameter(LegacyCommands.SUM_METHOD, 0);
|
||||
|
||||
thrown.expect(IllegalStateException.class);
|
||||
thrown.expectMessage("No converter found for --v1 from '38' to type int");
|
||||
resolve(methodParameter, "--v2 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoConverterFoundForSpecifiedValue() throws Exception {
|
||||
MethodParameter methodParameter = buildMethodParameter(LegacyCommands.SUM_METHOD, 1);
|
||||
|
||||
thrown.expect(IllegalStateException.class);
|
||||
thrown.expectMessage("No converter found for --v2 from '42' to type int");
|
||||
resolve(methodParameter, "--v1 1 --v2");
|
||||
}
|
||||
|
||||
private MethodParameter buildMethodParameter(Method method, int index) {
|
||||
MethodParameter methodParameter = new MethodParameter(method, index);
|
||||
methodParameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
|
||||
return methodParameter;
|
||||
}
|
||||
|
||||
private Object resolve(MethodParameter methodParameter, String command) {
|
||||
return parameterResolver.resolve(methodParameter, asList(command.split(" ")));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public Converter<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();
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.legacy;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.shell2.legacy.LegacyCommands.REGISTER_METHOD;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.shell.converters.BooleanConverter;
|
||||
import org.springframework.shell.converters.EnumConverter;
|
||||
import org.springframework.shell.converters.StringConverter;
|
||||
import org.springframework.shell.core.Converter;
|
||||
import org.springframework.shell2.ParameterResolver;
|
||||
import org.springframework.shell2.Utils;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = LegacyParameterResolverTest.Config.class)
|
||||
public class LegacyParameterResolverTest {
|
||||
|
||||
private static final int NAME_OR_ANONYMOUS = 0;
|
||||
private static final int TYPE = 1;
|
||||
private static final int COORDINATES = 2;
|
||||
private static final int FORCE = 3;
|
||||
|
||||
@Autowired
|
||||
ParameterResolver parameterResolver;
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void supportsParameterAnnotatedWithCliOption() throws Exception {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, NAME_OR_ANONYMOUS);
|
||||
|
||||
boolean result = parameterResolver.supports(methodParameter);
|
||||
|
||||
assertThat(result).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesParameterAnnotatedWithCliOption() throws Exception {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, NAME_OR_ANONYMOUS);
|
||||
|
||||
Object result = resolve(methodParameter, "--foo bar --name baz --qix bux");
|
||||
|
||||
assertThat(result).isEqualTo("baz");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesAnonymousParameterAnnotatedWithCliOption() throws Exception {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, NAME_OR_ANONYMOUS);
|
||||
|
||||
Object result = resolve(methodParameter, "--foo bar baz --qix bux");
|
||||
assertThat(result).isEqualTo("baz");
|
||||
|
||||
// As first param
|
||||
result = resolve(methodParameter, "baz --foo bar --qix bux");
|
||||
assertThat(result).isEqualTo("baz");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usesLegacyConverters() throws Exception {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, TYPE);
|
||||
|
||||
Object result = resolve(methodParameter, "--foo bar --name baz --qix bux --type processor");
|
||||
|
||||
assertThat(result).isSameAs(ArtifactType.processor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnspecifiedDefaultValue() throws Exception {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, FORCE);
|
||||
|
||||
Object result = resolve(methodParameter, "--foo bar --name baz --qix bux");
|
||||
|
||||
assertThat(result).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpecifiedDefaultValue() throws Exception {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, FORCE);
|
||||
|
||||
assertThat(resolve(methodParameter, "--force --foo bar --name baz --qix bux")).isEqualTo(true);
|
||||
assertThat(resolve(methodParameter, "--foo bar --name baz --qix bux --force")).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParameterNotFound() throws Exception {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, COORDINATES);
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Could not find parameter values for [--coordinates, --coords] in [--force, --foo, bar, --name, baz, --qix, bux]");
|
||||
resolve(methodParameter, "--force --foo bar --name baz --qix bux");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParameterFoundWithSameNameTooManyTimes() throws Exception {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, COORDINATES);
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Option --coordinates has already been set");
|
||||
resolve(methodParameter, "--force --coordinates bar --coordinates baz --qix bux");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoConverterFound() throws Exception {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.SUM_METHOD, 0);
|
||||
|
||||
thrown.expect(IllegalStateException.class);
|
||||
thrown.expectMessage("No converter found for --v1 from '1' to type int");
|
||||
resolve(methodParameter, "--v1 1 --v2 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoConverterFoundForUnspecifiedValue() throws Exception {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.SUM_METHOD, 0);
|
||||
|
||||
thrown.expect(IllegalStateException.class);
|
||||
thrown.expectMessage("No converter found for --v1 from '38' to type int");
|
||||
resolve(methodParameter, "--v2 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoConverterFoundForSpecifiedValue() throws Exception {
|
||||
MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.SUM_METHOD, 1);
|
||||
|
||||
thrown.expect(IllegalStateException.class);
|
||||
thrown.expectMessage("No converter found for --v2 from '42' to type int");
|
||||
resolve(methodParameter, "--v1 1 --v2");
|
||||
}
|
||||
|
||||
private Object resolve(MethodParameter methodParameter, String command) {
|
||||
return parameterResolver.resolve(methodParameter, asList(command.split(" ")));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public Converter<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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.standard;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.shell2.CompletionContext;
|
||||
import org.springframework.shell2.CompletionProposal;
|
||||
|
||||
/**
|
||||
* An example commands class.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Florent Biville
|
||||
*/
|
||||
public class Remote {
|
||||
|
||||
/**
|
||||
* A command method that showcases<ul>
|
||||
* <li>default handling for booleans (force)</li>
|
||||
* <li>default parameter name discovery (name)</li>
|
||||
* <li>default value supplying (foo and bar)</li>
|
||||
* </ul>
|
||||
*/
|
||||
@ShellMethod(help = "switch channels")
|
||||
public void zap(boolean force,
|
||||
String name,
|
||||
@ShellOption(defaultValue="defoolt") String foo,
|
||||
@ShellOption(value = {"--bar", "--baz"}, defaultValue = "last") String bar) {
|
||||
|
||||
}
|
||||
|
||||
@ShellMethod(help = "bye bye")
|
||||
public void shutdown(@ShellOption Delay delay) {
|
||||
|
||||
}
|
||||
|
||||
@ShellMethod(help = "add 3 numbers together")
|
||||
public void add(@ShellOption(arity = 3, valueProvider = NumberValueProvider.class) List<Integer> numbers) {
|
||||
|
||||
}
|
||||
|
||||
public enum Delay {
|
||||
small, medium, big;
|
||||
}
|
||||
|
||||
|
||||
public static class NumberValueProvider extends ValueProviderSupport {
|
||||
|
||||
@Override
|
||||
public List<CompletionProposal> complete(MethodParameter parameter, CompletionContext completionContext, String[] hints) {
|
||||
String prefix = completionContext.currentWord() != null ? completionContext.currentWord() : "";
|
||||
return Arrays.asList("12", "42", "7").stream()
|
||||
.filter(n -> n.startsWith(prefix))
|
||||
.map(n -> new CompletionProposal(n))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell2.standard;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.util.ReflectionUtils.findMethod;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jline.reader.ParsedLine;
|
||||
import org.jline.reader.impl.DefaultParser;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.shell2.CompletionContext;
|
||||
import org.springframework.shell2.CompletionProposal;
|
||||
import org.springframework.shell2.ParameterMissingResolutionException;
|
||||
import org.springframework.shell2.UnfinishedParameterResolutionException;
|
||||
import org.springframework.shell2.Utils;
|
||||
|
||||
/**
|
||||
* Unit tests for DefaultParameterResolver.
|
||||
* @author Eric Bottard
|
||||
* @author Florent Biville
|
||||
*/
|
||||
public class StandardParameterResolverTest {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private StandardParameterResolver resolver = new StandardParameterResolver(new DefaultConversionService());
|
||||
|
||||
// Tests for resolution
|
||||
|
||||
@Test
|
||||
public void testParses() throws Exception {
|
||||
Method method = findMethod(org.springframework.shell2.standard.Remote.class, "zap", boolean.class, String.class, String.class, String.class);
|
||||
|
||||
assertThat(resolver.resolve(
|
||||
Utils.createMethodParameter(method, 0),
|
||||
asList("--force --name --foo y".split(" "))
|
||||
)).isEqualTo(true);
|
||||
assertThat(resolver.resolve(
|
||||
Utils.createMethodParameter(method, 1),
|
||||
asList("--force --name --foo y".split(" "))
|
||||
)).isEqualTo("--foo");
|
||||
assertThat(resolver.resolve(
|
||||
Utils.createMethodParameter(method, 2),
|
||||
asList("--force --name --foo y".split(" "))
|
||||
)).isEqualTo("y");
|
||||
assertThat(resolver.resolve(
|
||||
Utils.createMethodParameter(method, 3),
|
||||
asList("--force --name --foo y".split(" "))
|
||||
)).isEqualTo("last");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParameterSpecifiedTwiceViaDifferentAliases() throws Exception {
|
||||
Method method = findMethod(org.springframework.shell2.standard.Remote.class, "zap", boolean.class, String.class, String.class, String.class);
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Named parameter has been specified multiple times via '--bar, --baz'");
|
||||
|
||||
resolver.resolve(
|
||||
Utils.createMethodParameter(method, 0),
|
||||
asList("--force --name --foo y --bar x --baz z".split(" "))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParameterSpecifiedTwiceViaSameKey() throws Exception {
|
||||
Method method = findMethod(org.springframework.shell2.standard.Remote.class, "zap", boolean.class, String.class, String.class, String.class);
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Parameter for '--baz' has already been specified");
|
||||
|
||||
resolver.resolve(
|
||||
Utils.createMethodParameter(method, 0),
|
||||
asList("--force --name --foo y --baz x --baz z".split(" "))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTooMuchInput() throws Exception {
|
||||
Method method = findMethod(org.springframework.shell2.standard.Remote.class, "zap", boolean.class, String.class, String.class, String.class);
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("the following could not be mapped to parameters: 'leftover'");
|
||||
|
||||
resolver.resolve(
|
||||
Utils.createMethodParameter(method, 0),
|
||||
asList("--foo hello --name bar --force --bar well leftover".split(" "))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncompleteCommandResolution() throws Exception {
|
||||
Method method = findMethod(org.springframework.shell2.standard.Remote.class, "shutdown", org.springframework.shell2.standard.Remote.Delay.class);
|
||||
|
||||
thrown.expect(UnfinishedParameterResolutionException.class);
|
||||
thrown.expectMessage("Error trying to resolve '--delay delay' using [--delay]");
|
||||
|
||||
resolver.resolve(
|
||||
Utils.createMethodParameter(method, 0),
|
||||
asList("--delay".split(" "))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncompleteCommandResolutionBigArity() throws Exception {
|
||||
Method method = findMethod(org.springframework.shell2.standard.Remote.class, "add", List.class);
|
||||
|
||||
thrown.expect(UnfinishedParameterResolutionException.class);
|
||||
thrown.expectMessage("Error trying to resolve '--numbers list list list' using [--numbers 1 2]");
|
||||
|
||||
resolver.resolve(
|
||||
Utils.createMethodParameter(method, 0),
|
||||
asList("--numbers 1 2".split(" "))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnresolvableArg() throws Exception {
|
||||
Method method = findMethod(org.springframework.shell2.standard.Remote.class, "zap", boolean.class, String.class, String.class, String.class);
|
||||
|
||||
thrown.expect(ParameterMissingResolutionException.class);
|
||||
thrown.expectMessage("Parameter '--name string' should be specified");
|
||||
|
||||
resolver.resolve(
|
||||
Utils.createMethodParameter(method, 1),
|
||||
asList("--foo hello --force --bar well".split(" "))
|
||||
);
|
||||
}
|
||||
|
||||
// Tests for completion
|
||||
|
||||
@Test
|
||||
public void testParameterKeyNotYetSetAppearsInProposals() {
|
||||
Method method = findMethod(org.springframework.shell2.standard.Remote.class, "zap", boolean.class, String.class, String.class, String.class);
|
||||
List<String> completions = resolver.complete(
|
||||
Utils.createMethodParameter(method, 1),
|
||||
contextFor("")
|
||||
).stream().map(CompletionProposal::value).collect(Collectors.toList());
|
||||
assertThat(completions).contains("--name");
|
||||
completions = resolver.complete(
|
||||
Utils.createMethodParameter(method, 1),
|
||||
contextFor("--force ")
|
||||
).stream().map(CompletionProposal::value).collect(Collectors.toList());
|
||||
assertThat(completions).contains("--name");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParameterKeyNotFullySpecified() {
|
||||
Method method = findMethod(org.springframework.shell2.standard.Remote.class, "zap", boolean.class, String.class, String.class, String.class);
|
||||
List<String> completions = resolver.complete(
|
||||
Utils.createMethodParameter(method, 1),
|
||||
contextFor("--na")
|
||||
).stream().map(CompletionProposal::value).collect(Collectors.toList());
|
||||
assertThat(completions).contains("--name");
|
||||
completions = resolver.complete(
|
||||
Utils.createMethodParameter(method, 1),
|
||||
contextFor("--force --na")
|
||||
).stream().map(CompletionProposal::value).collect(Collectors.toList());
|
||||
assertThat(completions).contains("--name");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoMoreAvailableParameters() {
|
||||
Method method = findMethod(org.springframework.shell2.standard.Remote.class, "zap", boolean.class, String.class, String.class, String.class);
|
||||
List<String> completions = resolver.complete(
|
||||
Utils.createMethodParameter(method, 2), // trying to complete --foo
|
||||
contextFor("--name ") // but input is currently focused on --name
|
||||
).stream().map(CompletionProposal::value).collect(Collectors.toList());
|
||||
System.out.println(completions);
|
||||
// assertThat(completions).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotTheRightTimeToCompleteThatParameter() {
|
||||
Method method = findMethod(org.springframework.shell2.standard.Remote.class, "shutdown", org.springframework.shell2.standard.Remote.Delay.class);
|
||||
List<String> completions = resolver.complete(
|
||||
Utils.createMethodParameter(method, 0),
|
||||
contextFor("--delay 323")
|
||||
).stream().map(CompletionProposal::value).collect(Collectors.toList());
|
||||
assertThat(completions).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValueCompletionWithNonDefaultArity() {
|
||||
|
||||
resolver.setValueProviders(Arrays.asList(new Remote.NumberValueProvider()));
|
||||
|
||||
Method method = findMethod(org.springframework.shell2.standard.Remote.class, "add", List.class);
|
||||
List<String> completions = resolver.complete(
|
||||
Utils.createMethodParameter(method, 0),
|
||||
contextFor("--numbers ")
|
||||
).stream().map(CompletionProposal::value).collect(Collectors.toList());
|
||||
assertThat(completions).contains("12");
|
||||
|
||||
completions = resolver.complete(
|
||||
Utils.createMethodParameter(method, 0),
|
||||
contextFor("--numbers 42 ")
|
||||
).stream().map(CompletionProposal::value).collect(Collectors.toList());
|
||||
assertThat(completions).contains("12");
|
||||
|
||||
completions = resolver.complete(
|
||||
Utils.createMethodParameter(method, 0),
|
||||
contextFor("--numbers 42 34 ")
|
||||
).stream().map(CompletionProposal::value).collect(Collectors.toList());
|
||||
assertThat(completions).contains("12");
|
||||
|
||||
completions = resolver.complete(
|
||||
Utils.createMethodParameter(method, 0),
|
||||
contextFor("--numbers 42 34 66 ")
|
||||
).stream().map(CompletionProposal::value).collect(Collectors.toList());
|
||||
assertThat(completions).isEmpty();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private CompletionContext contextFor(String input) {
|
||||
DefaultParser defaultParser = new DefaultParser();
|
||||
ParsedLine parsed = defaultParser.parse(input, input.length());
|
||||
List<String> words = parsed.words().stream().filter(w -> w.length() > 0).collect(Collectors.toList());
|
||||
|
||||
return new CompletionContext(words, parsed.wordIndex(), parsed.wordCursor());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user