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 `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))
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
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,
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,17 +162,55 @@ 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
|
||||||
) {
|
) {
|
||||||
|
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()];
|
final String[] result = new String[inputQueue.size()];
|
||||||
for (int i = 0; i < result.length; i++) {
|
for (int i = 0; i < result.length; i++) {
|
||||||
result[i] = inputQueue.remove();
|
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.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
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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