@@ -37,16 +37,22 @@ public class MethodTarget {
|
|||||||
|
|
||||||
private final String help;
|
private final String help;
|
||||||
|
|
||||||
|
private final String group;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If not null, returns whether or not the command is currently available. Implementations must be idempotent.
|
* If not null, returns whether or not the command is currently available. Implementations must be idempotent.
|
||||||
*/
|
*/
|
||||||
private final Supplier<Availability> availabilityIndicator;
|
private final Supplier<Availability> availabilityIndicator;
|
||||||
|
|
||||||
public MethodTarget(Method method, Object bean, String help) {
|
public MethodTarget(Method method, Object bean, String help) {
|
||||||
this(method, bean, help, null);
|
this(method, bean, help, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MethodTarget(Method method, Object bean, String help, Supplier<Availability> availabilityIndicator) {
|
public MethodTarget(Method method, Object bean, String help, Supplier<Availability> availabilityIndicator) {
|
||||||
|
this(method, bean, help, null, availabilityIndicator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodTarget(Method method, Object bean, String help, String group, Supplier<Availability> availabilityIndicator) {
|
||||||
Assert.notNull(method, "Method cannot be null");
|
Assert.notNull(method, "Method cannot be null");
|
||||||
Assert.notNull(bean, "Bean cannot be null");
|
Assert.notNull(bean, "Bean cannot be null");
|
||||||
Assert.hasText(help, String.format("Help cannot be blank when trying to define command based on '%s'", method));
|
Assert.hasText(help, String.format("Help cannot be blank when trying to define command based on '%s'", method));
|
||||||
@@ -54,6 +60,7 @@ public class MethodTarget {
|
|||||||
this.method = method;
|
this.method = method;
|
||||||
this.bean = bean;
|
this.bean = bean;
|
||||||
this.help = help;
|
this.help = help;
|
||||||
|
this.group = group != null ? group : "";
|
||||||
this.availabilityIndicator = availabilityIndicator != null ? availabilityIndicator : () -> Availability.available();
|
this.availabilityIndicator = availabilityIndicator != null ? availabilityIndicator : () -> Availability.available();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,21 +69,29 @@ public class MethodTarget {
|
|||||||
* in case of overloaded method.
|
* in case of overloaded method.
|
||||||
*/
|
*/
|
||||||
public static MethodTarget of(String name, Object bean, String help) {
|
public static MethodTarget of(String name, Object bean, String help) {
|
||||||
return of(name, bean, help, null);
|
return of(name, bean, help, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a MethodTarget for the unique method named {@literal name} on the given object. Fails with an exception
|
* Construct a MethodTarget for the unique method named {@literal name} on the given object. Fails with an exception
|
||||||
* in case of overloaded method.
|
* in case of overloaded method.
|
||||||
*/
|
*/
|
||||||
public static MethodTarget of(String name, Object bean, String help, Supplier<Availability> availabilityIndicator) {
|
public static MethodTarget of(String name, Object bean, String help, String group) {
|
||||||
|
return of(name, bean, help, group, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a MethodTarget for the unique method named {@literal name} on the given object. Fails with an exception
|
||||||
|
* in case of overloaded method.
|
||||||
|
*/
|
||||||
|
public static MethodTarget of(String name, Object bean, String help, String group, Supplier<Availability> availabilityIndicator) {
|
||||||
Set<Method> found = new HashSet<>();
|
Set<Method> found = new HashSet<>();
|
||||||
ReflectionUtils.doWithMethods(bean.getClass(), found::add, m -> m.getName().equals(name));
|
ReflectionUtils.doWithMethods(bean.getClass(), found::add, m -> m.getName().equals(name));
|
||||||
if (found.size() != 1) {
|
if (found.size() != 1) {
|
||||||
throw new IllegalArgumentException(String.format("Could not find unique method named '%s' on object of class %s. Found %s",
|
throw new IllegalArgumentException(String.format("Could not find unique method named '%s' on object of class %s. Found %s",
|
||||||
name, bean.getClass(), found));
|
name, bean.getClass(), found));
|
||||||
}
|
}
|
||||||
return new MethodTarget(found.iterator().next(), bean, help, availabilityIndicator);
|
return new MethodTarget(found.iterator().next(), bean, help, group, availabilityIndicator);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Method getMethod() {
|
public Method getMethod() {
|
||||||
@@ -91,6 +106,10 @@ public class MethodTarget {
|
|||||||
return help;
|
return help;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getGroup() {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
public Availability getAvailability() {
|
public Availability getAvailability() {
|
||||||
return availabilityIndicator.get();
|
return availabilityIndicator.get();
|
||||||
}
|
}
|
||||||
@@ -104,6 +123,7 @@ public class MethodTarget {
|
|||||||
|
|
||||||
if (!method.equals(that.method)) return false;
|
if (!method.equals(that.method)) return false;
|
||||||
if (!bean.equals(that.bean)) return false;
|
if (!bean.equals(that.bean)) return false;
|
||||||
|
if (!group.equals(that.group)) return false;
|
||||||
return help.equals(that.help);
|
return help.equals(that.help);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -113,6 +133,7 @@ public class MethodTarget {
|
|||||||
int result = method.hashCode();
|
int result = method.hashCode();
|
||||||
result = 31 * result + bean.hashCode();
|
result = 31 * result + bean.hashCode();
|
||||||
result = 31 * result + help.hashCode();
|
result = 31 * result + help.hashCode();
|
||||||
|
result = 31 * result + group.hashCode();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,14 +16,20 @@
|
|||||||
|
|
||||||
package org.springframework.shell.standard.commands;
|
package org.springframework.shell.standard.commands;
|
||||||
|
|
||||||
|
import static java.util.stream.Collectors.groupingBy;
|
||||||
import static java.util.stream.Collectors.mapping;
|
import static java.util.stream.Collectors.mapping;
|
||||||
import static java.util.stream.Collectors.toCollection;
|
import static java.util.stream.Collectors.toCollection;
|
||||||
|
import static java.util.stream.Collectors.toMap;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.SortedMap;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.TreeMap;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -267,39 +273,49 @@ public class Help {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private CharSequence listCommands() {
|
private CharSequence listCommands() {
|
||||||
Map<String, Set<String>> groupedByMethodTarget = commandRegistry.listCommands().entrySet().stream()
|
Map<String, MethodTarget> commandsByName = commandRegistry.listCommands();
|
||||||
.collect(Collectors.groupingBy(e -> e.getValue().getHelp(), // Use help() as the grouping key
|
|
||||||
mapping(Map.Entry::getKey, toCollection(TreeSet::new)))); // accumulate the command 'names' into
|
SortedMap<String, Map<String, MethodTarget>> commandsByGroupAndName = commandsByName.entrySet().stream()
|
||||||
// a sorted set
|
.collect(groupingBy(e -> e.getValue().getGroup(), TreeMap::new, // group by and sort by command group
|
||||||
|
toMap(Entry::getKey, Entry::getValue)));
|
||||||
|
|
||||||
// Then display commands, sorted alphabetically by their first alias
|
|
||||||
AttributedStringBuilder result = new AttributedStringBuilder();
|
AttributedStringBuilder result = new AttributedStringBuilder();
|
||||||
result.append("AVAILABLE COMMANDS\n\n", AttributedStyle.BOLD);
|
result.append("AVAILABLE COMMANDS\n\n", AttributedStyle.BOLD);
|
||||||
|
|
||||||
groupedByMethodTarget.entrySet().stream()
|
// display groups, sorted alphabetically, "Default" first
|
||||||
.sorted(sortByFirstElement())
|
commandsByGroupAndName.forEach((group, commandsInGroup) -> {
|
||||||
.forEach(e -> result.append(isAvailable(e) ? " " : " * ")
|
result.append("".equals(group) ? "Default" : group, AttributedStyle.BOLD).append('\n');
|
||||||
.append(e.getValue().stream().collect(Collectors.joining(", ")), AttributedStyle.BOLD)
|
|
||||||
|
Map<MethodTarget, SortedSet<String>> commandNamesByMethod = commandsInGroup.entrySet().stream()
|
||||||
|
.collect(groupingBy(Entry::getValue, // group by command method
|
||||||
|
mapping(Entry::getKey, toCollection(TreeSet::new)))); // sort command names
|
||||||
|
|
||||||
|
// display commands, sorted alphabetically by their first alias
|
||||||
|
commandNamesByMethod.entrySet().stream().sorted(sortByFirstCommandName()).forEach(e -> {
|
||||||
|
result
|
||||||
|
.append(isAvailable(e.getKey()) ? " " : " * ")
|
||||||
|
.append(String.join(", ", e.getValue()), AttributedStyle.BOLD)
|
||||||
.append(": ")
|
.append(": ")
|
||||||
.append(e.getKey())
|
.append(e.getKey().getHelp())
|
||||||
.append('\n'));
|
.append('\n');
|
||||||
|
});
|
||||||
|
|
||||||
groupedByMethodTarget.entrySet().stream()
|
result.append('\n');
|
||||||
.filter(e -> !isAvailable(e))
|
});
|
||||||
.findAny()
|
|
||||||
.ifPresent(e -> result.append(
|
|
||||||
"\nCommands marked with (*) are currently unavailable.\nType `help <command>` to learn more.\n"));
|
|
||||||
|
|
||||||
return result.append("\n");
|
if (commandsByName.values().stream().distinct().anyMatch(m -> !isAvailable(m))) {
|
||||||
|
result.append("Commands marked with (*) are currently unavailable.\nType `help <command>` to learn more.\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Comparator<Map.Entry<String, Set<String>>> sortByFirstElement() {
|
private Comparator<Entry<MethodTarget, SortedSet<String>>> sortByFirstCommandName() {
|
||||||
return Comparator.comparing(e -> e.getValue().iterator().next());
|
return Comparator.comparing(e -> e.getValue().first());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAvailable(Map.Entry<String, Set<String>> entry) {
|
private boolean isAvailable(MethodTarget methodTarget) {
|
||||||
String commandName = entry.getValue().iterator().next();
|
return methodTarget.getAvailability().isAvailable();
|
||||||
return commandRegistry.listCommands().get(commandName).getAvailability().isAvailable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void appendUnderlinedFormal(AttributedStringBuilder result, ParameterDescription description) {
|
private void appendUnderlinedFormal(AttributedStringBuilder result, ParameterDescription description) {
|
||||||
|
|||||||
@@ -125,6 +125,12 @@ public class HelpTest {
|
|||||||
methodTarget = MethodTarget.of("thirdCommand", commands(), "The last command.");
|
methodTarget = MethodTarget.of("thirdCommand", commands(), "The last command.");
|
||||||
result.put("third-command", methodTarget);
|
result.put("third-command", methodTarget);
|
||||||
|
|
||||||
|
methodTarget = MethodTarget.of("firstCommandInGroup", commands(), "The first command in a separate group.", "Example Group");
|
||||||
|
result.put("first-group-command", methodTarget);
|
||||||
|
|
||||||
|
methodTarget = MethodTarget.of("secondCommandInGroup", commands(), "The second command in a separate group.", "Example Group");
|
||||||
|
result.put("second-group-command", methodTarget);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -169,5 +175,15 @@ public class HelpTest {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ShellMethod
|
||||||
|
public void firstCommandInGroup() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ShellMethod
|
||||||
|
public void secondCommandInGroup() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
AVAILABLE COMMANDS&
|
AVAILABLE COMMANDS&
|
||||||
&
|
&
|
||||||
|
Default&
|
||||||
1st-command, first-command: A rather extensive description of some command.&
|
1st-command, first-command: A rather extensive description of some command.&
|
||||||
second-command, yet-another-command: The second command. This one is known under several aliases as well.&
|
second-command, yet-another-command: The second command. This one is known under several aliases as well.&
|
||||||
third-command: The last command.&
|
third-command: The last command.&
|
||||||
&
|
&
|
||||||
|
Example Group&
|
||||||
|
first-group-command: The first command in a separate group.&
|
||||||
|
second-group-command: The second command in a separate group.&
|
||||||
|
&
|
||||||
|
|||||||
@@ -53,4 +53,11 @@ public @interface ShellMethod {
|
|||||||
*/
|
*/
|
||||||
String prefix() default "--";
|
String prefix() default "--";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The command group which this command belongs to. The command group is used when printing a list of
|
||||||
|
* commands to group related commands.
|
||||||
|
* @return name of the command group
|
||||||
|
*/
|
||||||
|
String group() default "";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,9 +61,10 @@ public class StandardMethodTargetRegistrar implements MethodTargetRegistrar {
|
|||||||
if (keys.length == 0) {
|
if (keys.length == 0) {
|
||||||
keys = new String[] { Utils.unCamelify(method.getName()) };
|
keys = new String[] { Utils.unCamelify(method.getName()) };
|
||||||
}
|
}
|
||||||
|
String group = shellMapping.group();
|
||||||
for (String key : keys) {
|
for (String key : keys) {
|
||||||
Supplier<Availability> availabilityIndicator = findAvailabilityIndicator(keys, bean, method);
|
Supplier<Availability> availabilityIndicator = findAvailabilityIndicator(keys, bean, method);
|
||||||
MethodTarget target = new MethodTarget(method, bean, shellMapping.value(), availabilityIndicator);
|
MethodTarget target = new MethodTarget(method, bean, shellMapping.value(), group, availabilityIndicator);
|
||||||
registry.register(key, target);
|
registry.register(key, target);
|
||||||
commands.put(key, target);
|
commands.put(key, target);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user