Added a redirect option on nodes

This commit is contained in:
Nathan Adams 2017-07-31 14:14:24 +02:00
parent 7f490f0a8b
commit 4b9170fd7f
14 changed files with 162 additions and 41 deletions

View file

@ -7,7 +7,6 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.exceptions.CommandException;
import com.mojang.brigadier.exceptions.ParameterizedCommandExceptionType;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
@ -98,7 +97,11 @@ public class CommandDispatcher<S> {
throw ERROR_EXPECTED_ARGUMENT_SEPARATOR.createWithContext(reader);
}
reader.skip();
return parseNodes(child, reader, context);
if (child.getRedirect() != null) {
return parseNodes(child.getRedirect(), reader, context.redirect());
} else {
return parseNodes(child, reader, context);
}
} else {
return new ParseResults<>(context);
}
@ -122,7 +125,9 @@ public class CommandDispatcher<S> {
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()) {
getAllUsage(child, source, result, prefix.isEmpty() ? child.getUsageText() : prefix + ARGUMENT_SEPARATOR + child.getUsageText());
}
@ -153,36 +158,40 @@ public class CommandDispatcher<S> {
final String close = childOptional ? USAGE_OPTIONAL_CLOSE : USAGE_REQUIRED_CLOSE;
if (!deep) {
final Collection<CommandNode<S>> children = node.getChildren().stream().filter(c -> c.canUse(source)).collect(Collectors.toList());
if (children.size() == 1) {
final String usage = getSmartUsage(children.iterator().next(), source, childOptional, childOptional);
if (usage != null) {
return self + ARGUMENT_SEPARATOR + usage;
}
} else if (children.size() > 1) {
final Set<String> childUsage = Sets.newLinkedHashSet();
for (final CommandNode<S> child : children) {
final String usage = getSmartUsage(child, source, childOptional, true);
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());
if (children.size() == 1) {
final String usage = getSmartUsage(children.iterator().next(), source, childOptional, childOptional);
if (usage != null) {
childUsage.add(usage);
return self + ARGUMENT_SEPARATOR + usage;
}
}
if (childUsage.size() == 1) {
final String usage = childUsage.iterator().next();
return self + ARGUMENT_SEPARATOR + (childOptional ? USAGE_OPTIONAL_OPEN + usage + USAGE_OPTIONAL_CLOSE : usage);
} else if (childUsage.size() > 1) {
final StringBuilder builder = new StringBuilder(open);
int count = 0;
} else if (children.size() > 1) {
final Set<String> childUsage = Sets.newLinkedHashSet();
for (final CommandNode<S> child : children) {
if (count > 0) {
builder.append(USAGE_OR);
final String usage = getSmartUsage(child, source, childOptional, true);
if (usage != null) {
childUsage.add(usage);
}
builder.append(child.getUsageText());
count++;
}
if (count > 0) {
builder.append(close);
return self + ARGUMENT_SEPARATOR + builder.toString();
if (childUsage.size() == 1) {
final String usage = childUsage.iterator().next();
return self + ARGUMENT_SEPARATOR + (childOptional ? USAGE_OPTIONAL_OPEN + usage + USAGE_OPTIONAL_CLOSE : usage);
} else if (childUsage.size() > 1) {
final StringBuilder builder = new StringBuilder(open);
int count = 0;
for (final CommandNode<S> child : children) {
if (count > 0) {
builder.append(USAGE_OR);
}
builder.append(child.getUsageText());
count++;
}
if (count > 0) {
builder.append(close);
return self + ARGUMENT_SEPARATOR + builder.toString();
}
}
}
}
@ -192,6 +201,9 @@ public class CommandDispatcher<S> {
}
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();
for (final CommandNode<S> child : node.getChildren()) {
if (!child.canUse(source)) {

View file

@ -11,10 +11,14 @@ public abstract class ArgumentBuilder<S, T extends ArgumentBuilder<S, T>> {
private final RootCommandNode<S> arguments = new RootCommandNode<>();
private Command<S> command;
private Predicate<S> requirement = s -> true;
private CommandNode<S> target;
protected abstract T getThis();
public T then(final ArgumentBuilder<S, ?> argument) {
if (target != null) {
throw new IllegalStateException("Cannot add children to a redirected node");
}
arguments.addChild(argument.build());
return getThis();
}
@ -41,5 +45,17 @@ public abstract class ArgumentBuilder<S, T extends ArgumentBuilder<S, T>> {
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();
}

View file

@ -25,7 +25,7 @@ public class LiteralArgumentBuilder<S> extends ArgumentBuilder<S, LiteralArgumen
@Override
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()) {
result.addChild(argument);

View file

@ -31,7 +31,7 @@ public class RequiredArgumentBuilder<S, T> extends ArgumentBuilder<S, RequiredAr
}
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()) {
result.addChild(argument);

View file

@ -1,7 +1,6 @@
package com.mojang.brigadier.context;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.primitives.Primitives;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.tree.CommandNode;
@ -14,13 +13,19 @@ public class CommandContext<S> {
private final Map<String, ParsedArgument<S, ?>> arguments;
private final Map<CommandNode<S>, String> nodes;
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.arguments = arguments;
this.command = command;
this.nodes = nodes;
this.input = input;
this.parent = parent;
}
public CommandContext<S> getParent() {
return parent;
}
public Command<S> getCommand() {
@ -58,6 +63,7 @@ public class CommandContext<S> {
if (!Iterables.elementsEqual(nodes.entrySet(), that.nodes.entrySet())) return false;
if (command != null ? !command.equals(that.command) : that.command != null) return false;
if (!source.equals(that.source)) return false;
if (parent != null ? !parent.equals(that.parent) : that.parent != null) return false;
return true;
}
@ -68,6 +74,7 @@ public class CommandContext<S> {
result = 31 * result + arguments.hashCode();
result = 31 * result + (command != null ? command.hashCode() : 0);
result = 31 * result + nodes.hashCode();
result = 31 * result + (parent != null ? parent.hashCode() : 0);
return result;
}

View file

@ -14,6 +14,7 @@ public class CommandContextBuilder<S> {
private final StringBuilder input = new StringBuilder();
private S source;
private Command<S> command;
private CommandContext<S> parent;
public CommandContextBuilder(final CommandDispatcher<S> dispatcher, final S source) {
this.dispatcher = dispatcher;
@ -54,13 +55,24 @@ public class CommandContextBuilder<S> {
public CommandContextBuilder<S> copy() {
final CommandContextBuilder<S> copy = new CommandContextBuilder<>(dispatcher, source);
copy.command = this.command;
copy.command = command;
copy.arguments.putAll(arguments);
copy.nodes.putAll(this.nodes);
copy.nodes.putAll(nodes);
copy.input.append(input);
copy.parent = parent;
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() {
return input.toString();
}
@ -70,7 +82,7 @@ public class CommandContextBuilder<S> {
}
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() {

View file

@ -18,8 +18,8 @@ public class ArgumentCommandNode<S, T> extends CommandNode<S> {
private final String name;
private final ArgumentType<T> type;
public ArgumentCommandNode(final String name, final ArgumentType<T> type, final Command<S> command, final Predicate<S> requirement) {
super(command, 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, redirect);
this.name = name;
this.type = type;
}
@ -69,6 +69,7 @@ public class ArgumentCommandNode<S, T> extends CommandNode<S> {
public RequiredArgumentBuilder<S, T> createBuilder() {
final RequiredArgumentBuilder<S, T> builder = RequiredArgumentBuilder.argument(name, type);
builder.requires(getRequirement());
builder.redirect(getRedirect());
if (getCommand() != null) {
builder.executes(getCommand());
}

View file

@ -18,11 +18,13 @@ import java.util.stream.Collectors;
public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
private Map<Object, CommandNode<S>> children = Maps.newLinkedHashMap();
private final Predicate<S> requirement;
private final CommandNode<S> redirect;
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.requirement = requirement;
this.redirect = redirect;
}
public Command<S> getCommand() {
@ -33,6 +35,10 @@ public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
return children.values();
}
public CommandNode<S> getRedirect() {
return redirect;
}
public boolean canUse(final S source) {
return requirement.test(source);
}

View file

@ -15,8 +15,8 @@ public class LiteralCommandNode<S> extends CommandNode<S> {
private final String literal;
public LiteralCommandNode(final String literal, final Command<S> command, final Predicate<S> requirement) {
super(command, requirement);
public LiteralCommandNode(final String literal, final Command<S> command, final Predicate<S> requirement, final CommandNode<S> redirect) {
super(command, requirement, redirect);
this.literal = literal;
}
@ -78,6 +78,7 @@ public class LiteralCommandNode<S> extends CommandNode<S> {
public LiteralArgumentBuilder<S> createBuilder() {
final LiteralArgumentBuilder<S> builder = LiteralArgumentBuilder.literal(this.literal);
builder.requires(getRequirement());
builder.redirect(getRedirect());
if (getCommand() != null) {
builder.executes(getCommand());
}

View file

@ -9,7 +9,7 @@ import java.util.Set;
public class RootCommandNode<S> extends CommandNode<S> {
public RootCommandNode() {
super(null, c -> true);
super(null, c -> true, null);
}
@Override

View file

@ -39,6 +39,14 @@ public class CommandDispatcherCompletionsTest {
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
public void testSubCommand() throws Exception {
subject.register(literal("foo").then(literal("abc")).then(literal("def")).then(literal("ghi").requires(s -> false)));

View file

@ -14,9 +14,12 @@ import java.util.Collections;
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 org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.notNull;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@ -164,6 +167,30 @@ public class CommandDispatcherTest {
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
public void testExecuteOrphanedSubcommand() throws Exception {
subject.register(literal("foo").then(

View file

@ -87,6 +87,10 @@ public class CommandDispatcherUsagesTest {
.then(literal("1").executes(command))
.then(literal("2").executes(command))
);
subject.register(
literal("j")
.redirect(subject.getRoot())
);
}
private CommandNode<Object> get(final String command) {
@ -136,6 +140,7 @@ public class CommandDispatcherUsagesTest {
"i",
"i 1",
"i 2",
"j ...",
}));
}
@ -151,6 +156,7 @@ public class CommandDispatcherUsagesTest {
.put(get("g"), "g [1]")
.put(get("h"), "h [1|2|3]")
.put(get("i"), "i [1|2]")
.put(get("j"), "j ...")
.build()
));
}

View file

@ -3,12 +3,16 @@ package com.mojang.brigadier.builder;
import com.mojang.brigadier.tree.CommandNode;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
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 org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.core.IsCollectionContaining.hasItem;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
public class ArgumentBuilderTest {
private TestableArgumentBuilder<Object> builder;
@ -28,6 +32,27 @@ public class ArgumentBuilderTest {
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>> {
@Override
protected TestableArgumentBuilder<S> getThis() {