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:
parent
e889811380
commit
d4ab593460
11 changed files with 558 additions and 15 deletions
|
|
@ -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 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 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: `@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))
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
|
@ -57,8 +57,8 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||
*/
|
||||
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_ALIAS_PATTERN = Pattern.compile(" -(?<name>([A-Za-z]+))");
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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>
|
||||
* Will consume all but the last element from the input queue.
|
||||
*
|
||||
* @param commandContext Command context
|
||||
* @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(
|
||||
final @NonNull CommandContext<@NonNull C> commandContext,
|
||||
|
|
|
|||
|
|
@ -59,6 +59,16 @@ public final class StandardParameters {
|
|||
* Indicates that a string argument should be greedy
|
||||
*/
|
||||
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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
//
|
||||
package cloud.commandframework.arguments.parser;
|
||||
|
||||
import cloud.commandframework.annotations.specifier.FlagYielding;
|
||||
import cloud.commandframework.annotations.specifier.Greedy;
|
||||
import cloud.commandframework.annotations.specifier.Liberal;
|
||||
import cloud.commandframework.annotations.specifier.Quoted;
|
||||
|
|
@ -104,6 +105,10 @@ public final class StandardParserRegistry<C> implements ParserRegistry<C> {
|
|||
Liberal.class,
|
||||
(liberal, typeToken) -> ParserParameters.single(StandardParameters.LIBERAL, true)
|
||||
);
|
||||
this.<FlagYielding, String>registerAnnotationMapper(
|
||||
FlagYielding.class,
|
||||
(flagYielding, typeToken) -> ParserParameters.single(StandardParameters.FLAG_YIELDING, true)
|
||||
);
|
||||
|
||||
/* Register standard types */
|
||||
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)
|
||||
));
|
||||
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 */
|
||||
this.registerParserSupplier(TypeToken.get(String.class), options -> {
|
||||
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);
|
||||
if (greedy && quoted) {
|
||||
throw new IllegalArgumentException(
|
||||
|
|
@ -150,6 +158,8 @@ public final class StandardParserRegistry<C> implements ParserRegistry<C> {
|
|||
final StringArgument.StringMode stringMode;
|
||||
if (greedy) {
|
||||
stringMode = StringArgument.StringMode.GREEDY;
|
||||
} else if (greedyFlagAware) {
|
||||
stringMode = StringArgument.StringMode.GREEDY_FLAG_YIELDING;
|
||||
} else if (quoted) {
|
||||
stringMode = StringArgument.StringMode.QUOTED;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -166,6 +166,18 @@ public final class StringArgument<C> extends CommandArgument<C, String> {
|
|||
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
|
||||
*
|
||||
|
|
@ -189,8 +201,14 @@ public final class StringArgument<C> extends CommandArgument<C, String> {
|
|||
|
||||
public enum StringMode {
|
||||
SINGLE,
|
||||
QUOTED,
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
|
|
@ -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> {
|
||||
|
||||
private static final Pattern FLAG_PATTERN = Pattern.compile("(-[A-Za-z_\\-0-9])|(--[A-Za-z_\\-0-9]*)");
|
||||
|
||||
private final StringMode stringMode;
|
||||
private final BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider;
|
||||
|
||||
|
|
@ -390,6 +422,13 @@ public final class StringArgument<C> extends CommandArgument<C, String> {
|
|||
break;
|
||||
}
|
||||
|
||||
if (this.stringMode == StringMode.GREEDY_FLAG_YIELDING) {
|
||||
// The pattern requires a leading space.
|
||||
if (FLAG_PATTERN.matcher(string).matches()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sj.add(string);
|
||||
inputQueue.remove();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,9 +29,11 @@ import cloud.commandframework.arguments.parser.ArgumentParseResult;
|
|||
import cloud.commandframework.arguments.parser.ArgumentParser;
|
||||
import cloud.commandframework.context.CommandContext;
|
||||
import io.leangen.geantyref.TypeToken;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.regex.Pattern;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
|
|
@ -48,12 +50,13 @@ public final class StringArrayArgument<C> extends CommandArgument<C, String[]> {
|
|||
final boolean required,
|
||||
final @NonNull String name,
|
||||
final @Nullable BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider,
|
||||
final @NonNull ArgumentDescription defaultDescription
|
||||
final @NonNull ArgumentDescription defaultDescription,
|
||||
final boolean flagYielding
|
||||
) {
|
||||
super(
|
||||
required,
|
||||
name,
|
||||
new StringArrayParser<>(),
|
||||
new StringArrayParser<>(flagYielding),
|
||||
"",
|
||||
TypeToken.get(String[].class),
|
||||
suggestionsProvider,
|
||||
|
|
@ -74,10 +77,35 @@ public final class StringArrayArgument<C> extends CommandArgument<C, String[]> {
|
|||
final @NonNull BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider
|
||||
) {
|
||||
return new StringArrayArgument<>(
|
||||
true,
|
||||
true /* required */,
|
||||
name,
|
||||
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
|
||||
) {
|
||||
return new StringArrayArgument<>(
|
||||
false,
|
||||
false /* required */,
|
||||
name,
|
||||
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[]> {
|
||||
|
||||
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
|
||||
public @NonNull ArgumentParseResult<String @NonNull []> parse(
|
||||
final @NonNull CommandContext<@NonNull C> commandContext,
|
||||
final @NonNull Queue<@NonNull String> inputQueue
|
||||
) {
|
||||
final String[] result = new String[inputQueue.size()];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = inputQueue.remove();
|
||||
if (this.flagYielding) {
|
||||
final List<String> result = new LinkedList<>();
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import cloud.commandframework.arguments.standard.BooleanArgument;
|
|||
import cloud.commandframework.arguments.standard.EnumArgument;
|
||||
import cloud.commandframework.arguments.standard.IntegerArgument;
|
||||
import cloud.commandframework.arguments.standard.StringArgument;
|
||||
import cloud.commandframework.arguments.standard.StringArrayArgument;
|
||||
import cloud.commandframework.types.tuples.Pair;
|
||||
import cloud.commandframework.types.tuples.Triplet;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -38,6 +39,7 @@ import org.junit.jupiter.api.BeforeAll;
|
|||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static cloud.commandframework.util.TestUtils.createManager;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
public class CommandSuggestionsTest {
|
||||
|
||||
|
|
@ -394,6 +396,107 @@ public class CommandSuggestionsTest {
|
|||
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 {
|
||||
FOO,
|
||||
BAR
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ 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 java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.stream.Collectors;
|
||||
|
|
@ -78,4 +79,60 @@ class StringArrayParserTest {
|
|||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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"});
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue