From 1f3c3f2bd94cd5089ee2fff49953a551e234897e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derberg?= Date: Sat, 10 Oct 2020 01:24:16 +0200 Subject: [PATCH] :sparkles: Add command argument preprocessors --- .../annotations/AnnotationParser.java | 41 +++++- .../commandframework/annotations/Regex.java | 49 +++++++ .../cloud/commandframework/CommandTree.java | 27 +++- .../arguments/CommandArgument.java | 111 +++++++++++++-- .../preprocessor/RegexPreprocessor.java | 127 ++++++++++++++++++ .../arguments/preprocessor/package-info.java | 28 ++++ .../arguments/standard/BooleanArgument.java | 8 +- .../commandframework/CommandTreeTest.java | 18 +++ .../examples/bukkit/ExamplePlugin.java | 13 ++ 9 files changed, 405 insertions(+), 17 deletions(-) create mode 100644 cloud-annotations/src/main/java/cloud/commandframework/annotations/Regex.java create mode 100644 cloud-core/src/main/java/cloud/commandframework/arguments/preprocessor/RegexPreprocessor.java create mode 100644 cloud-core/src/main/java/cloud/commandframework/arguments/preprocessor/package-info.java diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java index 9f6888d4..9bec82b4 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java @@ -28,10 +28,13 @@ import cloud.commandframework.CommandManager; import cloud.commandframework.Description; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.flags.CommandFlag; +import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.parser.ArgumentParser; import cloud.commandframework.arguments.parser.ParserParameter; import cloud.commandframework.arguments.parser.ParserParameters; import cloud.commandframework.arguments.parser.StandardParameters; +import cloud.commandframework.arguments.preprocessor.RegexPreprocessor; +import cloud.commandframework.context.CommandContext; import cloud.commandframework.execution.CommandExecutionHandler; import cloud.commandframework.extra.confirmation.CommandConfirmationManager; import cloud.commandframework.meta.CommandMeta; @@ -49,6 +52,8 @@ import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Queue; +import java.util.function.BiFunction; import java.util.function.Function; /** @@ -63,6 +68,8 @@ public final class AnnotationParser { private final CommandManager manager; private final Map, Function> annotationMappers; + private final Map, Function, + @NonNull Queue<@NonNull String>, @NonNull ArgumentParseResult>>> preprocessorMappers; private final Class commandSenderClass; private final MetaFactory metaFactory; private final FlagExtractor flagExtractor; @@ -86,9 +93,11 @@ public final class AnnotationParser { this.manager = manager; this.metaFactory = new MetaFactory(this, metaMapper); this.annotationMappers = new HashMap<>(); + this.preprocessorMappers = new HashMap<>(); this.flagExtractor = new FlagExtractor(manager); this.registerAnnotationMapper(CommandDescription.class, d -> ParserParameters.single(StandardParameters.DESCRIPTION, d.value())); + this.registerPreprocessorMapper(Regex.class, annotation -> RegexPreprocessor.of(annotation.value())); } /** @@ -106,6 +115,21 @@ public final class AnnotationParser { this.annotationMappers.put(annotation, mapper); } + /** + * Register a preprocessor mapper + * + * @param annotation Annotation class + * @param preprocessorMapper Preprocessor mapper + * @param Annotation type + */ + public void registerPreprocessorMapper( + final @NonNull Class annotation, + final @NonNull Function, @NonNull Queue<@NonNull String>, + @NonNull ArgumentParseResult>> preprocessorMapper + ) { + this.preprocessorMappers.put(annotation, preprocessorMapper); + } + /** * Scan a class instance of {@link CommandMethod} annotations and attempt to * compile them into {@link Command} instances @@ -315,7 +339,22 @@ public final class AnnotationParser { } else { argumentBuilder.asRequired(); } - return argumentBuilder.manager(this.manager).withParser(parser).build(); + + final CommandArgument builtArgument = argumentBuilder.manager(this.manager).withParser(parser).build(); + + /* Add preprocessors */ + for (final Annotation annotation : annotations) { + @SuppressWarnings("ALL") final Function preprocessorMapper = + this.preprocessorMappers.get(annotation.annotationType()); + if (preprocessorMapper != null) { + final BiFunction<@NonNull CommandContext, @NonNull Queue<@NonNull String>, + @NonNull ArgumentParseResult> preprocessor = (BiFunction, + Queue, ArgumentParseResult>) preprocessorMapper.apply(annotation); + builtArgument.addPreprocessor(preprocessor); + } + } + + return builtArgument; } @NonNull Map<@NonNull Class<@NonNull ? extends Annotation>, diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/Regex.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/Regex.java new file mode 100644 index 00000000..45e31b35 --- /dev/null +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/Regex.java @@ -0,0 +1,49 @@ +// +// MIT License +// +// Copyright (c) 2020 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; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.function.BiFunction; + +/** + * Annotation version of adding {@link cloud.commandframework.arguments.preprocessor.RegexPreprocessor} + * as a preprocessor using {@link cloud.commandframework.arguments.CommandArgument#addPreprocessor(BiFunction)} + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface Regex { + + /** + * Regular expression pattern + * + * @return Pattern + */ + @NonNull String value(); + +} diff --git a/cloud-core/src/main/java/cloud/commandframework/CommandTree.java b/cloud-core/src/main/java/cloud/commandframework/CommandTree.java index 7c81a9a6..070768af 100644 --- a/cloud-core/src/main/java/cloud/commandframework/CommandTree.java +++ b/cloud-core/src/main/java/cloud/commandframework/CommandTree.java @@ -357,9 +357,20 @@ public final class CommandTree { final CommandArgument argument = child.getValue(); final CommandContext.ArgumentTiming argumentTiming = commandContext.createTiming(argument); + // START: Parsing argumentTiming.setStart(System.nanoTime()); - final ArgumentParseResult result = argument.getParser().parse(commandContext, commandQueue); + final ArgumentParseResult result; + final ArgumentParseResult preParseResult = child.getValue().preprocess( + commandContext, + commandQueue + ); + if (!preParseResult.getFailure().isPresent() && preParseResult.getParsedValue().orElse(false)) { + result = argument.getParser().parse(commandContext, commandQueue); + } else { + result = preParseResult; + } argumentTiming.setEnd(System.nanoTime(), result.getFailure().isPresent()); + // END: Parsing if (result.getParsedValue().isPresent()) { commandContext.store(child.getValue().getName(), result.getParsedValue().get()); @@ -476,6 +487,19 @@ public final class CommandTree { } else if (commandQueue.peek().isEmpty()) { return child.getValue().getSuggestionsProvider().apply(commandContext, commandQueue.remove()); } + + // START: Preprocessing + final ArgumentParseResult preParseResult = child.getValue().preprocess( + commandContext, + commandQueue + ); + if (preParseResult.getFailure().isPresent() || !preParseResult.getParsedValue().orElse(false)) { + final String value = commandQueue.peek() == null ? "" : commandQueue.peek(); + return child.getValue().getSuggestionsProvider().apply(commandContext, value); + } + // END: Preprocessing + + // START: Parsing final ArgumentParseResult result = child.getValue().getParser().parse(commandContext, commandQueue); if (result.getParsedValue().isPresent()) { commandContext.store(child.getValue().getName(), result.getParsedValue().get()); @@ -484,6 +508,7 @@ public final class CommandTree { final String value = commandQueue.peek() == null ? "" : commandQueue.peek(); return child.getValue().getSuggestionsProvider().apply(commandContext, value); } + // END: Parsing } } /* There are 0 or more static arguments as children. No variable child arguments are present */ diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/CommandArgument.java b/cloud-core/src/main/java/cloud/commandframework/arguments/CommandArgument.java index 81238e27..69b07077 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/CommandArgument.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/CommandArgument.java @@ -33,8 +33,12 @@ import io.leangen.geantyref.TypeToken; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; import java.util.List; import java.util.Objects; +import java.util.Queue; import java.util.function.BiFunction; import java.util.regex.Pattern; @@ -82,6 +86,12 @@ public class CommandArgument implements Comparable> * Suggestion provider */ private final BiFunction, String, List> suggestionsProvider; + /** + * Argument preprocessors that allows for extensions to existing argument types + * without having to update all parsers + */ + private final Collection, + @NonNull Queue<@NonNull String>, @NonNull ArgumentParseResult>> argumentPreprocessors; /** * Whether or not the argument has been used before */ @@ -89,6 +99,41 @@ public class CommandArgument implements Comparable> private Command owningCommand; + /** + * Construct a new command argument + * + * @param required Whether or not the argument is required + * @param name The argument name + * @param parser The argument parser + * @param defaultValue Default value used when no value is provided by the command sender + * @param valueType Type produced by the parser + * @param suggestionsProvider Suggestions provider + * @param argumentPreprocessors Argument preprocessors + */ + public CommandArgument( + final boolean required, + final @NonNull String name, + final @NonNull ArgumentParser parser, + final @NonNull String defaultValue, + final @NonNull TypeToken valueType, + final @Nullable BiFunction, String, List> suggestionsProvider, + final @NonNull Collection<@NonNull BiFunction<@NonNull CommandContext, @NonNull Queue<@NonNull String>, + @NonNull ArgumentParseResult>> argumentPreprocessors + ) { + this.required = required; + this.name = Objects.requireNonNull(name, "Name may not be null"); + if (!NAME_PATTERN.asPredicate().test(name)) { + throw new IllegalArgumentException("Name must be alphanumeric"); + } + this.parser = Objects.requireNonNull(parser, "Parser may not be null"); + this.defaultValue = defaultValue; + this.valueType = valueType; + this.suggestionsProvider = suggestionsProvider == null + ? buildDefaultSuggestionsProvider(this) + : suggestionsProvider; + this.argumentPreprocessors = new LinkedList<>(argumentPreprocessors); + } + /** * Construct a new command argument * @@ -107,17 +152,7 @@ public class CommandArgument implements Comparable> final @NonNull TypeToken valueType, final @Nullable BiFunction, String, List> suggestionsProvider ) { - this.required = required; - this.name = Objects.requireNonNull(name, "Name may not be null"); - if (!NAME_PATTERN.asPredicate().test(name)) { - throw new IllegalArgumentException("Name must be alphanumeric"); - } - this.parser = Objects.requireNonNull(parser, "Parser may not be null"); - this.defaultValue = defaultValue; - this.valueType = valueType; - this.suggestionsProvider = suggestionsProvider == null - ? buildDefaultSuggestionsProvider(this) - : suggestionsProvider; + this(required, name, parser, defaultValue, valueType, suggestionsProvider, Collections.emptyList()); } /** @@ -229,6 +264,48 @@ public class CommandArgument implements Comparable> return String.format("%s{name=%s}", this.getClass().getSimpleName(), this.name); } + /** + * Register a new preprocessor. If all preprocessor has succeeding {@link ArgumentParseResult results} + * that all return {@code true}, the argument will be passed onto the parser. + *

