Add non-interactive shell runner customizer
- Add non-interactive shell runner customizer - Update javadoc for DefaultApplicationRunner - Rename DefaultApplicationRunner - Formatting
This commit is contained in:
@@ -21,7 +21,7 @@ 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.shell.DefaultApplicationRunner;
|
||||
import org.springframework.shell.DefaultShellApplicationRunner;
|
||||
import org.springframework.shell.ShellApplicationRunner;
|
||||
import org.springframework.shell.ShellRunner;
|
||||
|
||||
@@ -31,7 +31,7 @@ public class ApplicationRunnerAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ShellApplicationRunner.class)
|
||||
public DefaultApplicationRunner defaultApplicationRunner(List<ShellRunner> shellRunners) {
|
||||
return new DefaultApplicationRunner(shellRunners);
|
||||
public DefaultShellApplicationRunner defaultShellApplicationRunner(List<ShellRunner> shellRunners) {
|
||||
return new DefaultShellApplicationRunner(shellRunners);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.springframework.shell.boot;
|
||||
|
||||
import org.springframework.shell.jline.NonInteractiveShellRunner;
|
||||
|
||||
/**
|
||||
* Callback interface that can be implemented by beans wishing to customize the
|
||||
* auto-configured {@link NonInteractiveShellRunner}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @since 2.1.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface NonInteractiveShellRunnerCustomizer {
|
||||
/**
|
||||
* Customize the {@link NonInteractiveShellRunner}.
|
||||
* @param shellRunner the non-interactive shell runner to customize
|
||||
*/
|
||||
void customize(NonInteractiveShellRunner shellRunner);
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package org.springframework.shell.boot;
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.Parser;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -54,8 +55,10 @@ public class ShellRunnerAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "spring.shell.noninteractive", value = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public NonInteractiveShellRunner nonInteractiveApplicationRunner() {
|
||||
return new NonInteractiveShellRunner(shell, shellContext);
|
||||
public NonInteractiveShellRunner nonInteractiveApplicationRunner(ObjectProvider<NonInteractiveShellRunnerCustomizer> customizer) {
|
||||
NonInteractiveShellRunner shellRunner = new NonInteractiveShellRunner(shell, shellContext);
|
||||
customizer.orderedStream().forEach((c) -> c.customize(shellRunner));
|
||||
return shellRunner;
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2022 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.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.shell.ParameterResolver;
|
||||
import org.springframework.shell.Shell;
|
||||
import org.springframework.shell.context.ShellContext;
|
||||
import org.springframework.shell.jline.InteractiveShellRunner;
|
||||
import org.springframework.shell.jline.NonInteractiveShellRunner;
|
||||
import org.springframework.shell.jline.PromptProvider;
|
||||
import org.springframework.shell.jline.ScriptShellRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link ShellRunnerAutoConfiguration}.
|
||||
*/
|
||||
class ShellRunnerAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(ShellRunnerAutoConfiguration.class))
|
||||
.withBean(Shell.class, () -> mock(Shell.class))
|
||||
.withBean(PromptProvider.class, () -> mock(PromptProvider.class))
|
||||
.withBean(LineReader.class, () -> mock(LineReader.class))
|
||||
.withBean(Parser.class, () -> mock(Parser.class))
|
||||
.withBean(ShellContext.class, () -> mock(ShellContext.class))
|
||||
.withBean(ParameterResolver.class, () -> mock(ParameterResolver.class));
|
||||
|
||||
@Nested
|
||||
class Interactive {
|
||||
@Test
|
||||
void enabledByDefault() {
|
||||
contextRunner.run(context -> assertThat(context).hasSingleBean(InteractiveShellRunner.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void disabledWhenPropertySet() {
|
||||
contextRunner.withPropertyValues("spring.shell.interactive.enabled:false")
|
||||
.run(context -> assertThat(context).doesNotHaveBean(InteractiveShellRunner.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class NonInteractive {
|
||||
@Test
|
||||
void enabledByDefault() {
|
||||
contextRunner.run(context -> assertThat(context).hasSingleBean(NonInteractiveShellRunner.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void disabledWhenPropertySet() {
|
||||
contextRunner.withPropertyValues("spring.shell.noninteractive.enabled:false")
|
||||
.run(context -> assertThat(context).doesNotHaveBean(NonInteractiveShellRunner.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void canBeCustomized() {
|
||||
NonInteractiveShellRunnerCustomizer customizer = mock(NonInteractiveShellRunnerCustomizer.class);
|
||||
contextRunner.withBean(NonInteractiveShellRunnerCustomizer.class, () -> customizer)
|
||||
.run(context -> {
|
||||
NonInteractiveShellRunner runner = context.getBean(NonInteractiveShellRunner.class);
|
||||
verify(customizer).customize(runner);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Script {
|
||||
@Test
|
||||
void enabledByDefault() {
|
||||
contextRunner.run(context -> assertThat(context).hasSingleBean(ScriptShellRunner.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void disabledWhenPropertySet() {
|
||||
contextRunner.withPropertyValues("spring.shell.script.enabled:false")
|
||||
.run(context -> assertThat(context).doesNotHaveBean(ScriptShellRunner.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
* Copyright 2021-2022 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.
|
||||
@@ -23,21 +23,28 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
/**
|
||||
* Default {@link ApplicationRunner} which dispatches to first ordered
|
||||
* {@link ShellRunner} able to handle shell.
|
||||
* Default {@link ShellApplicationRunner} which dispatches to the first ordered {@link ShellRunner} able to handle
|
||||
* the shell.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
* @author Chris Bono
|
||||
*/
|
||||
public class DefaultApplicationRunner implements ShellApplicationRunner {
|
||||
@Order(DefaultShellApplicationRunner.PRECEDENCE)
|
||||
public class DefaultShellApplicationRunner implements ShellApplicationRunner {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(DefaultApplicationRunner.class);
|
||||
/**
|
||||
* The precedence at which this runner is executed with respect to other ApplicationRunner beans
|
||||
*/
|
||||
public static final int PRECEDENCE = 0;
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(DefaultShellApplicationRunner.class);
|
||||
private final List<ShellRunner> shellRunners;
|
||||
|
||||
public DefaultApplicationRunner(List<ShellRunner> shellRunners) {
|
||||
public DefaultShellApplicationRunner(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
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
* Copyright 2017-2022 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.
|
||||
@@ -31,20 +31,21 @@ import org.springframework.shell.context.InteractionMode;
|
||||
import org.springframework.shell.context.ShellContext;
|
||||
|
||||
/**
|
||||
* Default Boot runner that bootstraps the shell application in interactive
|
||||
* mode.
|
||||
* A {@link ShellRunner} that bootstraps the shell in interactive mode.
|
||||
*
|
||||
* Runs the REPL of the shell unless the {@literal spring.shell.interactive}
|
||||
* property has been set to {@literal false}.
|
||||
* <p>Has lower precedence than {@link ScriptShellRunner} and {@link NonInteractiveShellRunner} which makes it the
|
||||
* default shell runner when the other runners opt-out of handling the shell.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Janne Valkealahti
|
||||
* @author Chris Bono
|
||||
*/
|
||||
@Order(InteractiveShellRunner.PRECEDENCE)
|
||||
public class InteractiveShellRunner implements ShellRunner {
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
* 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 = 0;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
* Copyright 2021-2022 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.
|
||||
@@ -17,6 +17,7 @@ package org.springframework.shell.jline;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.core.annotation.Order;
|
||||
@@ -26,37 +27,45 @@ import org.springframework.shell.Shell;
|
||||
import org.springframework.shell.ShellRunner;
|
||||
import org.springframework.shell.context.InteractionMode;
|
||||
import org.springframework.shell.context.ShellContext;
|
||||
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.
|
||||
* A {@link ShellRunner} that executes commands without entering interactive shell mode.
|
||||
*
|
||||
* <p>Has higher precedence than {@link InteractiveShellRunner} which gives it an opportunity to handle the shell
|
||||
* in non-interactive fashion.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
* @author Chris Bono
|
||||
*/
|
||||
@Order(InteractiveShellRunner.PRECEDENCE - 50)
|
||||
public class NonInteractiveShellRunner implements ShellRunner {
|
||||
|
||||
private final Shell shell;
|
||||
|
||||
private final ShellContext shellContext;
|
||||
|
||||
private Function<ApplicationArguments, List<String>> argsToShellCommand = (args) -> Arrays.asList(args.getSourceArgs());
|
||||
|
||||
public NonInteractiveShellRunner(Shell shell, ShellContext shellContext) {
|
||||
this.shell = shell;
|
||||
this.shellContext = shellContext;
|
||||
}
|
||||
|
||||
public void setArgsToShellCommand(Function<ApplicationArguments, List<String>> argsToShellCommand) {
|
||||
this.argsToShellCommand = argsToShellCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRun(ApplicationArguments args) {
|
||||
List<String> argsToShellCommand = Arrays.asList(args.getSourceArgs());
|
||||
return !ObjectUtils.isEmpty(argsToShellCommand);
|
||||
return !argsToShellCommand.apply(args).isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) throws Exception {
|
||||
shellContext.setInteractionMode(InteractionMode.NONINTERACTIVE);
|
||||
List<String> argsToShellCommand = Arrays.asList(args.getSourceArgs());
|
||||
InputProvider inputProvider = new StringInputProvider(argsToShellCommand);
|
||||
List<String> commands = this.argsToShellCommand.apply(args);
|
||||
InputProvider inputProvider = new StringInputProvider(commands);
|
||||
shell.run(inputProvider);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
* Copyright 2018-2022 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.
|
||||
@@ -31,11 +31,11 @@ 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.
|
||||
* A {@link ShellRunner} 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 InteractiveShellRunner} so that it
|
||||
* prevents it to run if scripts are found.
|
||||
* <p>Has higher precedence than {@link NonInteractiveShellRunner} and {@link InteractiveShellRunner} which gives it
|
||||
* top priority to run the shell if scripts are found.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@@ -44,15 +44,6 @@ import org.springframework.util.ObjectUtils;
|
||||
public class ScriptShellRunner implements ShellRunner {
|
||||
//end::documentation[]
|
||||
|
||||
public static final String SPRING_SHELL_SCRIPT = "spring.shell.script";
|
||||
public static final String ENABLED = "enabled";
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
Reference in New Issue
Block a user