From 6176369f35629df12089fff42113097b8134ec41 Mon Sep 17 00:00:00 2001 From: Magnus Ulf Date: Mon, 2 Mar 2015 17:08:11 +0100 Subject: [PATCH] Made better cmd error msgs. ArAbstractSelect will euggest a matching option if, an invalid arg was passed & a matching one exists at. If an invalid subcommand aliases were passed, a matching subcommand will be suggested. After feedback from MonMarty a few misc messages was changed. --- src/com/massivecraft/massivecore/Lang.java | 9 +- .../massivecore/cmd/MassiveCommand.java | 79 ++++++++++++++-- .../massivecore/cmd/arg/ARAbstractSelect.java | 93 +++++++++++++------ 3 files changed, 142 insertions(+), 39 deletions(-) diff --git a/src/com/massivecraft/massivecore/Lang.java b/src/com/massivecraft/massivecore/Lang.java index f11c54f1..cc143596 100644 --- a/src/com/massivecraft/massivecore/Lang.java +++ b/src/com/massivecraft/massivecore/Lang.java @@ -7,7 +7,10 @@ public class Lang public static final String COMMAND_SENDER_MUST_BE_PLAYER = "This command can only be used by ingame players."; public static final String COMMAND_SENDER_MUSNT_BE_PLAYER = "This command can not be used by ingame players."; - public static final String COMMAND_TO_FEW_ARGS = "Too few arguments. Use like this:"; - public static final String COMMAND_TO_MANY_ARGS = "Strange arguments %s."; - public static final String COMMAND_TO_MANY_ARGS2 = "Use the command like this:"; + public static final String COMMAND_TOO_FEW_ARGS = "Not enough command input. You should use it like this:"; + public static final String COMMAND_TOO_MANY_ARGS = "Too much command input %s."; + public static final String COMMAND_TOO_MANY_ARGS2 = "You should use the command like this:"; + public static final String COMMAND_NO_SUCH_SUB = "The server couldn't find the command \"%s\"."; + public static final String COMMAND_SUGGEST_SUB = "Maybe you could try %s"; + public static final String COMMAND_GET_HELP = "Use %s to see commands."; } diff --git a/src/com/massivecraft/massivecore/cmd/MassiveCommand.java b/src/com/massivecraft/massivecore/cmd/MassiveCommand.java index e394a697..5ceebbcb 100644 --- a/src/com/massivecraft/massivecore/cmd/MassiveCommand.java +++ b/src/com/massivecraft/massivecore/cmd/MassiveCommand.java @@ -10,6 +10,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import org.apache.commons.lang.StringUtils; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; @@ -102,6 +103,7 @@ public class MassiveCommand protected List subCommands; public List getSubCommands() { return this.subCommands; } public void setSubCommands(List subCommands) { this.subCommands = subCommands; } + public boolean isParentCommand() { return this.getSubCommands().size() > 0; } public MassiveCommand getSubCommand(String alias) { @@ -437,8 +439,8 @@ public class MassiveCommand { if (sender != null) { - msg(Lang.COMMAND_TO_FEW_ARGS); - sender.sendMessage(this.getUseageTemplate()); + msg(Lang.COMMAND_TOO_FEW_ARGS); + sendMessage(this.getUseageTemplate()); } return false; } @@ -447,11 +449,33 @@ public class MassiveCommand { if (sender != null) { - // Get the to many string slice - List theToMany = args.subList(this.getRequiredArgs().size() + this.optionalArgs.size(), args.size()); - msg(Lang.COMMAND_TO_MANY_ARGS, Txt.implodeCommaAndDot(theToMany, Txt.parse("%s"), Txt.parse(", "), Txt.parse(" and "), "")); - msg(Lang.COMMAND_TO_MANY_ARGS2); - sender.sendMessage(this.getUseageTemplate()); + if (this.isParentCommand()) + { + String arg = this.arg(0); + + // Try Levenshtein + List matches = this.getSimilarSubcommandAliases(arg, this.getMaxLevenshteinDistanceForArg(arg)); + + msg(Lang.COMMAND_NO_SUCH_SUB, this.getUseageTemplate() + " " + arg); + if ( ! matches.isEmpty()) + { + String suggest = Txt.parse(Txt.implodeCommaAnd(matches, ", ", " or ")); + msg(Lang.COMMAND_SUGGEST_SUB, this.getUseageTemplate() + " " + suggest); + } + else + { + msg(Lang.COMMAND_GET_HELP, this.getUseageTemplate()); + } + + } + else + { + // Get the too many string slice + List theTooMany = args.subList(this.getRequiredArgs().size() + this.optionalArgs.size(), args.size()); + msg(Lang.COMMAND_TOO_MANY_ARGS, Txt.implodeCommaAndDot(theTooMany, Txt.parse("%s"), Txt.parse(", "), Txt.parse(" and "), "")); + msg(Lang.COMMAND_TOO_MANY_ARGS2); + sendMessage(this.getUseageTemplate()); + } } return false; } @@ -462,6 +486,47 @@ public class MassiveCommand return this.isArgsValid(args, null); } + // -------------------------------------------- // + // MATCHING SUGGESTIONS + // -------------------------------------------- // + + public List getSimilarAliases(String arg, int maxLevenshteinDistance) + { + arg = arg.toLowerCase(); + + List matches = new ArrayList(); + + for (String alias : this.getAliases()) + { + String aliaslc = alias.toLowerCase(); + int distance = StringUtils.getLevenshteinDistance(arg, aliaslc); + if (distance > maxLevenshteinDistance) continue; + matches.add(alias); + } + return matches; + } + + public List getSimilarSubcommandAliases(String arg, int maxLevenshteinDistance) + { + // Try Levenshtein + List matches = new ArrayList(); + + for (MassiveCommand sub : this.getSubCommands()) + { + matches.addAll(sub.getSimilarAliases(arg, maxLevenshteinDistance)); + } + return matches; + } + + public int getMaxLevenshteinDistanceForArg(String arg) + { + if (arg.length() <= 1) return 0; // When dealing with 1 character aliases, there is way too many options. So we don't suggest. + if (arg.length() <= 3) return 1; // When dealing with low length aliases, there too many options. So we won't suggest much + if (arg.length() < 8) return 2; // 2 is default. + + return 3; // If it were 8 characters or more, we end up here. Because many characters allow for more typos. + } + // -------------------------------------------- // // HELP AND USAGE INFORMATION // -------------------------------------------- // diff --git a/src/com/massivecraft/massivecore/cmd/arg/ARAbstractSelect.java b/src/com/massivecraft/massivecore/cmd/arg/ARAbstractSelect.java index c3b8764c..09679da5 100644 --- a/src/com/massivecraft/massivecore/cmd/arg/ARAbstractSelect.java +++ b/src/com/massivecraft/massivecore/cmd/arg/ARAbstractSelect.java @@ -1,7 +1,10 @@ package com.massivecraft.massivecore.cmd.arg; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; +import org.apache.commons.lang.StringUtils; import org.bukkit.command.CommandSender; import com.massivecraft.massivecore.MassiveException; @@ -33,36 +36,68 @@ public abstract class ARAbstractSelect extends ArgReaderAbstract { T result = this.select(arg, sender); - if (result == null) - { - MassiveException exception = new MassiveException(); - exception.addMsg("No %s matches \"%s\".", this.typename(), arg); - - if (this.canList(sender)) - { - Collection names = this.altNames(sender); - if (names.size() == 0) - { - exception.addMsg("Note: There is no %s available.", this.typename()); - } - else if (names.size() > LIST_COUNT_MAX) - { - exception.addMsg("More than %d alternatives available.", LIST_COUNT_MAX); - } - else - { - String format = Txt.parse("%s"); - String comma = Txt.parse(", "); - String and = Txt.parse(" or "); - String dot = Txt.parse("."); - exception.addMsg("Use %s", Txt.implodeCommaAndDot(names, format, comma, and, dot)); - } - } - - throw exception; - } + if (result != null) return result; - return result; + MassiveException exception = new MassiveException(); + exception.addMsg("No %s matches \"%s\".", this.typename(), arg); + + if (this.canList(sender)) + { + Collection names = this.altNames(sender); + + // Try Levenshtein + List matches = this.getMatchingAltNames(arg, sender, this.getMaxLevenshteinDistanceForArg(arg)); + + if (names.isEmpty()) + { + exception.addMsg("Note: There is no %s available.", this.typename()); + } + else if ( ! matches.isEmpty() && matches.size() < LIST_COUNT_MAX) + { + // For some reason the arguments doesn't get parsed. + String suggest = Txt.parse(Txt.implodeCommaAnd(matches, ", ", " or ")); + exception.addMsg("Did you mean %s?", suggest); + } + else if (names.size() > LIST_COUNT_MAX) + { + exception.addMsg("More than %d alternatives available.", LIST_COUNT_MAX); + } + else + { + String format = Txt.parse("%s"); + String comma = Txt.parse(", "); + String and = Txt.parse(" or "); + String dot = Txt.parse("."); + exception.addMsg("Use %s", Txt.implodeCommaAndDot(names, format, comma, and, dot)); + } + } + + throw exception; + } + + public List getMatchingAltNames(String arg, CommandSender sender, int maxLevenshteinDistance) + { + arg = arg.toLowerCase(); + + // Try Levenshtein + List matches = new ArrayList(); + + for (String alias : this.altNames(sender)) + { + String aliaslc = alias.toLowerCase(); + int distance = StringUtils.getLevenshteinDistance(arg, aliaslc); + if (distance > maxLevenshteinDistance) continue; + matches.add(alias); + } + return matches; + } + + public int getMaxLevenshteinDistanceForArg(String arg) + { + if (arg.length() <= 1) return 0; // When dealing with 1 character aliases, there is way too many options. + if (arg.length() < 8) return 1; // 1 is default. + + return 2; // If it were 8 characters or more, we end up here. Because many characters allow for more typos. } }