Add primary command feature
- NonInteractiveShellRunner can now shortcircuit into primary command just running it and passing args. - Add hooks into autoconfig so that this is easy to configure. - Relates #755
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
* Copyright 2021-2023 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.
|
||||
@@ -21,8 +21,13 @@ import org.jline.reader.Parser;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.shell.Shell;
|
||||
import org.springframework.shell.boot.condition.OnNotPrimaryCommandCondition;
|
||||
import org.springframework.shell.boot.condition.OnPrimaryCommandCondition;
|
||||
import org.springframework.shell.context.ShellContext;
|
||||
import org.springframework.shell.jline.InteractiveShellRunner;
|
||||
import org.springframework.shell.jline.NonInteractiveShellRunner;
|
||||
@@ -32,38 +37,48 @@ import org.springframework.shell.jline.ScriptShellRunner;
|
||||
@AutoConfiguration
|
||||
public class ShellRunnerAutoConfiguration {
|
||||
|
||||
private Shell shell;
|
||||
private PromptProvider promptProvider;
|
||||
private LineReader lineReader;
|
||||
private Parser parser;
|
||||
private ShellContext shellContext;
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(SpringShellProperties.class)
|
||||
@Conditional(OnPrimaryCommandCondition.class)
|
||||
public static class PrimaryCommandConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "spring.shell.noninteractive", value = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public NonInteractiveShellRunner nonInteractiveApplicationRunner(Shell shell, ShellContext shellContext,
|
||||
ObjectProvider<NonInteractiveShellRunnerCustomizer> customizer, SpringShellProperties properties) {
|
||||
NonInteractiveShellRunner shellRunner = new NonInteractiveShellRunner(shell, shellContext,
|
||||
properties.getNoninteractive().getPrimaryCommand());
|
||||
customizer.orderedStream().forEach((c) -> c.customize(shellRunner));
|
||||
return shellRunner;
|
||||
}
|
||||
|
||||
public ShellRunnerAutoConfiguration(Shell shell, PromptProvider promptProvider, LineReader lineReader,
|
||||
Parser parser, ShellContext shellContext) {
|
||||
this.shell = shell;
|
||||
this.promptProvider = promptProvider;
|
||||
this.lineReader = lineReader;
|
||||
this.parser = parser;
|
||||
this.shellContext = shellContext;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "spring.shell.interactive", value = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public InteractiveShellRunner interactiveApplicationRunner() {
|
||||
return new InteractiveShellRunner(lineReader, promptProvider, shell, shellContext);
|
||||
}
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Conditional(OnNotPrimaryCommandCondition.class)
|
||||
public static class NonePrimaryCommandConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "spring.shell.noninteractive", value = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public NonInteractiveShellRunner nonInteractiveApplicationRunner(ObjectProvider<NonInteractiveShellRunnerCustomizer> customizer) {
|
||||
NonInteractiveShellRunner shellRunner = new NonInteractiveShellRunner(shell, shellContext);
|
||||
customizer.orderedStream().forEach((c) -> c.customize(shellRunner));
|
||||
return shellRunner;
|
||||
}
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "spring.shell.interactive", value = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public InteractiveShellRunner interactiveApplicationRunner(LineReader lineReader, PromptProvider promptProvider,
|
||||
Shell shell, ShellContext shellContext) {
|
||||
return new InteractiveShellRunner(lineReader, promptProvider, shell, shellContext);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "spring.shell.noninteractive", value = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public NonInteractiveShellRunner nonInteractiveApplicationRunner(Shell shell, ShellContext shellContext,
|
||||
ObjectProvider<NonInteractiveShellRunnerCustomizer> customizer) {
|
||||
NonInteractiveShellRunner shellRunner = new NonInteractiveShellRunner(shell, shellContext);
|
||||
customizer.orderedStream().forEach((c) -> c.customize(shellRunner));
|
||||
return shellRunner;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "spring.shell.script", value = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public ScriptShellRunner scriptApplicationRunner(Parser parser, Shell shell) {
|
||||
return new ScriptShellRunner(parser, shell);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "spring.shell.script", value = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public ScriptShellRunner scriptApplicationRunner() {
|
||||
return new ScriptShellRunner(parser, shell);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021-2022 the original author or authors.
|
||||
* Copyright 2021-2023 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.
|
||||
@@ -180,6 +180,7 @@ public class SpringShellProperties {
|
||||
public static class Noninteractive {
|
||||
|
||||
private boolean enabled = true;
|
||||
private String primaryCommand;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
@@ -188,6 +189,14 @@ public class SpringShellProperties {
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getPrimaryCommand() {
|
||||
return primaryCommand;
|
||||
}
|
||||
|
||||
public void setPrimaryCommand(String primaryCommand) {
|
||||
this.primaryCommand = primaryCommand;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Theme {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2023 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.condition;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
|
||||
|
||||
public class OnNotPrimaryCommandCondition extends NoneNestedConditions {
|
||||
|
||||
public OnNotPrimaryCommandCondition() {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(prefix = "spring.shell.noninteractive", value = "primary-command", matchIfMissing = false)
|
||||
static class NotPrimaryCommandCondition {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2023 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.condition;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
|
||||
public class OnPrimaryCommandCondition extends AllNestedConditions {
|
||||
|
||||
public OnPrimaryCommandCondition() {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(prefix = "spring.shell.noninteractive", value = "primary-command", matchIfMissing = false)
|
||||
static class PrimaryCommandCondition {
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
* Copyright 2022-2023 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.
|
||||
@@ -30,6 +30,7 @@ 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 org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -71,6 +72,16 @@ class ShellRunnerAutoConfigurationTests {
|
||||
contextRunner.run(context -> assertThat(context).hasSingleBean(NonInteractiveShellRunner.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void primaryCommandNotSet() {
|
||||
contextRunner.run(context -> {
|
||||
assertThat(context).hasSingleBean(NonInteractiveShellRunner.class);
|
||||
NonInteractiveShellRunner runner = context.getBean(NonInteractiveShellRunner.class);
|
||||
String command = (String) ReflectionTestUtils.getField(runner, "primaryCommand");
|
||||
assertThat(command).isNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void disabledWhenPropertySet() {
|
||||
contextRunner.withPropertyValues("spring.shell.noninteractive.enabled:false")
|
||||
@@ -101,4 +112,22 @@ class ShellRunnerAutoConfigurationTests {
|
||||
.run(context -> assertThat(context).doesNotHaveBean(ScriptShellRunner.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class PrimaryCommand {
|
||||
|
||||
@Test
|
||||
void primaryCommandDisablesOtherRunners() {
|
||||
contextRunner.withPropertyValues("spring.shell.noninteractive.primary-command:fake")
|
||||
.run(context -> {
|
||||
assertThat(context).doesNotHaveBean(InteractiveShellRunner.class);
|
||||
assertThat(context).doesNotHaveBean(ScriptShellRunner.class);
|
||||
assertThat(context).hasSingleBean(NonInteractiveShellRunner.class);
|
||||
NonInteractiveShellRunner runner = context.getBean(NonInteractiveShellRunner.class);
|
||||
String command = (String) ReflectionTestUtils.getField(runner, "primaryCommand");
|
||||
assertThat(command).isEqualTo("fake");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021-2022 the original author or authors.
|
||||
* Copyright 2021-2023 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.
|
||||
@@ -41,6 +41,7 @@ public class SpringShellPropertiesTests {
|
||||
assertThat(properties.getScript().isEnabled()).isTrue();
|
||||
assertThat(properties.getInteractive().isEnabled()).isTrue();
|
||||
assertThat(properties.getNoninteractive().isEnabled()).isTrue();
|
||||
assertThat(properties.getNoninteractive().getPrimaryCommand()).isNull();
|
||||
assertThat(properties.getTheme().getName()).isNull();
|
||||
assertThat(properties.getCommand().getClear().isEnabled()).isTrue();
|
||||
assertThat(properties.getCommand().getHelp().isEnabled()).isTrue();
|
||||
@@ -82,6 +83,7 @@ public class SpringShellPropertiesTests {
|
||||
.withPropertyValues("spring.shell.script.enabled=false")
|
||||
.withPropertyValues("spring.shell.interactive.enabled=false")
|
||||
.withPropertyValues("spring.shell.noninteractive.enabled=false")
|
||||
.withPropertyValues("spring.shell.noninteractive.primary-command=fakecommand")
|
||||
.withPropertyValues("spring.shell.theme.name=fake")
|
||||
.withPropertyValues("spring.shell.command.clear.enabled=false")
|
||||
.withPropertyValues("spring.shell.command.help.enabled=false")
|
||||
@@ -120,6 +122,7 @@ public class SpringShellPropertiesTests {
|
||||
assertThat(properties.getScript().isEnabled()).isFalse();
|
||||
assertThat(properties.getInteractive().isEnabled()).isFalse();
|
||||
assertThat(properties.getNoninteractive().isEnabled()).isFalse();
|
||||
assertThat(properties.getNoninteractive().getPrimaryCommand()).isEqualTo("fakecommand");
|
||||
assertThat(properties.getTheme().getName()).isEqualTo("fake");
|
||||
assertThat(properties.getCommand().getClear().isEnabled()).isFalse();
|
||||
assertThat(properties.getCommand().getHelp().isEnabled()).isFalse();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021-2022 the original author or authors.
|
||||
* Copyright 2021-2023 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.
|
||||
@@ -60,12 +60,19 @@ public class NonInteractiveShellRunner implements ShellRunner {
|
||||
|
||||
private Parser lineParser;
|
||||
|
||||
private String primaryCommand;
|
||||
|
||||
private static final String SINGLE_QUOTE = "\'";
|
||||
private static final String DOUBLE_QUOTE = "\"";
|
||||
|
||||
private Function<ApplicationArguments, List<String>> commandsFromInputArgs = args -> {
|
||||
if (args.getSourceArgs().length == 0) {
|
||||
return Collections.emptyList();
|
||||
if (StringUtils.hasText(primaryCommand)) {
|
||||
Collections.singletonList(primaryCommand);
|
||||
}
|
||||
else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
// re-quote if needed having whitespace
|
||||
String raw = Arrays.stream(args.getSourceArgs())
|
||||
@@ -76,7 +83,12 @@ public class NonInteractiveShellRunner implements ShellRunner {
|
||||
return a;
|
||||
})
|
||||
.collect(Collectors.joining(" "));
|
||||
return Collections.singletonList(raw);
|
||||
if (StringUtils.hasText(primaryCommand)) {
|
||||
return Collections.singletonList(primaryCommand + " " + raw);
|
||||
}
|
||||
else {
|
||||
return Collections.singletonList(raw);
|
||||
}
|
||||
};
|
||||
|
||||
private static boolean isQuoted(String str) {
|
||||
@@ -88,8 +100,13 @@ public class NonInteractiveShellRunner implements ShellRunner {
|
||||
}
|
||||
|
||||
public NonInteractiveShellRunner(Shell shell, ShellContext shellContext) {
|
||||
this(shell, shellContext, null);
|
||||
}
|
||||
|
||||
public NonInteractiveShellRunner(Shell shell, ShellContext shellContext, String primaryCommand) {
|
||||
this.shell = shell;
|
||||
this.shellContext = shellContext;
|
||||
this.primaryCommand = primaryCommand;
|
||||
this.lineParser = new DefaultParser();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user