diff --git a/src/main/java/com/mojang/brigadier/CommandDispatcher.java b/src/main/java/com/mojang/brigadier/CommandDispatcher.java index aab12a5..7251f03 100644 --- a/src/main/java/com/mojang/brigadier/CommandDispatcher.java +++ b/src/main/java/com/mojang/brigadier/CommandDispatcher.java @@ -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 { }; public void register(LiteralArgumentBuilder command) { - root.addChild(command.build()); + final LiteralCommandNode build = command.build(); + root.addChild(build); } public int execute(String input, S source) throws CommandException { @@ -97,48 +102,88 @@ public class CommandDispatcher { return new ParseResults<>(contextBuilder, command, errors); } - public String getUsage(String command, S source) throws CommandException { - final ParseResults parse = parseNodes(root, command, new CommandContextBuilder<>(source)); - if (parse.getContext().getNodes().isEmpty()) { - throw ERROR_UNKNOWN_COMMAND.create(); + public String[] getAllUsage(CommandNode node, S source) { + final ArrayList result = Lists.newArrayList(); + getAllUsage(node, source, result, ""); + return result.toArray(new String[result.size()]); + } + + private void getAllUsage(CommandNode node, S source, ArrayList result, String prefix) { + if (!node.canUse(source)) { + return; } - CommandContext context = parse.getContext().build(); - CommandNode base = Iterables.getLast(context.getNodes().keySet()); - List> 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 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, String> getSmartUsage(CommandNode node, S source) { + Map, String> result = Maps.newLinkedHashMap(); + + final boolean optional = node.getCommand() != null; + for (CommandNode child : node.getChildren()) { + String usage = getSmartUsage(child, source, optional, false); + if (usage != null) { + result.put(child, usage); + } + } + return result; + } + + private String getSmartUsage(CommandNode 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> 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 childUsage = Sets.newLinkedHashSet(); + for (final CommandNode 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 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 findSuggestions(CommandNode node, String command, CommandContextBuilder contextBuilder, Set result) { diff --git a/src/test/java/com/mojang/brigadier/CommandDispatcherUsagesTest.java b/src/test/java/com/mojang/brigadier/CommandDispatcherUsagesTest.java index a95f757..df47a32 100644 --- a/src/test/java/com/mojang/brigadier/CommandDispatcherUsagesTest.java +++ b/src/test/java/com/mojang/brigadier/CommandDispatcherUsagesTest.java @@ -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 command; + private Command 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 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.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.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, 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.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, 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 []")); - } - - @Test - public void testRequiredOneArgument() throws Exception { - subject.register( - literal("base").then( - argument("foo", integer()).executes(command) - ) - ); - - assertThat(subject.getUsage("base", source), hasToString("base ")); - } - - @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 [|]")); - } - - @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 (|)")); - } - - @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|]")); - } - - @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|)")); + public void testSmartUsage_h() throws Exception { + final Map, 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() + )); } } \ No newline at end of file