From db08d9baee29534b818df86c3b64c2fb765207b4 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Sat, 4 Feb 2023 02:30:44 +0100 Subject: [PATCH] Added documentation and refactored commands.yml handling - Added command argument help manager to read the arguments - MC tab completion still needs to be fixed --- .../java/buttondevteam/lib/chat/Command2.java | 87 ++++++------------- .../buttondevteam/lib/chat/Command2MC.java | 11 --- .../buttondevteam/lib/chat/ICommand2.java | 8 ++ .../commands/CommandArgumentHelpManager.java | 67 ++++++++++++++ 4 files changed, 103 insertions(+), 70 deletions(-) create mode 100644 Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgumentHelpManager.java diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.java b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.java index b1087b1..a9e6df7 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.java @@ -3,6 +3,7 @@ package buttondevteam.lib.chat; import buttondevteam.core.MainPlugin; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.chat.commands.CommandArgument; +import buttondevteam.lib.chat.commands.CommandArgumentHelpManager; import buttondevteam.lib.chat.commands.NumberArg; import buttondevteam.lib.chat.commands.SubcommandData; import buttondevteam.lib.player.ChromaGamerBase; @@ -16,12 +17,10 @@ import com.mojang.brigadier.tree.LiteralCommandNode; import lombok.RequiredArgsConstructor; import lombok.val; import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; import org.javatuples.Pair; import org.javatuples.Triplet; import org.jetbrains.annotations.NotNull; -import java.io.InputStreamReader; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -80,31 +79,6 @@ public abstract class Command2, TP extends Command2Send public @interface OptionalArg { } - /*protected static class SubcommandHelpData extends SubcommandData { - private final TreeSet ht = new TreeSet<>(); - private BukkitTask task; - - public SubcommandHelpData(Method method, T command, String[] helpText) { - super(method, command, helpText); - } - - public void addSubcommand(String command) { - ht.add(command); - if (task == null) - task = Bukkit.getScheduler().runTask(MainPlugin.Instance, () -> { - helpText = new String[ht.size() + 1]; //This will only run after the server is started List list = new ArrayList(size()); - helpText[0] = "ยง6---- Subcommands ----"; //TODO: There may be more to the help text - int i = 1; - for (Iterator iterator = ht.iterator(); - iterator.hasNext() && i < helpText.length; i++) { - String e = iterator.next(); - helpText[i] = e; - } - task = null; //Run again, if needed - }); - } - }*/ - @RequiredArgsConstructor protected static class ParamConverter { public final Function converter; @@ -205,7 +179,7 @@ public abstract class Command2, TP extends Command2Send if (ann == null) continue; String methodPath = getCommandPath(meth.getName(), ' '); val result = registerNodeFromPath(command.getCommandPath() + methodPath); - result.getValue0().addChild(getExecutableNode(meth, command, ann, result.getValue2())); + result.getValue0().addChild(getExecutableNode(meth, command, ann, result.getValue2(), new CommandArgumentHelpManager<>(command))); if (mainCommandNode == null) mainCommandNode = result.getValue1(); else if (!result.getValue1().getName().equals(mainCommandNode.getName())) { MainPlugin.Instance.getLogger().warning("Multiple commands are defined in the same class! This is not supported. Class: " + command.getClass().getSimpleName()); @@ -225,8 +199,8 @@ public abstract class Command2, TP extends Command2Send * @param path The command path * @return The executable node */ - private LiteralCommandNode getExecutableNode(Method method, TC command, Subcommand ann, String path) { - val paramsAndSenderType = getCommandParameters(method); // Param order is important + private LiteralCommandNode getExecutableNode(Method method, TC command, Subcommand ann, String path, CommandArgumentHelpManager argHelpManager) { + val paramsAndSenderType = getCommandParametersAndSender(method, argHelpManager); // Param order is important val params = paramsAndSenderType.getValue0(); val paramMap = new HashMap(); for (val param : params) { @@ -237,7 +211,7 @@ public abstract class Command2, TP extends Command2Send .executes(this::executeCommand); ArgumentBuilder parent = node; for (val param : params) { // Register parameters in the right order - parent.then(parent = CoreArgumentBuilder.argument(param.name, getParameterType(param), param.optional)); + parent.then(parent = CoreArgumentBuilder.argument(param.name, getArgumentType(param), param.optional)); } return node.build(); } @@ -272,12 +246,12 @@ public abstract class Command2, TP extends Command2Send * @return Parameter data objects and the sender type * @throws RuntimeException If there is no sender parameter declared in the method */ - private Pair> getCommandParameters(Method method) { + private Pair> getCommandParametersAndSender(Method method, CommandArgumentHelpManager argHelpManager) { val parameters = method.getParameters(); if (parameters.length == 0) throw new RuntimeException("No sender parameter for method '" + method + "'"); val ret = new CommandArgument[parameters.length]; - val usage = getParameterHelp(method); + val usage = argHelpManager.getParameterHelpForMethod(method); val paramNames = usage != null ? usage.split(" ") : null; for (int i = 1; i < parameters.length; i++) { val numAnn = parameters[i].getAnnotation(NumberArg.class); @@ -290,7 +264,14 @@ public abstract class Command2, TP extends Command2Send return new Pair<>(ret, parameters[0].getType()); } - private ArgumentType getParameterType(CommandArgument arg) { + /** + * Converts the Chroma representation of the argument declaration into Brigadier format. + * It does part of the command argument type processing. + * + * @param arg Our representation of the command argument + * @return The Brigadier representation of the command argument + */ + private ArgumentType getArgumentType(CommandArgument arg) { final Class ptype = arg.type; Number lowerLimit = arg.limits.getValue0(), upperLimit = arg.limits.getValue1(); if (arg.greedy) @@ -316,11 +297,24 @@ public abstract class Command2, TP extends Command2Send } } + /** + * Displays the help text based on the executed command. Each command node might have a help text stored. + * The help text is displayed either because of incorrect usage or it's explicitly requested. + * + * @param context The command context + * @return Vanilla command success level (0) + */ private int executeHelpText(CommandContext context) { System.out.println("Nodes:\n" + context.getNodes().stream().map(node -> node.getNode().getName() + "@" + node.getRange()).collect(Collectors.joining("\n"))); return 0; } + /** + * Executes the command itself by calling the subcommand method associated with the input command node. + * + * @param context The command context + * @return Vanilla command success level (0) + */ private int executeCommand(CommandContext context) { System.out.println("Execute command"); System.out.println("Should be running sync: " + runOnPrimaryThread); @@ -378,31 +372,6 @@ public abstract class Command2, TP extends Command2Send return 0; } - private String getParameterHelp(Method method) { - val str = method.getDeclaringClass().getResourceAsStream("/commands.yml"); - if (str == null) - TBMCCoreAPI.SendException("Error while getting command data!", new Exception("Resource not found!"), MainPlugin.Instance); - else { - YamlConfiguration yc = YamlConfiguration.loadConfiguration(new InputStreamReader(str)); //Generated by ButtonProcessor - val ccs = yc.getConfigurationSection(method.getDeclaringClass().getCanonicalName().replace('$', '.')); - if (ccs != null) { - val cs = ccs.getConfigurationSection(method.getName()); - if (cs != null) { - val mname = cs.getString("method"); - val params = cs.getString("params"); - int i = mname.indexOf('('); //Check only the name - the whole method is still stored for backwards compatibility and in case it may be useful - if (i != -1 && method.getName().equals(mname.substring(0, i)) && params != null) { - return params; - } else - TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("Method '" + method + "' != " + mname + " or params is " + params), MainPlugin.Instance); - } else - MainPlugin.Instance.getLogger().warning("Failed to get command data for " + method + " (cs is null)! Make sure to use 'clean install' when building the project."); - } else - MainPlugin.Instance.getLogger().warning("Failed to get command data for " + method + " (ccs is null)! Make sure to use 'clean install' when building the project."); - } - return null; - } - public abstract boolean hasPermission(TP sender, TC command, Method subcommand); public String[] getCommandsText() { diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java index 203d76d..ef1395b 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java @@ -170,17 +170,6 @@ public class Command2MC extends Command2 implemen .map(comp -> component.getClass().getSimpleName().equals(comp.getClass().getSimpleName())).orElse(false), true); } - /*@EventHandler - public void onTabComplete(TabCompleteEvent event) { - try { - event.getCompletions().clear(); //Remove player names - } catch (UnsupportedOperationException e) { - //System.out.println("Tabcomplete: " + event.getBuffer()); - //System.out.println("First completion: " + event.getCompletions().stream().findFirst().orElse("no completions")); - //System.out.println("Listeners: " + Arrays.toString(event.getHandlers().getRegisteredListeners())); - } - }*/ - @Override public boolean handleCommand(Command2MCSender sender, String commandline) { return handleCommand(sender, commandline, true); diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2.java b/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2.java index 3b0bee8..7817a3e 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2.java @@ -7,6 +7,13 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.function.Function; +/** + * This class is used as a base class for all the specific command implementations. + * It primarily holds information about the command itself and how it should be run, ideally in a programmer-friendly way. + * Any inferred and processed information about this command will be stored in the command manager (Command2*). + * + * @param The sender's type + */ public abstract class ICommand2 { /** * Default handler for commands, can be used to copy the args too. @@ -14,6 +21,7 @@ public abstract class ICommand2 { * @param sender The sender which ran the command * @return The success of the command */ + @SuppressWarnings("unused") public boolean def(TP sender) { return false; } diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgumentHelpManager.java b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgumentHelpManager.java new file mode 100644 index 0000000..3fe5ca8 --- /dev/null +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgumentHelpManager.java @@ -0,0 +1,67 @@ +package buttondevteam.lib.chat.commands; + +import buttondevteam.core.MainPlugin; +import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.lib.chat.Command2Sender; +import buttondevteam.lib.chat.ICommand2; +import lombok.val; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Method; + +/** + * Deals with reading the commands.yml file from the plugin. The file is generated by ButtonProcessor at compile-time. + * Only used when registering commands. + */ +public class CommandArgumentHelpManager, TP extends Command2Sender> { + private ConfigurationSection commandConfig; + + /** + * Read the yaml file for the given command class. + * + * @param command The command object to use + */ + public CommandArgumentHelpManager(TC command) { + val commandClass = command.getClass(); + // It will load it for each class, but it would be complicated to solve that + // Most plugins don't have a lot of command classes anyway + try (val str = commandClass.getResourceAsStream("/commands.yml")) { + if (str == null) { + TBMCCoreAPI.SendException("Error while getting command data!", new Exception("Resource not found!"), MainPlugin.Instance); + return; + } + val config = YamlConfiguration.loadConfiguration(new InputStreamReader(str)); + commandConfig = config.getConfigurationSection(commandClass.getCanonicalName().replace('$', '.')); + if (commandConfig == null) { + MainPlugin.Instance.getLogger().warning("Failed to get command data for " + commandClass + "! Make sure to use 'clean install' when building the project."); + } + } catch (IOException e) { + TBMCCoreAPI.SendException("Error while getting command data!", e, MainPlugin.Instance); + } + } + + /** + * Returns a parameter help string for the given subcommand method by reading it from the plugin. + * + * @param method The subcommand method + * @return The parameter part of the usage string for the command + */ + public String getParameterHelpForMethod(Method method) { + val cs = commandConfig.getConfigurationSection(method.getName()); + if (cs == null) { + MainPlugin.Instance.getLogger().warning("Failed to get command data for " + method + "! Make sure to use 'clean install' when building the project."); + return null; + } + val mname = cs.getString("method"); + val params = cs.getString("params"); + int i = mname.indexOf('('); //Check only the name - the whole method is still stored for backwards compatibility and in case it may be useful + if (i != -1 && method.getName().equals(mname.substring(0, i)) && params != null) { + return params; + } else + TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("Method '" + method + "' != " + mname + " or params is " + params), MainPlugin.Instance); + return null; + } +}