From 2d1bac1f9ab0f53734c24689510c631a0f573380 Mon Sep 17 00:00:00 2001 From: heavynimbus Date: Wed, 2 Oct 2024 00:50:03 +0200 Subject: [PATCH] feat: command registration builder with lambdas --- .../shell/command/CommandRegistration.java | 98 +++++++++++ .../samples/standard/FunctionCommands.java | 165 +++++++++++++++--- 2 files changed, 235 insertions(+), 28 deletions(-) diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandRegistration.java b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandRegistration.java index 2f1b0edc1..586663a34 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandRegistration.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandRegistration.java @@ -750,6 +750,15 @@ public interface Builder { */ OptionSpec withOption(); + /** + * Define an option what this command should user for. Can be used multiple + * times. + * + * @param customizer the option spec consumer + * @return option spec for chaining + */ + Builder withOption(Consumer customizer); + /** * Define a target what this command should execute * @@ -757,6 +766,14 @@ public interface Builder { */ TargetSpec withTarget(); + /** + * Define a target what this command should execute + * + * @param customizer the target spec consumer + * @return target spec for chaining + */ + Builder withTarget(Consumer customizer); + /** * Define an alias what this command should execute * @@ -764,6 +781,14 @@ public interface Builder { */ AliasSpec withAlias(); + /** + * Define an alias what this command should execute + * + * @param customizer the alias spec consumer + * @return alias spec for chaining + */ + Builder withAlias(Consumer customizer); + /** * Define an exit code what this command should execute * @@ -771,6 +796,14 @@ public interface Builder { */ ExitCodeSpec withExitCode(); + /** + * Define an exit code what this command should execute + * + * @param customizer the exit code spec consumer + * @return exit code spec for chaining + */ + Builder withExitCode(Consumer customizer); + /** * Define an error handling what this command should use * @@ -778,6 +811,14 @@ public interface Builder { */ ErrorHandlingSpec withErrorHandling(); + /** + * Define an error handling what this command should use + * + * @param customizer the error handling spec consumer + * @return error handling spec for chaining + */ + Builder withErrorHandling(Consumer customizer); + /** * Define help options what this command should use. * @@ -785,6 +826,14 @@ public interface Builder { */ HelpOptionsSpec withHelpOptions(); + /** + * Define help options what this command should use. + * + * @param customizer the help options spec consumer + * @return help options spec for chaining + */ + Builder withHelpOptions(Consumer customizer); + /** * Builds a {@link CommandRegistration}. * @@ -1407,6 +1456,14 @@ public OptionSpec withOption() { return spec; } + @Override + public Builder withOption(Consumer customizer) { + DefaultOptionSpec spec = new DefaultOptionSpec(this); + customizer.accept(spec); + optionSpecs.add(spec); + return this; + } + @Override public TargetSpec withTarget() { DefaultTargetSpec spec = new DefaultTargetSpec(this); @@ -1414,6 +1471,14 @@ public TargetSpec withTarget() { return spec; } + @Override + public Builder withTarget(Consumer customizer) { + DefaultTargetSpec spec = new DefaultTargetSpec(this); + customizer.accept(spec); + targetSpec = spec; + return this; + } + @Override public AliasSpec withAlias() { DefaultAliasSpec spec = new DefaultAliasSpec(this); @@ -1421,6 +1486,14 @@ public AliasSpec withAlias() { return spec; } + @Override + public Builder withAlias(Consumer customizer) { + DefaultAliasSpec spec = new DefaultAliasSpec(this); + customizer.accept(spec); + this.aliasSpecs.add(spec); + return this; + } + @Override public ExitCodeSpec withExitCode() { DefaultExitCodeSpec spec = new DefaultExitCodeSpec(this); @@ -1428,6 +1501,14 @@ public ExitCodeSpec withExitCode() { return spec; } + @Override + public Builder withExitCode(Consumer customizer) { + DefaultExitCodeSpec spec = new DefaultExitCodeSpec(this); + customizer.accept(spec); + this.exitCodeSpec = spec; + return this; + } + @Override public ErrorHandlingSpec withErrorHandling() { DefaultErrorHandlingSpec spec = new DefaultErrorHandlingSpec(this); @@ -1435,6 +1516,14 @@ public ErrorHandlingSpec withErrorHandling() { return spec; } + @Override + public Builder withErrorHandling(Consumer customizer) { + DefaultErrorHandlingSpec spec = new DefaultErrorHandlingSpec(this); + customizer.accept(spec); + this.errorHandlingSpec = spec; + return this; + } + @Override public HelpOptionsSpec withHelpOptions() { if (this.helpOptionsSpec == null) { @@ -1443,6 +1532,15 @@ public HelpOptionsSpec withHelpOptions() { return this.helpOptionsSpec; } + @Override + public Builder withHelpOptions(Consumer customizer) { + if (this.helpOptionsSpec == null) { + this.helpOptionsSpec = new DefaultHelpOptionsSpec(this); + } + customizer.accept(this.helpOptionsSpec); + return this; + } + @Override public CommandRegistration build() { Assert.notNull(commands, "command cannot be empty"); diff --git a/spring-shell-samples/spring-shell-sample-commands/src/main/java/org/springframework/shell/samples/standard/FunctionCommands.java b/spring-shell-samples/spring-shell-sample-commands/src/main/java/org/springframework/shell/samples/standard/FunctionCommands.java index 1912d6288..2db749708 100644 --- a/spring-shell-samples/spring-shell-sample-commands/src/main/java/org/springframework/shell/samples/standard/FunctionCommands.java +++ b/spring-shell-samples/spring-shell-sample-commands/src/main/java/org/springframework/shell/samples/standard/FunctionCommands.java @@ -17,6 +17,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.shell.command.CommandHandlingResult; import org.springframework.shell.command.CommandRegistration; @Configuration @@ -25,28 +26,28 @@ public class FunctionCommands { @Bean public CommandRegistration commandRegistration1() { return CommandRegistration.builder() - .command("function", "command1") - .description("function sample") - .group("Function Commands") - .withTarget() + .command("function", "command1") + .description("function sample") + .group("Function Commands") + .withTarget() .function(ctx -> { String arg1 = ctx.getOptionValue("arg1"); return String.format("hi, arg1 value is '%s'", arg1); }) .and() - .withOption() + .withOption() .longNames("arg1") .and() - .build(); + .build(); } @Bean public CommandRegistration commandRegistration2() { return CommandRegistration.builder() - .command("function", "command2") - .description("function sample") - .group("Function Commands") - .withTarget() + .command("function", "command2") + .description("function sample") + .group("Function Commands") + .withTarget() .function(ctx -> { Boolean a = ctx.getOptionValue("a"); Boolean b = ctx.getOptionValue("b"); @@ -54,55 +55,163 @@ public CommandRegistration commandRegistration2() { return String.format("hi, boolean values for a, b, c are '%s' '%s' '%s'", a, b, c); }) .and() - .withOption() + .withOption() .shortNames('a') .type(boolean.class) .and() - .withOption() + .withOption() .shortNames('b') .type(boolean.class) .and() - .withOption() + .withOption() .shortNames('c') .type(boolean.class) .and() - .build(); + .build(); } @Bean public CommandRegistration commandRegistration3() { return CommandRegistration.builder() - .command("function", "command3") - .description("function sample") - .group("Function Commands") - .withTarget() + .command("function", "command3") + .description("function sample") + .group("Function Commands") + .withTarget() .consumer(ctx -> { String arg1 = ctx.getOptionValue("arg1"); ctx.getTerminal().writer() - .println(String.format("hi, arg1 value is '%s'", arg1)); + .println(String.format("hi, arg1 value is '%s'", arg1)); }) .and() - .withOption() + .withOption() .longNames("arg1") .and() - .build(); + .build(); } @Bean public CommandRegistration commandRegistration4() { return CommandRegistration.builder() - .command("function", "command4") - .description("function sample") - .group("Function Commands") - .withTarget() + .command("function", "command4") + .description("function sample") + .group("Function Commands") + .withTarget() .consumer(ctx -> { ctx.getTerminal().writer() - .println(String.format("hi, command is '%s'", ctx.getCommandRegistration().getCommand())); + .println(String.format("hi, command is '%s'", ctx.getCommandRegistration().getCommand())); }) .and() - .withOption() + .withOption() .longNames("arg1") .and() - .build(); + .build(); + } + + @Bean + public CommandRegistration commandRegistration5() { + return CommandRegistration.builder() + .command("function", "command5") + .description("function sample") + .group("Function Commands") + .withTarget(targetSpec -> { + targetSpec.function(ctx -> { + String arg1 = ctx.getOptionValue("arg1"); + return String.format("hi, arg1 value is '%s'", arg1); + }); + }) + .withOption(optionSpec -> { + optionSpec.longNames("arg1"); + }) + .build(); + } + + @Bean + public CommandRegistration commandRegistration6() { + return CommandRegistration.builder() + .command("function", "command6") + .description("function sample") + .group("Function Commands") + .withTarget(targetSpec -> { + targetSpec.function(ctx -> { + Boolean a = ctx.getOptionValue("a"); + Boolean b = ctx.getOptionValue("b"); + Boolean c = ctx.getOptionValue("c"); + return String.format("hi, boolean values for a, b, c are '%s' '%s' '%s'", a, b, c); + }); + }) + .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(); + } + + + @Bean + public CommandRegistration commandRegistration7() { + return CommandRegistration.builder() + .command("function", "command7") + .description("function sample") + .group("Function Commands") + .withTarget(targetSpec -> { + targetSpec.consumer(ctx -> { + String arg1 = ctx.getOptionValue("arg1"); + ctx.getTerminal().writer() + .println(String.format("hi, arg1 value is '%s'", arg1)); + }); + }) + .withOption(optionSpec -> { + optionSpec.longNames("arg1"); + }) + .build(); + } + + @Bean + public CommandRegistration commandRegistration8() { + return CommandRegistration.builder() + .command("function", "command8") + .description("function sample") + .group("Function Commands") + .withTarget(targetSpec -> { + targetSpec.consumer(ctx -> { + ctx.getTerminal().writer() + .println(String.format("hi, command is '%s'", ctx.getCommandRegistration().getCommand())); + }); + }) + .withOption(optionSpec -> { + optionSpec.longNames("arg1"); + }) + .build(); + } + + @Bean + public CommandRegistration commandRegistration9() { + return CommandRegistration.builder() + .command("function", "command9") + .description("function sample") + .group("Function Commands") + .withTarget(targetSpec -> { + targetSpec.function(ctx -> { + throw new RuntimeException("Something went wrong"); + }); + }) + .withAlias( + aliasSpec -> { + aliasSpec.command("function", "command10"); + } + ) + .withErrorHandling( + errorHandlingSpec -> { + errorHandlingSpec.resolver((e) -> { + return CommandHandlingResult.of("Error handled: " + e.getMessage() + "\n"); + }); + } + ) + .build(); } }