Add option to allow flags anywhere after last literal argument (#395)
This commit is contained in:
parent
adea7d5ba9
commit
6c026f994b
8 changed files with 372 additions and 113 deletions
|
|
@ -62,6 +62,7 @@ public class Command<C> {
|
||||||
|
|
||||||
private final List<@NonNull CommandComponent<C>> components;
|
private final List<@NonNull CommandComponent<C>> components;
|
||||||
private final List<@NonNull CommandArgument<C, ?>> arguments;
|
private final List<@NonNull CommandArgument<C, ?>> arguments;
|
||||||
|
private final @Nullable FlagArgument<C> flagArgument;
|
||||||
private final CommandExecutionHandler<C> commandExecutionHandler;
|
private final CommandExecutionHandler<C> commandExecutionHandler;
|
||||||
private final Class<? extends C> senderType;
|
private final Class<? extends C> senderType;
|
||||||
private final CommandPermission commandPermission;
|
private final CommandPermission commandPermission;
|
||||||
|
|
@ -78,6 +79,7 @@ public class Command<C> {
|
||||||
* @since 1.3.0
|
* @since 1.3.0
|
||||||
*/
|
*/
|
||||||
@API(status = API.Status.STABLE, since = "1.3.0")
|
@API(status = API.Status.STABLE, since = "1.3.0")
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public Command(
|
public Command(
|
||||||
final @NonNull List<@NonNull CommandComponent<C>> commandComponents,
|
final @NonNull List<@NonNull CommandComponent<C>> commandComponents,
|
||||||
final @NonNull CommandExecutionHandler<@NonNull C> commandExecutionHandler,
|
final @NonNull CommandExecutionHandler<@NonNull C> commandExecutionHandler,
|
||||||
|
|
@ -90,6 +92,14 @@ public class Command<C> {
|
||||||
if (this.components.isEmpty()) {
|
if (this.components.isEmpty()) {
|
||||||
throw new IllegalArgumentException("At least one command component is required");
|
throw new IllegalArgumentException("At least one command component is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.flagArgument =
|
||||||
|
this.arguments.stream()
|
||||||
|
.filter(ca -> ca instanceof FlagArgument)
|
||||||
|
.map(ca -> (FlagArgument<C>) ca)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
// Enforce ordering of command arguments
|
// Enforce ordering of command arguments
|
||||||
boolean foundOptional = false;
|
boolean foundOptional = false;
|
||||||
for (final CommandArgument<C, ?> argument : this.arguments) {
|
for (final CommandArgument<C, ?> argument : this.arguments) {
|
||||||
|
|
@ -318,6 +328,32 @@ public class Command<C> {
|
||||||
return new ArrayList<>(this.arguments);
|
return new ArrayList<>(this.arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a mutable copy of the command arguments, ignoring flag arguments.
|
||||||
|
*
|
||||||
|
* @return argument list
|
||||||
|
* @since 1.8.0
|
||||||
|
*/
|
||||||
|
@API(status = API.Status.EXPERIMENTAL, since = "1.8.0")
|
||||||
|
public @NonNull List<CommandArgument<@NonNull C, @NonNull ?>> nonFlagArguments() {
|
||||||
|
List<CommandArgument<C, ?>> arguments = new ArrayList<>(this.arguments);
|
||||||
|
if (this.flagArgument != null) {
|
||||||
|
arguments.remove(this.flagArgument);
|
||||||
|
}
|
||||||
|
return arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the flag argument for this command, or null if no flags are supported.
|
||||||
|
*
|
||||||
|
* @return flag argument or null
|
||||||
|
* @since 1.8.0
|
||||||
|
*/
|
||||||
|
@API(status = API.Status.EXPERIMENTAL, since = "1.8.0")
|
||||||
|
public @Nullable FlagArgument<@NonNull C> flagArgument() {
|
||||||
|
return this.flagArgument;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a copy of the command component array
|
* Returns a copy of the command component array
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1417,7 +1417,17 @@ public abstract class CommandManager<C> {
|
||||||
* @since 1.2.0
|
* @since 1.2.0
|
||||||
*/
|
*/
|
||||||
@API(status = API.Status.STABLE, since = "1.2.0")
|
@API(status = API.Status.STABLE, since = "1.2.0")
|
||||||
OVERRIDE_EXISTING_COMMANDS
|
OVERRIDE_EXISTING_COMMANDS,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows parsing flags at any position after the last literal by appending flag argument nodes between each command node.
|
||||||
|
* It can have some conflicts when integrating with other command systems like Brigadier,
|
||||||
|
* and code inspecting the command tree may need to be adjusted.
|
||||||
|
*
|
||||||
|
* @since 1.8.0
|
||||||
|
*/
|
||||||
|
@API(status = API.Status.EXPERIMENTAL, since = "1.8.0")
|
||||||
|
LIBERAL_FLAG_PARSING
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -340,7 +340,8 @@ public final class CommandTree<C> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if (child.getValue() != null) {
|
if (child.getValue() != null) {
|
||||||
if (commandQueue.isEmpty()) {
|
// Flag arguments need to be skipped over, so that further defaults are handled
|
||||||
|
if (commandQueue.isEmpty() && !(child.getValue() instanceof FlagArgument)) {
|
||||||
if (child.getValue().hasDefaultValue()) {
|
if (child.getValue().hasDefaultValue()) {
|
||||||
commandQueue.add(child.getValue().getDefaultValue());
|
commandQueue.add(child.getValue().getDefaultValue());
|
||||||
} else if (!child.getValue().isRequired()) {
|
} else if (!child.getValue().isRequired()) {
|
||||||
|
|
@ -604,14 +605,14 @@ public final class CommandTree<C> {
|
||||||
* Use the flag argument parser to deduce what flag is being suggested right now
|
* Use the flag argument parser to deduce what flag is being suggested right now
|
||||||
* If empty, then no flag value is being typed, and the different flag options should
|
* If empty, then no flag value is being typed, and the different flag options should
|
||||||
* be suggested instead.
|
* be suggested instead.
|
||||||
*
|
|
||||||
* Note: the method parseCurrentFlag() will remove all but the last element from
|
|
||||||
* the queue!
|
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
FlagArgument.FlagArgumentParser<C> parser = (FlagArgument.FlagArgumentParser<C>) child.getValue().getParser();
|
FlagArgument.FlagArgumentParser<C> parser = (FlagArgument.FlagArgumentParser<C>) child.getValue().getParser();
|
||||||
Optional<String> lastFlag = parser.parseCurrentFlag(commandContext, commandQueue);
|
Optional<String> lastFlag = parser.parseCurrentFlag(commandContext, commandQueue);
|
||||||
lastFlag.ifPresent(s -> commandContext.store(FlagArgument.FLAG_META_KEY, s));
|
lastFlag.ifPresent(s -> commandContext.store(FlagArgument.FLAG_META_KEY, s));
|
||||||
|
if (!lastFlag.isPresent()) {
|
||||||
|
commandContext.remove(FlagArgument.FLAG_META_KEY);
|
||||||
|
}
|
||||||
} else if (GenericTypeReflector.erase(child.getValue().getValueType().getType()).isArray()) {
|
} else if (GenericTypeReflector.erase(child.getValue().getValueType().getType()).isArray()) {
|
||||||
while (commandQueue.size() > 1) {
|
while (commandQueue.size() > 1) {
|
||||||
commandQueue.remove();
|
commandQueue.remove();
|
||||||
|
|
@ -629,8 +630,7 @@ public final class CommandTree<C> {
|
||||||
if (commandQueue.isEmpty()) {
|
if (commandQueue.isEmpty()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
} else if (child.isLeaf() && commandQueue.size() < 2) {
|
} else if (child.isLeaf() && commandQueue.size() < 2) {
|
||||||
commandContext.setCurrentArgument(child.getValue());
|
return this.directSuggestions(commandContext, child, commandQueue.peek());
|
||||||
return child.getValue().getSuggestionsProvider().apply(commandContext, commandQueue.peek());
|
|
||||||
} else if (child.isLeaf()) {
|
} else if (child.isLeaf()) {
|
||||||
if (child.getValue() instanceof CompoundArgument) {
|
if (child.getValue() instanceof CompoundArgument) {
|
||||||
final String last = ((LinkedList<String>) commandQueue).getLast();
|
final String last = ((LinkedList<String>) commandQueue).getLast();
|
||||||
|
|
@ -639,8 +639,7 @@ public final class CommandTree<C> {
|
||||||
}
|
}
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
} else if (commandQueue.peek().isEmpty()) {
|
} else if (commandQueue.peek().isEmpty()) {
|
||||||
commandContext.setCurrentArgument(child.getValue());
|
return this.directSuggestions(commandContext, child, commandQueue.peek());
|
||||||
return child.getValue().getSuggestionsProvider().apply(commandContext, commandQueue.remove());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store original input command queue before the parsers below modify it
|
// Store original input command queue before the parsers below modify it
|
||||||
|
|
@ -691,8 +690,7 @@ public final class CommandTree<C> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: use suggestion provider of argument
|
// Fallback: use suggestion provider of argument
|
||||||
commandContext.setCurrentArgument(child.getValue());
|
return this.directSuggestions(commandContext, child, commandQueue.peek());
|
||||||
return child.getValue().getSuggestionsProvider().apply(commandContext, this.stringOrEmpty(commandQueue.peek()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private @NonNull String stringOrEmpty(final @Nullable String string) {
|
private @NonNull String stringOrEmpty(final @Nullable String string) {
|
||||||
|
|
@ -702,6 +700,31 @@ public final class CommandTree<C> {
|
||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private @NonNull List<@NonNull String> directSuggestions(
|
||||||
|
final @NonNull CommandContext<C> commandContext,
|
||||||
|
final @NonNull Node<@NonNull CommandArgument<C, ?>> current,
|
||||||
|
final @NonNull String text) {
|
||||||
|
CommandArgument<C, ?> argument = Objects.requireNonNull(current.getValue());
|
||||||
|
|
||||||
|
commandContext.setCurrentArgument(argument);
|
||||||
|
List<String> suggestions = argument.getSuggestionsProvider().apply(commandContext, text);
|
||||||
|
|
||||||
|
// When suggesting a flag, potentially suggest following nodes too
|
||||||
|
if (argument instanceof FlagArgument
|
||||||
|
&& !current.getChildren().isEmpty() // Has children
|
||||||
|
&& !text.startsWith("-") // Not a flag
|
||||||
|
&& !commandContext.getOptional(FlagArgument.FLAG_META_KEY).isPresent()) {
|
||||||
|
suggestions = new ArrayList<>(suggestions);
|
||||||
|
for (final Node<CommandArgument<C, ?>> child : current.getChildren()) {
|
||||||
|
argument = Objects.requireNonNull(child.getValue());
|
||||||
|
commandContext.setCurrentArgument(argument);
|
||||||
|
suggestions.addAll(argument.getSuggestionsProvider().apply(commandContext, text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggestions;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert a new command into the command tree
|
* Insert a new command into the command tree
|
||||||
*
|
*
|
||||||
|
|
@ -711,7 +734,15 @@ public final class CommandTree<C> {
|
||||||
public void insertCommand(final @NonNull Command<C> command) {
|
public void insertCommand(final @NonNull Command<C> command) {
|
||||||
synchronized (this.commandLock) {
|
synchronized (this.commandLock) {
|
||||||
Node<CommandArgument<C, ?>> node = this.internalTree;
|
Node<CommandArgument<C, ?>> node = this.internalTree;
|
||||||
for (final CommandArgument<C, ?> argument : command.getArguments()) {
|
FlagArgument<C> flags = command.flagArgument();
|
||||||
|
|
||||||
|
List<CommandArgument<C, ?>> nonFlagArguments = command.nonFlagArguments();
|
||||||
|
|
||||||
|
int flagStartIdx = this.flagStartIndex(nonFlagArguments, flags);
|
||||||
|
|
||||||
|
for (int i = 0; i < nonFlagArguments.size(); i++) {
|
||||||
|
final CommandArgument<C, ?> argument = nonFlagArguments.get(i);
|
||||||
|
|
||||||
Node<CommandArgument<C, ?>> tempNode = node.getChild(argument);
|
Node<CommandArgument<C, ?>> tempNode = node.getChild(argument);
|
||||||
if (tempNode == null) {
|
if (tempNode == null) {
|
||||||
tempNode = node.addChild(argument);
|
tempNode = node.addChild(argument);
|
||||||
|
|
@ -725,7 +756,12 @@ public final class CommandTree<C> {
|
||||||
}
|
}
|
||||||
tempNode.setParent(node);
|
tempNode.setParent(node);
|
||||||
node = tempNode;
|
node = tempNode;
|
||||||
|
|
||||||
|
if (i >= flagStartIdx) {
|
||||||
|
node = node.addChild(flags);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (node.getValue() != null) {
|
if (node.getValue() != null) {
|
||||||
if (node.getValue().getOwningCommand() != null) {
|
if (node.getValue().getOwningCommand() != null) {
|
||||||
throw new IllegalStateException(String.format(
|
throw new IllegalStateException(String.format(
|
||||||
|
|
@ -740,6 +776,25 @@ public final class CommandTree<C> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int flagStartIndex(final @NonNull List<CommandArgument<C, ?>> arguments, final @Nullable FlagArgument<C> flags) {
|
||||||
|
// Do not append flags
|
||||||
|
if (flags == null) {
|
||||||
|
return Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append flags after the last static argument
|
||||||
|
if (this.commandManager.getSetting(CommandManager.ManagerSettings.LIBERAL_FLAG_PARSING)) {
|
||||||
|
for (int i = arguments.size() - 1; i >= 0; i--) {
|
||||||
|
if (arguments.get(i) instanceof StaticArgument) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append flags after the last argument
|
||||||
|
return arguments.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
private @Nullable CommandPermission isPermitted(
|
private @Nullable CommandPermission isPermitted(
|
||||||
final @NonNull C sender,
|
final @NonNull C sender,
|
||||||
final @NonNull Node<@Nullable CommandArgument<C, ?>> node
|
final @NonNull Node<@Nullable CommandArgument<C, ?>> node
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,14 @@ public final class FlagArgument<C> extends CommandArgument<C, Object> {
|
||||||
*/
|
*/
|
||||||
public static final CloudKey<String> FLAG_META_KEY = SimpleCloudKey.of("__last_flag__", TypeToken.get(String.class));
|
public static final CloudKey<String> FLAG_META_KEY = SimpleCloudKey.of("__last_flag__", TypeToken.get(String.class));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Meta data for the set of parsed flags, used to detect duplicates.
|
||||||
|
* @since 1.8.0
|
||||||
|
*/
|
||||||
|
@API(status = API.Status.EXPERIMENTAL, since = "1.8.0")
|
||||||
|
public static final CloudKey<Set<CommandFlag<?>>> PARSED_FLAGS = SimpleCloudKey.of("__parsed_flags__",
|
||||||
|
new TypeToken<Set<CommandFlag<?>>>(){});
|
||||||
|
|
||||||
private static final String FLAG_ARGUMENT_NAME = "flags";
|
private static final String FLAG_ARGUMENT_NAME = "flags";
|
||||||
|
|
||||||
private final Collection<@NonNull CommandFlag<?>> flags;
|
private final Collection<@NonNull CommandFlag<?>> flags;
|
||||||
|
|
@ -157,16 +165,11 @@ public final class FlagArgument<C> extends CommandArgument<C, Object> {
|
||||||
parser.parse(commandContext, inputQueue);
|
parser.parse(commandContext, inputQueue);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Remove all but the last element from the command input queue
|
|
||||||
* If the parser parsed the entire queue, restore the last typed
|
* If the parser parsed the entire queue, restore the last typed
|
||||||
* input obtained earlier.
|
* input obtained earlier.
|
||||||
*/
|
*/
|
||||||
if (inputQueue.isEmpty()) {
|
if (inputQueue.isEmpty()) {
|
||||||
inputQueue.add(lastInputValue);
|
inputQueue.add(lastInputValue);
|
||||||
} else {
|
|
||||||
while (inputQueue.size() > 1) {
|
|
||||||
inputQueue.remove();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -309,11 +312,8 @@ public final class FlagArgument<C> extends CommandArgument<C, Object> {
|
||||||
final @NonNull CommandContext<@NonNull C> commandContext,
|
final @NonNull CommandContext<@NonNull C> commandContext,
|
||||||
final @NonNull Queue<@NonNull String> inputQueue
|
final @NonNull Queue<@NonNull String> inputQueue
|
||||||
) {
|
) {
|
||||||
/*
|
Set<CommandFlag<?>> parsedFlags = commandContext.computeIfAbsent(PARSED_FLAGS, k -> new HashSet());
|
||||||
This argument must necessarily be the last so we can just consume all remaining input. This argument type
|
|
||||||
is similar to a greedy string in that sense. But, we need to keep all flag logic contained to the parser
|
|
||||||
*/
|
|
||||||
final Set<CommandFlag<?>> parsedFlags = new HashSet<>();
|
|
||||||
CommandFlag<?> currentFlag = null;
|
CommandFlag<?> currentFlag = null;
|
||||||
String currentFlagName = null;
|
String currentFlagName = null;
|
||||||
|
|
||||||
|
|
@ -323,8 +323,12 @@ public final class FlagArgument<C> extends CommandArgument<C, Object> {
|
||||||
this.currentFlagBeingParsed = Optional.empty();
|
this.currentFlagBeingParsed = Optional.empty();
|
||||||
this.currentFlagNameBeingParsed = Optional.empty();
|
this.currentFlagNameBeingParsed = Optional.empty();
|
||||||
|
|
||||||
|
if (!string.startsWith("-") && currentFlag == null) {
|
||||||
|
/* Not flag waiting to be parsed */
|
||||||
|
return ArgumentParseResult.success(FLAG_PARSE_RESULT_OBJECT);
|
||||||
|
} else if (currentFlag == null) {
|
||||||
/* Parse next flag name to set */
|
/* Parse next flag name to set */
|
||||||
if (string.startsWith("-") && currentFlag == null) {
|
|
||||||
/* Remove flag argument from input queue */
|
/* Remove flag argument from input queue */
|
||||||
inputQueue.poll();
|
inputQueue.poll();
|
||||||
|
|
||||||
|
|
@ -417,13 +421,6 @@ public final class FlagArgument<C> extends CommandArgument<C, Object> {
|
||||||
/* We don't want to parse a value for this flag */
|
/* We don't want to parse a value for this flag */
|
||||||
currentFlag = null;
|
currentFlag = null;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (currentFlag == null) {
|
|
||||||
return ArgumentParseResult.failure(new FlagParseException(
|
|
||||||
string,
|
|
||||||
FailureReason.NO_FLAG_STARTED,
|
|
||||||
commandContext
|
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
/* Mark this flag as the one currently being typed */
|
/* Mark this flag as the one currently being typed */
|
||||||
this.currentFlagBeingParsed = Optional.of(currentFlag);
|
this.currentFlagBeingParsed = Optional.of(currentFlag);
|
||||||
|
|
@ -457,7 +454,6 @@ public final class FlagArgument<C> extends CommandArgument<C, Object> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Queue ran out while a flag argument needs to be parsed still */
|
/* Queue ran out while a flag argument needs to be parsed still */
|
||||||
if (currentFlag != null) {
|
if (currentFlag != null) {
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import org.apiguardian.api.API;
|
import org.apiguardian.api.API;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
|
@ -495,9 +496,9 @@ public class CommandContext<C> {
|
||||||
/**
|
/**
|
||||||
* Get a value if it exists, else return the provided default value
|
* Get a value if it exists, else return the provided default value
|
||||||
*
|
*
|
||||||
* @param key Argument key
|
* @param key Cloud key
|
||||||
* @param defaultValue Default value
|
* @param defaultValue Default value
|
||||||
* @param <T> Argument type
|
* @param <T> Value type
|
||||||
* @return Argument, or supplied default value
|
* @return Argument, or supplied default value
|
||||||
*/
|
*/
|
||||||
public <T> T getOrDefault(
|
public <T> T getOrDefault(
|
||||||
|
|
@ -510,9 +511,9 @@ public class CommandContext<C> {
|
||||||
/**
|
/**
|
||||||
* Get a value if it exists, else return the provided default value
|
* Get a value if it exists, else return the provided default value
|
||||||
*
|
*
|
||||||
* @param key Argument key
|
* @param key Cloud key
|
||||||
* @param defaultValue Default value
|
* @param defaultValue Default value
|
||||||
* @param <T> Argument type
|
* @param <T> Value type
|
||||||
* @return Argument, or supplied default value
|
* @return Argument, or supplied default value
|
||||||
* @since 1.4.0
|
* @since 1.4.0
|
||||||
*/
|
*/
|
||||||
|
|
@ -527,9 +528,9 @@ public class CommandContext<C> {
|
||||||
/**
|
/**
|
||||||
* Get a value if it exists, else return the value supplied by the given supplier
|
* Get a value if it exists, else return the value supplied by the given supplier
|
||||||
*
|
*
|
||||||
* @param key Argument key
|
* @param key Cloud key
|
||||||
* @param defaultSupplier Supplier of default value
|
* @param defaultSupplier Supplier of default value
|
||||||
* @param <T> Argument type
|
* @param <T> Value type
|
||||||
* @return Argument, or supplied default value
|
* @return Argument, or supplied default value
|
||||||
* @since 1.2.0
|
* @since 1.2.0
|
||||||
*/
|
*/
|
||||||
|
|
@ -544,9 +545,9 @@ public class CommandContext<C> {
|
||||||
/**
|
/**
|
||||||
* Get a value if it exists, else return the value supplied by the given supplier
|
* Get a value if it exists, else return the value supplied by the given supplier
|
||||||
*
|
*
|
||||||
* @param key Argument key
|
* @param key Cloud key
|
||||||
* @param defaultSupplier Supplier of default value
|
* @param defaultSupplier Supplier of default value
|
||||||
* @param <T> Argument type
|
* @param <T> Value type
|
||||||
* @return Argument, or supplied default value
|
* @return Argument, or supplied default value
|
||||||
* @since 1.4.0
|
* @since 1.4.0
|
||||||
*/
|
*/
|
||||||
|
|
@ -558,6 +559,25 @@ public class CommandContext<C> {
|
||||||
return this.getOptional(key).orElseGet(defaultSupplier);
|
return this.getOptional(key).orElseGet(defaultSupplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a value if it exists, else compute and store the value returned by the function and return it.
|
||||||
|
*
|
||||||
|
* @param key Cloud key
|
||||||
|
* @param defaultFunction Default value function
|
||||||
|
* @param <T> Value type
|
||||||
|
* @return present or computed value
|
||||||
|
* @since 1.8.0
|
||||||
|
*/
|
||||||
|
@API(status = API.Status.STABLE, since = "1.8.0")
|
||||||
|
public <T> T computeIfAbsent(
|
||||||
|
final @NonNull CloudKey<T> key,
|
||||||
|
final @NonNull Function<CloudKey<T>, T> defaultFunction
|
||||||
|
) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final T castedValue = (T) this.internalStorage.computeIfAbsent(key, k -> defaultFunction.apply((CloudKey<T>) k));
|
||||||
|
return castedValue;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the raw input.
|
* Get the raw input.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -462,30 +462,12 @@ public class CommandSuggestionsTest {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
final List<String> suggestions1 = manager.suggest(
|
final List<String> suggestions1 = suggest(manager, "command ");
|
||||||
new TestCommandSender(),
|
final List<String> suggestions2 = suggest(manager, "command hel");
|
||||||
"command "
|
final List<String> suggestions3 = suggest(manager, "command hello --");
|
||||||
);
|
final List<String> suggestions4 = suggest(manager, "command hello --f");
|
||||||
final List<String> suggestions2 = manager.suggest(
|
final List<String> suggestions5 = suggest(manager, "command hello -f");
|
||||||
new TestCommandSender(),
|
final List<String> suggestions6 = suggest(manager, "command hello -");
|
||||||
"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
|
// Assert
|
||||||
assertThat(suggestions1).containsExactly("hello");
|
assertThat(suggestions1).containsExactly("hello");
|
||||||
|
|
@ -513,30 +495,12 @@ public class CommandSuggestionsTest {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
final List<String> suggestions1 = manager.suggest(
|
final List<String> suggestions1 = suggest(manager, "command ");
|
||||||
new TestCommandSender(),
|
final List<String> suggestions2 = suggest(manager, "command hello");
|
||||||
"command "
|
final List<String> suggestions3 = suggest(manager, "command hello --");
|
||||||
);
|
final List<String> suggestions4 = suggest(manager, "command hello --f");
|
||||||
final List<String> suggestions2 = manager.suggest(
|
final List<String> suggestions5 = suggest(manager, "command hello -f");
|
||||||
new TestCommandSender(),
|
final List<String> suggestions6 = suggest(manager, "command hello -");
|
||||||
"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
|
// Assert
|
||||||
assertThat(suggestions1).isEmpty();
|
assertThat(suggestions1).isEmpty();
|
||||||
|
|
@ -547,6 +511,78 @@ public class CommandSuggestionsTest {
|
||||||
assertThat(suggestions6).isEmpty();
|
assertThat(suggestions6).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFlagYieldingGreedyStringWithLiberalFlagArgument() {
|
||||||
|
// Arrange
|
||||||
|
final CommandManager<TestCommandSender> manager = createManager();
|
||||||
|
manager.setSetting(CommandManager.ManagerSettings.LIBERAL_FLAG_PARSING, true);
|
||||||
|
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 = suggest(manager, "command ");
|
||||||
|
final List<String> suggestions2 = suggest(manager, "command hel");
|
||||||
|
final List<String> suggestions3 = suggest(manager, "command hello --");
|
||||||
|
final List<String> suggestions4 = suggest(manager, "command hello --f");
|
||||||
|
final List<String> suggestions5 = suggest(manager, "command hello -f");
|
||||||
|
final List<String> suggestions6 = suggest(manager, "command hello -");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(suggestions1).containsExactly("hello", "--flag", "--flag2", "-f");
|
||||||
|
assertThat(suggestions2).containsExactly("hello");
|
||||||
|
assertThat(suggestions3).containsExactly("--flag", "--flag2");
|
||||||
|
assertThat(suggestions4).containsExactly("--flag", "--flag2");
|
||||||
|
assertThat(suggestions5).containsExactly("-f");
|
||||||
|
assertThat(suggestions6).containsExactly("hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFlagYieldingStringArrayWithLiberalFlagArgument() {
|
||||||
|
// Arrange
|
||||||
|
final CommandManager<TestCommandSender> manager = createManager();
|
||||||
|
manager.setSetting(CommandManager.ManagerSettings.LIBERAL_FLAG_PARSING, true);
|
||||||
|
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 = suggest(manager, "command ");
|
||||||
|
final List<String> suggestions2 = suggest(manager, "command hello");
|
||||||
|
final List<String> suggestions3 = suggest(manager, "command hello --");
|
||||||
|
final List<String> suggestions4 = suggest(manager, "command hello --f");
|
||||||
|
final List<String> suggestions5 = suggest(manager, "command hello -f");
|
||||||
|
final List<String> suggestions6 = suggest(manager, "command hello -");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(suggestions1).containsExactly("--flag", "--flag2", "-f");
|
||||||
|
assertThat(suggestions2).isEmpty();
|
||||||
|
assertThat(suggestions3).containsExactly("--flag", "--flag2");
|
||||||
|
assertThat(suggestions4).containsExactly("--flag", "--flag2");
|
||||||
|
assertThat(suggestions5).containsExactly("-f");
|
||||||
|
assertThat(suggestions6).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> suggest(CommandManager<TestCommandSender> manager, String command) {
|
||||||
|
return manager.suggest(new TestCommandSender(), command);
|
||||||
|
}
|
||||||
|
|
||||||
public enum TestEnum {
|
public enum TestEnum {
|
||||||
FOO,
|
FOO,
|
||||||
BAR
|
BAR
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
//
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 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.compound.FlagArgument;
|
||||||
|
import cloud.commandframework.arguments.standard.StringArgument;
|
||||||
|
import cloud.commandframework.exceptions.ArgumentParseException;
|
||||||
|
import cloud.commandframework.execution.CommandResult;
|
||||||
|
import com.google.common.truth.ThrowableSubject;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.function.Executable;
|
||||||
|
|
||||||
|
import static cloud.commandframework.util.TestUtils.createManager;
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
class ArbitraryPositionFlagTest {
|
||||||
|
|
||||||
|
private CommandManager<TestCommandSender> commandManager;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setup() {
|
||||||
|
this.commandManager = createManager();
|
||||||
|
this.commandManager.setSetting(CommandManager.ManagerSettings.LIBERAL_FLAG_PARSING, true);
|
||||||
|
|
||||||
|
this.commandManager.command(
|
||||||
|
this.commandManager.commandBuilder("test")
|
||||||
|
.literal("literal")
|
||||||
|
.argument(StringArgument.greedyFlagYielding("text"))
|
||||||
|
.flag(this.commandManager.flagBuilder("flag").withAliases("f")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testParsingAllLocations() {
|
||||||
|
List<String> passing = Arrays.asList(
|
||||||
|
"test literal -f foo bar",
|
||||||
|
"test literal foo bar -f",
|
||||||
|
"test literal --flag foo bar",
|
||||||
|
"test literal foo bar --flag");
|
||||||
|
|
||||||
|
for (String cmd : passing) {
|
||||||
|
CommandResult<TestCommandSender> result = this.commandManager.executeCommand(new TestCommandSender(), cmd).join();
|
||||||
|
assertThat(result.getCommandContext().flags().isPresent("flag")).isEqualTo(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFailBeforeLiterals() {
|
||||||
|
List<String> failing = Arrays.asList(
|
||||||
|
"test -f literal foo bar",
|
||||||
|
"test --flag literal foo bar");
|
||||||
|
|
||||||
|
for (String cmd : failing) {
|
||||||
|
assertThrows(CompletionException.class, commandExecutable(cmd));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMultiFlagThrows() {
|
||||||
|
final CompletionException completionException = assertThrows(CompletionException.class,
|
||||||
|
commandExecutable("test literal -f foo bar -f"));
|
||||||
|
|
||||||
|
|
||||||
|
ThrowableSubject argParse = assertThat(completionException).hasCauseThat();
|
||||||
|
argParse.isInstanceOf(ArgumentParseException.class);
|
||||||
|
argParse.hasCauseThat().isInstanceOf(FlagArgument.FlagParseException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Executable commandExecutable(String cmd) {
|
||||||
|
return () -> this.commandManager.executeCommand(new TestCommandSender(), cmd).join();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
package cloud.commandframework.fabric.testmod;
|
package cloud.commandframework.fabric.testmod;
|
||||||
|
|
||||||
import cloud.commandframework.Command;
|
import cloud.commandframework.Command;
|
||||||
|
import cloud.commandframework.arguments.flags.CommandFlag;
|
||||||
import cloud.commandframework.arguments.standard.StringArgument;
|
import cloud.commandframework.arguments.standard.StringArgument;
|
||||||
import cloud.commandframework.execution.CommandExecutionCoordinator;
|
import cloud.commandframework.execution.CommandExecutionCoordinator;
|
||||||
import cloud.commandframework.fabric.FabricClientCommandManager;
|
import cloud.commandframework.fabric.FabricClientCommandManager;
|
||||||
|
|
@ -125,6 +126,11 @@ public final class FabricClientExample implements ClientModInitializer {
|
||||||
ctx.getSender().sendError(ComponentUtils.fromMessage(ex.getRawMessage()));
|
ctx.getSender().sendError(ComponentUtils.fromMessage(ex.getRawMessage()));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
commandManager.command(base.literal("flag_test")
|
||||||
|
.argument(StringArgument.optional("parameter"))
|
||||||
|
.flag(CommandFlag.newBuilder("flag").withAliases("f"))
|
||||||
|
.handler(ctx -> ctx.getSender().sendFeedback(Component.literal("Had flag: " + ctx.flags().isPresent("flag")))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void disconnectClient(final @NonNull Minecraft client) {
|
private static void disconnectClient(final @NonNull Minecraft client) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue