feat(core): add repeatable flags (#378)

implements #209.
This commit is contained in:
Alexander Söderberg 2022-06-14 17:21:51 +02:00 committed by Jason
parent d3864414aa
commit ec535dad7f
9 changed files with 459 additions and 80 deletions

View file

@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Core: Add flag yielding modes to `StringArgument` and `StringArrayArgument` ([#367](https://github.com/Incendo/cloud/pull/367))
- Core: Add [apiguardian](https://github.com/apiguardian-team/apiguardian) `@API` annotations ([#368](https://github.com/Incendo/cloud/pull/368))
- Core: Deprecate prefixed getters/setters in `CommandManager` ([#377](https://github.com/Incendo/cloud/pull/377))
- Core: Add repeatable flags ([#378](https://github.com/Incendo/cloud/pull/378))
- 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))

View file

@ -28,6 +28,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.function.BiFunction;
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
@ -94,4 +95,13 @@ public @interface Flag {
* @since 1.6.0
*/
@NonNull String permission() default "";
/**
* Whether the flag can be repeated.
*
* @return whether the flag can be repeated
* @since 1.7.0
*/
@API(status = API.Status.STABLE, since = "1.7.0")
boolean repeatable() default false;
}

View file

@ -30,6 +30,7 @@ import cloud.commandframework.arguments.flags.CommandFlag;
import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.arguments.parser.ParserRegistry;
import cloud.commandframework.permission.Permission;
import io.leangen.geantyref.GenericTypeReflector;
import io.leangen.geantyref.TypeToken;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
@ -65,15 +66,34 @@ final class FlagExtractor implements Function<@NonNull Method, Collection<@NonNu
}
final Flag flag = parameter.getAnnotation(Flag.class);
final String flagName = this.annotationParser.processString(flag.value());
final CommandFlag.Builder<Void> builder = this.commandManager
CommandFlag.Builder<Void> builder = this.commandManager
.flagBuilder(this.annotationParser.processString(flagName))
.withDescription(ArgumentDescription.of(this.annotationParser.processString(flag.description())))
.withAliases(this.annotationParser.processStrings(flag.aliases()))
.withPermission(Permission.of(this.annotationParser.processString(flag.permission())));
if (flag.repeatable()) {
builder = builder.asRepeatable();
}
if (parameter.getType().equals(boolean.class)) {
flags.add(builder.build());
} else {
final TypeToken<?> token = TypeToken.get(parameter.getType());
final TypeToken<?> token;
if (flag.repeatable() && Collection.class.isAssignableFrom(parameter.getType())) {
token = TypeToken.get(GenericTypeReflector.getTypeParameter(
parameter.getParameterizedType(),
Collection.class.getTypeParameters()[0]
));
} else {
token = TypeToken.get(parameter.getType());
}
if (token.equals(TypeToken.get(boolean.class))) {
flags.add(builder.build());
continue;
}
final Collection<Annotation> annotations = Arrays.asList(parameter.getAnnotations());
final ParserRegistry<?> registry = this.commandManager.parserRegistry();
final ArgumentParser<?, ?> parser;

View file

@ -126,8 +126,10 @@ public class MethodCommandExecutionHandler<C> implements CommandExecutionHandler
} else if (parameter.isAnnotationPresent(Flag.class)) {
final Flag flag = parameter.getAnnotation(Flag.class);
final String flagName = this.annotationParser.processString(flag.value());
if (parameter.getType() == boolean.class) {
if (parameter.getType().equals(boolean.class)) {
arguments.add(flagContext.isPresent(flagName));
} else if (flag.repeatable() && parameter.getType().isAssignableFrom(List.class)) {
arguments.add(flagContext.getAll(flagName));
} else {
arguments.add(flagContext.getValue(flagName, null));
}

View file

@ -0,0 +1,79 @@
//
// 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.feature;
import cloud.commandframework.CommandManager;
import cloud.commandframework.annotations.AnnotationParser;
import cloud.commandframework.annotations.CommandMethod;
import cloud.commandframework.annotations.Flag;
import cloud.commandframework.annotations.TestCommandManager;
import cloud.commandframework.annotations.TestCommandSender;
import cloud.commandframework.arguments.parser.StandardParameters;
import cloud.commandframework.execution.CommandExecutionCoordinator;
import cloud.commandframework.execution.CommandResult;
import cloud.commandframework.internal.CommandRegistrationHandler;
import cloud.commandframework.meta.CommandMeta;
import cloud.commandframework.meta.SimpleCommandMeta;
import io.leangen.geantyref.TypeToken;
import java.util.Collection;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static com.google.common.truth.Truth.assertThat;
class RepeatableFlagTest {
private CommandManager<TestCommandSender> commandManager;
@BeforeEach
void setup() {
this.commandManager = new TestCommandManager();
final AnnotationParser<TestCommandSender> annotationParser = new AnnotationParser<>(
this.commandManager,
TestCommandSender.class,
p -> SimpleCommandMeta.empty()
);
annotationParser.parse(new TestClassA());
}
@Test
void testRepeatableFlagParsing() {
// Act
final CommandResult<TestCommandSender> result = this.commandManager.executeCommand(
new TestCommandSender(),
"test --flag one --flag two --flag three"
).join();
// Assert
assertThat(result.getCommandContext().flags().getAll("flag")).containsExactly("one", "two", "three");
}
public static final class TestClassA {
@CommandMethod("test")
public void command(@Flag(value = "flag", repeatable = true) Collection<String> flags) {
}
}
}

View file

@ -41,6 +41,7 @@ import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
@ -186,8 +187,8 @@ public final class FlagArgument<C> extends CommandArgument<C, Object> {
final @NonNull String input
) {
/* Check if we have a last flag stored */
final String lastArg = commandContext.getOrDefault(FLAG_META_KEY, "");
if (lastArg.isEmpty() || !lastArg.startsWith("-")) {
final String lastArg = Objects.requireNonNull(commandContext.getOrDefault(FLAG_META_KEY, ""));
if (!lastArg.startsWith("-")) {
final String rawInput = commandContext.getRawInputJoined();
/* Collection containing all used flags */
final List<CommandFlag<?>> usedFlags = new LinkedList<>();
@ -220,38 +221,30 @@ public final class FlagArgument<C> extends CommandArgument<C, Object> {
final List<String> strings = new LinkedList<>();
/* Recommend "primary" flags */
for (final CommandFlag<?> flag : this.flags) {
if (usedFlags.contains(flag) || !commandContext.hasPermission(flag.permission())) {
if (usedFlags.contains(flag) && flag.mode() != CommandFlag.FlagMode.REPEATABLE) {
continue;
}
strings.add(
String.format(
"--%s",
flag.getName()
)
);
if (!commandContext.hasPermission(flag.permission())) {
continue;
}
strings.add(String.format("--%s", flag.getName()));
}
/* Recommend aliases */
final boolean suggestCombined = input.length() > 1 && input.charAt(0) == '-' && input.charAt(1) != '-';
for (final CommandFlag<?> flag : this.flags) {
if (usedFlags.contains(flag) || !commandContext.hasPermission(flag.permission())) {
if (usedFlags.contains(flag) && flag.mode() != CommandFlag.FlagMode.REPEATABLE) {
continue;
}
if (!commandContext.hasPermission(flag.permission())) {
continue;
}
for (final String alias : flag.getAliases()) {
if (suggestCombined && flag.getCommandArgument() == null) {
strings.add(
String.format(
"%s%s",
input,
alias
)
);
strings.add(String.format("%s%s", input, alias));
} else {
strings.add(
String.format(
"-%s",
alias
)
);
strings.add(String.format("-%s", alias));
}
}
}
@ -348,30 +341,32 @@ public final class FlagArgument<C> extends CommandArgument<C, Object> {
final String flagName = string.substring(1);
if (flagName.length() > 1) {
boolean oneAdded = false;
/* This is a multi-alias flag, find all flags that apply */
for (final CommandFlag<?> flag : FlagArgumentParser.this.flags) {
if (flag.getCommandArgument() != null) {
for (int i = 0; i < flagName.length(); i++) {
final String parsedFlag = Character.toString(flagName.charAt(i))
.toLowerCase(Locale.ENGLISH);
for (final CommandFlag<?> candidateFlag : FlagArgumentParser.this.flags) {
if (candidateFlag.getCommandArgument() != null) {
continue;
}
for (final String alias : flag.getAliases()) {
if (flagName.toLowerCase(Locale.ENGLISH).contains(alias.toLowerCase(Locale.ENGLISH))) {
if (parsedFlags.contains(flag)) {
if (candidateFlag.getAliases().contains(parsedFlag)) {
if (parsedFlags.contains(candidateFlag)
&& candidateFlag.mode() != CommandFlag.FlagMode.REPEATABLE) {
return ArgumentParseResult.failure(new FlagParseException(
string,
FailureReason.DUPLICATE_FLAG,
commandContext
));
} else if (!commandContext.hasPermission(flag.permission())) {
} else if (!commandContext.hasPermission(candidateFlag.permission())) {
return ArgumentParseResult.failure(new FlagParseException(
string,
FailureReason.NO_PERMISSION,
commandContext
));
}
parsedFlags.add(flag);
commandContext.flags().addPresenceFlag(flag);
parsedFlags.add(candidateFlag);
commandContext.flags().addPresenceFlag(candidateFlag);
oneAdded = true;
break;
}
}
}
@ -402,7 +397,7 @@ public final class FlagArgument<C> extends CommandArgument<C, Object> {
FailureReason.UNKNOWN_FLAG,
commandContext
));
} else if (parsedFlags.contains(currentFlag)) {
} else if (parsedFlags.contains(currentFlag) && currentFlag.mode() != CommandFlag.FlagMode.REPEATABLE) {
return ArgumentParseResult.failure(new FlagParseException(
string,
FailureReason.DUPLICATE_FLAG,

View file

@ -51,6 +51,7 @@ public final class CommandFlag<T> {
private final @NonNull String @NonNull [] aliases;
private final @NonNull ArgumentDescription description;
private final @NonNull CommandPermission permission;
private final @NonNull FlagMode mode;
private final @Nullable CommandArgument<?, T> commandArgument;
@ -59,13 +60,15 @@ public final class CommandFlag<T> {
final @NonNull String @NonNull [] aliases,
final @NonNull ArgumentDescription description,
final @NonNull CommandPermission permission,
final @Nullable CommandArgument<?, T> commandArgument
final @Nullable CommandArgument<?, T> commandArgument,
final @NonNull FlagMode mode
) {
this.name = Objects.requireNonNull(name, "name cannot be null");
this.aliases = Objects.requireNonNull(aliases, "aliases cannot be null");
this.description = Objects.requireNonNull(description, "description cannot be null");
this.permission = Objects.requireNonNull(permission, "permission cannot be null");
this.commandArgument = commandArgument;
this.mode = Objects.requireNonNull(mode, "mode cannot be null");
}
/**
@ -96,6 +99,16 @@ public final class CommandFlag<T> {
return Arrays.asList(this.aliases);
}
/**
* Returns the {@link FlagMode mode} of this flag.
*
* @return the flag mode
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public @NonNull FlagMode mode() {
return this.mode;
}
/**
* Get the flag description
*
@ -174,23 +187,26 @@ public final class CommandFlag<T> {
private final ArgumentDescription description;
private final CommandPermission permission;
private final CommandArgument<?, T> commandArgument;
private final FlagMode mode;
private Builder(
final @NonNull String name,
final @NonNull String[] aliases,
final @NonNull ArgumentDescription description,
final @NonNull CommandPermission permission,
final @Nullable CommandArgument<?, T> commandArgument
final @Nullable CommandArgument<?, T> commandArgument,
final @NonNull FlagMode mode
) {
this.name = name;
this.aliases = aliases;
this.description = description;
this.permission = permission;
this.commandArgument = commandArgument;
this.mode = mode;
}
private Builder(final @NonNull String name) {
this(name, new String[0], ArgumentDescription.empty(), Permission.empty(), null);
this(name, new String[0], ArgumentDescription.empty(), Permission.empty(), null, FlagMode.SINGLE);
}
/**
@ -200,7 +216,7 @@ public final class CommandFlag<T> {
* @param aliases Flag aliases
* @return New builder instance
*/
public Builder<T> withAliases(final @NonNull String... aliases) {
public @NonNull Builder<T> withAliases(final @NonNull String... aliases) {
final Set<String> filteredAliases = new HashSet<>();
for (final String alias : aliases) {
if (alias.isEmpty()) {
@ -221,7 +237,8 @@ public final class CommandFlag<T> {
filteredAliases.toArray(new String[0]),
this.description,
this.permission,
this.commandArgument
this.commandArgument,
this.mode
);
}
@ -234,7 +251,7 @@ public final class CommandFlag<T> {
*/
@Deprecated
@API(status = API.Status.DEPRECATED, since = "1.4.0")
public Builder<T> withDescription(final cloud.commandframework.@NonNull Description description) {
public @NonNull Builder<T> withDescription(final cloud.commandframework.@NonNull Description description) {
return this.withDescription((ArgumentDescription) description);
}
@ -246,8 +263,8 @@ public final class CommandFlag<T> {
* @since 1.4.0
*/
@API(status = API.Status.STABLE, since = "1.4.0")
public Builder<T> withDescription(final @NonNull ArgumentDescription description) {
return new Builder<>(this.name, this.aliases, description, this.permission, this.commandArgument);
public @NonNull Builder<T> withDescription(final @NonNull ArgumentDescription description) {
return new Builder<>(this.name, this.aliases, description, this.permission, this.commandArgument, this.mode);
}
/**
@ -257,8 +274,8 @@ public final class CommandFlag<T> {
* @param <N> New argument type
* @return New builder instance
*/
public <N> Builder<N> withArgument(final @NonNull CommandArgument<?, N> argument) {
return new Builder<>(this.name, this.aliases, this.description, this.permission, argument);
public <N> @NonNull Builder<N> withArgument(final @NonNull CommandArgument<?, N> argument) {
return new Builder<>(this.name, this.aliases, this.description, this.permission, argument, this.mode);
}
/**
@ -268,7 +285,7 @@ public final class CommandFlag<T> {
* @param <N> New argument type
* @return New builder instance
*/
public <N> Builder<N> withArgument(final CommandArgument.@NonNull Builder<?, N> builder) {
public <N> @NonNull Builder<N> withArgument(final CommandArgument.@NonNull Builder<?, N> builder) {
return this.withArgument(builder.build());
}
@ -280,8 +297,26 @@ public final class CommandFlag<T> {
* @since 1.6.0
*/
@API(status = API.Status.STABLE, since = "1.6.0")
public Builder<T> withPermission(final @NonNull CommandPermission permission) {
return new Builder<>(this.name, this.aliases, this.description, permission, this.commandArgument);
public @NonNull Builder<T> withPermission(final @NonNull CommandPermission permission) {
return new Builder<>(this.name, this.aliases, this.description, permission, this.commandArgument, this.mode);
}
/**
* Marks the flag as {@link FlagMode#REPEATABLE}.
*
* @return new builder instance
* @since 1.7.0
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public @NonNull Builder<T> asRepeatable() {
return new Builder<>(
this.name,
this.aliases,
this.description,
this.permission,
this.commandArgument,
FlagMode.REPEATABLE
);
}
/**
@ -290,7 +325,29 @@ public final class CommandFlag<T> {
* @return Constructed instance
*/
public @NonNull CommandFlag<T> build() {
return new CommandFlag<>(this.name, this.aliases, this.description, this.permission, this.commandArgument);
return new CommandFlag<>(
this.name,
this.aliases,
this.description,
this.permission,
this.commandArgument,
this.mode
);
}
}
@API(status = API.Status.STABLE, since = "1.7.0")
public enum FlagMode {
/**
* Only a single value can be provided for the flag, and should be extracted
* using {@link FlagContext#get(CommandFlag)}.
*/
SINGLE,
/**
* Multiple values can be provided for the flag, and sdhould be extracted
* using {@link FlagContext#getAll(CommandFlag)}.
*/
REPEATABLE
}
}

View file

@ -23,7 +23,11 @@
//
package cloud.commandframework.arguments.flags;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.apiguardian.api.API;
@ -34,6 +38,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
* Flag value mappings
*/
@API(status = API.Status.STABLE)
@SuppressWarnings({"rawtypes", "unchecked"})
public final class FlagContext {
/**
@ -41,7 +46,7 @@ public final class FlagContext {
*/
public static final Object FLAG_PRESENCE_VALUE = new Object();
private final Map<String, Object> flagValues;
private final Map<String, List> flagValues;
private FlagContext() {
this.flagValues = new HashMap<>();
@ -62,7 +67,10 @@ public final class FlagContext {
* @param flag Flag instance
*/
public void addPresenceFlag(final @NonNull CommandFlag<?> flag) {
this.flagValues.put(flag.getName(), FLAG_PRESENCE_VALUE);
((List<Object>) this.flagValues.computeIfAbsent(
flag.getName(),
$ -> new ArrayList<>()
)).add(FLAG_PRESENCE_VALUE);
}
/**
@ -76,7 +84,36 @@ public final class FlagContext {
final @NonNull CommandFlag<T> flag,
final @NonNull T value
) {
this.flagValues.put(flag.getName(), value);
((List<T>) this.flagValues.computeIfAbsent(
flag.getName(),
$ -> new ArrayList<>()
)).add(value);
}
/**
* Returns the number of values associated with the given {@code flag}.
*
* @param flag the flag
* @param <T> the flag value type
* @return the number of values associated with the flag
* @since 1.7.0
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public <T> int count(final @NonNull CommandFlag<T> flag) {
return this.getAll(flag).size();
}
/**
* Returns the number of values associated with the given {@code flag}.
*
* @param flag the flag
* @param <T> the flag value type
* @return the number of values associated with the flag
* @since 1.7.0
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public <T> int count(final @NonNull String flag) {
return this.getAll(flag).size();
}
/**
@ -88,8 +125,8 @@ public final class FlagContext {
* else {@code false}
*/
public boolean isPresent(final @NonNull String flag) {
final Object value = this.flagValues.get(flag);
return FLAG_PRESENCE_VALUE.equals(value);
final List value = this.flagValues.get(flag);
return value != null && !value.isEmpty();
}
/**
@ -107,7 +144,12 @@ public final class FlagContext {
}
/**
* Get a flag value as an optional. Will be empty if the value is not present.
* Returns a flag value.
* <p>
* If using {@link cloud.commandframework.arguments.flags.CommandFlag.FlagMode#SINGLE}
* then this returns the only value, if it has been specified. If using
* {@link cloud.commandframework.arguments.flags.CommandFlag.FlagMode#REPEATABLE} then
* it'll return the first value.
*
* @param name Flag name
* @param <T> Value type
@ -118,16 +160,20 @@ public final class FlagContext {
public <T> @NonNull Optional<T> getValue(
final @NonNull String name
) {
final Object value = this.flagValues.get(name);
if (value == null) {
final List value = this.flagValues.get(name);
if (value == null || value.isEmpty()) {
return Optional.empty();
}
@SuppressWarnings("unchecked") final T casted = (T) value;
return Optional.of(casted);
return Optional.of((T) value.get(0));
}
/**
* Get a flag value as an optional. Will be empty if the value is not present.
* Returns a flag value.
* <p>
* If using {@link cloud.commandframework.arguments.flags.CommandFlag.FlagMode#SINGLE}
* then this returns the only value, if it has been specified. If using
* {@link cloud.commandframework.arguments.flags.CommandFlag.FlagMode#REPEATABLE} then
* it'll return the first value.
*
* @param flag Flag type
* @param <T> Value type
@ -142,7 +188,12 @@ public final class FlagContext {
}
/**
* Get a flag value
* Returns a flag value.
* <p>
* If using {@link cloud.commandframework.arguments.flags.CommandFlag.FlagMode#SINGLE}
* then this returns the only value, if it has been specified. If using
* {@link cloud.commandframework.arguments.flags.CommandFlag.FlagMode#REPEATABLE} then
* it'll return the first value.
*
* @param name Flag name
* @param defaultValue Default value
@ -157,7 +208,12 @@ public final class FlagContext {
}
/**
* Get a flag value
* Returns a flag value.
* <p>
* If using {@link cloud.commandframework.arguments.flags.CommandFlag.FlagMode#SINGLE}
* then this returns the only value, if it has been specified. If using
* {@link cloud.commandframework.arguments.flags.CommandFlag.FlagMode#REPEATABLE} then
* it'll return the first value.
*
* @param name Flag value
* @param defaultValue Default value
@ -234,7 +290,12 @@ public final class FlagContext {
}
/**
* Get a flag value
* Returns a flag value.
* <p>
* If using {@link cloud.commandframework.arguments.flags.CommandFlag.FlagMode#SINGLE}
* then this returns the only value, if it has been specified. If using
* {@link cloud.commandframework.arguments.flags.CommandFlag.FlagMode#REPEATABLE} then
* it'll return the first value.
*
* @param name Flag name
* @param <T> Value type
@ -250,7 +311,12 @@ public final class FlagContext {
}
/**
* Get a flag value
* Returns a flag value.
* <p>
* If using {@link cloud.commandframework.arguments.flags.CommandFlag.FlagMode#SINGLE}
* then this returns the only value, if it has been specified. If using
* {@link cloud.commandframework.arguments.flags.CommandFlag.FlagMode#REPEATABLE} then
* it'll return the first value.
*
* @param flag Flag name
* @param <T> Value type
@ -263,4 +329,42 @@ public final class FlagContext {
) {
return this.getValue(flag).orElse(null);
}
/**
* Returns all supplied flag values for the given {@code flag}.
*
* @param flag the flag
* @param <T> the flag value type
* @return unmodifiable view of all stored flag values, or {@link Collections#emptyList()}.
* @since 1.7.0
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public <T> @NonNull Collection<T> getAll(
final @NonNull CommandFlag<T> flag
) {
final List values = this.flagValues.get(flag.getName());
if (values != null) {
return Collections.unmodifiableList((List<T>) values);
}
return Collections.emptyList();
}
/**
* Returns all supplied flag values for the given {@code flag}.
*
* @param flag the flag
* @param <T> the flag value type
* @return unmodifiable view of all stored flag values, or {@link Collections#emptyList()}.
* @since 1.7.0
*/
@API(status = API.Status.STABLE, since = "1.7.0")
public <T> @NonNull Collection<T> getAll(
final @NonNull String flag
) {
final List values = this.flagValues.get(flag);
if (values != null) {
return Collections.unmodifiableList((List<T>) values);
}
return Collections.emptyList();
}
}

View file

@ -0,0 +1,111 @@
//
// MIT License
//
// Copyright (c) 2021 Alexander Söderberg & Contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
package cloud.commandframework.feature;
import cloud.commandframework.CommandManager;
import cloud.commandframework.TestCommandSender;
import cloud.commandframework.arguments.standard.StringArgument;
import cloud.commandframework.execution.CommandResult;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static cloud.commandframework.util.TestUtils.createManager;
import static com.google.common.truth.Truth.assertThat;
class RepeatableFlagTest {
private CommandManager<TestCommandSender> commandManager;
@BeforeEach
void setup() {
this.commandManager = createManager();
}
@Test
void testParsingRepeatingValueFlags() {
// Arrange
this.commandManager.command(
this.commandManager.commandBuilder("test")
.flag(
this.commandManager.flagBuilder("flag")
.asRepeatable()
.withArgument(StringArgument.single("string"))
)
);
// Act
final CommandResult<TestCommandSender> result = this.commandManager.executeCommand(
new TestCommandSender(),
"test --flag one --flag two --flag three"
).join();
// Assert
assertThat(result.getCommandContext().flags().getAll("flag")).containsExactly("one", "two", "three");
}
@Test
void testParsingRepeatingPresenceFlags() {
// Arrange
this.commandManager.command(
this.commandManager.commandBuilder("test")
.flag(
this.commandManager.flagBuilder("flag")
.withAliases("f")
.asRepeatable()
)
);
// Act
final CommandResult<TestCommandSender> result = this.commandManager.executeCommand(
new TestCommandSender(),
"test --flag -fff"
).join();
// Assert
assertThat(result.getCommandContext().flags().count("flag")).isEqualTo(4);
}
@Test
void testSuggestingRepeatableFlags() {
// Arrange
this.commandManager.command(
this.commandManager.commandBuilder("test")
.flag(
this.commandManager.flagBuilder("flag")
.withAliases("f")
.asRepeatable()
)
);
// Act
final List<String> suggestions = this.commandManager.suggest(
new TestCommandSender(),
"test --flag --"
);
// Assert
assertThat(suggestions).containsExactly("--flag");
}
}