From a14657f341f1a9070fc0a853d51cb66450801125 Mon Sep 17 00:00:00 2001 From: Piotr Olaszewski Date: Fri, 7 Nov 2025 00:38:13 +0100 Subject: [PATCH 1/6] Introduce new command DSL Signed-off-by: Piotr Olaszewski --- .../boot/CommandRegistrationCustomizer.java | 8 +- .../CommandRegistryAutoConfiguration.java | 16 +- .../CommandRegistrationFactoryBeanTests.java | 580 ------------------ ...CommandRegistryAutoConfigurationTests.java | 16 +- .../shell/core/command/BuilderSupplier.java | 12 + .../shell/core/command/Command.java | 11 +- .../shell/core/command/CommandAlias.java | 27 + .../core/command/DefaultCommandBuilder.java | 186 ++++++ .../docs/CommandAvailabilitySnippets.java | 48 +- .../shell/docs/CommandNotFoundSnippets.java | 4 +- .../CommandRegistrationAliasSnippets.java | 38 +- .../docs/CommandRegistrationBeanSnippets.java | 9 +- ...ommandRegistrationHelpOptionsSnippets.java | 16 +- .../CommandRegistrationHiddenSnippets.java | 6 +- ...ndRegistrationInteractionModeSnippets.java | 10 +- .../docs/CommandRegistrationSnippets.java | 6 +- .../shell/docs/CommandRegistrySnippets.java | 12 +- .../shell/docs/CommandTargetSnippets.java | 20 +- .../shell/docs/CompletionSnippets.java | 9 +- .../shell/docs/ErrorHandlingSnippets.java | 14 +- .../shell/docs/ExitCodeSnippets.java | 6 +- .../shell/docs/OptionSnippets.java | 175 ++---- .../shell/docs/OptionTypesSnippets.java | 100 +-- .../shell/docs/ShortOptionSnippets.java | 38 +- .../shell/docs/WritingSnippets.java | 11 +- 25 files changed, 448 insertions(+), 930 deletions(-) delete mode 100644 spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandRegistrationFactoryBeanTests.java create mode 100644 spring-shell-core/src/main/java/org/springframework/shell/core/command/BuilderSupplier.java create mode 100644 spring-shell-core/src/main/java/org/springframework/shell/core/command/CommandAlias.java create mode 100644 spring-shell-core/src/main/java/org/springframework/shell/core/command/DefaultCommandBuilder.java diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandRegistrationCustomizer.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandRegistrationCustomizer.java index 0d525ca4e..333ab9a7c 100644 --- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandRegistrationCustomizer.java +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandRegistrationCustomizer.java @@ -15,10 +15,10 @@ */ package org.springframework.shell.boot; -import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.Command; /** - * Callback interface that can be used to customize a {@link CommandRegistration.Builder}. + * Callback interface that can be used to customize a {@link Command.Builder}. * * @author Janne Valkealahti */ @@ -26,9 +26,9 @@ public interface CommandRegistrationCustomizer { /** - * Callback to customize a {@link CommandRegistration.Builder} instance. + * Callback to customize a {@link Command.Builder} instance. * @param commandRegistrationBuilder the command registration builder to customize */ - void customize(CommandRegistration.Builder commandRegistrationBuilder); + void customize(Command.Builder commandRegistrationBuilder); } diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandRegistryAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandRegistryAutoConfiguration.java index 067a122b3..810b3b805 100644 --- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandRegistryAutoConfiguration.java +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandRegistryAutoConfiguration.java @@ -27,11 +27,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.shell.core.MethodTargetRegistrar; import org.springframework.shell.boot.SpringShellProperties.Help; +import org.springframework.shell.core.command.Command; import org.springframework.shell.core.command.CommandRegistry; import org.springframework.shell.core.command.CommandRegistryCustomizer; -import org.springframework.shell.core.command.CommandRegistration; -import org.springframework.shell.core.command.CommandRegistration.BuilderSupplier; -import org.springframework.shell.core.command.CommandRegistration.OptionNameModifier; +import org.springframework.shell.core.command.BuilderSupplier; +import org.springframework.shell.core.command.OptionNameModifier; import org.springframework.shell.core.command.support.OptionNameModifierSupport; import org.springframework.shell.core.command.CommandResolver; import org.springframework.shell.core.context.ShellContext; @@ -57,8 +57,7 @@ public CommandRegistry commandRegistry(ObjectProvider met } @Bean - public CommandRegistryCustomizer defaultCommandRegistryCustomizer( - ObjectProvider commandRegistrations) { + public CommandRegistryCustomizer defaultCommandRegistryCustomizer(ObjectProvider commandRegistrations) { return registry -> { commandRegistrations.orderedStream().forEach(registration -> { registry.register(registration); @@ -71,11 +70,10 @@ public CommandRegistrationCustomizer helpOptionsCommandRegistrationCustomizer(Sp return registration -> { Help help = properties.getHelp(); if (help.isEnabled()) { - registration.withHelpOptions() - .enabled(true) + registration.withHelpOptions(helpOptionsSpec -> helpOptionsSpec.enabled(true) .longNames(help.getLongNames()) .shortNames(help.getShortNames()) - .command(help.getCommand()); + .command(help.getCommand())); } }; } @@ -121,7 +119,7 @@ public CommandRegistrationCustomizer defaultOptionNameModifierCommandRegistratio public BuilderSupplier commandRegistrationBuilderSupplier( ObjectProvider customizerProvider) { return () -> { - CommandRegistration.Builder builder = CommandRegistration.builder(); + Command.Builder builder = Command.builder(); customizerProvider.orderedStream().forEach((customizer) -> customizer.customize(builder)); return builder; }; diff --git a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandRegistrationFactoryBeanTests.java b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandRegistrationFactoryBeanTests.java deleted file mode 100644 index 9f51d2bd9..000000000 --- a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandRegistrationFactoryBeanTests.java +++ /dev/null @@ -1,580 +0,0 @@ -/* - * Copyright 2023-present 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.Collections; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.shell.core.command.annotation.support.CommandRegistrationFactoryBean; -import org.springframework.shell.core.command.availability.Availability; -import org.springframework.shell.core.command.availability.AvailabilityProvider; -import org.springframework.shell.core.command.CommandRegistration; -import org.springframework.shell.core.command.CommandRegistration.OptionArity; -import org.springframework.shell.core.command.annotation.Command; -import org.springframework.shell.core.command.annotation.CommandAvailability; -import org.springframework.shell.core.command.annotation.Option; -import org.springframework.shell.core.command.annotation.OptionValues; -import org.springframework.shell.core.completion.CompletionProvider; - -import static org.assertj.core.api.Assertions.assertThat; - -class CommandRegistrationFactoryBeanTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); - - private static final String BEAN = "commandBean"; - - private static final String FACTORY_BEAN = "commandRegistrationFactoryBean"; - - private static final String FACTORY_BEAN_REF = "&" + FACTORY_BEAN; - - @Test - void hiddenOnClassLevel() { - configCommon(HiddenOnClassBean.class, new HiddenOnClassBean()).run(context -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.isHidden()).isTrue(); - }); - } - - @Command(hidden = true) - private static class HiddenOnClassBean { - - @Command - void command() { - } - - } - - @Test - void commandCommonThings() { - configCommon(OnBothClassAndMethod.class, new OnBothClassAndMethod(), "command1", new Class[] {}) - .run(context -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getCommand()).isEqualTo("one two"); - assertThat(registration.getAliases()).hasSize(1); - assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("three four"); - assertThat(registration.getGroup()).isEqualTo("group2"); - }); - configCommon(OnBothClassAndMethod.class, new OnBothClassAndMethod(), "command2", new Class[] {}) - .run((context) -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getCommand()).isEqualTo("one three"); - assertThat(registration.getAliases()).hasSize(2); - assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("three four"); - assertThat(registration.getAliases().get(1).getCommand()).isEqualTo("three five"); - assertThat(registration.getGroup()).isEqualTo("group2"); - }); - } - - @Command(command = "one", alias = "three", group = "group1") - private static class OnBothClassAndMethod { - - @Command(command = "two", alias = "four", group = "group2") - void command1() { - } - - @Command(command = "three", alias = { "four", "five" }, group = "group2") - void command2() { - } - - } - - @Test - void setsRequiredOption() { - configCommon(RequiredOption.class, new RequiredOption(), "command1", new Class[] { String.class }) - .run(context -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getOptions()).hasSize(1); - assertThat(registration.getOptions().get(0).isRequired()).isTrue(); - }); - configCommon(RequiredOption.class, new RequiredOption(), "command2", new Class[] { String.class }) - .run((context) -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getOptions()).hasSize(1); - assertThat(registration.getOptions().get(0).isRequired()).isFalse(); - }); - } - - @Command - private static class RequiredOption { - - @Command - void command1(@Option(required = true) String arg) { - } - - @Command - void command2(@Option(required = false) String arg) { - } - - } - - @Test - void setsAvailabilitySupplier() { - configCommon(AvailabilityIndicator.class, new AvailabilityIndicator(), "command1", new Class[] {}) - .run(context -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getAvailability()).isNotNull(); - assertThat(registration.getAvailability().getReason()).isEqualTo("fakereason"); - }); - } - - @Command - private static class AvailabilityIndicator { - - @Command - @CommandAvailability(provider = "testAvailability") - void command1() { - } - - @Bean - public AvailabilityProvider testAvailability() { - return () -> Availability.unavailable("fakereason"); - } - - } - - @Test - void setsOptionValuesWithBoolean() { - configCommon(OptionValuesWithBoolean.class, new OptionValuesWithBoolean(), "command1", - new Class[] { boolean.class }) - .run((context) -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(0); - assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(1); - }); - configCommon(OptionValuesWithBoolean.class, new OptionValuesWithBoolean(), "command2", - new Class[] { Boolean.class }) - .run((context) -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(0); - assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(1); - }); - configCommon(OptionValuesWithBoolean.class, new OptionValuesWithBoolean(), "command3", - new Class[] { Boolean.class }) - .run((context) -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(0); - assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(1); - }); - configCommon(OptionValuesWithBoolean.class, new OptionValuesWithBoolean(), "command4", - new Class[] { Boolean.class }) - .run((context) -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(0); - assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(1); - }); - configCommon(OptionValuesWithBoolean.class, new OptionValuesWithBoolean(), "command5", - new Class[] { boolean.class }) - .run((context) -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(0); - assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(1); - assertThat(registration.getOptions().get(0).isRequired()).isFalse(); - assertThat(registration.getOptions().get(0).getDefaultValue()).isEqualTo("false"); - }); - } - - @Command - private static class OptionValuesWithBoolean { - - @Command - void command1(@Option(defaultValue = "false") boolean arg) { - } - - @Command - void command2(@Option(defaultValue = "false") Boolean arg) { - } - - @Command - void command3(@Option Boolean arg) { - } - - @Command - void command4(Boolean arg) { - } - - @Command - void command5(boolean arg) { - } - - } - - @Test - void setsOptionWithCompletion() { - configCommon(OptionWithCompletion.class, new OptionWithCompletion(), "command1", new Class[] { String.class }) - .run(context -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getOptions().get(0).getCompletion()).isNotNull(); - }); - } - - @Command - private static class OptionWithCompletion { - - @Command - void command1(@Option(longNames = "arg") @OptionValues(provider = "completionProvider") String arg) { - } - - @Bean - CompletionProvider completionProvider() { - return ctx -> Collections.emptyList(); - } - - } - - @Test - void setsOptionWithArity() { - configCommon(OptionWithArity.class, new OptionWithArity(), "command1", new Class[] { String.class }) - .run(context -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(1); - assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(1); - }); - configCommon(OptionWithArity.class, new OptionWithArity(), "command2", new Class[] { String.class }) - .run((context) -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(1); - assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(1); - }); - configCommon(OptionWithArity.class, new OptionWithArity(), "command3", new Class[] { String.class }) - .run((context) -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(0); - assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(2); - }); - configCommon(OptionWithArity.class, new OptionWithArity(), "command4", new Class[] { String.class }) - .run((context) -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(0); - assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(2); - }); - } - - @Command - private static class OptionWithArity { - - @Command - void command1(@Option(longNames = "arg", arity = OptionArity.EXACTLY_ONE) String arg) { - } - - @Command - void command2(@Option(longNames = "arg", arityMin = 1) String arg) { - } - - @Command - void command3(@Option(longNames = "arg", arityMax = 2) String arg) { - } - - @Command - void command4(@Option(longNames = "arg", arityMax = 2, arity = OptionArity.EXACTLY_ONE) String arg) { - } - - } - - @Test - void setsOptionWithLabel() { - configCommon(OptionWithLabel.class, new OptionWithLabel(), "command1", new Class[] { String.class }) - .run(context -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getOptions().get(0).getLabel()).isEqualTo("label"); - }); - } - - @Command - private static class OptionWithLabel { - - @Command - void command1(@Option(longNames = "arg", label = "label") String arg) { - } - - } - - private ApplicationContextRunner configCommon(Class type, T bean) { - return configCommon(type, bean, "command", new Class[0]); - } - - private ApplicationContextRunner configCommon(Class type, T bean, String method, Class[] parameters) { - return this.contextRunner.withBean(BEAN, type, () -> bean) - .withBean(FACTORY_BEAN, CommandRegistrationFactoryBean.class, CommandRegistrationFactoryBean::new, bd -> { - bd.getPropertyValues().add(CommandRegistrationFactoryBean.COMMAND_BEAN_TYPE, type); - bd.getPropertyValues().add(CommandRegistrationFactoryBean.COMMAND_BEAN_NAME, BEAN); - bd.getPropertyValues().add(CommandRegistrationFactoryBean.COMMAND_METHOD_NAME, method); - bd.getPropertyValues().add(CommandRegistrationFactoryBean.COMMAND_METHOD_PARAMETERS, parameters); - }); - } - - @Nested - class Aliases { - - @Test - void aliasOnlyOnMethod() { - configCommon(AliasOnlyOnMethod.class, new AliasOnlyOnMethod(), "command1", new Class[] {}).run(context -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getCommand()).isEqualTo("one two"); - assertThat(registration.getAliases()).hasSize(1); - assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("four"); - }); - } - - @Command(command = "one") - private static class AliasOnlyOnMethod { - - @Command(command = "two", alias = "four") - void command1() { - } - - } - - @Test - void aliasOnlyOnClass() { - configCommon(AliasOnlyOnClass.class, new AliasOnlyOnClass(), "command1", new Class[] {}).run(context -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getCommand()).isEqualTo("one two"); - assertThat(registration.getAliases()).hasSize(0); - }); - } - - @Command(command = "one", alias = "three") - private static class AliasOnlyOnClass { - - @Command(command = "two") - void command1() { - } - - } - - @Test - void aliasOnlyOnMethodMultiCommandString() { - configCommon(AliasOnlyOnMethodMultiCommandString.class, new AliasOnlyOnMethodMultiCommandString(), - "command1", new Class[] {}) - .run(context -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getCommand()).isEqualTo("one two"); - assertThat(registration.getAliases()).hasSize(1); - assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("four five"); - }); - } - - @Command(command = "one") - private static class AliasOnlyOnMethodMultiCommandString { - - @Command(command = "two", alias = "four five") - void command1() { - } - - } - - @Test - void aliasOnlyOnMethodMultiCommandArray() { - configCommon(AliasOnlyOnMethodMultiCommandArray.class, new AliasOnlyOnMethodMultiCommandArray(), "command1", - new Class[] {}) - .run((context) -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getCommand()).isEqualTo("one two"); - assertThat(registration.getAliases()).hasSize(2); - assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("four"); - assertThat(registration.getAliases().get(1).getCommand()).isEqualTo("five"); - }); - } - - @Command(command = "one") - private static class AliasOnlyOnMethodMultiCommandArray { - - @Command(command = "two", alias = { "four", "five" }) - void command1() { - } - - } - - @Test - void aliasOnBothMethodStringEmpty() { - configCommon(AliasOnBothMethodStringEmpty.class, new AliasOnBothMethodStringEmpty(), "command1", - new Class[] {}) - .run(context -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getCommand()).isEqualTo("one two"); - assertThat(registration.getAliases()).hasSize(1); - assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("three"); - }); - } - - @Command(command = "one", alias = "three") - private static class AliasOnBothMethodStringEmpty { - - @Command(command = "two", alias = "") - void command1() { - } - - } - - @Test - void aliasOnBoth() { - configCommon(AliasOnBoth.class, new AliasOnBoth(), "command1", new Class[] {}).run(context -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getCommand()).isEqualTo("one two"); - assertThat(registration.getAliases()).hasSize(1); - assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("three four"); - }); - } - - @Command(command = "one", alias = "three") - private static class AliasOnBoth { - - @Command(command = "two", alias = "four") - void command1() { - } - - } - - @Test - void aliasWithCommandOnBothMethodStringEmpty() { - configCommon(AliasWithCommandOnBothMethodStringEmpty.class, new AliasWithCommandOnBothMethodStringEmpty(), - "command1", new Class[] {}) - .run(context -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getCommand()).isEqualTo("one"); - assertThat(registration.getAliases()).hasSize(1); - assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("ten"); - }); - configCommon(AliasWithCommandOnBothMethodStringEmpty.class, new AliasWithCommandOnBothMethodStringEmpty(), - "command2", new Class[] {}) - .run((context) -> { - CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, - CommandRegistrationFactoryBean.class); - assertThat(fb).isNotNull(); - CommandRegistration registration = fb.getObject(); - assertThat(registration).isNotNull(); - assertThat(registration.getCommand()).isEqualTo("one two"); - assertThat(registration.getAliases()).hasSize(1); - assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("ten twelve"); - }); - } - - @Command(command = "one", alias = "ten") - private static class AliasWithCommandOnBothMethodStringEmpty { - - @Command(command = "", alias = "") - void command1() { - } - - @Command(command = "two", alias = "twelve") - void command2() { - } - - } - - } - -} diff --git a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandRegistryAutoConfigurationTests.java b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandRegistryAutoConfigurationTests.java index 0f62d8fec..5d51e8756 100644 --- a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandRegistryAutoConfigurationTests.java +++ b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandRegistryAutoConfigurationTests.java @@ -25,11 +25,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.shell.core.command.CommandRegistry; -import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.Command; import org.springframework.shell.core.command.CommandResolver; -import org.springframework.shell.core.command.CommandRegistration.Builder; -import org.springframework.shell.core.command.CommandRegistration.BuilderSupplier; -import org.springframework.shell.core.command.CommandRegistration.OptionNameModifier; +import org.springframework.shell.core.command.Command.Builder; +import org.springframework.shell.core.command.BuilderSupplier; +import org.springframework.shell.core.command.OptionNameModifier; import static org.assertj.core.api.Assertions.assertThat; @@ -162,12 +162,10 @@ CommandRegistry customCommandRegistry() { static class CustomCommandRegistrationConfiguration { @Bean - CommandRegistration commandRegistration() { - return CommandRegistration.builder() + Command commandRegistration() { + return Command.builder() .command("customcommand") - .withTarget() - .function(ctx -> null) - .and() + .withTarget(targetSpec -> targetSpec.function(ctx -> null)) .build(); } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/core/command/BuilderSupplier.java b/spring-shell-core/src/main/java/org/springframework/shell/core/command/BuilderSupplier.java new file mode 100644 index 000000000..da36c82ed --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/core/command/BuilderSupplier.java @@ -0,0 +1,12 @@ +package org.springframework.shell.core.command; + +import java.util.function.Supplier; + +/** + * Interface used to supply instance of a {@link Command.Builder}. Meant to be a single + * point access to centrally configured builder in an application context. + */ +@FunctionalInterface +public interface BuilderSupplier extends Supplier { + +} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/core/command/Command.java b/spring-shell-core/src/main/java/org/springframework/shell/core/command/Command.java index 0f4c1e324..6482e9dc9 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/core/command/Command.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/core/command/Command.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.shell.core.command; import java.util.Collections; @@ -74,4 +73,14 @@ default List getAliases() { */ ExitStatus execute(CommandContext commandContext) throws Exception; + interface Builder { + Builder name(String name); + Builder description(String description); + Builder help(String help); + Builder group(String group); + Builder options(List options); + Builder aliases(List aliases); + Builder action(Command action); + Command build(); + } } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/core/command/CommandAlias.java b/spring-shell-core/src/main/java/org/springframework/shell/core/command/CommandAlias.java new file mode 100644 index 000000000..35c68c2db --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/core/command/CommandAlias.java @@ -0,0 +1,27 @@ +/* + * Copyright 2022-present 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.core.command; + +import org.jspecify.annotations.Nullable; + +/** + * Represents a command alias. + * + * @author Janne Valkealahti + * @author Piotr Olaszewski + */ +public record CommandAlias(String command, @Nullable String group) { +} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/core/command/DefaultCommandBuilder.java b/spring-shell-core/src/main/java/org/springframework/shell/core/command/DefaultCommandBuilder.java new file mode 100644 index 000000000..0fc61fc82 --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/core/command/DefaultCommandBuilder.java @@ -0,0 +1,186 @@ +package org.springframework.shell.core.command; + +import org.jspecify.annotations.Nullable; +import org.springframework.shell.core.command.Command.AliasSpec; +import org.springframework.shell.core.command.Command.ExitCodeSpec; +import org.springframework.shell.core.command.Command.TargetSpec; +import org.springframework.shell.core.command.DefaultCommand.*; +import org.springframework.shell.core.command.availability.Availability; +import org.springframework.shell.core.command.support.CommandUtils; +import org.springframework.shell.core.context.InteractionMode; +import org.springframework.util.Assert; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import static org.springframework.shell.core.command.Command.Builder; +import static org.springframework.shell.core.command.Command.OptionSpec; + +class DefaultCommandBuilder implements Builder { + + private @Nullable String command; + + private @Nullable String description; + + private @Nullable String group; + + private @Nullable InteractionMode interactionMode; + + private @Nullable Supplier availability; + + private boolean hidden; + + private @Nullable List optionSpecs; + + private @Nullable DefaultHelpOptionsSpec helpOptionsSpec; + + private @Nullable Function defaultOptionNameModifier; + + private @Nullable List aliasSpecs; + + private @Nullable DefaultTargetSpec targetSpec; + + private @Nullable DefaultExitCodeSpec exitCodeSpec; + + private @Nullable DefaultErrorHandlingSpec errorHandling; + + @Override + public Builder command(String... commands) { + this.command = CommandUtils.toCommand(commands); + return this; + } + + @Override + public Builder description(String description) { + this.description = description; + return this; + } + + @Override + public Builder group(String group) { + this.group = group; + return this; + } + + @Override + public Builder interactionMode(@Nullable InteractionMode interactionMode) { + this.interactionMode = interactionMode; + return this; + } + + @Override + public Builder isInteractive() { + interactionMode = InteractionMode.INTERACTIVE; + return this; + } + + @Override + public Builder isNonInteractive() { + interactionMode = InteractionMode.NONINTERACTIVE; + return this; + } + + @Override + public Builder availability(Supplier availability) { + this.availability = availability; + return this; + } + + @Override + public Builder hidden() { + return hidden(true); + } + + @Override + public Builder hidden(boolean hidden) { + this.hidden = hidden; + return this; + } + + @Override + public Builder withOption(Consumer optionConfigurer) { + DefaultOptionSpec optionSpec = new DefaultOptionSpec(); + optionConfigurer.accept(optionSpec); + initOptionSpecs().add(optionSpec); + return this; + } + + @Override + public Builder withHelpOptions(Consumer helpOptionsConfigurer) { + DefaultHelpOptionsSpec defaultHelpOptionsSpec = new DefaultHelpOptionsSpec(); + helpOptionsConfigurer.accept(defaultHelpOptionsSpec); + this.helpOptionsSpec = defaultHelpOptionsSpec; + return this; + } + + private List initOptionSpecs() { + if (optionSpecs == null) { + optionSpecs = new ArrayList<>(); + } + return optionSpecs; + } + + @Override + public Builder defaultOptionNameModifier(Function defaultOptionNameModifier) { + this.defaultOptionNameModifier = defaultOptionNameModifier; + return this; + } + + @Override + public Builder withAlias(Consumer aliasConfigurer) { + DefaultAliasSpec aliasSpec = new DefaultAliasSpec(); + aliasConfigurer.accept(aliasSpec); + initAliasSpecs().add(aliasSpec); + return this; + } + + private List initAliasSpecs() { + if (aliasSpecs == null) { + aliasSpecs = new ArrayList<>(); + } + return aliasSpecs; + } + + @Override + public Builder withTarget(Consumer targetConfigurer) { + DefaultTargetSpec defaultTargetSpec = new DefaultTargetSpec(); + targetConfigurer.accept(defaultTargetSpec); + this.targetSpec = defaultTargetSpec; + return this; + } + + @Override + public Builder withExitCode(Consumer exitCodeConfigurer) { + DefaultExitCodeSpec defaultExitCodeSpec = new DefaultExitCodeSpec(); + exitCodeConfigurer.accept(defaultExitCodeSpec); + this.exitCodeSpec = defaultExitCodeSpec; + return this; + } + + @Override + public Builder withErrorHandling(Consumer errorHandlingConfigurer) { + DefaultErrorHandlingSpec defaultErrorHandlingSpec = new DefaultErrorHandlingSpec(); + errorHandlingConfigurer.accept(defaultErrorHandlingSpec); + this.errorHandling = defaultErrorHandlingSpec; + return this; + } + + @Override + public Command build() { + Assert.hasText(command, "command cannot be empty"); + Assert.notNull(targetSpec, "target cannot be null"); + + InteractionMode interactionMode = this.interactionMode == null ? InteractionMode.ALL : this.interactionMode; + Supplier availability = this.availability == null ? Availability::available : this.availability; + List defaultOptionSpecs = initOptionSpecs(); + List defaultAliasSpecs = initAliasSpecs(); + + return new DefaultCommand(command, interactionMode, group, hidden, description, availability, + defaultOptionSpecs, targetSpec, defaultAliasSpecs, exitCodeSpec, errorHandling, helpOptionsSpec, + defaultOptionNameModifier); + } + +} diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandAvailabilitySnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandAvailabilitySnippets.java index 4fe1e9d39..253a0783d 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandAvailabilitySnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandAvailabilitySnippets.java @@ -18,10 +18,10 @@ import java.util.Calendar; import org.springframework.context.annotation.Bean; +import org.springframework.shell.core.command.BuilderSupplier; import org.springframework.shell.core.command.availability.Availability; import org.springframework.shell.core.command.availability.AvailabilityProvider; -import org.springframework.shell.core.command.CommandRegistration; -import org.springframework.shell.core.command.annotation.Command; +import org.springframework.shell.core.command.Command; import org.springframework.shell.core.command.annotation.CommandAvailability; import static java.util.Calendar.DAY_OF_WEEK; @@ -32,18 +32,18 @@ class CommandAvailabilitySnippets { class Dump1 { // tag::availability-method-in-shellcomponent[] - @Command + @org.springframework.shell.core.command.annotation.Command public class MyCommands { private boolean connected; - @Command(description = "Connect to the server.") + @org.springframework.shell.core.command.annotation.Command(description = "Connect to the server.") public void connect(String user, String password) { // do something connected = true; } - @Command(description = "Download the nuclear codes.") + @org.springframework.shell.core.command.annotation.Command(description = "Download the nuclear codes.") public void download() { // do something } @@ -62,7 +62,7 @@ class Dump2 { boolean connected; // tag::availability-method-name-in-shellcomponent[] - @Command(description = "Download the nuclear codes.") + @org.springframework.shell.core.command.annotation.Command(description = "Download the nuclear codes.") // @CommandAvailability("availabilityCheck") // <1> public void download() { } @@ -79,11 +79,11 @@ class Dump3 { boolean connected; // tag::availability-method-name-multi-in-shellcomponent[] - @Command(description = "Download the nuclear codes.") + @org.springframework.shell.core.command.annotation.Command(description = "Download the nuclear codes.") public void download() { } - @Command(description = "Disconnect from the server.") + @org.springframework.shell.core.command.annotation.Command(description = "Disconnect from the server.") public void disconnect() { } @@ -97,7 +97,7 @@ public Availability availabilityCheck() { } // tag::availability-method-default-value-in-shellcomponent[] - @Command + @org.springframework.shell.core.command.annotation.Command public class Toggles { // @CommandAvailability @@ -106,11 +106,11 @@ public Availability availabilityOnWeekdays() { : Availability.unavailable("today is not Sunday"); } - @Command + @org.springframework.shell.core.command.annotation.Command public void foo() { } - @Command + @org.springframework.shell.core.command.annotation.Command public void bar() { } @@ -120,17 +120,17 @@ public void bar() { class Dump4 { // tag::availability-method-annotation[] - @Command + @org.springframework.shell.core.command.annotation.Command class MyCommands { private boolean connected; - @Command(command = "connect") + @org.springframework.shell.core.command.annotation.Command(command = "connect") public void connect(String user, String password) { connected = true; } - @Command(command = "download") + @org.springframework.shell.core.command.annotation.Command(command = "download") @CommandAvailability(provider = "downloadAvailability") public void download() { // do something @@ -153,30 +153,24 @@ class Dump5 { private boolean connected; @Bean - public CommandRegistration connect(CommandRegistration.BuilderSupplier builder) { + public Command connect(BuilderSupplier builder) { return builder.get() .command("connect") - .withOption() - .longNames("connected") - .required() - .type(boolean.class) - .and() - .withTarget() - .consumer(ctx -> { + .withOption(optionSpec -> optionSpec.longNames("connected").required().type(boolean.class)) + .withTarget(targetSpec -> targetSpec.consumer(ctx -> { boolean connected = ctx.getOptionValue("connected"); this.connected = connected; - }) - .and() + })) .build(); } @Bean - public CommandRegistration download(CommandRegistration.BuilderSupplier builder) { + public Command download(BuilderSupplier builder) { return builder.get().command("download").availability(() -> { return connected ? Availability.available() : Availability.unavailable("you are not connected"); - }).withTarget().consumer(ctx -> { + }).withTarget(targetSpec -> targetSpec.consumer(ctx -> { // do something - }).and().build(); + })).build(); } // end::availability-method-programmatic[] diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandNotFoundSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandNotFoundSnippets.java index 0d951ecbc..10a5db6ab 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandNotFoundSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandNotFoundSnippets.java @@ -19,7 +19,7 @@ import java.util.Map; import org.springframework.context.annotation.Bean; -import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.Command; import org.springframework.shell.core.result.CommandNotFoundMessageProvider; @SuppressWarnings("unused") @@ -35,7 +35,7 @@ public String apply(ProviderContext context) { // actual error, usually CommandNotFound exception Throwable error = context.error(); // access to registrations at this time - Map registrations = context.registrations(); + Map registrations = context.registrations(); // raw text input from a user String text = context.text(); return "My custom message"; diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationAliasSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationAliasSnippets.java index 29513bc47..315087861 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationAliasSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationAliasSnippets.java @@ -15,23 +15,18 @@ */ package org.springframework.shell.docs; -import org.springframework.shell.core.command.CommandRegistration; -import org.springframework.shell.core.command.annotation.Command; +import org.springframework.shell.core.command.Command; class CommandRegistrationAliasSnippets { // tag::builder[] - CommandRegistration commandRegistration() { - return CommandRegistration.builder() + Command commandRegistration() { + return Command.builder() .command("mycommand") // define alias as myalias - .withAlias() - .command("myalias") - .and() + .withAlias(aliasSpec -> aliasSpec.command("myalias")) // define alias as myalias1 and myalias2 - .withAlias() - .command("myalias1", "myalias2") - .and() + .withAlias(aliasSpec -> aliasSpec.command("myalias1", "myalias2")) .build(); } // end::builder[] @@ -39,10 +34,10 @@ CommandRegistration commandRegistration() { class Dump1 { // tag::command1[] - @Command + @org.springframework.shell.core.command.annotation.Command class MyCommands { - @Command(command = "mycommand", alias = "myalias") + @org.springframework.shell.core.command.annotation.Command(command = "mycommand", alias = "myalias") void myCommand() { } @@ -54,10 +49,11 @@ void myCommand() { class Dump2 { // tag::command2[] - @Command + @org.springframework.shell.core.command.annotation.Command class MyCommands { - @Command(command = "mycommand", alias = { "myalias1", "myalias2" }) + @org.springframework.shell.core.command.annotation.Command(command = "mycommand", + alias = { "myalias1", "myalias2" }) void myCommand() { } @@ -69,10 +65,10 @@ void myCommand() { class Dump3 { // tag::command3[] - @Command(alias = "myalias") + @org.springframework.shell.core.command.annotation.Command(alias = "myalias") class MyCommands { - @Command(command = "mycommand") + @org.springframework.shell.core.command.annotation.Command(command = "mycommand") void myCommand() { } @@ -84,10 +80,10 @@ void myCommand() { class Dump4 { // tag::command4[] - @Command(alias = "myalias1") + @org.springframework.shell.core.command.annotation.Command(alias = "myalias1") class MyCommands { - @Command(command = "mycommand", alias = "myalias2") + @org.springframework.shell.core.command.annotation.Command(command = "mycommand", alias = "myalias2") void myCommand() { } @@ -99,14 +95,14 @@ void myCommand() { class Dump5 { // tag::command5[] - @Command(command = "mycommand", alias = "myalias") + @org.springframework.shell.core.command.annotation.Command(command = "mycommand", alias = "myalias") class MyCommands { - @Command(command = "", alias = "") + @org.springframework.shell.core.command.annotation.Command(command = "", alias = "") void myMainCommand() { } - @Command(command = "mysubcommand", alias = "mysubalias") + @org.springframework.shell.core.command.annotation.Command(command = "mysubcommand", alias = "mysubalias") void mySubCommand() { } diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationBeanSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationBeanSnippets.java index d88671f88..aed43857a 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationBeanSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationBeanSnippets.java @@ -17,7 +17,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.shell.boot.CommandRegistrationCustomizer; -import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.BuilderSupplier; +import org.springframework.shell.core.command.Command; public class CommandRegistrationBeanSnippets { @@ -25,8 +26,8 @@ class Dump1 { // tag::plain[] @Bean - CommandRegistration commandRegistration() { - return CommandRegistration.builder().command("mycommand").build(); + Command commandRegistration() { + return Command.builder().command("mycommand").build(); } // end::plain[] @@ -36,7 +37,7 @@ class Dump2 { // tag::fromsupplier[] @Bean - CommandRegistration commandRegistration(CommandRegistration.BuilderSupplier builder) { + Command commandRegistration(BuilderSupplier builder) { return builder.get().command("mycommand").build(); } // end::fromsupplier[] diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationHelpOptionsSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationHelpOptionsSnippets.java index fc20eec41..822e2fe81 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationHelpOptionsSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationHelpOptionsSnippets.java @@ -16,7 +16,7 @@ package org.springframework.shell.docs; import org.springframework.context.annotation.Bean; -import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.Command; class CommandRegistrationHelpOptionsSnippets { @@ -24,15 +24,13 @@ class Dump1 { // tag::defaults[] @Bean - CommandRegistration commandRegistration() { - return CommandRegistration.builder() + Command commandRegistration() { + return Command.builder() .command("mycommand") - .withHelpOptions() - .enabled(true) - .longNames("help") - .shortNames('h') - .command("help") - .and() + .withHelpOptions(helpOptionsSpec -> helpOptionsSpec.enabled(true) + .longNames("help") + .shortNames('h') + .command("help")) .build(); } // end::defaults[] diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationHiddenSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationHiddenSnippets.java index eea15cc38..178a4d8f6 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationHiddenSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationHiddenSnippets.java @@ -15,13 +15,13 @@ */ package org.springframework.shell.docs; -import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.Command; class CommandRegistrationHiddenSnippets { // tag::snippet1[] - CommandRegistration commandRegistration() { - return CommandRegistration.builder() + Command commandRegistration() { + return Command.builder() .command("mycommand") // define as hidden .hidden() diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationInteractionModeSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationInteractionModeSnippets.java index eaec45b03..d2ea6e553 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationInteractionModeSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationInteractionModeSnippets.java @@ -15,15 +15,14 @@ */ package org.springframework.shell.docs; -import org.springframework.shell.core.command.CommandRegistration; -import org.springframework.shell.core.command.annotation.Command; +import org.springframework.shell.core.command.Command; import org.springframework.shell.core.context.InteractionMode; public class CommandRegistrationInteractionModeSnippets { // tag::snippet1[] - CommandRegistration commandRegistration() { - return CommandRegistration.builder() + Command commandRegistration() { + return Command.builder() .command("mycommand") // can be defined for all modes .interactionMode(InteractionMode.ALL) @@ -38,7 +37,8 @@ CommandRegistration commandRegistration() { static class Dump1 { // tag::snippet2[] - @Command(command = "mycommand", interactionMode = InteractionMode.INTERACTIVE) + @org.springframework.shell.core.command.annotation.Command(command = "mycommand", + interactionMode = InteractionMode.INTERACTIVE) public void mycommand() { } // end::snippet2[] diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationSnippets.java index e39949563..9050c6b25 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationSnippets.java @@ -15,19 +15,19 @@ */ package org.springframework.shell.docs; -import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.Command; public class CommandRegistrationSnippets { void dump1() { // tag::snippet1[] - CommandRegistration.builder().withOption().longNames("myopt").and().build(); + Command.builder().withOption(optionSpec -> optionSpec.longNames("myopt")).build(); // end::snippet1[] } void dump2() { // tag::snippet2[] - CommandRegistration.builder().withOption().shortNames('s').and().build(); + Command.builder().withOption(optionSpec -> optionSpec.shortNames('s')).build(); // end::snippet2[] } diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrySnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrySnippets.java index 2ca17e6ee..6207861c6 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrySnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrySnippets.java @@ -18,9 +18,9 @@ import java.util.ArrayList; import java.util.List; +import org.springframework.shell.core.command.Command; import org.springframework.shell.core.command.CommandRegistry; import org.springframework.shell.core.command.CommandRegistryCustomizer; -import org.springframework.shell.core.command.CommandRegistration; import org.springframework.shell.core.command.CommandResolver; public class CommandRegistrySnippets { @@ -29,7 +29,7 @@ public class CommandRegistrySnippets { void dump1() { // tag::snippet1[] - CommandRegistration registration = CommandRegistration.builder().build(); + Command registration = Command.builder().build(); catalog.register(registration); // end::snippet1[] } @@ -37,15 +37,15 @@ void dump1() { // tag::snippet2[] static class CustomCommandResolver implements CommandResolver { - List registrations = new ArrayList<>(); + List registrations = new ArrayList<>(); CustomCommandResolver() { - CommandRegistration resolved = CommandRegistration.builder().command("resolve command").build(); + Command resolved = Command.builder().command("resolve command").build(); registrations.add(resolved); } @Override - public List resolve() { + public List resolve() { return registrations; } @@ -57,7 +57,7 @@ static class CustomCommandRegistryCustomizer implements CommandRegistryCustomize @Override public void customize(CommandRegistry commandRegistry) { - CommandRegistration registration = CommandRegistration.builder().command("resolve command").build(); + Command registration = Command.builder().command("resolve command").build(); commandRegistry.register(registration); } diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandTargetSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandTargetSnippets.java index fc5a68af1..47909df47 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandTargetSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandTargetSnippets.java @@ -15,7 +15,7 @@ */ package org.springframework.shell.docs; -import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.Command; public class CommandTargetSnippets { @@ -32,33 +32,29 @@ String command(String arg) { void dump1() { // tag::snippet12[] CommandPojo pojo = new CommandPojo(); - CommandRegistration.builder() + Command.builder() .command("command") - .withTarget() - .method(pojo, "command") - .and() - .withOption() - .longNames("arg") - .and() + .withTarget(targetSpec -> targetSpec.method(pojo, "command")) + .withOption(optionSpec -> optionSpec.longNames("arg")) .build(); // end::snippet12[] } void dump2() { // tag::snippet2[] - CommandRegistration.builder().command("command").withTarget().function(ctx -> { + Command.builder().command("command").withTarget(targetSpec -> targetSpec.function(ctx -> { String arg = ctx.getOptionValue("arg"); return String.format("hi, arg value is '%s'", arg); - }).and().withOption().longNames("arg").and().build(); + })).withOption(optionSpec -> optionSpec.longNames("arg")).build(); // end::snippet2[] } void dump3() { // tag::snippet3[] - CommandRegistration.builder().command("command").withTarget().consumer(ctx -> { + Command.builder().command("command").withTarget(targetSpec -> targetSpec.consumer(ctx -> { String arg = ctx.getOptionValue("arg"); ctx.getTerminal().writer().println(String.format("hi, arg value is '%s'", arg)); - }).and().withOption().longNames("arg").and().build(); + })).withOption(optionSpec -> optionSpec.longNames("arg")).build(); // end::snippet3[] } diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CompletionSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CompletionSnippets.java index 5f1f3156b..a163642d9 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CompletionSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CompletionSnippets.java @@ -21,8 +21,7 @@ import org.springframework.shell.core.completion.CompletionContext; import org.springframework.shell.core.completion.CompletionProposal; -import org.springframework.shell.core.command.CommandRegistration; -import org.springframework.shell.core.command.annotation.Command; +import org.springframework.shell.core.command.Command; import org.springframework.shell.core.command.annotation.Option; import org.springframework.shell.core.command.annotation.OptionValues; import org.springframework.shell.core.completion.CompletionProvider; @@ -32,9 +31,9 @@ public class CompletionSnippets { // tag::builder-1[] void dump1() { - CommandRegistration.builder().withOption().longNames("arg1").completion(ctx -> { + Command.builder().withOption(optionSpec -> optionSpec.longNames("arg1").completion(ctx -> { return Arrays.asList("val1", "val2").stream().map(CompletionProposal::new).collect(Collectors.toList()); - }).and().build(); + })).build(); } // end::builder-1[] @@ -63,7 +62,7 @@ public List apply(CompletionContext completionContext) { static class Dump1 { // tag::anno-method[] - @Command(command = "complete", description = "complete") + @org.springframework.shell.core.command.annotation.Command(command = "complete", description = "complete") public String complete(@Option @OptionValues(provider = "myCompletionProvider") String arg1) { return "You said " + arg1; } diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/ErrorHandlingSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/ErrorHandlingSnippets.java index 9b9977fd9..56739410e 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/ErrorHandlingSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/ErrorHandlingSnippets.java @@ -22,8 +22,8 @@ import org.springframework.boot.ExitCodeGenerator; import org.springframework.context.annotation.Bean; import org.springframework.shell.core.command.CommandExceptionResolver; -import org.springframework.shell.core.command.CommandHandlingResult; -import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.metadata.CommandHandlingResult; +import org.springframework.shell.core.command.Command; import org.springframework.shell.core.command.annotation.ExceptionResolver; import org.springframework.shell.core.command.annotation.ExitCode; @@ -46,7 +46,7 @@ static class CustomExceptionResolver implements CommandExceptionResolver { @Override public CommandHandlingResult resolve(Exception e) { if (e instanceof CustomException) { - return CommandHandlingResult.of("Hi, handled exception\n", 42); + return new CommandHandlingResult("Hi, handled exception\n", 42); } return null; } @@ -56,7 +56,9 @@ public CommandHandlingResult resolve(Exception e) { void dump1() { // tag::example1[] - CommandRegistration.builder().withErrorHandling().resolver(new CustomExceptionResolver()).and().build(); + Command.builder() + .withErrorHandling(errorHandlingSpec -> errorHandlingSpec.resolver(new CustomExceptionResolver())) + .build(); // end::example1[] } @@ -67,7 +69,7 @@ static class Dump1 { CommandHandlingResult errorHandler(Exception e) { // Exception would be type of RuntimeException, // optionally do something with it - return CommandHandlingResult.of("Hi, handled exception\n", 42); + return new CommandHandlingResult("Hi, handled exception\n", 42); } // end::exception-resolver-with-type-in-annotation[] @@ -78,7 +80,7 @@ static class Dump2 { // tag::exception-resolver-with-type-in-method[] @ExceptionResolver CommandHandlingResult errorHandler(RuntimeException e) { - return CommandHandlingResult.of("Hi, handled custom exception\n", 42); + return new CommandHandlingResult("Hi, handled custom exception\n", 42); } // end::exception-resolver-with-type-in-method[] diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/ExitCodeSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/ExitCodeSnippets.java index d4dcb68d4..033bd89f9 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/ExitCodeSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/ExitCodeSnippets.java @@ -15,7 +15,7 @@ */ package org.springframework.shell.docs; -import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.Command; public class ExitCodeSnippets { @@ -38,12 +38,12 @@ public int getCode() { void dump1() { // tag::example1[] - CommandRegistration.builder().withExitCode().map(MyException.class, 3).map(t -> { + Command.builder().withExitCode(exitCodeSpec -> exitCodeSpec.map(MyException.class, 3).map(t -> { if (t instanceof MyException) { return ((MyException) t).getCode(); } return 0; - }).and().build(); + })).build(); // end::example1[] } diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionSnippets.java index a6efd0362..549e04deb 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionSnippets.java @@ -18,10 +18,9 @@ import java.util.Arrays; import org.springframework.context.annotation.Bean; -import org.springframework.shell.core.command.CommandRegistration; -import org.springframework.shell.core.command.CommandRegistration.OptionArity; -import org.springframework.shell.core.command.CommandRegistration.OptionNameModifier; -import org.springframework.shell.core.command.annotation.Command; +import org.springframework.shell.core.command.Command; +import org.springframework.shell.core.command.Command.OptionArity; +import org.springframework.shell.core.command.OptionNameModifier; import org.springframework.shell.core.command.annotation.Option; public class OptionSnippets { @@ -29,7 +28,7 @@ public class OptionSnippets { class Dump1 { // tag::option-with-option-annotation[] - @Command + @org.springframework.shell.core.command.annotation.Command public String example(@Option(longNames = "arg") String arg1) { return "Hello " + arg1; } @@ -40,7 +39,7 @@ public String example(@Option(longNames = "arg") String arg1) { class Dump7 { // tag::option-with-annotation-without-prefix[] - @Command + @org.springframework.shell.core.command.annotation.Command public String example(@Option(label = "arg") String arg1) { return "Hello " + arg1; } @@ -51,7 +50,7 @@ public String example(@Option(label = "arg") String arg1) { class Dump2 { // tag::option-without-annotation[] - @Command + @org.springframework.shell.core.command.annotation.Command public String example(String arg1) { return "Hello " + arg1; } @@ -104,42 +103,27 @@ public String example(@Option(defaultValue = "defaultValue") String arg1) { public void dump1() { // tag::option-registration-longarg[] - CommandRegistration registration = CommandRegistration.builder().withOption().longNames("arg1").and().build(); + Command registration = Command.builder().withOption(optionSpec -> optionSpec.longNames("arg1")).build(); // end::option-registration-longarg[] // tag::option-registration-shortarg[] - CommandRegistration.builder() - .withOption() - .shortNames('a') - .and() - .withOption() - .shortNames('b') - .and() - .withOption() - .shortNames('c') - .and() + Command.builder() + .withOption(optionSpec -> optionSpec.shortNames('a')) + .withOption(optionSpec -> optionSpec.shortNames('b')) + .withOption(optionSpec -> optionSpec.shortNames('c')) .build(); // end::option-registration-shortarg[] // tag::option-registration-shortargbooleans[] - CommandRegistration.builder() - .withOption() - .shortNames('a') - .type(boolean.class) - .and() - .withOption() - .shortNames('b') - .type(boolean.class) - .and() - .withOption() - .shortNames('c') - .type(boolean.class) - .and() + Command.builder() + .withOption(optionSpec -> optionSpec.shortNames('a').type(boolean.class)) + .withOption(optionSpec -> optionSpec.shortNames('b').type(boolean.class)) + .withOption(optionSpec -> optionSpec.shortNames('c').type(boolean.class)) .build(); // end::option-registration-shortargbooleans[] // tag::option-registration-arityenum[] - CommandRegistration.builder().withOption().longNames("arg1").arity(OptionArity.EXACTLY_ONE).and().build(); + Command.builder().withOption(optionSpec -> optionSpec.longNames("arg1").arity(OptionArity.EXACTLY_ONE)).build(); // end::option-registration-arityenum[] // // tag::option-registration-arityints[] @@ -152,86 +136,62 @@ public void dump1() { // // end::option-registration-arityints[] // tag::option-registration-aritystrings-sample[] - CommandRegistration.builder() + Command.builder() .command("arity-errors") - .withOption() - .longNames("arg1") - .type(String[].class) - .required() - .arity(1, 2) - .and() - .withTarget() - .function(ctx -> { + .withOption(optionSpec -> optionSpec.longNames("arg1").type(String[].class).required().arity(1, 2)) + .withTarget(targetSpec -> targetSpec.function(ctx -> { String[] arg1 = ctx.getOptionValue("arg1"); return "Hello " + Arrays.asList(arg1); - }) - .and() + })) .build(); // end::option-registration-aritystrings-sample[] // tag::option-registration-aritystrings-position[] - CommandRegistration.builder() + Command.builder() .command("arity-strings-2") - .withOption() - .longNames("arg1") - .required() - .type(String[].class) - .arity(0, 2) - .position(0) - .and() - .withTarget() - .function(ctx -> { + .withOption( + optionSpec -> optionSpec.longNames("arg1").required().type(String[].class).arity(0, 2).position(0)) + .withTarget(targetSpec -> targetSpec.function(ctx -> { String[] arg1 = ctx.getOptionValue("arg1"); return "Hello " + Arrays.asList(arg1); - }) - .and() + })) .build(); // end::option-registration-aritystrings-position[] // tag::option-registration-aritystrings-noposition[] - CommandRegistration.builder() + Command.builder() .command("arity-strings-1") - .withOption() - .longNames("arg1") - .required() - .type(String[].class) - .arity(0, 2) - .and() - .withTarget() - .function(ctx -> { + .withOption(optionSpec -> optionSpec.longNames("arg1").required().type(String[].class).arity(0, 2)) + .withTarget(targetSpec -> targetSpec.function(ctx -> { String[] arg1 = ctx.getOptionValue("arg1"); return "Hello " + Arrays.asList(arg1); - }) - .and() + })) .build(); // end::option-registration-aritystrings-noposition[] // tag::option-registration-optional[] - CommandRegistration.builder().withOption().longNames("arg1").required().and().build(); + Command.builder().withOption(optionSpec -> optionSpec.longNames("arg1").required()).build(); // end::option-registration-optional[] // tag::option-registration-positional[] - CommandRegistration.builder().withOption().longNames("arg1").position(0).and().build(); + Command.builder().withOption(optionSpec -> optionSpec.longNames("arg1").position(0)).build(); // end::option-registration-positional[] // tag::option-registration-default[] - CommandRegistration.builder().withOption().longNames("arg1").defaultValue("defaultValue").and().build(); + Command.builder().withOption(optionSpec -> optionSpec.longNames("arg1").defaultValue("defaultValue")).build(); // end::option-registration-default[] // tag::option-registration-label[] - CommandRegistration.builder() - .withOption() - .longNames("arg1") - .and() - .withOption() - .longNames("arg2") - .label("MYLABEL") - .and() + Command.builder() + .withOption(optionSpec -> optionSpec.longNames("arg1")) + .withOption(optionSpec -> optionSpec.longNames("arg2").label("MYLABEL")) .build(); // end::option-registration-label[] // tag::option-registration-naming-case-req[] - CommandRegistration.builder().withOption().longNames("arg1").nameModifier(name -> "x" + name).and().build(); + Command.builder() + .withOption(optionSpec -> optionSpec.longNames("arg1").nameModifier(name -> "x" + name)) + .build(); // end::option-registration-naming-case-req[] } @@ -245,7 +205,7 @@ OptionNameModifier sampleOptionNameModifier() { // end::option-registration-naming-case-bean[] // tag::option-registration-naming-case-sample1[] - @Command(command = "option-naming-sample") + @org.springframework.shell.core.command.annotation.Command(command = "option-naming-sample") public void optionNamingSample(@Option(description = "from_snake") String snake, @Option(description = "fromCamel") String camel, @Option(description = "from-kebab") String kebab, @Option(description = "FromPascal") String pascal) { @@ -257,14 +217,14 @@ public void optionNamingSample(@Option(description = "from_snake") String snake, static class LegacyAnnotation { // tag::option-registration-zeroorone-legacyannotation[] - @Command(command = "example") + @org.springframework.shell.core.command.annotation.Command(command = "example") String zeroOrOne(@Option(arity = OptionArity.EXACTLY_ONE) String arg) { return String.format("Hi '%s'", arg); } // end::option-registration-zeroorone-legacyannotation[] // tag::option-registration-zerooronewithminmax-legacyannotation[] - @Command(command = "example") + @org.springframework.shell.core.command.annotation.Command(command = "example") String zeroOrOneWithMinMax(@Option(arity = OptionArity.EXACTLY_ONE) String arg) { return String.format("Hi '%s'", arg); } @@ -290,14 +250,14 @@ void defaultOption(@Option(defaultValue = "default") String arg) { static class Annotation { // tag::option-registration-zeroorone-annotation[] - @Command(command = "example") + @org.springframework.shell.core.command.annotation.Command(command = "example") String zeroOrOne(@Option(arity = OptionArity.ZERO_OR_ONE) String arg) { return String.format("Hi '%s'", arg); } // end::option-registration-zeroorone-annotation[] // tag::option-registration-zerooronewithminmax-annotation[] - @Command(command = "example") + @org.springframework.shell.core.command.annotation.Command(command = "example") String zeroOrOneWithMinMax(@Option(arityMin = 0, arityMax = 1) String arg) { return String.format("Hi '%s'", arg); } @@ -328,68 +288,53 @@ void labelOption(@Option(label = "MYLABEL") String arg) { static class Registration { // tag::option-registration-zeroorone-programmatic[] - CommandRegistration zeroOrOne() { - return CommandRegistration.builder() + Command zeroOrOne() { + return Command.builder() .command("example") - .withOption() - .longNames("arg") - .arity(OptionArity.ZERO_OR_ONE) - .and() + .withOption(optionSpec -> optionSpec.longNames("arg").arity(OptionArity.ZERO_OR_ONE)) .build(); } // end::option-registration-zeroorone-programmatic[] // tag::option-registration-zerooronewithminmax-programmatic[] - CommandRegistration zeroOrOneWithMinMax() { - return CommandRegistration.builder() + Command zeroOrOneWithMinMax() { + return Command.builder() .command("example") - .withOption() - .longNames("arg") - .arity(0, 1) - .and() + .withOption(optionSpec -> optionSpec.longNames("arg").arity(0, 1)) .build(); } // end::option-registration-zerooronewithminmax-programmatic[] // tag::option-optional-programmatic[] - CommandRegistration optionalOption() { - return CommandRegistration.builder() + Command optionalOption() { + return Command.builder() .command("optionalOption") - .withOption() - .longNames("arg") - .required(false) - .and() + .withOption(optionSpec -> optionSpec.longNames("arg").required(false)) .build(); } // end::option-optional-programmatic[] // tag::option-mandatory-programmatic[] - CommandRegistration mandatoryOption() { - return CommandRegistration.builder() + Command mandatoryOption() { + return Command.builder() .command("optionalOption") - .withOption() - .longNames("arg") - .required() - .and() + .withOption(optionSpec -> optionSpec.longNames("arg").required()) .build(); } // end::option-mandatory-programmatic[] // tag::option-default-programmatic[] - CommandRegistration defaultOption() { - return CommandRegistration.builder() + Command defaultOption() { + return Command.builder() .command("defaultOption") - .withOption() - .longNames("arg") - .defaultValue("default") - .and() + .withOption(optionSpec -> optionSpec.longNames("arg").defaultValue("default")) .build(); } // end::option-default-programmatic[] // tag::option-label-programmatic[] - CommandRegistration labelOption() { - return CommandRegistration.builder().withOption().longNames("arg").label("MYLABEL").and().build(); + Command labelOption() { + return Command.builder().withOption(optionSpec -> optionSpec.longNames("arg").label("MYLABEL")).build(); } // end::option-label-programmatic[] diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionTypesSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionTypesSnippets.java index 922f4ba4d..d14332e3f 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionTypesSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionTypesSnippets.java @@ -15,7 +15,7 @@ */ package org.springframework.shell.docs; -import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.Command; import org.springframework.shell.core.command.annotation.Option; class OptionTypesSnippets { @@ -32,38 +32,15 @@ String example(@Option() boolean arg1, @Option(defaultValue = "true") boolean ar // end::option-type-boolean-anno[] void dump() { // tag::option-type-boolean-reg[] - CommandRegistration.builder() + Command.builder() .command("example") - .withOption() - .longNames("arg1") - .type(boolean.class) - .and() - .withOption() - .longNames("arg2") - .type(boolean.class) - .defaultValue("true") - .and() - .withOption() - .longNames("arg3") - .type(boolean.class) - .defaultValue("false") - .and() - .withOption() - .longNames("arg4") - .type(Boolean.class) - .and() - .withOption() - .longNames("arg5") - .type(Boolean.class) - .defaultValue("true") - .and() - .withOption() - .longNames("arg6") - .type(Boolean.class) - .defaultValue("false") - .and() - .withTarget() - .function(ctx -> { + .withOption(optionSpec -> optionSpec.longNames("arg1").type(boolean.class)) + .withOption(optionSpec -> optionSpec.longNames("arg2").type(boolean.class).defaultValue("true")) + .withOption(optionSpec -> optionSpec.longNames("arg3").type(boolean.class).defaultValue("false")) + .withOption(optionSpec -> optionSpec.longNames("arg4").type(Boolean.class)) + .withOption(optionSpec -> optionSpec.longNames("arg5").type(Boolean.class).defaultValue("true")) + .withOption(optionSpec -> optionSpec.longNames("arg6").type(Boolean.class).defaultValue("false")) + .withTarget(targetSpec -> targetSpec.function(ctx -> { boolean arg1 = ctx.hasMappedOption("arg1") ? ctx.getOptionValue("arg1") : false; boolean arg2 = ctx.getOptionValue("arg2"); boolean arg3 = ctx.getOptionValue("arg3"); @@ -72,8 +49,7 @@ void dump() { Boolean arg6 = ctx.getOptionValue("arg6"); return String.format("Hello arg1=%s arg2=%s arg3=%s arg4=%s arg5=%s arg6=%s", arg1, arg2, arg3, arg4, arg5, arg6); - }) - .and() + })) .build(); // end::option-type-boolean-reg[] } @@ -90,19 +66,13 @@ String example(@Option(description = "arg1") int arg1) { // end::option-type-integer-anno[] void dump() { // tag::option-type-integer-reg[] - CommandRegistration.builder() + Command.builder() .command("example") - .withOption() - .longNames("arg1") - .type(int.class) - .required() - .and() - .withTarget() - .function(ctx -> { + .withOption(optionSpec -> optionSpec.longNames("arg1").type(int.class).required()) + .withTarget(targetSpec -> targetSpec.function(ctx -> { boolean arg1 = ctx.getOptionValue("arg1"); return "Hello " + arg1; - }) - .and() + })) .build(); // end::option-type-integer-reg[] } @@ -119,19 +89,13 @@ String example(@Option(description = "arg1") String arg1) { // end::option-type-string-anno[] void dump() { // tag::option-type-string-reg[] - CommandRegistration.builder() + Command.builder() .command("example") - .withOption() - .longNames("arg1") - .type(String.class) - .required() - .and() - .withTarget() - .function(ctx -> { + .withOption(optionSpec -> optionSpec.longNames("arg1").type(String.class).required()) + .withTarget(targetSpec -> targetSpec.function(ctx -> { String arg1 = ctx.getOptionValue("arg1"); return "Hello " + arg1; - }) - .and() + })) .build(); // end::option-type-string-reg[] } @@ -156,19 +120,13 @@ String example(@Option(description = "arg1") OptionTypeEnum arg1) { // end::option-type-enum-anno[] void dump() { // tag::option-type-enum-reg[] - CommandRegistration.builder() + Command.builder() .command("example") - .withOption() - .longNames("arg1") - .type(OptionTypeEnum.class) - .required() - .and() - .withTarget() - .function(ctx -> { + .withOption(optionSpec -> optionSpec.longNames("arg1").type(OptionTypeEnum.class).required()) + .withTarget(targetSpec -> targetSpec.function(ctx -> { OptionTypeEnum arg1 = ctx.getOptionValue("arg1"); return "Hello " + arg1; - }) - .and() + })) .build(); // end::option-type-enum-reg[] } @@ -185,19 +143,13 @@ String example(@Option(description = "arg1") String[] arg1) { // end::option-type-string-array-anno[] void dump() { // tag::option-type-string-array-reg[] - CommandRegistration.builder() + Command.builder() .command("example") - .withOption() - .longNames("arg1") - .type(String[].class) - .required() - .and() - .withTarget() - .function(ctx -> { + .withOption(optionSpec -> optionSpec.longNames("arg1").type(String[].class).required()) + .withTarget(targetSpec -> targetSpec.function(ctx -> { String[] arg1 = ctx.getOptionValue("arg1"); return "Hello " + arg1; - }) - .and() + })) .build(); // end::option-type-string-array-reg[] } diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/ShortOptionSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/ShortOptionSnippets.java index 4b802dd5f..0b775379d 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/ShortOptionSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/ShortOptionSnippets.java @@ -15,8 +15,7 @@ */ package org.springframework.shell.docs; -import org.springframework.shell.core.command.CommandRegistration; -import org.springframework.shell.core.command.annotation.Command; +import org.springframework.shell.core.command.Command; import org.springframework.shell.core.command.annotation.Option; public class ShortOptionSnippets { @@ -25,14 +24,14 @@ public class ShortOptionSnippets { static class Annotation { // tag::option-type-string-annotation[] - @Command(command = "example") + @org.springframework.shell.core.command.annotation.Command(command = "example") String stringWithShortOption(@Option(longNames = "arg", shortNames = 'a', required = true) String arg) { return String.format("Hi '%s'", arg); } // end::option-type-string-annotation[] // tag::option-type-multiple-booleans-annotation[] - @Command(command = "example") + @org.springframework.shell.core.command.annotation.Command(command = "example") public String multipleBooleans(@Option(shortNames = 'a') boolean a, @Option(shortNames = 'b') boolean b, @Option(shortNames = 'c') boolean c) { return String.format("Hi a='%s' b='%s' c='%s'", a, b, c); @@ -44,38 +43,25 @@ public String multipleBooleans(@Option(shortNames = 'a') boolean a, @Option(shor static class Registration { // tag::option-type-string-programmatic[] - CommandRegistration stringWithShortOption() { - return CommandRegistration.builder().command("example").withTarget().function(ctx -> { + Command stringWithShortOption() { + return Command.builder().command("example").withTarget(targetSpec -> targetSpec.function(ctx -> { String arg = ctx.hasMappedOption("arg") ? ctx.getOptionValue("arg") : null; return String.format("Hi arg='%s'", arg); - }).and().withOption().longNames("arg").shortNames('a').required().and().build(); + })).withOption(optionSpec -> optionSpec.longNames("arg").shortNames('a').required()).build(); } // end::option-type-string-programmatic[] // tag::option-type-multiple-booleans-programmatic[] - CommandRegistration multipleBooleans() { - return CommandRegistration.builder().command("example").withTarget().function(ctx -> { + Command multipleBooleans() { + return Command.builder().command("example").withTarget(targetSpec -> targetSpec.function(ctx -> { Boolean a = ctx.hasMappedOption("a") ? ctx.getOptionValue("a") : null; Boolean b = ctx.hasMappedOption("b") ? ctx.getOptionValue("b") : null; Boolean c = ctx.hasMappedOption("c") ? ctx.getOptionValue("c") : null; return String.format("Hi a='%s' b='%s' c='%s'", a, b, c); - }) - .and() - .withOption() - .shortNames('a') - .type(boolean.class) - .defaultValue("false") - .and() - .withOption() - .shortNames('b') - .type(boolean.class) - .defaultValue("false") - .and() - .withOption() - .shortNames('c') - .type(boolean.class) - .defaultValue("false") - .and() + })) + .withOption(optionSpec -> optionSpec.shortNames('a').type(boolean.class).defaultValue("false")) + .withOption(optionSpec -> optionSpec.shortNames('b').type(boolean.class).defaultValue("false")) + .withOption(optionSpec -> optionSpec.shortNames('c').type(boolean.class).defaultValue("false")) .build(); } // end::option-type-multiple-booleans-programmatic[] diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/WritingSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/WritingSnippets.java index eb863a0db..30043769a 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/WritingSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/WritingSnippets.java @@ -18,9 +18,8 @@ import org.jline.terminal.Terminal; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.shell.core.command.Command; import org.springframework.shell.core.command.CommandContext; -import org.springframework.shell.core.command.CommandRegistration; -import org.springframework.shell.core.command.annotation.Command; class WritingSnippets { @@ -30,7 +29,7 @@ class Dump1 { @Autowired Terminal terminal; - @Command + @org.springframework.shell.core.command.annotation.Command public void example() { terminal.writer().println("hi"); terminal.writer().flush(); @@ -42,7 +41,7 @@ public void example() { class Dump2 { // tag::anno-terminal-writer[] - @Command + @org.springframework.shell.core.command.annotation.Command public void example(CommandContext ctx) { ctx.getTerminal().writer().println("hi"); ctx.getTerminal().writer().flush(); @@ -53,10 +52,10 @@ public void example(CommandContext ctx) { void dump1() { // tag::reg-terminal-writer[] - CommandRegistration.builder().command("example").withTarget().consumer(ctx -> { + Command.builder().command("example").withTarget(targetSpec -> targetSpec.consumer(ctx -> { ctx.getTerminal().writer().println("hi"); ctx.getTerminal().writer().flush(); - }).and().build(); + })).build(); // end::reg-terminal-writer[] } From cedf6f383111af1f3eb2da6d88b438034859da71 Mon Sep 17 00:00:00 2001 From: Piotr Olaszewski Date: Sat, 29 Nov 2025 20:32:52 +0100 Subject: [PATCH 2/6] Cleanup Signed-off-by: Piotr Olaszewski --- .editorconfig | 5 + .../shell/core/command/BuilderSupplier.java | 12 -- .../shell/core/command/Command.java | 77 +++++++- .../shell/core/command/CommandAlias.java | 27 --- .../core/command/DefaultCommandBuilder.java | 170 ++++-------------- .../shell/core/commands/AbstractCommand.java | 4 +- .../core/commands/adapter/package-info.java | 4 + .../helloworld/SpringShellApplication.java | 29 ++- 8 files changed, 146 insertions(+), 182 deletions(-) create mode 100644 .editorconfig delete mode 100644 spring-shell-core/src/main/java/org/springframework/shell/core/command/BuilderSupplier.java delete mode 100644 spring-shell-core/src/main/java/org/springframework/shell/core/command/CommandAlias.java create mode 100644 spring-shell-core/src/main/java/org/springframework/shell/core/commands/adapter/package-info.java diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..2bfa4b538 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +root = true + +[*.{java,xml,gradle}] +indent_style = tab +indent_size = 4 diff --git a/spring-shell-core/src/main/java/org/springframework/shell/core/command/BuilderSupplier.java b/spring-shell-core/src/main/java/org/springframework/shell/core/command/BuilderSupplier.java deleted file mode 100644 index da36c82ed..000000000 --- a/spring-shell-core/src/main/java/org/springframework/shell/core/command/BuilderSupplier.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.springframework.shell.core.command; - -import java.util.function.Supplier; - -/** - * Interface used to supply instance of a {@link Command.Builder}. Meant to be a single - * point access to centrally configured builder in an application context. - */ -@FunctionalInterface -public interface BuilderSupplier extends Supplier { - -} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/core/command/Command.java b/spring-shell-core/src/main/java/org/springframework/shell/core/command/Command.java index 6482e9dc9..fecea40db 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/core/command/Command.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/core/command/Command.java @@ -17,6 +17,9 @@ import java.util.Collections; import java.util.List; +import java.util.function.Consumer; + +import org.springframework.shell.core.commands.AbstractCommand; /** * @author Eric Bottard @@ -73,14 +76,70 @@ default List getAliases() { */ ExitStatus execute(CommandContext commandContext) throws Exception; + /** + * Creates and returns a new instance of a {@code Builder} for defining and + * constructing commands. + *

+ * The builder allows customization of command properties such as name, description, + * group, help text, aliases, and execution logic. + * @return a new {@code Builder} instance for configuring and creating commands + */ + static Builder builder() { + return new DefaultCommandBuilder(); + } + + /** + * Builder for creating command. + */ interface Builder { - Builder name(String name); - Builder description(String description); - Builder help(String help); - Builder group(String group); - Builder options(List options); - Builder aliases(List aliases); - Builder action(Command action); - Command build(); - } + + /** + * Set the name of the command. + * @return this builder + */ + Builder name(String name); + + /** + * Set the description of the command. + * @return this builder + */ + Builder description(String description); + + /** + * Set the help of the command. + * @return this builder + */ + Builder help(String help); + + /** + * Set the group of the command. + * @return this builder + */ + Builder group(String group); + + /** + * Set the aliases of the command. + * @return this builder + */ + Builder aliases(String... aliases); + + /** + * Set the aliases of the command. + * @return this builder + */ + Builder aliases(List aliases); + + /** + * Set command execution logic. + * @return this builder + */ + Builder execute(Consumer commandExecutor); + + /** + * Build the {@link AbstractCommand}. + */ + AbstractCommand build(); + + } + } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/core/command/CommandAlias.java b/spring-shell-core/src/main/java/org/springframework/shell/core/command/CommandAlias.java deleted file mode 100644 index 35c68c2db..000000000 --- a/spring-shell-core/src/main/java/org/springframework/shell/core/command/CommandAlias.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2022-present 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.core.command; - -import org.jspecify.annotations.Nullable; - -/** - * Represents a command alias. - * - * @author Janne Valkealahti - * @author Piotr Olaszewski - */ -public record CommandAlias(String command, @Nullable String group) { -} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/core/command/DefaultCommandBuilder.java b/spring-shell-core/src/main/java/org/springframework/shell/core/command/DefaultCommandBuilder.java index 0fc61fc82..560fc4945 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/core/command/DefaultCommandBuilder.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/core/command/DefaultCommandBuilder.java @@ -1,55 +1,38 @@ package org.springframework.shell.core.command; -import org.jspecify.annotations.Nullable; -import org.springframework.shell.core.command.Command.AliasSpec; -import org.springframework.shell.core.command.Command.ExitCodeSpec; -import org.springframework.shell.core.command.Command.TargetSpec; -import org.springframework.shell.core.command.DefaultCommand.*; -import org.springframework.shell.core.command.availability.Availability; -import org.springframework.shell.core.command.support.CommandUtils; -import org.springframework.shell.core.context.InteractionMode; -import org.springframework.util.Assert; - -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; -import static org.springframework.shell.core.command.Command.Builder; -import static org.springframework.shell.core.command.Command.OptionSpec; +import org.jspecify.annotations.Nullable; +import org.springframework.shell.core.commands.AbstractCommand; +import org.springframework.shell.core.commands.adapter.ConsumerCommandAdapter; +import org.springframework.util.Assert; +import static org.springframework.shell.core.command.Command.*; + +/** + * Default implementation of {@link Builder}. + * + * @author Piotr Olaszewski + */ class DefaultCommandBuilder implements Builder { - private @Nullable String command; + private @Nullable String name; private @Nullable String description; - private @Nullable String group; - - private @Nullable InteractionMode interactionMode; - - private @Nullable Supplier availability; - - private boolean hidden; - - private @Nullable List optionSpecs; - - private @Nullable DefaultHelpOptionsSpec helpOptionsSpec; - - private @Nullable Function defaultOptionNameModifier; - - private @Nullable List aliasSpecs; + private String group = ""; - private @Nullable DefaultTargetSpec targetSpec; + private String help = ""; - private @Nullable DefaultExitCodeSpec exitCodeSpec; + private @Nullable List aliases; - private @Nullable DefaultErrorHandlingSpec errorHandling; + private @Nullable Consumer commandContextConsumer; @Override - public Builder command(String... commands) { - this.command = CommandUtils.toCommand(commands); + public Builder name(String name) { + this.name = name; return this; } @@ -60,127 +43,52 @@ public Builder description(String description) { } @Override - public Builder group(String group) { - this.group = group; - return this; - } - - @Override - public Builder interactionMode(@Nullable InteractionMode interactionMode) { - this.interactionMode = interactionMode; - return this; - } - - @Override - public Builder isInteractive() { - interactionMode = InteractionMode.INTERACTIVE; - return this; - } - - @Override - public Builder isNonInteractive() { - interactionMode = InteractionMode.NONINTERACTIVE; + public Builder help(String help) { + this.help = help; return this; } @Override - public Builder availability(Supplier availability) { - this.availability = availability; - return this; - } - - @Override - public Builder hidden() { - return hidden(true); - } - - @Override - public Builder hidden(boolean hidden) { - this.hidden = hidden; + public Builder group(String group) { + this.group = group; return this; } @Override - public Builder withOption(Consumer optionConfigurer) { - DefaultOptionSpec optionSpec = new DefaultOptionSpec(); - optionConfigurer.accept(optionSpec); - initOptionSpecs().add(optionSpec); + public Builder aliases(String... aliases) { + this.aliases = Arrays.asList(aliases); return this; } @Override - public Builder withHelpOptions(Consumer helpOptionsConfigurer) { - DefaultHelpOptionsSpec defaultHelpOptionsSpec = new DefaultHelpOptionsSpec(); - helpOptionsConfigurer.accept(defaultHelpOptionsSpec); - this.helpOptionsSpec = defaultHelpOptionsSpec; + public Builder aliases(List aliases) { + this.aliases = aliases; return this; } - private List initOptionSpecs() { - if (optionSpecs == null) { - optionSpecs = new ArrayList<>(); - } - return optionSpecs; - } - @Override - public Builder defaultOptionNameModifier(Function defaultOptionNameModifier) { - this.defaultOptionNameModifier = defaultOptionNameModifier; + public Builder execute(Consumer commandExecutor) { + this.commandContextConsumer = commandExecutor; return this; } @Override - public Builder withAlias(Consumer aliasConfigurer) { - DefaultAliasSpec aliasSpec = new DefaultAliasSpec(); - aliasConfigurer.accept(aliasSpec); - initAliasSpecs().add(aliasSpec); - return this; - } + public AbstractCommand build() { + ConsumerCommandAdapter abstractCommand = initCommand(); - private List initAliasSpecs() { - if (aliasSpecs == null) { - aliasSpecs = new ArrayList<>(); + if (aliases != null) { + abstractCommand.setAliases(aliases); } - return aliasSpecs; - } - @Override - public Builder withTarget(Consumer targetConfigurer) { - DefaultTargetSpec defaultTargetSpec = new DefaultTargetSpec(); - targetConfigurer.accept(defaultTargetSpec); - this.targetSpec = defaultTargetSpec; - return this; + return abstractCommand; } - @Override - public Builder withExitCode(Consumer exitCodeConfigurer) { - DefaultExitCodeSpec defaultExitCodeSpec = new DefaultExitCodeSpec(); - exitCodeConfigurer.accept(defaultExitCodeSpec); - this.exitCodeSpec = defaultExitCodeSpec; - return this; - } - - @Override - public Builder withErrorHandling(Consumer errorHandlingConfigurer) { - DefaultErrorHandlingSpec defaultErrorHandlingSpec = new DefaultErrorHandlingSpec(); - errorHandlingConfigurer.accept(defaultErrorHandlingSpec); - this.errorHandling = defaultErrorHandlingSpec; - return this; - } + private ConsumerCommandAdapter initCommand() { + Assert.hasText(name, "'name' must be specified"); + Assert.hasText(description, "description"); + Assert.notNull(commandContextConsumer, "'commandExecutor' must not be null"); - @Override - public Command build() { - Assert.hasText(command, "command cannot be empty"); - Assert.notNull(targetSpec, "target cannot be null"); - - InteractionMode interactionMode = this.interactionMode == null ? InteractionMode.ALL : this.interactionMode; - Supplier availability = this.availability == null ? Availability::available : this.availability; - List defaultOptionSpecs = initOptionSpecs(); - List defaultAliasSpecs = initAliasSpecs(); - - return new DefaultCommand(command, interactionMode, group, hidden, description, availability, - defaultOptionSpecs, targetSpec, defaultAliasSpecs, exitCodeSpec, errorHandling, helpOptionsSpec, - defaultOptionNameModifier); + return new ConsumerCommandAdapter(name, description, group, help, commandContextConsumer); } } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/core/commands/AbstractCommand.java b/spring-shell-core/src/main/java/org/springframework/shell/core/commands/AbstractCommand.java index fcb48dc2e..bd634b49e 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/core/commands/AbstractCommand.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/core/commands/AbstractCommand.java @@ -97,10 +97,10 @@ public ExitStatus execute(CommandContext commandContext) throws Exception { return doExecute(commandContext); } + public abstract ExitStatus doExecute(CommandContext commandContext) throws Exception; + private static boolean isHelp(CommandOption option) { return option.longName().equalsIgnoreCase("help") || option.shortName() == 'h'; } - public abstract ExitStatus doExecute(CommandContext commandContext) throws Exception; - } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/core/commands/adapter/package-info.java b/spring-shell-core/src/main/java/org/springframework/shell/core/commands/adapter/package-info.java new file mode 100644 index 000000000..007fb92f5 --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/core/commands/adapter/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.springframework.shell.core.commands.adapter; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-shell-samples/spring-shell-sample-hello-world/src/main/java/org/springframework/shell/samples/helloworld/SpringShellApplication.java b/spring-shell-samples/spring-shell-sample-hello-world/src/main/java/org/springframework/shell/samples/helloworld/SpringShellApplication.java index c878be64c..4719c33ea 100644 --- a/spring-shell-samples/spring-shell-sample-hello-world/src/main/java/org/springframework/shell/samples/helloworld/SpringShellApplication.java +++ b/spring-shell-samples/spring-shell-sample-hello-world/src/main/java/org/springframework/shell/samples/helloworld/SpringShellApplication.java @@ -3,13 +3,20 @@ import java.util.List; import org.jline.terminal.Terminal; +import org.jline.utils.AttributedStringBuilder; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.shell.core.ShellRunner; import org.springframework.shell.core.command.CommandContext; -import org.springframework.shell.core.command.annotation.*; +import org.springframework.shell.core.command.annotation.Argument; +import org.springframework.shell.core.command.annotation.Arguments; +import org.springframework.shell.core.command.annotation.Command; +import org.springframework.shell.core.command.annotation.EnableCommand; +import org.springframework.shell.core.command.annotation.Option; +import org.springframework.shell.core.commands.AbstractCommand; +import static org.jline.utils.AttributedStyle.*; @EnableCommand(SpringShellApplication.class) public class SpringShellApplication { @@ -43,6 +50,26 @@ public void sayYo(CommandContext commandContext) { terminal.writer().println("Yo there! what's up?"); } + @Bean + public AbstractCommand sayGoodMorning() { + return org.springframework.shell.core.command.Command.builder() + .name("good-morning") + .description("Say good morning") + .aliases("greetings") + .help("A command that greets the user with 'Good morning!'") + .execute(commandContext -> { + String ansiString = new AttributedStringBuilder().append("Good morning ") + .append("Sir", BOLD.foreground(GREEN)) + .append("!") + .toAnsi(); + + Terminal terminal = commandContext.terminal(); + terminal.writer().println(ansiString); + terminal.flush(); + }) + .build(); + } + @Bean public HelloCommand sayHello() { return new HelloCommand(); From c9848b3b8102576347566591424bb90bac06624c Mon Sep 17 00:00:00 2001 From: Piotr Olaszewski Date: Sun, 30 Nov 2025 09:51:09 +0100 Subject: [PATCH 3/6] Cleanup Signed-off-by: Piotr Olaszewski --- .../boot/CommandRegistrationCustomizer.java | 8 +- .../CommandRegistryAutoConfiguration.java | 16 +- .../CommandRegistrationFactoryBeanTests.java | 580 ++++++++++++++++++ ...CommandRegistryAutoConfigurationTests.java | 16 +- .../docs/CommandAvailabilitySnippets.java | 48 +- .../shell/docs/CommandNotFoundSnippets.java | 4 +- .../CommandRegistrationAliasSnippets.java | 38 +- .../docs/CommandRegistrationBeanSnippets.java | 9 +- ...ommandRegistrationHelpOptionsSnippets.java | 16 +- .../CommandRegistrationHiddenSnippets.java | 6 +- ...ndRegistrationInteractionModeSnippets.java | 10 +- .../docs/CommandRegistrationSnippets.java | 6 +- .../shell/docs/CommandRegistrySnippets.java | 12 +- .../shell/docs/CommandTargetSnippets.java | 20 +- .../shell/docs/CompletionSnippets.java | 9 +- .../shell/docs/ErrorHandlingSnippets.java | 14 +- .../shell/docs/ExitCodeSnippets.java | 6 +- .../shell/docs/OptionSnippets.java | 175 ++++-- .../shell/docs/OptionTypesSnippets.java | 100 ++- .../shell/docs/ShortOptionSnippets.java | 38 +- .../shell/docs/WritingSnippets.java | 11 +- .../helloworld/SpringShellApplication.java | 2 +- 22 files changed, 930 insertions(+), 214 deletions(-) create mode 100644 spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandRegistrationFactoryBeanTests.java diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandRegistrationCustomizer.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandRegistrationCustomizer.java index 333ab9a7c..0d525ca4e 100644 --- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandRegistrationCustomizer.java +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandRegistrationCustomizer.java @@ -15,10 +15,10 @@ */ package org.springframework.shell.boot; -import org.springframework.shell.core.command.Command; +import org.springframework.shell.core.command.CommandRegistration; /** - * Callback interface that can be used to customize a {@link Command.Builder}. + * Callback interface that can be used to customize a {@link CommandRegistration.Builder}. * * @author Janne Valkealahti */ @@ -26,9 +26,9 @@ public interface CommandRegistrationCustomizer { /** - * Callback to customize a {@link Command.Builder} instance. + * Callback to customize a {@link CommandRegistration.Builder} instance. * @param commandRegistrationBuilder the command registration builder to customize */ - void customize(Command.Builder commandRegistrationBuilder); + void customize(CommandRegistration.Builder commandRegistrationBuilder); } diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandRegistryAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandRegistryAutoConfiguration.java index 810b3b805..067a122b3 100644 --- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandRegistryAutoConfiguration.java +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandRegistryAutoConfiguration.java @@ -27,11 +27,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.shell.core.MethodTargetRegistrar; import org.springframework.shell.boot.SpringShellProperties.Help; -import org.springframework.shell.core.command.Command; import org.springframework.shell.core.command.CommandRegistry; import org.springframework.shell.core.command.CommandRegistryCustomizer; -import org.springframework.shell.core.command.BuilderSupplier; -import org.springframework.shell.core.command.OptionNameModifier; +import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.CommandRegistration.BuilderSupplier; +import org.springframework.shell.core.command.CommandRegistration.OptionNameModifier; import org.springframework.shell.core.command.support.OptionNameModifierSupport; import org.springframework.shell.core.command.CommandResolver; import org.springframework.shell.core.context.ShellContext; @@ -57,7 +57,8 @@ public CommandRegistry commandRegistry(ObjectProvider met } @Bean - public CommandRegistryCustomizer defaultCommandRegistryCustomizer(ObjectProvider commandRegistrations) { + public CommandRegistryCustomizer defaultCommandRegistryCustomizer( + ObjectProvider commandRegistrations) { return registry -> { commandRegistrations.orderedStream().forEach(registration -> { registry.register(registration); @@ -70,10 +71,11 @@ public CommandRegistrationCustomizer helpOptionsCommandRegistrationCustomizer(Sp return registration -> { Help help = properties.getHelp(); if (help.isEnabled()) { - registration.withHelpOptions(helpOptionsSpec -> helpOptionsSpec.enabled(true) + registration.withHelpOptions() + .enabled(true) .longNames(help.getLongNames()) .shortNames(help.getShortNames()) - .command(help.getCommand())); + .command(help.getCommand()); } }; } @@ -119,7 +121,7 @@ public CommandRegistrationCustomizer defaultOptionNameModifierCommandRegistratio public BuilderSupplier commandRegistrationBuilderSupplier( ObjectProvider customizerProvider) { return () -> { - Command.Builder builder = Command.builder(); + CommandRegistration.Builder builder = CommandRegistration.builder(); customizerProvider.orderedStream().forEach((customizer) -> customizer.customize(builder)); return builder; }; diff --git a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandRegistrationFactoryBeanTests.java b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandRegistrationFactoryBeanTests.java new file mode 100644 index 000000000..9f51d2bd9 --- /dev/null +++ b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandRegistrationFactoryBeanTests.java @@ -0,0 +1,580 @@ +/* + * Copyright 2023-present 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.Collections; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.shell.core.command.annotation.support.CommandRegistrationFactoryBean; +import org.springframework.shell.core.command.availability.Availability; +import org.springframework.shell.core.command.availability.AvailabilityProvider; +import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.CommandRegistration.OptionArity; +import org.springframework.shell.core.command.annotation.Command; +import org.springframework.shell.core.command.annotation.CommandAvailability; +import org.springframework.shell.core.command.annotation.Option; +import org.springframework.shell.core.command.annotation.OptionValues; +import org.springframework.shell.core.completion.CompletionProvider; + +import static org.assertj.core.api.Assertions.assertThat; + +class CommandRegistrationFactoryBeanTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); + + private static final String BEAN = "commandBean"; + + private static final String FACTORY_BEAN = "commandRegistrationFactoryBean"; + + private static final String FACTORY_BEAN_REF = "&" + FACTORY_BEAN; + + @Test + void hiddenOnClassLevel() { + configCommon(HiddenOnClassBean.class, new HiddenOnClassBean()).run(context -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.isHidden()).isTrue(); + }); + } + + @Command(hidden = true) + private static class HiddenOnClassBean { + + @Command + void command() { + } + + } + + @Test + void commandCommonThings() { + configCommon(OnBothClassAndMethod.class, new OnBothClassAndMethod(), "command1", new Class[] {}) + .run(context -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getCommand()).isEqualTo("one two"); + assertThat(registration.getAliases()).hasSize(1); + assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("three four"); + assertThat(registration.getGroup()).isEqualTo("group2"); + }); + configCommon(OnBothClassAndMethod.class, new OnBothClassAndMethod(), "command2", new Class[] {}) + .run((context) -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getCommand()).isEqualTo("one three"); + assertThat(registration.getAliases()).hasSize(2); + assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("three four"); + assertThat(registration.getAliases().get(1).getCommand()).isEqualTo("three five"); + assertThat(registration.getGroup()).isEqualTo("group2"); + }); + } + + @Command(command = "one", alias = "three", group = "group1") + private static class OnBothClassAndMethod { + + @Command(command = "two", alias = "four", group = "group2") + void command1() { + } + + @Command(command = "three", alias = { "four", "five" }, group = "group2") + void command2() { + } + + } + + @Test + void setsRequiredOption() { + configCommon(RequiredOption.class, new RequiredOption(), "command1", new Class[] { String.class }) + .run(context -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getOptions()).hasSize(1); + assertThat(registration.getOptions().get(0).isRequired()).isTrue(); + }); + configCommon(RequiredOption.class, new RequiredOption(), "command2", new Class[] { String.class }) + .run((context) -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getOptions()).hasSize(1); + assertThat(registration.getOptions().get(0).isRequired()).isFalse(); + }); + } + + @Command + private static class RequiredOption { + + @Command + void command1(@Option(required = true) String arg) { + } + + @Command + void command2(@Option(required = false) String arg) { + } + + } + + @Test + void setsAvailabilitySupplier() { + configCommon(AvailabilityIndicator.class, new AvailabilityIndicator(), "command1", new Class[] {}) + .run(context -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getAvailability()).isNotNull(); + assertThat(registration.getAvailability().getReason()).isEqualTo("fakereason"); + }); + } + + @Command + private static class AvailabilityIndicator { + + @Command + @CommandAvailability(provider = "testAvailability") + void command1() { + } + + @Bean + public AvailabilityProvider testAvailability() { + return () -> Availability.unavailable("fakereason"); + } + + } + + @Test + void setsOptionValuesWithBoolean() { + configCommon(OptionValuesWithBoolean.class, new OptionValuesWithBoolean(), "command1", + new Class[] { boolean.class }) + .run((context) -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(0); + assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(1); + }); + configCommon(OptionValuesWithBoolean.class, new OptionValuesWithBoolean(), "command2", + new Class[] { Boolean.class }) + .run((context) -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(0); + assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(1); + }); + configCommon(OptionValuesWithBoolean.class, new OptionValuesWithBoolean(), "command3", + new Class[] { Boolean.class }) + .run((context) -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(0); + assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(1); + }); + configCommon(OptionValuesWithBoolean.class, new OptionValuesWithBoolean(), "command4", + new Class[] { Boolean.class }) + .run((context) -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(0); + assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(1); + }); + configCommon(OptionValuesWithBoolean.class, new OptionValuesWithBoolean(), "command5", + new Class[] { boolean.class }) + .run((context) -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(0); + assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(1); + assertThat(registration.getOptions().get(0).isRequired()).isFalse(); + assertThat(registration.getOptions().get(0).getDefaultValue()).isEqualTo("false"); + }); + } + + @Command + private static class OptionValuesWithBoolean { + + @Command + void command1(@Option(defaultValue = "false") boolean arg) { + } + + @Command + void command2(@Option(defaultValue = "false") Boolean arg) { + } + + @Command + void command3(@Option Boolean arg) { + } + + @Command + void command4(Boolean arg) { + } + + @Command + void command5(boolean arg) { + } + + } + + @Test + void setsOptionWithCompletion() { + configCommon(OptionWithCompletion.class, new OptionWithCompletion(), "command1", new Class[] { String.class }) + .run(context -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getOptions().get(0).getCompletion()).isNotNull(); + }); + } + + @Command + private static class OptionWithCompletion { + + @Command + void command1(@Option(longNames = "arg") @OptionValues(provider = "completionProvider") String arg) { + } + + @Bean + CompletionProvider completionProvider() { + return ctx -> Collections.emptyList(); + } + + } + + @Test + void setsOptionWithArity() { + configCommon(OptionWithArity.class, new OptionWithArity(), "command1", new Class[] { String.class }) + .run(context -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(1); + assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(1); + }); + configCommon(OptionWithArity.class, new OptionWithArity(), "command2", new Class[] { String.class }) + .run((context) -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(1); + assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(1); + }); + configCommon(OptionWithArity.class, new OptionWithArity(), "command3", new Class[] { String.class }) + .run((context) -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(0); + assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(2); + }); + configCommon(OptionWithArity.class, new OptionWithArity(), "command4", new Class[] { String.class }) + .run((context) -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getOptions().get(0).getArityMin()).isEqualTo(0); + assertThat(registration.getOptions().get(0).getArityMax()).isEqualTo(2); + }); + } + + @Command + private static class OptionWithArity { + + @Command + void command1(@Option(longNames = "arg", arity = OptionArity.EXACTLY_ONE) String arg) { + } + + @Command + void command2(@Option(longNames = "arg", arityMin = 1) String arg) { + } + + @Command + void command3(@Option(longNames = "arg", arityMax = 2) String arg) { + } + + @Command + void command4(@Option(longNames = "arg", arityMax = 2, arity = OptionArity.EXACTLY_ONE) String arg) { + } + + } + + @Test + void setsOptionWithLabel() { + configCommon(OptionWithLabel.class, new OptionWithLabel(), "command1", new Class[] { String.class }) + .run(context -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getOptions().get(0).getLabel()).isEqualTo("label"); + }); + } + + @Command + private static class OptionWithLabel { + + @Command + void command1(@Option(longNames = "arg", label = "label") String arg) { + } + + } + + private ApplicationContextRunner configCommon(Class type, T bean) { + return configCommon(type, bean, "command", new Class[0]); + } + + private ApplicationContextRunner configCommon(Class type, T bean, String method, Class[] parameters) { + return this.contextRunner.withBean(BEAN, type, () -> bean) + .withBean(FACTORY_BEAN, CommandRegistrationFactoryBean.class, CommandRegistrationFactoryBean::new, bd -> { + bd.getPropertyValues().add(CommandRegistrationFactoryBean.COMMAND_BEAN_TYPE, type); + bd.getPropertyValues().add(CommandRegistrationFactoryBean.COMMAND_BEAN_NAME, BEAN); + bd.getPropertyValues().add(CommandRegistrationFactoryBean.COMMAND_METHOD_NAME, method); + bd.getPropertyValues().add(CommandRegistrationFactoryBean.COMMAND_METHOD_PARAMETERS, parameters); + }); + } + + @Nested + class Aliases { + + @Test + void aliasOnlyOnMethod() { + configCommon(AliasOnlyOnMethod.class, new AliasOnlyOnMethod(), "command1", new Class[] {}).run(context -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getCommand()).isEqualTo("one two"); + assertThat(registration.getAliases()).hasSize(1); + assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("four"); + }); + } + + @Command(command = "one") + private static class AliasOnlyOnMethod { + + @Command(command = "two", alias = "four") + void command1() { + } + + } + + @Test + void aliasOnlyOnClass() { + configCommon(AliasOnlyOnClass.class, new AliasOnlyOnClass(), "command1", new Class[] {}).run(context -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getCommand()).isEqualTo("one two"); + assertThat(registration.getAliases()).hasSize(0); + }); + } + + @Command(command = "one", alias = "three") + private static class AliasOnlyOnClass { + + @Command(command = "two") + void command1() { + } + + } + + @Test + void aliasOnlyOnMethodMultiCommandString() { + configCommon(AliasOnlyOnMethodMultiCommandString.class, new AliasOnlyOnMethodMultiCommandString(), + "command1", new Class[] {}) + .run(context -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getCommand()).isEqualTo("one two"); + assertThat(registration.getAliases()).hasSize(1); + assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("four five"); + }); + } + + @Command(command = "one") + private static class AliasOnlyOnMethodMultiCommandString { + + @Command(command = "two", alias = "four five") + void command1() { + } + + } + + @Test + void aliasOnlyOnMethodMultiCommandArray() { + configCommon(AliasOnlyOnMethodMultiCommandArray.class, new AliasOnlyOnMethodMultiCommandArray(), "command1", + new Class[] {}) + .run((context) -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getCommand()).isEqualTo("one two"); + assertThat(registration.getAliases()).hasSize(2); + assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("four"); + assertThat(registration.getAliases().get(1).getCommand()).isEqualTo("five"); + }); + } + + @Command(command = "one") + private static class AliasOnlyOnMethodMultiCommandArray { + + @Command(command = "two", alias = { "four", "five" }) + void command1() { + } + + } + + @Test + void aliasOnBothMethodStringEmpty() { + configCommon(AliasOnBothMethodStringEmpty.class, new AliasOnBothMethodStringEmpty(), "command1", + new Class[] {}) + .run(context -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getCommand()).isEqualTo("one two"); + assertThat(registration.getAliases()).hasSize(1); + assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("three"); + }); + } + + @Command(command = "one", alias = "three") + private static class AliasOnBothMethodStringEmpty { + + @Command(command = "two", alias = "") + void command1() { + } + + } + + @Test + void aliasOnBoth() { + configCommon(AliasOnBoth.class, new AliasOnBoth(), "command1", new Class[] {}).run(context -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getCommand()).isEqualTo("one two"); + assertThat(registration.getAliases()).hasSize(1); + assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("three four"); + }); + } + + @Command(command = "one", alias = "three") + private static class AliasOnBoth { + + @Command(command = "two", alias = "four") + void command1() { + } + + } + + @Test + void aliasWithCommandOnBothMethodStringEmpty() { + configCommon(AliasWithCommandOnBothMethodStringEmpty.class, new AliasWithCommandOnBothMethodStringEmpty(), + "command1", new Class[] {}) + .run(context -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getCommand()).isEqualTo("one"); + assertThat(registration.getAliases()).hasSize(1); + assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("ten"); + }); + configCommon(AliasWithCommandOnBothMethodStringEmpty.class, new AliasWithCommandOnBothMethodStringEmpty(), + "command2", new Class[] {}) + .run((context) -> { + CommandRegistrationFactoryBean fb = context.getBean(FACTORY_BEAN_REF, + CommandRegistrationFactoryBean.class); + assertThat(fb).isNotNull(); + CommandRegistration registration = fb.getObject(); + assertThat(registration).isNotNull(); + assertThat(registration.getCommand()).isEqualTo("one two"); + assertThat(registration.getAliases()).hasSize(1); + assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("ten twelve"); + }); + } + + @Command(command = "one", alias = "ten") + private static class AliasWithCommandOnBothMethodStringEmpty { + + @Command(command = "", alias = "") + void command1() { + } + + @Command(command = "two", alias = "twelve") + void command2() { + } + + } + + } + +} diff --git a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandRegistryAutoConfigurationTests.java b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandRegistryAutoConfigurationTests.java index 5d51e8756..0f62d8fec 100644 --- a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandRegistryAutoConfigurationTests.java +++ b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandRegistryAutoConfigurationTests.java @@ -25,11 +25,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.shell.core.command.CommandRegistry; -import org.springframework.shell.core.command.Command; +import org.springframework.shell.core.command.CommandRegistration; import org.springframework.shell.core.command.CommandResolver; -import org.springframework.shell.core.command.Command.Builder; -import org.springframework.shell.core.command.BuilderSupplier; -import org.springframework.shell.core.command.OptionNameModifier; +import org.springframework.shell.core.command.CommandRegistration.Builder; +import org.springframework.shell.core.command.CommandRegistration.BuilderSupplier; +import org.springframework.shell.core.command.CommandRegistration.OptionNameModifier; import static org.assertj.core.api.Assertions.assertThat; @@ -162,10 +162,12 @@ CommandRegistry customCommandRegistry() { static class CustomCommandRegistrationConfiguration { @Bean - Command commandRegistration() { - return Command.builder() + CommandRegistration commandRegistration() { + return CommandRegistration.builder() .command("customcommand") - .withTarget(targetSpec -> targetSpec.function(ctx -> null)) + .withTarget() + .function(ctx -> null) + .and() .build(); } diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandAvailabilitySnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandAvailabilitySnippets.java index 253a0783d..4fe1e9d39 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandAvailabilitySnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandAvailabilitySnippets.java @@ -18,10 +18,10 @@ import java.util.Calendar; import org.springframework.context.annotation.Bean; -import org.springframework.shell.core.command.BuilderSupplier; import org.springframework.shell.core.command.availability.Availability; import org.springframework.shell.core.command.availability.AvailabilityProvider; -import org.springframework.shell.core.command.Command; +import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.annotation.Command; import org.springframework.shell.core.command.annotation.CommandAvailability; import static java.util.Calendar.DAY_OF_WEEK; @@ -32,18 +32,18 @@ class CommandAvailabilitySnippets { class Dump1 { // tag::availability-method-in-shellcomponent[] - @org.springframework.shell.core.command.annotation.Command + @Command public class MyCommands { private boolean connected; - @org.springframework.shell.core.command.annotation.Command(description = "Connect to the server.") + @Command(description = "Connect to the server.") public void connect(String user, String password) { // do something connected = true; } - @org.springframework.shell.core.command.annotation.Command(description = "Download the nuclear codes.") + @Command(description = "Download the nuclear codes.") public void download() { // do something } @@ -62,7 +62,7 @@ class Dump2 { boolean connected; // tag::availability-method-name-in-shellcomponent[] - @org.springframework.shell.core.command.annotation.Command(description = "Download the nuclear codes.") + @Command(description = "Download the nuclear codes.") // @CommandAvailability("availabilityCheck") // <1> public void download() { } @@ -79,11 +79,11 @@ class Dump3 { boolean connected; // tag::availability-method-name-multi-in-shellcomponent[] - @org.springframework.shell.core.command.annotation.Command(description = "Download the nuclear codes.") + @Command(description = "Download the nuclear codes.") public void download() { } - @org.springframework.shell.core.command.annotation.Command(description = "Disconnect from the server.") + @Command(description = "Disconnect from the server.") public void disconnect() { } @@ -97,7 +97,7 @@ public Availability availabilityCheck() { } // tag::availability-method-default-value-in-shellcomponent[] - @org.springframework.shell.core.command.annotation.Command + @Command public class Toggles { // @CommandAvailability @@ -106,11 +106,11 @@ public Availability availabilityOnWeekdays() { : Availability.unavailable("today is not Sunday"); } - @org.springframework.shell.core.command.annotation.Command + @Command public void foo() { } - @org.springframework.shell.core.command.annotation.Command + @Command public void bar() { } @@ -120,17 +120,17 @@ public void bar() { class Dump4 { // tag::availability-method-annotation[] - @org.springframework.shell.core.command.annotation.Command + @Command class MyCommands { private boolean connected; - @org.springframework.shell.core.command.annotation.Command(command = "connect") + @Command(command = "connect") public void connect(String user, String password) { connected = true; } - @org.springframework.shell.core.command.annotation.Command(command = "download") + @Command(command = "download") @CommandAvailability(provider = "downloadAvailability") public void download() { // do something @@ -153,24 +153,30 @@ class Dump5 { private boolean connected; @Bean - public Command connect(BuilderSupplier builder) { + public CommandRegistration connect(CommandRegistration.BuilderSupplier builder) { return builder.get() .command("connect") - .withOption(optionSpec -> optionSpec.longNames("connected").required().type(boolean.class)) - .withTarget(targetSpec -> targetSpec.consumer(ctx -> { + .withOption() + .longNames("connected") + .required() + .type(boolean.class) + .and() + .withTarget() + .consumer(ctx -> { boolean connected = ctx.getOptionValue("connected"); this.connected = connected; - })) + }) + .and() .build(); } @Bean - public Command download(BuilderSupplier builder) { + public CommandRegistration download(CommandRegistration.BuilderSupplier builder) { return builder.get().command("download").availability(() -> { return connected ? Availability.available() : Availability.unavailable("you are not connected"); - }).withTarget(targetSpec -> targetSpec.consumer(ctx -> { + }).withTarget().consumer(ctx -> { // do something - })).build(); + }).and().build(); } // end::availability-method-programmatic[] diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandNotFoundSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandNotFoundSnippets.java index 10a5db6ab..0d951ecbc 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandNotFoundSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandNotFoundSnippets.java @@ -19,7 +19,7 @@ import java.util.Map; import org.springframework.context.annotation.Bean; -import org.springframework.shell.core.command.Command; +import org.springframework.shell.core.command.CommandRegistration; import org.springframework.shell.core.result.CommandNotFoundMessageProvider; @SuppressWarnings("unused") @@ -35,7 +35,7 @@ public String apply(ProviderContext context) { // actual error, usually CommandNotFound exception Throwable error = context.error(); // access to registrations at this time - Map registrations = context.registrations(); + Map registrations = context.registrations(); // raw text input from a user String text = context.text(); return "My custom message"; diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationAliasSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationAliasSnippets.java index 315087861..29513bc47 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationAliasSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationAliasSnippets.java @@ -15,18 +15,23 @@ */ package org.springframework.shell.docs; -import org.springframework.shell.core.command.Command; +import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.annotation.Command; class CommandRegistrationAliasSnippets { // tag::builder[] - Command commandRegistration() { - return Command.builder() + CommandRegistration commandRegistration() { + return CommandRegistration.builder() .command("mycommand") // define alias as myalias - .withAlias(aliasSpec -> aliasSpec.command("myalias")) + .withAlias() + .command("myalias") + .and() // define alias as myalias1 and myalias2 - .withAlias(aliasSpec -> aliasSpec.command("myalias1", "myalias2")) + .withAlias() + .command("myalias1", "myalias2") + .and() .build(); } // end::builder[] @@ -34,10 +39,10 @@ Command commandRegistration() { class Dump1 { // tag::command1[] - @org.springframework.shell.core.command.annotation.Command + @Command class MyCommands { - @org.springframework.shell.core.command.annotation.Command(command = "mycommand", alias = "myalias") + @Command(command = "mycommand", alias = "myalias") void myCommand() { } @@ -49,11 +54,10 @@ void myCommand() { class Dump2 { // tag::command2[] - @org.springframework.shell.core.command.annotation.Command + @Command class MyCommands { - @org.springframework.shell.core.command.annotation.Command(command = "mycommand", - alias = { "myalias1", "myalias2" }) + @Command(command = "mycommand", alias = { "myalias1", "myalias2" }) void myCommand() { } @@ -65,10 +69,10 @@ void myCommand() { class Dump3 { // tag::command3[] - @org.springframework.shell.core.command.annotation.Command(alias = "myalias") + @Command(alias = "myalias") class MyCommands { - @org.springframework.shell.core.command.annotation.Command(command = "mycommand") + @Command(command = "mycommand") void myCommand() { } @@ -80,10 +84,10 @@ void myCommand() { class Dump4 { // tag::command4[] - @org.springframework.shell.core.command.annotation.Command(alias = "myalias1") + @Command(alias = "myalias1") class MyCommands { - @org.springframework.shell.core.command.annotation.Command(command = "mycommand", alias = "myalias2") + @Command(command = "mycommand", alias = "myalias2") void myCommand() { } @@ -95,14 +99,14 @@ void myCommand() { class Dump5 { // tag::command5[] - @org.springframework.shell.core.command.annotation.Command(command = "mycommand", alias = "myalias") + @Command(command = "mycommand", alias = "myalias") class MyCommands { - @org.springframework.shell.core.command.annotation.Command(command = "", alias = "") + @Command(command = "", alias = "") void myMainCommand() { } - @org.springframework.shell.core.command.annotation.Command(command = "mysubcommand", alias = "mysubalias") + @Command(command = "mysubcommand", alias = "mysubalias") void mySubCommand() { } diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationBeanSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationBeanSnippets.java index aed43857a..d88671f88 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationBeanSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationBeanSnippets.java @@ -17,8 +17,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.shell.boot.CommandRegistrationCustomizer; -import org.springframework.shell.core.command.BuilderSupplier; -import org.springframework.shell.core.command.Command; +import org.springframework.shell.core.command.CommandRegistration; public class CommandRegistrationBeanSnippets { @@ -26,8 +25,8 @@ class Dump1 { // tag::plain[] @Bean - Command commandRegistration() { - return Command.builder().command("mycommand").build(); + CommandRegistration commandRegistration() { + return CommandRegistration.builder().command("mycommand").build(); } // end::plain[] @@ -37,7 +36,7 @@ class Dump2 { // tag::fromsupplier[] @Bean - Command commandRegistration(BuilderSupplier builder) { + CommandRegistration commandRegistration(CommandRegistration.BuilderSupplier builder) { return builder.get().command("mycommand").build(); } // end::fromsupplier[] diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationHelpOptionsSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationHelpOptionsSnippets.java index 822e2fe81..fc20eec41 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationHelpOptionsSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationHelpOptionsSnippets.java @@ -16,7 +16,7 @@ package org.springframework.shell.docs; import org.springframework.context.annotation.Bean; -import org.springframework.shell.core.command.Command; +import org.springframework.shell.core.command.CommandRegistration; class CommandRegistrationHelpOptionsSnippets { @@ -24,13 +24,15 @@ class Dump1 { // tag::defaults[] @Bean - Command commandRegistration() { - return Command.builder() + CommandRegistration commandRegistration() { + return CommandRegistration.builder() .command("mycommand") - .withHelpOptions(helpOptionsSpec -> helpOptionsSpec.enabled(true) - .longNames("help") - .shortNames('h') - .command("help")) + .withHelpOptions() + .enabled(true) + .longNames("help") + .shortNames('h') + .command("help") + .and() .build(); } // end::defaults[] diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationHiddenSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationHiddenSnippets.java index 178a4d8f6..eea15cc38 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationHiddenSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationHiddenSnippets.java @@ -15,13 +15,13 @@ */ package org.springframework.shell.docs; -import org.springframework.shell.core.command.Command; +import org.springframework.shell.core.command.CommandRegistration; class CommandRegistrationHiddenSnippets { // tag::snippet1[] - Command commandRegistration() { - return Command.builder() + CommandRegistration commandRegistration() { + return CommandRegistration.builder() .command("mycommand") // define as hidden .hidden() diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationInteractionModeSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationInteractionModeSnippets.java index d2ea6e553..eaec45b03 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationInteractionModeSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationInteractionModeSnippets.java @@ -15,14 +15,15 @@ */ package org.springframework.shell.docs; -import org.springframework.shell.core.command.Command; +import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.annotation.Command; import org.springframework.shell.core.context.InteractionMode; public class CommandRegistrationInteractionModeSnippets { // tag::snippet1[] - Command commandRegistration() { - return Command.builder() + CommandRegistration commandRegistration() { + return CommandRegistration.builder() .command("mycommand") // can be defined for all modes .interactionMode(InteractionMode.ALL) @@ -37,8 +38,7 @@ Command commandRegistration() { static class Dump1 { // tag::snippet2[] - @org.springframework.shell.core.command.annotation.Command(command = "mycommand", - interactionMode = InteractionMode.INTERACTIVE) + @Command(command = "mycommand", interactionMode = InteractionMode.INTERACTIVE) public void mycommand() { } // end::snippet2[] diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationSnippets.java index 9050c6b25..e39949563 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrationSnippets.java @@ -15,19 +15,19 @@ */ package org.springframework.shell.docs; -import org.springframework.shell.core.command.Command; +import org.springframework.shell.core.command.CommandRegistration; public class CommandRegistrationSnippets { void dump1() { // tag::snippet1[] - Command.builder().withOption(optionSpec -> optionSpec.longNames("myopt")).build(); + CommandRegistration.builder().withOption().longNames("myopt").and().build(); // end::snippet1[] } void dump2() { // tag::snippet2[] - Command.builder().withOption(optionSpec -> optionSpec.shortNames('s')).build(); + CommandRegistration.builder().withOption().shortNames('s').and().build(); // end::snippet2[] } diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrySnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrySnippets.java index 6207861c6..2ca17e6ee 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrySnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandRegistrySnippets.java @@ -18,9 +18,9 @@ import java.util.ArrayList; import java.util.List; -import org.springframework.shell.core.command.Command; import org.springframework.shell.core.command.CommandRegistry; import org.springframework.shell.core.command.CommandRegistryCustomizer; +import org.springframework.shell.core.command.CommandRegistration; import org.springframework.shell.core.command.CommandResolver; public class CommandRegistrySnippets { @@ -29,7 +29,7 @@ public class CommandRegistrySnippets { void dump1() { // tag::snippet1[] - Command registration = Command.builder().build(); + CommandRegistration registration = CommandRegistration.builder().build(); catalog.register(registration); // end::snippet1[] } @@ -37,15 +37,15 @@ void dump1() { // tag::snippet2[] static class CustomCommandResolver implements CommandResolver { - List registrations = new ArrayList<>(); + List registrations = new ArrayList<>(); CustomCommandResolver() { - Command resolved = Command.builder().command("resolve command").build(); + CommandRegistration resolved = CommandRegistration.builder().command("resolve command").build(); registrations.add(resolved); } @Override - public List resolve() { + public List resolve() { return registrations; } @@ -57,7 +57,7 @@ static class CustomCommandRegistryCustomizer implements CommandRegistryCustomize @Override public void customize(CommandRegistry commandRegistry) { - Command registration = Command.builder().command("resolve command").build(); + CommandRegistration registration = CommandRegistration.builder().command("resolve command").build(); commandRegistry.register(registration); } diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandTargetSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandTargetSnippets.java index 47909df47..fc5a68af1 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandTargetSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandTargetSnippets.java @@ -15,7 +15,7 @@ */ package org.springframework.shell.docs; -import org.springframework.shell.core.command.Command; +import org.springframework.shell.core.command.CommandRegistration; public class CommandTargetSnippets { @@ -32,29 +32,33 @@ String command(String arg) { void dump1() { // tag::snippet12[] CommandPojo pojo = new CommandPojo(); - Command.builder() + CommandRegistration.builder() .command("command") - .withTarget(targetSpec -> targetSpec.method(pojo, "command")) - .withOption(optionSpec -> optionSpec.longNames("arg")) + .withTarget() + .method(pojo, "command") + .and() + .withOption() + .longNames("arg") + .and() .build(); // end::snippet12[] } void dump2() { // tag::snippet2[] - Command.builder().command("command").withTarget(targetSpec -> targetSpec.function(ctx -> { + CommandRegistration.builder().command("command").withTarget().function(ctx -> { String arg = ctx.getOptionValue("arg"); return String.format("hi, arg value is '%s'", arg); - })).withOption(optionSpec -> optionSpec.longNames("arg")).build(); + }).and().withOption().longNames("arg").and().build(); // end::snippet2[] } void dump3() { // tag::snippet3[] - Command.builder().command("command").withTarget(targetSpec -> targetSpec.consumer(ctx -> { + CommandRegistration.builder().command("command").withTarget().consumer(ctx -> { String arg = ctx.getOptionValue("arg"); ctx.getTerminal().writer().println(String.format("hi, arg value is '%s'", arg)); - })).withOption(optionSpec -> optionSpec.longNames("arg")).build(); + }).and().withOption().longNames("arg").and().build(); // end::snippet3[] } diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CompletionSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CompletionSnippets.java index a163642d9..5f1f3156b 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CompletionSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CompletionSnippets.java @@ -21,7 +21,8 @@ import org.springframework.shell.core.completion.CompletionContext; import org.springframework.shell.core.completion.CompletionProposal; -import org.springframework.shell.core.command.Command; +import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.annotation.Command; import org.springframework.shell.core.command.annotation.Option; import org.springframework.shell.core.command.annotation.OptionValues; import org.springframework.shell.core.completion.CompletionProvider; @@ -31,9 +32,9 @@ public class CompletionSnippets { // tag::builder-1[] void dump1() { - Command.builder().withOption(optionSpec -> optionSpec.longNames("arg1").completion(ctx -> { + CommandRegistration.builder().withOption().longNames("arg1").completion(ctx -> { return Arrays.asList("val1", "val2").stream().map(CompletionProposal::new).collect(Collectors.toList()); - })).build(); + }).and().build(); } // end::builder-1[] @@ -62,7 +63,7 @@ public List apply(CompletionContext completionContext) { static class Dump1 { // tag::anno-method[] - @org.springframework.shell.core.command.annotation.Command(command = "complete", description = "complete") + @Command(command = "complete", description = "complete") public String complete(@Option @OptionValues(provider = "myCompletionProvider") String arg1) { return "You said " + arg1; } diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/ErrorHandlingSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/ErrorHandlingSnippets.java index 56739410e..9b9977fd9 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/ErrorHandlingSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/ErrorHandlingSnippets.java @@ -22,8 +22,8 @@ import org.springframework.boot.ExitCodeGenerator; import org.springframework.context.annotation.Bean; import org.springframework.shell.core.command.CommandExceptionResolver; -import org.springframework.shell.core.command.metadata.CommandHandlingResult; -import org.springframework.shell.core.command.Command; +import org.springframework.shell.core.command.CommandHandlingResult; +import org.springframework.shell.core.command.CommandRegistration; import org.springframework.shell.core.command.annotation.ExceptionResolver; import org.springframework.shell.core.command.annotation.ExitCode; @@ -46,7 +46,7 @@ static class CustomExceptionResolver implements CommandExceptionResolver { @Override public CommandHandlingResult resolve(Exception e) { if (e instanceof CustomException) { - return new CommandHandlingResult("Hi, handled exception\n", 42); + return CommandHandlingResult.of("Hi, handled exception\n", 42); } return null; } @@ -56,9 +56,7 @@ public CommandHandlingResult resolve(Exception e) { void dump1() { // tag::example1[] - Command.builder() - .withErrorHandling(errorHandlingSpec -> errorHandlingSpec.resolver(new CustomExceptionResolver())) - .build(); + CommandRegistration.builder().withErrorHandling().resolver(new CustomExceptionResolver()).and().build(); // end::example1[] } @@ -69,7 +67,7 @@ static class Dump1 { CommandHandlingResult errorHandler(Exception e) { // Exception would be type of RuntimeException, // optionally do something with it - return new CommandHandlingResult("Hi, handled exception\n", 42); + return CommandHandlingResult.of("Hi, handled exception\n", 42); } // end::exception-resolver-with-type-in-annotation[] @@ -80,7 +78,7 @@ static class Dump2 { // tag::exception-resolver-with-type-in-method[] @ExceptionResolver CommandHandlingResult errorHandler(RuntimeException e) { - return new CommandHandlingResult("Hi, handled custom exception\n", 42); + return CommandHandlingResult.of("Hi, handled custom exception\n", 42); } // end::exception-resolver-with-type-in-method[] diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/ExitCodeSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/ExitCodeSnippets.java index 033bd89f9..d4dcb68d4 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/ExitCodeSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/ExitCodeSnippets.java @@ -15,7 +15,7 @@ */ package org.springframework.shell.docs; -import org.springframework.shell.core.command.Command; +import org.springframework.shell.core.command.CommandRegistration; public class ExitCodeSnippets { @@ -38,12 +38,12 @@ public int getCode() { void dump1() { // tag::example1[] - Command.builder().withExitCode(exitCodeSpec -> exitCodeSpec.map(MyException.class, 3).map(t -> { + CommandRegistration.builder().withExitCode().map(MyException.class, 3).map(t -> { if (t instanceof MyException) { return ((MyException) t).getCode(); } return 0; - })).build(); + }).and().build(); // end::example1[] } diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionSnippets.java index 549e04deb..a6efd0362 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionSnippets.java @@ -18,9 +18,10 @@ import java.util.Arrays; import org.springframework.context.annotation.Bean; -import org.springframework.shell.core.command.Command; -import org.springframework.shell.core.command.Command.OptionArity; -import org.springframework.shell.core.command.OptionNameModifier; +import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.CommandRegistration.OptionArity; +import org.springframework.shell.core.command.CommandRegistration.OptionNameModifier; +import org.springframework.shell.core.command.annotation.Command; import org.springframework.shell.core.command.annotation.Option; public class OptionSnippets { @@ -28,7 +29,7 @@ public class OptionSnippets { class Dump1 { // tag::option-with-option-annotation[] - @org.springframework.shell.core.command.annotation.Command + @Command public String example(@Option(longNames = "arg") String arg1) { return "Hello " + arg1; } @@ -39,7 +40,7 @@ public String example(@Option(longNames = "arg") String arg1) { class Dump7 { // tag::option-with-annotation-without-prefix[] - @org.springframework.shell.core.command.annotation.Command + @Command public String example(@Option(label = "arg") String arg1) { return "Hello " + arg1; } @@ -50,7 +51,7 @@ public String example(@Option(label = "arg") String arg1) { class Dump2 { // tag::option-without-annotation[] - @org.springframework.shell.core.command.annotation.Command + @Command public String example(String arg1) { return "Hello " + arg1; } @@ -103,27 +104,42 @@ public String example(@Option(defaultValue = "defaultValue") String arg1) { public void dump1() { // tag::option-registration-longarg[] - Command registration = Command.builder().withOption(optionSpec -> optionSpec.longNames("arg1")).build(); + CommandRegistration registration = CommandRegistration.builder().withOption().longNames("arg1").and().build(); // end::option-registration-longarg[] // tag::option-registration-shortarg[] - Command.builder() - .withOption(optionSpec -> optionSpec.shortNames('a')) - .withOption(optionSpec -> optionSpec.shortNames('b')) - .withOption(optionSpec -> optionSpec.shortNames('c')) + CommandRegistration.builder() + .withOption() + .shortNames('a') + .and() + .withOption() + .shortNames('b') + .and() + .withOption() + .shortNames('c') + .and() .build(); // end::option-registration-shortarg[] // tag::option-registration-shortargbooleans[] - Command.builder() - .withOption(optionSpec -> optionSpec.shortNames('a').type(boolean.class)) - .withOption(optionSpec -> optionSpec.shortNames('b').type(boolean.class)) - .withOption(optionSpec -> optionSpec.shortNames('c').type(boolean.class)) + CommandRegistration.builder() + .withOption() + .shortNames('a') + .type(boolean.class) + .and() + .withOption() + .shortNames('b') + .type(boolean.class) + .and() + .withOption() + .shortNames('c') + .type(boolean.class) + .and() .build(); // end::option-registration-shortargbooleans[] // tag::option-registration-arityenum[] - Command.builder().withOption(optionSpec -> optionSpec.longNames("arg1").arity(OptionArity.EXACTLY_ONE)).build(); + CommandRegistration.builder().withOption().longNames("arg1").arity(OptionArity.EXACTLY_ONE).and().build(); // end::option-registration-arityenum[] // // tag::option-registration-arityints[] @@ -136,62 +152,86 @@ public void dump1() { // // end::option-registration-arityints[] // tag::option-registration-aritystrings-sample[] - Command.builder() + CommandRegistration.builder() .command("arity-errors") - .withOption(optionSpec -> optionSpec.longNames("arg1").type(String[].class).required().arity(1, 2)) - .withTarget(targetSpec -> targetSpec.function(ctx -> { + .withOption() + .longNames("arg1") + .type(String[].class) + .required() + .arity(1, 2) + .and() + .withTarget() + .function(ctx -> { String[] arg1 = ctx.getOptionValue("arg1"); return "Hello " + Arrays.asList(arg1); - })) + }) + .and() .build(); // end::option-registration-aritystrings-sample[] // tag::option-registration-aritystrings-position[] - Command.builder() + CommandRegistration.builder() .command("arity-strings-2") - .withOption( - optionSpec -> optionSpec.longNames("arg1").required().type(String[].class).arity(0, 2).position(0)) - .withTarget(targetSpec -> targetSpec.function(ctx -> { + .withOption() + .longNames("arg1") + .required() + .type(String[].class) + .arity(0, 2) + .position(0) + .and() + .withTarget() + .function(ctx -> { String[] arg1 = ctx.getOptionValue("arg1"); return "Hello " + Arrays.asList(arg1); - })) + }) + .and() .build(); // end::option-registration-aritystrings-position[] // tag::option-registration-aritystrings-noposition[] - Command.builder() + CommandRegistration.builder() .command("arity-strings-1") - .withOption(optionSpec -> optionSpec.longNames("arg1").required().type(String[].class).arity(0, 2)) - .withTarget(targetSpec -> targetSpec.function(ctx -> { + .withOption() + .longNames("arg1") + .required() + .type(String[].class) + .arity(0, 2) + .and() + .withTarget() + .function(ctx -> { String[] arg1 = ctx.getOptionValue("arg1"); return "Hello " + Arrays.asList(arg1); - })) + }) + .and() .build(); // end::option-registration-aritystrings-noposition[] // tag::option-registration-optional[] - Command.builder().withOption(optionSpec -> optionSpec.longNames("arg1").required()).build(); + CommandRegistration.builder().withOption().longNames("arg1").required().and().build(); // end::option-registration-optional[] // tag::option-registration-positional[] - Command.builder().withOption(optionSpec -> optionSpec.longNames("arg1").position(0)).build(); + CommandRegistration.builder().withOption().longNames("arg1").position(0).and().build(); // end::option-registration-positional[] // tag::option-registration-default[] - Command.builder().withOption(optionSpec -> optionSpec.longNames("arg1").defaultValue("defaultValue")).build(); + CommandRegistration.builder().withOption().longNames("arg1").defaultValue("defaultValue").and().build(); // end::option-registration-default[] // tag::option-registration-label[] - Command.builder() - .withOption(optionSpec -> optionSpec.longNames("arg1")) - .withOption(optionSpec -> optionSpec.longNames("arg2").label("MYLABEL")) + CommandRegistration.builder() + .withOption() + .longNames("arg1") + .and() + .withOption() + .longNames("arg2") + .label("MYLABEL") + .and() .build(); // end::option-registration-label[] // tag::option-registration-naming-case-req[] - Command.builder() - .withOption(optionSpec -> optionSpec.longNames("arg1").nameModifier(name -> "x" + name)) - .build(); + CommandRegistration.builder().withOption().longNames("arg1").nameModifier(name -> "x" + name).and().build(); // end::option-registration-naming-case-req[] } @@ -205,7 +245,7 @@ OptionNameModifier sampleOptionNameModifier() { // end::option-registration-naming-case-bean[] // tag::option-registration-naming-case-sample1[] - @org.springframework.shell.core.command.annotation.Command(command = "option-naming-sample") + @Command(command = "option-naming-sample") public void optionNamingSample(@Option(description = "from_snake") String snake, @Option(description = "fromCamel") String camel, @Option(description = "from-kebab") String kebab, @Option(description = "FromPascal") String pascal) { @@ -217,14 +257,14 @@ public void optionNamingSample(@Option(description = "from_snake") String snake, static class LegacyAnnotation { // tag::option-registration-zeroorone-legacyannotation[] - @org.springframework.shell.core.command.annotation.Command(command = "example") + @Command(command = "example") String zeroOrOne(@Option(arity = OptionArity.EXACTLY_ONE) String arg) { return String.format("Hi '%s'", arg); } // end::option-registration-zeroorone-legacyannotation[] // tag::option-registration-zerooronewithminmax-legacyannotation[] - @org.springframework.shell.core.command.annotation.Command(command = "example") + @Command(command = "example") String zeroOrOneWithMinMax(@Option(arity = OptionArity.EXACTLY_ONE) String arg) { return String.format("Hi '%s'", arg); } @@ -250,14 +290,14 @@ void defaultOption(@Option(defaultValue = "default") String arg) { static class Annotation { // tag::option-registration-zeroorone-annotation[] - @org.springframework.shell.core.command.annotation.Command(command = "example") + @Command(command = "example") String zeroOrOne(@Option(arity = OptionArity.ZERO_OR_ONE) String arg) { return String.format("Hi '%s'", arg); } // end::option-registration-zeroorone-annotation[] // tag::option-registration-zerooronewithminmax-annotation[] - @org.springframework.shell.core.command.annotation.Command(command = "example") + @Command(command = "example") String zeroOrOneWithMinMax(@Option(arityMin = 0, arityMax = 1) String arg) { return String.format("Hi '%s'", arg); } @@ -288,53 +328,68 @@ void labelOption(@Option(label = "MYLABEL") String arg) { static class Registration { // tag::option-registration-zeroorone-programmatic[] - Command zeroOrOne() { - return Command.builder() + CommandRegistration zeroOrOne() { + return CommandRegistration.builder() .command("example") - .withOption(optionSpec -> optionSpec.longNames("arg").arity(OptionArity.ZERO_OR_ONE)) + .withOption() + .longNames("arg") + .arity(OptionArity.ZERO_OR_ONE) + .and() .build(); } // end::option-registration-zeroorone-programmatic[] // tag::option-registration-zerooronewithminmax-programmatic[] - Command zeroOrOneWithMinMax() { - return Command.builder() + CommandRegistration zeroOrOneWithMinMax() { + return CommandRegistration.builder() .command("example") - .withOption(optionSpec -> optionSpec.longNames("arg").arity(0, 1)) + .withOption() + .longNames("arg") + .arity(0, 1) + .and() .build(); } // end::option-registration-zerooronewithminmax-programmatic[] // tag::option-optional-programmatic[] - Command optionalOption() { - return Command.builder() + CommandRegistration optionalOption() { + return CommandRegistration.builder() .command("optionalOption") - .withOption(optionSpec -> optionSpec.longNames("arg").required(false)) + .withOption() + .longNames("arg") + .required(false) + .and() .build(); } // end::option-optional-programmatic[] // tag::option-mandatory-programmatic[] - Command mandatoryOption() { - return Command.builder() + CommandRegistration mandatoryOption() { + return CommandRegistration.builder() .command("optionalOption") - .withOption(optionSpec -> optionSpec.longNames("arg").required()) + .withOption() + .longNames("arg") + .required() + .and() .build(); } // end::option-mandatory-programmatic[] // tag::option-default-programmatic[] - Command defaultOption() { - return Command.builder() + CommandRegistration defaultOption() { + return CommandRegistration.builder() .command("defaultOption") - .withOption(optionSpec -> optionSpec.longNames("arg").defaultValue("default")) + .withOption() + .longNames("arg") + .defaultValue("default") + .and() .build(); } // end::option-default-programmatic[] // tag::option-label-programmatic[] - Command labelOption() { - return Command.builder().withOption(optionSpec -> optionSpec.longNames("arg").label("MYLABEL")).build(); + CommandRegistration labelOption() { + return CommandRegistration.builder().withOption().longNames("arg").label("MYLABEL").and().build(); } // end::option-label-programmatic[] diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionTypesSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionTypesSnippets.java index d14332e3f..922f4ba4d 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionTypesSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionTypesSnippets.java @@ -15,7 +15,7 @@ */ package org.springframework.shell.docs; -import org.springframework.shell.core.command.Command; +import org.springframework.shell.core.command.CommandRegistration; import org.springframework.shell.core.command.annotation.Option; class OptionTypesSnippets { @@ -32,15 +32,38 @@ String example(@Option() boolean arg1, @Option(defaultValue = "true") boolean ar // end::option-type-boolean-anno[] void dump() { // tag::option-type-boolean-reg[] - Command.builder() + CommandRegistration.builder() .command("example") - .withOption(optionSpec -> optionSpec.longNames("arg1").type(boolean.class)) - .withOption(optionSpec -> optionSpec.longNames("arg2").type(boolean.class).defaultValue("true")) - .withOption(optionSpec -> optionSpec.longNames("arg3").type(boolean.class).defaultValue("false")) - .withOption(optionSpec -> optionSpec.longNames("arg4").type(Boolean.class)) - .withOption(optionSpec -> optionSpec.longNames("arg5").type(Boolean.class).defaultValue("true")) - .withOption(optionSpec -> optionSpec.longNames("arg6").type(Boolean.class).defaultValue("false")) - .withTarget(targetSpec -> targetSpec.function(ctx -> { + .withOption() + .longNames("arg1") + .type(boolean.class) + .and() + .withOption() + .longNames("arg2") + .type(boolean.class) + .defaultValue("true") + .and() + .withOption() + .longNames("arg3") + .type(boolean.class) + .defaultValue("false") + .and() + .withOption() + .longNames("arg4") + .type(Boolean.class) + .and() + .withOption() + .longNames("arg5") + .type(Boolean.class) + .defaultValue("true") + .and() + .withOption() + .longNames("arg6") + .type(Boolean.class) + .defaultValue("false") + .and() + .withTarget() + .function(ctx -> { boolean arg1 = ctx.hasMappedOption("arg1") ? ctx.getOptionValue("arg1") : false; boolean arg2 = ctx.getOptionValue("arg2"); boolean arg3 = ctx.getOptionValue("arg3"); @@ -49,7 +72,8 @@ void dump() { Boolean arg6 = ctx.getOptionValue("arg6"); return String.format("Hello arg1=%s arg2=%s arg3=%s arg4=%s arg5=%s arg6=%s", arg1, arg2, arg3, arg4, arg5, arg6); - })) + }) + .and() .build(); // end::option-type-boolean-reg[] } @@ -66,13 +90,19 @@ String example(@Option(description = "arg1") int arg1) { // end::option-type-integer-anno[] void dump() { // tag::option-type-integer-reg[] - Command.builder() + CommandRegistration.builder() .command("example") - .withOption(optionSpec -> optionSpec.longNames("arg1").type(int.class).required()) - .withTarget(targetSpec -> targetSpec.function(ctx -> { + .withOption() + .longNames("arg1") + .type(int.class) + .required() + .and() + .withTarget() + .function(ctx -> { boolean arg1 = ctx.getOptionValue("arg1"); return "Hello " + arg1; - })) + }) + .and() .build(); // end::option-type-integer-reg[] } @@ -89,13 +119,19 @@ String example(@Option(description = "arg1") String arg1) { // end::option-type-string-anno[] void dump() { // tag::option-type-string-reg[] - Command.builder() + CommandRegistration.builder() .command("example") - .withOption(optionSpec -> optionSpec.longNames("arg1").type(String.class).required()) - .withTarget(targetSpec -> targetSpec.function(ctx -> { + .withOption() + .longNames("arg1") + .type(String.class) + .required() + .and() + .withTarget() + .function(ctx -> { String arg1 = ctx.getOptionValue("arg1"); return "Hello " + arg1; - })) + }) + .and() .build(); // end::option-type-string-reg[] } @@ -120,13 +156,19 @@ String example(@Option(description = "arg1") OptionTypeEnum arg1) { // end::option-type-enum-anno[] void dump() { // tag::option-type-enum-reg[] - Command.builder() + CommandRegistration.builder() .command("example") - .withOption(optionSpec -> optionSpec.longNames("arg1").type(OptionTypeEnum.class).required()) - .withTarget(targetSpec -> targetSpec.function(ctx -> { + .withOption() + .longNames("arg1") + .type(OptionTypeEnum.class) + .required() + .and() + .withTarget() + .function(ctx -> { OptionTypeEnum arg1 = ctx.getOptionValue("arg1"); return "Hello " + arg1; - })) + }) + .and() .build(); // end::option-type-enum-reg[] } @@ -143,13 +185,19 @@ String example(@Option(description = "arg1") String[] arg1) { // end::option-type-string-array-anno[] void dump() { // tag::option-type-string-array-reg[] - Command.builder() + CommandRegistration.builder() .command("example") - .withOption(optionSpec -> optionSpec.longNames("arg1").type(String[].class).required()) - .withTarget(targetSpec -> targetSpec.function(ctx -> { + .withOption() + .longNames("arg1") + .type(String[].class) + .required() + .and() + .withTarget() + .function(ctx -> { String[] arg1 = ctx.getOptionValue("arg1"); return "Hello " + arg1; - })) + }) + .and() .build(); // end::option-type-string-array-reg[] } diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/ShortOptionSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/ShortOptionSnippets.java index 0b775379d..4b802dd5f 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/ShortOptionSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/ShortOptionSnippets.java @@ -15,7 +15,8 @@ */ package org.springframework.shell.docs; -import org.springframework.shell.core.command.Command; +import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.annotation.Command; import org.springframework.shell.core.command.annotation.Option; public class ShortOptionSnippets { @@ -24,14 +25,14 @@ public class ShortOptionSnippets { static class Annotation { // tag::option-type-string-annotation[] - @org.springframework.shell.core.command.annotation.Command(command = "example") + @Command(command = "example") String stringWithShortOption(@Option(longNames = "arg", shortNames = 'a', required = true) String arg) { return String.format("Hi '%s'", arg); } // end::option-type-string-annotation[] // tag::option-type-multiple-booleans-annotation[] - @org.springframework.shell.core.command.annotation.Command(command = "example") + @Command(command = "example") public String multipleBooleans(@Option(shortNames = 'a') boolean a, @Option(shortNames = 'b') boolean b, @Option(shortNames = 'c') boolean c) { return String.format("Hi a='%s' b='%s' c='%s'", a, b, c); @@ -43,25 +44,38 @@ public String multipleBooleans(@Option(shortNames = 'a') boolean a, @Option(shor static class Registration { // tag::option-type-string-programmatic[] - Command stringWithShortOption() { - return Command.builder().command("example").withTarget(targetSpec -> targetSpec.function(ctx -> { + CommandRegistration stringWithShortOption() { + return CommandRegistration.builder().command("example").withTarget().function(ctx -> { String arg = ctx.hasMappedOption("arg") ? ctx.getOptionValue("arg") : null; return String.format("Hi arg='%s'", arg); - })).withOption(optionSpec -> optionSpec.longNames("arg").shortNames('a').required()).build(); + }).and().withOption().longNames("arg").shortNames('a').required().and().build(); } // end::option-type-string-programmatic[] // tag::option-type-multiple-booleans-programmatic[] - Command multipleBooleans() { - return Command.builder().command("example").withTarget(targetSpec -> targetSpec.function(ctx -> { + CommandRegistration multipleBooleans() { + return CommandRegistration.builder().command("example").withTarget().function(ctx -> { Boolean a = ctx.hasMappedOption("a") ? ctx.getOptionValue("a") : null; Boolean b = ctx.hasMappedOption("b") ? ctx.getOptionValue("b") : null; Boolean c = ctx.hasMappedOption("c") ? ctx.getOptionValue("c") : null; return String.format("Hi a='%s' b='%s' c='%s'", a, b, c); - })) - .withOption(optionSpec -> optionSpec.shortNames('a').type(boolean.class).defaultValue("false")) - .withOption(optionSpec -> optionSpec.shortNames('b').type(boolean.class).defaultValue("false")) - .withOption(optionSpec -> optionSpec.shortNames('c').type(boolean.class).defaultValue("false")) + }) + .and() + .withOption() + .shortNames('a') + .type(boolean.class) + .defaultValue("false") + .and() + .withOption() + .shortNames('b') + .type(boolean.class) + .defaultValue("false") + .and() + .withOption() + .shortNames('c') + .type(boolean.class) + .defaultValue("false") + .and() .build(); } // end::option-type-multiple-booleans-programmatic[] diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/WritingSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/WritingSnippets.java index 30043769a..eb863a0db 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/WritingSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/WritingSnippets.java @@ -18,8 +18,9 @@ import org.jline.terminal.Terminal; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.shell.core.command.Command; import org.springframework.shell.core.command.CommandContext; +import org.springframework.shell.core.command.CommandRegistration; +import org.springframework.shell.core.command.annotation.Command; class WritingSnippets { @@ -29,7 +30,7 @@ class Dump1 { @Autowired Terminal terminal; - @org.springframework.shell.core.command.annotation.Command + @Command public void example() { terminal.writer().println("hi"); terminal.writer().flush(); @@ -41,7 +42,7 @@ public void example() { class Dump2 { // tag::anno-terminal-writer[] - @org.springframework.shell.core.command.annotation.Command + @Command public void example(CommandContext ctx) { ctx.getTerminal().writer().println("hi"); ctx.getTerminal().writer().flush(); @@ -52,10 +53,10 @@ public void example(CommandContext ctx) { void dump1() { // tag::reg-terminal-writer[] - Command.builder().command("example").withTarget(targetSpec -> targetSpec.consumer(ctx -> { + CommandRegistration.builder().command("example").withTarget().consumer(ctx -> { ctx.getTerminal().writer().println("hi"); ctx.getTerminal().writer().flush(); - })).build(); + }).and().build(); // end::reg-terminal-writer[] } diff --git a/spring-shell-samples/spring-shell-sample-hello-world/src/main/java/org/springframework/shell/samples/helloworld/SpringShellApplication.java b/spring-shell-samples/spring-shell-sample-hello-world/src/main/java/org/springframework/shell/samples/helloworld/SpringShellApplication.java index 4719c33ea..7bf7bd380 100644 --- a/spring-shell-samples/spring-shell-sample-hello-world/src/main/java/org/springframework/shell/samples/helloworld/SpringShellApplication.java +++ b/spring-shell-samples/spring-shell-sample-hello-world/src/main/java/org/springframework/shell/samples/helloworld/SpringShellApplication.java @@ -55,7 +55,7 @@ public AbstractCommand sayGoodMorning() { return org.springframework.shell.core.command.Command.builder() .name("good-morning") .description("Say good morning") - .aliases("greetings") + .group("greetings") .help("A command that greets the user with 'Good morning!'") .execute(commandContext -> { String ansiString = new AttributedStringBuilder().append("Good morning ") From 932b5936a6e5a0e514327d069fce83f44679fc57 Mon Sep 17 00:00:00 2001 From: Piotr Olaszewski Date: Mon, 1 Dec 2025 20:35:55 +0100 Subject: [PATCH 4/6] Cleanup Signed-off-by: Piotr Olaszewski --- .../shell/core/command/Command.java | 123 +++++++++++------- .../core/command/DefaultCommandBuilder.java | 94 ------------- .../helloworld/SpringShellApplication.java | 16 ++- 3 files changed, 86 insertions(+), 147 deletions(-) delete mode 100644 spring-shell-core/src/main/java/org/springframework/shell/core/command/DefaultCommandBuilder.java diff --git a/spring-shell-core/src/main/java/org/springframework/shell/core/command/Command.java b/spring-shell-core/src/main/java/org/springframework/shell/core/command/Command.java index fecea40db..ca51d4a1c 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/core/command/Command.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/core/command/Command.java @@ -15,11 +15,18 @@ */ package org.springframework.shell.core.command; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Consumer; +import java.util.function.Function; + +import org.jspecify.annotations.Nullable; import org.springframework.shell.core.commands.AbstractCommand; +import org.springframework.shell.core.commands.adapter.ConsumerCommandAdapter; +import org.springframework.shell.core.commands.adapter.FunctionCommandAdapter; +import org.springframework.util.Assert; /** * @author Eric Bottard @@ -85,60 +92,78 @@ default List getAliases() { * @return a new {@code Builder} instance for configuring and creating commands */ static Builder builder() { - return new DefaultCommandBuilder(); + return new Builder(); } /** * Builder for creating command. */ - interface Builder { - - /** - * Set the name of the command. - * @return this builder - */ - Builder name(String name); - - /** - * Set the description of the command. - * @return this builder - */ - Builder description(String description); - - /** - * Set the help of the command. - * @return this builder - */ - Builder help(String help); - - /** - * Set the group of the command. - * @return this builder - */ - Builder group(String group); - - /** - * Set the aliases of the command. - * @return this builder - */ - Builder aliases(String... aliases); - - /** - * Set the aliases of the command. - * @return this builder - */ - Builder aliases(List aliases); - - /** - * Set command execution logic. - * @return this builder - */ - Builder execute(Consumer commandExecutor); - - /** - * Build the {@link AbstractCommand}. - */ - AbstractCommand build(); + final class Builder { + + private @Nullable String name; + + private @Nullable String description; + + private String group = ""; + + private String help = ""; + + private @Nullable List aliases; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder description(String description) { + this.description = description; + return this; + } + + public Builder help(String help) { + this.help = help; + return this; + } + + public Builder group(String group) { + this.group = group; + return this; + } + + public Builder aliases(String... aliases) { + this.aliases = Arrays.asList(aliases); + return this; + } + + public Command execute(Consumer commandExecutor) { + Assert.hasText(name, "'name' must be specified"); + Assert.hasText(description, "description"); + + ConsumerCommandAdapter command = new ConsumerCommandAdapter(name, description, group, help, + commandExecutor); + + initAliases(command); + + return command; + } + + public Command execute(Function commandExecutor) { + Assert.hasText(name, "'name' must be specified"); + Assert.hasText(description, "'description' must be specified"); + + FunctionCommandAdapter command = new FunctionCommandAdapter(name, description, group, help, + commandExecutor); + + initAliases(command); + + return command; + } + + private void initAliases(AbstractCommand command) { + if (aliases != null) { + command.setAliases(aliases); + } + } } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/core/command/DefaultCommandBuilder.java b/spring-shell-core/src/main/java/org/springframework/shell/core/command/DefaultCommandBuilder.java deleted file mode 100644 index 560fc4945..000000000 --- a/spring-shell-core/src/main/java/org/springframework/shell/core/command/DefaultCommandBuilder.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.springframework.shell.core.command; - -import java.util.Arrays; -import java.util.List; -import java.util.function.Consumer; - -import org.jspecify.annotations.Nullable; - -import org.springframework.shell.core.commands.AbstractCommand; -import org.springframework.shell.core.commands.adapter.ConsumerCommandAdapter; -import org.springframework.util.Assert; -import static org.springframework.shell.core.command.Command.*; - -/** - * Default implementation of {@link Builder}. - * - * @author Piotr Olaszewski - */ -class DefaultCommandBuilder implements Builder { - - private @Nullable String name; - - private @Nullable String description; - - private String group = ""; - - private String help = ""; - - private @Nullable List aliases; - - private @Nullable Consumer commandContextConsumer; - - @Override - public Builder name(String name) { - this.name = name; - return this; - } - - @Override - public Builder description(String description) { - this.description = description; - return this; - } - - @Override - public Builder help(String help) { - this.help = help; - return this; - } - - @Override - public Builder group(String group) { - this.group = group; - return this; - } - - @Override - public Builder aliases(String... aliases) { - this.aliases = Arrays.asList(aliases); - return this; - } - - @Override - public Builder aliases(List aliases) { - this.aliases = aliases; - return this; - } - - @Override - public Builder execute(Consumer commandExecutor) { - this.commandContextConsumer = commandExecutor; - return this; - } - - @Override - public AbstractCommand build() { - ConsumerCommandAdapter abstractCommand = initCommand(); - - if (aliases != null) { - abstractCommand.setAliases(aliases); - } - - return abstractCommand; - } - - private ConsumerCommandAdapter initCommand() { - Assert.hasText(name, "'name' must be specified"); - Assert.hasText(description, "description"); - Assert.notNull(commandContextConsumer, "'commandExecutor' must not be null"); - - return new ConsumerCommandAdapter(name, description, group, help, commandContextConsumer); - } - -} diff --git a/spring-shell-samples/spring-shell-sample-hello-world/src/main/java/org/springframework/shell/samples/helloworld/SpringShellApplication.java b/spring-shell-samples/spring-shell-sample-hello-world/src/main/java/org/springframework/shell/samples/helloworld/SpringShellApplication.java index 7bf7bd380..7fc6fc4d9 100644 --- a/spring-shell-samples/spring-shell-sample-hello-world/src/main/java/org/springframework/shell/samples/helloworld/SpringShellApplication.java +++ b/spring-shell-samples/spring-shell-sample-hello-world/src/main/java/org/springframework/shell/samples/helloworld/SpringShellApplication.java @@ -15,7 +15,6 @@ import org.springframework.shell.core.command.annotation.Command; import org.springframework.shell.core.command.annotation.EnableCommand; import org.springframework.shell.core.command.annotation.Option; -import org.springframework.shell.core.commands.AbstractCommand; import static org.jline.utils.AttributedStyle.*; @EnableCommand(SpringShellApplication.class) @@ -51,7 +50,7 @@ public void sayYo(CommandContext commandContext) { } @Bean - public AbstractCommand sayGoodMorning() { + public org.springframework.shell.core.command.Command sayGoodMorning() { return org.springframework.shell.core.command.Command.builder() .name("good-morning") .description("Say good morning") @@ -66,8 +65,17 @@ public AbstractCommand sayGoodMorning() { Terminal terminal = commandContext.terminal(); terminal.writer().println(ansiString); terminal.flush(); - }) - .build(); + }); + } + + @Bean + public org.springframework.shell.core.command.Command sayGoodDay() { + return org.springframework.shell.core.command.Command.builder() + .name("good-day") + .description("Say good day") + .group("greetings") + .help("A command that greets the user with 'Good day!'") + .execute(commandContext -> "Good day!"); } @Bean From 21a789e59e3aa6f47fa1e843ceabe258be4e783a Mon Sep 17 00:00:00 2001 From: Piotr Olaszewski Date: Mon, 1 Dec 2025 21:38:04 +0100 Subject: [PATCH 5/6] Cleanup Signed-off-by: Piotr Olaszewski --- .../annotation/support/EnableCommandRegistrar.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/spring-shell-core/src/main/java/org/springframework/shell/core/command/annotation/support/EnableCommandRegistrar.java b/spring-shell-core/src/main/java/org/springframework/shell/core/command/annotation/support/EnableCommandRegistrar.java index 08060efce..72e2740ec 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/core/command/annotation/support/EnableCommandRegistrar.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/core/command/annotation/support/EnableCommandRegistrar.java @@ -17,7 +17,6 @@ import java.io.IOException; import java.lang.reflect.Method; -import java.util.Arrays; import java.util.Set; import org.jline.reader.LineReader; @@ -62,7 +61,6 @@ public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionR // register user defined commands Class[] candidateClasses = shellAnnotation.value(); for (Class candidateClass : candidateClasses) { - registerCommands(candidateClass, registry); registerAnnotatedMethods(candidateClass, registry); } @@ -108,16 +106,6 @@ public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionR } } - private void registerCommands(Class candidateClass, BeanDefinitionRegistry registry) { - Arrays.stream(candidateClass.getDeclaredMethods()) - .filter(method -> method.getReturnType().equals(org.springframework.shell.core.command.Command.class)) - .forEach(method -> { - RootBeanDefinition beanDefinition = new RootBeanDefinition( - org.springframework.shell.core.command.Command.class); - registry.registerBeanDefinition(method.getName(), beanDefinition); - }); - } - private void registerAnnotatedMethods(Class candidateClass, BeanDefinitionRegistry registry) { ReflectionUtils.MethodFilter filter = method -> AnnotatedElementUtils.hasAnnotation(method, Command.class); Set methods = MethodIntrospector.selectMethods(candidateClass, filter); From 7a70ff57679a4dd9c6ef94d2fdb36382206b35e4 Mon Sep 17 00:00:00 2001 From: Piotr Olaszewski Date: Mon, 1 Dec 2025 21:40:45 +0100 Subject: [PATCH 6/6] Cleanup Signed-off-by: Piotr Olaszewski --- .../core/command/annotation/support/EnableCommandRegistrar.java | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-shell-core/src/main/java/org/springframework/shell/core/command/annotation/support/EnableCommandRegistrar.java b/spring-shell-core/src/main/java/org/springframework/shell/core/command/annotation/support/EnableCommandRegistrar.java index 72e2740ec..c82906078 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/core/command/annotation/support/EnableCommandRegistrar.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/core/command/annotation/support/EnableCommandRegistrar.java @@ -44,6 +44,7 @@ * * @author Janne Valkealahti * @author Mahmoud Ben Hassine + * @author Piotr Olaszewski */ public final class EnableCommandRegistrar implements ImportBeanDefinitionRegistrar {