✨ Add named suggestion providers
This allows for pre-registration of command suggestion providers, that can then be used in annotated command methods.
This commit is contained in:
parent
37e0b4e91b
commit
3c7bd63f07
6 changed files with 112 additions and 8 deletions
|
|
@ -52,7 +52,9 @@ import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
@ -300,7 +302,7 @@ public final class AnnotationParser<C> {
|
||||||
final TypeToken<?> token = TypeToken.get(parameter.getParameterizedType());
|
final TypeToken<?> token = TypeToken.get(parameter.getParameterizedType());
|
||||||
final ParserParameters parameters = this.manager.getParserRegistry()
|
final ParserParameters parameters = this.manager.getParserRegistry()
|
||||||
.parseAnnotations(token, annotations);
|
.parseAnnotations(token, annotations);
|
||||||
|
/* Create the argument parser */
|
||||||
final ArgumentParser<C, ?> parser;
|
final ArgumentParser<C, ?> parser;
|
||||||
if (argumentPair.getArgument().parserName().isEmpty()) {
|
if (argumentPair.getArgument().parserName().isEmpty()) {
|
||||||
parser = this.manager.getParserRegistry()
|
parser = this.manager.getParserRegistry()
|
||||||
|
|
@ -323,17 +325,19 @@ public final class AnnotationParser<C> {
|
||||||
token.toString()
|
token.toString()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
/* Check whether or not the corresponding method parameter actually exists */
|
||||||
if (syntaxFragment == null || syntaxFragment.getArgumentMode() == ArgumentMode.LITERAL) {
|
if (syntaxFragment == null || syntaxFragment.getArgumentMode() == ArgumentMode.LITERAL) {
|
||||||
throw new IllegalArgumentException(String.format(
|
throw new IllegalArgumentException(String.format(
|
||||||
"Invalid command argument '%s' in method '%s': "
|
"Invalid command argument '%s' in method '%s': "
|
||||||
+ "Missing syntax mapping", argumentPair.getArgument().value(), method.getName()));
|
+ "Missing syntax mapping", argumentPair.getArgument().value(), method.getName()));
|
||||||
}
|
}
|
||||||
final Argument argument = argumentPair.getArgument();
|
final Argument argument = argumentPair.getArgument();
|
||||||
|
/* Create the argument builder */
|
||||||
@SuppressWarnings("ALL") final CommandArgument.Builder argumentBuilder = CommandArgument.ofType(
|
@SuppressWarnings("ALL") final CommandArgument.Builder argumentBuilder = CommandArgument.ofType(
|
||||||
parameter.getType(),
|
parameter.getType(),
|
||||||
argument.value()
|
argument.value()
|
||||||
);
|
);
|
||||||
|
/* Set the argument requirement status */
|
||||||
if (syntaxFragment.getArgumentMode() == ArgumentMode.OPTIONAL) {
|
if (syntaxFragment.getArgumentMode() == ArgumentMode.OPTIONAL) {
|
||||||
if (argument.defaultValue().isEmpty()) {
|
if (argument.defaultValue().isEmpty()) {
|
||||||
argumentBuilder.asOptional();
|
argumentBuilder.asOptional();
|
||||||
|
|
@ -343,9 +347,21 @@ public final class AnnotationParser<C> {
|
||||||
} else {
|
} else {
|
||||||
argumentBuilder.asRequired();
|
argumentBuilder.asRequired();
|
||||||
}
|
}
|
||||||
|
/* Check whether or not a suggestion provider should be set */
|
||||||
|
if (!argument.suggestions().isEmpty()) {
|
||||||
|
final String suggestionProviderName = argument.suggestions();
|
||||||
|
final Optional<BiFunction<CommandContext<C>, String, List<String>>> suggestionsFunction =
|
||||||
|
this.manager.getParserRegistry().getSuggestionProvider(suggestionProviderName);
|
||||||
|
argumentBuilder.withSuggestionsProvider(
|
||||||
|
suggestionsFunction.orElseThrow(() ->
|
||||||
|
new IllegalArgumentException(String.format(
|
||||||
|
"There is no suggestion provider with name '%s'. Did you forget to register it?",
|
||||||
|
suggestionProviderName
|
||||||
|
)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/* Build the argument */
|
||||||
final CommandArgument<C, ?> builtArgument = argumentBuilder.manager(this.manager).withParser(parser).build();
|
final CommandArgument<C, ?> builtArgument = argumentBuilder.manager(this.manager).withParser(parser).build();
|
||||||
|
|
||||||
/* Add preprocessors */
|
/* Add preprocessors */
|
||||||
for (final Annotation annotation : annotations) {
|
for (final Annotation annotation : annotations) {
|
||||||
@SuppressWarnings("ALL") final Function preprocessorMapper =
|
@SuppressWarnings("ALL") final Function preprocessorMapper =
|
||||||
|
|
@ -357,7 +373,7 @@ public final class AnnotationParser<C> {
|
||||||
builtArgument.addPreprocessor(preprocessor);
|
builtArgument.addPreprocessor(preprocessor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* Yay, we're done */
|
||||||
return builtArgument;
|
return builtArgument;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation used to indicate that a method parameter is a command argument
|
* Annotation used to indicate that a method parameter is a command argument
|
||||||
|
|
@ -49,6 +50,22 @@ public @interface Argument {
|
||||||
*/
|
*/
|
||||||
String parserName() default "";
|
String parserName() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the suggestions provider to use. If the string is left empty, the default
|
||||||
|
* provider for the argument parser will be used. Otherwise,
|
||||||
|
* the {@link cloud.commandframework.arguments.parser.ParserRegistry} instance in the
|
||||||
|
* {@link cloud.commandframework.CommandManager} will be queried for a matching suggestion provider.
|
||||||
|
* <p>
|
||||||
|
* For this to work, the suggestion needs to be registered in the parser registry. To do this, use
|
||||||
|
* {@link cloud.commandframework.arguments.parser.ParserRegistry#registerSuggestionProvider(String, BiFunction)}.
|
||||||
|
* The registry instance can be retrieved using {@link cloud.commandframework.CommandManager#getParserRegistry()}.
|
||||||
|
*
|
||||||
|
* @return The name of the suggestion provider, or {@code ""} if the default suggestion provider for the argument parser
|
||||||
|
* should be used instead
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
String suggestions() default "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the default value
|
* Get the default value
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,16 @@ import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletionException;
|
import java.util.concurrent.CompletionException;
|
||||||
|
|
||||||
class AnnotationParserTest {
|
class AnnotationParserTest {
|
||||||
|
|
||||||
|
private static final List<String> NAMED_SUGGESTIONS = Arrays.asList("Dancing-Queen", "Gimme!-Gimme!-Gimme!", "Waterloo");
|
||||||
|
|
||||||
private static CommandManager<TestCommandSender> manager;
|
private static CommandManager<TestCommandSender> manager;
|
||||||
private static AnnotationParser<TestCommandSender> annotationParser;
|
private static AnnotationParser<TestCommandSender> annotationParser;
|
||||||
|
|
||||||
|
|
@ -47,6 +51,11 @@ class AnnotationParserTest {
|
||||||
annotationParser = new AnnotationParser<>(manager, TestCommandSender.class, p -> SimpleCommandMeta.empty());
|
annotationParser = new AnnotationParser<>(manager, TestCommandSender.class, p -> SimpleCommandMeta.empty());
|
||||||
manager.getParserRegistry().registerNamedParserSupplier("potato", p -> new StringArgument.StringParser<>(
|
manager.getParserRegistry().registerNamedParserSupplier("potato", p -> new StringArgument.StringParser<>(
|
||||||
StringArgument.StringMode.SINGLE, (c, s) -> Collections.singletonList("potato")));
|
StringArgument.StringMode.SINGLE, (c, s) -> Collections.singletonList("potato")));
|
||||||
|
/* Register a suggestion provider */
|
||||||
|
manager.getParserRegistry().registerSuggestionProvider(
|
||||||
|
"some-name",
|
||||||
|
(context, input) -> NAMED_SUGGESTIONS
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -62,6 +71,11 @@ class AnnotationParserTest {
|
||||||
manager.executeCommand(new TestCommandSender(), "flagcommand --print --word peanut").join();
|
manager.executeCommand(new TestCommandSender(), "flagcommand --print --word peanut").join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNamedSuggestionProvider() {
|
||||||
|
Assertions.assertEquals(NAMED_SUGGESTIONS, manager.suggest(new TestCommandSender(), "namedsuggestions "));
|
||||||
|
}
|
||||||
|
|
||||||
@ProxiedBy("proxycommand")
|
@ProxiedBy("proxycommand")
|
||||||
@CommandMethod("test|t literal <int> [string]")
|
@CommandMethod("test|t literal <int> [string]")
|
||||||
public void testCommand(
|
public void testCommand(
|
||||||
|
|
@ -75,12 +89,18 @@ class AnnotationParserTest {
|
||||||
@CommandMethod("flagcommand")
|
@CommandMethod("flagcommand")
|
||||||
public void testFlags(
|
public void testFlags(
|
||||||
final TestCommandSender sender,
|
final TestCommandSender sender,
|
||||||
@Flag(value = "print", aliases = "p") boolean print,
|
@Flag(value = "print", aliases = "p") final boolean print,
|
||||||
@Flag(value = "word", aliases = "w") String word
|
@Flag(value = "word", aliases = "w") final String word
|
||||||
) {
|
) {
|
||||||
if (print) {
|
if (print) {
|
||||||
System.out.println(word);
|
System.out.println(word);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CommandMethod("namedsuggestions <input>")
|
||||||
|
public void testNamedSuggestionProviders(
|
||||||
|
@Argument(value = "input", suggestions = "some-name") final String argument
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -621,7 +621,7 @@ public final class CommandTree<C> {
|
||||||
Objects.requireNonNull(
|
Objects.requireNonNull(
|
||||||
Objects.requireNonNull(
|
Objects.requireNonNull(
|
||||||
node.value,
|
node.value,
|
||||||
"node.value"
|
"node.value: "
|
||||||
).getOwningCommand(),
|
).getOwningCommand(),
|
||||||
"owning command"
|
"owning command"
|
||||||
).getCommandPermission()
|
).getCommandPermission()
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,13 @@
|
||||||
//
|
//
|
||||||
package cloud.commandframework.arguments.parser;
|
package cloud.commandframework.arguments.parser;
|
||||||
|
|
||||||
|
import cloud.commandframework.context.CommandContext;
|
||||||
import io.leangen.geantyref.TypeToken;
|
import io.leangen.geantyref.TypeToken;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
@ -121,4 +123,30 @@ public interface ParserRegistry<C> {
|
||||||
@NonNull ParserParameters parserParameters
|
@NonNull ParserParameters parserParameters
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new named suggestion provider
|
||||||
|
*
|
||||||
|
* @param name Name of the suggestions provider. The name is case independent.
|
||||||
|
* @param suggestionsProvider The suggestions provider
|
||||||
|
* @see #getSuggestionProvider(String) Get a suggestion provider
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
void registerSuggestionProvider(
|
||||||
|
@NonNull String name,
|
||||||
|
@NonNull BiFunction<@NonNull CommandContext<C>, @NonNull String, @NonNull List<String>> suggestionsProvider
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a named suggestion provider, if a suggestion provider with the given name exists in the registry
|
||||||
|
*
|
||||||
|
* @param name Suggestion provider name. The name is case independent.
|
||||||
|
* @return Optional that either contains the suggestion provider name, or nothing ({@link Optional#empty()}) if no
|
||||||
|
* suggestion provider is registered with the given name
|
||||||
|
* @see #registerSuggestionProvider(String, BiFunction) Register a suggestion provider
|
||||||
|
* @since 1.1.0
|
||||||
|
*/
|
||||||
|
@NonNull Optional<BiFunction<@NonNull CommandContext<C>, @NonNull String, @NonNull List<String>>> getSuggestionProvider(
|
||||||
|
@NonNull String name
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import cloud.commandframework.arguments.standard.ShortArgument;
|
||||||
import cloud.commandframework.arguments.standard.StringArgument;
|
import cloud.commandframework.arguments.standard.StringArgument;
|
||||||
import cloud.commandframework.arguments.standard.StringArrayArgument;
|
import cloud.commandframework.arguments.standard.StringArrayArgument;
|
||||||
import cloud.commandframework.arguments.standard.UUIDArgument;
|
import cloud.commandframework.arguments.standard.UUIDArgument;
|
||||||
|
import cloud.commandframework.context.CommandContext;
|
||||||
import io.leangen.geantyref.GenericTypeReflector;
|
import io.leangen.geantyref.GenericTypeReflector;
|
||||||
import io.leangen.geantyref.TypeToken;
|
import io.leangen.geantyref.TypeToken;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
|
@ -45,6 +46,8 @@ import java.lang.annotation.Annotation;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -75,6 +78,8 @@ public final class StandardParserRegistry<C> implements ParserRegistry<C> {
|
||||||
private final Map<TypeToken<?>, Function<ParserParameters, ArgumentParser<C, ?>>> parserSuppliers = new HashMap<>();
|
private final Map<TypeToken<?>, Function<ParserParameters, ArgumentParser<C, ?>>> parserSuppliers = new HashMap<>();
|
||||||
private final Map<Class<? extends Annotation>, BiFunction<? extends Annotation, TypeToken<?>, ParserParameters>>
|
private final Map<Class<? extends Annotation>, BiFunction<? extends Annotation, TypeToken<?>, ParserParameters>>
|
||||||
annotationMappers = new HashMap<>();
|
annotationMappers = new HashMap<>();
|
||||||
|
private final Map<String, BiFunction<@NonNull CommandContext<C>, @NonNull String, @NonNull List<String>>>
|
||||||
|
namedSuggestionProviders = new HashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new {@link StandardParserRegistry} instance. This will also
|
* Construct a new {@link StandardParserRegistry} instance. This will also
|
||||||
|
|
@ -221,6 +226,24 @@ public final class StandardParserRegistry<C> implements ParserRegistry<C> {
|
||||||
return Optional.of(parser);
|
return Optional.of(parser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerSuggestionProvider(
|
||||||
|
@NonNull final String name,
|
||||||
|
@NonNull final BiFunction<@NonNull CommandContext<C>, @NonNull String, @NonNull List<String>> suggestionsProvider
|
||||||
|
) {
|
||||||
|
this.namedSuggestionProviders.put(name.toLowerCase(Locale.ENGLISH), suggestionsProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull Optional<BiFunction<@NonNull CommandContext<C>, @NonNull String, @NonNull List<String>>> getSuggestionProvider(
|
||||||
|
@NonNull final String name
|
||||||
|
) {
|
||||||
|
final BiFunction<@NonNull CommandContext<C>, @NonNull String, @NonNull List<String>> suggestionProvider =
|
||||||
|
this.namedSuggestionProviders.get(name.toLowerCase(Locale.ENGLISH));
|
||||||
|
return Optional.ofNullable(suggestionProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static final class RangeMapper<T> implements BiFunction<@NonNull Range, @NonNull TypeToken<?>,
|
private static final class RangeMapper<T> implements BiFunction<@NonNull Range, @NonNull TypeToken<?>,
|
||||||
@NonNull ParserParameters> {
|
@NonNull ParserParameters> {
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue