feat(core): flag yielding arguments (#367)

Implements https://github.com/Incendo/cloud/issues/218
Also confirmed to fix https://github.com/Incendo/cloud/issues/321.

Both `StringArgument` and `StringArrayArgument` now have flag-yielding modes. For annotated command method users, this can be activated using ´@FlagYielding`
This commit is contained in:
Alexander Söderberg 2022-06-06 07:49:23 +02:00 committed by Jason
parent e889811380
commit d4ab593460
11 changed files with 558 additions and 15 deletions

View file

@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Core: Add `DurationArgument` for parsing `java.time.Duration` ([#330](https://github.com/Incendo/cloud/pull/330)) - Core: Add `DurationArgument` for parsing `java.time.Duration` ([#330](https://github.com/Incendo/cloud/pull/330))
- Core: Add delegating command execution handlers ([#363](https://github.com/Incendo/cloud/pull/363)) - Core: Add delegating command execution handlers ([#363](https://github.com/Incendo/cloud/pull/363))
- Core: Add `builder()` getter to `Command.Builder` ([#363](https://github.com/Incendo/cloud/pull/363)) - Core: Add `builder()` getter to `Command.Builder` ([#363](https://github.com/Incendo/cloud/pull/363))
- Core: Add flag yielding modes to `StringArgument` and `StringArrayArgument` ([#367](https://github.com/Incendo/cloud/pull/367))
- Annotations: Annotation string processors ([#353](https://github.com/Incendo/cloud/pull/353)) - Annotations: Annotation string processors ([#353](https://github.com/Incendo/cloud/pull/353))
- Annotations: `@CommandContainer` annotation processing ([#364](https://github.com/Incendo/cloud/pull/364)) - Annotations: `@CommandContainer` annotation processing ([#364](https://github.com/Incendo/cloud/pull/364))
- Annotations: `@CommandMethod` annotation processing for compile-time validation ([#365](https://github.com/Incendo/cloud/pull/365)) - Annotations: `@CommandMethod` annotation processing for compile-time validation ([#365](https://github.com/Incendo/cloud/pull/365))

View file

@ -0,0 +1,43 @@
//
// 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.annotations.specifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that the argument should stop parsing when encountering what
* could potentially be a flag.
* <p>
* This only has an effect on greedy arguments that consume all remaining input.
*
* @since 1.7.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface FlagYielding {
}

View file

@ -57,8 +57,8 @@ import org.checkerframework.checker.nullness.qual.NonNull;
*/ */
public final class FlagArgument<C> extends CommandArgument<C, Object> { public final class FlagArgument<C> extends CommandArgument<C, Object> {
private static final Pattern FLAG_ALIAS_PATTERN = Pattern.compile(" -(?<name>([A-Za-z]+))");
private static final Pattern FLAG_PRIMARY_PATTERN = Pattern.compile(" --(?<name>([A-Za-z]+))"); private static final Pattern FLAG_PRIMARY_PATTERN = Pattern.compile(" --(?<name>([A-Za-z]+))");
private static final Pattern FLAG_ALIAS_PATTERN = Pattern.compile(" -(?<name>([A-Za-z]+))");
/** /**
* Dummy object that indicates that flags were parsed successfully * Dummy object that indicates that flags were parsed successfully
@ -125,13 +125,13 @@ public final class FlagArgument<C> extends CommandArgument<C, Object> {
/** /**
* Parse command input to figure out what flag is currently being * Parse command input to figure out what flag is currently being
* typed at the end of the input queue. If no flag value is being * typed at the end of the input queue. If no flag value is being
* inputed, returns {@link Optional#empty()}.<br> * inputted, returns {@link Optional#empty()}.<br>
* <br> * <br>
* Will consume all but the last element from the input queue. * Will consume all but the last element from the input queue.
* *
* @param commandContext Command context * @param commandContext Command context
* @param inputQueue The input queue of arguments * @param inputQueue The input queue of arguments
* @return current flag being typed, or <i>empty()</i> if none is * @return current flag being typed, or {@code empty()} if none is
*/ */
public @NonNull Optional<String> parseCurrentFlag( public @NonNull Optional<String> parseCurrentFlag(
final @NonNull CommandContext<@NonNull C> commandContext, final @NonNull CommandContext<@NonNull C> commandContext,

View file

@ -59,6 +59,16 @@ public final class StandardParameters {
* Indicates that a string argument should be greedy * Indicates that a string argument should be greedy
*/ */
public static final ParserParameter<Boolean> GREEDY = create("greedy", TypeToken.get(Boolean.class)); public static final ParserParameter<Boolean> GREEDY = create("greedy", TypeToken.get(Boolean.class));
/**
* Indicates that an argument should stop parsing when encountering a potential flag.
*
* @since 1.7.0
* @see cloud.commandframework.annotations.specifier.FlagYielding
*/
public static final ParserParameter<Boolean> FLAG_YIELDING = create(
"flag_yielding",
TypeToken.get(Boolean.class)
);
/** /**
* Indicates that a string argument should be quoted. * Indicates that a string argument should be quoted.
* *

View file

@ -23,6 +23,7 @@
// //
package cloud.commandframework.arguments.parser; package cloud.commandframework.arguments.parser;
import cloud.commandframework.annotations.specifier.FlagYielding;
import cloud.commandframework.annotations.specifier.Greedy; import cloud.commandframework.annotations.specifier.Greedy;
import cloud.commandframework.annotations.specifier.Liberal; import cloud.commandframework.annotations.specifier.Liberal;
import cloud.commandframework.annotations.specifier.Quoted; import cloud.commandframework.annotations.specifier.Quoted;
@ -104,6 +105,10 @@ public final class StandardParserRegistry<C> implements ParserRegistry<C> {
Liberal.class, Liberal.class,
(liberal, typeToken) -> ParserParameters.single(StandardParameters.LIBERAL, true) (liberal, typeToken) -> ParserParameters.single(StandardParameters.LIBERAL, true)
); );
this.<FlagYielding, String>registerAnnotationMapper(
FlagYielding.class,
(flagYielding, typeToken) -> ParserParameters.single(StandardParameters.FLAG_YIELDING, true)
);
/* Register standard types */ /* Register standard types */
this.registerParserSupplier(TypeToken.get(Byte.class), options -> this.registerParserSupplier(TypeToken.get(Byte.class), options ->
@ -137,10 +142,13 @@ public final class StandardParserRegistry<C> implements ParserRegistry<C> {
(double) options.get(StandardParameters.RANGE_MAX, Double.POSITIVE_INFINITY) (double) options.get(StandardParameters.RANGE_MAX, Double.POSITIVE_INFINITY)
)); ));
this.registerParserSupplier(TypeToken.get(Character.class), options -> new CharArgument.CharacterParser<>()); this.registerParserSupplier(TypeToken.get(Character.class), options -> new CharArgument.CharacterParser<>());
this.registerParserSupplier(TypeToken.get(String[].class), options -> new StringArrayArgument.StringArrayParser<>()); this.registerParserSupplier(TypeToken.get(String[].class), options ->
new StringArrayArgument.StringArrayParser<>(options.get(StandardParameters.FLAG_YIELDING, false))
);
/* Make this one less awful */ /* Make this one less awful */
this.registerParserSupplier(TypeToken.get(String.class), options -> { this.registerParserSupplier(TypeToken.get(String.class), options -> {
final boolean greedy = options.get(StandardParameters.GREEDY, false); final boolean greedy = options.get(StandardParameters.GREEDY, false);
final boolean greedyFlagAware = options.get(StandardParameters.FLAG_YIELDING, false);
final boolean quoted = options.get(StandardParameters.QUOTED, false); final boolean quoted = options.get(StandardParameters.QUOTED, false);
if (greedy && quoted) { if (greedy && quoted) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
@ -150,6 +158,8 @@ public final class StandardParserRegistry<C> implements ParserRegistry<C> {
final StringArgument.StringMode stringMode; final StringArgument.StringMode stringMode;
if (greedy) { if (greedy) {
stringMode = StringArgument.StringMode.GREEDY; stringMode = StringArgument.StringMode.GREEDY;
} else if (greedyFlagAware) {
stringMode = StringArgument.StringMode.GREEDY_FLAG_YIELDING;
} else if (quoted) { } else if (quoted) {
stringMode = StringArgument.StringMode.QUOTED; stringMode = StringArgument.StringMode.QUOTED;
} else { } else {

View file

@ -166,6 +166,18 @@ public final class StringArgument<C> extends CommandArgument<C, String> {
return of(name, StringMode.GREEDY); return of(name, StringMode.GREEDY);
} }
/**
* Create a new required command argument with the 'greedy flag yielding' parsing mode
*
* @param name Argument name
* @param <C> Command sender type
* @return Created argument
* @since 1.7.0
*/
public static <C> @NonNull CommandArgument<C, String> greedyFlagYielding(final @NonNull String name) {
return of(name, StringMode.GREEDY_FLAG_YIELDING);
}
/** /**
* Create a new required command argument with the 'quoted' parsing mode * Create a new required command argument with the 'quoted' parsing mode
* *
@ -189,8 +201,14 @@ public final class StringArgument<C> extends CommandArgument<C, String> {
public enum StringMode { public enum StringMode {
SINGLE, SINGLE,
QUOTED,
GREEDY, GREEDY,
QUOTED /**
* Greedy string that will consume the input until a flag is present.
*
* @since 1.7.0
*/
GREEDY_FLAG_YIELDING
} }
@ -224,6 +242,17 @@ public final class StringArgument<C> extends CommandArgument<C, String> {
return this; return this;
} }
/**
* Greedy string that will consume the input until a flag is present.
*
* @return Builder instance
* @since 1.7.0
*/
public @NonNull @This Builder<C> greedyFlagYielding() {
this.stringMode = StringMode.GREEDY_FLAG_YIELDING;
return this;
}
/** /**
* Set the string mode to single * Set the string mode to single
* *
@ -274,8 +303,11 @@ public final class StringArgument<C> extends CommandArgument<C, String> {
} }
@SuppressWarnings("UnnecessaryLambda")
public static final class StringParser<C> implements ArgumentParser<C, String> { public static final class StringParser<C> implements ArgumentParser<C, String> {
private static final Pattern FLAG_PATTERN = Pattern.compile("(-[A-Za-z_\\-0-9])|(--[A-Za-z_\\-0-9]*)");
private final StringMode stringMode; private final StringMode stringMode;
private final BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider; private final BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider;
@ -390,6 +422,13 @@ public final class StringArgument<C> extends CommandArgument<C, String> {
break; break;
} }
if (this.stringMode == StringMode.GREEDY_FLAG_YIELDING) {
// The pattern requires a leading space.
if (FLAG_PATTERN.matcher(string).matches()) {
break;
}
}
sj.add(string); sj.add(string);
inputQueue.remove(); inputQueue.remove();
} }

View file

@ -29,9 +29,11 @@ import cloud.commandframework.arguments.parser.ArgumentParseResult;
import cloud.commandframework.arguments.parser.ArgumentParser; import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.context.CommandContext; import cloud.commandframework.context.CommandContext;
import io.leangen.geantyref.TypeToken; import io.leangen.geantyref.TypeToken;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Queue; import java.util.Queue;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.regex.Pattern;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -48,12 +50,13 @@ public final class StringArrayArgument<C> extends CommandArgument<C, String[]> {
final boolean required, final boolean required,
final @NonNull String name, final @NonNull String name,
final @Nullable BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider, final @Nullable BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider,
final @NonNull ArgumentDescription defaultDescription final @NonNull ArgumentDescription defaultDescription,
final boolean flagYielding
) { ) {
super( super(
required, required,
name, name,
new StringArrayParser<>(), new StringArrayParser<>(flagYielding),
"", "",
TypeToken.get(String[].class), TypeToken.get(String[].class),
suggestionsProvider, suggestionsProvider,
@ -74,10 +77,35 @@ public final class StringArrayArgument<C> extends CommandArgument<C, String[]> {
final @NonNull BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider final @NonNull BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider
) { ) {
return new StringArrayArgument<>( return new StringArrayArgument<>(
true, true /* required */,
name, name,
suggestionsProvider, suggestionsProvider,
ArgumentDescription.empty() ArgumentDescription.empty(),
false /* flagYielding */
);
}
/**
* Create a new required string array argument
*
* @param name Argument name
* @param flagYielding Whether the parser should stop parsing when encountering a potential flag
* @param suggestionsProvider Suggestions provider
* @param <C> Command sender type
* @return Created argument
* @since 1.7.0
*/
public static <C> @NonNull StringArrayArgument<C> of(
final @NonNull String name,
final boolean flagYielding,
final @NonNull BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider
) {
return new StringArrayArgument<>(
true /* required */,
name,
suggestionsProvider,
ArgumentDescription.empty(),
flagYielding
); );
} }
@ -94,10 +122,35 @@ public final class StringArrayArgument<C> extends CommandArgument<C, String[]> {
final @NonNull BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider final @NonNull BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider
) { ) {
return new StringArrayArgument<>( return new StringArrayArgument<>(
false, false /* required */,
name, name,
suggestionsProvider, suggestionsProvider,
ArgumentDescription.empty() ArgumentDescription.empty(),
false /* flagYielding */
);
}
/**
* Create a new optional string array argument
*
* @param name Argument name
* @param flagYielding Whether the parser should stop parsing when encountering a potential flag
* @param suggestionsProvider Suggestions provider
* @param <C> Command sender type
* @return Created argument
* @since 1.7.0
*/
public static <C> @NonNull StringArrayArgument<C> optional(
final @NonNull String name,
final boolean flagYielding,
final @NonNull BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider
) {
return new StringArrayArgument<>(
false /* required */,
name,
suggestionsProvider,
ArgumentDescription.empty(),
flagYielding
); );
} }
@ -109,16 +162,54 @@ public final class StringArrayArgument<C> extends CommandArgument<C, String[]> {
*/ */
public static final class StringArrayParser<C> implements ArgumentParser<C, String[]> { public static final class StringArrayParser<C> implements ArgumentParser<C, String[]> {
private static final Pattern FLAG_PATTERN = Pattern.compile("(-[A-Za-z_\\-0-9])|(--[A-Za-z_\\-0-9]*)");
private final boolean flagYielding;
/**
* Construct a new string array parser.
*/
public StringArrayParser() {
this.flagYielding = false;
}
/**
* Construct a new string array parser.
*
* @param flagYielding Whether the parser should stop parsing when encountering a potential flag
* @since 1.7.0
*/
public StringArrayParser(final boolean flagYielding) {
this.flagYielding = flagYielding;
}
@Override @Override
public @NonNull ArgumentParseResult<String @NonNull []> parse( public @NonNull ArgumentParseResult<String @NonNull []> parse(
final @NonNull CommandContext<@NonNull C> commandContext, final @NonNull CommandContext<@NonNull C> commandContext,
final @NonNull Queue<@NonNull String> inputQueue final @NonNull Queue<@NonNull String> inputQueue
) { ) {
final String[] result = new String[inputQueue.size()]; if (this.flagYielding) {
for (int i = 0; i < result.length; i++) { final List<String> result = new LinkedList<>();
result[i] = inputQueue.remove(); final int size = inputQueue.size();
for (int i = 0; i < size; i++) {
final String string = inputQueue.peek();
if (string == null || FLAG_PATTERN.matcher(string).matches()) {
break;
}
inputQueue.remove();
result.add(string);
}
return ArgumentParseResult.success(result.toArray(new String[0]));
} else {
final String[] result = new String[inputQueue.size()];
for (int i = 0; i < result.length; i++) {
result[i] = inputQueue.remove();
}
return ArgumentParseResult.success(result);
} }
return ArgumentParseResult.success(result);
} }
} }

View file

@ -28,6 +28,7 @@ import cloud.commandframework.arguments.standard.BooleanArgument;
import cloud.commandframework.arguments.standard.EnumArgument; import cloud.commandframework.arguments.standard.EnumArgument;
import cloud.commandframework.arguments.standard.IntegerArgument; import cloud.commandframework.arguments.standard.IntegerArgument;
import cloud.commandframework.arguments.standard.StringArgument; import cloud.commandframework.arguments.standard.StringArgument;
import cloud.commandframework.arguments.standard.StringArrayArgument;
import cloud.commandframework.types.tuples.Pair; import cloud.commandframework.types.tuples.Pair;
import cloud.commandframework.types.tuples.Triplet; import cloud.commandframework.types.tuples.Triplet;
import java.util.Arrays; import java.util.Arrays;
@ -38,6 +39,7 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static cloud.commandframework.util.TestUtils.createManager; import static cloud.commandframework.util.TestUtils.createManager;
import static com.google.common.truth.Truth.assertThat;
public class CommandSuggestionsTest { public class CommandSuggestionsTest {
@ -394,6 +396,107 @@ public class CommandSuggestionsTest {
Assertions.assertEquals(Collections.singletonList("later"), suggestions6); Assertions.assertEquals(Collections.singletonList("later"), suggestions6);
} }
@Test
void testFlagYieldingGreedyStringFollowedByFlagArgument() {
// Arrange
final CommandManager<TestCommandSender> manager = createManager();
manager.command(
manager.commandBuilder("command")
.argument(
StringArgument.<TestCommandSender>newBuilder("string")
.greedyFlagYielding()
.withSuggestionsProvider((context, input) -> Collections.singletonList("hello"))
.build()
).flag(manager.flagBuilder("flag").withAliases("f").build())
.flag(manager.flagBuilder("flag2").build())
);
// Act
final List<String> suggestions1 = manager.suggest(
new TestCommandSender(),
"command "
);
final List<String> suggestions2 = manager.suggest(
new TestCommandSender(),
"command hel"
);
final List<String> suggestions3 = manager.suggest(
new TestCommandSender(),
"command hello --"
);
final List<String> suggestions4 = manager.suggest(
new TestCommandSender(),
"command hello --f"
);
final List<String> suggestions5 = manager.suggest(
new TestCommandSender(),
"command hello -f"
);
final List<String> suggestions6 = manager.suggest(
new TestCommandSender(),
"command hello -"
);
// Assert
assertThat(suggestions1).containsExactly("hello");
assertThat(suggestions2).containsExactly("hello");
assertThat(suggestions3).containsExactly("--flag", "--flag2");
assertThat(suggestions4).containsExactly("--flag", "--flag2");
assertThat(suggestions5).containsExactly("-f");
assertThat(suggestions6).containsExactly("hello");
}
@Test
void testFlagYieldingStringArrayFollowedByFlagArgument() {
// Arrange
final CommandManager<TestCommandSender> manager = createManager();
manager.command(
manager.commandBuilder("command")
.argument(
StringArrayArgument.of(
"array",
true,
(context, input) -> Collections.emptyList()
)
).flag(manager.flagBuilder("flag").withAliases("f").build())
.flag(manager.flagBuilder("flag2").build())
);
// Act
final List<String> suggestions1 = manager.suggest(
new TestCommandSender(),
"command "
);
final List<String> suggestions2 = manager.suggest(
new TestCommandSender(),
"command hello"
);
final List<String> suggestions3 = manager.suggest(
new TestCommandSender(),
"command hello --"
);
final List<String> suggestions4 = manager.suggest(
new TestCommandSender(),
"command hello --f"
);
final List<String> suggestions5 = manager.suggest(
new TestCommandSender(),
"command hello -f"
);
final List<String> suggestions6 = manager.suggest(
new TestCommandSender(),
"command hello -"
);
// Assert
assertThat(suggestions1).isEmpty();
assertThat(suggestions2).isEmpty();
assertThat(suggestions3).containsExactly("--flag", "--flag2");
assertThat(suggestions4).containsExactly("--flag", "--flag2");
assertThat(suggestions5).containsExactly("-f");
assertThat(suggestions6).isEmpty();
}
public enum TestEnum { public enum TestEnum {
FOO, FOO,
BAR BAR

View file

@ -26,6 +26,7 @@ package cloud.commandframework.arguments.standard;
import cloud.commandframework.TestCommandSender; import cloud.commandframework.TestCommandSender;
import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.parser.ArgumentParseResult;
import cloud.commandframework.context.CommandContext; import cloud.commandframework.context.CommandContext;
import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -78,4 +79,60 @@ class StringArrayParserTest {
assertThat(input).isEmpty(); assertThat(input).isEmpty();
} }
@Test
void Parse_GreedyFlagAwareLongFormFlag_EndsAfterFlag() {
// Arrange
final StringArrayArgument.StringArrayParser<TestCommandSender> parser = new StringArrayArgument.StringArrayParser<>(true);
final LinkedList<String> input = ArgumentTestHelper.linkedListOf(
"this",
"is",
"a",
"string",
"--flag",
"more",
"flag",
"content"
);
// Act
final ArgumentParseResult<String[]> result = parser.parse(
this.context,
input
);
// Assert
assertThat(result.getFailure()).isEmpty();
assertThat(result.getParsedValue()).hasValue(new String[] {"this", "is", "a", "string"});
assertThat(input).containsExactly("--flag", "more", "flag", "content");
}
@Test
void Parse_GreedyFlagAwareShortFormFlag_EndsAfterFlag() {
// Arrange
final StringArrayArgument.StringArrayParser<TestCommandSender> parser = new StringArrayArgument.StringArrayParser<>(true);
final LinkedList<String> input = ArgumentTestHelper.linkedListOf(
"this",
"is",
"a",
"string",
"-f",
"-l",
"-a",
"-g"
);
// Act
final ArgumentParseResult<String[]> result = parser.parse(
this.context,
input
);
// Assert
assertThat(result.getFailure()).isEmpty();
assertThat(result.getParsedValue()).hasValue(new String[] {"this", "is", "a", "string"});
assertThat(input).containsExactly("-f", "-l", "-a", "-g");
}
} }

View file

@ -0,0 +1,106 @@
//
// 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.arguments.standard;
import cloud.commandframework.TestCommandSender;
import cloud.commandframework.arguments.parser.ArgumentParseResult;
import cloud.commandframework.context.CommandContext;
import java.util.Collections;
import java.util.LinkedList;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
@ExtendWith(MockitoExtension.class)
class StringParserTest {
@Mock
private CommandContext<TestCommandSender> context;
@Test
void Parse_GreedyFlagAwareLongFormFlag_EndsAfterFlag() {
// Arrange
final StringArgument.StringParser<TestCommandSender> parser = new StringArgument.StringParser<>(
StringArgument.StringMode.GREEDY_FLAG_YIELDING,
(context, input) -> Collections.emptyList()
);
final LinkedList<String> input = ArgumentTestHelper.linkedListOf(
"this",
"is",
"a",
"string",
"--flag",
"more",
"flag",
"content"
);
// Act
final ArgumentParseResult<String> result = parser.parse(
this.context,
input
);
// Assert
assertThat(result.getFailure()).isEmpty();
assertThat(result.getParsedValue()).hasValue("this is a string");
assertThat(input).containsExactly("--flag", "more", "flag", "content");
}
@Test
void Parse_GreedyFlagAwareShortFormFlag_EndsAfterFlag() {
// Arrange
final StringArgument.StringParser<TestCommandSender> parser = new StringArgument.StringParser<>(
StringArgument.StringMode.GREEDY_FLAG_YIELDING,
(context, input) -> Collections.emptyList()
);
final LinkedList<String> input = ArgumentTestHelper.linkedListOf(
"this",
"is",
"a",
"string",
"-f",
"-l",
"-a",
"-g"
);
// Act
final ArgumentParseResult<String> result = parser.parse(
this.context,
input
);
// Assert
assertThat(result.getFailure()).isEmpty();
assertThat(result.getParsedValue()).hasValue("this is a string");
assertThat(input).containsExactly("-f", "-l", "-a", "-g");
}
}

View file

@ -0,0 +1,83 @@
//
// 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.issue;
import cloud.commandframework.CommandManager;
import cloud.commandframework.TestCommandSender;
import cloud.commandframework.arguments.flags.FlagContext;
import cloud.commandframework.arguments.standard.StringArrayArgument;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.execution.CommandResult;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import static cloud.commandframework.util.TestUtils.createManager;
import static com.google.common.truth.Truth8.assertThat;
/**
* Test for https://github.com/Incendo/cloud/issues/321.
*/
class Issue321 {
@Test
void flagSucceedingFlagWithStringArrayArgument() {
// Arrange
final CommandManager<TestCommandSender> commandManager = createManager();
commandManager.command(
commandManager.commandBuilder("command")
.flag(
commandManager.flagBuilder("flag1")
.withArgument(
StringArrayArgument.of(
"array",
true /* flagYielding */,
(context, input) -> Collections.emptyList()
)
)
).flag(
commandManager.flagBuilder("flag2")
.withArgument(
StringArrayArgument.of(
"array",
true /* flagYielding */,
(context, input) -> Collections.emptyList()
)
)
)
);
// Act
final CommandResult<TestCommandSender> result = commandManager.executeCommand(
new TestCommandSender(),
"command --flag1 one two three --flag2 1 2 3"
).join();
// Assert
final CommandContext<TestCommandSender> context = result.getCommandContext();
final FlagContext flags = context.flags();
assertThat(flags.<String[]>getValue("flag1")).hasValue(new String[] {"one", "two", "three"});
assertThat(flags.<String[]>getValue("flag2")).hasValue(new String[] {"1", "2", "3"});
}
}