+ * It is important that the preprocessor doesn't pop any input. Instead, it should only peek. + * + * @param preprocessor Preprocessor + * @return {@code this} + */ + public @NonNull CommandArgument addPreprocessor( + final @NonNull BiFunction<@NonNull CommandContext, @NonNull Queue, + @NonNull ArgumentParseResult> preprocessor + ) { + this.argumentPreprocessors.add(preprocessor); + return this; + } + + /** + * Preprocess command input. This will immediately forward any failed argument parse results. + * If none fails, a {@code true} result will be returned + * + * @param context Command context + * @param input Remaining command input. None will be popped + * @return Parsing error, or argument containing {@code true} + */ + public @NonNull ArgumentParseResult preprocess( + final @NonNull CommandContext context, + final @NonNull Queue input + ) { + for (final BiFunction<@NonNull CommandContext, @NonNull Queue, + @NonNull ArgumentParseResult> preprocessor : this.argumentPreprocessors) { + final ArgumentParseResult result = preprocessor.apply( + context, + input + ); + if (result.getFailure().isPresent()) { + return result; + } + } + return ArgumentParseResult.success(true); + } + /** * Get the owning command * @@ -376,6 +453,9 @@ public class CommandArgument implements Comparable> private String defaultValue = ""; private BiFunction<@NonNull CommandContext, @NonNull String, @NonNull List> suggestionsProvider; + private final Collection, + @NonNull String, @NonNull ArgumentParseResult>> argumentPreprocessors = new LinkedList<>(); + protected Builder( final @NonNull TypeToken valueType, final @NonNull String name @@ -489,8 +569,13 @@ public class CommandArgument implements Comparable> if (this.suggestionsProvider == null) { this.suggestionsProvider = new DelegatingSuggestionsProvider<>(this.name, this.parser); } - return new CommandArgument<>(this.required, this.name, this.parser, - this.defaultValue, this.valueType, this.suggestionsProvider + return new CommandArgument<>( + this.required, + this.name, + this.parser, + this.defaultValue, + this.valueType, + this.suggestionsProvider ); } diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/preprocessor/RegexPreprocessor.java b/cloud-core/src/main/java/cloud/commandframework/arguments/preprocessor/RegexPreprocessor.java new file mode 100644 index 00000000..b64ee4f8 --- /dev/null +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/preprocessor/RegexPreprocessor.java @@ -0,0 +1,127 @@ +// +// MIT License +// +// Copyright (c) 2020 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.preprocessor; + +import cloud.commandframework.arguments.parser.ArgumentParseResult; +import cloud.commandframework.context.CommandContext; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Queue; +import java.util.function.BiFunction; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +/** + * Command preprocessor that filters based on regular expressions + * + * @param Command sender type + */ +public final class RegexPreprocessor implements BiFunction<@NonNull CommandContext, @NonNull Queue<@NonNull String>, + @NonNull ArgumentParseResult> { + + private final String rawPattern; + private final Predicate<@NonNull String> predicate; + + private RegexPreprocessor(final @NonNull String pattern) { + this.rawPattern = pattern; + this.predicate = Pattern.compile(pattern).asPredicate(); + } + + /** + * Create a new preprocessor + * + * @param pattern Regular expression + * @param Command sender type + * @return Preprocessor instance + */ + public static @NonNull RegexPreprocessor of(final @NonNull String pattern) { + return new RegexPreprocessor<>(pattern); + } + + @Override + public @NonNull ArgumentParseResult apply( + @NonNull final CommandContext context, @NonNull final Queue<@NonNull String> strings + ) { + final String head = strings.peek(); + if (head == null) { + throw new NullPointerException("No input"); + } + if (predicate.test(head)) { + return ArgumentParseResult.success(true); + } + return ArgumentParseResult.failure( + new RegexValidationException( + this.rawPattern, + head + ) + ); + } + + + /** + * Exception thrown when input fails regex matching in {@link RegexPreprocessor} + */ + public static final class RegexValidationException extends IllegalArgumentException { + + private final String pattern; + private final String failedString; + + private RegexValidationException( + @NonNull final String pattern, + @NonNull final String failedString + ) { + this.pattern = pattern; + this.failedString = failedString; + } + + @Override + public String getMessage() { + return String.format( + "Input '%s' does not match the required pattern '%s'", + failedString, + pattern + ); + } + + /** + * Get the string that failed the verification + * + * @return Failed string + */ + public @NonNull String getFailedString() { + return this.failedString; + } + + /** + * Get the pattern that caused the string to fail + * + * @return Pattern + */ + public @NonNull String getPattern() { + return this.pattern; + } + + } + +} diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/preprocessor/package-info.java b/cloud-core/src/main/java/cloud/commandframework/arguments/preprocessor/package-info.java new file mode 100644 index 00000000..f8fa282b --- /dev/null +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/preprocessor/package-info.java @@ -0,0 +1,28 @@ +// +// MIT License +// +// Copyright (c) 2020 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. +// + +/** + * Pre-made argument preprocessors + */ +package cloud.commandframework.arguments.preprocessor; diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/BooleanArgument.java b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/BooleanArgument.java index 95596d4a..e0ffdb9c 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/BooleanArgument.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/BooleanArgument.java @@ -135,8 +135,12 @@ public final class BooleanArgument extends CommandArgument { */ @Override public @NonNull BooleanArgument build() { - return new BooleanArgument<>(this.isRequired(), this.getName(), this.liberal, - this.getDefaultValue(), this.getSuggestionsProvider() + return new BooleanArgument<>( + this.isRequired(), + this.getName(), + this.liberal, + this.getDefaultValue(), + this.getSuggestionsProvider() ); } diff --git a/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java b/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java index 3a8ec332..bc5a29ad 100644 --- a/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java @@ -25,6 +25,7 @@ package cloud.commandframework; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.compound.ArgumentPair; +import cloud.commandframework.arguments.preprocessor.RegexPreprocessor; import cloud.commandframework.arguments.standard.EnumArgument; import cloud.commandframework.arguments.standard.FloatArgument; import cloud.commandframework.arguments.standard.IntegerArgument; @@ -134,6 +135,14 @@ class CommandTreeTest { .handler(c -> { System.out.printf("%f\n", c.get("num")); })); + + /* Build command for testing preprocessing */ + manager.command(manager.commandBuilder("preprocess") + .argument( + StringArgument.of("argument") + .addPreprocessor(RegexPreprocessor.of("[A-Za-z]{3,5}")) + ) + ); } @Test @@ -252,6 +261,15 @@ class CommandTreeTest { manager.executeCommand(new TestCommandSender(), "float 100").join(); } + @Test + void testPreprocessors() { + manager.executeCommand(new TestCommandSender(), "preprocess abc").join(); + Assertions.assertThrows( + CompletionException.class, + () -> manager.executeCommand(new TestCommandSender(), "preprocess ab").join() + ); + } + public static final class SpecificCommandSender extends TestCommandSender { diff --git a/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java b/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java index ef870c0a..5dde5db1 100644 --- a/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java +++ b/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java @@ -35,6 +35,7 @@ import cloud.commandframework.annotations.CommandMethod; import cloud.commandframework.annotations.CommandPermission; import cloud.commandframework.annotations.Confirmation; import cloud.commandframework.annotations.Flag; +import cloud.commandframework.annotations.Regex; import cloud.commandframework.annotations.specifier.Greedy; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.parser.ArgumentParseResult; @@ -440,4 +441,16 @@ public final class ExamplePlugin extends JavaPlugin { player.sendMessage(ChatColor.GREEN + String.format("You have been given %d x %s", number, material)); } + @CommandMethod("example pay ") + @CommandDescription("Command to test the preprocessing system") + private void commandPay( + final @NonNull CommandSender sender, + final @Argument("money") @Regex("(?=.*?\\d)^\\$?(([1-9]\\d{0,2}(,\\d{3})*)|\\d+)?(\\.\\d{1,2})?$") String money + ) { + bukkitAudiences.sender(sender).sendMessage( + Component.text().append(Component.text("You have been given ", NamedTextColor.AQUA)) + .append(Component.text(money, NamedTextColor.GOLD)) + ); + } + }