#315 #305 Refactor HelpProvider - add bit flags, reduce duplication (wip)

This commit is contained in:
ljacqu 2015-12-12 22:50:07 +01:00
parent d26cbfbd14
commit 01cba2a508
4 changed files with 242 additions and 118 deletions

View File

@ -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<String> 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<CommandDescription> getChildren() {
return this.children;
return children;
}
/**
* Return all arguments the command takes.
*
* @return Command arguments.
*/
public List<CommandArgumentDescription> 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;
}
/**

View File

@ -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<String>, boolean, boolean, ...)
List<String> lines = HelpProvider.printHelp(result, HelpProvider.SHOW_ARGUMENTS);
for (String line : lines) {
sender.sendMessage(line);
}
List<String> 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

View File

@ -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 <i>not</i> 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<String> 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<String> reference, List<String> 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<String> 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<String> 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<String> 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<String> 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<String> correctLabels, List<String> lines) {
// Ensure that we have all labels to go to the command
int requiredLabels = command.getParentCount() + 1;
List<String> 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<String> 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<String> 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<String> 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<String> labels, List<String> 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<String> 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<String> 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<String>() {
// 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<String> 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<String> parentLabels, List<String> 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;
}
}

View File

@ -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;
}
}