diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ThemingAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ThemingAutoConfiguration.java index b371552b..eeeba652 100644 --- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ThemingAutoConfiguration.java +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ThemingAutoConfiguration.java @@ -33,12 +33,12 @@ public class ThemingAutoConfiguration { public ThemeRegistry themeRegistry(ObjectProvider 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()); } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/ShellRunner.java b/spring-shell-core/src/main/java/org/springframework/shell/ShellRunner.java index f7707d39..7ca6c523 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/ShellRunner.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/ShellRunner.java @@ -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); diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellRunner.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellRunner.java index 7832a85f..0738dba8 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellRunner.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellRunner.java @@ -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> argsToShellCommand = (args) -> Arrays.asList(args.getSourceArgs()); + private Parser lineParser; + + private Function> 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> 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> 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 commands = this.argsToShellCommand.apply(args); - InputProvider inputProvider = new StringInputProvider(commands); + List commands = this.commandsFromInputArgs.apply(args); + List 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 commands; + private final List parsedLineInputs; + private int inputIdx; - private boolean done; - - StringInputProvider(List commands) { - this.commands = commands; + MultiParsedLineInputProvider(List 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 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 words() { + return Utils.sanitizeInput(parsedLine.words()); + } } } } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/ScriptShellRunner.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/ScriptShellRunner.java index ebdbdc8b..54e1da68 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/jline/ScriptShellRunner.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/ScriptShellRunner.java @@ -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;