diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/FileInputProvider.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/FileInputProvider.java new file mode 100644 index 00000000..9575b0a7 --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/FileInputProvider.java @@ -0,0 +1,78 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.shell.jline; + +import org.jline.reader.ParsedLine; +import org.jline.reader.Parser; +import org.springframework.shell.Input; +import org.springframework.shell.InputProvider; + +import java.io.BufferedReader; +import java.io.Closeable; +import java.io.IOException; +import java.io.Reader; + +/** + * An {@link InputProvider} that reads input from file-like sources. + *
+ *
Still uses a {@link org.jline.reader.Parser} to interpret word boundaries. Supports backslashes at end + * of line to signal line continuation.
+ * + * @author Eric Bottard + */ +public class FileInputProvider implements InputProvider, Closeable { + + private static final String BACKSLASH_AT_EOL_REGEX = "(.*)\\\\\\s*$"; + private final BufferedReader reader; + + private final Parser parser; + + public FileInputProvider(Reader reader, Parser parser) { + this.reader = new BufferedReader(reader); + this.parser = parser; + } + + @Override + public Input readInput() { + StringBuilder sb = new StringBuilder(); + boolean continued = false; + String line; + try { + do { + line = reader.readLine(); + if (line == null) { + break; + } + continued = line.matches(BACKSLASH_AT_EOL_REGEX); + sb.append(line.replaceFirst(BACKSLASH_AT_EOL_REGEX, "$1 ")); + } while (continued); + } catch (IOException e) { + throw new RuntimeException(e); + } + if (line == null) { + return null; + } else { + ParsedLine parsedLine = parser.parse(sb.toString(), sb.toString().length()); + return new ParsedLineInput(parsedLine); + } + } + + @Override + public void close() throws IOException { + reader.close(); + } +} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/JLineShellAutoConfiguration.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/JLineShellAutoConfiguration.java index b7cc5110..3d496d96 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/jline/JLineShellAutoConfiguration.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/JLineShellAutoConfiguration.java @@ -22,13 +22,7 @@ import java.util.stream.Collectors; import javax.annotation.PostConstruct; -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.UserInterruptException; +import org.jline.reader.*; import org.jline.terminal.Terminal; import org.jline.terminal.TerminalBuilder; import org.jline.utils.AttributedString; @@ -101,11 +95,15 @@ class JLineShellAutoConfiguration { } @Bean - public LineReader lineReader() { + public Parser parser() { ExtendedDefaultParser parser = new ExtendedDefaultParser(); parser.setEofOnUnclosedQuote(true); parser.setEofOnEscapedNewLine(true); + return parser; + } + @Bean + public LineReader lineReader() { LineReaderBuilder lineReaderBuilder = LineReaderBuilder.builder() .terminal(terminal()) .appName("Spring Shell") @@ -130,7 +128,7 @@ class JLineShellAutoConfiguration { } } }) - .parser(parser); + .parser(parser()); return lineReaderBuilder.build(); } @@ -139,7 +137,7 @@ class JLineShellAutoConfiguration { * Sanitize the buffer input given the customizations applied to the JLine parser (e.g. support for * line continuations, etc.) */ - static private List+ *
To override the script command, simply register your own bean implementing that interface + * and the standard implementation will back off.
+ *+ *
To disable the {@literal script} command entirely, set the {@literal spring.shell.command.script.enabled=false} + * property in the environment.
+ * + * @author Eric Bottard + */ + public interface Command { + } + + @ShellMethod(value = "Read and execute commands from a file") + public void script(File file) throws IOException { + Reader reader = new FileReader(file); + try (FileInputProvider inputProvider = new FileInputProvider(reader, parser)) { + Input input = inputProvider.readInput(); + while (input != null) { + shell.evaluate(input); + input = inputProvider.readInput(); + } + } + } + +} diff --git a/spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/StandardCommandsAutoConfiguration.java b/spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/StandardCommandsAutoConfiguration.java index a40d9785..c6c8218a 100644 --- a/spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/StandardCommandsAutoConfiguration.java +++ b/spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/StandardCommandsAutoConfiguration.java @@ -18,6 +18,7 @@ package org.springframework.shell.standard.commands; import java.util.List; +import org.jline.reader.Parser; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; @@ -61,4 +62,10 @@ public class StandardCommandsAutoConfiguration { return new Stacktrace(); } + @Bean + @ConditionalOnMissingBean(Script.Command.class) + @ConditionalOnProperty(prefix = "spring.shell.command.script", value = "enabled", havingValue = "true", matchIfMissing = true) + public Script script(Shell shell, Parser parser) { + return new Script(shell, parser); + } } diff --git a/spring-shell-standard/src/main/java/org/springframework/shell/standard/CommandValueProvider.java b/spring-shell-standard/src/main/java/org/springframework/shell/standard/CommandValueProvider.java index c4d5ba05..6338b146 100644 --- a/spring-shell-standard/src/main/java/org/springframework/shell/standard/CommandValueProvider.java +++ b/spring-shell-standard/src/main/java/org/springframework/shell/standard/CommandValueProvider.java @@ -32,7 +32,6 @@ import org.springframework.stereotype.Component; * * @author Eric Bottard */ -@Component public class CommandValueProvider extends ValueProviderSupport { private final CommandRegistry commandRegistry; diff --git a/spring-shell-standard/src/main/java/org/springframework/shell/standard/EnumValueProvider.java b/spring-shell-standard/src/main/java/org/springframework/shell/standard/EnumValueProvider.java index cfaa6703..baa1645f 100644 --- a/spring-shell-standard/src/main/java/org/springframework/shell/standard/EnumValueProvider.java +++ b/spring-shell-standard/src/main/java/org/springframework/shell/standard/EnumValueProvider.java @@ -28,7 +28,6 @@ import org.springframework.stereotype.Component; * A {@link ValueProvider} that knows how to complete values for {@link Enum} typed parameters. * @author Eric Bottard */ -@Component public class EnumValueProvider implements ValueProvider { @Override diff --git a/spring-shell-standard/src/main/java/org/springframework/shell/standard/FileValueProvider.java b/spring-shell-standard/src/main/java/org/springframework/shell/standard/FileValueProvider.java new file mode 100644 index 00000000..7485e4a7 --- /dev/null +++ b/spring-shell-standard/src/main/java/org/springframework/shell/standard/FileValueProvider.java @@ -0,0 +1,65 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.shell.standard; + +import org.springframework.core.MethodParameter; +import org.springframework.shell.CompletionContext; +import org.springframework.shell.CompletionProposal; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.FileVisitOption; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; + +import static java.nio.file.FileVisitOption.FOLLOW_LINKS; + +/** + * A {@link ValueProvider} that can populate names of local {@link File}s, either absolute or relative to the + * current working directory. + * + * @author Eric Bottard + */ +public class FileValueProvider implements ValueProvider { + @Override + public boolean supports(MethodParameter parameter, CompletionContext completionContext) { + return parameter.getParameterType().equals(File.class); + } + + @Override + public List