diff --git a/cloud-spring/src/main/java/org/incendo/cloud/spring/CommandSenderSupplier.java b/cloud-spring/src/main/java/org/incendo/cloud/spring/CommandSenderMapper.java similarity index 78% rename from cloud-spring/src/main/java/org/incendo/cloud/spring/CommandSenderSupplier.java rename to cloud-spring/src/main/java/org/incendo/cloud/spring/CommandSenderMapper.java index dfc1dcd..f25212e 100644 --- a/cloud-spring/src/main/java/org/incendo/cloud/spring/CommandSenderSupplier.java +++ b/cloud-spring/src/main/java/org/incendo/cloud/spring/CommandSenderMapper.java @@ -25,14 +25,17 @@ import org.apiguardian.api.API; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.springframework.shell.command.CommandContext; @API(status = API.Status.STABLE, since = "1.0.0") -public interface CommandSenderSupplier { +public interface CommandSenderMapper { /** - * Supplies the command sender. + * Maps the given {@code context} to a command sender of type {@link C}. * + * @param context the context, will be {@code null} during completions * @return the sender */ - @NonNull C supply(); + @NonNull C map(@Nullable CommandContext context); } diff --git a/cloud-spring/src/main/java/org/incendo/cloud/spring/SpringCommandManager.java b/cloud-spring/src/main/java/org/incendo/cloud/spring/SpringCommandManager.java index 466e17c..cafdf28 100644 --- a/cloud-spring/src/main/java/org/incendo/cloud/spring/SpringCommandManager.java +++ b/cloud-spring/src/main/java/org/incendo/cloud/spring/SpringCommandManager.java @@ -61,7 +61,7 @@ public class SpringCommandManager extends CommandManager implements Comple private static final Logger LOGGER = LoggerFactory.getLogger(SpringCommandManager.class); private final SpringCommandPermissionHandler commandPermissionHandler; - private final CommandSenderSupplier commandSenderSupplier; + private final CommandSenderMapper commandSenderMapper; private final SuggestionFactory suggestionFactory; /** @@ -70,17 +70,17 @@ public class SpringCommandManager extends CommandManager implements Comple * @param commandExecutionCoordinatorResolver the resolver for the execution coordinator * @param commandPermissionHandler the permission handler * @param commandRegistrationHandler the registration handler - * @param commandSenderSupplier the supplier of the custom command sender type + * @param commandSenderMapper the mapper for the custom command sender type */ public SpringCommandManager( final @NonNull SpringCommandExecutionCoordinatorResolver commandExecutionCoordinatorResolver, final @NonNull SpringCommandPermissionHandler commandPermissionHandler, final @NonNull SpringCommandRegistrationHandler commandRegistrationHandler, - final @NonNull CommandSenderSupplier commandSenderSupplier + final @NonNull CommandSenderMapper commandSenderMapper ) { super(commandExecutionCoordinatorResolver, commandRegistrationHandler); this.commandPermissionHandler = commandPermissionHandler; - this.commandSenderSupplier = commandSenderSupplier; + this.commandSenderMapper = commandSenderMapper; this.suggestionFactory = super.suggestionFactory().mapped(CloudCompletionProposal::fromSuggestion); this.registerDefaultExceptionHandlers(); @@ -94,7 +94,7 @@ public final boolean hasPermission(final @NonNull C sender, final @NonNull Strin @EventListener(CommandExecutionEvent.class) void commandExecutionEvent(final @NonNull CommandExecutionEvent event) { final CommandInput commandInput = CommandInput.of(Arrays.asList(event.context().getRawArgs())); - this.executeCommand(this.commandSenderSupplier.supply(), commandInput.input()); + this.executeCommand(this.commandSenderMapper.map(event.context()), commandInput.input()); } @Override @@ -109,7 +109,7 @@ void commandExecutionEvent(final @NonNull CommandExecutionEvent event) { strings.addAll(completionContext.getWords()); final String input = String.join(" ", strings); - return this.suggestionFactory().suggestImmediately(this.commandSenderSupplier.supply(), input).stream() + return this.suggestionFactory().suggestImmediately(this.commandSenderMapper.map(null), input).stream() .map(suggestion -> (CompletionProposal) suggestion) .toList(); } diff --git a/cloud-spring/src/main/java/org/incendo/cloud/spring/SpringCommandSender.java b/cloud-spring/src/main/java/org/incendo/cloud/spring/SpringCommandSender.java index 725edda..912870a 100644 --- a/cloud-spring/src/main/java/org/incendo/cloud/spring/SpringCommandSender.java +++ b/cloud-spring/src/main/java/org/incendo/cloud/spring/SpringCommandSender.java @@ -23,26 +23,55 @@ // package org.incendo.cloud.spring; +import java.util.Objects; import org.apiguardian.api.API; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.springframework.shell.command.CommandContext; /** - * Dummy command sender type for spring. + * Command sender for Spring. * * @since 1.0.0 */ @API(status = API.Status.STABLE, since = "1.0.0") -public interface SpringCommandSender { - - SpringCommandSender INSTANCE = new SpringCommandSender() { - }; +public sealed interface SpringCommandSender permits SpringCommandSender.SpringCommandSenderImpl { /** * Returns the sender instance. * + * @param context the context * @return the sender instance */ - static @NonNull SpringCommandSender sender() { - return INSTANCE; + static @NonNull SpringCommandSender sender(final @Nullable CommandContext context) { + return new SpringCommandSenderImpl(context); + } + + /** + * Returns the context. + * + *

During suggestions the context will be {@code null}.

+ * + * @return the context + */ + @Nullable CommandContext context(); + + /** + * Writes the given {@code line} to the terminal. + * + *

A line break will be added to the line before it's written.

+ * + *

Do not use this during suggestions, as {@link #context()} will be null.

+ * + * @param line the line to write + */ + default void writeLine(final @NonNull String line) { + final CommandContext context = Objects.requireNonNull(this.context()); + context.getTerminal().writer().println(line); + } + + + @API(status = API.Status.INTERNAL, consumers = "org.incendo.cloud.spring.*", since = "1.0.0") + record SpringCommandSenderImpl(@Nullable CommandContext context) implements SpringCommandSender { } } diff --git a/cloud-spring/src/main/java/org/incendo/cloud/spring/config/CloudSpringConfig.java b/cloud-spring/src/main/java/org/incendo/cloud/spring/config/CloudSpringConfig.java index c7fd895..2d78cb9 100644 --- a/cloud-spring/src/main/java/org/incendo/cloud/spring/config/CloudSpringConfig.java +++ b/cloud-spring/src/main/java/org/incendo/cloud/spring/config/CloudSpringConfig.java @@ -26,7 +26,7 @@ import cloud.commandframework.execution.CommandExecutionCoordinator; import org.apiguardian.api.API; import org.checkerframework.checker.nullness.qual.NonNull; -import org.incendo.cloud.spring.CommandSenderSupplier; +import org.incendo.cloud.spring.CommandSenderMapper; import org.incendo.cloud.spring.SpringCommandExecutionCoordinatorResolver; import org.incendo.cloud.spring.SpringCommandPermissionHandler; import org.incendo.cloud.spring.SpringCommandSender; @@ -56,8 +56,8 @@ public class CloudSpringConfig { } @Bean - @ConditionalOnMissingBean(CommandSenderSupplier.class) - @NonNull CommandSenderSupplier commandSenderMapper() { + @ConditionalOnMissingBean(CommandSenderMapper.class) + @NonNull CommandSenderMapper commandSenderMapper() { return SpringCommandSender::sender; } } diff --git a/cloud-spring/src/test/java/org/incendo/cloud/spring/ApplicationIntegrationTest.java b/cloud-spring/src/test/java/org/incendo/cloud/spring/ApplicationIntegrationTest.java index ed2cf38..64d03cb 100644 --- a/cloud-spring/src/test/java/org/incendo/cloud/spring/ApplicationIntegrationTest.java +++ b/cloud-spring/src/test/java/org/incendo/cloud/spring/ApplicationIntegrationTest.java @@ -85,8 +85,8 @@ static class TestApplication { static class TestConfig { @Bean - @NonNull CommandSenderSupplier commandSenderSupplier() { - return TestCommandSender::new; + @NonNull CommandSenderMapper commandSenderSupplier() { + return ctx -> new TestCommandSender(); } @Bean diff --git a/example/src/main/java/org/incendo/cloud/spring/example/ExampleConfig.java b/example/src/main/java/org/incendo/cloud/spring/example/ExampleConfig.java index b204388..bfdbd49 100644 --- a/example/src/main/java/org/incendo/cloud/spring/example/ExampleConfig.java +++ b/example/src/main/java/org/incendo/cloud/spring/example/ExampleConfig.java @@ -25,7 +25,6 @@ import cloud.commandframework.annotations.AnnotationParser; import org.checkerframework.checker.nullness.qual.NonNull; -import org.incendo.cloud.spring.CommandSenderSupplier; import org.incendo.cloud.spring.SpringCommandManager; import org.incendo.cloud.spring.SpringCommandSender; import org.incendo.cloud.spring.annotation.CommandGroup; @@ -38,11 +37,6 @@ @Configuration public class ExampleConfig { - @Bean - @NonNull CommandSenderSupplier commandSenderMapper() { - return SpringCommandSender::sender; - } - @Bean @NonNull AnnotationParser annotationParser( final @NonNull SpringCommandManager commandManager diff --git a/example/src/main/java/org/incendo/cloud/spring/example/commands/AddCatCommand.java b/example/src/main/java/org/incendo/cloud/spring/example/commands/AddCatCommand.java index bf98f83..debcb24 100644 --- a/example/src/main/java/org/incendo/cloud/spring/example/commands/AddCatCommand.java +++ b/example/src/main/java/org/incendo/cloud/spring/example/commands/AddCatCommand.java @@ -71,7 +71,9 @@ public AddCatCommand(final @NonNull CatService catService) { } @Override - protected Command.Builder configure(final Command.Builder builder) { + protected Command.@NonNull Builder configure( + final Command.@NonNull Builder builder + ) { return builder.literal("add") .required("name", stringParser(), SuggestionProvider.blocking((ctx, in) -> List.of( CloudCompletionProposal.of("Missy").displayText("Missy (A cute cat name)"), @@ -83,10 +85,10 @@ protected Command.Builder configure(final Command.Builder commandContext) { + public void execute(final @NonNull CommandContext commandContext) { final String name = commandContext.get("name"); final boolean override = commandContext.flags().hasFlag("override"); final Cat cat = this.catService.addCat(name, override); - LOGGER.info("Added cat {}", cat.name()); + commandContext.sender().writeLine(String.format("Added cat: %s", cat.name())); } } diff --git a/example/src/main/java/org/incendo/cloud/spring/example/commands/ListCatCommand.java b/example/src/main/java/org/incendo/cloud/spring/example/commands/ListCatCommand.java index 32f20d1..bf2efbe 100644 --- a/example/src/main/java/org/incendo/cloud/spring/example/commands/ListCatCommand.java +++ b/example/src/main/java/org/incendo/cloud/spring/example/commands/ListCatCommand.java @@ -26,6 +26,7 @@ import cloud.commandframework.annotations.CommandDescription; import cloud.commandframework.annotations.CommandMethod; import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.spring.SpringCommandSender; import org.incendo.cloud.spring.annotation.CommandGroup; import org.incendo.cloud.spring.annotation.ScanCommands; import org.incendo.cloud.spring.example.service.CatService; @@ -52,12 +53,14 @@ public ListCatCommand(final @NonNull CatService catService) { /** * Command that lists all registered cats. + * + * @param sender the command sender */ @CommandGroup("Cat") @CommandDescription("List the cats") @CommandMethod("cat list") - public void listCats() { - LOGGER.info("Cats"); - this.catService.cats().forEach(cat -> LOGGER.info("- {}", cat.name())); + public void listCats(final @NonNull SpringCommandSender sender) { + sender.writeLine("Cats"); + this.catService.cats().forEach(cat -> sender.writeLine(String.format("- %s", cat.name()))); } } diff --git a/example/src/main/java/org/incendo/cloud/spring/example/commands/RemoveCatCommand.java b/example/src/main/java/org/incendo/cloud/spring/example/commands/RemoveCatCommand.java index f06471e..103a1bb 100644 --- a/example/src/main/java/org/incendo/cloud/spring/example/commands/RemoveCatCommand.java +++ b/example/src/main/java/org/incendo/cloud/spring/example/commands/RemoveCatCommand.java @@ -82,9 +82,10 @@ public void execute(@NonNull final CommandContext commandCo final String name = commandContext.get("name"); final Cat cat = this.catService.removeCat(name); if (cat == null) { + commandContext.sender().writeLine("No such cat :("); LOGGER.error("No such cat :("); } else { - LOGGER.info("Removed cat {}", cat.name()); + commandContext.sender().writeLine(String.format("Removed cat: %s", cat.name())); } } }