Added simple usage generation

This commit is contained in:
Nathan Adams 2014-10-09 12:15:54 +02:00
parent b3703db535
commit 91559191b6
12 changed files with 340 additions and 5 deletions

View file

@ -1,14 +1,31 @@
package com.mojang.brigadier;
import com.google.common.base.Predicate;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
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.SimpleCommandExceptionType;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.mojang.brigadier.tree.RootCommandNode;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class CommandDispatcher<T> {
private static final Predicate<CommandNode> HAS_COMMAND = new Predicate<CommandNode>() {
@Override
public boolean apply(@Nullable CommandNode input) {
return input != null && (input.getCommand() != null || Iterables.any(input.getChildren(), HAS_COMMAND));
}
};
public static final SimpleCommandExceptionType ERROR_UNKNOWN_COMMAND = new SimpleCommandExceptionType("unknown_command", "Unknown command");
public static final String ARGUMENT_SEPARATOR = " ";
@ -39,13 +56,57 @@ public class CommandDispatcher<T> {
}
}
if (exception != null) {
throw exception;
}
if (command.length() > 0) {
if (exception != null) {
throw exception;
}
throw ERROR_UNKNOWN_COMMAND.create();
}
return contextBuilder.build();
}
public String getUsage(String command, T source) throws CommandException {
CommandContext<T> context = parseNodes(root, command, new CommandContextBuilder<T>(source));
CommandNode base = Iterables.getLast(context.getNodes().keySet());
List<CommandNode> children = Lists.newArrayList(Iterables.filter(base.getChildren(), HAS_COMMAND));
boolean optional = base.getCommand() != null;
if (children.isEmpty()) {
return context.getInput();
}
Collections.sort(children, new Comparator<CommandNode>() {
@Override
public int compare(CommandNode o1, CommandNode o2) {
return ComparisonChain.start()
.compareTrueFirst(o1 instanceof LiteralCommandNode, o2 instanceof LiteralCommandNode)
.result();
}
});
StringBuilder result = new StringBuilder(context.getInput());
result.append(ARGUMENT_SEPARATOR);
if (optional) {
result.append("[");
} else if (children.size() > 1) {
result.append("(");
}
for (int i = 0; i < children.size(); i++) {
result.append(children.get(i).getUsageText());
if (i < children.size() - 1) {
result.append("|");
}
}
if (optional) {
result.append("]");
} else if (children.size() > 1) {
result.append(")");
}
return result.toString();
}
}

View file

