Add very simple help utility

This commit is contained in:
Alexander Söderberg 2020-09-20 22:01:38 +02:00
parent c336a2d7e8
commit 756908a3b3
No known key found for this signature in database
GPG key ID: C0207FF7EA146678
5 changed files with 199 additions and 4 deletions

View file

@ -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<C> {
private final CommandManager<C> commandManager;
CommandHelpHandler(@Nonnull final CommandManager<C> 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<VerboseHelpEntry<C>> getAllCommands() {
final List<VerboseHelpEntry<C>> syntaxHints = new ArrayList<>();
for (final Command<C> command : this.commandManager.getCommands()) {
final List<CommandArgument<C, ?>> 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<String> getLongestSharedChains() {
final List<String> 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<C> {
private final Command<C> command;
private final String syntaxString;
private VerboseHelpEntry(@Nonnull final Command<C> command, @Nonnull final String syntaxString) {
this.command = command;
this.syntaxString = syntaxString;
}
/**
* Get the command
*
* @return Command
*/
@Nonnull
public Command<C> getCommand() {
return this.command;
}
/**
* Get the syntax string
*
* @return Syntax string
*/
@Nonnull
public String getSyntaxString() {
return this.syntaxString;
}
}
}

View file

@ -23,6 +23,7 @@
// //
package com.intellectualsites.commands; package com.intellectualsites.commands;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.reflect.TypeToken; import com.google.common.reflect.TypeToken;
import com.intellectualsites.commands.arguments.CommandArgument; import com.intellectualsites.commands.arguments.CommandArgument;
@ -70,6 +71,7 @@ public abstract class CommandManager<C> {
private final ServicePipeline servicePipeline = ServicePipeline.builder().build(); private final ServicePipeline servicePipeline = ServicePipeline.builder().build();
private final ParserRegistry<C> parserRegistry = new StandardParserRegistry<>(); private final ParserRegistry<C> parserRegistry = new StandardParserRegistry<>();
private final Map<Class<? extends Exception>, BiConsumer<C, ? extends Exception>> exceptionHandlers = Maps.newHashMap(); private final Map<Class<? extends Exception>, BiConsumer<C, ? extends Exception>> exceptionHandlers = Maps.newHashMap();
private final Collection<Command<C>> commands = Lists.newLinkedList();
private final CommandExecutionCoordinator<C> commandExecutionCoordinator; private final CommandExecutionCoordinator<C> commandExecutionCoordinator;
private final CommandTree<C> commandTree; private final CommandTree<C> commandTree;
@ -166,6 +168,7 @@ public abstract class CommandManager<C> {
*/ */
public CommandManager<C> command(@Nonnull final Command<C> command) { public CommandManager<C> command(@Nonnull final Command<C> command) {
this.commandTree.insertCommand(command); this.commandTree.insertCommand(command);
this.commands.add(command);
return this; return this;
} }
@ -198,6 +201,10 @@ public abstract class CommandManager<C> {
return this.commandRegistrationHandler; 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 * Check if the command sender has the required permission. If the permission node is
* empty, this should return {@code true} * empty, this should return {@code true}
@ -389,8 +396,24 @@ public abstract class CommandManager<C> {
Optional.ofNullable(this.getExceptionHandler(clazz)).orElse(defaultHandler).accept(sender, exception); 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<Command<C>> getCommands() {
return Collections.unmodifiableCollection(this.commands);
}
/**
* Get a command help handler instance
*
* @return Command help handler
*/
@Nonnull
public final CommandHelpHandler<C> getCommandHelpHandler() {
return new CommandHelpHandler<>(this);
} }
} }

View file

@ -26,6 +26,7 @@ package com.intellectualsites.commands.arguments;
import com.intellectualsites.commands.CommandTree; import com.intellectualsites.commands.CommandTree;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List; import java.util.List;
/** /**
@ -45,6 +46,6 @@ public interface CommandSyntaxFormatter<C> {
*/ */
@Nonnull @Nonnull
String apply(@Nonnull List<CommandArgument<C, ?>> commandArguments, String apply(@Nonnull List<CommandArgument<C, ?>> commandArguments,
@Nonnull CommandTree.Node<CommandArgument<C, ?>> node); @Nullable CommandTree.Node<CommandArgument<C, ?>> node);
} }

View file

@ -26,6 +26,7 @@ package com.intellectualsites.commands.arguments;
import com.intellectualsites.commands.CommandTree; import com.intellectualsites.commands.CommandTree;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -44,7 +45,7 @@ public class StandardCommandSyntaxFormatter<C> implements CommandSyntaxFormatter
@Nonnull @Nonnull
@Override @Override
public final String apply(@Nonnull final List<CommandArgument<C, ?>> commandArguments, public final String apply(@Nonnull final List<CommandArgument<C, ?>> commandArguments,
@Nonnull final CommandTree.Node<CommandArgument<C, ?>> node) { @Nullable final CommandTree.Node<CommandArgument<C, ?>> node) {
final StringBuilder stringBuilder = new StringBuilder(); final StringBuilder stringBuilder = new StringBuilder();
final Iterator<CommandArgument<C, ?>> iterator = commandArguments.iterator(); final Iterator<CommandArgument<C, ?>> iterator = commandArguments.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {

View file

@ -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<TestCommandSender> 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<CommandHelpHandler.VerboseHelpEntry<TestCommandSender>> syntaxHints
= manager.getCommandHelpHandler().getAllCommands();
final CommandHelpHandler.VerboseHelpEntry<TestCommandSender> entry1 = syntaxHints.get(0);
Assertions.assertEquals("test int <int>", entry1.getSyntaxString());
final CommandHelpHandler.VerboseHelpEntry<TestCommandSender> entry2 = syntaxHints.get(1);
Assertions.assertEquals("test this thing", entry2.getSyntaxString());
}
@Test
void testLongestChains() {
final List<String> longestChains = manager.getCommandHelpHandler().getLongestSharedChains();
Assertions.assertEquals(Arrays.asList("test int|this"), longestChains);
}
}