Skip to content

Add non-interactive shell runner customizer #358

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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);

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
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;
Expand Down Expand Up @@ -54,8 +55,10 @@ public InteractiveShellRunner interactiveApplicationRunner() {

@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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -23,17 +23,24 @@
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
*/
@Order(DefaultApplicationRunner.PRECEDENCE)
public class DefaultApplicationRunner implements ShellApplicationRunner {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[UNRELATED] I would like to rename this to DefaultShellApplicationRunner for specificity.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah true, we could do it with this pr.


/**
* 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(DefaultApplicationRunner.class);
private final List<ShellRunner> shellRunners;

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -31,20 +31,21 @@
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;

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -17,6 +17,7 @@

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;

import org.springframework.boot.ApplicationArguments;
import org.springframework.core.annotation.Order;
Expand All @@ -26,37 +27,45 @@
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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -31,11 +31,11 @@
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
*/
Expand All @@ -44,15 +44,6 @@
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;
Expand Down