diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ShellRunnerAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ShellRunnerAutoConfiguration.java index 29346dcc..041b9391 100644 --- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ShellRunnerAutoConfiguration.java +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ShellRunnerAutoConfiguration.java @@ -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 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 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 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); } } diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellProperties.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellProperties.java index 1894ec1c..90669207 100644 --- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellProperties.java +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellProperties.java @@ -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 { diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/condition/OnNotPrimaryCommandCondition.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/condition/OnNotPrimaryCommandCondition.java new file mode 100644 index 00000000..11a7fb15 --- /dev/null +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/condition/OnNotPrimaryCommandCondition.java @@ -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 { + } + +} diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/condition/OnPrimaryCommandCondition.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/condition/OnPrimaryCommandCondition.java new file mode 100644 index 00000000..6f8e7afd --- /dev/null +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/condition/OnPrimaryCommandCondition.java @@ -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 { + } +} diff --git a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ShellRunnerAutoConfigurationTests.java b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ShellRunnerAutoConfigurationTests.java index c433088d..119f68c1 100644 --- a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ShellRunnerAutoConfigurationTests.java +++ b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ShellRunnerAutoConfigurationTests.java @@ -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"); + }); + } + + } } diff --git a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/SpringShellPropertiesTests.java b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/SpringShellPropertiesTests.java index 47465773..249ffcc9 100644 --- a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/SpringShellPropertiesTests.java +++ b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/SpringShellPropertiesTests.java @@ -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(); diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellRunner.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellRunner.java index daa247bf..7abcd1fa 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellRunner.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellRunner.java @@ -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> 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(); }