Implement aliases, child validation and tree build tests

This commit is contained in:
MD 2023-06-06 17:11:41 +01:00
parent b8e33d995e
commit c18f54bed1
2 changed files with 57 additions and 25 deletions

View File

@ -5,7 +5,10 @@ import org.bukkit.Server;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale;
public abstract class EssentialsCommandNode<T> { public abstract class EssentialsCommandNode<T> {
private final ArrayList<EssentialsCommandNode<T>> childNodes = new ArrayList<>(); private final ArrayList<EssentialsCommandNode<T>> childNodes = new ArrayList<>();
@ -37,6 +40,10 @@ public abstract class EssentialsCommandNode<T> {
throw new NoChargeException(); throw new NoChargeException();
} }
protected List<EssentialsCommandNode<T>> getChildNodes() {
return Collections.unmodifiableList(childNodes);
}
public abstract boolean matches(final WalkContext<T> context); public abstract boolean matches(final WalkContext<T> context);
public static Root<CommandSource> root(final Initializer<CommandSource> initializer) { public static Root<CommandSource> root(final Initializer<CommandSource> initializer) {
@ -54,8 +61,8 @@ public abstract class EssentialsCommandNode<T> {
this.node = node; this.node = node;
} }
public void literal(final String name, final Initializer<T> initializer) { public void literal(final String name, final Initializer<T> initializer, final String... aliases) {
node.childNodes.add(new Literal<>(name, initializer)); node.childNodes.add(new Literal<>(name, aliases, initializer));
} }
public void execute(final RunHandler<T> runHandler) { public void execute(final RunHandler<T> runHandler) {
@ -112,6 +119,9 @@ public abstract class EssentialsCommandNode<T> {
public static class Root<T> extends EssentialsCommandNode<T> { public static class Root<T> extends EssentialsCommandNode<T> {
protected Root(Initializer<T> initializer) { protected Root(Initializer<T> initializer) {
super(initializer); super(initializer);
if (getChildNodes().isEmpty()) {
throw new RuntimeException("Root nodes must be initialised with at least one child");
}
} }
@Override @Override
@ -130,14 +140,24 @@ public abstract class EssentialsCommandNode<T> {
public static class Literal<T> extends EssentialsCommandNode<T> { public static class Literal<T> extends EssentialsCommandNode<T> {
private final String name; private final String name;
private final HashSet<String> aliases;
protected Literal(String name, Initializer<T> initializer) { protected Literal(String name, String[] aliases, Initializer<T> initializer) {
super(initializer); super(initializer);
if (getChildNodes().isEmpty()) {
throw new RuntimeException("Literal nodes must be initialised with at least one child (node name: " + name + ")");
}
this.name = name; this.name = name;
this.aliases = new HashSet<>();
this.aliases.add(name.toLowerCase(Locale.ROOT));
for (final String alias : aliases) {
this.aliases.add(alias.toLowerCase(Locale.ROOT));
}
} }
public boolean matches(WalkContext<T> context) { public boolean matches(WalkContext<T> context) {
return context.args.length > 0 && context.args[0].equalsIgnoreCase(name); return context.args.length > 0 && aliases.contains(context.args[0].toLowerCase(Locale.ROOT));
} }
@Override @Override

View File

@ -6,13 +6,13 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.Executable;
import org.mockito.InOrder; import org.mockito.InOrder;
import org.mockito.Mockito;
import java.util.Arrays; import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
class EssentialsCommandNodeTest { class EssentialsCommandNodeTest {
private FakeServer fakeServer; private FakeServer fakeServer;
@ -27,21 +27,18 @@ class EssentialsCommandNodeTest {
consoleSource = mock(CommandSource.class); consoleSource = mock(CommandSource.class);
} }
@Test EssentialsCommandNode.Root<CommandSource> buildCommonTree() {
void testNonTerminateThrow() { return EssentialsCommandNode.root(root -> {
final EssentialsCommandNode.Root<CommandSource> rootNode = EssentialsCommandNode.root(root -> { root.literal("hello", hello -> hello.execute(ctx -> {
root.literal("hello", hello -> { if (ctx.args().length < 1) {
hello.execute(ctx -> { ctx.sender().sendMessage("hello to who?");
if (ctx.args().length < 1) { } else if (ctx.args().length < 2) {
ctx.sender().sendMessage("hello to who?"); ctx.sender().sendMessage("hi there " + ctx.args()[0]);
} else if (ctx.args().length < 2) { } else {
ctx.sender().sendMessage("hi there " + ctx.args()[0]); ctx.sender().sendMessage("woah hi " + String.join(" and ", ctx.args()));
} else { }
ctx.sender().sendMessage("woah hi " + String.join(" and ", ctx.args())); System.out.println(Arrays.toString(ctx.args()));
} }));
System.out.println(Arrays.toString(ctx.args()));
});
});
root.literal("bye", bye -> { root.literal("bye", bye -> {
bye.literal("forever just kidding", bye1 -> { bye1.execute(ctx -> { throw new RuntimeException("this shouldn't happen"); }); }); bye.literal("forever just kidding", bye1 -> { bye1.execute(ctx -> { throw new RuntimeException("this shouldn't happen"); }); });
bye.literal("forever", bye2 -> bye2.execute(ctx -> ctx.sender().sendMessage(":(("))); bye.literal("forever", bye2 -> bye2.execute(ctx -> ctx.sender().sendMessage(":((")));
@ -53,8 +50,23 @@ class EssentialsCommandNodeTest {
ctx.sender().sendMessage("wait you can't leave"); ctx.sender().sendMessage("wait you can't leave");
} }
}); });
}); }, "farewell", "tschuss");
}); });
}
@Test
void testBuild() {
assertThrows(RuntimeException.class, () -> EssentialsCommandNode.root(root -> {}), "empty root");
assertThrows(RuntimeException.class, () -> EssentialsCommandNode.root(root -> {
root.literal("potato", potato -> {});
}), "empty literal");
assertDoesNotThrow(this::buildCommonTree, "build complete tree");
}
@Test
void testEval() {
final EssentialsCommandNode.Root<CommandSource> rootNode = buildCommonTree();
assertThrows(NoChargeException.class, () -> rootNode.run(fakeServer, playerSource, "test", new String[]{""}), "wrongly parsed empty arg"); assertThrows(NoChargeException.class, () -> rootNode.run(fakeServer, playerSource, "test", new String[]{""}), "wrongly parsed empty arg");
assertThrows(NoChargeException.class, () -> rootNode.run(fakeServer, playerSource, "test", new String[]{"wilkommen"}), "wrongly parsed unknown literal"); // wrongly parsed German assertThrows(NoChargeException.class, () -> rootNode.run(fakeServer, playerSource, "test", new String[]{"wilkommen"}), "wrongly parsed unknown literal"); // wrongly parsed German
@ -63,17 +75,17 @@ class EssentialsCommandNodeTest {
Executable playerHelloOneArg = () -> rootNode.run(fakeServer, playerSource, "test", new String[]{"hello", "world"}); Executable playerHelloOneArg = () -> rootNode.run(fakeServer, playerSource, "test", new String[]{"hello", "world"});
Executable playerHelloManyArgs = () -> rootNode.run(fakeServer, playerSource, "test", new String[]{"hello", "jroy", "pop", "lax", "evident"}); Executable playerHelloManyArgs = () -> rootNode.run(fakeServer, playerSource, "test", new String[]{"hello", "jroy", "pop", "lax", "evident"});
Executable playerBye = () -> rootNode.run(fakeServer, playerSource, "test", new String[]{"bye", "legacy", "code"}); Executable playerBye = () -> rootNode.run(fakeServer, playerSource, "test", new String[]{"bye", "legacy", "code"});
Executable consoleBye = () -> rootNode.run(fakeServer, consoleSource, "test", new String[]{"bye", "player", "data"}); Executable consoleFarewell = () -> rootNode.run(fakeServer, consoleSource, "test", new String[]{"fAREWELL", "player", "data"});
Executable consoleByeForeverJk = () -> rootNode.run(fakeServer, consoleSource, "test", new String[]{"bye", "forever", "just", "kidding"}); Executable consoleByeForeverJk = () -> rootNode.run(fakeServer, consoleSource, "test", new String[]{"bye", "forever", "just", "kidding"});
assertDoesNotThrow(playerHelloNoArgs, "parsing first level no-arg command"); assertDoesNotThrow(playerHelloNoArgs, "parsing first level no-arg command");
assertDoesNotThrow(playerHelloOneArg, "parsing first level 1 arg command"); assertDoesNotThrow(playerHelloOneArg, "parsing first level 1 arg command");
assertDoesNotThrow(playerHelloManyArgs, "parsing first level multi-arg command"); assertDoesNotThrow(playerHelloManyArgs, "parsing first level multi-arg command");
assertDoesNotThrow(playerBye); assertDoesNotThrow(playerBye);
assertDoesNotThrow(consoleBye); assertDoesNotThrow(consoleFarewell, "parsing with literal alias");
assertDoesNotThrow(consoleByeForeverJk); assertDoesNotThrow(consoleByeForeverJk);
InOrder ordered = Mockito.inOrder(playerSource, consoleSource); InOrder ordered = inOrder(playerSource, consoleSource);
ordered.verify(playerSource).sendMessage("hello to who?"); ordered.verify(playerSource).sendMessage("hello to who?");
ordered.verify(playerSource).sendMessage("hi there world"); ordered.verify(playerSource).sendMessage("hi there world");
ordered.verify(playerSource).sendMessage("woah hi jroy and pop and lax and evident"); ordered.verify(playerSource).sendMessage("woah hi jroy and pop and lax and evident");