Skip to content

Commit c656745

Browse files
committed
Add support for hidden commands
1 parent 7cbff27 commit c656745

File tree

13 files changed

+184
-21
lines changed

13 files changed

+184
-21
lines changed

spring-shell-core/src/main/java/org/springframework/shell/core/command/AbstractCommand.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,24 @@ public abstract class AbstractCommand implements Command {
3737

3838
private final String group;
3939

40+
private final boolean hidden;
41+
4042
private List<String> aliases = new ArrayList<>();
4143

4244
public AbstractCommand(String name, String description) {
43-
this(name, description, "", "");
45+
this(name, description, "", "", false);
4446
}
4547

4648
public AbstractCommand(String name, String description, String group) {
47-
this(name, description, group, "");
49+
this(name, description, group, "", false);
4850
}
4951

50-
public AbstractCommand(String name, String description, String group, String help) {
52+
public AbstractCommand(String name, String description, String group, String help, boolean hidden) {
5153
this.name = name;
5254
this.description = description;
5355
this.group = group;
5456
this.help = help;
57+
this.hidden = hidden;
5558
}
5659

5760
@Override
@@ -74,6 +77,11 @@ public String getHelp() {
7477
return this.help;
7578
}
7679

80+
@Override
81+
public boolean isHidden() {
82+
return this.hidden;
83+
}
84+
7785
@Override
7886
public List<String> getAliases() {
7987
return this.aliases;

spring-shell-core/src/main/java/org/springframework/shell/core/command/Command.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ default String getHelp() {
5858
return getName() + "(" + String.join(",", getAliases()) + "): " + getDescription();
5959
}
6060

61+
/**
62+
* Check if the command is hidden.
63+
* @return true if the command is hidden, false otherwise
64+
*/
65+
default boolean isHidden() {
66+
return false;
67+
}
68+
6169
/**
6270
* Get the group of the command.
6371
* @return the group of the command
@@ -106,6 +114,8 @@ final class Builder {
106114

107115
private String help = "";
108116

117+
private boolean hidden = false;
118+
109119
private List<String> aliases = new ArrayList<>();
110120

111121
public Builder name(String name) {
@@ -128,6 +138,11 @@ public Builder group(String group) {
128138
return this;
129139
}
130140

141+
public Builder hidden(boolean hidden) {
142+
this.hidden = hidden;
143+
return this;
144+
}
145+
131146
public Builder aliases(String... aliases) {
132147
this.aliases = Arrays.asList(aliases);
133148
return this;
@@ -136,7 +151,7 @@ public Builder aliases(String... aliases) {
136151
public AbstractCommand execute(Consumer<CommandContext> commandExecutor) {
137152
Assert.hasText(name, "'name' must be specified");
138153

139-
ConsumerCommandAdapter command = new ConsumerCommandAdapter(name, description, group, help,
154+
ConsumerCommandAdapter command = new ConsumerCommandAdapter(name, description, group, help, hidden,
140155
commandExecutor);
141156

142157
initAliases(command);
@@ -147,7 +162,7 @@ public AbstractCommand execute(Consumer<CommandContext> commandExecutor) {
147162
public AbstractCommand execute(Function<CommandContext, String> commandExecutor) {
148163
Assert.hasText(name, "'name' must be specified");
149164

150-
FunctionCommandAdapter command = new FunctionCommandAdapter(name, description, group, help,
165+
FunctionCommandAdapter command = new FunctionCommandAdapter(name, description, group, help, hidden,
151166
commandExecutor);
152167

153168
initAliases(command);

spring-shell-core/src/main/java/org/springframework/shell/core/command/CommandRegistry.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,18 @@ public Set<Command> getCommands() {
5858
}
5959

6060
@Nullable public Command getCommandByName(String name) {
61-
return commands.stream().filter(command -> command.getName().equals(name)).findFirst().orElse(null);
61+
return commands.stream()
62+
.filter(command -> !command.isHidden())
63+
.filter(command -> command.getName().equals(name))
64+
.findFirst()
65+
.orElse(null);
6266
}
6367

6468
public List<Command> getCommandsByPrefix(String prefix) {
65-
return commands.stream().filter(command -> command.getName().startsWith(prefix)).toList();
69+
return commands.stream()
70+
.filter(command -> !command.isHidden())
71+
.filter(command -> command.getName().startsWith(prefix))
72+
.toList();
6673
}
6774

6875
public void registerCommand(Command command) {

spring-shell-core/src/main/java/org/springframework/shell/core/command/adapter/ConsumerCommandAdapter.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ public class ConsumerCommandAdapter extends AbstractCommand {
3737
* @param description the description of the command
3838
* @param group the group of the command
3939
* @param help the help text of the command
40+
* @param hidden whether the command is hidden or not
4041
* @param commandExecutor the consumer to adapt as a command
4142
*/
42-
public ConsumerCommandAdapter(String name, String description, String group, String help,
43+
public ConsumerCommandAdapter(String name, String description, String group, String help, boolean hidden,
4344
Consumer<CommandContext> commandExecutor) {
44-
super(name, description, group, help);
45+
super(name, description, group, help, hidden);
4546
this.commandExecutor = commandExecutor;
4647
}
4748

spring-shell-core/src/main/java/org/springframework/shell/core/command/adapter/FunctionCommandAdapter.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,12 @@ public class FunctionCommandAdapter extends AbstractCommand {
3939
* @param description the description of the command
4040
* @param group the group of the command
4141
* @param help the help text of the command
42+
* @param hidden whether the command is hidden or not
4243
* @param commandFunction the function to adapt as a command
4344
*/
44-
public FunctionCommandAdapter(String name, String description, String group, String help,
45+
public FunctionCommandAdapter(String name, String description, String group, String help, boolean hidden,
4546
Function<CommandContext, String> commandFunction) {
46-
super(name, description, group, help);
47+
super(name, description, group, help, hidden);
4748
this.commandFunction = commandFunction;
4849
}
4950

spring-shell-core/src/main/java/org/springframework/shell/core/command/adapter/MethodInvokerCommandAdapter.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,14 @@ public class MethodInvokerCommandAdapter extends AbstractCommand {
5555
* @param description the description of the command
5656
* @param group the group of the command
5757
* @param help the help text of the command
58+
* @param hidden whether the command is hidden or not
5859
* @param method the method to invoke
5960
* @param targetObject the target object on which to invoke the method
6061
* @param conversionService the conversion service to use for parameter conversion
6162
*/
62-
public MethodInvokerCommandAdapter(String name, String description, String group, String help, Method method,
63-
Object targetObject, ConfigurableConversionService conversionService) {
64-
super(name, description, group, help);
63+
public MethodInvokerCommandAdapter(String name, String description, String group, String help, boolean hidden,
64+
Method method, Object targetObject, ConfigurableConversionService conversionService) {
65+
super(name, description, group, help, hidden);
6566
this.method = method;
6667
this.targetObject = targetObject;
6768
this.conversionService = conversionService;

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,12 @@ public Command getObject() {
5959
org.springframework.shell.core.command.annotation.Command command = MergedAnnotations.from(this.method)
6060
.get(org.springframework.shell.core.command.annotation.Command.class)
6161
.synthesize();
62-
// TODO handle aliases, hidden flag.
62+
// TODO handle aliases
6363
String name = String.join(" ", command.name());
6464
String description = command.description();
6565
String help = command.help();
6666
String group = command.group();
67+
boolean hidden = command.hidden();
6768
log.debug("Creating command bean for method '" + this.method + "' with name '" + name + "'");
6869
Class<?> declaringClass = this.method.getDeclaringClass();
6970
Object targetObject;
@@ -86,7 +87,7 @@ Ensure that the declaring class is annotated with a Spring stereotype annotation
8687
catch (BeansException e) {
8788
log.debug("No ConfigurableConversionService bean found, using a default conversion service.");
8889
}
89-
return new MethodInvokerCommandAdapter(name, description, group, help, this.method, targetObject,
90+
return new MethodInvokerCommandAdapter(name, description, group, help, hidden, this.method, targetObject,
9091
configurableConversionService);
9192
}
9293

spring-shell-core/src/main/java/org/springframework/shell/core/utils/CommandUtils.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public class CommandUtils {
4242
}
4343

4444
/**
45-
* Get a formatted string of available commands from the command registry.
45+
* Get a formatted string of available non-hidden commands from the command registry.
4646
* @param commandRegistry the command registry
4747
* @return a string of available commands with their descriptions
4848
*/
@@ -51,6 +51,7 @@ public static String formatAvailableCommands(CommandRegistry commandRegistry) {
5151
stringBuilder.append(System.lineSeparator());
5252
List<String> groups = commandRegistry.getCommands()
5353
.stream()
54+
.filter(command -> !command.isHidden())
5455
.map(Command::getGroup)
5556
.distinct()
5657
.sorted()
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.shell.core.command;
17+
18+
import org.junit.jupiter.api.Test;
19+
20+
import static org.junit.jupiter.api.Assertions.assertEquals;
21+
import static org.junit.jupiter.api.Assertions.assertNotNull;
22+
import static org.junit.jupiter.api.Assertions.assertNull;
23+
import static org.junit.jupiter.api.Assertions.assertTrue;
24+
25+
class CommandRegistryTests {
26+
27+
private final CommandRegistry commandRegistry = new CommandRegistry();
28+
29+
@Test
30+
void getCommandByName() {
31+
// given
32+
commandRegistry.registerCommand(new Command() {
33+
34+
@Override
35+
public String getName() {
36+
return "cmd1";
37+
}
38+
39+
@Override
40+
public ExitStatus execute(CommandContext commandContext) {
41+
return ExitStatus.OK;
42+
}
43+
});
44+
commandRegistry.registerCommand(new Command() {
45+
46+
@Override
47+
public String getName() {
48+
return "cmd2";
49+
}
50+
51+
@Override
52+
public boolean isHidden() {
53+
return true;
54+
}
55+
56+
@Override
57+
public ExitStatus execute(CommandContext commandContext) {
58+
return ExitStatus.OK;
59+
}
60+
});
61+
62+
// when
63+
Command cmd1 = commandRegistry.getCommandByName("cmd1");
64+
Command cmd2 = commandRegistry.getCommandByName("cmd2");
65+
Command cmd3 = commandRegistry.getCommandByName("cmd3");
66+
67+
// then
68+
assertNotNull(cmd1);
69+
assertNull(cmd2); // hidden command should not be returned
70+
assertNull(cmd3); // non-existing command should return null
71+
}
72+
73+
@Test
74+
void getCommandsByPrefix() {
75+
// given
76+
commandRegistry.registerCommand(new Command() {
77+
78+
@Override
79+
public String getName() {
80+
return "start";
81+
}
82+
83+
@Override
84+
public ExitStatus execute(CommandContext commandContext) {
85+
return ExitStatus.OK;
86+
}
87+
});
88+
commandRegistry.registerCommand(new Command() {
89+
90+
@Override
91+
public String getName() {
92+
return "status";
93+
}
94+
95+
@Override
96+
public ExitStatus execute(CommandContext commandContext) {
97+
return ExitStatus.OK;
98+
}
99+
});
100+
commandRegistry.registerCommand(new Command() {
101+
102+
@Override
103+
public String getName() {
104+
return "stop";
105+
}
106+
107+
@Override
108+
public boolean isHidden() {
109+
return true;
110+
}
111+
112+
@Override
113+
public ExitStatus execute(CommandContext commandContext) {
114+
return ExitStatus.OK;
115+
}
116+
});
117+
118+
// when
119+
var commands = commandRegistry.getCommandsByPrefix("st");
120+
121+
// then
122+
assertEquals(2, commands.size());
123+
assertTrue(commands.stream().anyMatch(cmd -> cmd.getName().equals("start")));
124+
assertTrue(commands.stream().anyMatch(cmd -> cmd.getName().equals("status")));
125+
}
126+
127+
}

spring-shell-samples/spring-shell-sample-petclinic/src/main/java/org/springframework/shell/samples/petclinic/commands/OwnerDetailsCommand.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public class OwnerDetailsCommand extends AbstractCommand {
3636
private final JdbcClient jdbcClient;
3737

3838
public OwnerDetailsCommand(JdbcClient jdbcClient) {
39-
super("owners info", "Show details of a given owner", "Owners", "show the details of a given owner");
39+
super("owners info", "Show details of a given owner", "Owners", "show the details of a given owner", false);
4040
this.jdbcClient = jdbcClient;
4141
}
4242

0 commit comments

Comments
 (0)