@ -1,19 +1,27 @@
package com.mojang.brigadier.context;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.common.primitives.Primitives;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.tree.CommandNode;
import java.util.Map;
public class CommandContext<T> {
private final Joiner JOINER = Joiner.on(CommandDispatcher.ARGUMENT_SEPARATOR);
private final T source;
private final Map<String, ParsedArgument<?>> arguments;
private final Command command;
private final Map<CommandNode, String> nodes;
public CommandContext(T source, Map<String, ParsedArgument<?>> arguments, Command command) {
public CommandContext(T source, Map<String, ParsedArgument<?>> arguments, Command command, Map<CommandNode, String> nodes) {
this.source = source;
this.arguments = arguments;
this.command = command;
this.nodes = nodes;
}
public Command getCommand() {
@ -47,6 +55,7 @@ public class CommandContext<T> {
CommandContext that = (CommandContext) o;
if (!arguments.equals(that.arguments)) 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 (!source.equals(that.source)) return false;
@ -58,6 +67,15 @@ public class CommandContext<T> {
int result = source.hashCode();
result = 31 * result + arguments.hashCode();
result = 31 * result + (command != null ? command.hashCode() : 0);
result = 31 * result + nodes.hashCode();
return result;
}
public String getInput() {
return JOINER.join(nodes.values());
}
public Map<CommandNode, String> getNodes() {
return nodes;
}
}

View file

@ -2,11 +2,13 @@ package com.mojang.brigadier.context;
import com.google.common.collect.Maps;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.tree.CommandNode;
import java.util.Map;
public class CommandContextBuilder<T> {
private final Map<String, ParsedArgument<?>> arguments = Maps.newHashMap();
private final Map<CommandNode, String> nodes = Maps.newLinkedHashMap();
private final T source;
private Command command;
@ -28,14 +30,20 @@ public class CommandContextBuilder<T> {
return this;
}
public CommandContextBuilder<T> withNode(CommandNode node, String raw) {
this.nodes.put(node, raw);
return this;
}
public CommandContextBuilder<T> copy() {
CommandContextBuilder<T> copy = new CommandContextBuilder<T>(source);
copy.command = this.command;
copy.arguments.putAll(this.arguments);
copy.nodes.putAll(this.nodes);
return copy;
}
public CommandContext<T> build() {
return new CommandContext<T>(source, arguments, command);
return new CommandContext<T>(source, arguments, command, nodes);
}
}

View file

@ -29,12 +29,18 @@ public class ArgumentCommandNode<T> extends CommandNode {
return name;
}
@Override
public String getUsageText() {
return "<" + name + ">";
}
@Override
public String parse(String command, CommandContextBuilder<?> contextBuilder) throws CommandException {
ParsedArgument<T> parsed = type.parse(command);
int start = parsed.getRaw().length();
contextBuilder.withArgument(name, parsed);
contextBuilder.withNode(this, parsed.getRaw());
if (command.length() > start) {
return command.substring(start + 1);

View file

@ -59,5 +59,7 @@ public abstract class CommandNode {
protected abstract Object getMergeKey();
public abstract String getUsageText();
public abstract String parse(String command, CommandContextBuilder<?> contextBuilder) throws CommandException;
}

View file

@ -33,6 +33,7 @@ public class LiteralCommandNode extends CommandNode {
throw ERROR_INCORRECT_LITERAL.create(literal);
}
contextBuilder.withNode(this, literal);
int start = expected.length();
return command.substring(start);
}
@ -48,6 +49,11 @@ public class LiteralCommandNode extends CommandNode {
return super.equals(o);
}
@Override
public String getUsageText() {
return literal;
}
@Override
public int hashCode() {
int result = literal.hashCode();

View file

@ -13,6 +13,11 @@ public class RootCommandNode extends CommandNode {
throw new UnsupportedOperationException("Cannot add a RootCommandNode as a child to any other CommandNode");
}
@Override
public String getUsageText() {
return "";
}
@Override
public String parse(String command, CommandContextBuilder<?> contextBuilder) throws CommandException {
return command;

View file

@ -0,0 +1,200 @@
package com.mojang.brigadier;
import com.mojang.brigadier.exceptions.CommandException;
import com.mojang.brigadier.exceptions.CommandExceptionType;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
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.hasToString;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
@RunWith(MockitoJUnitRunner.class)
public class CommandDispatcherUsagesTest {
CommandDispatcher<Object> subject;
@Mock Object source;
@Mock Command command;
@Before
public void setUp() throws Exception {
subject = new CommandDispatcher<Object>();
}
@Test
public void testUnknownCommand() throws Exception {
try {
subject.getUsage("foo", source);
fail();
} catch (CommandException ex) {
assertThat(ex.getType(), is((CommandExceptionType) CommandDispatcher.ERROR_UNKNOWN_COMMAND));
assertThat(ex.getData(), is(Collections.<String, Object>emptyMap()));
}
}
@Test
public void testSubcommandUsage() throws Exception {
subject.register(
literal("base").then(
literal("foo").executes(command)
).then(
literal("bar").then(
literal("baz").executes(command)
).then(
literal("qux").then(
literal("not_runnable")
)
).then(
literal("quux").then(
literal("corge").executes(command)
)
).executes(command)
).executes(command)
);
assertThat(subject.getUsage("base bar", source), hasToString("base bar [baz|quux]"));
}
@Test
public void testOptionalSingleLiteral() throws Exception {
subject.register(
literal("base").then(
literal("foo").executes(command)
).executes(command)
);
assertThat(subject.getUsage("base", source), hasToString("base [foo]"));
}
@Test
public void testNoArguments() throws Exception {
subject.register(
literal("base").executes(command)
);
assertThat(subject.getUsage("base", source), hasToString("base"));
}
@Test
public void testRequiredSingleLiteral() throws Exception {
subject.register(
literal("base").then(
literal("foo").executes(command)
)
);
assertThat(subject.getUsage("base", source), hasToString("base foo"));
}
@Test
public void testOptionalTwoLiterals() throws Exception {
subject.register(
literal("base").then(
literal("foo").executes(command)
).then(
literal("bar").executes(command)
).executes(command)
);
assertThat(subject.getUsage("base", source), hasToString("base [foo|bar]"));
}
@Test
public void testRequiredTwoLiterals() throws Exception {
subject.register(
literal("base").then(
literal("foo").executes(command)
).then(
literal("bar").executes(command)
)
);
assertThat(subject.getUsage("base", source), hasToString("base (foo|bar)"));
}
@Test
public void testOptionalOneArgument() throws Exception {
subject.register(
literal("base").then(
argument("foo", integer()).executes(command)
).executes(command)
);
assertThat(subject.getUsage("base", source), hasToString("base [<foo>]"));
}
@Test
public void testRequiredOneArgument() throws Exception {
subject.register(
literal("base").then(
argument("foo", integer()).executes(command)
)
);
assertThat(subject.getUsage("base", source), hasToString("base <foo>"));
}
@Test
public void testOptionalTwoArguments() throws Exception {
subject.register(
literal("base").then(
argument("foo", integer()).executes(command)
).then(
argument("bar", integer()).executes(command)
).executes(command)
);
assertThat(subject.getUsage("base", source), hasToString("base [<foo>|<bar>]"));
}
@Test
public void testRequiredTwoArguments() throws Exception {
subject.register(
literal("base").then(
argument("foo", integer()).executes(command)
).then(
argument("bar", integer()).executes(command)
)
);
assertThat(subject.getUsage("base", source), hasToString("base (<foo>|<bar>)"));
}
@Test
public void testOptionalLiteralOrArgument() throws Exception {
subject.register(
literal("base").then(
literal("foo").executes(command)
).then(
argument("bar", integer()).executes(command)
).then(
literal("baz").executes(command)
).executes(command)
);
assertThat(subject.getUsage("base", source), hasToString("base [foo|baz|<bar>]"));
}
@Test
public void testRequiredLiteralOrArgument() throws Exception {
subject.register(
literal("base").then(
literal("foo").executes(command)
).then(
argument("bar", integer()).executes(command)
).then(
literal("baz").executes(command)
)
);
assertThat(subject.getUsage("base", source), hasToString("base (foo|baz|<bar>)"));
}
}

View file

@ -2,6 +2,7 @@ package com.mojang.brigadier.context;
import com.google.common.testing.EqualsTester;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.tree.CommandNode;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -9,6 +10,8 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
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.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
@ -50,12 +53,23 @@ public class CommandContextTest {
Object otherSource = new Object();
Command command = mock(Command.class);
Command otherCommand = mock(Command.class);
CommandNode node = mock(CommandNode.class);
CommandNode otherNode = mock(CommandNode.class);
new EqualsTester()
.addEqualityGroup(new CommandContextBuilder<Object>(source).build(), new CommandContextBuilder<Object>(source).build())
.addEqualityGroup(new CommandContextBuilder<Object>(otherSource).build(), new CommandContextBuilder<Object>(otherSource).build())
.addEqualityGroup(new CommandContextBuilder<Object>(source).withCommand(command).build(), new CommandContextBuilder<Object>(source).withCommand(command).build())
.addEqualityGroup(new CommandContextBuilder<Object>(source).withCommand(otherCommand).build(), new CommandContextBuilder<Object>(source).withCommand(otherCommand).build())
.addEqualityGroup(new CommandContextBuilder<Object>(source).withArgument("foo", integer().parse("123")).build(), new CommandContextBuilder<Object>(source).withArgument("foo", integer().parse("123")).build())
.addEqualityGroup(new CommandContextBuilder<Object>(source).withNode(node, "foo").withNode(otherNode, "bar").build(), new CommandContextBuilder<Object>(source).withNode(node, "foo").withNode(otherNode, "bar").build())
.addEqualityGroup(new CommandContextBuilder<Object>(source).withNode(otherNode, "bar").withNode(node, "foo").build(), new CommandContextBuilder<Object>(source).withNode(otherNode, "bar").withNode(node, "foo").build())
.testEquals();
}
@Test
public void testGetInput() throws Exception {
CommandContext<Object> context = builder.withNode(literal("foo").build(), "foo").withNode(argument("bar", integer()).build(), "100").withNode(literal("baz").build(), "baz").build();
assertThat(context.getInput(), is("foo 100 baz"));
}
}

View file

@ -61,6 +61,11 @@ public class ArgumentCommandNodeTest extends AbstractCommandNodeTest {
}
}
@Test
public void testUsage() throws Exception {
assertThat(node.getUsageText(), is("<foo>"));
}
@Test
public void testEquals() throws Exception {
Command command = mock(Command.class);

View file

@ -64,6 +64,11 @@ public class LiteralCommandNodeTest extends AbstractCommandNodeTest {
}
}
@Test
public void testUsage() throws Exception {
assertThat(node.getUsageText(), is("foo"));
}
@Test
public void testEquals() throws Exception {
Command command = mock(Command.class);

View file

@ -32,6 +32,11 @@ public class RootCommandNodeTest extends AbstractCommandNodeTest {
node.addChild(new RootCommandNode());
}
@Test
public void testUsage() throws Exception {
assertThat(node.getUsageText(), is(""));
}
@Test
public void testEquals() throws Exception {
new EqualsTester()