diff --git a/e2e/README.adoc b/e2e/README.adoc index fc64050dc..bd4554c5d 100644 --- a/e2e/README.adoc +++ b/e2e/README.adoc @@ -2,28 +2,28 @@ Testing shell features within unit tests is not always enough to have a full coverage as shell applications has been traditionally been difficult to test as there is too many moving parts. -While it is relative easy to test parts of a java code in your `spring-shell` app, knowing those -will actualy execute with as run in a hosting environment is totally different topic. As -spring-shell` application can be run on a different environment either a simple `spring-boot` app +While it is relatively easy to test parts of a java code in your `spring-shell` app, knowing those +will actually execute with as run in a hosting environment is a totally different topic. As +`spring-shell` applications can be run on a different environment either a simple `spring-boot` app via java runtime environment or via `graalvm` build binary many things can simply go wrong. `spring-shell-e2e` is a node module which uses `node-pty` and `xterm.js` to run your shell application whether you run shell in any ways as it just assumes a command runs a shell application. Relationship between `node-pty` and `xterm.js` is that _pty_ environment is providing underlying host capabilities running shell applications and _xterm_ having -a knowledge to translate all shell command sequinces to a representive text. +a knowledge to translate all shell command sequences to a representative text. [NOTE] ==== We chose to use javascript space for e2e framework as it provides a good set of -libraries to work with various environments and is much more close to native -environment what we could do from a java space. +libraries to work with various environments and is much more close to native a +environment than what we could do from a java space. ==== `spring-shell-e2e-tests` is simply using `spring-shell-e2e` to implement _e2e_ tests and runs both _fatjar_ and _native_ built apps. -`spring-shell-e2e` is work-in-progress so it's not yet published into _npmjs_. +`spring-shell-e2e` is work-in-progress, so it's not yet published into _npmjs_. Generic workflow to run `spring-shell-e2e-tests` is: @@ -31,7 +31,7 @@ Generic workflow to run `spring-shell-e2e-tests` is: [source, bash] ---- spring-shell -$ ./mvnw clean package -DskipTests +$ ./gradlew build -x test spring-shell/e2e/spring-shell-e2e $ npm install diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandCatalogAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandCatalogAutoConfiguration.java index fbef81996..9b1cca2ea 100644 --- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandCatalogAutoConfiguration.java +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CommandCatalogAutoConfiguration.java @@ -27,8 +27,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.shell.MethodTargetRegistrar; import org.springframework.shell.boot.SpringShellProperties.Help; -import org.springframework.shell.command.CommandCatalog; -import org.springframework.shell.command.CommandCatalogCustomizer; +import org.springframework.shell.command.catalog.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalogCustomizer; import org.springframework.shell.command.CommandRegistration; import org.springframework.shell.command.CommandRegistration.BuilderSupplier; import org.springframework.shell.command.CommandRegistration.OptionNameModifier; diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ExitCodeAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ExitCodeAutoConfiguration.java index 3dfec7414..a1827f49f 100644 --- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ExitCodeAutoConfiguration.java +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ExitCodeAutoConfiguration.java @@ -25,7 +25,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; -import org.springframework.shell.command.CommandExecution; +import org.springframework.shell.command.execution.CommandExecution; import org.springframework.shell.exit.ExitCodeExceptionProvider; import org.springframework.shell.exit.ExitCodeMappings; diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/LineReaderAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/LineReaderAutoConfiguration.java index bfba557d0..12919824d 100644 --- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/LineReaderAutoConfiguration.java +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/LineReaderAutoConfiguration.java @@ -38,7 +38,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.EventListener; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; import org.springframework.shell.config.UserConfigPathProvider; import org.springframework.util.StringUtils; diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ParameterResolverAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ParameterResolverAutoConfiguration.java index 4ec18d23e..8eda625bf 100644 --- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ParameterResolverAutoConfiguration.java +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ParameterResolverAutoConfiguration.java @@ -24,7 +24,7 @@ import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; import org.springframework.shell.command.ArgumentHeaderMethodArgumentResolver; import org.springframework.shell.command.CommandContextMethodArgumentResolver; -import org.springframework.shell.command.CommandExecution.CommandExecutionHandlerMethodArgumentResolvers; +import org.springframework.shell.command.execution.CommandExecution.CommandExecutionHandlerMethodArgumentResolvers; import org.springframework.shell.completion.CompletionResolver; import org.springframework.shell.completion.RegistrationOptionsCompletionResolver; import org.springframework.shell.config.ShellConversionServiceSupplier; diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellAutoConfiguration.java index 9d97957dc..97c7e3d3d 100644 --- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellAutoConfiguration.java +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellAutoConfiguration.java @@ -31,7 +31,7 @@ import org.springframework.shell.ResultHandler; import org.springframework.shell.ResultHandlerService; import org.springframework.shell.Shell; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; import org.springframework.shell.config.ShellConversionServiceSupplier; import org.springframework.shell.context.ShellContext; import org.springframework.shell.exit.ExitCodeMappings; diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/StandardAPIAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/StandardAPIAutoConfiguration.java index c015011d8..b35092070 100644 --- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/StandardAPIAutoConfiguration.java +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/StandardAPIAutoConfiguration.java @@ -20,7 +20,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.shell.MethodTargetRegistrar; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; import org.springframework.shell.command.CommandRegistration; import org.springframework.shell.standard.CommandValueProvider; import org.springframework.shell.standard.EnumValueProvider; diff --git a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandCatalogAutoConfigurationTests.java b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandCatalogAutoConfigurationTests.java index dd7bdb240..220194f23 100644 --- a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandCatalogAutoConfigurationTests.java +++ b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/CommandCatalogAutoConfigurationTests.java @@ -24,7 +24,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; import org.springframework.shell.command.CommandRegistration; import org.springframework.shell.command.CommandResolver; import org.springframework.shell.command.CommandRegistration.Builder; diff --git a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/LineReaderAutoConfigurationTests.java b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/LineReaderAutoConfigurationTests.java index f4aa0fcfc..09c156135 100644 --- a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/LineReaderAutoConfigurationTests.java +++ b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/LineReaderAutoConfigurationTests.java @@ -29,7 +29,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; import org.springframework.shell.config.UserConfigPathProvider; import static org.assertj.core.api.Assertions.assertThat; diff --git a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ParameterResolverAutoConfigurationTests.java b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ParameterResolverAutoConfigurationTests.java index 44c3c6c65..1bfdcf2a4 100644 --- a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ParameterResolverAutoConfigurationTests.java +++ b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ParameterResolverAutoConfigurationTests.java @@ -22,7 +22,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.shell.command.CommandExecution.CommandExecutionHandlerMethodArgumentResolvers; +import org.springframework.shell.command.execution.CommandExecution.CommandExecutionHandlerMethodArgumentResolvers; import org.springframework.shell.completion.CompletionResolver; import org.springframework.shell.config.ShellConversionServiceSupplier; diff --git a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ShellRunnerAutoConfigurationTests.java b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ShellRunnerAutoConfigurationTests.java index c433088d9..c4f2dd63a 100644 --- a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ShellRunnerAutoConfigurationTests.java +++ b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ShellRunnerAutoConfigurationTests.java @@ -23,7 +23,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.shell.Shell; -import org.springframework.shell.command.CommandExecution.CommandExecutionHandlerMethodArgumentResolvers; +import org.springframework.shell.command.execution.CommandExecution.CommandExecutionHandlerMethodArgumentResolvers; import org.springframework.shell.completion.CompletionResolver; import org.springframework.shell.context.ShellContext; import org.springframework.shell.jline.InteractiveShellRunner; diff --git a/spring-shell-core/src/main/java/org/springframework/shell/MethodTargetRegistrar.java b/spring-shell-core/src/main/java/org/springframework/shell/MethodTargetRegistrar.java index 2a663b702..95e9080f2 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/MethodTargetRegistrar.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/MethodTargetRegistrar.java @@ -16,7 +16,7 @@ package org.springframework.shell; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; /** * Strategy interface for registering commands. diff --git a/spring-shell-core/src/main/java/org/springframework/shell/Shell.java b/spring-shell-core/src/main/java/org/springframework/shell/Shell.java index 34b6ddbd9..8e0f331f2 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/Shell.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/Shell.java @@ -37,11 +37,11 @@ import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.shell.command.CommandAlias; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; import org.springframework.shell.command.CommandExceptionResolver; -import org.springframework.shell.command.CommandExecution; -import org.springframework.shell.command.CommandExecution.CommandExecutionException; -import org.springframework.shell.command.CommandExecution.CommandExecutionHandlerMethodArgumentResolvers; +import org.springframework.shell.command.execution.CommandExecution; +import org.springframework.shell.command.execution.CommandExecution.CommandExecutionException; +import org.springframework.shell.command.execution.CommandExecution.CommandExecutionHandlerMethodArgumentResolvers; import org.springframework.shell.command.CommandHandlingResult; import org.springframework.shell.command.CommandOption; import org.springframework.shell.command.CommandRegistration; @@ -58,6 +58,8 @@ * @author Eric Bottard * @author Janne Valkealahti */ + +// Shit goes down here public class Shell { private final static Logger log = LoggerFactory.getLogger(Shell.class); @@ -171,7 +173,7 @@ else if (result instanceof Exception) { throw (Exception) result; } if (handlingResultNonInt instanceof CommandExecution.CommandParserExceptionsException) { - throw (CommandExecution.CommandParserExceptionsException) handlingResultNonInt; + throw handlingResultNonInt; } else if (processExceptionNonInt != null && processExceptionNonInt.exitCode() != null && exitCodeExceptionProvider != null) { @@ -196,7 +198,7 @@ protected Object evaluate(Input input) { } List words = input.words(); - String line = words.stream().collect(Collectors.joining(" ")).trim(); + String line = String.join(" ", words).trim(); String command = findLongestCommand(line, false); if (command == null) { @@ -230,7 +232,7 @@ protected Object evaluate(Input input) { } Thread commandThread = Thread.currentThread(); - Object sh = Signals.register("INT", () -> commandThread.interrupt()); + Object interruptionSignalHandler = Signals.register("INT", commandThread::interrupt); CommandExecution execution = CommandExecution.of( argumentResolvers != null ? argumentResolvers.getResolvers() : null, validator, terminal, @@ -238,6 +240,10 @@ protected Object evaluate(Input input) { List commandExceptionResolvers = commandRegistration.get().getExceptionResolvers(); + return evaluateAndCatch(words, interruptionSignalHandler, execution, commandExceptionResolvers); + } + + private Object evaluateAndCatch(List words, Object interruptionSignalHandler, CommandExecution execution, List commandExceptionResolvers) { Object evaluate = null; Exception e = null; try { @@ -249,20 +255,21 @@ protected Object evaluate(Input input) { } return ute.getCause(); } - catch (CommandExecutionException e1) { - if (e1.getCause() instanceof Exception e11) { - e = e11; + catch (CommandExecutionException commandExecutionException) { + if (commandExecutionException.getCause() instanceof Exception causeException) { + e = causeException; } else { - return e1.getCause(); + return commandExecutionException.getCause(); } } - catch (Exception e2) { - e = e2; + catch (Exception exception) { + e = exception; } finally { - Signals.unregister("INT", sh); + Signals.unregister("INT", interruptionSignalHandler); } + if (e != null && !(e instanceof ExitRequest)) { try { CommandHandlingResult processException = processException(commandExceptionResolvers, e); @@ -284,6 +291,7 @@ protected Object evaluate(Input input) { if (e != null) { evaluate = e; } + return evaluate; } @@ -447,7 +455,7 @@ private CompletionProposal toCommandProposal(String command, CommandRegistration private String findLongestCommand(String prefix, boolean filterHidden) { Map registrations = commandRegistry.getRegistrations(); if (filterHidden) { - registrations = Utils.removeHiddenCommands(registrations); + Utils.removeHiddenCommands(registrations); } String result = registrations.keySet().stream() .filter(command -> prefix.equals(command) || prefix.startsWith(command + " ")) diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandCatalog.java b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandCatalog.java deleted file mode 100644 index 7e1c38a9c..000000000 --- a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandCatalog.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2022 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.command; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -import org.springframework.shell.context.InteractionMode; -import org.springframework.shell.context.ShellContext; - -/** - * Interface defining contract to handle existing {@link CommandRegistration}s. - * - * @author Janne Valkealahti - */ -public interface CommandCatalog { - - /** - * Register a {@link CommandRegistration}. - * - * @param registration the command registration - */ - void register(CommandRegistration... registration); - - /** - * Unregister a {@link CommandRegistration}. - * - * @param registration the command registration - */ - void unregister(CommandRegistration... registration); - - /** - * Unregister a {@link CommandRegistration} by its command name. - * - * @param commandName the command name - */ - void unregister(String... commandName); - - /** - * Gets all {@link CommandRegistration}s mapped with their names. - * Returned map is a copy and cannot be used to register new commands. - * - * @return all command registrations - */ - Map getRegistrations(); - - /** - * Gets an instance of a default {@link CommandCatalog}. - * - * @return default command catalog - */ - static CommandCatalog of() { - return new DefaultCommandCatalog(null, null); - } - - /** - * Gets an instance of a default {@link CommandCatalog}. - * - * @param resolvers the command resolvers - * @param shellContext the shell context - * @return default command catalog - */ - static CommandCatalog of(Collection resolvers, ShellContext shellContext) { - return new DefaultCommandCatalog(resolvers, shellContext); - } - - /** - * Default implementation of a {@link CommandCatalog}. - */ - static class DefaultCommandCatalog implements CommandCatalog { - - private final Map commandRegistrations = new HashMap<>(); - private final Collection resolvers = new ArrayList<>(); - private final ShellContext shellContext; - - DefaultCommandCatalog(Collection resolvers, ShellContext shellContext) { - this.shellContext = shellContext; - if (resolvers != null) { - this.resolvers.addAll(resolvers); - } - } - - @Override - public void register(CommandRegistration... registration) { - for (CommandRegistration r : registration) { - String commandName = r.getCommand(); - commandRegistrations.put(commandName, r); - for (CommandAlias a : r.getAliases()) { - commandRegistrations.put(a.getCommand(), r); - } - } - } - - @Override - public void unregister(CommandRegistration... registration) { - for (CommandRegistration r : registration) { - String commandName = r.getCommand(); - commandRegistrations.remove(commandName); - for (CommandAlias a : r.getAliases()) { - commandRegistrations.remove(a.getCommand()); - } - } - } - - @Override - public void unregister(String... commandName) { - for (String n : commandName) { - commandRegistrations.remove(n); - } - } - - @Override - public Map getRegistrations() { - Map regs = new HashMap<>(); - regs.putAll(commandRegistrations); - for (CommandResolver resolver : resolvers) { - resolver.resolve().stream().forEach(r -> { - regs.put(r.getCommand(), r); - }); - } - return regs.entrySet().stream() - .filter(filterByInteractionMode(shellContext)) - .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); - } - - /** - * Filter registration entries by currently set mode. Having it set to ALL or null - * effectively disables filtering as as we only care if mode is set to interactive - * or non-interactive. - */ - private static Predicate> filterByInteractionMode(ShellContext shellContext) { - return e -> { - InteractionMode mim = e.getValue().getInteractionMode(); - InteractionMode cim = shellContext != null ? shellContext.getInteractionMode() : InteractionMode.ALL; - if (mim == null || cim == null || mim == InteractionMode.ALL) { - return true; - } - else if (mim == InteractionMode.INTERACTIVE) { - return cim == InteractionMode.INTERACTIVE || cim == InteractionMode.ALL; - } - else if (mim == InteractionMode.NONINTERACTIVE) { - return cim == InteractionMode.NONINTERACTIVE || cim == InteractionMode.ALL; - } - return true; - }; - } - } -} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandExecution.java b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandExecution.java deleted file mode 100644 index 51ff29cfe..000000000 --- a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandExecution.java +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright 2022-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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.command; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import jakarta.validation.Validator; -import org.jline.terminal.Terminal; - -import org.springframework.core.MethodParameter; -import org.springframework.core.annotation.Order; -import org.springframework.core.convert.ConversionService; -import org.springframework.messaging.Message; -import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; -import org.springframework.messaging.support.MessageBuilder; -import org.springframework.shell.Availability; -import org.springframework.shell.CommandNotCurrentlyAvailable; -import org.springframework.shell.command.CommandParser.CommandParserException; -import org.springframework.shell.command.CommandParser.CommandParserResults; -import org.springframework.shell.command.CommandRegistration.HelpOptionInfo; -import org.springframework.shell.command.CommandRegistration.TargetInfo; -import org.springframework.shell.command.CommandRegistration.TargetInfo.TargetType; -import org.springframework.shell.command.invocation.InvocableShellMethod; -import org.springframework.shell.command.invocation.ShellMethodArgumentResolverComposite; -import org.springframework.shell.command.parser.ParserConfig; -import org.springframework.util.ObjectUtils; - -/** - * Interface to evaluate a result from a command with an arguments. - * - * @author Janne Valkealahti - */ -public interface CommandExecution { - - /** - * Evaluate a command with a given arguments. - * - * @param args the command args - * @return evaluated execution - */ - Object evaluate(String[] args); - - /** - * Gets an instance of a default {@link CommandExecution}. - * - * @param resolvers the handler method argument resolvers - * @return default command execution - */ - public static CommandExecution of(List resolvers) { - return new DefaultCommandExecution(resolvers, null, null, null, null); - } - - /** - * Gets an instance of a default {@link CommandExecution}. - * - * @param resolvers the handler method argument resolvers - * @param validator the validator - * @param terminal the terminal - * @param conversionService the conversion services - * @return default command execution - */ - public static CommandExecution of(List resolvers, Validator validator, - Terminal terminal, ConversionService conversionService) { - return new DefaultCommandExecution(resolvers, validator, terminal, conversionService, null); - } - - /** - * Gets an instance of a default {@link CommandExecution}. - * - * @param resolvers the handler method argument resolvers - * @param validator the validator - * @param terminal the terminal - * @param conversionService the conversion services - * @return default command execution - */ - public static CommandExecution of(List resolvers, Validator validator, - Terminal terminal, ConversionService conversionService, CommandCatalog commandCatalog) { - return new DefaultCommandExecution(resolvers, validator, terminal, conversionService, commandCatalog); - } - - /** - * Default implementation of a {@link CommandExecution}. - */ - static class DefaultCommandExecution implements CommandExecution { - - private List resolvers; - private Validator validator; - private Terminal terminal; - private ConversionService conversionService; - private CommandCatalog commandCatalog; - - public DefaultCommandExecution(List resolvers, Validator validator, - Terminal terminal, ConversionService conversionService, CommandCatalog commandCatalog) { - this.resolvers = resolvers; - this.validator = validator; - this.terminal = terminal; - this.conversionService = conversionService; - this.commandCatalog = commandCatalog; - } - - public Object evaluate(String[] args) { - CommandParser parser = CommandParser.of(conversionService, commandCatalog.getRegistrations(), new ParserConfig()); - CommandParserResults results = parser.parse(args); - CommandRegistration registration = results.registration(); - - // fast fail with availability before doing anything else - Availability availability = registration.getAvailability(); - if (availability != null && !availability.isAvailable()) { - return new CommandNotCurrentlyAvailable(registration.getCommand(), availability); - } - - // check help options to short circuit - boolean handleHelpOption = false; - HelpOptionInfo helpOption = registration.getHelpOption(); - if (helpOption.isEnabled() && helpOption.getCommand() != null && (!ObjectUtils.isEmpty(helpOption.getLongNames()) || !ObjectUtils.isEmpty(helpOption.getShortNames()))) { - handleHelpOption = results.results().stream() - .filter(cpr -> { - boolean present = false; - if (helpOption.getLongNames() != null) { - present = Arrays.asList(cpr.option().getLongNames()).stream() - .filter(ln -> ObjectUtils.containsElement(helpOption.getLongNames(), ln)) - .findFirst() - .isPresent(); - } - if (present) { - return true; - } - if (helpOption.getShortNames() != null) { - present = Arrays.asList(cpr.option().getShortNames()).stream() - .filter(sn -> ObjectUtils.containsElement(helpOption.getShortNames(), sn)) - .findFirst() - .isPresent(); - } - return present; - }) - .findFirst() - .isPresent(); - } - - // if needed switch registration to help command if we're short circuiting - CommandRegistration usedRegistration; - if (handleHelpOption) { - String command = registration.getCommand(); - CommandParser helpParser = CommandParser.of(conversionService, commandCatalog.getRegistrations(), - new ParserConfig()); - CommandRegistration helpCommandRegistration = commandCatalog.getRegistrations() - .get(registration.getHelpOption().getCommand()); - CommandParserResults helpResults = helpParser.parse(new String[] { "help", "--command", command }); - results = helpResults; - usedRegistration = helpCommandRegistration; - } - else { - usedRegistration = registration; - } - - if (!results.errors().isEmpty()) { - throw new CommandParserExceptionsException("Command parser resulted errors", results.errors()); - } - - CommandContext ctx = CommandContext.of(args, results, terminal, usedRegistration); - - Object res = null; - - TargetInfo targetInfo = usedRegistration.getTarget(); - - // pick the target to execute - if (targetInfo.getTargetType() == TargetType.FUNCTION) { - res = targetInfo.getFunction().apply(ctx); - } - else if (targetInfo.getTargetType() == TargetType.CONSUMER) { - targetInfo.getConsumer().accept(ctx); - } - else if (targetInfo.getTargetType() == TargetType.METHOD) { - try { - MessageBuilder messageBuilder = MessageBuilder.withPayload(args); - Map paramValues = new HashMap<>(); - results.results().stream().forEach(r -> { - if (r.option().getLongNames() != null) { - for (String n : r.option().getLongNames()) { - messageBuilder.setHeader(ArgumentHeaderMethodArgumentResolver.ARGUMENT_PREFIX + n, r.value()); - paramValues.put(n, r.value()); - } - } - if (r.option().getShortNames() != null) { - for (Character n : r.option().getShortNames()) { - messageBuilder.setHeader(ArgumentHeaderMethodArgumentResolver.ARGUMENT_PREFIX + n.toString(), r.value()); - } - } - }); - messageBuilder.setHeader(CommandContextMethodArgumentResolver.HEADER_COMMAND_CONTEXT, ctx); - - InvocableShellMethod invocableShellMethod = new InvocableShellMethod(targetInfo.getBean(), targetInfo.getMethod()); - invocableShellMethod.setConversionService(conversionService); - invocableShellMethod.setValidator(validator); - ShellMethodArgumentResolverComposite argumentResolvers = new ShellMethodArgumentResolverComposite(); - if (resolvers != null) { - argumentResolvers.addResolvers(resolvers); - } - if (!paramValues.isEmpty()) { - argumentResolvers.addResolver(new ParamNameHandlerMethodArgumentResolver(paramValues, conversionService)); - } - invocableShellMethod.setMessageMethodArgumentResolvers(argumentResolvers); - - res = invocableShellMethod.invoke(messageBuilder.build(), (Object[])null); - - } catch (Exception e) { - throw new CommandExecutionException(e); - } - } - - return res; - } - } - - @Order(100) - static class ParamNameHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { - - private final Map paramValues = new HashMap<>(); - private final ConversionService conversionService; - - ParamNameHandlerMethodArgumentResolver(Map paramValues, ConversionService conversionService) { - this.paramValues.putAll(paramValues); - this.conversionService = conversionService; - } - - @Override - public boolean supportsParameter(MethodParameter parameter) { - String parameterName = parameter.getParameterName(); - if (parameterName == null) { - return false; - } - Class sourceType = paramValues.get(parameterName) != null ? paramValues.get(parameterName).getClass() - : null; - return paramValues.containsKey(parameterName) && conversionService - .canConvert(sourceType, parameter.getParameterType()); - } - - @Override - public Object resolveArgument(MethodParameter parameter, Message message) throws Exception { - return conversionService.convert(paramValues.get(parameter.getParameterName()), parameter.getParameterType()); - } - - } - - static class CommandExecutionException extends RuntimeException { - - public CommandExecutionException(Throwable cause) { - super(cause); - } - } - - public static class CommandParserExceptionsException extends RuntimeException { - - private final List parserExceptions; - - public CommandParserExceptionsException(String message, List parserExceptions) { - super(message); - this.parserExceptions = parserExceptions; - } - - public static CommandParserExceptionsException of(String message, List parserExceptions) { - return new CommandParserExceptionsException(message, parserExceptions); - } - - public List getParserExceptions() { - return parserExceptions; - } - } - - static class CommandExecutionHandlerMethodArgumentResolvers { - - private final List resolvers; - - public CommandExecutionHandlerMethodArgumentResolvers(List resolvers) { - this.resolvers = resolvers; - } - - public List getResolvers() { - return resolvers; - } - } -} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandParserExceptionResolver.java b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandParserExceptionResolver.java index ecc7fbdc2..e3aa8939a 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandParserExceptionResolver.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandParserExceptionResolver.java @@ -19,7 +19,7 @@ import org.jline.utils.AttributedStringBuilder; import org.jline.utils.AttributedStyle; -import org.springframework.shell.command.CommandExecution.CommandParserExceptionsException; +import org.springframework.shell.command.execution.CommandExecution.CommandParserExceptionsException; /** * Handles {@link CommandParserExceptionsException}. diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/catalog/CommandCatalog.java b/spring-shell-core/src/main/java/org/springframework/shell/command/catalog/CommandCatalog.java new file mode 100644 index 000000000..5051d67ec --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/command/catalog/CommandCatalog.java @@ -0,0 +1,85 @@ +/* + * Copyright 2022 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.command.catalog; + +import java.util.*; +import java.util.Map.Entry; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.springframework.shell.command.CommandAlias; +import org.springframework.shell.command.CommandRegistration; +import org.springframework.shell.command.CommandResolver; +import org.springframework.shell.context.InteractionMode; +import org.springframework.shell.context.ShellContext; + +/** + * Interface defining contract to handle existing {@link CommandRegistration}s. + * + * @author Janne Valkealahti + */ +public interface CommandCatalog { + + /** + * Register a {@link CommandRegistration}. + * + * @param registration the command registration + */ + void register(CommandRegistration... registration); + + /** + * Unregister a {@link CommandRegistration}. + * + * @param registration the command registration + */ + void unregister(CommandRegistration... registration); + + /** + * Unregister a {@link CommandRegistration} by its command name. + * + * @param commandName the command name + */ + void unregister(String... commandName); + + /** + * Gets all {@link CommandRegistration}s mapped with their names. + * Returned map is a copy and cannot be used to register new commands. + * + * @return all command registrations + */ + Map getRegistrations(); + + /** + * Gets an instance of a default {@link CommandCatalog}. + * + * @return default command catalog + */ + static CommandCatalog of() { + return new DefaultCommandCatalog(null, null); + } + + /** + * Gets an instance of a default {@link CommandCatalog}. + * + * @param resolvers the command resolvers + * @param shellContext the shell context + * @return default command catalog + */ + static CommandCatalog of(Collection resolvers, ShellContext shellContext) { + return new DefaultCommandCatalog(resolvers, shellContext); + } + +} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandCatalogCustomizer.java b/spring-shell-core/src/main/java/org/springframework/shell/command/catalog/CommandCatalogCustomizer.java similarity index 94% rename from spring-shell-core/src/main/java/org/springframework/shell/command/CommandCatalogCustomizer.java rename to spring-shell-core/src/main/java/org/springframework/shell/command/catalog/CommandCatalogCustomizer.java index 4bf2a678c..d8c6d9cfe 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandCatalogCustomizer.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/command/catalog/CommandCatalogCustomizer.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.shell.command; +package org.springframework.shell.command.catalog; /** * Interface to customize a {@link CommandCatalog}. diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/catalog/DefaultCommandCatalog.java b/spring-shell-core/src/main/java/org/springframework/shell/command/catalog/DefaultCommandCatalog.java new file mode 100644 index 000000000..573a63ab5 --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/command/catalog/DefaultCommandCatalog.java @@ -0,0 +1,91 @@ +package org.springframework.shell.command.catalog; + +import org.springframework.shell.command.CommandAlias; +import org.springframework.shell.command.CommandRegistration; +import org.springframework.shell.command.CommandResolver; +import org.springframework.shell.context.InteractionMode; +import org.springframework.shell.context.ShellContext; + +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * Default implementation of a {@link CommandCatalog}. + */ +class DefaultCommandCatalog implements CommandCatalog { + + private final Map commandRegistrations = new HashMap<>(); + private final Collection resolvers = new ArrayList<>(); + private final ShellContext shellContext; + + DefaultCommandCatalog(Collection resolvers, ShellContext shellContext) { + this.shellContext = shellContext; + if (resolvers != null) { + this.resolvers.addAll(resolvers); + } + } + + @Override + public void register(CommandRegistration... registration) { + for (CommandRegistration r : registration) { + String commandName = r.getCommand(); + commandRegistrations.put(commandName, r); + for (CommandAlias a : r.getAliases()) { + commandRegistrations.put(a.getCommand(), r); + } + } + } + + @Override + public void unregister(CommandRegistration... registration) { + for (CommandRegistration r : registration) { + String commandName = r.getCommand(); + commandRegistrations.remove(commandName); + for (CommandAlias a : r.getAliases()) { + commandRegistrations.remove(a.getCommand()); + } + } + } + + @Override + public void unregister(String... commandName) { + for (String n : commandName) { + commandRegistrations.remove(n); + } + } + + @Override + public Map getRegistrations() { + Map regs = new HashMap<>(commandRegistrations); + for (CommandResolver resolver : resolvers) { + resolver.resolve().forEach(r -> regs.put(r.getCommand(), r)); + } + return regs.entrySet().stream() + .filter(filterByInteractionMode(shellContext)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + /** + * Filter registration entries by currently set mode. Having it set to ALL or null + * effectively disables filtering as as we only care if mode is set to interactive + * or non-interactive. + */ + private static Predicate> filterByInteractionMode(ShellContext shellContext) { + return e -> { + InteractionMode mim = e.getValue().getInteractionMode(); + InteractionMode cim = shellContext != null ? shellContext.getInteractionMode() : InteractionMode.ALL; + if (mim == null || cim == null || mim == InteractionMode.ALL) { + return true; + } + else if (mim == InteractionMode.INTERACTIVE) { + return cim == InteractionMode.INTERACTIVE || cim == InteractionMode.ALL; + } + else if (mim == InteractionMode.NONINTERACTIVE) { + return cim == InteractionMode.NONINTERACTIVE || cim == InteractionMode.ALL; + } + return true; + }; + } + +} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/execution/CommandExecution.java b/spring-shell-core/src/main/java/org/springframework/shell/command/execution/CommandExecution.java new file mode 100644 index 000000000..4751a993f --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/command/execution/CommandExecution.java @@ -0,0 +1,119 @@ +/* + * Copyright 2022-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.command.execution; + +import java.util.List; + +import jakarta.validation.Validator; +import org.jline.terminal.Terminal; + +import org.springframework.core.convert.ConversionService; +import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; +import org.springframework.shell.command.CommandParser.CommandParserException; +import org.springframework.shell.command.catalog.CommandCatalog; + + +/** + * Interface to evaluate a result from a command with arguments. + * + * @author Janne Valkealahti + */ +public interface CommandExecution { + + /** + * Evaluate a command with given arguments. + * + * @param args the command args + * @return evaluated execution + */ + Object evaluate(String[] args); + + /** + * Gets an instance of a default {@link CommandExecution}. + * + * @param resolvers the handler method argument resolvers + * @return default command execution + */ + static CommandExecution of(List resolvers) { + return new DefaultCommandExecution(resolvers, null, null, null, null); + } + + /** + * Gets an instance of a default {@link CommandExecution}. + * + * @param resolvers the handler method argument resolvers + * @param validator the validator + * @param terminal the terminal + * @param conversionService the conversion services + * @return default command execution + */ + static CommandExecution of(List resolvers, Validator validator, + Terminal terminal, ConversionService conversionService) { + return new DefaultCommandExecution(resolvers, validator, terminal, conversionService, null); + } + + /** + * Gets an instance of a default {@link CommandExecution}. + * + * @param resolvers the handler method argument resolvers + * @param validator the validator + * @param terminal the terminal + * @param conversionService the conversion services + * @return default command execution + */ + static CommandExecution of(List resolvers, Validator validator, + Terminal terminal, ConversionService conversionService, CommandCatalog commandCatalog) { + return new DefaultCommandExecution(resolvers, validator, terminal, conversionService, commandCatalog); + } + + class CommandExecutionException extends RuntimeException { + + public CommandExecutionException(Throwable cause) { + super(cause); + } + } + + class CommandParserExceptionsException extends RuntimeException { + + private final List parserExceptions; + + public CommandParserExceptionsException(String message, List parserExceptions) { + super(message); + this.parserExceptions = parserExceptions; + } + + public static CommandParserExceptionsException of(String message, List parserExceptions) { + return new CommandParserExceptionsException(message, parserExceptions); + } + + public List getParserExceptions() { + return parserExceptions; + } + } + + class CommandExecutionHandlerMethodArgumentResolvers { + + private final List resolvers; + + public CommandExecutionHandlerMethodArgumentResolvers(List resolvers) { + this.resolvers = resolvers; + } + + public List getResolvers() { + return resolvers; + } + } +} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/execution/DefaultCommandExecution.java b/spring-shell-core/src/main/java/org/springframework/shell/command/execution/DefaultCommandExecution.java new file mode 100644 index 000000000..6d760ed2d --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/command/execution/DefaultCommandExecution.java @@ -0,0 +1,161 @@ +package org.springframework.shell.command.execution; + +import jakarta.validation.Validator; +import org.jline.terminal.Terminal; +import org.springframework.core.convert.ConversionService; +import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.shell.Availability; +import org.springframework.shell.CommandNotCurrentlyAvailable; +import org.springframework.shell.command.*; +import org.springframework.shell.command.catalog.CommandCatalog; +import org.springframework.shell.command.invocation.InvocableShellMethod; +import org.springframework.shell.command.invocation.ShellMethodArgumentResolverComposite; +import org.springframework.shell.command.parser.ParserConfig; +import org.springframework.util.ObjectUtils; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Default implementation of a {@link CommandExecution}. + */ +public class DefaultCommandExecution implements CommandExecution { + + private List resolvers; + private Validator validator; + private Terminal terminal; + private ConversionService conversionService; + private CommandCatalog commandCatalog; + + public DefaultCommandExecution(List resolvers, Validator validator, + Terminal terminal, ConversionService conversionService, CommandCatalog commandCatalog) { + this.resolvers = resolvers; + this.validator = validator; + this.terminal = terminal; + this.conversionService = conversionService; + this.commandCatalog = commandCatalog; + } + + public Object evaluate(String[] args) { + CommandParser parser = CommandParser.of(conversionService, commandCatalog.getRegistrations(), new ParserConfig()); + CommandParser.CommandParserResults commandParserResults = parser.parse(args); + CommandRegistration commandRegistration = commandParserResults.registration(); + + // fast fail with availability before doing anything else + Availability availability = commandRegistration.getAvailability(); + if (availability != null && !availability.isAvailable()) { + return new CommandNotCurrentlyAvailable(commandRegistration.getCommand(), availability); + } + + // check help options to short circuit + boolean helpOptionIsRequested = false; + CommandRegistration.HelpOptionInfo helpOption = commandRegistration.getHelpOption(); + if (helpOptionIsEnabled(helpOption)) { + helpOptionIsRequested = helpOptionIsRequested(helpOption, commandParserResults); + } + + // if needed switch registration to help command if we're short circuiting + CommandRegistration usedRegistration; + if (helpOptionIsRequested) { + String command = commandRegistration.getCommand(); + CommandParser helpParser = CommandParser.of(conversionService, commandCatalog.getRegistrations(), + new ParserConfig()); + CommandRegistration helpCommandRegistration = commandCatalog.getRegistrations() + .get(commandRegistration.getHelpOption().getCommand()); + commandParserResults = helpParser.parse(new String[] { "help", "--command", command }); + usedRegistration = helpCommandRegistration; + } + else { + usedRegistration = commandRegistration; + } + + if (!commandParserResults.errors().isEmpty()) { + throw new CommandParserExceptionsException("Command parser resulted errors", commandParserResults.errors()); + } + + CommandContext ctx = CommandContext.of(args, commandParserResults, terminal, usedRegistration); + + return executeAndGetResult(args, commandParserResults, usedRegistration, ctx); + } + + private Object executeAndGetResult(String[] args, CommandParser.CommandParserResults results, CommandRegistration usedRegistration, CommandContext ctx) { + Object res = null; + + CommandRegistration.TargetInfo targetInfo = usedRegistration.getTarget(); + + // pick the target to execute + if (targetInfo.getTargetType() == CommandRegistration.TargetInfo.TargetType.FUNCTION) { + res = targetInfo.getFunction().apply(ctx); + } + else if (targetInfo.getTargetType() == CommandRegistration.TargetInfo.TargetType.CONSUMER) { + targetInfo.getConsumer().accept(ctx); + } + else if (targetInfo.getTargetType() == CommandRegistration.TargetInfo.TargetType.METHOD) { + try { + MessageBuilder messageBuilder = MessageBuilder.withPayload(args); + Map paramValues = new HashMap<>(); + results.results().forEach(r -> { + if (r.option().getLongNames() != null) { + for (String n : r.option().getLongNames()) { + messageBuilder.setHeader(ArgumentHeaderMethodArgumentResolver.ARGUMENT_PREFIX + n, r.value()); + paramValues.put(n, r.value()); + } + } + if (r.option().getShortNames() != null) { + for (Character n : r.option().getShortNames()) { + messageBuilder.setHeader(ArgumentHeaderMethodArgumentResolver.ARGUMENT_PREFIX + n.toString(), r.value()); + } + } + }); + messageBuilder.setHeader(CommandContextMethodArgumentResolver.HEADER_COMMAND_CONTEXT, ctx); + + InvocableShellMethod invocableShellMethod = new InvocableShellMethod(targetInfo.getBean(), targetInfo.getMethod()); + invocableShellMethod.setConversionService(conversionService); + invocableShellMethod.setValidator(validator); + ShellMethodArgumentResolverComposite argumentResolvers = new ShellMethodArgumentResolverComposite(); + if (resolvers != null) { + argumentResolvers.addResolvers(resolvers); + } + if (!paramValues.isEmpty()) { + argumentResolvers.addResolver(new ParamNameHandlerMethodArgumentResolver(paramValues, conversionService)); + } + invocableShellMethod.setMessageMethodArgumentResolvers(argumentResolvers); + + res = invocableShellMethod.invoke(messageBuilder.build(), (Object[])null); + + } catch (Exception e) { + throw new CommandExecutionException(e); + } + } + + return res; + } + + private boolean helpOptionIsRequested(CommandRegistration.HelpOptionInfo helpOption, CommandParser.CommandParserResults commandParserResults) { + return commandParserResults.results().stream() + .anyMatch(commandParserResult -> { + boolean present = false; + if (helpOption.getLongNames() != null) { + present = Arrays.stream(commandParserResult.option().getLongNames()) + .anyMatch(longName -> ObjectUtils.containsElement(helpOption.getLongNames(), longName)); + } + if (present) { + return true; + } + + if (helpOption.getShortNames() != null) { + present = Arrays.stream(commandParserResult.option().getShortNames()) + .anyMatch(shortName -> ObjectUtils.containsElement(helpOption.getShortNames(), shortName)); + } + return present; + }); + } + + private boolean helpOptionIsEnabled(CommandRegistration.HelpOptionInfo helpOption) { + return helpOption.isEnabled() && helpOption.getCommand() != null && (!ObjectUtils.isEmpty(helpOption.getLongNames()) || !ObjectUtils.isEmpty(helpOption.getShortNames())); + } +} + diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/execution/ParamNameHandlerMethodArgumentResolver.java b/spring-shell-core/src/main/java/org/springframework/shell/command/execution/ParamNameHandlerMethodArgumentResolver.java new file mode 100644 index 000000000..b37e329d1 --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/command/execution/ParamNameHandlerMethodArgumentResolver.java @@ -0,0 +1,40 @@ +package org.springframework.shell.command.execution; + +import org.springframework.core.MethodParameter; +import org.springframework.core.annotation.Order; +import org.springframework.core.convert.ConversionService; +import org.springframework.messaging.Message; +import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; + +import java.util.HashMap; +import java.util.Map; + +@Order(100) +public class ParamNameHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { + + private final Map paramValues = new HashMap<>(); + private final ConversionService conversionService; + + ParamNameHandlerMethodArgumentResolver(Map paramValues, ConversionService conversionService) { + this.paramValues.putAll(paramValues); + this.conversionService = conversionService; + } + + @Override + public boolean supportsParameter(MethodParameter parameter) { + String parameterName = parameter.getParameterName(); + if (parameterName == null) { + return false; + } + Class sourceType = paramValues.get(parameterName) != null ? paramValues.get(parameterName).getClass() + : null; + return paramValues.containsKey(parameterName) && conversionService + .canConvert(sourceType, parameter.getParameterType()); + } + + @Override + public Object resolveArgument(MethodParameter parameter, Message message) { + return conversionService.convert(paramValues.get(parameter.getParameterName()), parameter.getParameterType()); + } + +} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/ResultHandlerConfig.java b/spring-shell-core/src/main/java/org/springframework/shell/result/ResultHandlerConfig.java index 12f8e5362..e88c6a6d1 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/result/ResultHandlerConfig.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/result/ResultHandlerConfig.java @@ -23,7 +23,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.shell.TerminalSizeAware; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; import org.springframework.shell.command.CommandExceptionResolver; import org.springframework.shell.command.CommandParserExceptionResolver; import org.springframework.shell.context.ShellContext; diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/ThrowableResultHandler.java b/spring-shell-core/src/main/java/org/springframework/shell/result/ThrowableResultHandler.java index daa2486ab..7834523eb 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/result/ThrowableResultHandler.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/result/ThrowableResultHandler.java @@ -25,7 +25,7 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.shell.ResultHandler; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; import org.springframework.shell.context.InteractionMode; import org.springframework.shell.context.ShellContext; import org.springframework.shell.jline.InteractiveShellRunner; diff --git a/spring-shell-core/src/test/java/org/springframework/shell/ShellTests.java b/spring-shell-core/src/test/java/org/springframework/shell/ShellTests.java index f91742921..025440fb4 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/ShellTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/ShellTests.java @@ -28,7 +28,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; import org.springframework.shell.command.CommandRegistration; import org.springframework.shell.completion.RegistrationOptionsCompletionResolver; diff --git a/spring-shell-core/src/test/java/org/springframework/shell/command/CommandCatalogTests.java b/spring-shell-core/src/test/java/org/springframework/shell/command/CommandCatalogTests.java index b0664c33b..249dfc294 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/command/CommandCatalogTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/command/CommandCatalogTests.java @@ -20,6 +20,7 @@ import java.util.List; import org.junit.jupiter.api.Test; +import org.springframework.shell.command.catalog.CommandCatalog; import static org.assertj.core.api.Assertions.assertThat; diff --git a/spring-shell-core/src/test/java/org/springframework/shell/command/CommandExecutionTests.java b/spring-shell-core/src/test/java/org/springframework/shell/command/CommandExecutionTests.java index e77744355..131bb3cb4 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/command/CommandExecutionTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/command/CommandExecutionTests.java @@ -29,7 +29,9 @@ import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; import org.springframework.shell.Availability; import org.springframework.shell.CommandNotCurrentlyAvailable; -import org.springframework.shell.command.CommandExecution.CommandParserExceptionsException; +import org.springframework.shell.command.catalog.CommandCatalog; +import org.springframework.shell.command.execution.CommandExecution; +import org.springframework.shell.command.execution.CommandExecution.CommandParserExceptionsException; import org.springframework.shell.command.CommandRegistration.OptionArity; import static org.assertj.core.api.Assertions.assertThat; diff --git a/spring-shell-core/src/test/java/org/springframework/shell/command/CommandParserExceptionResolverTests.java b/spring-shell-core/src/test/java/org/springframework/shell/command/CommandParserExceptionResolverTests.java index 92419ac32..d3b1a83a0 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/command/CommandParserExceptionResolverTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/command/CommandParserExceptionResolverTests.java @@ -20,7 +20,7 @@ import org.junit.jupiter.api.Test; -import org.springframework.shell.command.CommandExecution.CommandParserExceptionsException; +import org.springframework.shell.command.execution.CommandExecution.CommandParserExceptionsException; import org.springframework.shell.command.CommandParser.CommandParserException; import static org.assertj.core.api.Assertions.assertThat; diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandCatalogSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandCatalogSnippets.java index 03dec5bbb..d1575d293 100644 --- a/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandCatalogSnippets.java +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/CommandCatalogSnippets.java @@ -18,8 +18,8 @@ import java.util.ArrayList; import java.util.List; -import org.springframework.shell.command.CommandCatalog; -import org.springframework.shell.command.CommandCatalogCustomizer; +import org.springframework.shell.command.catalog.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalogCustomizer; import org.springframework.shell.command.CommandRegistration; import org.springframework.shell.command.CommandResolver; diff --git a/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentFlowCommands.java b/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentFlowCommands.java index 696c1e001..79b463f0a 100644 --- a/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentFlowCommands.java +++ b/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentFlowCommands.java @@ -27,7 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; -import org.springframework.shell.command.CommandExecution.CommandParserExceptionsException; +import org.springframework.shell.command.execution.CommandExecution.CommandParserExceptionsException; import org.springframework.shell.command.CommandParser; import org.springframework.shell.command.CommandParser.CommandParserException; import org.springframework.shell.command.CommandRegistration; diff --git a/spring-shell-standard-commands/src/test/java/org/springframework/shell/standard/commands/GroupsInfoModelTests.java b/spring-shell-standard-commands/src/test/java/org/springframework/shell/standard/commands/GroupsInfoModelTests.java index 95a21e145..853ddc06c 100644 --- a/spring-shell-standard-commands/src/test/java/org/springframework/shell/standard/commands/GroupsInfoModelTests.java +++ b/spring-shell-standard-commands/src/test/java/org/springframework/shell/standard/commands/GroupsInfoModelTests.java @@ -18,7 +18,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; import org.springframework.shell.command.CommandRegistration; import static org.assertj.core.api.Assertions.assertThat; diff --git a/spring-shell-standard-commands/src/test/java/org/springframework/shell/standard/commands/HelpTests.java b/spring-shell-standard-commands/src/test/java/org/springframework/shell/standard/commands/HelpTests.java index 563b6f33c..564cab6a6 100644 --- a/spring-shell-standard-commands/src/test/java/org/springframework/shell/standard/commands/HelpTests.java +++ b/spring-shell-standard-commands/src/test/java/org/springframework/shell/standard/commands/HelpTests.java @@ -36,7 +36,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; import org.springframework.shell.command.CommandRegistration; import org.springframework.shell.standard.ShellComponent; import org.springframework.shell.standard.ShellMethod; diff --git a/spring-shell-standard/src/main/java/org/springframework/shell/standard/AbstractShellComponent.java b/spring-shell-standard/src/main/java/org/springframework/shell/standard/AbstractShellComponent.java index cd584ae4a..2eec37106 100644 --- a/spring-shell-standard/src/main/java/org/springframework/shell/standard/AbstractShellComponent.java +++ b/spring-shell-standard/src/main/java/org/springframework/shell/standard/AbstractShellComponent.java @@ -27,7 +27,7 @@ import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.ResourceLoader; import org.springframework.shell.Shell; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; import org.springframework.shell.completion.CompletionResolver; import org.springframework.shell.style.TemplateExecutor; import org.springframework.shell.style.ThemeResolver; diff --git a/spring-shell-standard/src/main/java/org/springframework/shell/standard/CommandValueProvider.java b/spring-shell-standard/src/main/java/org/springframework/shell/standard/CommandValueProvider.java index 2c4827157..965c84eb1 100644 --- a/spring-shell-standard/src/main/java/org/springframework/shell/standard/CommandValueProvider.java +++ b/spring-shell-standard/src/main/java/org/springframework/shell/standard/CommandValueProvider.java @@ -21,7 +21,7 @@ import org.springframework.shell.CompletionContext; import org.springframework.shell.CompletionProposal; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; /** * A {@link ValueProvider} that can be used to auto-complete names of shell commands. diff --git a/spring-shell-standard/src/main/java/org/springframework/shell/standard/StandardMethodTargetRegistrar.java b/spring-shell-standard/src/main/java/org/springframework/shell/standard/StandardMethodTargetRegistrar.java index a55144ee6..350f997f8 100644 --- a/spring-shell-standard/src/main/java/org/springframework/shell/standard/StandardMethodTargetRegistrar.java +++ b/spring-shell-standard/src/main/java/org/springframework/shell/standard/StandardMethodTargetRegistrar.java @@ -40,7 +40,7 @@ import org.springframework.shell.CompletionProposal; import org.springframework.shell.MethodTargetRegistrar; import org.springframework.shell.Utils; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; import org.springframework.shell.command.CommandRegistration; import org.springframework.shell.command.CommandRegistration.Builder; import org.springframework.shell.command.CommandRegistration.OptionArity; diff --git a/spring-shell-standard/src/main/java/org/springframework/shell/standard/completion/AbstractCompletions.java b/spring-shell-standard/src/main/java/org/springframework/shell/standard/completion/AbstractCompletions.java index 8a1d862e0..ec8dc3309 100644 --- a/spring-shell-standard/src/main/java/org/springframework/shell/standard/completion/AbstractCompletions.java +++ b/spring-shell-standard/src/main/java/org/springframework/shell/standard/completion/AbstractCompletions.java @@ -37,7 +37,7 @@ import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.shell.Utils; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; import org.springframework.shell.command.CommandRegistration; import org.springframework.util.FileCopyUtils; import org.springframework.util.LinkedMultiValueMap; diff --git a/spring-shell-standard/src/main/java/org/springframework/shell/standard/completion/BashCompletions.java b/spring-shell-standard/src/main/java/org/springframework/shell/standard/completion/BashCompletions.java index 291bbf481..d161f3ce0 100644 --- a/spring-shell-standard/src/main/java/org/springframework/shell/standard/completion/BashCompletions.java +++ b/spring-shell-standard/src/main/java/org/springframework/shell/standard/completion/BashCompletions.java @@ -16,7 +16,7 @@ package org.springframework.shell.standard.completion; import org.springframework.core.io.ResourceLoader; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; /** * Completion script generator for a {@code bash}. diff --git a/spring-shell-standard/src/test/java/org/springframework/shell/standard/CommandValueProviderTests.java b/spring-shell-standard/src/test/java/org/springframework/shell/standard/CommandValueProviderTests.java index 4d2de9e12..2778a02ff 100644 --- a/spring-shell-standard/src/test/java/org/springframework/shell/standard/CommandValueProviderTests.java +++ b/spring-shell-standard/src/test/java/org/springframework/shell/standard/CommandValueProviderTests.java @@ -27,7 +27,7 @@ import org.springframework.shell.CompletionContext; import org.springframework.shell.CompletionProposal; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; import org.springframework.shell.command.CommandRegistration; import static org.assertj.core.api.Assertions.assertThat; diff --git a/spring-shell-standard/src/test/java/org/springframework/shell/standard/StandardMethodTargetRegistrarTests.java b/spring-shell-standard/src/test/java/org/springframework/shell/standard/StandardMethodTargetRegistrarTests.java index fe7885e69..1639cfcf6 100644 --- a/spring-shell-standard/src/test/java/org/springframework/shell/standard/StandardMethodTargetRegistrarTests.java +++ b/spring-shell-standard/src/test/java/org/springframework/shell/standard/StandardMethodTargetRegistrarTests.java @@ -24,7 +24,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.shell.Availability; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; import org.springframework.shell.command.CommandRegistration; import org.springframework.shell.context.DefaultShellContext; import org.springframework.shell.context.InteractionMode; diff --git a/spring-shell-standard/src/test/java/org/springframework/shell/standard/completion/AbstractCompletionsTests.java b/spring-shell-standard/src/test/java/org/springframework/shell/standard/completion/AbstractCompletionsTests.java index cfcd083ed..f5052d244 100644 --- a/spring-shell-standard/src/test/java/org/springframework/shell/standard/completion/AbstractCompletionsTests.java +++ b/spring-shell-standard/src/test/java/org/springframework/shell/standard/completion/AbstractCompletionsTests.java @@ -19,7 +19,7 @@ import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; import org.springframework.shell.command.CommandRegistration; import org.springframework.shell.standard.ShellMethod; import org.springframework.shell.standard.ShellOption; diff --git a/spring-shell-standard/src/test/java/org/springframework/shell/standard/completion/BashCompletionsTests.java b/spring-shell-standard/src/test/java/org/springframework/shell/standard/completion/BashCompletionsTests.java index af374b092..8d062c16e 100644 --- a/spring-shell-standard/src/test/java/org/springframework/shell/standard/completion/BashCompletionsTests.java +++ b/spring-shell-standard/src/test/java/org/springframework/shell/standard/completion/BashCompletionsTests.java @@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.shell.command.CommandCatalog; +import org.springframework.shell.command.catalog.CommandCatalog; import org.springframework.shell.command.CommandContext; import org.springframework.shell.command.CommandRegistration;