Skip to content

Commit 07a080a

Browse files
committed
Added UnknownSubcommandException
1 parent dff2c1d commit 07a080a

File tree

3 files changed

+163
-3
lines changed

3 files changed

+163
-3
lines changed

src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import fr.zcraft.quartzlib.components.commands.exceptions.CommandException;
44
import fr.zcraft.quartzlib.components.commands.exceptions.MissingSubcommandException;
5+
import fr.zcraft.quartzlib.components.commands.exceptions.UnknownSubcommandException;
56
import java.lang.reflect.Field;
67
import java.util.Arrays;
78
import java.util.Collection;
@@ -97,9 +98,11 @@ private void runSelf(Object instance, CommandSender sender, String[] args) throw
9798
throw new MissingSubcommandException(this);
9899
}
99100

100-
String commandName = args[0];
101-
CommandNode subCommand = subCommands.get(commandName);
102-
// TODO: handle null
101+
CommandNode subCommand = subCommands.get(args[0]);
102+
if (subCommand == null) {
103+
throw new UnknownSubcommandException(this, args[0]);
104+
}
105+
103106
subCommand.run(instance, sender, Arrays.copyOfRange(args, 1, args.length));
104107
}
105108

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package fr.zcraft.quartzlib.components.commands.exceptions;
2+
3+
import fr.zcraft.quartzlib.components.commands.CommandGroup;
4+
import fr.zcraft.quartzlib.components.commands.CommandNode;
5+
import fr.zcraft.quartzlib.components.i18n.I;
6+
import fr.zcraft.quartzlib.components.rawtext.RawText;
7+
import fr.zcraft.quartzlib.components.rawtext.RawTextPart;
8+
import fr.zcraft.quartzlib.tools.text.StringUtils;
9+
import org.bukkit.ChatColor;
10+
import org.bukkit.command.CommandSender;
11+
import org.jetbrains.annotations.Nullable;
12+
13+
public class UnknownSubcommandException extends CommandException {
14+
public static final String CHAT_PREFIX = "┃ ";
15+
private final CommandGroup commandGroup;
16+
private final String attemptedSubcommand;
17+
18+
public UnknownSubcommandException(CommandGroup commandGroup, String attemptedSubcommand) {
19+
this.commandGroup = commandGroup;
20+
this.attemptedSubcommand = attemptedSubcommand;
21+
}
22+
23+
@Override
24+
public RawText display(CommandSender sender) {
25+
RawTextPart<?> text = new RawText(CHAT_PREFIX).color(ChatColor.DARK_RED)
26+
.then(I.t("Unknown subcommand: ")).color(ChatColor.RED)
27+
.then("/").color(ChatColor.WHITE)
28+
.then(getParents() + " ").color(ChatColor.AQUA)
29+
.then(attemptedSubcommand).style(ChatColor.RED, ChatColor.UNDERLINE, ChatColor.BOLD)
30+
.hover(appendSubCommandList(new RawText()));
31+
32+
String nearest = getNearestCommand();
33+
34+
if (nearest != null) {
35+
text = text.then("\n" + CHAT_PREFIX).color(ChatColor.AQUA)
36+
.then(" " + I.t("Did you mean") + ": ").color(ChatColor.GRAY)
37+
.then("/").color(ChatColor.WHITE)
38+
.then(getParents() + " ").color(ChatColor.AQUA)
39+
.then(nearest).color(ChatColor.DARK_AQUA);
40+
}
41+
42+
return text.build();
43+
}
44+
45+
private static final int MAX_DISTANCE = 10;
46+
47+
@Nullable
48+
private String getNearestCommand() {
49+
String nearest = null;
50+
int nearestDistance = MAX_DISTANCE;
51+
52+
for (CommandNode subCommand : commandGroup.getSubCommands()) {
53+
String name = subCommand.getName();
54+
int distance = StringUtils.levenshteinDistance(attemptedSubcommand, name);
55+
56+
if (distance < nearestDistance) {
57+
nearest = name;
58+
nearestDistance = distance;
59+
}
60+
}
61+
62+
return nearest;
63+
}
64+
65+
private String getParents() {
66+
StringBuilder builder = new StringBuilder();
67+
68+
CommandGroup group = commandGroup;
69+
70+
do {
71+
if (builder.length() > 0) {
72+
builder.append(' ');
73+
}
74+
builder.append(group.getName());
75+
group = group.getParent();
76+
} while (group != null);
77+
78+
return builder.toString();
79+
}
80+
81+
private RawTextPart<?> appendSubCommandList(RawTextPart<?> text) {
82+
boolean first = true;
83+
text = text.then(I.t("Should be one of the following:\n "));
84+
for (CommandNode subCommand : commandGroup.getSubCommands()) {
85+
if (!first) {
86+
text = text.then(", ").color(ChatColor.GRAY);
87+
}
88+
first = false;
89+
90+
text = text.then(subCommand.getName()).color(ChatColor.AQUA);
91+
}
92+
93+
return text;
94+
}
95+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package fr.zcraft.quartzlib.tools.text;
2+
3+
/**
4+
* Various string-related utilities.
5+
*/
6+
public final class StringUtils {
7+
private StringUtils() {
8+
}
9+
10+
/**
11+
* Compute the distance of Levenshtein Distance between two strings.
12+
*
13+
* <p>Implementation is from:
14+
* https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Java</p>
15+
* @param lhs The first string
16+
* @param rhs The second string
17+
* @return The distance between the two strings
18+
*/
19+
public static int levenshteinDistance(CharSequence lhs, CharSequence rhs) {
20+
int len0 = lhs.length() + 1;
21+
int len1 = rhs.length() + 1;
22+
23+
// the array of distances
24+
int[] cost = new int[len0];
25+
int[] newcost = new int[len0];
26+
27+
// initial cost of skipping prefix in String s0
28+
for (int i = 0; i < len0; i++) {
29+
cost[i] = i;
30+
}
31+
32+
// dynamically computing the array of distances
33+
34+
// transformation cost for each letter in s1
35+
for (int j = 1; j < len1; j++) {
36+
// initial cost of skipping prefix in String s1
37+
newcost[0] = j;
38+
39+
// transformation cost for each letter in s0
40+
for (int i = 1; i < len0; i++) {
41+
// matching current letters in both strings
42+
int match = (lhs.charAt(i - 1) == rhs.charAt(j - 1)) ? 0 : 1;
43+
44+
// computing cost for each transformation
45+
int costReplace = cost[i - 1] + match;
46+
int costInsert = cost[i] + 1;
47+
int costDelete = newcost[i - 1] + 1;
48+
49+
// keep minimum cost
50+
newcost[i] = Math.min(Math.min(costInsert, costDelete), costReplace);
51+
}
52+
53+
// swap cost/newcost arrays
54+
int[] swap = cost;
55+
cost = newcost;
56+
newcost = swap;
57+
}
58+
59+
// the distance is the cost for transforming all letters in both strings
60+
return cost[len0 - 1];
61+
}
62+
}

0 commit comments

Comments
 (0)