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

diff --git a/cloud-core/src/main/java/cloud/commandframework/CommandManager.java b/cloud-core/src/main/java/cloud/commandframework/CommandManager.java index 9ebf6a71..61d5c352 100644 --- a/cloud-core/src/main/java/cloud/commandframework/CommandManager.java +++ b/cloud-core/src/main/java/cloud/commandframework/CommandManager.java @@ -73,6 +73,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.function.Function; +import java.util.function.Predicate; /** * The manager is responsible for command registration, parsing delegation, etc. @@ -753,13 +754,30 @@ public abstract class CommandManager { /** * 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 * each time this method is called. */ public final @NonNull CommandHelpHandler 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 getCommandHelpHandler( + final @NonNull Predicate> commandPredicate + ) { + return new CommandHelpHandler<>(this, commandPredicate); } /** diff --git a/cloud-core/src/test/java/cloud/commandframework/CommandHelpHandlerTest.java b/cloud-core/src/test/java/cloud/commandframework/CommandHelpHandlerTest.java index ffec9679..3493ed75 100644 --- a/cloud-core/src/test/java/cloud/commandframework/CommandHelpHandlerTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/CommandHelpHandlerTest.java @@ -23,6 +23,8 @@ // package cloud.commandframework; +import cloud.commandframework.arguments.CommandArgument; +import cloud.commandframework.arguments.StaticArgument; import cloud.commandframework.arguments.standard.IntegerArgument; import cloud.commandframework.arguments.standard.StringArgument; import cloud.commandframework.meta.CommandMeta; @@ -36,6 +38,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; class CommandHelpHandlerTest { @@ -93,6 +97,94 @@ class CommandHelpHandlerTest { 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> predicate = (command) -> { + return command.toString().startsWith("test ") + && !command.toString().endsWith(" thing"); + }; + + /* + * List all commands from root, which should show only: + * - /test + * - /test int + */ + final CommandHelpHandler.HelpTopic query1 = manager.getCommandHelpHandler(predicate).queryHelp(""); + Assertions.assertTrue(query1 instanceof CommandHelpHandler.IndexHelpTopic); + Assertions.assertEquals(Arrays.asList("test ", "test int "), getSortedSyntaxStrings(query1)); + + /* + * List all commands from /test, which should show only: + * - /test + * - /test int + */ + final CommandHelpHandler.HelpTopic query2 = manager.getCommandHelpHandler(predicate).queryHelp("test"); + Assertions.assertTrue(query2 instanceof CommandHelpHandler.MultiHelpTopic); + Assertions.assertEquals(Arrays.asList("test ", "test int "), getSortedSyntaxStrings(query2)); + + /* + * List all commands from /test int, which should show only: + * - /test int + */ + final CommandHelpHandler.HelpTopic query3 = manager.getCommandHelpHandler(predicate).queryHelp("test int"); + Assertions.assertTrue(query3 instanceof CommandHelpHandler.VerboseHelpTopic); + Assertions.assertEquals(Arrays.asList("test int "), getSortedSyntaxStrings(query3)); + + /* + * List all commands from /vec, which should show none + */ + final CommandHelpHandler.HelpTopic 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 getSortedSyntaxStrings( + final CommandHelpHandler.HelpTopic helpTopic + ) { + if (helpTopic instanceof CommandHelpHandler.IndexHelpTopic) { + CommandHelpHandler.IndexHelpTopic index = + (CommandHelpHandler.IndexHelpTopic) helpTopic; + + return index.getEntries().stream() + .map(CommandHelpHandler.VerboseHelpEntry::getSyntaxString) + .sorted() + .collect(Collectors.toList()); + } else if (helpTopic instanceof CommandHelpHandler.MultiHelpTopic) { + CommandHelpHandler.MultiHelpTopic multi = + (CommandHelpHandler.MultiHelpTopic) helpTopic; + + return multi.getChildSuggestions().stream() + .sorted() + .collect(Collectors.toList()); + } else if (helpTopic instanceof CommandHelpHandler.VerboseHelpTopic) { + CommandHelpHandler.VerboseHelpTopic verbose = + (CommandHelpHandler.VerboseHelpTopic) helpTopic; + + //TODO: Use CommandManager syntax for this + StringBuilder syntax = new StringBuilder(); + for (CommandArgument 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( final String query, final CommandHelpHandler.HelpTopic helpTopic diff --git a/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftHelp.java b/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftHelp.java index 9102438d..46e35b49 100644 --- a/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftHelp.java +++ b/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftHelp.java @@ -23,6 +23,7 @@ // package cloud.commandframework.minecraft.extras; +import cloud.commandframework.Command; import cloud.commandframework.CommandComponent; import cloud.commandframework.CommandHelpHandler; import cloud.commandframework.CommandManager; @@ -44,6 +45,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.function.BiFunction; +import java.util.function.Predicate; /** * Opinionated extension of {@link CommandHelpHandler} for Minecraft @@ -86,6 +88,7 @@ public final class MinecraftHelp { private final String commandPrefix; private final Map messageMap = new HashMap<>(); + private Predicate> commandFilter = c -> true; private BiFunction messageProvider = (sender, key) -> this.messageMap.get(key); private HelpColors colors = DEFAULT_HELP_COLORS; private int headerFooterLength = DEFAULT_HEADER_FOOTER_LENGTH; @@ -151,6 +154,17 @@ public final class MinecraftHelp { return this.audienceProvider.apply(sender); } + /** + * Sets a filter for what commands are visible inside the help menu. + * When the {@link Predicate} tests true, then the command + * is included in the listings. + * + * @param commandPredicate Predicate to filter commands by + */ + public void setCommandFilter(final @NonNull Predicate> commandPredicate) { + this.commandFilter = commandPredicate; + } + /** * Configure a message * @@ -241,7 +255,7 @@ public final class MinecraftHelp { recipient, query, page, - this.commandManager.getCommandHelpHandler().queryHelp(recipient, query) + this.commandManager.getCommandHelpHandler(this.commandFilter).queryHelp(recipient, query) ); }