diff --git a/spring-shell-core-autoconfigure/src/main/java/org/springframework/shell/core/autoconfigure/SpringShellProperties.java b/spring-shell-core-autoconfigure/src/main/java/org/springframework/shell/core/autoconfigure/SpringShellProperties.java index 2bc6f3aad..9d2619bf6 100644 --- a/spring-shell-core-autoconfigure/src/main/java/org/springframework/shell/core/autoconfigure/SpringShellProperties.java +++ b/spring-shell-core-autoconfigure/src/main/java/org/springframework/shell/core/autoconfigure/SpringShellProperties.java @@ -245,6 +245,8 @@ public void setEnabled(boolean enabled) { public static class Command { + private String prefix = ""; + private HelpCommand help = new HelpCommand(); private ClearCommand clear = new ClearCommand(); @@ -255,6 +257,14 @@ public static class Command { private VersionCommand version = new VersionCommand(); + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + public void setHelp(HelpCommand help) { this.help = help; } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/core/command/annotation/support/CommandFactoryBean.java b/spring-shell-core/src/main/java/org/springframework/shell/core/command/annotation/support/CommandFactoryBean.java index 30ac91785..82dcd77d3 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/core/command/annotation/support/CommandFactoryBean.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/core/command/annotation/support/CommandFactoryBean.java @@ -91,7 +91,7 @@ public Command getObject() { } // get command metadata - String name = groupPrefix + (groupPrefix.isEmpty() ? "" : " ") + String.join(" ", command.name()); + var name = resolveName(groupPrefix, command); name = name.isEmpty() ? Utils.unCamelify(this.method.getName()) : name; String description = command.description(); description = description.isEmpty() ? "N/A" : description; @@ -138,6 +138,19 @@ public Command getObject() { return methodInvokerCommandAdapter; } + private String resolveName(String groupPrefix, org.springframework.shell.core.command.annotation.Command command) { + String globalPrefix = this.applicationContext.getEnvironment().getProperty("spring.shell.command.prefix", ""); + List parts = new ArrayList<>(); + if (!globalPrefix.isEmpty()) { + parts.add(globalPrefix); + } + if (!groupPrefix.isEmpty()) { + parts.add(groupPrefix); + } + parts.add(String.join(" ", command.name())); + return String.join(" ", parts); + } + private List getCommandOptions() { List commandOptions = new ArrayList<>(); for (Parameter parameter : this.method.getParameters()) { diff --git a/spring-shell-core/src/test/java/org/springframework/shell/core/command/annotation/support/CommandFactoryBeanTests.java b/spring-shell-core/src/test/java/org/springframework/shell/core/command/annotation/support/CommandFactoryBeanTests.java index dfae79d8d..994abc714 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/core/command/annotation/support/CommandFactoryBeanTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/core/command/annotation/support/CommandFactoryBeanTests.java @@ -25,6 +25,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContext; import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.env.Environment; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterFactory; import org.springframework.core.convert.converter.GenericConverter; @@ -48,10 +49,17 @@ class CommandFactoryBeanTests { private static ApplicationContext mockApplicationContext() { + return mockApplicationContext(""); + } + + private static ApplicationContext mockApplicationContext(String globalPrefix) { ApplicationContext context = mock(ApplicationContext.class); when(context.getBeansOfType(Converter.class)).thenReturn(Collections.emptyMap()); when(context.getBeansOfType(GenericConverter.class)).thenReturn(Collections.emptyMap()); when(context.getBeansOfType(ConverterFactory.class)).thenReturn(Collections.emptyMap()); + Environment environment = mock(Environment.class); + when(environment.getProperty("spring.shell.command.prefix", "")).thenReturn(globalPrefix); + when(context.getEnvironment()).thenReturn(environment); return context; } @@ -181,6 +189,62 @@ private static void runConvertCommandAndAssert(ApplicationContext context) throw assertThat(ConverterTarget.lastSeen.text).isEqualTo("hello"); } + @Test + void globalPrefixAppliedWhenNoCommandGroupPrefix() throws Exception { + ApplicationContext context = mockApplicationContext("app"); + when(context.getBean(NoGroupCommands.class)).thenReturn(new NoGroupCommands()); + Method method = Arrays.stream(NoGroupCommands.class.getDeclaredMethods()) + .filter(m -> m.getName().equals("ping")) + .findFirst() + .orElseThrow(); + CommandFactoryBean factory = new CommandFactoryBean(method); + factory.setApplicationContext(context); + + org.springframework.shell.core.command.Command result = factory.getObject(); + + assertEquals("app ping", result.getName()); + } + + @Test + void globalAndCommandGroupPrefixesAreChained() throws Exception { + ApplicationContext context = mockApplicationContext("shell"); + when(context.getBean(GreetingCommands.class)).thenReturn(new GreetingCommands()); + Method method = Arrays.stream(GreetingCommands.class.getDeclaredMethods()) + .filter(m -> m.getName().equals("hi")) + .findFirst() + .orElseThrow(); + CommandFactoryBean factory = new CommandFactoryBean(method); + factory.setApplicationContext(context); + + org.springframework.shell.core.command.Command result = factory.getObject(); + + assertEquals("shell greeting hi", result.getName()); + } + + @Test + void noPrefixesLeaveNameUnchanged() throws Exception { + ApplicationContext context = mockApplicationContext(""); + when(context.getBean(NoGroupCommands.class)).thenReturn(new NoGroupCommands()); + Method method = Arrays.stream(NoGroupCommands.class.getDeclaredMethods()) + .filter(m -> m.getName().equals("ping")) + .findFirst() + .orElseThrow(); + CommandFactoryBean factory = new CommandFactoryBean(method); + factory.setApplicationContext(context); + + org.springframework.shell.core.command.Command result = factory.getObject(); + + assertEquals("ping", result.getName()); + } + + static class NoGroupCommands { + + @Command(name = "ping") + public void ping() { + } + + } + static class Message { String text;