Add support for suspending functions

This also changes CommandExecutionHandler to return futures instead. The old method is still supported, and the new future-returning method will delegate to the old non-returning method.
This commit is contained in:
Alexander Söderberg 2021-07-07 14:03:06 +01:00 committed by Jason
parent c16ee8049b
commit 7bb5e63dc7
12 changed files with 556 additions and 249 deletions

View file

@ -48,6 +48,9 @@ import cloud.commandframework.extra.confirmation.CommandConfirmationManager;
import cloud.commandframework.meta.CommandMeta; import cloud.commandframework.meta.CommandMeta;
import cloud.commandframework.meta.SimpleCommandMeta; import cloud.commandframework.meta.SimpleCommandMeta;
import io.leangen.geantyref.TypeToken; import io.leangen.geantyref.TypeToken;
import java.util.function.Predicate;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -85,6 +88,8 @@ public final class AnnotationParser<C> {
@NonNull Queue<@NonNull String>, @NonNull ArgumentParseResult<Boolean>>>> preprocessorMappers; @NonNull Queue<@NonNull String>, @NonNull ArgumentParseResult<Boolean>>>> preprocessorMappers;
private final Map<Class<? extends Annotation>, BiFunction<? extends Annotation, Command.Builder<C>, Command.Builder<C>>> private final Map<Class<? extends Annotation>, BiFunction<? extends Annotation, Command.Builder<C>, Command.Builder<C>>>
builderModifiers; builderModifiers;
private final Map<Predicate<Method>, Function<MethodCommandExecutionHandler.CommandMethodContext<C>,
MethodCommandExecutionHandler<C>>> commandMethodFactories;
private final Class<C> commandSenderClass; private final Class<C> commandSenderClass;
private final MetaFactory metaFactory; private final MetaFactory metaFactory;
private final FlagExtractor flagExtractor; private final FlagExtractor flagExtractor;
@ -110,6 +115,7 @@ public final class AnnotationParser<C> {
this.annotationMappers = new HashMap<>(); this.annotationMappers = new HashMap<>();
this.preprocessorMappers = new HashMap<>(); this.preprocessorMappers = new HashMap<>();
this.builderModifiers = new HashMap<>(); this.builderModifiers = new HashMap<>();
this.commandMethodFactories = new HashMap<>();
this.flagExtractor = new FlagExtractor(manager); this.flagExtractor = new FlagExtractor(manager);
this.registerAnnotationMapper(CommandDescription.class, d -> this.registerAnnotationMapper(CommandDescription.class, d ->
ParserParameters.single(StandardParameters.DESCRIPTION, d.value())); ParserParameters.single(StandardParameters.DESCRIPTION, d.value()));
@ -180,6 +186,29 @@ public final class AnnotationParser<C> {
return getMethodOrClassAnnotation(method, clazz) != null; return getMethodOrClassAnnotation(method, clazz) != null;
} }
/**
* Returns the command manager that was used to create this parser
*
* @return Command manager
*/
public @NonNull CommandManager<C> manager() {
return this.manager;
}
/**
* Registers a new command execution method factory. This allows for the registration of
* custom command method execution strategies.
*
* @param predicate The predicate that decides whether or not to apply the custom execution handler to the given method
* @param function The function that produces the command execution handler
*/
public void registerCommandExecutionMethodFactory(
final @NonNull Predicate<@NonNull Method> predicate,
final @NonNull Function<MethodCommandExecutionHandler.CommandMethodContext<C>, MethodCommandExecutionHandler<C>> function
) {
this.commandMethodFactories.put(predicate, function);
}
/** /**
* Register a builder modifier for a specific annotation. The builder modifiers are * Register a builder modifier for a specific annotation. The builder modifiers are
* allowed to act on a {@link Command.Builder} after all arguments have been added * allowed to act on a {@link Command.Builder} after all arguments have been added
@ -265,12 +294,6 @@ public final class AnnotationParser<C> {
if (!method.isAccessible()) { if (!method.isAccessible()) {
method.setAccessible(true); method.setAccessible(true);
} }
if (method.getReturnType() != Void.TYPE) {
throw new IllegalArgumentException(String.format(
"@CommandMethod annotated method '%s' has non-void return type",
method.getName()
));
}
if (Modifier.isStatic(method.getModifiers())) { if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalArgumentException(String.format( throw new IllegalArgumentException(String.format(
"@CommandMethod annotated method '%s' is static! @CommandMethod annotated methods should not be static.", "@CommandMethod annotated method '%s' is static! @CommandMethod annotated methods should not be static.",
@ -460,13 +483,25 @@ public final class AnnotationParser<C> {
builder = builder.senderType(senderType); builder = builder.senderType(senderType);
} }
try { try {
/* Construct the handler */ final MethodCommandExecutionHandler.CommandMethodContext<C> context = new MethodCommandExecutionHandler.CommandMethodContext<>(
final CommandExecutionHandler<C> commandExecutionHandler = new MethodCommandExecutionHandler<>(
instance, instance,
commandArguments, commandArguments,
method, method,
this.getParameterInjectorRegistry() this.getParameterInjectorRegistry()
); );
/* Create the command execution handler */
CommandExecutionHandler<C> commandExecutionHandler = new MethodCommandExecutionHandler<>(context);
for (final Map.Entry<Predicate<Method>, Function<MethodCommandExecutionHandler.CommandMethodContext<C>, MethodCommandExecutionHandler<C>>> entry :
commandMethodFactories.entrySet()) {
if (entry.getKey().test(method)) {
commandExecutionHandler = entry.getValue().apply(context);
/* Once we have our custom handler, we stop */
break;
}
}
builder = builder.handler(commandExecutionHandler); builder = builder.handler(commandExecutionHandler);
} catch (final Exception e) { } catch (final Exception e) {
throw new RuntimeException("Failed to construct command execution handler", e); throw new RuntimeException("Failed to construct command execution handler", e);

View file

@ -40,38 +40,61 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
class MethodCommandExecutionHandler<C> implements CommandExecutionHandler<C> { /**
* A command execution handler that invokes a method.
*
* @param <C> Command sender type.
*/
public class MethodCommandExecutionHandler<C> implements CommandExecutionHandler<C> {
private final CommandMethodContext<C> context;
private final Parameter[] parameters; private final Parameter[] parameters;
private final MethodHandle methodHandle; private final MethodHandle methodHandle;
private final Map<String, CommandArgument<C, ?>> commandArguments;
private final ParameterInjectorRegistry<C> injectorRegistry;
private final AnnotationAccessor annotationAccessor; private final AnnotationAccessor annotationAccessor;
MethodCommandExecutionHandler( /**
final @NonNull Object instance, * Constructs a new method command execution handler
final @NonNull Map<@NonNull String, @NonNull CommandArgument<@NonNull C, @NonNull ?>> commandArguments, *
final @NonNull Method method, * @param context The context
final @NonNull ParameterInjectorRegistry<C> injectorRegistry */
public MethodCommandExecutionHandler(
final @NonNull CommandMethodContext<C> context
) throws Exception { ) throws Exception {
this.commandArguments = commandArguments; this.context = context;
method.setAccessible(true); this.methodHandle = MethodHandles.lookup().unreflect(context.method).bindTo(context.instance);
this.methodHandle = MethodHandles.lookup().unreflect(method).bindTo(instance); this.parameters = context.method.getParameters();
this.parameters = method.getParameters(); this.annotationAccessor = AnnotationAccessor.of(context.method);
this.injectorRegistry = injectorRegistry;
this.annotationAccessor = AnnotationAccessor.of(method);
} }
/**
* {@inheritDoc}
*/
@Override @Override
public void execute(final @NonNull CommandContext<C> commandContext) { public void execute(final @NonNull CommandContext<C> commandContext) {
final List<Object> arguments = new ArrayList<>(this.parameters.length); /* Invoke the command method */
final FlagContext flagContext = commandContext.flags(); try {
this.methodHandle.invokeWithArguments(createParameterValues(
commandContext,
commandContext.flags(),
true)
);
} catch (final Error e) {
throw e;
} catch (final Throwable throwable) {
throw new CommandExecutionException(throwable, commandContext);
}
}
/* Bind parameters to context */ protected List<Object> createParameterValues(
final CommandContext<C> commandContext,
final FlagContext flagContext,
final boolean throwOnMissing
) {
final List<Object> arguments = new ArrayList<>(this.parameters.length);
for (final Parameter parameter : this.parameters) { for (final Parameter parameter : this.parameters) {
if (parameter.isAnnotationPresent(Argument.class)) { if (parameter.isAnnotationPresent(Argument.class)) {
final Argument argument = parameter.getAnnotation(Argument.class); final Argument argument = parameter.getAnnotation(Argument.class);
final CommandArgument<C, ?> commandArgument = this.commandArguments.get(argument.value()); final CommandArgument<C, ?> commandArgument = this.context.commandArguments.get(argument.value());
if (commandArgument.isRequired()) { if (commandArgument.isRequired()) {
arguments.add(commandContext.get(argument.value())); arguments.add(commandContext.get(argument.value()));
} else { } else {
@ -89,14 +112,14 @@ class MethodCommandExecutionHandler<C> implements CommandExecutionHandler<C> {
if (parameter.getType().isAssignableFrom(commandContext.getSender().getClass())) { if (parameter.getType().isAssignableFrom(commandContext.getSender().getClass())) {
arguments.add(commandContext.getSender()); arguments.add(commandContext.getSender());
} else { } else {
final Optional<?> value = this.injectorRegistry.getInjectable( final Optional<?> value = this.context.injectorRegistry.getInjectable(
parameter.getType(), parameter.getType(),
commandContext, commandContext,
AnnotationAccessor.of(AnnotationAccessor.of(parameter), this.annotationAccessor) AnnotationAccessor.of(AnnotationAccessor.of(parameter), this.annotationAccessor)
); );
if (value.isPresent()) { if (value.isPresent()) {
arguments.add(value.get()); arguments.add(value.get());
} else { } else if (throwOnMissing) {
throw new IllegalArgumentException(String.format( throw new IllegalArgumentException(String.format(
"Unknown command parameter '%s' in method '%s'", "Unknown command parameter '%s' in method '%s'",
parameter.getName(), parameter.getName(),
@ -106,15 +129,106 @@ class MethodCommandExecutionHandler<C> implements CommandExecutionHandler<C> {
} }
} }
} }
return arguments;
/* Invoke the command method */
try {
this.methodHandle.invokeWithArguments(arguments);
} catch (final Error e) {
throw e;
} catch (final Throwable throwable) {
throw new CommandExecutionException(throwable, commandContext);
} }
/**
* Returns the command method context
*
* @return The context
*/
public @NonNull CommandMethodContext<C> context() {
return this.context;
}
/**
* Returns all parameters passed to the method
*
* @return The parameters
*/
public final @NonNull Parameter @NonNull [] parameters() {
return this.parameters;
}
/**
* Returns the compiled method handle for the command method.
*
* @return The method handle
*/
public final @NonNull MethodHandle methodHandle() {
return this.methodHandle;
}
/**
* The annotation accessor for the command method
*
* @return Annotation accessor
*/
public final AnnotationAccessor annotationAccessor() {
return this.annotationAccessor;
}
/**
* Context for command methods
*
* @param <C> Command sender type
*/
public static class CommandMethodContext<C> {
private final Object instance;
private final Map<String, CommandArgument<C, ?>> commandArguments;
private final Method method;
private final ParameterInjectorRegistry<C> injectorRegistry;
CommandMethodContext(
final @NonNull Object instance,
final @NonNull Map<@NonNull String, @NonNull CommandArgument<@NonNull C, @NonNull ?>> commandArguments,
final @NonNull Method method,
final @NonNull ParameterInjectorRegistry<C> injectorRegistry
) {
this.instance = instance;
this.commandArguments = commandArguments;
this.method = method;
this.method.setAccessible(true);
this.injectorRegistry = injectorRegistry;
}
/**
* The instance that owns the command method
*
* @return The instance
*/
public @NonNull Object instance() {
return this.instance;
}
/**
* The command method
*
* @return The method
*/
public final @NonNull Method method() {
return this.method;
}
/**
* The compiled command arguments
*
* @return Compiled command arguments
*/
public final @NonNull Map<@NonNull String, @NonNull CommandArgument<C, ?>> commandArguments() {
return this.commandArguments;
}
/**
* The injector registry
*
* @return Injector registry
*/
public final @NonNull ParameterInjectorRegistry<C> injectorRegistry() {
return this.injectorRegistry;
}
} }
} }

View file

@ -910,6 +910,15 @@ public abstract class CommandManager<C> {
} }
} }
/**
* Returns the command execution coordinator used in this manager
*
* @return Command execution coordinator
*/
public @NonNull CommandExecutionCoordinator<C> commandExecutionCoordinator() {
return this.commandExecutionCoordinator;
}
/** /**
* Transition from the {@code in} state to the {@code out} state, if the manager is not already in that state. * Transition from the {@code in} state to the {@code out} state, if the manager is not already in that state.
* *

View file

@ -81,14 +81,16 @@ public final class AsynchronousCommandExecutionCoordinator<C> extends CommandExe
final Consumer<Command<C>> commandConsumer = command -> { final Consumer<Command<C>> commandConsumer = command -> {
if (this.commandManager.postprocessContext(commandContext, command) == State.ACCEPTED) { if (this.commandManager.postprocessContext(commandContext, command) == State.ACCEPTED) {
try { command.getCommandExecutionHandler().executeFuture(commandContext).whenComplete((result, throwable) -> {
command.getCommandExecutionHandler().execute(commandContext); if (throwable != null) {
} catch (final CommandExecutionException exception) { if (throwable instanceof CommandExecutionException) {
resultFuture.completeExceptionally(exception); resultFuture.completeExceptionally(throwable);
} catch (final Exception exception) { } else {
resultFuture.completeExceptionally(new CommandExecutionException(exception, commandContext)); resultFuture.completeExceptionally(new CommandExecutionException(throwable, commandContext));
} }
} }
});
}
}; };
if (this.synchronizeParsing) { if (this.synchronizeParsing) {

View file

@ -118,7 +118,7 @@ public abstract class CommandExecutionCoordinator<C> {
final Command<C> command = Objects.requireNonNull(pair.getFirst()); final Command<C> command = Objects.requireNonNull(pair.getFirst());
if (this.getCommandTree().getCommandManager().postprocessContext(commandContext, command) == State.ACCEPTED) { if (this.getCommandTree().getCommandManager().postprocessContext(commandContext, command) == State.ACCEPTED) {
try { try {
command.getCommandExecutionHandler().execute(commandContext); command.getCommandExecutionHandler().executeFuture(commandContext).get();
} catch (final CommandExecutionException exception) { } catch (final CommandExecutionException exception) {
completableFuture.completeExceptionally(exception); completableFuture.completeExceptionally(exception);
} catch (final Exception exception) { } catch (final Exception exception) {

View file

@ -25,7 +25,11 @@ package cloud.commandframework.execution;
import cloud.commandframework.Command; import cloud.commandframework.Command;
import cloud.commandframework.context.CommandContext; import cloud.commandframework.context.CommandContext;
import java.util.concurrent.CompletableFuture;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* Handler that is invoked whenever a {@link Command} is executed * Handler that is invoked whenever a {@link Command} is executed
@ -43,6 +47,23 @@ public interface CommandExecutionHandler<C> {
*/ */
void execute(@NonNull CommandContext<C> commandContext); void execute(@NonNull CommandContext<C> commandContext);
/**
* Handle command execution
*
* @param commandContext Command context
* @return future that completes when the command has finished execution
*/
default CompletableFuture<@Nullable Object> executeFuture(@NonNull CommandContext<C> commandContext) {
final CompletableFuture<Object> future = new CompletableFuture<>();
try {
execute(commandContext);
/* The command executed successfully */
future.complete(null);
} catch (final Throwable throwable) {
future.completeExceptionally(throwable);
}
return future;
}
/** /**
* Command execution handler that does nothing * Command execution handler that does nothing
@ -57,4 +78,27 @@ public interface CommandExecutionHandler<C> {
} }
/**
* Handler that is invoked whenever a {@link Command} is executed
* by a command sender
*
* @param <C> Command sender type
*/
@FunctionalInterface
interface FutureCommandExecutionHandler<C> extends CommandExecutionHandler<C> {
@Override
@SuppressWarnings("FunctionalInterfaceMethodChanged")
default void execute(
@NonNull CommandContext<C> commandContext
) {
}
@Override
CompletableFuture<@Nullable Object> executeFuture(
@NonNull CommandContext<C> commandContext
);
}
} }

View file

@ -24,6 +24,7 @@
package cloud.commandframework.extra.confirmation; package cloud.commandframework.extra.confirmation;
import cloud.commandframework.CommandManager; import cloud.commandframework.CommandManager;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.execution.CommandExecutionHandler; import cloud.commandframework.execution.CommandExecutionHandler;
import cloud.commandframework.execution.postprocessor.CommandPostprocessingContext; import cloud.commandframework.execution.postprocessor.CommandPostprocessingContext;
import cloud.commandframework.execution.postprocessor.CommandPostprocessor; import cloud.commandframework.execution.postprocessor.CommandPostprocessor;
@ -31,6 +32,9 @@ import cloud.commandframework.meta.CommandMeta;
import cloud.commandframework.meta.SimpleCommandMeta; import cloud.commandframework.meta.SimpleCommandMeta;
import cloud.commandframework.services.types.ConsumerService; import cloud.commandframework.services.types.ConsumerService;
import cloud.commandframework.types.tuples.Pair; import cloud.commandframework.types.tuples.Pair;
import java.util.concurrent.CompletableFuture;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -158,20 +162,20 @@ public class CommandConfirmationManager<C> {
* @return Handler for a confirmation command * @return Handler for a confirmation command
*/ */
public @NonNull CommandExecutionHandler<C> createConfirmationExecutionHandler() { public @NonNull CommandExecutionHandler<C> createConfirmationExecutionHandler() {
return context -> { return (CommandExecutionHandler.FutureCommandExecutionHandler<C>) context -> {
final Optional<CommandPostprocessingContext<C>> pending = this.getPending(context.getSender()); final Optional<CommandPostprocessingContext<C>> pending = this.getPending(context.getSender());
if (pending.isPresent()) { if (pending.isPresent()) {
final CommandPostprocessingContext<C> postprocessingContext = pending.get(); final CommandPostprocessingContext<C> postprocessingContext = pending.get();
postprocessingContext.getCommand() return postprocessingContext.getCommand()
.getCommandExecutionHandler() .getCommandExecutionHandler()
.execute(postprocessingContext.getCommandContext()); .executeFuture(postprocessingContext.getCommandContext());
} else { } else {
this.errorNotifier.accept(context.getSender()); this.errorNotifier.accept(context.getSender());
} }
return CompletableFuture.completedFuture(null);
}; };
} }
private final class CommandConfirmationPostProcessor implements CommandPostprocessor<C> { private final class CommandConfirmationPostProcessor implements CommandPostprocessor<C> {
@Override @Override

View file

@ -4,6 +4,7 @@ import java.net.URL
plugins { plugins {
kotlin("jvm") version "1.4.31" kotlin("jvm") version "1.4.31"
id("org.jetbrains.dokka") version "1.4.20" id("org.jetbrains.dokka") version "1.4.20"
id("com.ncorti.ktfmt.gradle") version "0.6.0"
} }
configurations.all { configurations.all {
@ -13,7 +14,14 @@ configurations.all {
dependencies { dependencies {
api(project(":cloud-core")) api(project(":cloud-core"))
implementation(kotlin("stdlib-jdk8")) implementation(kotlin("stdlib-jdk8"))
implementation(project(":cloud-annotations"))
implementation("org.jetbrains.kotlin:kotlin-reflect:1.4.31")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.4.3")
testImplementation("org.jetbrains.kotlin", "kotlin-test-junit5") testImplementation("org.jetbrains.kotlin", "kotlin-test-junit5")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.4.3")
} }
tasks { tasks {
@ -42,3 +50,7 @@ tasks {
kotlin { kotlin {
explicitApi() explicitApi()
} }
ktfmt {
dropboxStyle()
}

View file

@ -1,7 +1,7 @@
// //
// MIT License // MIT License
// //
// Copyright (c) 2021 Alexander Söderberg & Contributors // Copyright (c) 2021 Alexander Söderberg & Contributors
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -36,7 +36,8 @@ import cloud.commandframework.permission.CommandPermission
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
* A mutable [Command.Builder] wrapper, providing functions to assist in creating commands using the Kotlin builder DSL style * A mutable [Command.Builder] wrapper, providing functions to assist in creating commands using the
* Kotlin builder DSL style
* *
* @since 1.3.0 * @since 1.3.0
*/ */
@ -54,7 +55,9 @@ public class MutableCommandBuilder<C : Any> {
* @since 1.3.0 * @since 1.3.0
*/ */
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) @Deprecated(
message = "ArgumentDescription should be used over Description",
level = DeprecationLevel.HIDDEN)
public constructor( public constructor(
name: String, name: String,
description: Description = Description.empty(), description: Description = Description.empty(),
@ -95,7 +98,9 @@ public class MutableCommandBuilder<C : Any> {
* @since 1.3.0 * @since 1.3.0
*/ */
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) @Deprecated(
message = "ArgumentDescription should be used over Description",
level = DeprecationLevel.HIDDEN)
public constructor( public constructor(
name: String, name: String,
description: Description = Description.empty(), description: Description = Description.empty(),
@ -126,10 +131,7 @@ public class MutableCommandBuilder<C : Any> {
lambda(this) lambda(this)
} }
private constructor( private constructor(commandManager: CommandManager<C>, commandBuilder: Command.Builder<C>) {
commandManager: CommandManager<C>,
commandBuilder: Command.Builder<C>
) {
this.commandManager = commandManager this.commandManager = commandManager
this.commandBuilder = commandBuilder this.commandBuilder = commandBuilder
} }
@ -140,19 +142,17 @@ public class MutableCommandBuilder<C : Any> {
* @return built command * @return built command
* @since 1.3.0 * @since 1.3.0
*/ */
public fun build(): Command<C> = public fun build(): Command<C> = this.commandBuilder.build()
this.commandBuilder.build()
/** /**
* Invoke the provided receiver lambda on this builder, then build a [Command] from the resulting state * Invoke the provided receiver lambda on this builder, then build a [Command] from the
* resulting state
* *
* @param lambda receiver lambda which will be invoked on builder before building * @param lambda receiver lambda which will be invoked on builder before building
* @return built command * @return built command
* @since 1.3.0 * @since 1.3.0
*/ */
public fun build( public fun build(lambda: MutableCommandBuilder<C>.() -> Unit): Command<C> {
lambda: MutableCommandBuilder<C>.() -> Unit
): Command<C> {
lambda(this) lambda(this)
return this.commandBuilder.build() return this.commandBuilder.build()
} }
@ -171,9 +171,7 @@ public class MutableCommandBuilder<C : Any> {
return this return this
} }
private fun onlyMutate( private fun onlyMutate(mutator: (Command.Builder<C>) -> Command.Builder<C>): Unit {
mutator: (Command.Builder<C>) -> Command.Builder<C>
): Unit {
mutate(mutator) mutate(mutator)
} }
@ -193,15 +191,12 @@ public class MutableCommandBuilder<C : Any> {
* @return a copy of this mutable builder * @return a copy of this mutable builder
* @since 1.3.0 * @since 1.3.0
*/ */
public fun copy( public fun copy(lambda: MutableCommandBuilder<C>.() -> Unit): MutableCommandBuilder<C> =
lambda: MutableCommandBuilder<C>.() -> Unit copy().apply { lambda(this) }
): MutableCommandBuilder<C> =
copy().apply {
lambda(this)
}
/** /**
* Make a new copy of this [MutableCommandBuilder], append a literal, and invoke the provided receiver lambda on it * Make a new copy of this [MutableCommandBuilder], append a literal, and invoke the provided
* receiver lambda on it
* *
* @param literal name for the literal * @param literal name for the literal
* @param description description for the literal * @param description description for the literal
@ -210,7 +205,9 @@ public class MutableCommandBuilder<C : Any> {
* @since 1.3.0 * @since 1.3.0
*/ */
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) @Deprecated(
message = "ArgumentDescription should be used over Description",
level = DeprecationLevel.HIDDEN)
public fun copy( public fun copy(
literal: String, literal: String,
description: Description, description: Description,
@ -222,7 +219,8 @@ public class MutableCommandBuilder<C : Any> {
} }
/** /**
* Make a new copy of this [MutableCommandBuilder], append a literal, and invoke the provided receiver lambda on it * Make a new copy of this [MutableCommandBuilder], append a literal, and invoke the provided
* receiver lambda on it
* *
* @param literal name for the literal * @param literal name for the literal
* @param description description for the literal * @param description description for the literal
@ -241,7 +239,8 @@ public class MutableCommandBuilder<C : Any> {
} }
/** /**
* Make a new copy of this [MutableCommandBuilder], append a literal, and invoke the provided receiver lambda on it * Make a new copy of this [MutableCommandBuilder], append a literal, and invoke the provided
* receiver lambda on it
* *
* @param literal name for the literal * @param literal name for the literal
* @param lambda receiver lambda which will be invoked on the new builder * @param lambda receiver lambda which will be invoked on the new builder
@ -264,29 +263,23 @@ public class MutableCommandBuilder<C : Any> {
* @see [CommandManager.command] * @see [CommandManager.command]
* @since 1.3.0 * @since 1.3.0
*/ */
public fun register(): MutableCommandBuilder<C> = public fun register(): MutableCommandBuilder<C> = apply { this.commandManager.command(this) }
apply {
this.commandManager.command(this)
}
/** /**
* Create a new copy of this mutable builder, act on it with a receiver lambda, and then register it with the owning * Create a new copy of this mutable builder, act on it with a receiver lambda, and then
* command manager * register it with the owning command manager
* *
* @param lambda receiver lambda which will be invoked on the new builder * @param lambda receiver lambda which will be invoked on the new builder
* @return the new mutable builder * @return the new mutable builder
* @see [CommandManager.command] * @see [CommandManager.command]
* @since 1.3.0 * @since 1.3.0
*/ */
public fun registerCopy( public fun registerCopy(lambda: MutableCommandBuilder<C>.() -> Unit): MutableCommandBuilder<C> =
lambda: MutableCommandBuilder<C>.() -> Unit
): MutableCommandBuilder<C> =
copy(lambda).register() copy(lambda).register()
/** /**
* Create a new copy of this mutable builder, append a literal, act on it with a receiver lambda, and then register it with * Create a new copy of this mutable builder, append a literal, act on it with a receiver
* the owning * lambda, and then register it with the owning command manager
* command manager
* *
* @param literal name for the literal * @param literal name for the literal
* @param lambda receiver lambda which will be invoked on the new builder * @param lambda receiver lambda which will be invoked on the new builder
@ -297,13 +290,11 @@ public class MutableCommandBuilder<C : Any> {
public fun registerCopy( public fun registerCopy(
literal: String, literal: String,
lambda: MutableCommandBuilder<C>.() -> Unit lambda: MutableCommandBuilder<C>.() -> Unit
): MutableCommandBuilder<C> = ): MutableCommandBuilder<C> = copy(literal, lambda).register()
copy(literal, lambda).register()
/** /**
* Create a new copy of this mutable builder, append a literal, act on it with a receiver lambda, and then register it with * Create a new copy of this mutable builder, append a literal, act on it with a receiver
* the owning * lambda, and then register it with the owning command manager
* command manager
* *
* @param literal name for the literal * @param literal name for the literal
* @param description description for the literal * @param description description for the literal
@ -313,18 +304,18 @@ public class MutableCommandBuilder<C : Any> {
* @since 1.3.0 * @since 1.3.0
*/ */
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) @Deprecated(
message = "ArgumentDescription should be used over Description",
level = DeprecationLevel.HIDDEN)
public fun registerCopy( public fun registerCopy(
literal: String, literal: String,
description: Description, description: Description,
lambda: MutableCommandBuilder<C>.() -> Unit lambda: MutableCommandBuilder<C>.() -> Unit
): MutableCommandBuilder<C> = ): MutableCommandBuilder<C> = copy(literal, description, lambda).register()
copy(literal, description, lambda).register()
/** /**
* Create a new copy of this mutable builder, append a literal, act on it with a receiver lambda, and then register it with * Create a new copy of this mutable builder, append a literal, act on it with a receiver
* the owning * lambda, and then register it with the owning command manager
* command manager
* *
* @param literal name for the literal * @param literal name for the literal
* @param description description for the literal * @param description description for the literal
@ -337,8 +328,7 @@ public class MutableCommandBuilder<C : Any> {
literal: String, literal: String,
description: ArgumentDescription, description: ArgumentDescription,
lambda: MutableCommandBuilder<C>.() -> Unit lambda: MutableCommandBuilder<C>.() -> Unit
): MutableCommandBuilder<C> = ): MutableCommandBuilder<C> = copy(literal, description, lambda).register()
copy(literal, description, lambda).register()
/** /**
* Set the value for a certain [CommandMeta.Key] in the command meta storage for this builder * Set the value for a certain [CommandMeta.Key] in the command meta storage for this builder
@ -349,11 +339,10 @@ public class MutableCommandBuilder<C : Any> {
* @return this mutable builder * @return this mutable builder
* @since 1.3.0 * @since 1.3.0
*/ */
public fun <T : Any> meta( public fun <T : Any> meta(key: CommandMeta.Key<T>, value: T): MutableCommandBuilder<C> =
key: CommandMeta.Key<T>, mutate {
value: T it.meta(key, value)
): MutableCommandBuilder<C> = }
mutate { it.meta(key, value) }
/** /**
* Set the value for a certain [CommandMeta.Key] in the command meta storage for this builder * Set the value for a certain [CommandMeta.Key] in the command meta storage for this builder
@ -363,9 +352,7 @@ public class MutableCommandBuilder<C : Any> {
* @return this mutable builder * @return this mutable builder
* @since 1.3.0 * @since 1.3.0
*/ */
public infix fun <T : Any> CommandMeta.Key<T>.to( public infix fun <T : Any> CommandMeta.Key<T>.to(value: T): MutableCommandBuilder<C> =
value: T
): MutableCommandBuilder<C> =
meta(this, value) meta(this, value)
/** /**
@ -375,9 +362,7 @@ public class MutableCommandBuilder<C : Any> {
* @return this mutable builder * @return this mutable builder
* @since 1.3.0 * @since 1.3.0
*/ */
public fun commandDescription( public fun commandDescription(description: String): MutableCommandBuilder<C> =
description: String
): MutableCommandBuilder<C> =
meta(CommandMeta.DESCRIPTION, description) meta(CommandMeta.DESCRIPTION, description)
/** /**
@ -387,9 +372,7 @@ public class MutableCommandBuilder<C : Any> {
* @return this mutable builder * @return this mutable builder
* @since 1.3.0 * @since 1.3.0
*/ */
public fun longCommandDescription( public fun longCommandDescription(description: String): MutableCommandBuilder<C> =
description: String
): MutableCommandBuilder<C> =
meta(CommandMeta.LONG_DESCRIPTION, description) meta(CommandMeta.LONG_DESCRIPTION, description)
/** /**
@ -399,9 +382,7 @@ public class MutableCommandBuilder<C : Any> {
* @return this mutable builder * @return this mutable builder
* @since 1.3.0 * @since 1.3.0
*/ */
public fun hidden( public fun hidden(hidden: Boolean = true): MutableCommandBuilder<C> =
hidden: Boolean = true
): MutableCommandBuilder<C> =
meta(CommandMeta.HIDDEN, hidden) meta(CommandMeta.HIDDEN, hidden)
/** /**
@ -411,8 +392,9 @@ public class MutableCommandBuilder<C : Any> {
* @return this mutable builder * @return this mutable builder
* @since 1.3.0 * @since 1.3.0
*/ */
public inline fun <reified T : C> senderType(): MutableCommandBuilder<C> = public inline fun <reified T : C> senderType(): MutableCommandBuilder<C> = mutate {
mutate { it.senderType(T::class) } it.senderType(T::class)
}
/** /**
* Specify a required sender type * Specify a required sender type
@ -421,10 +403,9 @@ public class MutableCommandBuilder<C : Any> {
* @return this mutable builder * @return this mutable builder
* @since 1.3.0 * @since 1.3.0
*/ */
public fun senderType( public fun senderType(type: KClass<out C>): MutableCommandBuilder<C> = mutate {
type: KClass<out C> it.senderType(type)
): MutableCommandBuilder<C> = }
mutate { it.senderType(type) }
/** /**
* Field to get and set the required sender type for this command builder * Field to get and set the required sender type for this command builder
@ -445,10 +426,9 @@ public class MutableCommandBuilder<C : Any> {
* @return this mutable builder * @return this mutable builder
* @since 1.3.0 * @since 1.3.0
*/ */
public fun senderType( public fun senderType(type: Class<out C>): MutableCommandBuilder<C> = mutate {
type: Class<out C> it.senderType(type)
): MutableCommandBuilder<C> = }
mutate { it.senderType(type) }
/** /**
* Specify a permission required to execute this command * Specify a permission required to execute this command
@ -457,10 +437,9 @@ public class MutableCommandBuilder<C : Any> {
* @return this mutable builder * @return this mutable builder
* @since 1.3.0 * @since 1.3.0
*/ */
public fun permission( public fun permission(permission: String): MutableCommandBuilder<C> = mutate {
permission: String it.permission(permission)
): MutableCommandBuilder<C> = }
mutate { it.permission(permission) }
/** /**
* Specify a permission required to execute this command * Specify a permission required to execute this command
@ -469,10 +448,9 @@ public class MutableCommandBuilder<C : Any> {
* @return this mutable builder * @return this mutable builder
* @since 1.3.0 * @since 1.3.0
*/ */
public fun permission( public fun permission(permission: CommandPermission): MutableCommandBuilder<C> = mutate {
permission: CommandPermission it.permission(permission)
): MutableCommandBuilder<C> = }
mutate { it.permission(permission) }
/** /**
* Field to get and set the required permission for this command builder * Field to get and set the required permission for this command builder
@ -501,12 +479,13 @@ public class MutableCommandBuilder<C : Any> {
* @since 1.3.0 * @since 1.3.0
*/ */
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) @Deprecated(
message = "ArgumentDescription should be used over Description",
level = DeprecationLevel.HIDDEN)
public fun argument( public fun argument(
argument: CommandArgument<C, *>, argument: CommandArgument<C, *>,
description: Description = Description.empty() description: Description = Description.empty()
): MutableCommandBuilder<C> = ): MutableCommandBuilder<C> = mutate { it.argument(argument, description) }
mutate { it.argument(argument, description) }
/** /**
* Add a new argument to this command * Add a new argument to this command
@ -519,8 +498,7 @@ public class MutableCommandBuilder<C : Any> {
public fun argument( public fun argument(
argument: CommandArgument<C, *>, argument: CommandArgument<C, *>,
description: ArgumentDescription = ArgumentDescription.empty() description: ArgumentDescription = ArgumentDescription.empty()
): MutableCommandBuilder<C> = ): MutableCommandBuilder<C> = mutate { it.argument(argument, description) }
mutate { it.argument(argument, description) }
/** /**
* Add a new argument to this command * Add a new argument to this command
@ -531,12 +509,13 @@ public class MutableCommandBuilder<C : Any> {
* @since 1.3.0 * @since 1.3.0
*/ */
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) @Deprecated(
message = "ArgumentDescription should be used over Description",
level = DeprecationLevel.HIDDEN)
public fun argument( public fun argument(
argument: CommandArgument.Builder<C, *>, argument: CommandArgument.Builder<C, *>,
description: Description = Description.empty() description: Description = Description.empty()
): MutableCommandBuilder<C> = ): MutableCommandBuilder<C> = mutate { it.argument(argument, description) }
mutate { it.argument(argument, description) }
/** /**
* Add a new argument to this command * Add a new argument to this command
@ -549,8 +528,7 @@ public class MutableCommandBuilder<C : Any> {
public fun argument( public fun argument(
argument: CommandArgument.Builder<C, *>, argument: CommandArgument.Builder<C, *>,
description: ArgumentDescription = ArgumentDescription.empty() description: ArgumentDescription = ArgumentDescription.empty()
): MutableCommandBuilder<C> = ): MutableCommandBuilder<C> = mutate { it.argument(argument, description) }
mutate { it.argument(argument, description) }
/** /**
* Add a new argument to this command * Add a new argument to this command
@ -561,12 +539,13 @@ public class MutableCommandBuilder<C : Any> {
* @since 1.3.0 * @since 1.3.0
*/ */
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) @Deprecated(
message = "ArgumentDescription should be used over Description",
level = DeprecationLevel.HIDDEN)
public fun argument( public fun argument(
description: Description = Description.empty(), description: Description = Description.empty(),
argumentSupplier: () -> CommandArgument<C, *> argumentSupplier: () -> CommandArgument<C, *>
): MutableCommandBuilder<C> = ): MutableCommandBuilder<C> = mutate { it.argument(argumentSupplier(), description) }
mutate { it.argument(argumentSupplier(), description) }
/** /**
* Add a new argument to this command * Add a new argument to this command
@ -579,8 +558,7 @@ public class MutableCommandBuilder<C : Any> {
public fun argument( public fun argument(
description: ArgumentDescription = ArgumentDescription.empty(), description: ArgumentDescription = ArgumentDescription.empty(),
argumentSupplier: () -> CommandArgument<C, *> argumentSupplier: () -> CommandArgument<C, *>
): MutableCommandBuilder<C> = ): MutableCommandBuilder<C> = mutate { it.argument(argumentSupplier(), description) }
mutate { it.argument(argumentSupplier(), description) }
/** /**
* Add a new literal argument to this command * Add a new literal argument to this command
@ -592,13 +570,14 @@ public class MutableCommandBuilder<C : Any> {
* @since 1.3.0 * @since 1.3.0
*/ */
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) @Deprecated(
message = "ArgumentDescription should be used over Description",
level = DeprecationLevel.HIDDEN)
public fun literal( public fun literal(
name: String, name: String,
description: Description = Description.empty(), description: Description = Description.empty(),
vararg aliases: String vararg aliases: String
): MutableCommandBuilder<C> = ): MutableCommandBuilder<C> = mutate { it.literal(name, description, *aliases) }
mutate { it.literal(name, description, *aliases) }
/** /**
* Add a new literal argument to this command * Add a new literal argument to this command
@ -613,8 +592,7 @@ public class MutableCommandBuilder<C : Any> {
name: String, name: String,
description: ArgumentDescription = ArgumentDescription.empty(), description: ArgumentDescription = ArgumentDescription.empty(),
vararg aliases: String vararg aliases: String
): MutableCommandBuilder<C> = ): MutableCommandBuilder<C> = mutate { it.literal(name, description, *aliases) }
mutate { it.literal(name, description, *aliases) }
/** /**
* Set the [CommandExecutionHandler] for this builder * Set the [CommandExecutionHandler] for this builder
@ -623,10 +601,9 @@ public class MutableCommandBuilder<C : Any> {
* @return this mutable builder * @return this mutable builder
* @since 1.3.0 * @since 1.3.0
*/ */
public fun handler( public fun handler(handler: CommandExecutionHandler<C>): MutableCommandBuilder<C> = mutate {
handler: CommandExecutionHandler<C> it.handler(handler)
): MutableCommandBuilder<C> = }
mutate { it.handler(handler) }
/** /**
* Add a new flag argument to this command * Add a new flag argument to this command
@ -645,12 +622,12 @@ public class MutableCommandBuilder<C : Any> {
argumentSupplier: () -> CommandArgument<C, *> argumentSupplier: () -> CommandArgument<C, *>
): MutableCommandBuilder<C> = mutate { ): MutableCommandBuilder<C> = mutate {
it.flag( it.flag(
this.commandManager.flagBuilder(name) this.commandManager
.flagBuilder(name)
.withAliases(*aliases) .withAliases(*aliases)
.withDescription(description) .withDescription(description)
.withArgument(argumentSupplier()) .withArgument(argumentSupplier())
.build() .build())
)
} }
/** /**
@ -670,12 +647,12 @@ public class MutableCommandBuilder<C : Any> {
argument: CommandArgument<C, *> argument: CommandArgument<C, *>
): MutableCommandBuilder<C> = mutate { ): MutableCommandBuilder<C> = mutate {
it.flag( it.flag(
this.commandManager.flagBuilder(name) this.commandManager
.flagBuilder(name)
.withAliases(*aliases) .withAliases(*aliases)
.withDescription(description) .withDescription(description)
.withArgument(argument) .withArgument(argument)
.build() .build())
)
} }
/** /**
@ -695,12 +672,12 @@ public class MutableCommandBuilder<C : Any> {
argumentBuilder: CommandArgument.Builder<C, *> argumentBuilder: CommandArgument.Builder<C, *>
): MutableCommandBuilder<C> = mutate { ): MutableCommandBuilder<C> = mutate {
it.flag( it.flag(
this.commandManager.flagBuilder(name) this.commandManager
.flagBuilder(name)
.withAliases(*aliases) .withAliases(*aliases)
.withDescription(description) .withDescription(description)
.withArgument(argumentBuilder) .withArgument(argumentBuilder)
.build() .build())
)
} }
/** /**
@ -718,10 +695,10 @@ public class MutableCommandBuilder<C : Any> {
description: ArgumentDescription = ArgumentDescription.empty(), description: ArgumentDescription = ArgumentDescription.empty(),
): MutableCommandBuilder<C> = mutate { ): MutableCommandBuilder<C> = mutate {
it.flag( it.flag(
this.commandManager.flagBuilder(name) this.commandManager
.flagBuilder(name)
.withAliases(*aliases) .withAliases(*aliases)
.withDescription(description) .withDescription(description)
.build() .build())
)
} }
} }

View file

@ -0,0 +1,51 @@
package cloud.commandframework.kotlin.coroutines
import cloud.commandframework.annotations.AnnotationParser
import cloud.commandframework.annotations.MethodCommandExecutionHandler
import cloud.commandframework.context.CommandContext
import cloud.commandframework.execution.CommandExecutionCoordinator
import java.lang.reflect.Method
import java.util.concurrent.CompletableFuture
import java.util.function.Predicate
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.reflect.full.callSuspend
import kotlin.reflect.jvm.kotlinFunction
import kotlinx.coroutines.*
import kotlinx.coroutines.future.asCompletableFuture
/** Adds coroutine support to the [AnnotationParser]. */
public fun <C> AnnotationParser<C>.installCoroutineSupport(
scope: CoroutineScope = GlobalScope,
context: CoroutineContext = EmptyCoroutineContext
) {
if (manager().commandExecutionCoordinator() is CommandExecutionCoordinator.SimpleCoordinator) {
RuntimeException(
"""You are highly advised to not use the simple command execution coordinator together
with coroutine support. Consider using the asynchronous command execution coordinator instead.""")
.printStackTrace()
}
val predicate = Predicate<Method> { it.kotlinFunction?.isSuspend == true }
registerCommandExecutionMethodFactory(predicate) {
KotlinMethodCommandExecutionHandler(scope, context, it)
}
}
private class KotlinMethodCommandExecutionHandler<C>(
private val coroutineScope: CoroutineScope,
private val coroutineContext: CoroutineContext,
context: CommandMethodContext<C>
) : MethodCommandExecutionHandler<C>(context) {
override fun executeFuture(commandContext: CommandContext<C>): CompletableFuture<Any?> {
val instance = context().instance()
val params = createParameterValues(commandContext, commandContext.flags(), false)
// We need to propagate exceptions to the caller.
return coroutineScope
.async(this@KotlinMethodCommandExecutionHandler.coroutineContext) {
context().method().kotlinFunction?.callSuspend(instance, *params.toTypedArray())
}
.asCompletableFuture()
}
}

View file

@ -1,7 +1,7 @@
// //
// MIT License // MIT License
// //
// Copyright (c) 2021 Alexander Söderberg & Contributors // Copyright (c) 2021 Alexander Söderberg & Contributors
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -40,14 +40,15 @@ import kotlin.reflect.KClass
* @since 1.3.0 * @since 1.3.0
*/ */
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) @Deprecated(
message = "ArgumentDescription should be used over Description",
level = DeprecationLevel.HIDDEN)
public fun <C : Any> CommandManager<C>.commandBuilder( public fun <C : Any> CommandManager<C>.commandBuilder(
name: String, name: String,
description: Description = Description.empty(), description: Description = Description.empty(),
aliases: Array<String> = emptyArray(), aliases: Array<String> = emptyArray(),
lambda: MutableCommandBuilder<C>.() -> Unit lambda: MutableCommandBuilder<C>.() -> Unit
): MutableCommandBuilder<C> = ): MutableCommandBuilder<C> = MutableCommandBuilder(name, description, aliases, this, lambda)
MutableCommandBuilder(name, description, aliases, this, lambda)
/** /**
* Create a new [MutableCommandBuilder] and invoke the provided receiver lambda on it * Create a new [MutableCommandBuilder] and invoke the provided receiver lambda on it
@ -63,12 +64,11 @@ public fun <C : Any> CommandManager<C>.commandBuilder(
description: ArgumentDescription = ArgumentDescription.empty(), description: ArgumentDescription = ArgumentDescription.empty(),
aliases: Array<String> = emptyArray(), aliases: Array<String> = emptyArray(),
lambda: MutableCommandBuilder<C>.() -> Unit lambda: MutableCommandBuilder<C>.() -> Unit
): MutableCommandBuilder<C> = ): MutableCommandBuilder<C> = MutableCommandBuilder(name, description, aliases, this, lambda)
MutableCommandBuilder(name, description, aliases, this, lambda)
/** /**
* Create a new [MutableCommandBuilder] which will invoke the provided receiver lambda, and then register itself with the * Create a new [MutableCommandBuilder] which will invoke the provided receiver lambda, and then
* owning [CommandManager] * register itself with the owning [CommandManager]
* *
* @param name name for the root command node * @param name name for the root command node
* @param description description for the root command node * @param description description for the root command node
@ -77,18 +77,19 @@ public fun <C : Any> CommandManager<C>.commandBuilder(
* @since 1.3.0 * @since 1.3.0
*/ */
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) @Deprecated(
message = "ArgumentDescription should be used over Description",
level = DeprecationLevel.HIDDEN)
public fun <C : Any> CommandManager<C>.buildAndRegister( public fun <C : Any> CommandManager<C>.buildAndRegister(
name: String, name: String,
description: Description = Description.empty(), description: Description = Description.empty(),
aliases: Array<String> = emptyArray(), aliases: Array<String> = emptyArray(),
lambda: MutableCommandBuilder<C>.() -> Unit lambda: MutableCommandBuilder<C>.() -> Unit
): MutableCommandBuilder<C> = ): MutableCommandBuilder<C> = commandBuilder(name, description, aliases, lambda).register()
commandBuilder(name, description, aliases, lambda).register()
/** /**
* Create a new [MutableCommandBuilder] which will invoke the provided receiver lambda, and then register itself with the * Create a new [MutableCommandBuilder] which will invoke the provided receiver lambda, and then
* owning [CommandManager] * register itself with the owning [CommandManager]
* *
* @param name name for the root command node * @param name name for the root command node
* @param description description for the root command node * @param description description for the root command node
@ -101,11 +102,11 @@ public fun <C : Any> CommandManager<C>.buildAndRegister(
description: ArgumentDescription = ArgumentDescription.empty(), description: ArgumentDescription = ArgumentDescription.empty(),
aliases: Array<String> = emptyArray(), aliases: Array<String> = emptyArray(),
lambda: MutableCommandBuilder<C>.() -> Unit lambda: MutableCommandBuilder<C>.() -> Unit
): MutableCommandBuilder<C> = ): MutableCommandBuilder<C> = commandBuilder(name, description, aliases, lambda).register()
commandBuilder(name, description, aliases, lambda).register()
/** /**
* Build the provided [MutableCommandBuilder]s into [Command]s, and then register them with the command manager * Build the provided [MutableCommandBuilder]s into [Command]s, and then register them with the
* command manager
* *
* @param commands mutable command builder(s) to register * @param commands mutable command builder(s) to register
* @return the command manager * @return the command manager
@ -114,12 +115,7 @@ public fun <C : Any> CommandManager<C>.buildAndRegister(
*/ */
public fun <C : Any> CommandManager<C>.command( public fun <C : Any> CommandManager<C>.command(
vararg commands: MutableCommandBuilder<C> vararg commands: MutableCommandBuilder<C>
): CommandManager<C> = ): CommandManager<C> = apply { commands.forEach { command -> this.command(command.build()) } }
apply {
commands.forEach { command ->
this.command(command.build())
}
}
/** /**
* Specify a required sender type * Specify a required sender type
@ -141,11 +137,8 @@ public fun <C : Any> Command.Builder<C>.senderType(type: KClass<out C>): Command
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@Deprecated( @Deprecated(
message = "Use interface variant that allows for rich text", message = "Use interface variant that allows for rich text",
replaceWith = ReplaceWith("argumentDescription(description)") replaceWith = ReplaceWith("argumentDescription(description)"))
) public fun description(description: String = ""): Description =
public fun description(
description: String = ""
): Description =
if (description.isEmpty()) Description.empty() else Description.of(description) if (description.isEmpty()) Description.empty() else Description.of(description)
/** /**
@ -155,7 +148,5 @@ public fun description(
* @return the description * @return the description
* @since 1.4.0 * @since 1.4.0
*/ */
public fun argumentDescription( public fun argumentDescription(description: String = ""): ArgumentDescription =
description: String = ""
): ArgumentDescription =
if (description.isEmpty()) ArgumentDescription.empty() else ArgumentDescription.of(description) if (description.isEmpty()) ArgumentDescription.empty() else ArgumentDescription.of(description)

View file

@ -0,0 +1,68 @@
package cloud.commandframework.kotlin.coroutines
import cloud.commandframework.CommandManager
import cloud.commandframework.annotations.AnnotationParser
import cloud.commandframework.annotations.CommandMethod
import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator
import cloud.commandframework.internal.CommandRegistrationHandler
import cloud.commandframework.meta.CommandMeta
import cloud.commandframework.meta.SimpleCommandMeta
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.future.await
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
class KotlinAnnotatedMethodsTest {
companion object {
val executorService = Executors.newSingleThreadExecutor()
}
private lateinit var commandManager: CommandManager<TestCommandSender>
@BeforeEach
fun setUp() {
commandManager = TestCommandManager()
}
private fun awaitCommands() {
executorService.shutdown()
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS)
}
@Test
fun `test suspending command methods`(): Unit = runBlocking {
AnnotationParser(commandManager, TestCommandSender::class.java) {
SimpleCommandMeta.empty()
}.also { it.installCoroutineSupport() }.parse(CommandMethods())
commandManager.executeCommand(TestCommandSender(), "test").await()
}
private class TestCommandSender {}
private class TestCommandManager : CommandManager<TestCommandSender>(
AsynchronousCommandExecutionCoordinator.newBuilder<TestCommandSender>().withExecutor(executorService).build(),
CommandRegistrationHandler.nullCommandRegistrationHandler()
) {
override fun hasPermission(sender: TestCommandSender, permission: String): Boolean = true
override fun createDefaultCommandMeta(): CommandMeta = SimpleCommandMeta.empty()
}
public class CommandMethods {
@CommandMethod("test")
public suspend fun suspendingCommand(): Unit = withContext(Dispatchers.Default) {
println("called from thread: ${Thread.currentThread().name}")
}
}
}