diff --git a/spring-shell-core/src/main/java/org/springframework/shell/Shell.java b/spring-shell-core/src/main/java/org/springframework/shell/Shell.java
index c35bc617..bbeb3e2a 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/Shell.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/Shell.java
@@ -118,7 +118,8 @@ public class Shell implements CommandRegistry {
*
*/
public void run(InputProvider inputProvider) throws IOException {
- while (true) {
+ Object result = null;
+ while (!(result instanceof ExitRequest)) {
Input input;
try {
input = inputProvider.readInput();
@@ -130,8 +131,8 @@ public class Shell implements CommandRegistry {
if (input == null) {
break;
}
- Object result = evaluate(input);
- if (result != NO_INPUT) {
+ result = evaluate(input);
+ if (result != NO_INPUT && !(result instanceof ExitRequest)) {
resultHandler.handleResult(result);
}
}
@@ -155,7 +156,6 @@ public class Shell implements CommandRegistry {
String command = findLongestCommand(line);
List words = input.words();
- Object result;
if (command != null) {
MethodTarget methodTarget = methodTargets.get(command);
Availability availability = methodTarget.getAvailability();
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/SpringShellAutoConfiguration.java b/spring-shell-core/src/main/java/org/springframework/shell/SpringShellAutoConfiguration.java
index d80b5804..1f9bbf5f 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/SpringShellAutoConfiguration.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/SpringShellAutoConfiguration.java
@@ -18,14 +18,15 @@ package org.springframework.shell;
import java.util.Collection;
+import javax.validation.Validation;
+import javax.validation.Validator;
+
import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.boot.ApplicationArguments;
-import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
@@ -33,14 +34,11 @@ import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.shell.result.ResultHandlerConfig;
-import javax.validation.Validation;
-import javax.validation.Validator;
-
/**
* Creates supporting beans for running the Shell
*/
@Configuration
-@ComponentScan(basePackageClasses = ResultHandlerConfig.class)
+@Import(ResultHandlerConfig.class)
public class SpringShellAutoConfiguration {
@Bean
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/DefaultShellApplicationRunner.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/InteractiveShellApplicationRunner.java
similarity index 57%
rename from spring-shell-core/src/main/java/org/springframework/shell/jline/DefaultShellApplicationRunner.java
rename to spring-shell-core/src/main/java/org/springframework/shell/jline/InteractiveShellApplicationRunner.java
index 404b7c37..c853b855 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/jline/DefaultShellApplicationRunner.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/InteractiveShellApplicationRunner.java
@@ -16,11 +16,7 @@
package org.springframework.shell.jline;
-import java.io.File;
-import java.io.FileReader;
-import java.io.Reader;
-import java.util.List;
-import java.util.stream.Collectors;
+import java.util.Collections;
import org.jline.reader.LineReader;
import org.jline.reader.Parser;
@@ -30,28 +26,38 @@ import org.jline.utils.AttributedString;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.Environment;
+import org.springframework.core.env.MapPropertySource;
import org.springframework.shell.ExitRequest;
import org.springframework.shell.Input;
import org.springframework.shell.InputProvider;
import org.springframework.shell.Shell;
/**
- * Default Boot runner that bootstraps the shell application.
+ * Default Boot runner that bootstraps the shell application in interactive mode.
*
*
- * Default implementation has default priority and looks for application arguments that start with an {@literal @},
- * assuming they are paths to script files. Executes them and quits if they are present, starts the shell interactively
- * otherwise.
+ * Runs the REPL of the shell unless the {@literal spring.shell.interactive} property has been set to {@literal false}.
*
*
* @author Eric Bottard
*/
-//tag::documentation[]
-@Order(DefaultShellApplicationRunner.PRECEDENCE)
-public class DefaultShellApplicationRunner implements ApplicationRunner {
-//end::documentation[]
+@Order(InteractiveShellApplicationRunner.PRECEDENCE)
+public class InteractiveShellApplicationRunner implements ApplicationRunner {
+
+ /**
+ * The precedence at which this runner is set. Highger precedence runners may effectively disable this one by setting
+ * the {@link #SPRING_SHELL_INTERACTIVE_ENABLED} property to {@literal false}.
+ */
public static final int PRECEDENCE = 0;
+ public static final String SPRING_SHELL_INTERACTIVE = "spring.shell.interactive";
+ public static final String ENABLED = "enabled";
+
+ /** The name of the property that controls whether this runner effectively does something. */
+ public static final String SPRING_SHELL_INTERACTIVE_ENABLED = SPRING_SHELL_INTERACTIVE + "." + ENABLED;
+
private final LineReader lineReader;
private final PromptProvider promptProvider;
@@ -60,33 +66,37 @@ public class DefaultShellApplicationRunner implements ApplicationRunner {
private final Shell shell;
- public DefaultShellApplicationRunner(LineReader lineReader, PromptProvider promptProvider, Parser parser, Shell shell) {
+ private final Environment environment;
+
+ public InteractiveShellApplicationRunner(LineReader lineReader, PromptProvider promptProvider, Parser parser, Shell shell, Environment environment) {
this.lineReader = lineReader;
this.promptProvider = promptProvider;
this.parser = parser;
this.shell = shell;
+ this.environment = environment;
}
- //tag::documentation[]
@Override
public void run(ApplicationArguments args) throws Exception {
- List scriptsToRun = args.getNonOptionArgs().stream()
- .filter(s -> s.startsWith("@"))
- .map(s -> new File(s.substring(1)))
- .collect(Collectors.toList());
-
- if (scriptsToRun.isEmpty()) {
+ boolean interactive = isEnabled();
+ if (interactive) {
InputProvider inputProvider = new JLineInputProvider(lineReader, promptProvider);
shell.run(inputProvider);
- } else {
- for (File file : scriptsToRun) {
- try (Reader reader = new FileReader(file); FileInputProvider inputProvider = new FileInputProvider(reader, parser)) {
- shell.run(inputProvider);
- }
- }
}
}
- //end::documentation[]
+
+ public boolean isEnabled() {
+ return environment.getProperty(SPRING_SHELL_INTERACTIVE_ENABLED,boolean.class, true);
+ }
+
+ /**
+ * Helper method to dynamically disable this runner.
+ */
+ public static void disable(ConfigurableEnvironment environment) {
+ environment.getPropertySources().addFirst(new MapPropertySource("interactive.override",
+ Collections.singletonMap(SPRING_SHELL_INTERACTIVE_ENABLED, "false")));
+ }
+
public static class JLineInputProvider implements InputProvider {
private final LineReader lineReader;
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 2617ac55..3afc5ac5 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
@@ -16,6 +16,9 @@
package org.springframework.shell.jline;
+import static org.springframework.shell.jline.InteractiveShellApplicationRunner.SPRING_SHELL_INTERACTIVE;
+import static org.springframework.shell.jline.ScriptShellApplicationRunner.SPRING_SHELL_SCRIPT;
+
import java.io.*;
import java.nio.file.Paths;
import java.util.List;
@@ -33,25 +36,20 @@ import org.jline.utils.AttributedStyle;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.context.ApplicationEventPublisherAware;
-import org.springframework.context.ApplicationListener;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.EventListener;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.Environment;
import org.springframework.shell.CompletingParsedLine;
import org.springframework.shell.CompletionContext;
import org.springframework.shell.CompletionProposal;
-import org.springframework.shell.ExitRequest;
-import org.springframework.shell.Input;
-import org.springframework.shell.InputProvider;
-import org.springframework.shell.ResultHandler;
import org.springframework.shell.Shell;
/**
@@ -83,9 +81,15 @@ public class JLineShellAutoConfiguration {
}
@Bean
- @ConditionalOnMissingBean(ApplicationRunner.class)
- public ApplicationRunner applicationRunner(Parser parser) {
- return new DefaultShellApplicationRunner(lineReader(), promptProvider, parser, shell);
+ @ConditionalOnProperty(prefix = SPRING_SHELL_INTERACTIVE, value = InteractiveShellApplicationRunner.ENABLED, havingValue = "true", matchIfMissing = true)
+ public ApplicationRunner interactiveApplicationRunner(Parser parser, Environment environment) {
+ return new InteractiveShellApplicationRunner(lineReader(), promptProvider, parser, shell, environment);
+ }
+
+ @Bean
+ @ConditionalOnProperty(prefix = SPRING_SHELL_SCRIPT, value = ScriptShellApplicationRunner.ENABLED, havingValue = "true", matchIfMissing = true)
+ public ApplicationRunner scriptApplicationRunner(Parser parser, ConfigurableEnvironment environment) {
+ return new ScriptShellApplicationRunner(parser, shell, environment);
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/ScriptShellApplicationRunner.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/ScriptShellApplicationRunner.java
new file mode 100644
index 00000000..68f0929f
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/ScriptShellApplicationRunner.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2018 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 java.io.File;
+import java.io.FileReader;
+import java.io.Reader;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.jline.reader.Parser;
+
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.shell.Shell;
+
+/**
+ * Spring Boot ApplicationRunner that looks for process arguments that start with
+ * {@literal @}, which are then interpreted as references to script files to run and exit.
+ *
+ *
+ * Has higher precedence than {@link InteractiveShellApplicationRunner} so that it
+ * prevents it to run if scripts are found.
+ *
+ *
+ * @author Eric Bottard
+ */
+//tag::documentation[]
+@Order(InteractiveShellApplicationRunner.PRECEDENCE - 100) // Runs before InteractiveShellApplicationRunner
+public class ScriptShellApplicationRunner implements ApplicationRunner {
+//end::documentation[]
+
+ public static final String SPRING_SHELL_SCRIPT = "spring.shell.script";
+ public static final String ENABLED = "spring.shell.script";
+
+ /**
+ * The name of the environment property that allows to disable the behavior of this
+ * runner.
+ */
+ public static final String SPRING_SHELL_SCRIPT_ENABLED = SPRING_SHELL_SCRIPT + "." + ENABLED;
+
+ private final Parser parser;
+
+ private final Shell shell;
+
+ private final ConfigurableEnvironment environment;
+
+ public ScriptShellApplicationRunner(Parser parser, Shell shell, ConfigurableEnvironment environment) {
+ this.parser = parser;
+ this.shell = shell;
+ this.environment = environment;
+ }
+
+ //tag::documentation[]
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ List scriptsToRun = args.getNonOptionArgs().stream()
+ .filter(s -> s.startsWith("@"))
+ .map(s -> new File(s.substring(1)))
+ .collect(Collectors.toList());
+
+ boolean batchEnabled = environment.getProperty(SPRING_SHELL_SCRIPT_ENABLED, boolean.class, true);
+
+ if (!scriptsToRun.isEmpty() && batchEnabled) {
+ InteractiveShellApplicationRunner.disable(environment);
+ for (File file : scriptsToRun) {
+ try (Reader reader = new FileReader(file);
+ FileInputProvider inputProvider = new FileInputProvider(reader, parser)) {
+ shell.run(inputProvider);
+ }
+ }
+ }
+ }
+ //end::documentation[]
+
+}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/AttributedCharSequenceResultHandler.java b/spring-shell-core/src/main/java/org/springframework/shell/result/AttributedCharSequenceResultHandler.java
index f7df75ff..f1b3ed3a 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/result/AttributedCharSequenceResultHandler.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/result/AttributedCharSequenceResultHandler.java
@@ -26,7 +26,6 @@ import org.springframework.stereotype.Component;
*
* @author Eric Bottard
*/
-@Component
public class AttributedCharSequenceResultHandler extends TerminalAwareResultHandler {
@Override
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/DefaultResultHandler.java b/spring-shell-core/src/main/java/org/springframework/shell/result/DefaultResultHandler.java
index 4a0c2a9c..a75ee58d 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/result/DefaultResultHandler.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/result/DefaultResultHandler.java
@@ -25,11 +25,10 @@ import org.springframework.stereotype.Component;
*
* @author Eric Bottard
*/
-@Component
-public class DefaultResultHandler implements ResultHandler