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 137bf229..ca882005 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java @@ -23,9 +23,9 @@ // package cloud.commandframework.annotations; +import cloud.commandframework.ArgumentDescription; import cloud.commandframework.Command; import cloud.commandframework.CommandManager; -import cloud.commandframework.Description; import cloud.commandframework.annotations.injection.ParameterInjectorRegistry; import cloud.commandframework.annotations.injection.RawArgs; import cloud.commandframework.annotations.parsers.MethodArgumentParser; @@ -434,7 +434,7 @@ public final class AnnotationParser { } final String description = argumentDescriptions.getOrDefault(argument, ""); - builder = builder.argument(argument, Description.of(description)); + builder = builder.argument(argument, ArgumentDescription.of(description)); } } /* Try to find the command sender type */ diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/FlagExtractor.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/FlagExtractor.java index 5054b9b0..2fa6df29 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/FlagExtractor.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/FlagExtractor.java @@ -23,8 +23,8 @@ // package cloud.commandframework.annotations; +import cloud.commandframework.ArgumentDescription; import cloud.commandframework.CommandManager; -import cloud.commandframework.Description; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.flags.CommandFlag; import cloud.commandframework.arguments.parser.ArgumentParser; @@ -60,7 +60,7 @@ final class FlagExtractor implements Function<@NonNull Method, Collection<@NonNu final Flag flag = parameter.getAnnotation(Flag.class); final CommandFlag.Builder builder = this.commandManager .flagBuilder(flag.value()) - .withDescription(Description.of(flag.description())) + .withDescription(ArgumentDescription.of(flag.description())) .withAliases(flag.aliases()); if (parameter.getType().equals(boolean.class)) { flags.add(builder.build()); diff --git a/cloud-core/src/main/java/cloud/commandframework/ArgumentDescription.java b/cloud-core/src/main/java/cloud/commandframework/ArgumentDescription.java new file mode 100644 index 00000000..6bf4470b --- /dev/null +++ b/cloud-core/src/main/java/cloud/commandframework/ArgumentDescription.java @@ -0,0 +1,80 @@ +// +// 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; + +import cloud.commandframework.arguments.CommandArgument; +import org.checkerframework.checker.nullness.qual.NonNull; + +import static java.util.Objects.requireNonNull; + +/** + * A description for a {@link CommandArgument} + * + * @since 1.4.0 + */ +public interface ArgumentDescription { + + /** + * Get an empty command description. + * + * @return Command description + */ + @SuppressWarnings("deprecation") + static @NonNull ArgumentDescription empty() { + return Description.EMPTY; + } + + /** + * Create a command description instance. + * + * @param string Command description + * @return Created command description + */ + @SuppressWarnings("deprecation") + static @NonNull ArgumentDescription of(final @NonNull String string) { + if (requireNonNull(string, "string").isEmpty()) { + return Description.EMPTY; + } else { + return new Description(string); + } + } + + /** + * Get the plain-text description. + * + * @return Command description + */ + @NonNull String getDescription(); + + /** + * Get whether or not this description contains contents. + * + * @return if this description is empty or not + */ + default boolean isEmpty() { + return this.getDescription().isEmpty(); + } + +} diff --git a/cloud-core/src/main/java/cloud/commandframework/Command.java b/cloud-core/src/main/java/cloud/commandframework/Command.java index 191eda93..c4ea8ebc 100644 --- a/cloud-core/src/main/java/cloud/commandframework/Command.java +++ b/cloud-core/src/main/java/cloud/commandframework/Command.java @@ -209,6 +209,7 @@ public class Command { // Converts a map of CommandArgument and Description pairs to a List of CommandComponent // Used for backwards-compatibility + @SuppressWarnings("deprecation") private static @NonNull List<@NonNull CommandComponent> mapToComponents( final @NonNull Map<@NonNull CommandArgument, @NonNull Description> commandArguments ) { @@ -227,12 +228,35 @@ public class Command { * @param aliases Command aliases * @param Command sender type * @return Command builder + * @deprecated for removal since 1.4.0. Use {@link #newBuilder(String, CommandMeta, ArgumentDescription, String...)} instead. */ + @Deprecated public static @NonNull Builder newBuilder( final @NonNull String commandName, final @NonNull CommandMeta commandMeta, final @NonNull Description description, final @NonNull String... aliases + ) { + return newBuilder(commandName, commandMeta, (ArgumentDescription) description, aliases); + } + + /** + * Create a new command builder. Is recommended to use the builder methods + * in {@link CommandManager} rather than invoking this method directly. + * + * @param commandName Base command argument + * @param commandMeta Command meta instance + * @param description Command description + * @param aliases Command aliases + * @param Command sender type + * @return Command builder + * @since 1.4.0 + */ + public static @NonNull Builder newBuilder( + final @NonNull String commandName, + final @NonNull CommandMeta commandMeta, + final @NonNull ArgumentDescription description, + final @NonNull String... aliases ) { final List> commands = new ArrayList<>(); commands.add(CommandComponent.of(StaticArgument.of(commandName, aliases), description)); @@ -263,7 +287,7 @@ public class Command { final @NonNull String... aliases ) { final List> commands = new ArrayList<>(); - commands.add(CommandComponent.of(StaticArgument.of(commandName, aliases), Description.empty())); + commands.add(CommandComponent.of(StaticArgument.of(commandName, aliases), ArgumentDescription.empty())); return new Builder<>( null, commandMeta, @@ -343,7 +367,7 @@ public class Command { public @NonNull String getArgumentDescription(final @NonNull CommandArgument argument) { for (final CommandComponent component : this.components) { if (component.getArgument().equals(argument)) { - return component.getDescription().getDescription(); + return component.getArgumentDescription().getDescription(); } } throw new IllegalArgumentException("Command argument not found: " + argument); @@ -512,7 +536,9 @@ public class Command { * @param description Literal description * @param aliases Argument aliases * @return New builder instance with the modified command chain + * @deprecated for removal since 1.4.0. Use {@link #literal(String, ArgumentDescription, String...)} instead. */ + @Deprecated public @NonNull Builder literal( final @NonNull String main, final @NonNull Description description, @@ -521,6 +547,23 @@ public class Command { return this.argument(StaticArgument.of(main, aliases), description); } + /** + * Inserts a required {@link StaticArgument} into the command chain + * + * @param main Main argument name + * @param description Literal description + * @param aliases Argument aliases + * @return New builder instance with the modified command chain + * @since 1.4.0 + */ + public @NonNull Builder literal( + final @NonNull String main, + final @NonNull ArgumentDescription description, + final @NonNull String... aliases + ) { + return this.argument(StaticArgument.of(main, aliases), description); + } + /** * Add a new command argument with an empty description to the command * @@ -530,7 +573,7 @@ public class Command { * @return New builder instance with the command argument inserted into the argument list */ public @NonNull Builder argument(final CommandArgument.@NonNull Builder builder) { - return this.argument(builder.build(), Description.empty()); + return this.argument(builder.build(), ArgumentDescription.empty()); } /** @@ -541,7 +584,7 @@ public class Command { * @return New builder instance with the command argument inserted into the argument list */ public @NonNull Builder argument(final @NonNull CommandArgument argument) { - return this.argument(argument, Description.empty()); + return this.argument(argument, ArgumentDescription.empty()); } /** @@ -551,10 +594,28 @@ public class Command { * @param description Argument description * @param Argument type * @return New builder instance with the command argument inserted into the argument list + * @deprecated for removal since 1.4.0. Use {@link #argument(CommandArgument, ArgumentDescription)} instead. */ + @Deprecated public @NonNull Builder argument( final @NonNull CommandArgument argument, final @NonNull Description description + ) { + return this.argument(argument, (ArgumentDescription) description); + } + + /** + * Add a new command argument to the command + * + * @param argument Argument to add + * @param description Argument description + * @param Argument type + * @return New builder instance with the command argument inserted into the argument list + * @since 1.4.0 + */ + public @NonNull Builder argument( + final @NonNull CommandArgument argument, + final @NonNull ArgumentDescription description ) { if (argument.isArgumentRegistered()) { throw new IllegalArgumentException("The provided argument has already been associated with a command." @@ -582,10 +643,29 @@ public class Command { * @param description Argument description * @param Argument type * @return New builder instance with the command argument inserted into the argument list + * @deprecated for removal since 1.4.0. Use {@link #argument(CommandArgument.Builder, ArgumentDescription)} instead. */ + @Deprecated public @NonNull Builder argument( final CommandArgument.@NonNull Builder builder, final @NonNull Description description + ) { + return this.argument(builder, (ArgumentDescription) description); + } + + /** + * Add a new command argument to the command + * + * @param builder Argument to add. {@link CommandArgument.Builder#build()} will be invoked + * and the result will be registered in the command. + * @param description Argument description + * @param Argument type + * @return New builder instance with the command argument inserted into the argument list + * @since 1.4.0 + */ + public @NonNull Builder argument( + final CommandArgument.@NonNull Builder builder, + final @NonNull ArgumentDescription description ) { final List> commandComponents = new ArrayList<>(this.commandComponents); commandComponents.add(CommandComponent.of(builder.build(), description)); @@ -640,12 +720,41 @@ public class Command { * @param First type * @param Second type * @return Builder instance with the argument inserted + * @deprecated for removal since 1.4.0. Use {@link #argumentPair(String, Pair, Pair, ArgumentDescription)} instead. */ + @Deprecated public @NonNull Builder argumentPair( final @NonNull String name, final @NonNull Pair<@NonNull String, @NonNull String> names, final @NonNull Pair<@NonNull Class, @NonNull Class> parserPair, final @NonNull Description description + ) { + return this.argumentPair(name, names, parserPair, (ArgumentDescription) description); + } + + /** + * Create a new argument pair that maps to {@link Pair} + *

