From 01cba2a508ca9590e539076da3ae3f8858fa12ee Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 12 Dec 2015 22:50:07 +0100 Subject: [PATCH] #315 #305 Refactor HelpProvider - add bit flags, reduce duplication (wip) --- .../authme/command/CommandDescription.java | 24 +- .../xephi/authme/command/CommandHandler.java | 12 +- .../authme/command/help/HelpProvider.java | 301 ++++++++++++------ .../authme/permission/DefaultPermission.java | 23 +- 4 files changed, 242 insertions(+), 118 deletions(-) diff --git a/src/main/java/fr/xephi/authme/command/CommandDescription.java b/src/main/java/fr/xephi/authme/command/CommandDescription.java index b9736352..6903916d 100644 --- a/src/main/java/fr/xephi/authme/command/CommandDescription.java +++ b/src/main/java/fr/xephi/authme/command/CommandDescription.java @@ -65,7 +65,7 @@ public class CommandDescription { } /** - * Create an instance for internal use. + * Create an instance. * * @param labels List of command labels. * @param description Command description. @@ -108,7 +108,7 @@ public class CommandDescription { * @return All relative labels. */ public List getLabels() { - return this.labels; + return labels; } /** @@ -145,23 +145,34 @@ public class CommandDescription { return parent; } + /** + * Return the number of parents that precede the command description. + * + * @return The number of parents, e.g. for "/authme abc def" the parent count is 2 ("/authme abc", "/authme") + */ + public int getParentCount() { + if (parent == null) { + return 0; + } + return parent.getParentCount() + 1; + } + /** * Return all command children. * * @return Command children. */ public List getChildren() { - return this.children; + return children; } - /** * Return all arguments the command takes. * * @return Command arguments. */ public List getArguments() { - return this.arguments; + return arguments; } /** @@ -182,14 +193,13 @@ public class CommandDescription { return detailedDescription; } - /** * Return the permissions required to execute the command. * * @return The command permissions, or null if none are required to execute the command. */ public CommandPermissions getCommandPermissions() { - return this.permissions; + return permissions; } /** diff --git a/src/main/java/fr/xephi/authme/command/CommandHandler.java b/src/main/java/fr/xephi/authme/command/CommandHandler.java index 61a99188..d8047a6b 100644 --- a/src/main/java/fr/xephi/authme/command/CommandHandler.java +++ b/src/main/java/fr/xephi/authme/command/CommandHandler.java @@ -128,11 +128,15 @@ public class CommandHandler { // Show the command argument help sender.sendMessage(ChatColor.DARK_RED + "Incorrect command arguments!"); - // TODO: Define showHelp(CommandSender, CommandDescription, List, boolean, boolean, ...) + List lines = HelpProvider.printHelp(result, HelpProvider.SHOW_ARGUMENTS); + for (String line : lines) { + sender.sendMessage(line); + } + List labels = result.getLabels(); - HelpProvider.showHelp(sender, command, labels, true, false, true, false, false, false); - sender.sendMessage(ChatColor.GOLD + "Detailed help: " + ChatColor.WHITE + "/" + labels.get(0) - + " help " + CommandUtils.labelsToString(labels.subList(1, labels.size()))); + String childLabel = labels.size() >= 2 ? labels.get(1) : ""; + sender.sendMessage(ChatColor.GOLD + "Detailed help: " + ChatColor.WHITE + + "/" + labels.get(0) + " help " + childLabel); } // TODO ljacqu 20151212: Remove me once I am a MessageKey diff --git a/src/main/java/fr/xephi/authme/command/help/HelpProvider.java b/src/main/java/fr/xephi/authme/command/help/HelpProvider.java index 501a8426..97556ab3 100644 --- a/src/main/java/fr/xephi/authme/command/help/HelpProvider.java +++ b/src/main/java/fr/xephi/authme/command/help/HelpProvider.java @@ -1,141 +1,234 @@ package fr.xephi.authme.command.help; -import fr.xephi.authme.AuthMe; +import com.google.common.collect.ImmutableList; +import fr.xephi.authme.command.CommandArgumentDescription; import fr.xephi.authme.command.CommandDescription; +import fr.xephi.authme.command.CommandPermissions; import fr.xephi.authme.command.CommandUtils; import fr.xephi.authme.command.FoundCommandResult; +import fr.xephi.authme.permission.DefaultPermission; +import fr.xephi.authme.permission.PermissionNode; +import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.settings.Settings; - import fr.xephi.authme.util.CollectionUtils; +import fr.xephi.authme.util.StringUtils; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; +import static java.util.Collections.singletonList; + /** + * Help syntax generator for AuthMe commands. */ -public class HelpProvider { +public final class HelpProvider { - /** - * Show help for a specific command. - * - * @param sender The command sender the help needs to be shown to. - * @param reference The command reference to the help command. - * @param helpQuery The query to show help for. - */ - public static void showHelp(CommandSender sender, CommandParts reference, CommandParts helpQuery) { - showHelp(sender, reference, helpQuery, true, true, true, true, true, true); + // --- Bit flags --- + /** Set to not show the command. */ + public static final int HIDE_COMMAND = 0x001; + /** Set to show the detailed description of a command. */ + public static final int SHOW_LONG_DESCRIPTION = 0x002; + /** Set to include the arguments the command takes. */ + public static final int SHOW_ARGUMENTS = 0x004; + /** Set to show the permissions required to execute the command. */ + public static final int SHOW_PERMISSIONS = 0x008; + /** Set to show alternative labels for the command. */ + public static final int SHOW_ALTERNATIVES = 0x010; + /** Set to show the child commands of the command. */ + public static final int SHOW_CHILDREN = 0x020; + + /** Shortcut for setting all options apart from {@link HelpProvider#HIDE_COMMAND}. */ + public static final int ALL_OPTIONS = ~HIDE_COMMAND; + + private HelpProvider() { } - public static void showHelp(CommandSender sender, CommandParts reference, CommandParts helpQuery, - boolean showCommand, boolean showDescription, boolean showArguments, - boolean showPermissions, boolean showAlternatives, boolean showCommands) { - showHelp(sender, reference.getList(), helpQuery.getList(), showCommand, showDescription, showArguments, - showPermissions, showAlternatives, showCommands); + public static List printHelp(FoundCommandResult foundCommand, int options) { + return printHelp(foundCommand, null, null, options); } - /** - * Show help for a specific command. - * - * @param sender The command sender the help needs to be shown to. - * @param reference The command reference to the help command. - * @param helpQuery The query to show help for. - * @param showCommand True to show the command. - * @param showDescription True to show the command description, both the short and detailed description. - * @param showArguments True to show the command argument help. - * @param showPermissions True to show the command permission help. - * @param showAlternatives True to show the command alternatives. - * @param showCommands True to show the child commands. - */ - public static void showHelp(CommandSender sender, List reference, List helpQuery, - boolean showCommand, boolean showDescription, boolean showArguments, - boolean showPermissions, boolean showAlternatives, boolean showCommands) { - // Find the command for this help query, one with and one without a prefixed base command - FoundCommandResult result = AuthMe.getInstance().getCommandHandler().findCommand(new CommandParts(helpQuery.getList())); - - // TODO ljacqu 20151204 Fix me to nicer code - List parts = new ArrayList<>(helpQuery.getList()); - parts.add(0, reference.get(0)); - CommandParts commandReferenceOther = new CommandParts(parts); - - FoundCommandResult resultOther = AuthMe.getInstance().getCommandHandler().findCommand(commandReferenceOther); - if (resultOther != null) { - if (result == null) - result = resultOther; - - else if (result.getDifference() > resultOther.getDifference()) - result = resultOther; + // sender and permissions manager may be null if SHOW_PERMISSIONS is not set + public static List printHelp(FoundCommandResult foundCommand, CommandSender sender, + PermissionsManager permissionsManager, int options) { + if (foundCommand.getCommandDescription() == null) { + return singletonList(ChatColor.DARK_RED + "Failed to retrieve any help information!"); } - // Make sure a result was found - if (result == null) { - // Show a warning message - sender.sendMessage(ChatColor.DARK_RED + "" + ChatColor.ITALIC + helpQuery); - sender.sendMessage(ChatColor.DARK_RED + "Couldn't show any help information for this help query."); + List lines = new ArrayList<>(); + lines.add(ChatColor.GOLD + "==========[ " + Settings.helpHeader + " HELP ]=========="); + + CommandDescription command = foundCommand.getCommandDescription(); + // TODO ljacqu 20151212: Remove immutability once class is stable. We don't want mutability but the overhead + // isn't worth it either. This is just a temporary safeguard during development + List labels = ImmutableList.copyOf(foundCommand.getLabels()); + + if (!hasFlag(HIDE_COMMAND, options)) { + printCommand(command, labels, lines); // FIXME: Pass `correctLabels` and not `labels` + } + if (hasFlag(SHOW_LONG_DESCRIPTION, options)) { + printDetailedDescription(command, lines); + } + if (hasFlag(SHOW_ARGUMENTS, options)) { + printArguments(command, lines); + } + if (hasFlag(SHOW_PERMISSIONS, options) && sender != null && permissionsManager != null) { + printPermissions(command, sender, permissionsManager, lines); + } + if (hasFlag(SHOW_ALTERNATIVES, options)) { + printAlternatives(command, labels, lines); + } + if (hasFlag(SHOW_CHILDREN, options)) { + printChildren(command, labels, lines); + } + + return lines; + } + + private static void printCommand(CommandDescription command, List correctLabels, List lines) { + // Ensure that we have all labels to go to the command + int requiredLabels = command.getParentCount() + 1; + List givenLabels = new ArrayList<>(correctLabels); + // Only case this is possible: givenLabels.size() == 1 && requiredLabels == 2, + // since command.getParentCount() never exceeds 1 in AuthMe + // FIXME: Might be smart to put this logic outside and to pass it as `correctLabels`? We will need this at a few + // places annotated with a FIXME + if (givenLabels.size() < requiredLabels) { + givenLabels.add(command.getLabels().get(0)); + } + + // FIXME: Create highlight logic to mark arguments and the 2nd label as yellow + String syntaxLine = "/" + CommandUtils.labelsToString(givenLabels); + for (CommandArgumentDescription argument : command.getArguments()) { + syntaxLine += " " + formatArgument(argument); + } + lines.add(syntaxLine); + } + + private static void printDetailedDescription(CommandDescription command, List lines) { + lines.add(ChatColor.GOLD + "Short Description: " + ChatColor.WHITE + command.getDescription()); + lines.add(ChatColor.GOLD + "Detailed Description:"); + lines.add(ChatColor.WHITE + " " + command.getDetailedDescription()); + } + + private static void printArguments(CommandDescription command, List lines) { + if (!command.getArguments().isEmpty()) { return; } - // Get the command description, and make sure it's valid - CommandDescription command = result.getCommandDescription(); - if (command == null) { - // Show a warning message - sender.sendMessage(ChatColor.DARK_RED + "Failed to retrieve any help information!"); - return; - } + lines.add(ChatColor.GOLD + "Arguments:"); + for (CommandArgumentDescription argument : command.getArguments()) { + StringBuilder argString = new StringBuilder(); + argString.append(" ").append(ChatColor.YELLOW).append(ChatColor.ITALIC).append(argument.getName()) + .append(": ").append(ChatColor.WHITE).append(argument.getDescription()); - // Get the proper command reference to use for the help page - CommandParts commandReference = command.getCommandReference(result.getLabels()); - - // Get the base command - String baseCommand = commandReference.get(0); - - // Make sure the difference between the command reference and the actual command isn't too big - final double commandDifference = result.getDifference(); - if (commandDifference > 0.20) { - // Show the unknown command warning - sender.sendMessage(ChatColor.DARK_RED + "No help found for '" + helpQuery + "'!"); - - // Show a command suggestion if available and the difference isn't too big - if (commandDifference < 0.75 && result.getCommandDescription() != null) { - // Get the suggested command - List suggestedCommandParts = CollectionUtils.getRange( - result.getCommandDescription().getCommandReference(commandReference).getList(), 1); - sender.sendMessage(ChatColor.YELLOW + "Did you mean " + ChatColor.GOLD + "/" + baseCommand - + " help " + CommandUtils.labelsToString(suggestedCommandParts) + ChatColor.YELLOW + "?"); + if (argument.isOptional()) { + argString.append(ChatColor.GRAY).append(ChatColor.ITALIC).append(" (Optional)"); } + lines.add(argString.toString()); + } + } - // Show the help command - sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/" + baseCommand + " help" + ChatColor.YELLOW + " to view help."); + // FIXME: labels is currently assumed to be only the ones leading to the given command, but we have scenarios where + // we're guessing the command, so the final label isn't any existing one + private static void printAlternatives(CommandDescription command, List labels, List lines) { + if (command.getLabels().size() <= 1) { return; } - // Show a message when the command handler is assuming a command - if (commandDifference > 0) { - // Get the suggested command - List suggestedCommandParts = CollectionUtils.getRange( - result.getCommandDescription().getCommandReference(commandReference).getList(), 1); + // Print the header + lines.add(ChatColor.GOLD + "Alternatives:"); - // Show the suggested command - sender.sendMessage(ChatColor.DARK_RED + "No help found, assuming '" + ChatColor.GOLD - + CommandUtils.labelsToString(suggestedCommandParts) + ChatColor.DARK_RED + "'!"); + // Get the label used + // fixme this is not correct if help is triggered by incorrect number of arguments + final String usedLabel = labels.get(labels.size() - 1); + + // Create a list of alternatives + List alternatives = new ArrayList<>(); + for (String entry : command.getLabels()) { + if (!entry.equalsIgnoreCase(usedLabel)) { + alternatives.add(entry); + } } - // Print the help header - sender.sendMessage(ChatColor.GOLD + "==========[ " + Settings.helpHeader.toUpperCase() + " HELP ]=========="); + // Sort the alternatives + Collections.sort(alternatives, new Comparator() { + // TODO ljacqu 20151212: This computes the difference each time anew. It might make sense to compute the + // difference once and to store it in some map-like structure (Guava has some interesting ones) + @Override + public int compare(String o1, String o2) { + return Double.compare(StringUtils.getDifference(usedLabel, o1), + StringUtils.getDifference(usedLabel, o2)); + } + }); - // Print the command help information - if (showCommand) - HelpPrinter.printCommand(sender, command, commandReference); - if (showDescription) - HelpPrinter.printCommandDescription(sender, command); - if (showArguments) - HelpPrinter.printArguments(sender, command); - if (showPermissions) - HelpPrinter.printPermissions(sender, command); - if (showAlternatives) - HelpPrinter.printAlternatives(sender, command, commandReference); - if (showCommands) - HelpPrinter.printChildren(sender, command, commandReference); + // Print each alternative with proper syntax + for (String alternative : alternatives) { + // fixme add highlight functionality (see commented old line) + // sender.sendMessage(" " + _HelpSyntaxHelper.getCommandSyntax(command, commandReference, alternative, true)); + lines.add(" " + CommandUtils.labelsToString(labels) + " " + alternative); + } } + + public static void printPermissions(CommandDescription command, CommandSender sender, + PermissionsManager permissionsManager, List lines) { + CommandPermissions permissions = command.getCommandPermissions(); + if (permissions == null || CollectionUtils.isEmpty(permissions.getPermissionNodes())) { + return; + } + lines.add(ChatColor.GOLD + "Permissions:"); + + for (PermissionNode node : permissions.getPermissionNodes()) { + boolean hasPermission = permissionsManager.hasPermission(sender, node); + final String nodePermsString = "" + ChatColor.GRAY + ChatColor.ITALIC + + (hasPermission ? " (You have permission)" : " (No permission)"); + lines.add(" " + ChatColor.YELLOW + ChatColor.ITALIC + node.getNode() + nodePermsString); + } + + // Addendum to the line to specify whether the sender has permission or not when default is OP_ONLY + final DefaultPermission defaultPermission = permissions.getDefaultPermission(); + String addendum = ""; + if (DefaultPermission.OP_ONLY.equals(defaultPermission)) { + addendum = PermissionsManager.evaluateDefaultPermission(defaultPermission, sender) + ? " (You have permission)" + : " (No permission)"; + } + lines.add(ChatColor.GOLD + "Default: " + ChatColor.GRAY + ChatColor.ITALIC + + defaultPermission.getTitle() + addendum); + + // Evaluate if the sender has permission to the command + if (permissionsManager.hasPermission(sender, command)) { + lines.add(ChatColor.GOLD + " Result: " + ChatColor.GREEN + ChatColor.ITALIC + "You have permission"); + } else { + lines.add(ChatColor.GOLD + " Result: " + ChatColor.DARK_RED + ChatColor.ITALIC + "No permission"); + } + } + + private static void printChildren(CommandDescription command, List parentLabels, List lines) { + if (command.getChildren().isEmpty()) { + return; + } + + lines.add(ChatColor.GOLD + "Commands:"); + String parentCommandPath = CommandUtils.labelsToString(parentLabels); + for (CommandDescription child : command.getChildren()) { + lines.add(" " + parentCommandPath + child.getLabels().get(0) + + ChatColor.GRAY + ChatColor.ITALIC + ": " + child.getDescription()); + } + } + + private static String formatArgument(CommandArgumentDescription argument) { + if (argument.isOptional()) { + return " [" + argument.getName() + "]"; + } + return " <" + argument.getName() + ">"; + } + + private static boolean hasFlag(int flag, int options) { + return (flag & options) != 0; + } + } diff --git a/src/main/java/fr/xephi/authme/permission/DefaultPermission.java b/src/main/java/fr/xephi/authme/permission/DefaultPermission.java index 09f0856a..f09de526 100644 --- a/src/main/java/fr/xephi/authme/permission/DefaultPermission.java +++ b/src/main/java/fr/xephi/authme/permission/DefaultPermission.java @@ -6,11 +6,28 @@ package fr.xephi.authme.permission; public enum DefaultPermission { /** No one can execute the command. */ - NOT_ALLOWED, + NOT_ALLOWED("No permission"), /** Only players with the OP status may execute the command. */ - OP_ONLY, + OP_ONLY("OP's only"), /** The command can be executed by anyone. */ - ALLOWED + ALLOWED("Everyone allowed"); + + /** Textual representation of the default permission. */ + private final String title; + + /** + * Constructor. + * @param title The textual representation + */ + DefaultPermission(String title) { + this.title = title; + } + + /** Return the textual representation. */ + public String getTitle() { + return title; + } + }