diff --git a/cloud-core/src/main/java/cloud/commandframework/CommandTree.java b/cloud-core/src/main/java/cloud/commandframework/CommandTree.java index 4449c85af..079b03491 100644 --- a/cloud-core/src/main/java/cloud/commandframework/CommandTree.java +++ b/cloud-core/src/main/java/cloud/commandframework/CommandTree.java @@ -597,7 +597,7 @@ private CommandTree(final @NonNull CommandManager commandManager) { commandContext.store(PARSING_ARGUMENT_KEY, i + 2); } } - } else if (child.getValue().getParser() instanceof FlagArgument.FlagArgumentParser) { + } else if (child.getValue().getParser() instanceof FlagArgument.FlagArgumentParser && child.isLeaf()) { /* * Use the flag argument parser to deduce what flag is being suggested right now @@ -628,8 +628,7 @@ private CommandTree(final @NonNull CommandManager commandManager) { if (commandQueue.isEmpty()) { return Collections.emptyList(); } else if (child.isLeaf() && commandQueue.size() < 2) { - commandContext.setCurrentArgument(child.getValue()); - return child.getValue().getSuggestionsProvider().apply(commandContext, commandQueue.peek()); + return directSuggestions(commandContext, child, commandQueue.peek()); } else if (child.isLeaf()) { if (child.getValue() instanceof CompoundArgument) { final String last = ((LinkedList) commandQueue).getLast(); @@ -638,8 +637,7 @@ private CommandTree(final @NonNull CommandManager commandManager) { } return Collections.emptyList(); } else if (commandQueue.peek().isEmpty()) { - commandContext.setCurrentArgument(child.getValue()); - return child.getValue().getSuggestionsProvider().apply(commandContext, commandQueue.remove()); + return directSuggestions(commandContext, child, commandQueue.peek()); } // Store original input command queue before the parsers below modify it @@ -670,8 +668,7 @@ private CommandTree(final @NonNull CommandManager commandManager) { commandQueue.addAll(commandQueueOriginal); // Fallback: use suggestion provider of argument - commandContext.setCurrentArgument(child.getValue()); - return child.getValue().getSuggestionsProvider().apply(commandContext, this.stringOrEmpty(commandQueue.peek())); + return directSuggestions(commandContext, child, commandQueue.peek()); } private @NonNull String stringOrEmpty(final @Nullable String string) { @@ -681,17 +678,42 @@ private CommandTree(final @NonNull CommandManager commandManager) { return string; } + private @NonNull List<@NonNull String> directSuggestions( + final @NonNull CommandContext commandContext, + final @NonNull Node<@NonNull CommandArgument> root, + final @NonNull String text) { + commandContext.setCurrentArgument(root.getValue()); + + List suggestions = root.getValue().getSuggestionsProvider().apply(commandContext, text); + if (!(root.getValue() instanceof FlagArgument) || root.getChildren().isEmpty()) return suggestions; + + suggestions = new ArrayList<>(suggestions); + for (final Node> child : root.getChildren()) { + suggestions.addAll(child.getValue().getSuggestionsProvider().apply(commandContext, text)); + } + return suggestions; + } + /** * Insert a new command into the command tree * * @param command Command to insert */ - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "ReferenceEquality"}) public void insertCommand(final @NonNull Command command) { synchronized (this.commandLock) { Node> node = this.internalTree; + + List> arguments = command.getArguments(); + + CommandArgument lastArgument = arguments.get(arguments.size() - 1); + FlagArgument flags = lastArgument instanceof FlagArgument ? (FlagArgument) lastArgument : null; + for (final CommandArgument argument : command.getArguments()) { Node> tempNode = node.getChild(argument); + if (flags != null && node != this.internalTree && argument != lastArgument) + node = node.addChild(flags); + if (tempNode == null) { tempNode = node.addChild(argument); } else if (argument instanceof StaticArgument && tempNode.getValue() != null) { @@ -703,6 +725,7 @@ public void insertCommand(final @NonNull Command command) { node.children.sort(Comparator.comparing(Node::getValue)); } tempNode.setParent(node); + node = tempNode; } if (node.getValue() != null) { diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/compound/FlagArgument.java b/cloud-core/src/main/java/cloud/commandframework/arguments/compound/FlagArgument.java index fe18e821e..cc0b3ba38 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/compound/FlagArgument.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/compound/FlagArgument.java @@ -323,6 +323,10 @@ private class FlagParser { this.currentFlagBeingParsed = Optional.empty(); this.currentFlagNameBeingParsed = Optional.empty(); + if (!string.startsWith("-") && currentFlag == null) { + return ArgumentParseResult.success(FLAG_PARSE_RESULT_OBJECT); + } + /* Parse next flag name to set */ if (string.startsWith("-") && currentFlag == null) { /* Remove flag argument from input queue */ diff --git a/cloud-core/src/test/java/cloud/commandframework/CommandSuggestionsTest.java b/cloud-core/src/test/java/cloud/commandframework/CommandSuggestionsTest.java index 42b9bdf1b..dc8e1e94c 100644 --- a/cloud-core/src/test/java/cloud/commandframework/CommandSuggestionsTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/CommandSuggestionsTest.java @@ -441,7 +441,7 @@ void testFlagYieldingGreedyStringFollowedByFlagArgument() { ); // Assert - assertThat(suggestions1).containsExactly("hello"); + assertThat(suggestions1).containsExactly("hello", "--flag", "--flag2", "-f"); assertThat(suggestions2).containsExactly("hello"); assertThat(suggestions3).containsExactly("--flag", "--flag2"); assertThat(suggestions4).containsExactly("--flag", "--flag2"); @@ -492,7 +492,7 @@ void testFlagYieldingStringArrayFollowedByFlagArgument() { ); // Assert - assertThat(suggestions1).isEmpty(); + assertThat(suggestions1).containsExactly("--flag", "--flag2", "-f"); assertThat(suggestions2).isEmpty(); assertThat(suggestions3).containsExactly("--flag", "--flag2"); assertThat(suggestions4).containsExactly("--flag", "--flag2"); diff --git a/cloud-core/src/test/java/cloud/commandframework/feature/ArbitraryPositionFlagTest.java b/cloud-core/src/test/java/cloud/commandframework/feature/ArbitraryPositionFlagTest.java new file mode 100644 index 000000000..e366f5b44 --- /dev/null +++ b/cloud-core/src/test/java/cloud/commandframework/feature/ArbitraryPositionFlagTest.java @@ -0,0 +1,74 @@ +// +// MIT License +// +// Copyright (c) 2021 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package cloud.commandframework.feature; + +import cloud.commandframework.CommandManager; +import cloud.commandframework.TestCommandSender; +import cloud.commandframework.arguments.standard.StringArgument; +import cloud.commandframework.execution.CommandResult; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static cloud.commandframework.util.TestUtils.createManager; +import static com.google.common.truth.Truth.assertThat; + +class ArbitraryPositionFlagTest { + + private CommandManager commandManager; + + @BeforeEach + void setup() { + this.commandManager = createManager(); + } + + @Test + void testParsingAllLocations() { + // Arrange + this.commandManager.command( + this.commandManager.commandBuilder("test") + .literal("param") + .argument(StringArgument.greedyFlagYielding("text")) + .flag(this.commandManager.flagBuilder("flag").withAliases("f"))); + + // Act + final CommandResult result1 = this.commandManager.executeCommand( + new TestCommandSender(), + "test -f param foo bar" + ).join(); + final CommandResult result2 = this.commandManager.executeCommand( + new TestCommandSender(), + "test param -f foo bar" + ).join(); + final CommandResult result3 = this.commandManager.executeCommand( + new TestCommandSender(), + "test param foo bar -f" + ).join(); + + // Assert + assertThat(result1.getCommandContext().flags().isPresent("flag")).isEqualTo(true); + assertThat(result2.getCommandContext().flags().isPresent("flag")).isEqualTo(true); + assertThat(result3.getCommandContext().flags().isPresent("flag")).isEqualTo(true); + } + +}