From 756908a3b3b68eb516d5471abd1cf610726455af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derberg?= Date: Sun, 20 Sep 2020 22:01:38 +0200 Subject: [PATCH] Add very simple help utility --- .../commands/CommandHelpHandler.java | 108 ++++++++++++++++++ .../commands/CommandManager.java | 27 ++++- .../arguments/CommandSyntaxFormatter.java | 3 +- .../StandardCommandSyntaxFormatter.java | 3 +- .../commands/CommandHelpHandlerTest.java | 62 ++++++++++ 5 files changed, 199 insertions(+), 4 deletions(-) create mode 100644 cloud-core/src/main/java/com/intellectualsites/commands/CommandHelpHandler.java create mode 100644 cloud-core/src/test/java/com/intellectualsites/commands/CommandHelpHandlerTest.java diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/CommandHelpHandler.java b/cloud-core/src/main/java/com/intellectualsites/commands/CommandHelpHandler.java new file mode 100644 index 00000000..177367fa --- /dev/null +++ b/cloud-core/src/main/java/com/intellectualsites/commands/CommandHelpHandler.java @@ -0,0 +1,108 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package com.intellectualsites.commands; + +import com.intellectualsites.commands.arguments.CommandArgument; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public final class CommandHelpHandler { + + private final CommandManager commandManager; + + CommandHelpHandler(@Nonnull final CommandManager commandManager) { + this.commandManager = commandManager; + } + + /** + * Get exact syntax hints for all commands + * + * @return Syntax hints for all registered commands, order in lexicographical order + */ + @Nonnull + public List> getAllCommands() { + final List> syntaxHints = new ArrayList<>(); + for (final Command command : this.commandManager.getCommands()) { + final List> arguments = command.getArguments(); + syntaxHints.add(new VerboseHelpEntry<>(command, + this.commandManager.getCommandSyntaxFormatter().apply(arguments, null))); + } + syntaxHints.sort(Comparator.comparing(VerboseHelpEntry::getSyntaxString)); + return syntaxHints; + } + + /** + * Get a list of the longest shared command chains of all commands. + * If there are two commands "foo bar 1" and "foo bar 2", this would + * then return "foo bar 1|2" + * + * @return Longest shared command chains + */ + @Nonnull + public List getLongestSharedChains() { + final List chains = new ArrayList<>(); + this.commandManager.getCommandTree().getRootNodes().forEach(node -> + chains.add(node.getValue().getName() + this.commandManager.getCommandSyntaxFormatter() + .apply(Collections.emptyList(), node))); + chains.sort(String::compareTo); + return chains; + } + + + public static final class VerboseHelpEntry { + + private final Command command; + private final String syntaxString; + + private VerboseHelpEntry(@Nonnull final Command command, @Nonnull final String syntaxString) { + this.command = command; + this.syntaxString = syntaxString; + } + + /** + * Get the command + * + * @return Command + */ + @Nonnull + public Command getCommand() { + return this.command; + } + + /** + * Get the syntax string + * + * @return Syntax string + */ + @Nonnull + public String getSyntaxString() { + return this.syntaxString; + } + } + +} diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java b/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java index 1f2cfa63..f141f4fb 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java @@ -23,6 +23,7 @@ // package com.intellectualsites.commands; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.reflect.TypeToken; import com.intellectualsites.commands.arguments.CommandArgument; @@ -70,6 +71,7 @@ public abstract class CommandManager { private final ServicePipeline servicePipeline = ServicePipeline.builder().build(); private final ParserRegistry parserRegistry = new StandardParserRegistry<>(); private final Map, BiConsumer> exceptionHandlers = Maps.newHashMap(); + private final Collection> commands = Lists.newLinkedList(); private final CommandExecutionCoordinator commandExecutionCoordinator; private final CommandTree commandTree; @@ -166,6 +168,7 @@ public abstract class CommandManager { */ public CommandManager command(@Nonnull final Command command) { this.commandTree.insertCommand(command); + this.commands.add(command); return this; } @@ -198,6 +201,10 @@ public abstract class CommandManager { return this.commandRegistrationHandler; } + protected final void setCommandRegistrationHandler(@Nonnull final CommandRegistrationHandler commandRegistrationHandler) { + this.commandRegistrationHandler = commandRegistrationHandler; + } + /** * Check if the command sender has the required permission. If the permission node is * empty, this should return {@code true} @@ -389,8 +396,24 @@ public abstract class CommandManager { Optional.ofNullable(this.getExceptionHandler(clazz)).orElse(defaultHandler).accept(sender, exception); } - protected final void setCommandRegistrationHandler(@Nonnull final CommandRegistrationHandler commandRegistrationHandler) { - this.commandRegistrationHandler = commandRegistrationHandler; + /** + * Get all registered commands + * + * @return Unmodifiable view of all registered commands + */ + @Nonnull + public final Collection> getCommands() { + return Collections.unmodifiableCollection(this.commands); + } + + /** + * Get a command help handler instance + * + * @return Command help handler + */ + @Nonnull + public final CommandHelpHandler getCommandHelpHandler() { + return new CommandHelpHandler<>(this); } } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/arguments/CommandSyntaxFormatter.java b/cloud-core/src/main/java/com/intellectualsites/commands/arguments/CommandSyntaxFormatter.java index f4c9b437..fe331303 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/arguments/CommandSyntaxFormatter.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/arguments/CommandSyntaxFormatter.java @@ -26,6 +26,7 @@ package com.intellectualsites.commands.arguments; import com.intellectualsites.commands.CommandTree; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.List; /** @@ -45,6 +46,6 @@ public interface CommandSyntaxFormatter { */ @Nonnull String apply(@Nonnull List> commandArguments, - @Nonnull CommandTree.Node> node); + @Nullable CommandTree.Node> node); } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/arguments/StandardCommandSyntaxFormatter.java b/cloud-core/src/main/java/com/intellectualsites/commands/arguments/StandardCommandSyntaxFormatter.java index c4449c1a..fa7f9855 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/arguments/StandardCommandSyntaxFormatter.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/arguments/StandardCommandSyntaxFormatter.java @@ -26,6 +26,7 @@ package com.intellectualsites.commands.arguments; import com.intellectualsites.commands.CommandTree; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.Iterator; import java.util.List; @@ -44,7 +45,7 @@ public class StandardCommandSyntaxFormatter implements CommandSyntaxFormatter @Nonnull @Override public final String apply(@Nonnull final List> commandArguments, - @Nonnull final CommandTree.Node> node) { + @Nullable final CommandTree.Node> node) { final StringBuilder stringBuilder = new StringBuilder(); final Iterator> iterator = commandArguments.iterator(); while (iterator.hasNext()) { diff --git a/cloud-core/src/test/java/com/intellectualsites/commands/CommandHelpHandlerTest.java b/cloud-core/src/test/java/com/intellectualsites/commands/CommandHelpHandlerTest.java new file mode 100644 index 00000000..8fbca513 --- /dev/null +++ b/cloud-core/src/test/java/com/intellectualsites/commands/CommandHelpHandlerTest.java @@ -0,0 +1,62 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package com.intellectualsites.commands; + +import com.intellectualsites.commands.arguments.standard.IntegerArgument; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +class CommandHelpHandlerTest { + + private static CommandManager manager; + + @BeforeAll + static void setup() { + manager = new TestCommandManager(); + manager.command(manager.commandBuilder("test").literal("this").literal("thing").build()); + manager.command(manager.commandBuilder("test").literal("int"). + argument(IntegerArgument.required("int")).build()); + } + + @Test + void testVerboseHelp() { + final List> syntaxHints + = manager.getCommandHelpHandler().getAllCommands(); + final CommandHelpHandler.VerboseHelpEntry entry1 = syntaxHints.get(0); + Assertions.assertEquals("test int ", entry1.getSyntaxString()); + final CommandHelpHandler.VerboseHelpEntry entry2 = syntaxHints.get(1); + Assertions.assertEquals("test this thing", entry2.getSyntaxString()); + } + + @Test + void testLongestChains() { + final List longestChains = manager.getCommandHelpHandler().getLongestSharedChains(); + Assertions.assertEquals(Arrays.asList("test int|this"), longestChains); + } + +}