diff --git a/src/main/java/com/mojang/brigadier/CommandDispatcher.java b/src/main/java/com/mojang/brigadier/CommandDispatcher.java index eebdb1f..bee21f5 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.Maps; import com.google.common.collect.Sets; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; @@ -14,6 +15,7 @@ import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.RootCommandNode; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -21,7 +23,6 @@ import java.util.stream.Collectors; public class CommandDispatcher { public static final SimpleCommandExceptionType ERROR_UNKNOWN_COMMAND = new SimpleCommandExceptionType("command.unknown.command", "Unknown command"); public static final ParameterizedCommandExceptionType ERROR_UNKNOWN_ARGUMENT = new ParameterizedCommandExceptionType("command.unknown.argument", "Incorrect argument for command, couldn't parse: ${argument}", "argument"); - public static final SimpleCommandExceptionType ERROR_IMPERMISSIBLE = new SimpleCommandExceptionType("command.impermissible", "You are not allowed to use this command"); public static final String ARGUMENT_SEPARATOR = " "; private static final String USAGE_OPTIONAL_OPEN = "["; @@ -43,7 +44,17 @@ public class CommandDispatcher { } public int execute(String input, S source) throws CommandException { - CommandContext context = parse(input, source).build(); + final ParseResults parse = parse(input, source); + if (parse.getRemaining().length() > 0) { + if (parse.getExceptions().size() == 1) { + throw parse.getExceptions().values().iterator().next(); + } else if (parse.getContext().getInput().isEmpty()) { + throw ERROR_UNKNOWN_COMMAND.create(); + } else { + throw ERROR_UNKNOWN_ARGUMENT.create(parse.getRemaining()); + } + } + CommandContext context = parse.getContext().build(); Command command = context.getCommand(); if (command == null) { throw ERROR_UNKNOWN_COMMAND.create(); @@ -51,17 +62,16 @@ public class CommandDispatcher { return command.run(context); } - public CommandContextBuilder parse(String command, S source) throws CommandException { + public ParseResults parse(String command, S source) throws CommandException { return parseNodes(root, command, new CommandContextBuilder<>(source)); } - private CommandContextBuilder parseNodes(CommandNode node, String command, CommandContextBuilder contextBuilder) throws CommandException { - CommandException exception = null; + private ParseResults parseNodes(CommandNode node, String command, CommandContextBuilder contextBuilder) throws CommandException { final S source = contextBuilder.getSource(); + Map, CommandException> errors = Maps.newHashMap(); for (CommandNode child : node.getChildren()) { if (!child.canUse(source)) { - exception = ERROR_IMPERMISSIBLE.create(); continue; } CommandContextBuilder context = contextBuilder.copy(); @@ -69,7 +79,7 @@ public class CommandDispatcher { try { remaining = child.parse(command, context); } catch (CommandException ex) { - exception = ex; + errors.put(child, ex); continue; } @@ -77,27 +87,22 @@ public class CommandDispatcher { context.withCommand(child.getCommand()); } if (remaining.isEmpty()) { - return context; + return new ParseResults<>(context); } else { return parseNodes(child, remaining.substring(1), context); } } - if (command.length() > 0) { - if (node == root && (exception == null || exception.getType() != ERROR_IMPERMISSIBLE)) { - throw ERROR_UNKNOWN_COMMAND.create(); - } - if (exception != null && node.getChildren().size() == 1) { - throw exception; - } - throw ERROR_UNKNOWN_ARGUMENT.create(command); - } - - return contextBuilder; + return new ParseResults<>(contextBuilder, command, errors); } public String getUsage(String command, S source) throws CommandException { - CommandContext context = parseNodes(root, command, new CommandContextBuilder<>(source)).build(); + final ParseResults parse = parseNodes(root, command, new CommandContextBuilder<>(source)); + if (parse.getContext().getNodes().isEmpty()) { + throw ERROR_UNKNOWN_COMMAND.create(); + } + + 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; diff --git a/src/main/java/com/mojang/brigadier/ParseResults.java b/src/main/java/com/mojang/brigadier/ParseResults.java new file mode 100644 index 0000000..a5a446e --- /dev/null +++ b/src/main/java/com/mojang/brigadier/ParseResults.java @@ -0,0 +1,36 @@ +package com.mojang.brigadier; + +import com.mojang.brigadier.context.CommandContextBuilder; +import com.mojang.brigadier.exceptions.CommandException; +import com.mojang.brigadier.tree.CommandNode; + +import java.util.Collections; +import java.util.Map; + +public class ParseResults { + private final CommandContextBuilder context; + private final String remaining; + private final Map, CommandException> exceptions; + + public ParseResults(CommandContextBuilder context, String remaining, Map, CommandException> exceptions) { + this.context = context; + this.remaining = remaining; + this.exceptions = exceptions; + } + + public ParseResults(CommandContextBuilder context) { + this(context, "", Collections.emptyMap()); + } + + public CommandContextBuilder getContext() { + return context; + } + + public String getRemaining() { + return remaining; + } + + public Map, CommandException> getExceptions() { + return exceptions; + } +} diff --git a/src/main/java/com/mojang/brigadier/context/CommandContextBuilder.java b/src/main/java/com/mojang/brigadier/context/CommandContextBuilder.java index 1455108..962332a 100644 --- a/src/main/java/com/mojang/brigadier/context/CommandContextBuilder.java +++ b/src/main/java/com/mojang/brigadier/context/CommandContextBuilder.java @@ -63,6 +63,10 @@ public class CommandContextBuilder { return input.toString(); } + public Map, String> getNodes() { + return nodes; + } + public CommandContext build() { return new CommandContext<>(source, arguments, command, nodes, input.toString()); } diff --git a/src/test/java/com/mojang/brigadier/CommandDispatcherTest.java b/src/test/java/com/mojang/brigadier/CommandDispatcherTest.java index 02d8bd0..702e239 100644 --- a/src/test/java/com/mojang/brigadier/CommandDispatcherTest.java +++ b/src/test/java/com/mojang/brigadier/CommandDispatcherTest.java @@ -79,7 +79,7 @@ public class CommandDispatcherTest { subject.execute("foo", source); fail(); } catch (CommandException ex) { - assertThat(ex.getType(), is(CommandDispatcher.ERROR_IMPERMISSIBLE)); + assertThat(ex.getType(), is(CommandDispatcher.ERROR_UNKNOWN_COMMAND)); assertThat(ex.getData(), is(Collections.emptyMap())); } } diff --git a/src/test/java/com/mojang/brigadier/CommandDispatcherUsagesTest.java b/src/test/java/com/mojang/brigadier/CommandDispatcherUsagesTest.java index bbe16d0..a95f757 100644 --- a/src/test/java/com/mojang/brigadier/CommandDispatcherUsagesTest.java +++ b/src/test/java/com/mojang/brigadier/CommandDispatcherUsagesTest.java @@ -42,6 +42,40 @@ public class CommandDispatcherUsagesTest { } } + @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())); + } + } + + @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]")); + } + @Test public void testInaccessibleCommand() throws Exception { subject.register(literal("foo").requires(s -> false)); @@ -50,7 +84,7 @@ public class CommandDispatcherUsagesTest { subject.getUsage("foo", source); fail(); } catch (CommandException ex) { - assertThat(ex.getType(), is(CommandDispatcher.ERROR_IMPERMISSIBLE)); + assertThat(ex.getType(), is(CommandDispatcher.ERROR_UNKNOWN_COMMAND)); assertThat(ex.getData(), is(Collections.emptyMap())); } }