Added a redirect option on nodes

This commit is contained in:
Nathan Adams
2017-07-31 14:39:59 +02:00
parent 7f490f0a8b
commit 4b9170fd7f
14 changed files with 162 additions and 41 deletions
@@ -7,7 +7,6 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder; import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.exceptions.CommandException; import com.mojang.brigadier.exceptions.CommandException;
import com.mojang.brigadier.exceptions.ParameterizedCommandExceptionType;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.LiteralCommandNode;
@@ -98,7 +97,11 @@ public class CommandDispatcher<S> {
throw ERROR_EXPECTED_ARGUMENT_SEPARATOR.createWithContext(reader); throw ERROR_EXPECTED_ARGUMENT_SEPARATOR.createWithContext(reader);
} }
reader.skip(); reader.skip();
if (child.getRedirect() != null) {
return parseNodes(child.getRedirect(), reader, context.redirect());
} else {
return parseNodes(child, reader, context); return parseNodes(child, reader, context);
}
} else { } else {
return new ParseResults<>(context); return new ParseResults<>(context);
} }
@@ -122,7 +125,9 @@ public class CommandDispatcher<S> {
result.add(prefix); result.add(prefix);
} }
if (!node.getChildren().isEmpty()) { if (node.getRedirect() != null) {
result.add(prefix.isEmpty() ? node.getUsageText() + ARGUMENT_SEPARATOR + "..." : prefix + ARGUMENT_SEPARATOR + "...");
} else if (!node.getChildren().isEmpty()) {
for (final CommandNode<S> child : node.getChildren()) { for (final CommandNode<S> child : node.getChildren()) {
getAllUsage(child, source, result, prefix.isEmpty() ? child.getUsageText() : prefix + ARGUMENT_SEPARATOR + child.getUsageText()); getAllUsage(child, source, result, prefix.isEmpty() ? child.getUsageText() : prefix + ARGUMENT_SEPARATOR + child.getUsageText());
} }
@@ -153,6 +158,9 @@ public class CommandDispatcher<S> {
final String close = childOptional ? USAGE_OPTIONAL_CLOSE : USAGE_REQUIRED_CLOSE; final String close = childOptional ? USAGE_OPTIONAL_CLOSE : USAGE_REQUIRED_CLOSE;
if (!deep) { if (!deep) {
if (node.getRedirect() != null) {
return self + ARGUMENT_SEPARATOR + "...";
} else {
final Collection<CommandNode<S>> children = node.getChildren().stream().filter(c -> c.canUse(source)).collect(Collectors.toList()); final Collection<CommandNode<S>> children = node.getChildren().stream().filter(c -> c.canUse(source)).collect(Collectors.toList());
if (children.size() == 1) { if (children.size() == 1) {
final String usage = getSmartUsage(children.iterator().next(), source, childOptional, childOptional); final String usage = getSmartUsage(children.iterator().next(), source, childOptional, childOptional);
@@ -187,11 +195,15 @@ public class CommandDispatcher<S> {
} }
} }
} }
}
return self; return self;
} }
private Set<String> findSuggestions(final CommandNode<S> node, final StringReader reader, final CommandContextBuilder<S> contextBuilder, final Set<String> result) { private Set<String> findSuggestions(final CommandNode<S> node, final StringReader reader, final CommandContextBuilder<S> contextBuilder, final Set<String> result) {
if (node.getRedirect() != null) {
return findSuggestions(node.getRedirect(), reader, contextBuilder, result);
}
final S source = contextBuilder.getSource(); final S source = contextBuilder.getSource();
for (final CommandNode<S> child : node.getChildren()) { for (final CommandNode<S> child : node.getChildren()) {
if (!child.canUse(source)) { if (!child.canUse(source)) {
@@ -11,10 +11,14 @@ public abstract class ArgumentBuilder<S, T extends ArgumentBuilder<S, T>> {
private final RootCommandNode<S> arguments = new RootCommandNode<>(); private final RootCommandNode<S> arguments = new RootCommandNode<>();
private Command<S> command; private Command<S> command;
private Predicate<S> requirement = s -> true; private Predicate<S> requirement = s -> true;
private CommandNode<S> target;
protected abstract T getThis(); protected abstract T getThis();
public T then(final ArgumentBuilder<S, ?> argument) { public T then(final ArgumentBuilder<S, ?> argument) {
if (target != null) {
throw new IllegalStateException("Cannot add children to a redirected node");
}
arguments.addChild(argument.build()); arguments.addChild(argument.build());
return getThis(); return getThis();
} }
@@ -41,5 +45,17 @@ public abstract class ArgumentBuilder<S, T extends ArgumentBuilder<S, T>> {
return requirement; return requirement;
} }
public T redirect(final CommandNode<S> target) {
if (!arguments.getChildren().isEmpty()) {
throw new IllegalStateException("Cannot redirect a node with children");
}
this.target = target;
return getThis();
}
public CommandNode<S> getRedirect() {
return target;
}
public abstract CommandNode<S> build(); public abstract CommandNode<S> build();
} }
@@ -25,7 +25,7 @@ public class LiteralArgumentBuilder<S> extends ArgumentBuilder<S, LiteralArgumen
@Override @Override
public LiteralCommandNode<S> build() { public LiteralCommandNode<S> build() {
final LiteralCommandNode<S> result = new LiteralCommandNode<>(getLiteral(), getCommand(), getRequirement()); final LiteralCommandNode<S> result = new LiteralCommandNode<>(getLiteral(), getCommand(), getRequirement(), getRedirect());
for (final CommandNode<S> argument : getArguments()) { for (final CommandNode<S> argument : getArguments()) {
result.addChild(argument); result.addChild(argument);
@@ -31,7 +31,7 @@ public class RequiredArgumentBuilder<S, T> extends ArgumentBuilder<S, RequiredAr
} }
public ArgumentCommandNode<S, T> build() { public ArgumentCommandNode<S, T> build() {
final ArgumentCommandNode<S, T> result = new ArgumentCommandNode<>(getName(), getType(), getCommand(), getRequirement()); final ArgumentCommandNode<S, T> result = new ArgumentCommandNode<>(getName(), getType(), getCommand(), getRequirement(), getRedirect());
for (final CommandNode<S> argument : getArguments()) { for (final CommandNode<S> argument : getArguments()) {
result.addChild(argument); result.addChild(argument);
@@ -1,7 +1,6 @@
package com.mojang.brigadier.context; package com.mojang.brigadier.context;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.primitives.Primitives; import com.google.common.primitives.Primitives;
import com.mojang.brigadier.Command; import com.mojang.brigadier.Command;
import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.CommandNode;
@@ -14,13 +13,19 @@ public class CommandContext<S> {
private final Map<String, ParsedArgument<S, ?>> arguments; private final Map<String, ParsedArgument<S, ?>> arguments;
private final Map<CommandNode<S>, String> nodes; private final Map<CommandNode<S>, String> nodes;
private final String input; private final String input;
private final CommandContext<S> parent;
public CommandContext(final S source, final Map<String, ParsedArgument<S, ?>> arguments, final Command<S> command, final Map<CommandNode<S>, String> nodes, final String input) { public CommandContext(final S source, final Map<String, ParsedArgument<S, ?>> arguments, final Command<S> command, final Map<CommandNode<S>, String> nodes, final String input, final CommandContext<S> parent) {
this.source = source; this.source = source;
this.arguments = arguments; this.arguments = arguments;
this.command = command; this.command = command;
this.nodes = nodes; this.nodes = nodes;
this.input = input; this.input = input;
this.parent = parent;
}
public CommandContext<S> getParent() {
return parent;
} }
public Command<S> getCommand() { public Command<S> getCommand() {
@@ -58,6 +63,7 @@ public class CommandContext<S> {
if (!Iterables.elementsEqual(nodes.entrySet(), that.nodes.entrySet())) return false; if (!Iterables.elementsEqual(nodes.entrySet(), that.nodes.entrySet())) return false;
if (command != null ? !command.equals(that.command) : that.command != null) return false; if (command != null ? !command.equals(that.command) : that.command != null) return false;
if (!source.equals(that.source)) return false; if (!source.equals(that.source)) return false;
if (parent != null ? !parent.equals(that.parent) : that.parent != null) return false;
return true; return true;
} }
@@ -68,6 +74,7 @@ public class CommandContext<S> {
result = 31 * result + arguments.hashCode(); result = 31 * result + arguments.hashCode();
result = 31 * result + (command != null ? command.hashCode() : 0); result = 31 * result + (command != null ? command.hashCode() : 0);
result = 31 * result + nodes.hashCode(); result = 31 * result + nodes.hashCode();
result = 31 * result + (parent != null ? parent.hashCode() : 0);
return result; return result;
} }
@@ -14,6 +14,7 @@ public class CommandContextBuilder<S> {
private final StringBuilder input = new StringBuilder(); private final StringBuilder input = new StringBuilder();
private S source; private S source;
private Command<S> command; private Command<S> command;
private CommandContext<S> parent;
public CommandContextBuilder(final CommandDispatcher<S> dispatcher, final S source) { public CommandContextBuilder(final CommandDispatcher<S> dispatcher, final S source) {
this.dispatcher = dispatcher; this.dispatcher = dispatcher;
@@ -54,13 +55,24 @@ public class CommandContextBuilder<S> {
public CommandContextBuilder<S> copy() { public CommandContextBuilder<S> copy() {
final CommandContextBuilder<S> copy = new CommandContextBuilder<>(dispatcher, source); final CommandContextBuilder<S> copy = new CommandContextBuilder<>(dispatcher, source);
copy.command = this.command; copy.command = command;
copy.arguments.putAll(arguments); copy.arguments.putAll(arguments);
copy.nodes.putAll(this.nodes); copy.nodes.putAll(nodes);
copy.input.append(input); copy.input.append(input);
copy.parent = parent;
return copy; return copy;
} }
public CommandContextBuilder<S> redirect() {
final CommandContextBuilder<S> result = new CommandContextBuilder<>(dispatcher, source);
result.parent = build();
return result;
}
public CommandContext<S> getParent() {
return parent;
}
public String getInput() { public String getInput() {
return input.toString(); return input.toString();
} }
@@ -70,7 +82,7 @@ public class CommandContextBuilder<S> {
} }
public CommandContext<S> build() { public CommandContext<S> build() {
return new CommandContext<>(source, arguments, command, nodes, input.toString()); return new CommandContext<>(source, arguments, command, nodes, input.toString(), parent);
} }
public CommandDispatcher<S> getDispatcher() { public CommandDispatcher<S> getDispatcher() {
@@ -18,8 +18,8 @@ public class ArgumentCommandNode<S, T> extends CommandNode<S> {
private final String name; private final String name;
private final ArgumentType<T> type; private final ArgumentType<T> type;
public ArgumentCommandNode(final String name, final ArgumentType<T> type, final Command<S> command, final Predicate<S> requirement) { public ArgumentCommandNode(final String name, final ArgumentType<T> type, final Command<S> command, final Predicate<S> requirement, final CommandNode<S> redirect) {
super(command, requirement); super(command, requirement, redirect);
this.name = name; this.name = name;
this.type = type; this.type = type;
} }
@@ -69,6 +69,7 @@ public class ArgumentCommandNode<S, T> extends CommandNode<S> {
public RequiredArgumentBuilder<S, T> createBuilder() { public RequiredArgumentBuilder<S, T> createBuilder() {
final RequiredArgumentBuilder<S, T> builder = RequiredArgumentBuilder.argument(name, type); final RequiredArgumentBuilder<S, T> builder = RequiredArgumentBuilder.argument(name, type);
builder.requires(getRequirement()); builder.requires(getRequirement());
builder.redirect(getRedirect());
if (getCommand() != null) { if (getCommand() != null) {
builder.executes(getCommand()); builder.executes(getCommand());
} }
@@ -18,11 +18,13 @@ import java.util.stream.Collectors;
public abstract class CommandNode<S> implements Comparable<CommandNode<S>> { public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
private Map<Object, CommandNode<S>> children = Maps.newLinkedHashMap(); private Map<Object, CommandNode<S>> children = Maps.newLinkedHashMap();
private final Predicate<S> requirement; private final Predicate<S> requirement;
private final CommandNode<S> redirect;
private Command<S> command; private Command<S> command;
protected CommandNode(final Command<S> command, final Predicate<S> requirement) { protected CommandNode(final Command<S> command, final Predicate<S> requirement, final CommandNode<S> redirect) {
this.command = command; this.command = command;
this.requirement = requirement; this.requirement = requirement;
this.redirect = redirect;
} }
public Command<S> getCommand() { public Command<S> getCommand() {
@@ -33,6 +35,10 @@ public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
return children.values(); return children.values();
} }
public CommandNode<S> getRedirect() {
return redirect;
}
public boolean canUse(final S source) { public boolean canUse(final S source) {
return requirement.test(source); return requirement.test(source);
} }
@@ -15,8 +15,8 @@ public class LiteralCommandNode<S> extends CommandNode<S> {
private final String literal; private final String literal;
public LiteralCommandNode(final String literal, final Command<S> command, final Predicate<S> requirement) { public LiteralCommandNode(final String literal, final Command<S> command, final Predicate<S> requirement, final CommandNode<S> redirect) {
super(command, requirement); super(command, requirement, redirect);
this.literal = literal; this.literal = literal;
} }
@@ -78,6 +78,7 @@ public class LiteralCommandNode<S> extends CommandNode<S> {
public LiteralArgumentBuilder<S> createBuilder() { public LiteralArgumentBuilder<S> createBuilder() {
final LiteralArgumentBuilder<S> builder = LiteralArgumentBuilder.literal(this.literal); final LiteralArgumentBuilder<S> builder = LiteralArgumentBuilder.literal(this.literal);
builder.requires(getRequirement()); builder.requires(getRequirement());
builder.redirect(getRedirect());
if (getCommand() != null) { if (getCommand() != null) {
builder.executes(getCommand()); builder.executes(getCommand());
} }
@@ -9,7 +9,7 @@ import java.util.Set;
public class RootCommandNode<S> extends CommandNode<S> { public class RootCommandNode<S> extends CommandNode<S> {
public RootCommandNode() { public RootCommandNode() {
super(null, c -> true); super(null, c -> true, null);
} }
@Override @Override
@@ -39,6 +39,14 @@ public class CommandDispatcherCompletionsTest {
assertThat(subject.getCompletionSuggestions("q", source), is(emptyArray())); assertThat(subject.getCompletionSuggestions("q", source), is(emptyArray()));
} }
@Test
public void testCommand_redirect() throws Exception {
subject.register(literal("foo"));
subject.register(literal("bar"));
subject.register(literal("redirect").redirect(subject.getRoot()));
assertThat(subject.getCompletionSuggestions("redirect ", source), equalTo(new String[]{"bar", "foo", "redirect"}));
}
@Test @Test
public void testSubCommand() throws Exception { public void testSubCommand() throws Exception {
subject.register(literal("foo").then(literal("abc")).then(literal("def")).then(literal("ghi").requires(s -> false))); subject.register(literal("foo").then(literal("abc")).then(literal("def")).then(literal("ghi").requires(s -> false)));
@@ -14,9 +14,12 @@ import java.util.Collections;
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer; import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
import static com.mojang.brigadier.builder.LiteralArgumentBuilder.literal; import static com.mojang.brigadier.builder.LiteralArgumentBuilder.literal;
import static com.mojang.brigadier.builder.RequiredArgumentBuilder.argument; import static com.mojang.brigadier.builder.RequiredArgumentBuilder.argument;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.Matchers.notNull;
import static org.mockito.Mockito.any; import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
@@ -164,6 +167,30 @@ public class CommandDispatcherTest {
verify(subCommand).run(any(CommandContext.class)); verify(subCommand).run(any(CommandContext.class));
} }
@SuppressWarnings("unchecked")
@Test
public void testExecuteRedirected() throws Exception {
subject.register(literal("actual").executes(command));
subject.register(literal("redirected").redirect(subject.getRoot()));
final ParseResults<Object> parse = subject.parse("redirected redirected actual", source);
assertThat(parse.getContext().getInput(), equalTo("actual"));
assertThat(parse.getContext().getNodes().size(), is(1));
final CommandContext<Object> parent1 = parse.getContext().getParent();
assertThat(parent1, is(notNullValue()));
assertThat(parent1.getInput(), equalTo("redirected"));
assertThat(parent1.getNodes().size(), is(1));
final CommandContext<Object> parent2 = parent1.getParent();
assertThat(parent2, is(notNullValue()));
assertThat(parent2.getInput(), equalTo("redirected"));
assertThat(parent2.getNodes().size(), is(1));
assertThat(subject.execute(parse), is(42));
verify(command).run(any(CommandContext.class));
}
@Test @Test
public void testExecuteOrphanedSubcommand() throws Exception { public void testExecuteOrphanedSubcommand() throws Exception {
subject.register(literal("foo").then( subject.register(literal("foo").then(
@@ -87,6 +87,10 @@ public class CommandDispatcherUsagesTest {
.then(literal("1").executes(command)) .then(literal("1").executes(command))
.then(literal("2").executes(command)) .then(literal("2").executes(command))
); );
subject.register(
literal("j")
.redirect(subject.getRoot())
);
} }
private CommandNode<Object> get(final String command) { private CommandNode<Object> get(final String command) {
@@ -136,6 +140,7 @@ public class CommandDispatcherUsagesTest {
"i", "i",
"i 1", "i 1",
"i 2", "i 2",
"j ...",
})); }));
} }
@@ -151,6 +156,7 @@ public class CommandDispatcherUsagesTest {
.put(get("g"), "g [1]") .put(get("g"), "g [1]")
.put(get("h"), "h [1|2|3]") .put(get("h"), "h [1|2|3]")
.put(get("i"), "i [1|2]") .put(get("i"), "i [1|2]")
.put(get("j"), "j ...")
.build() .build()
)); ));
} }
@@ -3,12 +3,16 @@ package com.mojang.brigadier.builder;
import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.CommandNode;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito;
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer; import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
import static com.mojang.brigadier.builder.LiteralArgumentBuilder.literal;
import static com.mojang.brigadier.builder.RequiredArgumentBuilder.argument; import static com.mojang.brigadier.builder.RequiredArgumentBuilder.argument;
import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.core.IsCollectionContaining.hasItem; import static org.hamcrest.core.IsCollectionContaining.hasItem;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
public class ArgumentBuilderTest { public class ArgumentBuilderTest {
private TestableArgumentBuilder<Object> builder; private TestableArgumentBuilder<Object> builder;
@@ -28,6 +32,27 @@ public class ArgumentBuilderTest {
assertThat(builder.getArguments(), hasItem((CommandNode<Object>) argument.build())); assertThat(builder.getArguments(), hasItem((CommandNode<Object>) argument.build()));
} }
@Test
public void testRedirect() throws Exception {
final CommandNode<Object> target = mock(CommandNode.class);
builder.redirect(target);
assertThat(builder.getRedirect(), is(target));
}
@Test(expected = IllegalStateException.class)
public void testRedirect_withChild() throws Exception {
final CommandNode<Object> target = mock(CommandNode.class);
builder.then(literal("foo"));
builder.redirect(target);
}
@Test(expected = IllegalStateException.class)
public void testThen_withRedirect() throws Exception {
final CommandNode<Object> target = mock(CommandNode.class);
builder.redirect(target);
builder.then(literal("foo"));
}
private static class TestableArgumentBuilder<S> extends ArgumentBuilder<S, TestableArgumentBuilder<S>> { private static class TestableArgumentBuilder<S> extends ArgumentBuilder<S, TestableArgumentBuilder<S>> {
@Override @Override
protected TestableArgumentBuilder<S> getThis() { protected TestableArgumentBuilder<S> getThis() {