Add support for multiple non-interactive commmands

Also:
* Rename ThemeResolver bean to avoid clash w/ Spring Boot registered ThemeResolver
* Move shell runner precedence to public static field to allow extension/access
This commit is contained in:
onobc
2022-02-18 13:34:36 -06:00
committed by Janne Valkealahti
parent 7746e571b6
commit 5f447aa831
4 changed files with 85 additions and 34 deletions

View File

@@ -33,12 +33,12 @@ public class ThemingAutoConfiguration {
public ThemeRegistry themeRegistry(ObjectProvider<Theme> themes) {
ThemeRegistry registry = new ThemeRegistry();
registry.register(Theme.of("default", ThemeSettings.themeSettings()));
themes.orderedStream().forEachOrdered(theme -> registry.register(theme));
themes.orderedStream().forEachOrdered(registry::register);
return registry;
}
@Bean
public ThemeResolver themeResolver(ThemeRegistry themeRegistry, SpringShellProperties properties) {
public ThemeResolver shellThemeResolver(ThemeRegistry themeRegistry, SpringShellProperties properties) {
return new ThemeResolver(themeRegistry, properties.getTheme().getName());
}

View File

@@ -27,7 +27,7 @@ public interface ShellRunner {
/**
* Checks if a particular shell runner can execute.
*
* @param args the application argumets
* @param args the application arguments
* @return true if shell runner can execute
*/
boolean canRun(ApplicationArguments args);

View File

@@ -15,9 +15,14 @@
*/
package org.springframework.shell.jline;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.jline.reader.ParsedLine;
import org.jline.reader.Parser;
import org.jline.reader.impl.DefaultParser;
import org.springframework.boot.ApplicationArguments;
import org.springframework.core.annotation.Order;
@@ -25,9 +30,9 @@ import org.springframework.shell.Input;
import org.springframework.shell.InputProvider;
import org.springframework.shell.Shell;
import org.springframework.shell.ShellRunner;
import org.springframework.shell.Utils;
import org.springframework.shell.context.InteractionMode;
import org.springframework.shell.context.ShellContext;
import org.springframework.util.StringUtils;
/**
* A {@link ShellRunner} that executes commands without entering interactive shell mode.
@@ -38,66 +43,106 @@ import org.springframework.util.StringUtils;
* @author Janne Valkealahti
* @author Chris Bono
*/
@Order(InteractiveShellRunner.PRECEDENCE - 50)
@Order(NonInteractiveShellRunner.PRECEDENCE)
public class NonInteractiveShellRunner implements ShellRunner {
/**
* The precedence at which this runner is ordered by the DefaultApplicationRunner - which also controls
* the order it is consulted on the ability to handle the current shell.
*/
public static final int PRECEDENCE = InteractiveShellRunner.PRECEDENCE - 50;
private final Shell shell;
private final ShellContext shellContext;
private Function<ApplicationArguments, List<String>> argsToShellCommand = (args) -> Arrays.asList(args.getSourceArgs());
private Parser lineParser;
private Function<ApplicationArguments, List<String>> commandsFromInputArgs;
public NonInteractiveShellRunner(Shell shell, ShellContext shellContext) {
this.shell = shell;
this.shellContext = shellContext;
this.lineParser = new DefaultParser();
this.commandsFromInputArgs = (args) ->
Collections.singletonList(String.join(" ", args.getSourceArgs()));
}
public void setArgsToShellCommand(Function<ApplicationArguments, List<String>> argsToShellCommand) {
this.argsToShellCommand = argsToShellCommand;
/**
* Sets the function that creates the command() to run from the input application arguments.
*
* @param commandsFromInputArgs function that takes input application arguments and creates zero or more commands
* where each command is a string that specifies the command and options
* (eg. 'history --file myHistory.txt')
*/
public void setCommandsFromInputArgs(Function<ApplicationArguments, List<String>> commandsFromInputArgs) {
this.commandsFromInputArgs = commandsFromInputArgs;
}
/**
* Sets the line parser used to parse commands.
*
* @param lineParser the line parser used to parse commands
*/
public void setLineParser(Parser lineParser) {
this.lineParser = lineParser;
}
@Override
public boolean canRun(ApplicationArguments args) {
return !argsToShellCommand.apply(args).isEmpty();
return !commandsFromInputArgs.apply(args).isEmpty();
}
@Override
public void run(ApplicationArguments args) throws Exception {
shellContext.setInteractionMode(InteractionMode.NONINTERACTIVE);
List<String> commands = this.argsToShellCommand.apply(args);
InputProvider inputProvider = new StringInputProvider(commands);
List<String> commands = this.commandsFromInputArgs.apply(args);
List<ParsedLine> parsedLines = commands.stream()
.map(rawCommandLine -> lineParser.parse(rawCommandLine, rawCommandLine.length() + 1))
.collect(Collectors.toList());
MultiParsedLineInputProvider inputProvider = new MultiParsedLineInputProvider(parsedLines);
shell.run(inputProvider);
}
private class StringInputProvider implements InputProvider {
/**
* An {@link InputProvider} that returns an input for each entry in a list of {@link ParsedLine parsed lines}.
*/
static class MultiParsedLineInputProvider implements InputProvider {
private final List<String> commands;
private final List<ParsedLineInput> parsedLineInputs;
private int inputIdx;
private boolean done;
StringInputProvider(List<String> commands) {
this.commands = commands;
MultiParsedLineInputProvider(List<ParsedLine> parsedLines) {
this.parsedLineInputs = parsedLines.stream()
.map(ParsedLineInput::new)
.collect(Collectors.toList());
}
@Override
public Input readInput() {
if (!done) {
done = true;
return new Input() {
@Override
public List<String> words() {
return commands;
}
@Override
public String rawText() {
return StringUtils.collectionToDelimitedString(commands, " ");
}
};
}
else {
if (inputIdx == parsedLineInputs.size()) {
return null;
}
return parsedLineInputs.get(inputIdx++);
}
private static class ParsedLineInput implements Input {
private final ParsedLine parsedLine;
ParsedLineInput(ParsedLine parsedLine) {
this.parsedLine = parsedLine;
}
@Override
public String rawText() {
return parsedLine.line();
}
@Override
public List<String> words() {
return Utils.sanitizeInput(parsedLine.words());
}
}
}
}

View File

@@ -40,10 +40,16 @@ import org.springframework.util.ObjectUtils;
* @author Eric Bottard
*/
//tag::documentation[]
@Order(InteractiveShellRunner.PRECEDENCE - 100)
@Order(ScriptShellRunner.PRECEDENCE)
public class ScriptShellRunner implements ShellRunner {
//end::documentation[]
/**
* The precedence at which this runner is ordered by the DefaultApplicationRunner - which also controls
* the order it is consulted on the ability to handle the current shell.
*/
public static final int PRECEDENCE = InteractiveShellRunner.PRECEDENCE - 100;
private final Parser parser;
private final Shell shell;