Support for non-interactive shell commands
- Add support for running shell commands as a non-interactive mode. - This works by adding new ShellApplicationRunner interface which is an extension to ApplicationRunner forcing to have exactly one main ApplicationRunner and then DefaultApplicationRunner dispatches to new interface ShellRunner which allows to pick between script, interactive and non-interactive, etc. - It is sort of a breaking change but works much better not having a need to have previous hooks between application runners to disable things at runtime. - All this makes it closer for a user to have a choice between using shell commands as is without entering interactive mode. - Also add SpringShellProperties for better config props support for boot users. - Fixes #342
This commit is contained in:
@@ -15,46 +15,23 @@
|
||||
*/
|
||||
package org.springframework.shell.boot;
|
||||
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.Parser;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.shell.Shell;
|
||||
import org.springframework.shell.jline.InteractiveShellApplicationRunner;
|
||||
import org.springframework.shell.jline.PromptProvider;
|
||||
import org.springframework.shell.jline.ScriptShellApplicationRunner;
|
||||
|
||||
import static org.springframework.shell.jline.InteractiveShellApplicationRunner.SPRING_SHELL_INTERACTIVE;
|
||||
import static org.springframework.shell.jline.ScriptShellApplicationRunner.SPRING_SHELL_SCRIPT;
|
||||
import org.springframework.shell.DefaultApplicationRunner;
|
||||
import org.springframework.shell.ShellApplicationRunner;
|
||||
import org.springframework.shell.ShellRunner;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(SpringShellProperties.class)
|
||||
public class ApplicationRunnerAutoConfiguration {
|
||||
|
||||
private Shell shell;
|
||||
|
||||
private PromptProvider promptProvider;
|
||||
|
||||
private LineReader lineReader;
|
||||
|
||||
public ApplicationRunnerAutoConfiguration(Shell shell, PromptProvider promptProvider, LineReader lineReader) {
|
||||
this.shell = shell;
|
||||
this.promptProvider = promptProvider;
|
||||
this.lineReader = lineReader;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = SPRING_SHELL_INTERACTIVE, value = InteractiveShellApplicationRunner.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
public InteractiveShellApplicationRunner interactiveApplicationRunner(Environment environment) {
|
||||
return new InteractiveShellApplicationRunner(lineReader, promptProvider, shell, environment);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = SPRING_SHELL_SCRIPT, value = ScriptShellApplicationRunner.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
public ScriptShellApplicationRunner scriptApplicationRunner(Parser parser, ConfigurableEnvironment environment) {
|
||||
return new ScriptShellApplicationRunner(parser, shell, environment);
|
||||
@ConditionalOnMissingBean(ShellApplicationRunner.class)
|
||||
public DefaultApplicationRunner defaultApplicationRunner(List<ShellRunner> shellRunners) {
|
||||
return new DefaultApplicationRunner(shellRunners);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.boot;
|
||||
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.Parser;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.shell.Shell;
|
||||
import org.springframework.shell.jline.InteractiveShellApplicationRunner;
|
||||
import org.springframework.shell.jline.NonInteractiveShellApplicationRunner;
|
||||
import org.springframework.shell.jline.PromptProvider;
|
||||
import org.springframework.shell.jline.ScriptShellApplicationRunner;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class ShellRunnerAutoConfiguration {
|
||||
|
||||
private Shell shell;
|
||||
private PromptProvider promptProvider;
|
||||
private LineReader lineReader;
|
||||
private Parser parser;
|
||||
|
||||
public ShellRunnerAutoConfiguration(Shell shell, PromptProvider promptProvider, LineReader lineReader, Parser parser) {
|
||||
this.shell = shell;
|
||||
this.promptProvider = promptProvider;
|
||||
this.lineReader = lineReader;
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "spring.shell.interactive", value = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public InteractiveShellApplicationRunner interactiveApplicationRunner() {
|
||||
return new InteractiveShellApplicationRunner(lineReader, promptProvider, shell);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "spring.shell.noninteractive", value = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public NonInteractiveShellApplicationRunner nonInteractiveApplicationRunner() {
|
||||
return new NonInteractiveShellApplicationRunner(shell);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "spring.shell.script", value = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public ScriptShellApplicationRunner scriptApplicationRunner() {
|
||||
return new ScriptShellApplicationRunner(parser, shell);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.boot;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Configuration properties for shell.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.shell")
|
||||
public class SpringShellProperties {
|
||||
|
||||
private Script script = new Script();
|
||||
private Interactive interactive = new Interactive();
|
||||
private Noninteractive noninteractive = new Noninteractive();
|
||||
private Command command = new Command();
|
||||
|
||||
public void setScript(Script script) {
|
||||
this.script = script;
|
||||
}
|
||||
|
||||
public Script getScript() {
|
||||
return script;
|
||||
}
|
||||
|
||||
public void setInteractive(Interactive interactive) {
|
||||
this.interactive = interactive;
|
||||
}
|
||||
|
||||
public Interactive getInteractive() {
|
||||
return interactive;
|
||||
}
|
||||
|
||||
public Noninteractive getNoninteractive() {
|
||||
return noninteractive;
|
||||
}
|
||||
|
||||
public void setNoninteractive(Noninteractive noninteractive) {
|
||||
this.noninteractive = noninteractive;
|
||||
}
|
||||
|
||||
public Command getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public void setCommand(Command command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public static class Script {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Interactive {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Noninteractive {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public static class HelpCommand {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClearCommand {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public static class QuitCommand {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public static class StacktraceCommand {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ScriptCommand {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public static class HistoryCommand {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Command {
|
||||
|
||||
private HelpCommand help = new HelpCommand();
|
||||
private ClearCommand clear = new ClearCommand();
|
||||
private QuitCommand quit = new QuitCommand();
|
||||
private StacktraceCommand stacktrace = new StacktraceCommand();
|
||||
private ScriptCommand script = new ScriptCommand();
|
||||
private HistoryCommand history = new HistoryCommand();
|
||||
|
||||
public void setHelp(HelpCommand help) {
|
||||
this.help = help;
|
||||
}
|
||||
|
||||
public HelpCommand getHelp() {
|
||||
return help;
|
||||
}
|
||||
|
||||
public ClearCommand getClear() {
|
||||
return clear;
|
||||
}
|
||||
|
||||
public void setClear(ClearCommand clear) {
|
||||
this.clear = clear;
|
||||
}
|
||||
|
||||
public QuitCommand getQuit() {
|
||||
return quit;
|
||||
}
|
||||
|
||||
public void setQuit(QuitCommand quit) {
|
||||
this.quit = quit;
|
||||
}
|
||||
|
||||
public StacktraceCommand getStacktrace() {
|
||||
return stacktrace;
|
||||
}
|
||||
|
||||
public void setStacktrace(StacktraceCommand stacktrace) {
|
||||
this.stacktrace = stacktrace;
|
||||
}
|
||||
|
||||
public ScriptCommand getScript() {
|
||||
return script;
|
||||
}
|
||||
|
||||
public void setScript(ScriptCommand script) {
|
||||
this.script = script;
|
||||
}
|
||||
|
||||
public HistoryCommand getHistory() {
|
||||
return history;
|
||||
}
|
||||
|
||||
public void setHistory(HistoryCommand history) {
|
||||
this.history = history;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.shell.boot.SpringShellAutoConfiguration,\
|
||||
org.springframework.shell.boot.ShellRunnerAutoConfiguration,\
|
||||
org.springframework.shell.boot.ApplicationRunnerAutoConfiguration,\
|
||||
org.springframework.shell.boot.CommandRegistryAutoConfiguration,\
|
||||
org.springframework.shell.boot.LineReaderAutoConfiguration,\
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.boot;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.core.env.SystemEnvironmentPropertySource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class SpringShellPropertiesTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
|
||||
|
||||
@Test
|
||||
public void defaultNoPropertiesSet() {
|
||||
this.contextRunner
|
||||
.withUserConfiguration(Config1.class)
|
||||
.run((context) -> {
|
||||
SpringShellProperties properties = context.getBean(SpringShellProperties.class);
|
||||
assertThat(properties.getScript().isEnabled()).isTrue();
|
||||
assertThat(properties.getInteractive().isEnabled()).isTrue();
|
||||
assertThat(properties.getNoninteractive().isEnabled()).isTrue();
|
||||
assertThat(properties.getCommand().getClear().isEnabled()).isTrue();
|
||||
assertThat(properties.getCommand().getHelp().isEnabled()).isTrue();
|
||||
assertThat(properties.getCommand().getHistory().isEnabled()).isTrue();
|
||||
assertThat(properties.getCommand().getQuit().isEnabled()).isTrue();
|
||||
assertThat(properties.getCommand().getScript().isEnabled()).isTrue();
|
||||
assertThat(properties.getCommand().getStacktrace().isEnabled()).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setProperties() {
|
||||
this.contextRunner
|
||||
.withInitializer(context -> {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("spring.shell.script.enabled", "false");
|
||||
map.put("spring.shell.interactive.enabled", "false");
|
||||
map.put("spring.shell.noninteractive.enabled", "false");
|
||||
map.put("spring.shell.command.clear.enabled", "false");
|
||||
map.put("spring.shell.command.help.enabled", "false");
|
||||
map.put("spring.shell.command.history.enabled", "false");
|
||||
map.put("spring.shell.command.quit.enabled", "false");
|
||||
map.put("spring.shell.command.script.enabled", "false");
|
||||
map.put("spring.shell.command.stacktrace.enabled", "false");
|
||||
context.getEnvironment().getPropertySources().addLast(new SystemEnvironmentPropertySource(
|
||||
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map));
|
||||
})
|
||||
.withUserConfiguration(Config1.class)
|
||||
.run((context) -> {
|
||||
SpringShellProperties properties = context.getBean(SpringShellProperties.class);
|
||||
assertThat(properties.getScript().isEnabled()).isFalse();
|
||||
assertThat(properties.getInteractive().isEnabled()).isFalse();
|
||||
assertThat(properties.getNoninteractive().isEnabled()).isFalse();
|
||||
assertThat(properties.getCommand().getClear().isEnabled()).isFalse();
|
||||
assertThat(properties.getCommand().getHelp().isEnabled()).isFalse();
|
||||
assertThat(properties.getCommand().getHistory().isEnabled()).isFalse();
|
||||
assertThat(properties.getCommand().getQuit().isEnabled()).isFalse();
|
||||
assertThat(properties.getCommand().getScript().isEnabled()).isFalse();
|
||||
assertThat(properties.getCommand().getStacktrace().isEnabled()).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@EnableConfigurationProperties({ SpringShellProperties.class })
|
||||
private static class Config1 {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
|
||||
/**
|
||||
* Default {@link ApplicationRunner} which dispatches to first ordered
|
||||
* {@link ShellRunner} able to handle shell.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*/
|
||||
public class DefaultApplicationRunner implements ShellApplicationRunner {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(DefaultApplicationRunner.class);
|
||||
private final List<ShellRunner> shellRunners;
|
||||
|
||||
public DefaultApplicationRunner(List<ShellRunner> shellRunners) {
|
||||
// TODO: follow up with spring-native
|
||||
// Looks like with fatjar it comes on a correct order from
|
||||
// a context(not really sure if that's how spring context works) but
|
||||
// not with native, so call AnnotationAwareOrderComparator manually.
|
||||
Collections.sort(shellRunners, new AnnotationAwareOrderComparator());
|
||||
this.shellRunners = shellRunners;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) throws Exception {
|
||||
log.debug("Checking shell runners {}", shellRunners);
|
||||
Optional<ShellRunner> optional = shellRunners.stream()
|
||||
.filter(sh -> sh.canRun(args))
|
||||
.findFirst();
|
||||
ShellRunner shellRunner = optional.orElse(null);
|
||||
log.debug("Using shell runner {}", shellRunner);
|
||||
if (shellRunner != null) {
|
||||
shellRunner.run(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
|
||||
/**
|
||||
* Marker interface for a main spring shell {@link ApplicationRunner}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*/
|
||||
public interface ShellApplicationRunner extends ApplicationRunner {
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
|
||||
/**
|
||||
* Interface for shell runners.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*/
|
||||
public interface ShellRunner {
|
||||
|
||||
/**
|
||||
* Checks if a particular shell runner can execute.
|
||||
*
|
||||
* @param args the application argumets
|
||||
* @return true if shell runner can execute
|
||||
*/
|
||||
boolean canRun(ApplicationArguments args);
|
||||
|
||||
/**
|
||||
* Execute application.
|
||||
*
|
||||
* @param args the application argumets
|
||||
* @throws Exception in errors
|
||||
*/
|
||||
void run(ApplicationArguments args) throws Exception;
|
||||
}
|
||||
@@ -16,34 +16,29 @@
|
||||
|
||||
package org.springframework.shell.jline;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.UserInterruptException;
|
||||
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;
|
||||
import org.springframework.shell.ShellRunner;
|
||||
|
||||
/**
|
||||
* Default Boot runner that bootstraps the shell application in interactive mode.
|
||||
* Default Boot runner that bootstraps the shell application in interactive
|
||||
* mode.
|
||||
*
|
||||
* <p>
|
||||
* Runs the REPL of the shell unless the {@literal spring.shell.interactive} property has been set to {@literal false}.
|
||||
* </p>
|
||||
* Runs the REPL of the shell unless the {@literal spring.shell.interactive}
|
||||
* property has been set to {@literal false}.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@Order(InteractiveShellApplicationRunner.PRECEDENCE)
|
||||
public class InteractiveShellApplicationRunner implements ApplicationRunner {
|
||||
public class InteractiveShellApplicationRunner implements ShellRunner {
|
||||
|
||||
/**
|
||||
* The precedence at which this runner is set. Highger precedence runners may effectively disable this one by setting
|
||||
@@ -51,47 +46,27 @@ public class InteractiveShellApplicationRunner implements ApplicationRunner {
|
||||
*/
|
||||
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;
|
||||
|
||||
private final Shell shell;
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
public InteractiveShellApplicationRunner(LineReader lineReader, PromptProvider promptProvider, Shell shell,
|
||||
Environment environment) {
|
||||
public InteractiveShellApplicationRunner(LineReader lineReader, PromptProvider promptProvider, Shell shell) {
|
||||
this.lineReader = lineReader;
|
||||
this.promptProvider = promptProvider;
|
||||
this.shell = shell;
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) throws Exception {
|
||||
boolean interactive = isEnabled();
|
||||
if (interactive) {
|
||||
InputProvider inputProvider = new JLineInputProvider(lineReader, promptProvider);
|
||||
shell.run(inputProvider);
|
||||
}
|
||||
InputProvider inputProvider = new JLineInputProvider(lineReader, promptProvider);
|
||||
shell.run(inputProvider);
|
||||
}
|
||||
|
||||
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")));
|
||||
@Override
|
||||
public boolean canRun(ApplicationArguments args) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static class JLineInputProvider implements InputProvider {
|
||||
@@ -121,5 +96,4 @@ public class InteractiveShellApplicationRunner implements ApplicationRunner {
|
||||
return new ParsedLineInput(lineReader.getParsedLine());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.shell.Input;
|
||||
import org.springframework.shell.InputProvider;
|
||||
import org.springframework.shell.Shell;
|
||||
import org.springframework.shell.ShellRunner;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Non interactive {@link ShellRunner} which is meant to execute shell commands
|
||||
* without entering interactive shell.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*/
|
||||
@Order(InteractiveShellApplicationRunner.PRECEDENCE - 50)
|
||||
public class NonInteractiveShellApplicationRunner implements ShellRunner {
|
||||
|
||||
private final Shell shell;
|
||||
|
||||
public NonInteractiveShellApplicationRunner(Shell shell) {
|
||||
this.shell = shell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRun(ApplicationArguments args) {
|
||||
List<String> argsToShellCommand = Arrays.asList(args.getSourceArgs());
|
||||
return !ObjectUtils.isEmpty(argsToShellCommand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) throws Exception {
|
||||
List<String> argsToShellCommand = Arrays.asList(args.getSourceArgs());
|
||||
InputProvider inputProvider = new StringInputProvider(argsToShellCommand);
|
||||
shell.run(inputProvider);
|
||||
}
|
||||
|
||||
private class StringInputProvider implements InputProvider {
|
||||
|
||||
private final List<String> commands;
|
||||
|
||||
private boolean done;
|
||||
|
||||
StringInputProvider(List<String> commands) {
|
||||
this.commands = commands;
|
||||
}
|
||||
|
||||
@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 {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.
|
||||
@@ -25,25 +25,23 @@ 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;
|
||||
import org.springframework.shell.ShellRunner;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
*
|
||||
* Has higher precedence than {@link InteractiveShellApplicationRunner} so that it
|
||||
* prevents it to run if scripts are found.
|
||||
* </p>
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
//tag::documentation[]
|
||||
@Order(InteractiveShellApplicationRunner.PRECEDENCE - 100) // Runs before InteractiveShellApplicationRunner
|
||||
public class ScriptShellApplicationRunner implements ApplicationRunner {
|
||||
public class ScriptShellApplicationRunner implements ShellRunner {
|
||||
//end::documentation[]
|
||||
|
||||
public static final String SPRING_SHELL_SCRIPT = "spring.shell.script";
|
||||
@@ -59,12 +57,18 @@ public class ScriptShellApplicationRunner implements ApplicationRunner {
|
||||
|
||||
private final Shell shell;
|
||||
|
||||
private final ConfigurableEnvironment environment;
|
||||
|
||||
public ScriptShellApplicationRunner(Parser parser, Shell shell, ConfigurableEnvironment environment) {
|
||||
public ScriptShellApplicationRunner(Parser parser, Shell shell) {
|
||||
this.parser = parser;
|
||||
this.shell = shell;
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRun(ApplicationArguments args) {
|
||||
List<File> scriptsToRun = args.getNonOptionArgs().stream()
|
||||
.filter(s -> s.startsWith("@"))
|
||||
.map(s -> new File(s.substring(1)))
|
||||
.collect(Collectors.toList());
|
||||
return !ObjectUtils.isEmpty(scriptsToRun);
|
||||
}
|
||||
|
||||
//tag::documentation[]
|
||||
@@ -76,15 +80,10 @@ public class ScriptShellApplicationRunner implements ApplicationRunner {
|
||||
.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);
|
||||
}
|
||||
for (File file : scriptsToRun) {
|
||||
try (Reader reader = new FileReader(file);
|
||||
FileInputProvider inputProvider = new FileInputProvider(reader, parser)) {
|
||||
shell.run(inputProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public class ThrowableResultHandler extends TerminalAwareResultHandler<Throwable
|
||||
String toPrint = StringUtils.hasLength(result.getMessage()) ? result.getMessage() : result.toString();
|
||||
terminal.writer().println(new AttributedString(toPrint,
|
||||
AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)).toAnsi());
|
||||
if (interactiveRunner.getIfAvailable().isEnabled() && commandRegistry.listCommands().containsKey(DETAILS_COMMAND_NAME)) {
|
||||
if (interactiveRunner.getIfAvailable() != null && commandRegistry.listCommands().containsKey(DETAILS_COMMAND_NAME)) {
|
||||
terminal.writer().println(
|
||||
new AttributedStringBuilder()
|
||||
.append("Details of the error have been omitted. You can use the ", AttributedStyle.DEFAULT.foreground(AttributedStyle.RED))
|
||||
@@ -70,7 +70,7 @@ public class ThrowableResultHandler extends TerminalAwareResultHandler<Throwable
|
||||
);
|
||||
}
|
||||
terminal.writer().flush();
|
||||
if (!interactiveRunner.getIfAvailable().isEnabled()) {
|
||||
if (interactiveRunner.getIfAvailable() == null) {
|
||||
if (result instanceof RuntimeException) {
|
||||
throw (RuntimeException) result;
|
||||
}
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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.samples;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.ExitCodeExceptionMapper;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.shell.ExitRequest;
|
||||
import org.springframework.shell.Input;
|
||||
import org.springframework.shell.InputProvider;
|
||||
import org.springframework.shell.Shell;
|
||||
import org.springframework.shell.jline.InteractiveShellApplicationRunner;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class ExampleApplicationRunnerConfiguration {
|
||||
|
||||
@Autowired
|
||||
private Shell shell;
|
||||
|
||||
@Bean
|
||||
public CommandLineRunner exampleCommandLineRunner(ConfigurableEnvironment environment) {
|
||||
return new ExampleCommandLineRunner(shell, environment);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExitCodeExceptionMapper exitCodeExceptionMapper() {
|
||||
return exception -> {
|
||||
Throwable e = exception;
|
||||
while (e != null && !(e instanceof ExitRequest)) {
|
||||
e = e.getCause();
|
||||
}
|
||||
return e == null ? 1 : ((ExitRequest) e).status();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example CommandLineRunner that shows how overall shell behavior can be customized. In
|
||||
* this particular example, any program (process) arguments are assumed to be shell
|
||||
* commands that need to be executed (and the shell then quits).
|
||||
*/
|
||||
@Order(InteractiveShellApplicationRunner.PRECEDENCE - 2)
|
||||
class ExampleCommandLineRunner implements CommandLineRunner {
|
||||
|
||||
private Shell shell;
|
||||
|
||||
private final ConfigurableEnvironment environment;
|
||||
|
||||
public ExampleCommandLineRunner(Shell shell, ConfigurableEnvironment environment) {
|
||||
this.shell = shell;
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
List<String> commandsToRun = Arrays.stream(args)
|
||||
.filter(w -> !w.startsWith("@"))
|
||||
.collect(Collectors.toList());
|
||||
if (!commandsToRun.isEmpty()) {
|
||||
InteractiveShellApplicationRunner.disable(environment);
|
||||
shell.run(new StringInputProvider(commandsToRun));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StringInputProvider implements InputProvider {
|
||||
|
||||
private final List<String> words;
|
||||
|
||||
private boolean done;
|
||||
|
||||
public StringInputProvider(List<String> words) {
|
||||
this.words = words;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Input readInput() {
|
||||
if (!done) {
|
||||
done = true;
|
||||
return new Input() {
|
||||
@Override
|
||||
public List<String> words() {
|
||||
return words;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String rawText() {
|
||||
return StringUtils.collectionToDelimitedString(words, " ");
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.shell.CommandRegistry;
|
||||
import org.springframework.shell.MethodTarget;
|
||||
import org.springframework.shell.Shell;
|
||||
import org.springframework.shell.jline.InteractiveShellApplicationRunner;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -24,7 +23,7 @@ import static org.springframework.util.ReflectionUtils.findMethod;
|
||||
*
|
||||
* @author Sualeh Fatehi
|
||||
*/
|
||||
@SpringBootTest(properties = { InteractiveShellApplicationRunner.SPRING_SHELL_INTERACTIVE_ENABLED + "=" + false })
|
||||
@SpringBootTest(properties = { "spring.shell.interactive.enabled=false" })
|
||||
@ContextConfiguration(classes = TestCalculatorStateConfig.class)
|
||||
public class CalculatorCommandsIntegrationTest extends BaseCalculatorTest {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user