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/Command.java b/spring-shell-core/src/main/java/org/springframework/shell/core/command/Command.java index 0f4c1e324..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 @@ -13,11 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - 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 @@ -74,4 +83,88 @@ 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 Builder(); + } + + /** + * Builder for creating command. + */ + 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/annotation/support/EnableCommandRegistrar.java b/spring-shell-core/src/main/java/org/springframework/shell/core/command/annotation/support/EnableCommandRegistrar.java index 08060efce..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 @@ -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; @@ -45,6 +44,7 @@ * * @author Janne Valkealahti * @author Mahmoud Ben Hassine + * @author Piotr Olaszewski */ public final class EnableCommandRegistrar implements ImportBeanDefinitionRegistrar { @@ -62,7 +62,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 +107,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); 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..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 @@ -3,13 +3,19 @@ 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 static org.jline.utils.AttributedStyle.*; @EnableCommand(SpringShellApplication.class) public class SpringShellApplication { @@ -43,6 +49,35 @@ public void sayYo(CommandContext commandContext) { terminal.writer().println("Yo there! what's up?"); } + @Bean + public org.springframework.shell.core.command.Command sayGoodMorning() { + return org.springframework.shell.core.command.Command.builder() + .name("good-morning") + .description("Say good morning") + .group("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(); + }); + } + + @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 public HelloCommand sayHello() { return new HelloCommand();