Skip to content

Commit dc18b3d

Browse files
committed
Refine contribution #1360
- Fix global command prefix breaking default command names - Update documentation
1 parent 7f036b1 commit dc18b3d

3 files changed

Lines changed: 65 additions & 6 deletions

File tree

‎spring-shell-core/src/main/java/org/springframework/shell/core/command/annotation/support/CommandFactoryBean.java‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,9 @@ public Command getObject() {
9292
}
9393

9494
// get command metadata
95-
var name = resolveName(groupPrefix, command);
96-
name = name.isEmpty() ? Utils.unCamelify(this.method.getName()) : name;
95+
String baseName = String.join(" ", command.name());
96+
baseName = baseName.isEmpty() ? Utils.unCamelify(this.method.getName()) : baseName;
97+
String name = resolveName(groupPrefix, baseName);
9798
String description = command.description();
9899
description = description.isEmpty() ? "N/A" : description;
99100
String help = command.help();
@@ -139,7 +140,7 @@ public Command getObject() {
139140
return methodInvokerCommandAdapter;
140141
}
141142

142-
private String resolveName(String groupPrefix, org.springframework.shell.core.command.annotation.Command command) {
143+
private String resolveName(String groupPrefix, String baseName) {
143144
String globalPrefix = this.applicationContext.getEnvironment().getProperty("spring.shell.command.prefix", "");
144145
List<String> parts = new ArrayList<>();
145146
if (!globalPrefix.isEmpty()) {
@@ -148,7 +149,7 @@ private String resolveName(String groupPrefix, org.springframework.shell.core.co
148149
if (!groupPrefix.isEmpty()) {
149150
parts.add(groupPrefix);
150151
}
151-
parts.add(String.join(" ", command.name()));
152+
parts.add(baseName);
152153
return String.join(" ", parts);
153154
}
154155

‎spring-shell-core/src/test/java/org/springframework/shell/core/command/annotation/support/CommandFactoryBeanTests.java‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@
2525
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
2626
import org.springframework.context.ApplicationContext;
2727
import org.springframework.core.convert.TypeDescriptor;
28-
import org.springframework.core.env.Environment;
2928
import org.springframework.core.convert.converter.Converter;
3029
import org.springframework.core.convert.converter.ConverterFactory;
3130
import org.springframework.core.convert.converter.GenericConverter;
3231
import org.springframework.core.convert.support.ConfigurableConversionService;
33-
import org.springframework.shell.core.command.CommandContext;
32+
import org.springframework.core.env.Environment;
3433
import org.springframework.shell.core.command.CommandArgument;
34+
import org.springframework.shell.core.command.CommandContext;
3535
import org.springframework.shell.core.command.CommandOption;
3636
import org.springframework.shell.core.command.adapter.MethodInvokerCommandAdapter;
3737
import org.springframework.shell.core.command.annotation.Argument;
@@ -295,12 +295,32 @@ void noPrefixesLeaveNameUnchanged() throws Exception {
295295
assertEquals("ping", result.getName());
296296
}
297297

298+
@Test
299+
void globalPrefixIsChainedWithMethodNameFallback() throws Exception {
300+
ApplicationContext context = mockApplicationContext("app");
301+
when(context.getBean(NoGroupCommands.class)).thenReturn(new NoGroupCommands());
302+
Method method = Arrays.stream(NoGroupCommands.class.getDeclaredMethods())
303+
.filter(m -> m.getName().equals("unnamed"))
304+
.findFirst()
305+
.orElseThrow();
306+
CommandFactoryBean factory = new CommandFactoryBean(method);
307+
factory.setApplicationContext(context);
308+
309+
org.springframework.shell.core.command.Command result = factory.getObject();
310+
311+
assertEquals("app unnamed", result.getName());
312+
}
313+
298314
static class NoGroupCommands {
299315

300316
@Command(name = "ping")
301317
public void ping() {
302318
}
303319

320+
@Command
321+
public void unnamed() {
322+
}
323+
304324
}
305325

306326
static class Message {

‎spring-shell-docs/modules/ROOT/pages/commands/organize.adoc‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,41 @@ public class AuthenticationCommands {
6565
In this example, both the `login` and `logout` commands belong to the `Authentication Commands` group, and they will be displayed together in the help screen under that group. The `prefix` attribute specifies a common prefix for all commands in that group, which can be used to invoke the commands (e.g., `auth login` and `auth logout`).
6666

6767
NOTE: If a command provides a `group` attribute, it will take precedence over the group specified at the class level. This allows you to override the group for specific commands if needed.
68+
69+
[[global-command-prefix]]
70+
== Global Command Prefix
71+
72+
While `@CommandGroup` lets you namespace commands within a single class, the `spring.shell.command.prefix` property lets you apply a prefix to every user-defined command in the application at once. This is useful when embedding a Spring Shell application inside a larger system where all commands need to share a consistent namespace.
73+
74+
[source,yaml]
75+
----
76+
spring:
77+
shell:
78+
command:
79+
prefix: myapp
80+
----
81+
82+
When set, the global prefix is prepended to the name of every user-defined `@Command` method. If a `@CommandGroup` also defines a `prefix` on the declaring class, both are chained together, with the global prefix first, then the group prefix, then the command name:
83+
84+
|===
85+
| Global prefix | Group prefix | Command name | Resulting command
86+
87+
| `myapp`
88+
| (none)
89+
| `hello`
90+
| `myapp hello`
91+
92+
| `myapp`
93+
| `my-group`
94+
| `hello`
95+
| `myapp my-group hello`
96+
97+
| (none)
98+
| `my-group`
99+
| `hello`
100+
| `my-group hello`
101+
|===
102+
103+
Built-in commands (`help`, `clear`, `version`, and so on) are not affected by this property.
104+
105+
IMPORTANT: Do not choose a global prefix that matches the name of a built-in command (`help`, `clear`, `version`, `script`, `history`, `exit`/`quit`). Since every prefixed command name now starts with that word, invoking the built-in command with an argument becomes ambiguous. For example, if `spring.shell.command.prefix` is set to `help` and a command `hello` becomes `help hello`, then typing `help hello` invokes your `help hello` command directly instead of showing help for `hello` through the built-in `help` command.

0 commit comments

Comments
 (0)