+ * For this to work, there must be a {@link CommandManager} + * attached to the command builder. To guarantee this, it is recommended to get the command builder instance + * using {@link CommandManager#commandBuilder(String, String...)} + * + * @param name Name of the argument + * @param names Pair containing the names of the sub-arguments + * @param parserPair Pair containing the types of the sub-arguments. There must be parsers for these types registered + * in the {@link cloud.commandframework.arguments.parser.ParserRegistry} used by the + * {@link CommandManager} attached to this command + * @param description Description of the argument + * @param First type + * @param Second type + * @return Builder instance with the argument inserted + * @since 1.4.0 + */ + public @NonNull Builder argumentPair( + final @NonNull String name, + final @NonNull Pair<@NonNull String, @NonNull String> names, + final @NonNull Pair<@NonNull Class, @NonNull Class> parserPair, + final @NonNull ArgumentDescription description ) { if (this.commandManager == null) { throw new IllegalStateException("This cannot be called from a command that has no command manager attached"); @@ -672,7 +781,10 @@ public class Command { * @param Second type * @param Output type * @return Builder instance with the argument inserted + * @deprecated for removal since 1.4.0. Use + * {@link #argumentPair(String, TypeToken, Pair, Pair, BiFunction, ArgumentDescription)} instead. */ + @Deprecated public @NonNull Builder argumentPair( final @NonNull String name, final @NonNull TypeToken outputType, @@ -680,6 +792,38 @@ public class Command { final @NonNull Pair, Class> parserPair, final @NonNull BiFunction, O> mapper, final @NonNull Description description + ) { + return this.argumentPair(name, outputType, names, parserPair, mapper, (ArgumentDescription) description); + } + + /** + * Create a new argument pair that maps to a custom type. + *

+ * For this to work, there must be a {@link CommandManager} + * attached to the command builder. To guarantee this, it is recommended to get the command builder instance + * using {@link CommandManager#commandBuilder(String, String...)} + * + * @param name Name of the argument + * @param outputType The output type + * @param names Pair containing the names of the sub-arguments + * @param parserPair Pair containing the types of the sub-arguments. There must be parsers for these types registered + * in the {@link cloud.commandframework.arguments.parser.ParserRegistry} used by the + * {@link CommandManager} attached to this command + * @param mapper Mapper that maps from {@link Pair} to the custom type + * @param description Description of the argument + * @param First type + * @param Second type + * @param Output type + * @return Builder instance with the argument inserted + * @since 1.4.0 + */ + public @NonNull Builder argumentPair( + final @NonNull String name, + final @NonNull TypeToken outputType, + final @NonNull Pair names, + final @NonNull Pair, Class> parserPair, + final @NonNull BiFunction, O> mapper, + final @NonNull ArgumentDescription description ) { if (this.commandManager == null) { throw new IllegalStateException("This cannot be called from a command that has no command manager attached"); @@ -707,12 +851,43 @@ public class Command { * @param Second type * @param Third type * @return Builder instance with the argument inserted + * @deprecated for removal since 1.4.0. Use {@link #argumentTriplet(String, Triplet, Triplet, ArgumentDescription)} + * instead. */ + @Deprecated public @NonNull Builder argumentTriplet( final @NonNull String name, final @NonNull Triplet names, final @NonNull Triplet, Class, Class> parserTriplet, final @NonNull Description description + ) { + return this.argumentTriplet(name, names, parserTriplet, (ArgumentDescription) description); + } + + /** + * Create a new argument pair that maps to {@link cloud.commandframework.types.tuples.Triplet} + *

+ * For this to work, there must be a {@link CommandManager} + * attached to the command builder. To guarantee this, it is recommended to get the command builder instance + * using {@link CommandManager#commandBuilder(String, String...)} + * + * @param name Name of the argument + * @param names Triplet containing the names of the sub-arguments + * @param parserTriplet Triplet containing the types of the sub-arguments. There must be parsers for these types + * registered in the {@link cloud.commandframework.arguments.parser.ParserRegistry} + * used by the {@link CommandManager} attached to this command + * @param description Description of the argument + * @param First type + * @param Second type + * @param Third type + * @return Builder instance with the argument inserted + * @since 1.4.0 + */ + public @NonNull Builder argumentTriplet( + final @NonNull String name, + final @NonNull Triplet names, + final @NonNull Triplet, Class, Class> parserTriplet, + final @NonNull ArgumentDescription description ) { if (this.commandManager == null) { throw new IllegalStateException("This cannot be called from a command that has no command manager attached"); @@ -740,15 +915,57 @@ public class Command { * @param Third type * @param Output type * @return Builder instance with the argument inserted + * @deprecated for removal since 1.4.0, use + * {@link #argumentTriplet(String, TypeToken, Triplet, Triplet, BiFunction, ArgumentDescription)} instead. + */ + @Deprecated + public @NonNull Builder argumentTriplet( + final @NonNull String name, + final @NonNull TypeToken outputType, + final @NonNull Triplet names, + final @NonNull Triplet, Class, Class> parserTriplet, + final @NonNull BiFunction, O> mapper, + final @NonNull Description description + ) { + return this.argumentTriplet( + name, + outputType, + names, + parserTriplet, + mapper, + (ArgumentDescription) description + ); + } + + /** + * Create a new argument triplet that maps to a custom type. + *

+ * For this to work, there must be a {@link CommandManager} + * attached to the command builder. To guarantee this, it is recommended to get the command builder instance + * using {@link CommandManager#commandBuilder(String, String...)} + * + * @param name Name of the argument + * @param outputType The output type + * @param names Triplet containing the names of the sub-arguments + * @param parserTriplet Triplet containing the types of the sub-arguments. There must be parsers for these types + * registered in the {@link cloud.commandframework.arguments.parser.ParserRegistry} used by + * the {@link CommandManager} attached to this command + * @param mapper Mapper that maps from {@link Triplet} to the custom type + * @param description Description of the argument + * @param First type + * @param Second type + * @param Third type + * @param Output type + * @return Builder instance with the argument inserted + * @since 1.4.0 */ public @NonNull Builder argumentTriplet( final @NonNull String name, final @NonNull TypeToken outputType, final @NonNull Triplet names, - final @NonNull Triplet, Class, - Class> parserTriplet, + final @NonNull Triplet, Class, Class> parserTriplet, final @NonNull BiFunction, O> mapper, - final @NonNull Description description + final @NonNull ArgumentDescription description ) { if (this.commandManager == null) { throw new IllegalStateException("This cannot be called from a command that has no command manager attached"); @@ -852,7 +1069,7 @@ public class Command { continue; } final CommandArgument builtArgument = argument.copy(); - builder = builder.argument(builtArgument, component.getDescription()); + builder = builder.argument(builtArgument, component.getArgumentDescription()); } if (this.commandPermission.toString().isEmpty()) { builder = builder.permission(command.getCommandPermission()); @@ -910,9 +1127,9 @@ public class Command { public @NonNull Command build() { final List> commandComponents = new ArrayList<>(this.commandComponents); /* Construct flag node */ - if (!flags.isEmpty()) { + if (!this.flags.isEmpty()) { final FlagArgument flagArgument = new FlagArgument<>(this.flags); - commandComponents.add(CommandComponent.of(flagArgument, Description.of("Command flags"))); + commandComponents.add(CommandComponent.of(flagArgument, ArgumentDescription.of("Command flags"))); } return new Command<>( Collections.unmodifiableList(commandComponents), diff --git a/cloud-core/src/main/java/cloud/commandframework/CommandComponent.java b/cloud-core/src/main/java/cloud/commandframework/CommandComponent.java index c34a9bc6..6173c43f 100644 --- a/cloud-core/src/main/java/cloud/commandframework/CommandComponent.java +++ b/cloud-core/src/main/java/cloud/commandframework/CommandComponent.java @@ -37,7 +37,7 @@ import java.util.Objects; public final class CommandComponent { private final CommandArgument argument; - private final Description description; + private final ArgumentDescription description; /** * Initializes a new CommandComponent @@ -47,7 +47,7 @@ public final class CommandComponent { */ private CommandComponent( final @NonNull CommandArgument commandArgument, - final @NonNull Description commandDescription + final @NonNull ArgumentDescription commandDescription ) { this.argument = commandArgument; this.description = commandDescription; @@ -66,14 +66,30 @@ public final class CommandComponent { * Gets the command component description * * @return command component description + * @deprecated for removal since 1.4.0. Use {@link #getArgumentDescription()} instead. */ + @Deprecated public @NonNull Description getDescription() { + if (this.description instanceof Description) { + return (Description) this.description; + } else { + return new Description(this.description.getDescription()); + } + } + + /** + * Gets the command component description + * + * @return command component description + * @since 1.4.0 + */ + public @NonNull ArgumentDescription getArgumentDescription() { return this.description; } @Override public int hashCode() { - return Objects.hash(getArgument(), getDescription()); + return Objects.hash(this.getArgument(), this.getArgumentDescription()); } @Override @@ -81,9 +97,9 @@ public final class CommandComponent { if (this == o) { return true; } else if (o instanceof CommandComponent) { - CommandComponent that = (CommandComponent) o; - return getArgument().equals(that.getArgument()) - && getDescription().equals(that.getDescription()); + final CommandComponent that = (CommandComponent) o; + return this.getArgument().equals(that.getArgument()) + && this.getArgumentDescription().equals(that.getArgumentDescription()); } else { return false; } @@ -102,11 +118,28 @@ public final class CommandComponent { * @param commandArgument Command Component Argument * @param commandDescription Command Component Description * @return new CommandComponent + * @deprecated for removal since 1.4.0. Use {@link #of(CommandArgument, ArgumentDescription)} instead. */ + @Deprecated public static @NonNull CommandComponent of( final @NonNull CommandArgument commandArgument, final @NonNull Description commandDescription ) { return new CommandComponent(commandArgument, commandDescription); } + + /** + * Creates a new CommandComponent with the provided argument and description + * + * @param Command sender type + * @param commandArgument Command Component Argument + * @param commandDescription Command Component Description + * @return new CommandComponent + */ + public static @NonNull CommandComponent of( + final @NonNull CommandArgument commandArgument, + final @NonNull ArgumentDescription commandDescription + ) { + return new CommandComponent(commandArgument, commandDescription); + } } diff --git a/cloud-core/src/main/java/cloud/commandframework/CommandManager.java b/cloud-core/src/main/java/cloud/commandframework/CommandManager.java index f37f6a87..4d1565db 100644 --- a/cloud-core/src/main/java/cloud/commandframework/CommandManager.java +++ b/cloud-core/src/main/java/cloud/commandframework/CommandManager.java @@ -353,12 +353,40 @@ public abstract class CommandManager { * @param description Description for the root literal * @param meta Command meta * @return Builder instance + * @deprecated for removal since 1.4.0. Use {@link #commandBuilder(String, Collection, Description, CommandMeta)} instead. */ + @Deprecated public Command.@NonNull Builder commandBuilder( final @NonNull String name, final @NonNull Collection aliases, final @NonNull Description description, final @NonNull CommandMeta meta + ) { + return commandBuilder(name, aliases, (ArgumentDescription) description, meta); + } + + /** + * Create a new command builder. This will also register the creating manager in the command + * builder using {@link Command.Builder#manager(CommandManager)}, so that the command + * builder is associated with the creating manager. This allows for parser inference based on + * the type, with the help of the {@link ParserRegistry parser registry} + *

+ * This method will not register the command in the manager. To do that, {@link #command(Command.Builder)} + * or {@link #command(Command)} has to be invoked with either the {@link Command.Builder} instance, or the constructed + * {@link Command command} instance + * + * @param name Command name + * @param aliases Command aliases + * @param description Description for the root literal + * @param meta Command meta + * @return Builder instance + * @since 1.4.0 + */ + public Command.@NonNull Builder commandBuilder( + final @NonNull String name, + final @NonNull Collection aliases, + final @NonNull ArgumentDescription description, + final @NonNull CommandMeta meta ) { return Command.newBuilder( name, @@ -393,7 +421,7 @@ public abstract class CommandManager { return Command.newBuilder( name, meta, - Description.empty(), + ArgumentDescription.empty(), aliases.toArray(new String[0]) ).manager(this); } @@ -413,12 +441,41 @@ public abstract class CommandManager { * @param description Description for the root literal * @param aliases Command aliases * @return Builder instance + * @deprecated for removal since 1.4.0. Use {@link #commandBuilder(String, CommandMeta, ArgumentDescription, String...)} + * instead. */ + @Deprecated public Command.@NonNull Builder commandBuilder( final @NonNull String name, final @NonNull CommandMeta meta, final @NonNull Description description, final @NonNull String... aliases + ) { + return this.commandBuilder(name, meta, (ArgumentDescription) description, aliases); + } + + /** + * Create a new command builder. This will also register the creating manager in the command + * builder using {@link Command.Builder#manager(CommandManager)}, so that the command + * builder is associated with the creating manager. This allows for parser inference based on + * the type, with the help of the {@link ParserRegistry parser registry} + *

+ * This method will not register the command in the manager. To do that, {@link #command(Command.Builder)} + * or {@link #command(Command)} has to be invoked with either the {@link Command.Builder} instance, or the constructed + * {@link Command command} instance + * + * @param name Command name + * @param meta Command meta + * @param description Description for the root literal + * @param aliases Command aliases + * @return Builder instance + * @since 1.4.0 + */ + public Command.@NonNull Builder commandBuilder( + final @NonNull String name, + final @NonNull CommandMeta meta, + final @NonNull ArgumentDescription description, + final @NonNull String... aliases ) { return Command.newBuilder( name, @@ -453,7 +510,7 @@ public abstract class CommandManager { return Command.newBuilder( name, meta, - Description.empty(), + ArgumentDescription.empty(), aliases ).manager(this); } @@ -476,11 +533,41 @@ public abstract class CommandManager { * @return Builder instance * @throws UnsupportedOperationException If the command manager does not support default command meta creation * @see #createDefaultCommandMeta() Default command meta creation + * @deprecated for removal since 1.4.0. Use {@link #commandBuilder(String, ArgumentDescription, String...)} instead. */ + @Deprecated public Command.@NonNull Builder commandBuilder( final @NonNull String name, final @NonNull Description description, final @NonNull String... aliases + ) { + return this.commandBuilder(name, (ArgumentDescription) description, aliases); + } + + /** + * Create a new command builder using default command meta created by {@link #createDefaultCommandMeta()}. + *

+ * This will also register the creating manager in the command + * builder using {@link Command.Builder#manager(CommandManager)}, so that the command + * builder is associated with the creating manager. This allows for parser inference based on + * the type, with the help of the {@link ParserRegistry parser registry} + *

+ * This method will not register the command in the manager. To do that, {@link #command(Command.Builder)} + * or {@link #command(Command)} has to be invoked with either the {@link Command.Builder} instance, or the constructed + * {@link Command command} instance + * + * @param name Command name + * @param description Description for the root literal + * @param aliases Command aliases + * @return Builder instance + * @throws UnsupportedOperationException If the command manager does not support default command meta creation + * @see #createDefaultCommandMeta() Default command meta creation + * @since 1.4.0 + */ + public Command.@NonNull Builder commandBuilder( + final @NonNull String name, + final @NonNull ArgumentDescription description, + final @NonNull String... aliases ) { return Command.newBuilder( name, @@ -516,7 +603,7 @@ public abstract class CommandManager { return Command.newBuilder( name, this.createDefaultCommandMeta(), - Description.empty(), + ArgumentDescription.empty(), aliases ).manager(this); } diff --git a/cloud-core/src/main/java/cloud/commandframework/Description.java b/cloud-core/src/main/java/cloud/commandframework/Description.java index 4df3b2c9..fa5de816 100644 --- a/cloud-core/src/main/java/cloud/commandframework/Description.java +++ b/cloud-core/src/main/java/cloud/commandframework/Description.java @@ -28,17 +28,20 @@ import org.checkerframework.checker.nullness.qual.NonNull; /** * {@link CommandArgument} description + * + * @deprecated to become package-private since 1.4.0. Use {@link ArgumentDescription} instead. */ -public final class Description { +@Deprecated +public final class Description implements ArgumentDescription { /** * Empty command description */ - private static final Description EMPTY = Description.of(""); + static final Description EMPTY = new Description(""); private final String description; - private Description(final @NonNull String description) { + Description(final @NonNull String description) { this.description = description; } @@ -46,7 +49,9 @@ public final class Description { * Get an empty command description * * @return Command description + * @deprecated for removal since 1.4.0. See {@link ArgumentDescription#empty()} */ + @Deprecated public static @NonNull Description empty() { return EMPTY; } @@ -56,7 +61,9 @@ public final class Description { * * @param string Command description * @return Created command description + * @deprecated for removal since 1.4.0. See {@link ArgumentDescription#of(String)} */ + @Deprecated public static @NonNull Description of(final @NonNull String string) { return new Description(string); } @@ -66,6 +73,7 @@ public final class Description { * * @return Command description */ + @Override public @NonNull String getDescription() { return this.description; } diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/flags/CommandFlag.java b/cloud-core/src/main/java/cloud/commandframework/arguments/flags/CommandFlag.java index 92686f20..fbf2dd24 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/flags/CommandFlag.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/flags/CommandFlag.java @@ -23,7 +23,7 @@ // package cloud.commandframework.arguments.flags; -import cloud.commandframework.Description; +import cloud.commandframework.ArgumentDescription; import cloud.commandframework.arguments.CommandArgument; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -46,14 +46,14 @@ public final class CommandFlag { private final @NonNull String name; private final @NonNull String @NonNull [] aliases; - private final @NonNull Description description; + private final @NonNull ArgumentDescription description; private final @Nullable CommandArgument commandArgument; private CommandFlag( final @NonNull String name, final @NonNull String @NonNull [] aliases, - final @NonNull Description description, + final @NonNull ArgumentDescription description, final @Nullable CommandArgument commandArgument ) { this.name = Objects.requireNonNull(name, "name cannot be null"); @@ -95,8 +95,24 @@ public final class CommandFlag { *

* * @return Flag description + * @deprecated for removal since 1.4.0. Use {@link #getArgumentDescription()} instead. */ - public @NonNull Description getDescription() { + @Deprecated + public cloud.commandframework.@NonNull Description getDescription() { + if (this.description instanceof cloud.commandframework.Description) { + return ((cloud.commandframework.Description) this.description); + } else { + return cloud.commandframework.Description.of(this.description.getDescription()); + } + } + + /** + * Get the flag description. + * + * @return Flag description + * @since 1.4.0 + */ + public @NonNull ArgumentDescription getArgumentDescription() { return this.description; } @@ -119,16 +135,16 @@ public final class CommandFlag { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (o == null || this.getClass() != o.getClass()) { return false; } final CommandFlag that = (CommandFlag) o; - return getName().equals(that.getName()); + return this.getName().equals(that.getName()); } @Override public int hashCode() { - return Objects.hash(getName()); + return Objects.hash(this.getName()); } @@ -136,13 +152,13 @@ public final class CommandFlag { private final String name; private final String[] aliases; - private final Description description; + private final ArgumentDescription description; private final CommandArgument commandArgument; private Builder( final @NonNull String name, final @NonNull String[] aliases, - final @NonNull Description description, + final @NonNull ArgumentDescription description, final @Nullable CommandArgument commandArgument ) { this.name = name; @@ -152,7 +168,7 @@ public final class CommandFlag { } private Builder(final @NonNull String name) { - this(name, new String[0], Description.empty(), null); + this(name, new String[0], ArgumentDescription.empty(), null); } /** @@ -191,8 +207,21 @@ public final class CommandFlag { * * @param description Flag description * @return New builder instance + * @deprecated for removal since 1.4.0. Use {@link #withDescription(ArgumentDescription)} instead. */ - public Builder withDescription(final @NonNull Description description) { + @Deprecated + public Builder withDescription(final cloud.commandframework.@NonNull Description description) { + return this.withDescription((ArgumentDescription) description); + } + + /**d + * Create a new builder instance using the given flag description + * + * @param description Flag description + * @return New builder instance + * @since 1.4.0 + */ + public Builder withDescription(final @NonNull ArgumentDescription description) { return new Builder<>(this.name, this.aliases, description, this.commandArgument); } diff --git a/cloud-core/src/test/java/cloud/commandframework/CommandHelpHandlerTest.java b/cloud-core/src/test/java/cloud/commandframework/CommandHelpHandlerTest.java index 3493ed75..0c1f8605 100644 --- a/cloud-core/src/test/java/cloud/commandframework/CommandHelpHandlerTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/CommandHelpHandlerTest.java @@ -52,13 +52,13 @@ class CommandHelpHandlerTest { manager.command(manager.commandBuilder("test", meta1).literal("this").literal("thing").build()); final SimpleCommandMeta meta2 = SimpleCommandMeta.builder().with(CommandMeta.DESCRIPTION, "Command with variables").build(); manager.command(manager.commandBuilder("test", meta2).literal("int"). - argument(IntegerArgument.of("int"), Description.of("A number")).build()); + argument(IntegerArgument.of("int"), ArgumentDescription.of("A number")).build()); manager.command(manager.commandBuilder("test").argument(StringArgument.of("potato"))); manager.command(manager.commandBuilder("vec") .meta(CommandMeta.DESCRIPTION, "Takes in a vector") .argumentPair("vec", Pair.of("x", "y"), - Pair.of(Double.class, Double.class), Description.of("Vector") + Pair.of(Double.class, Double.class), ArgumentDescription.of("Vector") ) .build()); } @@ -241,7 +241,7 @@ class CommandHelpHandlerTest { while (iterator.hasNext()) { final CommandComponent component = iterator.next(); - String description = component.getDescription().getDescription(); + String description = component.getArgumentDescription().getDescription(); if (!description.isEmpty()) { description = ": " + description; } diff --git a/cloud-core/src/test/java/cloud/commandframework/CommandSuggestionsTest.java b/cloud-core/src/test/java/cloud/commandframework/CommandSuggestionsTest.java index 7af522f8..0c5ff2d5 100644 --- a/cloud-core/src/test/java/cloud/commandframework/CommandSuggestionsTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/CommandSuggestionsTest.java @@ -66,13 +66,13 @@ public class CommandSuggestionsTest { manager.command(manager.commandBuilder("com") .argumentPair("com", Pair.of("x", "y"), Pair.of(Integer.class, TestEnum.class), - Description.empty() + ArgumentDescription.empty() ) .argument(IntegerArgument.of("int"))); manager.command(manager.commandBuilder("com2") .argumentPair("com", Pair.of("x", "enum"), - Pair.of(Integer.class, TestEnum.class), Description.empty() + Pair.of(Integer.class, TestEnum.class), ArgumentDescription.empty() )); manager.command(manager.commandBuilder("flags") diff --git a/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java b/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java index 933f6912..5b3edba6 100644 --- a/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java @@ -236,9 +236,9 @@ class CommandTreeTest { // Create and register a command Command command = manager.commandBuilder("component") .literal("literal", "literalalias") - .literal("detail", Description.of("detaildescription")) + .literal("detail", ArgumentDescription.of("detaildescription")) .argument(CommandArgument.ofType(int.class, "argument"), - Description.of("argumentdescription")) + ArgumentDescription.of("argumentdescription")) .build(); manager.command(command); @@ -264,10 +264,10 @@ class CommandTreeTest { Assertions.assertEquals(TypeToken.get(int.class), arguments.get(3).getValueType()); // Check description is set for all components, is empty when not specified - Assertions.assertEquals("", components.get(0).getDescription().getDescription()); - Assertions.assertEquals("", components.get(1).getDescription().getDescription()); - Assertions.assertEquals("detaildescription", components.get(2).getDescription().getDescription()); - Assertions.assertEquals("argumentdescription", components.get(3).getDescription().getDescription()); + Assertions.assertEquals("", components.get(0).getArgumentDescription().getDescription()); + Assertions.assertEquals("", components.get(1).getArgumentDescription().getDescription()); + Assertions.assertEquals("detaildescription", components.get(2).getArgumentDescription().getDescription()); + Assertions.assertEquals("argumentdescription", components.get(3).getArgumentDescription().getDescription()); } @Test diff --git a/cloud-kotlin-extensions/src/main/kotlin/cloud/commandframework/kotlin/MutableCommandBuilder.kt b/cloud-kotlin-extensions/src/main/kotlin/cloud/commandframework/kotlin/MutableCommandBuilder.kt index 735eef50..96cc5078 100644 --- a/cloud-kotlin-extensions/src/main/kotlin/cloud/commandframework/kotlin/MutableCommandBuilder.kt +++ b/cloud-kotlin-extensions/src/main/kotlin/cloud/commandframework/kotlin/MutableCommandBuilder.kt @@ -23,6 +23,7 @@ // package cloud.commandframework.kotlin +import cloud.commandframework.ArgumentDescription import cloud.commandframework.Command import cloud.commandframework.CommandManager import cloud.commandframework.Description @@ -52,6 +53,8 @@ public class MutableCommandBuilder { * @param commandManager the command manager which will own this command * @since 1.3.0 */ + @Suppress("DEPRECATION") + @Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) public constructor( name: String, description: Description = Description.empty(), @@ -62,6 +65,25 @@ public class MutableCommandBuilder { this.commandBuilder = commandManager.commandBuilder(name, description, *aliases) } + /** + * Create a new [MutableCommandBuilder] + * + * @param name name for the root command node + * @param description description for the root command node + * @param aliases aliases for the root command node + * @param commandManager the command manager which will own this command + * @since 1.4.0 + */ + public constructor( + name: String, + description: ArgumentDescription = ArgumentDescription.empty(), + aliases: Array = emptyArray(), + commandManager: CommandManager + ) { + this.commandManager = commandManager + this.commandBuilder = commandManager.commandBuilder(name, description, *aliases) + } + /** * Create a new [MutableCommandBuilder] and invoke the provided receiver lambda on it * @@ -72,6 +94,8 @@ public class MutableCommandBuilder { * @param lambda receiver lambda which will be invoked on the new builder * @since 1.3.0 */ + @Suppress("DEPRECATION") + @Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) public constructor( name: String, description: Description = Description.empty(), @@ -82,6 +106,26 @@ public class MutableCommandBuilder { lambda(this) } + /** + * Create a new [MutableCommandBuilder] and invoke the provided receiver lambda on it + * + * @param name name for the root command node + * @param description description for the root command node + * @param aliases aliases for the root command node + * @param commandManager the command manager which will own this command + * @param lambda receiver lambda which will be invoked on the new builder + * @since 1.4.0 + */ + public constructor( + name: String, + description: ArgumentDescription = ArgumentDescription.empty(), + aliases: Array = emptyArray(), + commandManager: CommandManager, + lambda: MutableCommandBuilder.() -> Unit + ) : this(name, description, aliases, commandManager) { + lambda(this) + } + private constructor( commandManager: CommandManager, commandBuilder: Command.Builder @@ -165,6 +209,8 @@ public class MutableCommandBuilder { * @return a copy of this mutable builder * @since 1.3.0 */ + @Suppress("DEPRECATION") + @Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) public fun copy( literal: String, description: Description, @@ -175,6 +221,25 @@ public class MutableCommandBuilder { lambda(this) } + /** + * 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 description description for the literal + * @param lambda receiver lambda which will be invoked on the new builder + * @return a copy of this mutable builder + * @since 1.4.0 + */ + public fun copy( + literal: String, + description: ArgumentDescription, + lambda: MutableCommandBuilder.() -> Unit + ): MutableCommandBuilder = + copy().apply { + literal(literal, description) + lambda(this) + } + /** * Make a new copy of this [MutableCommandBuilder], append a literal, and invoke the provided receiver lambda on it * @@ -247,6 +312,8 @@ public class MutableCommandBuilder { * @see [CommandManager.command] * @since 1.3.0 */ + @Suppress("DEPRECATION") + @Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) public fun registerCopy( literal: String, description: Description, @@ -254,6 +321,25 @@ public class MutableCommandBuilder { ): MutableCommandBuilder = 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 + * the owning + * command manager + * + * @param literal name for the literal + * @param description description for the literal + * @param lambda receiver lambda which will be invoked on the new builder + * @return the new mutable builder + * @see [CommandManager.command] + * @since 1.4.0 + */ + public fun registerCopy( + literal: String, + description: ArgumentDescription, + lambda: MutableCommandBuilder.() -> Unit + ): MutableCommandBuilder = + copy(literal, description, lambda).register() + /** * Set the value for a certain [CommandMeta.Key] in the command meta storage for this builder * @@ -414,12 +500,28 @@ public class MutableCommandBuilder { * @return this mutable builder * @since 1.3.0 */ + @Suppress("DEPRECATION") + @Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) public fun argument( argument: CommandArgument, description: Description = Description.empty() ): MutableCommandBuilder = mutate { it.argument(argument, description) } + /** + * Add a new argument to this command + * + * @param argument argument to add + * @param description description of the argument + * @return this mutable builder + * @since 1.4.0 + */ + public fun argument( + argument: CommandArgument, + description: ArgumentDescription = ArgumentDescription.empty() + ): MutableCommandBuilder = + mutate { it.argument(argument, description) } + /** * Add a new argument to this command * @@ -428,12 +530,28 @@ public class MutableCommandBuilder { * @return this mutable builder * @since 1.3.0 */ + @Suppress("DEPRECATION") + @Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) public fun argument( argument: CommandArgument.Builder, description: Description = Description.empty() ): MutableCommandBuilder = mutate { it.argument(argument, description) } + /** + * Add a new argument to this command + * + * @param argument argument to add + * @param description description of the argument + * @return this mutable builder + * @since 1.4.0 + */ + public fun argument( + argument: CommandArgument.Builder, + description: ArgumentDescription = ArgumentDescription.empty() + ): MutableCommandBuilder = + mutate { it.argument(argument, description) } + /** * Add a new argument to this command * @@ -442,12 +560,28 @@ public class MutableCommandBuilder { * @return this mutable builder * @since 1.3.0 */ + @Suppress("DEPRECATION") + @Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) public fun argument( description: Description = Description.empty(), argumentSupplier: () -> CommandArgument ): MutableCommandBuilder = mutate { it.argument(argumentSupplier(), description) } + /** + * Add a new argument to this command + * + * @param description description of the argument + * @param argumentSupplier supplier of the argument to add + * @return this mutable builder + * @since 1.4.0 + */ + public fun argument( + description: ArgumentDescription = ArgumentDescription.empty(), + argumentSupplier: () -> CommandArgument + ): MutableCommandBuilder = + mutate { it.argument(argumentSupplier(), description) } + /** * Add a new literal argument to this command * @@ -457,6 +591,8 @@ public class MutableCommandBuilder { * @return this mutable builder * @since 1.3.0 */ + @Suppress("DEPRECATION") + @Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) public fun literal( name: String, description: Description = Description.empty(), @@ -464,6 +600,22 @@ public class MutableCommandBuilder { ): MutableCommandBuilder = mutate { it.literal(name, description, *aliases) } + /** + * Add a new literal argument to this command + * + * @param name main argument name + * @param description literal description + * @param aliases argument aliases + * @return this mutable builder + * @since 1.4.0 + */ + public fun literal( + name: String, + description: ArgumentDescription = ArgumentDescription.empty(), + vararg aliases: String + ): MutableCommandBuilder = + mutate { it.literal(name, description, *aliases) } + /** * Set the [CommandExecutionHandler] for this builder * @@ -489,7 +641,7 @@ public class MutableCommandBuilder { public fun flag( name: String, aliases: Array = emptyArray(), - description: Description = Description.empty(), + description: ArgumentDescription = ArgumentDescription.empty(), argumentSupplier: () -> CommandArgument ): MutableCommandBuilder = mutate { it.flag( @@ -514,7 +666,7 @@ public class MutableCommandBuilder { public fun flag( name: String, aliases: Array = emptyArray(), - description: Description = Description.empty(), + description: ArgumentDescription = ArgumentDescription.empty(), argument: CommandArgument ): MutableCommandBuilder = mutate { it.flag( @@ -539,7 +691,7 @@ public class MutableCommandBuilder { public fun flag( name: String, aliases: Array = emptyArray(), - description: Description = Description.empty(), + description: ArgumentDescription = ArgumentDescription.empty(), argumentBuilder: CommandArgument.Builder ): MutableCommandBuilder = mutate { it.flag( @@ -563,7 +715,7 @@ public class MutableCommandBuilder { public fun flag( name: String, aliases: Array = emptyArray(), - description: Description = Description.empty(), + description: ArgumentDescription = ArgumentDescription.empty(), ): MutableCommandBuilder = mutate { it.flag( this.commandManager.flagBuilder(name) diff --git a/cloud-kotlin-extensions/src/main/kotlin/cloud/commandframework/kotlin/extension/CommandBuildingExtensions.kt b/cloud-kotlin-extensions/src/main/kotlin/cloud/commandframework/kotlin/extension/CommandBuildingExtensions.kt index 65991533..ebc3d621 100644 --- a/cloud-kotlin-extensions/src/main/kotlin/cloud/commandframework/kotlin/extension/CommandBuildingExtensions.kt +++ b/cloud-kotlin-extensions/src/main/kotlin/cloud/commandframework/kotlin/extension/CommandBuildingExtensions.kt @@ -23,6 +23,7 @@ // package cloud.commandframework.kotlin.extension +import cloud.commandframework.ArgumentDescription import cloud.commandframework.Command import cloud.commandframework.CommandManager import cloud.commandframework.Description @@ -38,6 +39,8 @@ import kotlin.reflect.KClass * @param lambda receiver lambda which will be invoked on the new builder * @since 1.3.0 */ +@Suppress("DEPRECATION") +@Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) public fun CommandManager.commandBuilder( name: String, description: Description = Description.empty(), @@ -46,6 +49,23 @@ public fun CommandManager.commandBuilder( ): MutableCommandBuilder = MutableCommandBuilder(name, description, aliases, this, lambda) +/** + * Create a new [MutableCommandBuilder] and invoke the provided receiver lambda on it + * + * @param name name for the root command node + * @param description description for the root command node + * @param aliases aliases for the root command node + * @param lambda receiver lambda which will be invoked on the new builder + * @since 1.4.0 + */ +public fun CommandManager.commandBuilder( + name: String, + description: ArgumentDescription = ArgumentDescription.empty(), + aliases: Array = emptyArray(), + lambda: MutableCommandBuilder.() -> Unit +): MutableCommandBuilder = + MutableCommandBuilder(name, description, aliases, this, lambda) + /** * Create a new [MutableCommandBuilder] which will invoke the provided receiver lambda, and then register itself with the * owning [CommandManager] @@ -56,6 +76,8 @@ public fun CommandManager.commandBuilder( * @param lambda receiver lambda which will be invoked on the new builder * @since 1.3.0 */ +@Suppress("DEPRECATION") +@Deprecated(message = "ArgumentDescription should be used over Description", level = DeprecationLevel.HIDDEN) public fun CommandManager.buildAndRegister( name: String, description: Description = Description.empty(), @@ -64,6 +86,24 @@ public fun CommandManager.buildAndRegister( ): MutableCommandBuilder = commandBuilder(name, description, aliases, lambda).register() +/** + * Create a new [MutableCommandBuilder] which will invoke the provided receiver lambda, and then register itself with the + * owning [CommandManager] + * + * @param name name for the root command node + * @param description description for the root command node + * @param aliases aliases for the root command node + * @param lambda receiver lambda which will be invoked on the new builder + * @since 1.4.0 + */ +public fun CommandManager.buildAndRegister( + name: String, + description: ArgumentDescription = ArgumentDescription.empty(), + aliases: Array = emptyArray(), + lambda: MutableCommandBuilder.() -> Unit +): MutableCommandBuilder = + commandBuilder(name, description, aliases, lambda).register() + /** * Build the provided [MutableCommandBuilder]s into [Command]s, and then register them with the command manager * @@ -98,7 +138,24 @@ public fun Command.Builder.senderType(type: KClass): Command * @return the description * @since 1.3.0 */ +@Suppress("DEPRECATION") +@Deprecated( + message = "Use interface variant that allows for rich text", + replaceWith = ReplaceWith("argumentDescription(description)") +) public fun description( description: String = "" ): Description = if (description.isEmpty()) Description.empty() else Description.of(description) + +/** + * Get a [ArgumentDescription], defaulting to [ArgumentDescription.empty] + * + * @param description description string + * @return the description + * @since 1.4.0 + */ +public fun argumentDescription( + description: String = "" +): ArgumentDescription = + if (description.isEmpty()) ArgumentDescription.empty() else ArgumentDescription.of(description) diff --git a/cloud-kotlin-extensions/src/test/kotlin/cloud/commandframework/kotlin/CommandBuildingDSLTest.kt b/cloud-kotlin-extensions/src/test/kotlin/cloud/commandframework/kotlin/CommandBuildingDSLTest.kt index 3ea19986..3d3d4996 100644 --- a/cloud-kotlin-extensions/src/test/kotlin/cloud/commandframework/kotlin/CommandBuildingDSLTest.kt +++ b/cloud-kotlin-extensions/src/test/kotlin/cloud/commandframework/kotlin/CommandBuildingDSLTest.kt @@ -27,10 +27,7 @@ import cloud.commandframework.CommandManager import cloud.commandframework.arguments.standard.StringArgument import cloud.commandframework.execution.CommandExecutionCoordinator import cloud.commandframework.internal.CommandRegistrationHandler -import cloud.commandframework.kotlin.extension.buildAndRegister -import cloud.commandframework.kotlin.extension.command -import cloud.commandframework.kotlin.extension.commandBuilder -import cloud.commandframework.kotlin.extension.description +import cloud.commandframework.kotlin.extension.* import cloud.commandframework.meta.CommandMeta import cloud.commandframework.meta.SimpleCommandMeta import org.junit.jupiter.api.Assertions @@ -48,7 +45,7 @@ class CommandBuildingDSLTest { senderType() literal("dsl") - argument(description("An amazing command argument")) { + argument(argumentDescription("An amazing command argument")) { StringArgument.of("moment") } handler { diff --git a/cloud-minecraft/cloud-minecraft-extras/build.gradle.kts b/cloud-minecraft/cloud-minecraft-extras/build.gradle.kts index 8a878997..f5359b00 100644 --- a/cloud-minecraft/cloud-minecraft-extras/build.gradle.kts +++ b/cloud-minecraft/cloud-minecraft-extras/build.gradle.kts @@ -1,4 +1,5 @@ dependencies { api(project(":cloud-core")) api("net.kyori", "adventure-api", Versions.adventureApi) + api("net.kyori", "adventure-text-serializer-plain", Versions.adventureApi) } diff --git a/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftHelp.java b/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftHelp.java index 95265a49..5cc71d48 100644 --- a/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftHelp.java +++ b/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftHelp.java @@ -23,6 +23,7 @@ // package cloud.commandframework.minecraft.extras; +import cloud.commandframework.ArgumentDescription; import cloud.commandframework.Command; import cloud.commandframework.CommandComponent; import cloud.commandframework.CommandHelpHandler; @@ -488,7 +489,7 @@ public final class MinecraftHelp { final CommandComponent component = iterator.next(); final CommandArgument argument = component.getArgument(); - String syntax = this.commandManager.getCommandSyntaxFormatter() + final String syntax = this.commandManager.getCommandSyntaxFormatter() .apply(Collections.singletonList(argument), null); final TextComponent.Builder textComponent = text() @@ -502,10 +503,10 @@ public final class MinecraftHelp { ); textComponent.append(text(")", this.colors.alternateHighlight)); } - final String description = component.getDescription().getDescription(); + final ArgumentDescription description = component.getArgumentDescription(); if (!description.isEmpty()) { textComponent.append(text(" - ", this.colors.accent)); - textComponent.append(this.descriptionDecorator.apply(description).color(this.colors.text)); + textComponent.append(this.formatDescription(description).colorIfAbsent(this.colors.text)); } audience.sendMessage(textComponent); @@ -514,6 +515,14 @@ public final class MinecraftHelp { audience.sendMessage(this.footer(sender)); } + private Component formatDescription(final ArgumentDescription description) { + if (description instanceof RichDescription) { + return ((RichDescription) description).getContents(); + } else { + return this.descriptionDecorator.apply(description.getDescription()); + } + } + private @NonNull Component showingResults( final @NonNull C sender, final @NonNull String query diff --git a/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/RichDescription.java b/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/RichDescription.java new file mode 100644 index 00000000..700f1096 --- /dev/null +++ b/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/RichDescription.java @@ -0,0 +1,133 @@ +// +// 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.minecraft.extras; + +import cloud.commandframework.ArgumentDescription; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; +import net.kyori.adventure.translation.GlobalTranslator; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Locale; + +import static java.util.Objects.requireNonNull; + +/** + * An argument description implementation that uses Adventure components. + * + * @since 1.4.0 + */ +public final class RichDescription implements ArgumentDescription { + private static final RichDescription EMPTY = new RichDescription(Component.empty()); + + private final Component contents; + + RichDescription(final Component contents) { + this.contents = contents; + } + + /** + * Get an empty description. + * + * @return the empty description + */ + public static @NonNull RichDescription empty() { + return EMPTY; + } + + /** + * Create a new rich description from the provided component. + * + * @param contents the rich contents + * @return a new rich description + */ + public static @NonNull RichDescription of(final @NonNull ComponentLike contents) { + final Component componentContents = requireNonNull(contents, "contents").asComponent(); + if (Component.empty().equals(componentContents)) { + return EMPTY; + } + + return new RichDescription(componentContents); + } + + /* Translatable helper methods */ + + /** + * Create a rich description pointing to a translation key. + * + * @param key the translation key + * @return a new rich description + */ + public static @NonNull RichDescription translatable(final @NonNull String key) { + requireNonNull(key, "key"); + + return new RichDescription(Component.translatable(key)); + } + + /** + * Create a rich description pointing to a translation key. + * + * @param key the translation key + * @param args the arguments to use with the translation key + * @return a new rich description + */ + public static @NonNull RichDescription translatable( + final @NonNull String key, + final @NonNull ComponentLike @NonNull... args + ) { + requireNonNull(key, "key"); + requireNonNull(args, "args"); + + return new RichDescription(Component.translatable(key, args)); + } + + /** + * {@inheritDoc} + * + * @deprecated to discourage use. A plain serialization is a somewhat expensive and lossy operation, use + * {@link #getContents()} instead. + */ + @Override + @Deprecated + public @NonNull String getDescription() { + return PlainComponentSerializer.plain().serialize(GlobalTranslator.render(this.contents, Locale.getDefault())); + } + + /** + * Get the contents of this description. + * + * @return the component contents of this description + */ + public @NonNull Component getContents() { + return this.contents; + } + + @Override + public boolean isEmpty() { + return Component.empty().equals(this.contents); + } + +} 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 5f66ded9..e9b2d5f1 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 @@ -23,9 +23,9 @@ // package cloud.commandframework.examples.bukkit; +import cloud.commandframework.ArgumentDescription; import cloud.commandframework.Command; import cloud.commandframework.CommandTree; -import cloud.commandframework.Description; import cloud.commandframework.minecraft.extras.MinecraftExceptionHandler; import cloud.commandframework.minecraft.extras.MinecraftHelp; import cloud.commandframework.annotations.AnnotationParser; @@ -56,6 +56,7 @@ import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator; import cloud.commandframework.execution.CommandExecutionCoordinator; import cloud.commandframework.extra.confirmation.CommandConfirmationManager; import cloud.commandframework.meta.CommandMeta; +import cloud.commandframework.minecraft.extras.RichDescription; import cloud.commandframework.minecraft.extras.TextColorArgument; import cloud.commandframework.paper.PaperCommandManager; import cloud.commandframework.tasks.TaskConsumer; @@ -63,8 +64,9 @@ import cloud.commandframework.types.tuples.Triplet; import io.leangen.geantyref.TypeToken; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.platform.bukkit.BukkitAudiences; -import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextDecoration; import org.apache.commons.lang.StringUtils; import org.bukkit.Bukkit; import org.bukkit.ChatColor; @@ -87,6 +89,8 @@ import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.Function; +import static net.kyori.adventure.text.Component.text; + /** * Example plugin class */ @@ -196,10 +200,10 @@ public final class ExamplePlugin extends JavaPlugin { .withArgumentParsingHandler() .withCommandExecutionHandler() .withDecorator( - component -> Component.text() - .append(Component.text("[", NamedTextColor.DARK_GRAY)) - .append(Component.text("Example", NamedTextColor.GOLD)) - .append(Component.text("] ", NamedTextColor.DARK_GRAY)) + component -> text() + .append(text("[", NamedTextColor.DARK_GRAY)) + .append(text("Example", NamedTextColor.GOLD)) + .append(text("] ", NamedTextColor.DARK_GRAY)) .append(component).build() ).apply(manager, bukkitAudiences::sender); // @@ -234,7 +238,7 @@ public final class ExamplePlugin extends JavaPlugin { .literal("me") // Require a player sender .senderType(Player.class) - .argument(worldArgument, Description.of("World name")) + .argument(worldArgument, ArgumentDescription.of("World name")) .argumentTriplet( "coords", TypeToken.get(Vector.class), @@ -243,7 +247,7 @@ public final class ExamplePlugin extends JavaPlugin { (sender, triplet) -> new Vector(triplet.getFirst(), triplet.getSecond(), triplet.getThird() ), - Description.of("Coordinates") + ArgumentDescription.of("Coordinates") ) .handler(context -> manager.taskRecipe().begin(context) .synchronous(commandContext -> { @@ -258,7 +262,7 @@ public final class ExamplePlugin extends JavaPlugin { .senderType(Player.class) .argument( SingleEntitySelectorArgument.of("entity"), - Description.of("Entity to teleport") + ArgumentDescription.of("Entity to teleport") ) .literal("here") .handler( @@ -277,7 +281,7 @@ public final class ExamplePlugin extends JavaPlugin { )) .command(builder.literal("teleport") .meta(CommandMeta.DESCRIPTION, "Teleport to a world") - .argument(WorldArgument.of("world"), Description.of("World to teleport to")) + .argument(WorldArgument.of("world"), ArgumentDescription.of("World to teleport to")) .handler(context -> manager.taskRecipe().begin(context).synchronous(ctx -> { final Player player = (Player) ctx.getSender(); player.teleport(ctx.get("world").getSpawnLocation()); @@ -330,23 +334,23 @@ public final class ExamplePlugin extends JavaPlugin { .literal("helpcolors") .argument( TextColorArgument.of("primary"), - Description.of("The primary color for the color scheme") + RichDescription.of(text("The primary color for the color scheme", Style.style(TextDecoration.ITALIC))) ) .argument( TextColorArgument.of("highlight"), - Description.of("The primary color used to highlight commands and queries") + RichDescription.of(text("The primary color used to highlight commands and queries")) ) .argument( TextColorArgument.of("alternate_highlight"), - Description.of("The secondary color used to highlight commands and queries") + RichDescription.of(text("The secondary color used to highlight commands and queries")) ) .argument( TextColorArgument.of("text"), - Description.of("The color used for description text") + RichDescription.of(text("The color used for description text")) ) .argument( TextColorArgument.of("accent"), - Description.of("The color used for accents and symbols") + RichDescription.of(text("The color used for accents and symbols")) ) .handler(c -> minecraftHelp.setHelpColors(MinecraftHelp.HelpColors.of( c.get("primary"), @@ -363,7 +367,7 @@ public final class ExamplePlugin extends JavaPlugin { manager.command( manager.commandBuilder( "arraycommand", - Description.of("Bukkit-esque cmmand") + ArgumentDescription.of("Bukkit-esque cmmand") ).argument( StringArrayArgument.optional( "args", @@ -375,7 +379,7 @@ public final class ExamplePlugin extends JavaPlugin { return Collections.emptyList(); } ), - Description.of("Arguments") + ArgumentDescription.of("Arguments") ).handler(context -> { final String[] args = context.getOrDefault("args", new String[0]); context.getSender().sendMessage("You wrote: " + StringUtils.join(args, " ")); @@ -408,7 +412,7 @@ public final class ExamplePlugin extends JavaPlugin { private void commandClear(final @NonNull Player player) { player.getInventory().clear(); this.bukkitAudiences.player(player) - .sendMessage(Identity.nil(), Component.text("Your inventory has been cleared", NamedTextColor.GOLD)); + .sendMessage(Identity.nil(), text("Your inventory has been cleared", NamedTextColor.GOLD)); } @CommandMethod("example give ") @@ -452,8 +456,8 @@ public final class ExamplePlugin extends JavaPlugin { ) { bukkitAudiences.sender(sender).sendMessage( Identity.nil(), - Component.text().append(Component.text("You have been given ", NamedTextColor.AQUA)) - .append(Component.text(money, NamedTextColor.GOLD)) + text().append(text("You have been given ", NamedTextColor.AQUA)) + .append(text(money, NamedTextColor.GOLD)) ); } diff --git a/examples/example-bungee/src/main/java/cloud/commandframework/examples/bungee/ExamplePlugin.java b/examples/example-bungee/src/main/java/cloud/commandframework/examples/bungee/ExamplePlugin.java index c463fca2..1d6496fb 100644 --- a/examples/example-bungee/src/main/java/cloud/commandframework/examples/bungee/ExamplePlugin.java +++ b/examples/example-bungee/src/main/java/cloud/commandframework/examples/bungee/ExamplePlugin.java @@ -23,9 +23,9 @@ // package cloud.commandframework.examples.bungee; +import cloud.commandframework.ArgumentDescription; import cloud.commandframework.Command; import cloud.commandframework.CommandTree; -import cloud.commandframework.Description; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.bungee.BungeeCommandManager; import cloud.commandframework.bungee.arguments.PlayerArgument; @@ -35,8 +35,8 @@ import cloud.commandframework.execution.CommandExecutionCoordinator; import cloud.commandframework.extra.confirmation.CommandConfirmationManager; import cloud.commandframework.meta.CommandMeta; import cloud.commandframework.minecraft.extras.MinecraftExceptionHandler; +import cloud.commandframework.minecraft.extras.RichDescription; import net.kyori.adventure.platform.bungeecord.BungeeAudiences; -import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.config.ServerInfo; @@ -46,6 +46,8 @@ import net.md_5.bungee.api.plugin.Plugin; import java.util.concurrent.TimeUnit; import java.util.function.Function; +import static net.kyori.adventure.text.Component.text; + public final class ExamplePlugin extends Plugin { private BungeeCommandManager manager; @@ -78,10 +80,10 @@ public final class ExamplePlugin extends Plugin { 30L, TimeUnit.SECONDS, context -> bungeeAudiences.sender(context.getCommandContext().getSender()).sendMessage( - Component.text( + text( "Confirmation required. Confirm using /example confirm.", NamedTextColor.RED)), sender -> bungeeAudiences.sender(sender).sendMessage( - Component.text("You do not have any pending commands.", NamedTextColor.RED)) + text("You do not have any pending commands.", NamedTextColor.RED)) ); this.confirmationManager.registerConfirmationProcessor(manager); @@ -91,10 +93,10 @@ public final class ExamplePlugin extends Plugin { .withInvalidSenderHandler() .withNoPermissionHandler() .withArgumentParsingHandler() - .withDecorator(component -> Component.text() - .append(Component.text("[", NamedTextColor.DARK_GRAY)) - .append(Component.text("Example", NamedTextColor.GOLD)) - .append(Component.text("] ", NamedTextColor.DARK_GRAY)) + .withDecorator(component -> text() + .append(text("[", NamedTextColor.DARK_GRAY)) + .append(text("Example", NamedTextColor.GOLD)) + .append(text("] ", NamedTextColor.DARK_GRAY)) .append(component).build() ).apply(manager, bungeeAudiences::sender); this.constructCommands(); @@ -121,12 +123,12 @@ public final class ExamplePlugin extends Plugin { this.manager.command( manager.commandBuilder("player") .senderType(ProxiedPlayer.class) - .argument(playerArgument, Description.of("Player name")) + .argument(playerArgument, RichDescription.of(text("Player ").append(text("name", NamedTextColor.GOLD)))) .handler(context -> { final ProxiedPlayer player = context.get("player"); bungeeAudiences.sender(context.getSender()).sendMessage( - Component.text("Selected ", NamedTextColor.GOLD) - .append(Component.text(player.getDisplayName(), NamedTextColor.AQUA)) + text("Selected ", NamedTextColor.GOLD) + .append(text(player.getDisplayName(), NamedTextColor.AQUA)) ); }) ); @@ -137,12 +139,12 @@ public final class ExamplePlugin extends Plugin { this.manager.command( this.manager.commandBuilder("server") .senderType(ProxiedPlayer.class) - .argument(serverArgument, Description.of("Server name")) + .argument(serverArgument, ArgumentDescription.of("Server name")) .handler(context -> { final ServerInfo server = context.get("server"); bungeeAudiences.sender(context.getSender()).sendMessage( - Component.text("Selected ", NamedTextColor.GOLD) - .append(Component.text(server.getName(), NamedTextColor.AQUA)) + text("Selected ", NamedTextColor.GOLD) + .append(text(server.getName(), NamedTextColor.AQUA)) ); }) );