Overhauled usage generation
This commit is contained in:
parent
4337a9645e
commit
9a3f987c97
2 changed files with 201 additions and 233 deletions
|
@ -2,6 +2,7 @@ package com.mojang.brigadier;
|
|||
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
|
@ -14,6 +15,9 @@ import com.mojang.brigadier.tree.CommandNode;
|
|||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import com.mojang.brigadier.tree.RootCommandNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -41,7 +45,8 @@ public class CommandDispatcher<S> {
|
|||
};
|
||||
|
||||
public void register(LiteralArgumentBuilder<S> command) {
|
||||
root.addChild(command.build());
|
||||
final LiteralCommandNode<S> build = command.build();
|
||||
root.addChild(build);
|
||||
}
|
||||
|
||||
public int execute(String input, S source) throws CommandException {
|
||||
|
@ -97,48 +102,88 @@ public class CommandDispatcher<S> {
|
|||
return new ParseResults<>(contextBuilder, command, errors);
|
||||
}
|
||||
|
||||
public String getUsage(String command, S source) throws CommandException {
|
||||
final ParseResults<S> parse = parseNodes(root, command, new CommandContextBuilder<>(source));
|
||||
if (parse.getContext().getNodes().isEmpty()) {
|
||||
throw ERROR_UNKNOWN_COMMAND.create();
|
||||
public String[] getAllUsage(CommandNode<S> node, S source) {
|
||||
final ArrayList<String> result = Lists.newArrayList();
|
||||
getAllUsage(node, source, result, "");
|
||||
return result.toArray(new String[result.size()]);
|
||||
}
|
||||
|
||||
private void getAllUsage(CommandNode<S> node, S source, ArrayList<String> result, String prefix) {
|
||||
if (!node.canUse(source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CommandContext<S> context = parse.getContext().build();
|
||||
CommandNode<S> base = Iterables.getLast(context.getNodes().keySet());
|
||||
List<CommandNode<S>> children = base.getChildren().stream().filter(hasCommand).collect(Collectors.toList());
|
||||
boolean optional = base.getCommand() != null;
|
||||
|
||||
if (children.isEmpty()) {
|
||||
return context.getInput();
|
||||
if (node.getCommand() != null) {
|
||||
result.add(prefix);
|
||||
}
|
||||
|
||||
children.sort((o1, o2) -> ComparisonChain.start()
|
||||
.compareTrueFirst(o1 instanceof LiteralCommandNode, o2 instanceof LiteralCommandNode)
|
||||
.result());
|
||||
if (!node.getChildren().isEmpty()) {
|
||||
for (final CommandNode<S> child : node.getChildren()) {
|
||||
getAllUsage(child, source, result, prefix.isEmpty() ? child.getUsageText() : prefix + ARGUMENT_SEPARATOR + child.getUsageText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder(context.getInput());
|
||||
result.append(ARGUMENT_SEPARATOR);
|
||||
if (optional) {
|
||||
result.append(USAGE_OPTIONAL_OPEN);
|
||||
} else if (children.size() > 1) {
|
||||
result.append(USAGE_REQUIRED_OPEN);
|
||||
public Map<CommandNode<S>, String> getSmartUsage(CommandNode<S> node, S source) {
|
||||
Map<CommandNode<S>, String> result = Maps.newLinkedHashMap();
|
||||
|
||||
final boolean optional = node.getCommand() != null;
|
||||
for (CommandNode<S> child : node.getChildren()) {
|
||||
String usage = getSmartUsage(child, source, optional, false);
|
||||
if (usage != null) {
|
||||
result.put(child, usage);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getSmartUsage(CommandNode<S> node, S source, boolean optional, boolean deep) {
|
||||
if (!node.canUse(source)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
result.append(children.get(i).getUsageText());
|
||||
String self = optional ? USAGE_OPTIONAL_OPEN + node.getUsageText() + USAGE_OPTIONAL_CLOSE : node.getUsageText();
|
||||
boolean childOptional = node.getCommand() != null;
|
||||
String open = childOptional ? USAGE_OPTIONAL_OPEN : USAGE_REQUIRED_OPEN;
|
||||
String close = childOptional ? USAGE_OPTIONAL_CLOSE : USAGE_REQUIRED_CLOSE;
|
||||
|
||||
if (i < children.size() - 1) {
|
||||
result.append(USAGE_OR);
|
||||
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) {
|
||||
Set<String> childUsage = Sets.newLinkedHashSet();
|
||||
for (final CommandNode<S> child : children) {
|
||||
final String usage = getSmartUsage(child, source, childOptional, true);
|
||||
if (usage != null) {
|
||||
childUsage.add(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) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (optional) {
|
||||
result.append(USAGE_OPTIONAL_CLOSE);
|
||||
} else if (children.size() > 1) {
|
||||
result.append(USAGE_REQUIRED_CLOSE);
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
return self;
|
||||
}
|
||||
|
||||
private Set<String> findSuggestions(CommandNode<S> node, String command, CommandContextBuilder<S> contextBuilder, Set<String> result) {
|
||||
|
|
|
@ -1,18 +1,23 @@
|
|||
package com.mojang.brigadier;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.mojang.brigadier.exceptions.CommandException;
|
||||
import com.mojang.brigadier.exceptions.CommandExceptionType;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
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 java.util.Map;
|
||||
|
||||
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.empty;
|
||||
import static org.hamcrest.Matchers.emptyArray;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasToString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
@ -24,226 +29,144 @@ public class CommandDispatcherUsagesTest {
|
|||
@Mock
|
||||
private Object source;
|
||||
@Mock
|
||||
private Command<Object> command;
|
||||
private Command<Object> command;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
subject = new CommandDispatcher<>();
|
||||
subject.register(
|
||||
literal("a")
|
||||
.then(
|
||||
literal("1")
|
||||
.then(literal("i").executes(command))
|
||||
.then(literal("ii").executes(command))
|
||||
)
|
||||
.then(
|
||||
literal("2")
|
||||
.then(literal("i").executes(command))
|
||||
.then(literal("ii").executes(command))
|
||||
)
|
||||
);
|
||||
subject.register(literal("b").then(literal("1").executes(command)));
|
||||
subject.register(literal("c").executes(command));
|
||||
subject.register(literal("d").requires(s -> false).executes(command));
|
||||
subject.register(
|
||||
literal("e")
|
||||
.executes(command)
|
||||
.then(
|
||||
literal("1")
|
||||
.executes(command)
|
||||
.then(literal("i").executes(command))
|
||||
.then(literal("ii").executes(command))
|
||||
)
|
||||
);
|
||||
subject.register(
|
||||
literal("f")
|
||||
.then(
|
||||
literal("1")
|
||||
.then(literal("i").executes(command))
|
||||
.then(literal("ii").executes(command).requires(s -> false))
|
||||
)
|
||||
.then(
|
||||
literal("2")
|
||||
.then(literal("i").executes(command).requires(s -> false))
|
||||
.then(literal("ii").executes(command))
|
||||
)
|
||||
);
|
||||
subject.register(
|
||||
literal("g")
|
||||
.executes(command)
|
||||
.then(literal("1").then(literal("i").executes(command)))
|
||||
);
|
||||
subject.register(
|
||||
literal("h")
|
||||
.executes(command)
|
||||
.then(literal("1").then(literal("i").executes(command)))
|
||||
.then(literal("2").then(literal("i").then(literal("ii").executes(command))))
|
||||
.then(literal("3").executes(command))
|
||||
);
|
||||
subject.register(
|
||||
literal("i")
|
||||
.executes(command)
|
||||
.then(literal("1").executes(command))
|
||||
.then(literal("2").executes(command))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnknownCommand() throws Exception {
|
||||
private CommandNode<Object> get(String command) {
|
||||
try {
|
||||
subject.getUsage("foo", source);
|
||||
fail();
|
||||
} catch (CommandException ex) {
|
||||
assertThat(ex.getType(), is(CommandDispatcher.ERROR_UNKNOWN_COMMAND));
|
||||
assertThat(ex.getData(), is(Collections.<String, Object>emptyMap()));
|
||||
return Iterators.getLast(subject.parse(command, source).getContext().getNodes().keySet().iterator());
|
||||
} catch (CommandException e) {
|
||||
throw new AssertionError("get() failed unexpectedly", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoCommand() throws Exception {
|
||||
try {
|
||||
subject.getUsage("", source);
|
||||
fail();
|
||||
} catch (CommandException ex) {
|
||||
assertThat(ex.getType(), is(CommandDispatcher.ERROR_UNKNOWN_COMMAND));
|
||||
assertThat(ex.getData(), is(Collections.<String, Object>emptyMap()));
|
||||
}
|
||||
public void testAllUsage_noCommands() throws Exception {
|
||||
subject = new CommandDispatcher<>();
|
||||
final String[] results = subject.getAllUsage(subject.getRoot(), source);
|
||||
assertThat(results, is(emptyArray()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnknownSubcommand() 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 unknown", source), hasToString("base [foo|bar]"));
|
||||
public void testSmartUsage_noCommands() throws Exception {
|
||||
subject = new CommandDispatcher<>();
|
||||
final Map<CommandNode<Object>, String> results = subject.getSmartUsage(subject.getRoot(), source);
|
||||
assertThat(results.entrySet(), is(empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInaccessibleCommand() throws Exception {
|
||||
subject.register(literal("foo").requires(s -> false));
|
||||
|
||||
try {
|
||||
subject.getUsage("foo", source);
|
||||
fail();
|
||||
} catch (CommandException ex) {
|
||||
assertThat(ex.getType(), is(CommandDispatcher.ERROR_UNKNOWN_COMMAND));
|
||||
assertThat(ex.getData(), is(Collections.<String, Object>emptyMap()));
|
||||
}
|
||||
public void testAllUsage_root() throws Exception {
|
||||
final String[] results = subject.getAllUsage(subject.getRoot(), source);
|
||||
assertThat(results, equalTo(new String[] {
|
||||
"a 1 i",
|
||||
"a 1 ii",
|
||||
"a 2 i",
|
||||
"a 2 ii",
|
||||
"b 1",
|
||||
"c",
|
||||
"e",
|
||||
"e 1",
|
||||
"e 1 i",
|
||||
"e 1 ii",
|
||||
"f 1 i",
|
||||
"f 2 ii",
|
||||
"g",
|
||||
"g 1 i",
|
||||
"h",
|
||||
"h 1 i",
|
||||
"h 2 i ii",
|
||||
"h 3",
|
||||
"i",
|
||||
"i 1",
|
||||
"i 2",
|
||||
}));
|
||||
}
|
||||
|
||||
@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]"));
|
||||
public void testSmartUsage_root() throws Exception {
|
||||
final Map<CommandNode<Object>, String> results = subject.getSmartUsage(subject.getRoot(), source);
|
||||
assertThat(results, equalTo(ImmutableMap.builder()
|
||||
.put(get("a"), "a (1|2)")
|
||||
.put(get("b"), "b 1")
|
||||
.put(get("c"), "c")
|
||||
.put(get("e"), "e [1]")
|
||||
.put(get("f"), "f (1|2)")
|
||||
.put(get("g"), "g [1]")
|
||||
.put(get("h"), "h [1|2|3]")
|
||||
.put(get("i"), "i [1|2]")
|
||||
.build()
|
||||
));
|
||||
}
|
||||
|
||||
@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>)"));
|
||||
public void testSmartUsage_h() throws Exception {
|
||||
final Map<CommandNode<Object>, String> results = subject.getSmartUsage(get("h"), source);
|
||||
assertThat(results, equalTo(ImmutableMap.builder()
|
||||
.put(get("h 1"), "[1] i")
|
||||
.put(get("h 2"), "[2] i ii")
|
||||
.put(get("h 3"), "[3]")
|
||||
.build()
|
||||
));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue