Add predicate command filter option to the cloud help system

Signed-off-by: Irmo van den Berge <irmo.vandenberge@ziggo.nl>
This commit is contained in:
Irmo van den Berge 2020-12-20 20:30:30 +01:00 committed by Alexander Söderberg
parent 4556f12b6d
commit cabb7f426c
4 changed files with 168 additions and 5 deletions

View file

@ -38,13 +38,19 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate;
public final class CommandHelpHandler<C> { public final class CommandHelpHandler<C> {
private final CommandManager<C> commandManager; private final CommandManager<C> commandManager;
private final Predicate<Command<C>> commandPredicate;
CommandHelpHandler(final @NonNull CommandManager<C> commandManager) { CommandHelpHandler(
final @NonNull CommandManager<C> commandManager,
final @NonNull Predicate<Command<C>> commandPredicate
) {
this.commandManager = commandManager; this.commandManager = commandManager;
this.commandPredicate = commandPredicate;
} }
/** /**
@ -55,6 +61,11 @@ public final class CommandHelpHandler<C> {
public @NonNull List<@NonNull VerboseHelpEntry<C>> getAllCommands() { public @NonNull List<@NonNull VerboseHelpEntry<C>> getAllCommands() {
final List<VerboseHelpEntry<C>> syntaxHints = new ArrayList<>(); final List<VerboseHelpEntry<C>> syntaxHints = new ArrayList<>();
for (final Command<C> command : this.commandManager.getCommands()) { for (final Command<C> command : this.commandManager.getCommands()) {
/* Check command is not filtered */
if (!this.commandPredicate.test(command)) {
continue;
}
final List<CommandArgument<C, ?>> arguments = command.getArguments(); final List<CommandArgument<C, ?>> arguments = command.getArguments();
final String description = command.getCommandMeta().getOrDefault(CommandMeta.DESCRIPTION, ""); final String description = command.getCommandMeta().getOrDefault(CommandMeta.DESCRIPTION, "");
syntaxHints.add(new VerboseHelpEntry<>( syntaxHints.add(new VerboseHelpEntry<>(
@ -188,7 +199,7 @@ public final class CommandHelpHandler<C> {
int index = 0; int index = 0;
outer: outer:
while (head != null) { while (head != null && this.isNodeVisible(head)) {
++index; ++index;
traversedNodes.add(head.getValue()); traversedNodes.add(head.getValue());
@ -233,6 +244,11 @@ public final class CommandHelpHandler<C> {
/* Attempt to parse the longest possible description for the children */ /* Attempt to parse the longest possible description for the children */
final List<String> childSuggestions = new LinkedList<>(); final List<String> childSuggestions = new LinkedList<>();
for (final CommandTree.Node<CommandArgument<C, ?>> child : head.getChildren()) { for (final CommandTree.Node<CommandArgument<C, ?>> child : head.getChildren()) {
/* Check filtered by predicate */
if (!this.isNodeVisible(child)) {
continue;
}
final List<CommandArgument<C, ?>> traversedNodesSub = new LinkedList<>(traversedNodes); final List<CommandArgument<C, ?>> traversedNodesSub = new LinkedList<>(traversedNodes);
if (recipient == null if (recipient == null
|| child.getValue() == null || child.getValue() == null
@ -252,6 +268,29 @@ public final class CommandHelpHandler<C> {
return new IndexHelpTopic<>(Collections.emptyList()); return new IndexHelpTopic<>(Collections.emptyList());
} }
/* Checks using the predicate whether a command node or one of its children is visible */
private boolean isNodeVisible(
final CommandTree.@NonNull Node<CommandArgument<C, ?>> node
) {
/* Check node is itself a command that is visible */
final CommandArgument<C, ?> argument = node.getValue();
if (argument != null) {
final Command<C> owningCommand = argument.getOwningCommand();
if (owningCommand != null && this.commandPredicate.test(owningCommand)) {
return true;
}
}
/* Query the children recursively */
for (CommandTree.Node<CommandArgument<C, ?>> childNode : node.getChildren()) {
if (this.isNodeVisible(childNode)) {
return true;
}
}
return false;
}
/** /**
* Something that can be returned as the result of a help query * Something that can be returned as the result of a help query
* <p> * <p>

View file

@ -73,6 +73,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
/** /**
* The manager is responsible for command registration, parsing delegation, etc. * The manager is responsible for command registration, parsing delegation, etc.
@ -753,13 +754,30 @@ public abstract class CommandManager<C> {
/** /**
* Get a command help handler instance. This can be used to assist in the production * Get a command help handler instance. This can be used to assist in the production
* of command help menus, etc. * of command help menus, etc. This command help handler instance will display
* all commands registered in this command manager.
* *
* @return Command help handler. A new instance will be created * @return Command help handler. A new instance will be created
* each time this method is called. * each time this method is called.
*/ */
public final @NonNull CommandHelpHandler<C> getCommandHelpHandler() { public final @NonNull CommandHelpHandler<C> getCommandHelpHandler() {
return new CommandHelpHandler<>(this); return new CommandHelpHandler<>(this, cmd -> true);
}
/**
* Get a command help handler instance. This can be used to assist in the production
* of command help menus, etc. A predicate can be specified to filter what commands
* registered in this command manager are visible in the help menu.
*
* @param commandPredicate Predicate that filters what commands are displayed in
* the help menu.
* @return Command help handler. A new instance will be created
* each time this method is called.
*/
public final @NonNull CommandHelpHandler<C> getCommandHelpHandler(
final @NonNull Predicate<Command<C>> commandPredicate
) {
return new CommandHelpHandler<>(this, commandPredicate);
} }
/** /**

View file

@ -23,6 +23,8 @@
// //
package cloud.commandframework; package cloud.commandframework;
import cloud.commandframework.arguments.CommandArgument;
import cloud.commandframework.arguments.StaticArgument;
import cloud.commandframework.arguments.standard.IntegerArgument; import cloud.commandframework.arguments.standard.IntegerArgument;
import cloud.commandframework.arguments.standard.StringArgument; import cloud.commandframework.arguments.standard.StringArgument;
import cloud.commandframework.meta.CommandMeta; import cloud.commandframework.meta.CommandMeta;
@ -36,6 +38,8 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
class CommandHelpHandlerTest { class CommandHelpHandlerTest {
@ -93,6 +97,94 @@ class CommandHelpHandlerTest {
this.printTopic("vec", query4); this.printTopic("vec", query4);
} }
@Test
void testPredicateFilter() {
/*
* This predicate only displays the commands starting with /test
* The one command ending in 'thing' is excluded as well, for complexity
*/
final Predicate<Command<TestCommandSender>> predicate = (command) -> {
return command.toString().startsWith("test ")
&& !command.toString().endsWith(" thing");
};
/*
* List all commands from root, which should show only:
* - /test <potato>
* - /test int <int>
*/
final CommandHelpHandler.HelpTopic<TestCommandSender> query1 = manager.getCommandHelpHandler(predicate).queryHelp("");
Assertions.assertTrue(query1 instanceof CommandHelpHandler.IndexHelpTopic);
Assertions.assertEquals(Arrays.asList("test <potato>", "test int <int>"), getSortedSyntaxStrings(query1));
/*
* List all commands from /test, which should show only:
* - /test <potato>
* - /test int <int>
*/
final CommandHelpHandler.HelpTopic<TestCommandSender> query2 = manager.getCommandHelpHandler(predicate).queryHelp("test");
Assertions.assertTrue(query2 instanceof CommandHelpHandler.MultiHelpTopic);
Assertions.assertEquals(Arrays.asList("test <potato>", "test int <int>"), getSortedSyntaxStrings(query2));
/*
* List all commands from /test int, which should show only:
* - /test int <int>
*/
final CommandHelpHandler.HelpTopic<TestCommandSender> query3 = manager.getCommandHelpHandler(predicate).queryHelp("test int");
Assertions.assertTrue(query3 instanceof CommandHelpHandler.VerboseHelpTopic);
Assertions.assertEquals(Arrays.asList("test int <int>"), getSortedSyntaxStrings(query3));
/*
* List all commands from /vec, which should show none
*/
final CommandHelpHandler.HelpTopic<TestCommandSender> query4 = manager.getCommandHelpHandler(predicate).queryHelp("vec");
Assertions.assertTrue(query4 instanceof CommandHelpHandler.IndexHelpTopic);
Assertions.assertEquals(Collections.emptyList(), getSortedSyntaxStrings(query4));
}
/* Lists all the syntax strings of the commands displayed in a help topic */
private List<String> getSortedSyntaxStrings(
final CommandHelpHandler.HelpTopic<TestCommandSender> helpTopic
) {
if (helpTopic instanceof CommandHelpHandler.IndexHelpTopic) {
CommandHelpHandler.IndexHelpTopic<TestCommandSender> index =
(CommandHelpHandler.IndexHelpTopic<TestCommandSender>) helpTopic;
return index.getEntries().stream()
.map(CommandHelpHandler.VerboseHelpEntry::getSyntaxString)
.sorted()
.collect(Collectors.toList());
} else if (helpTopic instanceof CommandHelpHandler.MultiHelpTopic) {
CommandHelpHandler.MultiHelpTopic<TestCommandSender> multi =
(CommandHelpHandler.MultiHelpTopic<TestCommandSender>) helpTopic;
return multi.getChildSuggestions().stream()
.sorted()
.collect(Collectors.toList());
} else if (helpTopic instanceof CommandHelpHandler.VerboseHelpTopic) {
CommandHelpHandler.VerboseHelpTopic<TestCommandSender> verbose =
(CommandHelpHandler.VerboseHelpTopic<TestCommandSender>) helpTopic;
//TODO: Use CommandManager syntax for this
StringBuilder syntax = new StringBuilder();
for (CommandArgument<TestCommandSender, ?> argument : verbose.getCommand().getArguments()) {
if (argument instanceof StaticArgument) {
syntax.append(argument.getName());
} else if (argument.isRequired()) {
syntax.append('<').append(argument.getName()).append('>');
} else {
syntax.append('[').append(argument.getName()).append(']');
}
syntax.append(' ');
}
syntax.setLength(syntax.length() - 1);
return Collections.singletonList(syntax.toString());
}
/* Dunno */
return Collections.emptyList();
}
private void printTopic( private void printTopic(
final String query, final String query,
final CommandHelpHandler.HelpTopic<TestCommandSender> helpTopic final CommandHelpHandler.HelpTopic<TestCommandSender> helpTopic

View file

@ -23,6 +23,7 @@
// //
package cloud.commandframework.minecraft.extras; package cloud.commandframework.minecraft.extras;
import cloud.commandframework.Command;
import cloud.commandframework.CommandComponent; import cloud.commandframework.CommandComponent;
import cloud.commandframework.CommandHelpHandler; import cloud.commandframework.CommandHelpHandler;
import cloud.commandframework.CommandManager; import cloud.commandframework.CommandManager;
@ -44,6 +45,7 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Predicate;
/** /**
* Opinionated extension of {@link CommandHelpHandler} for Minecraft * Opinionated extension of {@link CommandHelpHandler} for Minecraft
@ -86,6 +88,7 @@ public final class MinecraftHelp<C> {
private final String commandPrefix; private final String commandPrefix;
private final Map<String, String> messageMap = new HashMap<>(); private final Map<String, String> messageMap = new HashMap<>();
private Predicate<Command<C>> commandFilter = c -> true;
private BiFunction<C, String, String> messageProvider = (sender, key) -> this.messageMap.get(key); private BiFunction<C, String, String> messageProvider = (sender, key) -> this.messageMap.get(key);
private HelpColors colors = DEFAULT_HELP_COLORS; private HelpColors colors = DEFAULT_HELP_COLORS;
private int headerFooterLength = DEFAULT_HEADER_FOOTER_LENGTH; private int headerFooterLength = DEFAULT_HEADER_FOOTER_LENGTH;
@ -151,6 +154,17 @@ public final class MinecraftHelp<C> {
return this.audienceProvider.apply(sender); return this.audienceProvider.apply(sender);
} }
/**
* Sets a filter for what commands are visible inside the help menu.
* When the {@link Predicate} tests <i>true</i>, then the command
* is included in the listings.
*
* @param commandPredicate Predicate to filter commands by
*/
public void setCommandFilter(final @NonNull Predicate<Command<C>> commandPredicate) {
this.commandFilter = commandPredicate;
}
/** /**
* Configure a message * Configure a message
* *
@ -241,7 +255,7 @@ public final class MinecraftHelp<C> {
recipient, recipient,
query, query,
page, page,
this.commandManager.getCommandHelpHandler().queryHelp(recipient, query) this.commandManager.getCommandHelpHandler(this.commandFilter).queryHelp(recipient, query)
); );
} }