From 688869d1a5d60b8bdd89c78e0deab6b53b04a694 Mon Sep 17 00:00:00 2001 From: Wyvest <45589059+Wyvest@users.noreply.github.com> Date: Sat, 28 May 2022 22:41:38 +0700 Subject: command utils --- .../java/cc/polyfrost/oneconfig/OneConfig.java | 3 + .../cc/polyfrost/oneconfig/test/TestCommand.java | 39 +++ .../oneconfig/utils/commands/CommandHelper.java | 19 ++ .../oneconfig/utils/commands/CommandManager.java | 295 +++++++++++++++++++++ .../utils/commands/annotations/Command.java | 115 ++++++++ .../utils/commands/annotations/Greedy.java | 20 ++ .../oneconfig/utils/commands/annotations/Main.java | 20 ++ .../oneconfig/utils/commands/annotations/Name.java | 22 ++ .../utils/commands/annotations/Optional.java | 12 + .../utils/commands/annotations/SubCommand.java | 34 +++ .../utils/commands/arguments/ArgumentParser.java | 12 + .../utils/commands/arguments/Arguments.java | 33 +++ .../utils/commands/arguments/BooleanParser.java | 11 + .../utils/commands/arguments/DoubleParser.java | 10 + .../utils/commands/arguments/FloatParser.java | 11 + .../utils/commands/arguments/IntegerParser.java | 8 + .../utils/commands/arguments/StringParser.java | 18 ++ 17 files changed, 682 insertions(+) create mode 100644 src/main/java/cc/polyfrost/oneconfig/test/TestCommand.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/utils/commands/CommandHelper.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/utils/commands/CommandManager.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Command.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Greedy.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Main.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Name.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Optional.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/SubCommand.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/ArgumentParser.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/Arguments.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/BooleanParser.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/DoubleParser.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/FloatParser.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/IntegerParser.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/StringParser.java (limited to 'src/main') diff --git a/src/main/java/cc/polyfrost/oneconfig/OneConfig.java b/src/main/java/cc/polyfrost/oneconfig/OneConfig.java index 60bb37e..c1301d3 100644 --- a/src/main/java/cc/polyfrost/oneconfig/OneConfig.java +++ b/src/main/java/cc/polyfrost/oneconfig/OneConfig.java @@ -11,7 +11,9 @@ import cc.polyfrost.oneconfig.lwjgl.BlurHandler; import cc.polyfrost.oneconfig.lwjgl.RenderManager; import cc.polyfrost.oneconfig.lwjgl.font.Fonts; import cc.polyfrost.oneconfig.lwjgl.image.Images; +import cc.polyfrost.oneconfig.test.TestCommand; import cc.polyfrost.oneconfig.test.TestConfig; +import cc.polyfrost.oneconfig.utils.commands.CommandManager; import cc.polyfrost.oneconfig.utils.hypixel.HypixelUtils; import net.minecraft.launchwrapper.Launch; import net.minecraftforge.client.ClientCommandHandler; @@ -50,6 +52,7 @@ public class OneConfig { public void onFMLInitialization(net.minecraftforge.fml.common.event.FMLInitializationEvent event) { BlurHandler.INSTANCE.load(); testConfig = new TestConfig(); + CommandManager.registerCommand(new TestCommand()); ClientCommandHandler.instance.registerCommand(new OneConfigCommand()); EventManager.INSTANCE.register(new HudCore()); EventManager.INSTANCE.register(HypixelUtils.INSTANCE); diff --git a/src/main/java/cc/polyfrost/oneconfig/test/TestCommand.java b/src/main/java/cc/polyfrost/oneconfig/test/TestCommand.java new file mode 100644 index 0000000..753c353 --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/test/TestCommand.java @@ -0,0 +1,39 @@ +package cc.polyfrost.oneconfig.test; + +import cc.polyfrost.oneconfig.libs.universal.UChat; +import cc.polyfrost.oneconfig.utils.commands.annotations.Command; +import cc.polyfrost.oneconfig.utils.commands.annotations.Main; +import cc.polyfrost.oneconfig.utils.commands.annotations.Name; +import cc.polyfrost.oneconfig.utils.commands.annotations.SubCommand; + +@Command(value = "test", aliases = {"t"}) +public class TestCommand { + + @Main + private static void main() { // /test + UChat.chat("Main command"); + } + + @SubCommand(value = "subcommand", aliases = {"s"}) + private static class TestSubCommand { + + @Main(priority = 999) + private static void main(int a, float b, @Name("named c") String c) { // /test subcommand + UChat.chat("Integer main: " + a + " " + b + " " + c); + } + + @Main(priority = 10001) + private static void main(double a, double b, @Name("named c") String c) { // /test subcommand + UChat.chat("Double main: " + a + " " + b + " " + c); + } + + @SubCommand(value = "subsubcommand", aliases = {"ss"}) + private static class TestSubSubCommand { + + @Main + private static void main(String a, String b, @Name("named c") String c) { // /test subcommand subsubcommand + UChat.chat(a + " " + b + " " + c); + } + } + } +} diff --git a/src/main/java/cc/polyfrost/oneconfig/utils/commands/CommandHelper.java b/src/main/java/cc/polyfrost/oneconfig/utils/commands/CommandHelper.java new file mode 100644 index 0000000..bc57fc2 --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/utils/commands/CommandHelper.java @@ -0,0 +1,19 @@ +package cc.polyfrost.oneconfig.utils.commands; + +/** + * A helper class for commands. + * Extend this class and run {@link CommandHelper#preload()} (which does nothing, + * just makes loading look nicer lol) + * + * @see cc.polyfrost.oneconfig.utils.commands.annotations.Command + */ +public abstract class CommandHelper { + + public CommandHelper() { + CommandManager.registerCommand(this); + } + + public void preload() { + + } +} diff --git a/src/main/java/cc/polyfrost/oneconfig/utils/commands/CommandManager.java b/src/main/java/cc/polyfrost/oneconfig/utils/commands/CommandManager.java new file mode 100644 index 0000000..a4e2569 --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/utils/commands/CommandManager.java @@ -0,0 +1,295 @@ +package cc.polyfrost.oneconfig.utils.commands; + +import cc.polyfrost.oneconfig.libs.universal.ChatColor; +import cc.polyfrost.oneconfig.libs.universal.UChat; +import cc.polyfrost.oneconfig.utils.commands.annotations.*; +import cc.polyfrost.oneconfig.utils.commands.arguments.*; +import net.minecraft.command.CommandBase; +import net.minecraft.command.ICommandSender; +import net.minecraftforge.client.ClientCommandHandler; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Handles the registration of OneConfig commands. + * + * @see Command + */ +public class CommandManager { + private static final HashMap, ArgumentParser> parsers = new HashMap<>(); + private static final String NOT_FOUND_TEXT = "Command not found! Type /@ROOT_COMMAND@ help for help."; + private static final String METHOD_RUN_ERROR = "Error while running @ROOT_COMMAND@ method! Please report this to the developer."; + + /** + * Adds a parser to the parsers map. + * + * @param parser The parser to add. + * @param clazz The class of the parser. + */ + public static void addParser(ArgumentParser parser, Class clazz) { + parsers.put(clazz, parser); + } + + /** + * Adds a parser to the parsers map. + * @param parser The parser to add. + */ + public static void addParser(ArgumentParser parser) { + addParser(parser, parser.typeClass); + } + + static { + addParser(new StringParser()); + addParser(new IntegerParser()); + addParser(new IntegerParser(), int.class); + addParser(new DoubleParser()); + addParser(new DoubleParser(), double.class); + addParser(new FloatParser()); + addParser(new FloatParser(), float.class); + addParser(new BooleanParser()); + addParser(new BooleanParser(), boolean.class); + } + + /** + * Registers the provided command. + * + * @param command The command to register. + */ + public static void registerCommand(Object command) { + Class clazz = command.getClass(); + if (clazz.isAnnotationPresent(Command.class)) { + final Command annotation = clazz.getAnnotation(Command.class); + ArrayList mainCommandFuncs = new ArrayList<>(); + + for (Method method : clazz.getDeclaredMethods()) { + if (method.isAnnotationPresent(Main.class) && method.getParameterCount() == 0) { + mainCommandFuncs.add(new InternalCommand.InternalCommandInvoker(annotation.value(), annotation.aliases(), method)); + break; + } + } + + final InternalCommand root = new InternalCommand(annotation.value(), annotation.aliases(), annotation.description().trim().isEmpty() ? "Main command for " + annotation.value() : annotation.description(), mainCommandFuncs); + addToInvokers(clazz.getDeclaredClasses(), root); + ClientCommandHandler.instance.registerCommand(new CommandBase() { + @Override + public String getCommandName() { + return annotation.value(); + } + + @Override + public String getCommandUsage(ICommandSender sender) { + return "/" + annotation.value(); + } + + @Override + public void processCommand(ICommandSender sender, String[] args) { + if (args.length == 0) { + if (!root.invokers.isEmpty()) { + try { + root.invokers.stream().findFirst().get().method.invoke(null); + } catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException | ExceptionInInitializerError e) { + UChat.chat(ChatColor.RED.toString() + ChatColor.BOLD + METHOD_RUN_ERROR); + } + } + } else { + if (annotation.helpCommand() && args[0].equalsIgnoreCase("help")) { + UChat.chat(sendHelpCommand(root)); + } else { + for (InternalCommand command : root.children) { + String result = runThroughCommands(command, 0, args); + if (result == null) { + return; + } else if (!result.equals(NOT_FOUND_TEXT)) { + UChat.chat(ChatColor.RED.toString() + ChatColor.BOLD + result.replace("@ROOT_COMMAND@", getCommandName())); + return; + } + } + UChat.chat(ChatColor.RED.toString() + ChatColor.BOLD + NOT_FOUND_TEXT.replace("@ROOT_COMMAND@", getCommandName())); + } + } + } + + @Override + public int getRequiredPermissionLevel() { + return -1; + } + }); + } + } + + private static String sendHelpCommand(InternalCommand root) { + StringBuilder builder = new StringBuilder(); + builder.append(ChatColor.GOLD.toString() + "Help for " + ChatColor.BOLD + root.name + ChatColor.RESET + ChatColor.GOLD + ":\n"); + builder.append("\n"); + for (InternalCommand command : root.children) { + runThroughCommandsHelp(root.name, root, builder); + } + builder.append("\n" + ChatColor.GOLD + "Aliases: " + ChatColor.BOLD); + int index = 0; + for (String alias : root.aliases) { + ++index; + builder.append(alias + (index < root.aliases.length ? ", " : "")); + } + builder.append("\n"); + return builder.toString(); + } + + private static void runThroughCommandsHelp(String append, InternalCommand command, StringBuilder builder) { + for (InternalCommand.InternalCommandInvoker invoker : command.invokers) { + builder.append("\n" + ChatColor.GOLD + "/" + append + " " + command.name); + for (Parameter parameter : invoker.method.getParameters()) { + String name = parameter.getName(); + if (parameter.isAnnotationPresent(Name.class)) { + name = parameter.getAnnotation(Name.class).value(); + } + builder.append(" <" + name + ">"); + } + if (!command.description.trim().isEmpty()) { + builder.append(": " + ChatColor.BOLD + command.description); + } + } + for (InternalCommand subCommand : command.children) { + runThroughCommandsHelp(append + " " + command.name, subCommand, builder); + } + } + + private static String runThroughCommands(InternalCommand command, int layer, String[] args) { + int newLayer = layer + 1; + if (command.isEqual(args[layer]) && !command.invokers.isEmpty()) { + Set invokers = command.invokers.stream().filter(invoker -> newLayer == args.length - invoker.parameterTypes.length).sorted(Comparator.comparingInt((a) -> a.method.getAnnotation(Main.class).priority())).collect(Collectors.toSet()); + if (!invokers.isEmpty()) { + for (InternalCommand.InternalCommandInvoker invoker : invokers) { + try { + String a = tryInvoker(invoker, newLayer, args); + if (a == null) { + return null; + } else if (a.contains(METHOD_RUN_ERROR)) { + return a; + } + } catch (Exception ignored) { + + } + } + } else { + for (InternalCommand subCommand : command.children) { + String result = runThroughCommands(subCommand, newLayer, args); + if (result == null) { + return null; + } else if (!result.equals(NOT_FOUND_TEXT)) { + return result; + } + } + } + } + return NOT_FOUND_TEXT; + } + + private static String tryInvoker(InternalCommand.InternalCommandInvoker invoker, int newLayer, String[] args) { + try { + ArrayList params = new ArrayList<>(); + int processed = newLayer; + int currentParam = 0; + while (processed < args.length) { + Parameter param = invoker.method.getParameters()[currentParam]; + if (param.isAnnotationPresent(Greedy.class) && currentParam + 1 != invoker.method.getParameterCount()) { + return "Parsing failed: Greedy parameter must be the last one."; + } + ArgumentParser parser = parsers.get(param.getType()); + if (parser == null) { + return "No parser for " + invoker.method.getParameterTypes()[currentParam].getSimpleName() + "! Please report this to the mod author."; + } + try { + Arguments arguments = new Arguments(Arrays.copyOfRange(args, processed, args.length), param.isAnnotationPresent(Greedy.class)); + try { + Object a = parser.parse(arguments); + if (a != null) { + params.add(a); + processed += arguments.getPosition(); + currentParam++; + } else { + return "Failed to parse " + param.getType().getSimpleName() + "! Please report this to the mod author."; + } + } catch (Exception e) { + return "A " + e.getClass().getSimpleName() + " has occured while try to parse " + param.getType().getSimpleName() + "! Please report this to the mod author."; + } + } catch (Exception e) { + return "A " + e.getClass().getSimpleName() + " has occured while try to parse " + param.getType().getSimpleName() + "! Please report this to the mod author."; + } + } + invoker.method.invoke(null, params.toArray()); + return null; + } catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException | ExceptionInInitializerError e) { + return ChatColor.RED.toString() + ChatColor.BOLD + METHOD_RUN_ERROR; + } + } + + private static void addToInvokers(Class[] classes, InternalCommand parent) { + for (Class clazz : classes) { + if (clazz.isAnnotationPresent(SubCommand.class)) { + SubCommand annotation = clazz.getAnnotation(SubCommand.class); + ArrayList mainMethods = new ArrayList<>(); + for (Method method : clazz.getDeclaredMethods()) { + if (method.isAnnotationPresent(Main.class)) { + mainMethods.add(new InternalCommand.InternalCommandInvoker(annotation.value(), annotation.aliases(), method)); + } + } + InternalCommand command = new InternalCommand(annotation.value(), annotation.aliases(), annotation.description(), mainMethods); + parent.children.add(command); + addToInvokers(clazz.getDeclaredClasses(), command); + } + } + } + + private static class InternalCommand { + public final String name; + public final String[] aliases; + public final String description; + public final ArrayList invokers; + public final ArrayList children = new ArrayList<>(); + + public InternalCommand(String name, String[] aliases, String description, ArrayList invokers) { + this.name = name; + this.aliases = aliases; + this.invokers = invokers; + this.description = description; + } + + public boolean isEqual(String name) { + if (this.name.equals(name)) { + return true; + } else { + for (String alias : aliases) { + if (alias.equals(name)) { + return true; + } + } + } + return false; + } + + public static class InternalCommandInvoker { + public final String name; + public final String[] aliases; + public final Method method; + public final Parameter[] parameterTypes; + + public InternalCommandInvoker(String name, String[] aliases, Method method) { + if (!Modifier.isStatic(method.getModifiers())) { + throw new IllegalArgumentException("All command methods must be static!"); + } + this.name = name; + this.aliases = aliases; + this.method = method; + this.parameterTypes = method.getParameters().clone(); + if (Modifier.isPrivate(method.getModifiers()) || Modifier.isProtected(method.getModifiers())) { + method.setAccessible(true); + } + } + } + } +} diff --git a/src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Command.java b/src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Command.java new file mode 100644 index 0000000..7717d46 --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Command.java @@ -0,0 +1,115 @@ +package cc.polyfrost.oneconfig.utils.commands.annotations; + +import cc.polyfrost.oneconfig.utils.commands.CommandHelper; +import cc.polyfrost.oneconfig.utils.commands.CommandManager; +import cc.polyfrost.oneconfig.utils.commands.arguments.ArgumentParser; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a class as a command. + *

+ * To start, create a class which is annotated with this annotation, and then a method which is annotated with {@link Main}. + *

{@code
+ *     @literal @Command(name = "test", description = "A test command", aliases = {"t"})
+ *     public class TestCommand {
+ *         @literal @Main
+ *         public static void mainCommandMethod() {
+ *             // do stuff
+ *         }
+ *     }
+ *     }
+ *

+ * Keep in mind how {@code mainCommandMethod} is a private and static method. + * With OneConfig's command utility, methods for commands can be any kind of visibility, and must be static. + * If the methods are not static, the {@link CommandManager} will throw an exception. + * + *

+ * Command methods can also having multiple parameters of virtually any type, as long as it is a + * {@link String}, {@code boolean}, {@code int}, {@code double}, {@code float}, + * or is added as an {@link ArgumentParser} and added to the + * {@link CommandManager} via {@link CommandManager#addParser(ArgumentParser)}. + * Parameters can also be annotated with various annotations, such as {@link Name}, which names the parameter + * for the user, {@link Greedy}, which takes all following arguments along with itself, and more to come. + * For example, the following command method: + *

{@code
+ *     @literal @Main
+ *     private static void mainCommandMethod(String arg1, @Name("nameOfSecondArgument") boolean arg2, int arg3, double arg4, float arg5, @Greedy String greedyArgument) {
+ *         // do things here
+ *         System.out.println(greedyArgument); // Greedily takes all remaining arguments after greedyArgument as well as itself. 1984
+ *     }
+ *     }
+ *

+ * + *

+ * Of course, {@link SubCommand}s can be added and "stacked". For example, the following command class: + *

{@code
+ *     @literal @Command(name = "mycommand", aliases = {"alias1"}, description = "My command description")
+ *     public class TestCommand {
+ *         @literal @Main
+ *         private static void mainCommandMethod() {
+ *             // do things here
+ *         }
+ *
+ *         @literal @SubCommand(name = "subcommand", aliases = {"subalias1"}, description = "My subcommand description")
+ *         private static class SubCommandClass {
+ *             @literal @Main
+ *             private static void subCommandMethod() {
+ *                 // do things here
+ *             }
+ *
+ *             @literal @SubCommand(name = "subsubcommand", aliases = {"subsubalias1"}, description = "My subsubcommand description")
+ *             private static class SubSubCommandClass {
+ *                 @literal @Main
+ *                 private static void subSubCommandMethod() {
+ *                     // do things here
+ *                 }
+ *             }
+ *         }
+ *     }
+ *     }
+ *

+ * + * To register commands, either extend {@link CommandHelper} and run {@link CommandHelper#preload()} (which does nothing, + * just makes loading look nicer lol), or use {@link CommandManager#registerCommand(Object)}. + * + *

+ * Note: if you're viewing this in IntelliJ or just see the @literal tag everywhere, please ignore that. + *

+ * + * @see cc.polyfrost.oneconfig.test.TestCommand + * @see Main + * @see CommandManager + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface Command { + /** + * The name of the command. + * + * @return The name of the command. + */ + String value(); + + /** + * The aliases of the command. + * + * @return The aliases of the command. + */ + String[] aliases() default {}; + + /** + * The description of the command. + * + * @return The description of the command. + */ + String description() default ""; + + /** + * Whether the command generates a help command. + */ + boolean helpCommand() default true; +} diff --git a/src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Greedy.java b/src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Greedy.java new file mode 100644 index 0000000..31c948d --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Greedy.java @@ -0,0 +1,20 @@ +package cc.polyfrost.oneconfig.utils.commands.annotations; + +import cc.polyfrost.oneconfig.utils.commands.arguments.Arguments; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that the specific parameter should capture all remaining arguments and itself + * Can only be used on the last parameter, and only works with {@link cc.polyfrost.oneconfig.utils.commands.arguments.StringParser} by default. + * + * @see cc.polyfrost.oneconfig.utils.commands.arguments.StringParser#parse(Arguments) + * @see Command + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface Greedy { +} diff --git a/src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Main.java b/src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Main.java new file mode 100644 index 0000000..3c105c7 --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Main.java @@ -0,0 +1,20 @@ +package cc.polyfrost.oneconfig.utils.commands.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a method as the main method of a command. + * + * @see Command + * @see SubCommand + * @see cc.polyfrost.oneconfig.utils.commands.CommandManager + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface Main { + String description() default ""; + int priority() default 1000; +} diff --git a/src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Name.java b/src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Name.java new file mode 100644 index 0000000..ef178a0 --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Name.java @@ -0,0 +1,22 @@ +package cc.polyfrost.oneconfig.utils.commands.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks the name of a parameter. + * + * @see Main + * @see Command + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface Name { + /** + * The name of the parameter. + * @return The name of the parameter. + */ + String value(); +} diff --git a/src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Optional.java b/src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Optional.java new file mode 100644 index 0000000..4dbe8b5 --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Optional.java @@ -0,0 +1,12 @@ +package cc.polyfrost.oneconfig.utils.commands.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface Optional { + +} diff --git a/src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/SubCommand.java b/src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/SubCommand.java new file mode 100644 index 0000000..b1cf035 --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/SubCommand.java @@ -0,0 +1,34 @@ +package cc.polyfrost.oneconfig.utils.commands.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a class as a subcommand. Can be stacked together. + * + * @see Command + * @see cc.polyfrost.oneconfig.utils.commands.CommandManager + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface SubCommand { + /** + * The name of the command. + * @return The name of the command. + */ + String value(); + + /** + * The aliases of the command. + * @return The aliases of the command. + */ + String[] aliases() default {}; + + /** + * The description of the command. + * @return The description of the command. + */ + String description() default ""; +} diff --git a/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/ArgumentParser.java b/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/ArgumentParser.java new file mode 100644 index 0000000..d9d51b0 --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/ArgumentParser.java @@ -0,0 +1,12 @@ +package cc.polyfrost.oneconfig.utils.commands.arguments; + +import com.google.common.reflect.TypeToken; +import org.jetbrains.annotations.Nullable; + +@SuppressWarnings("unstable") +public abstract class ArgumentParser { + private final TypeToken type = new TypeToken(getClass()) {}; + public final Class typeClass = type.getRawType(); + @Nullable + public abstract T parse(Arguments arguments); +} diff --git a/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/Arguments.java b/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/Arguments.java new file mode 100644 index 0000000..74a0840 --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/Arguments.java @@ -0,0 +1,33 @@ +package cc.polyfrost.oneconfig.utils.commands.arguments; + +public class Arguments { + private int position = 0; + public final String[] args; + public final boolean greedy; + + public Arguments(String[] args, boolean greedy) { + this.args = args; + this.greedy = greedy; + } + + public String poll() { + ++position; + return args[position - 1]; + } + + public String peek() { + if (hasNext()) { + return args[position]; + } else { + return null; + } + } + + public boolean hasNext() { + return position < args.length; + } + + public int getPosition() { + return position; + } +} diff --git a/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/BooleanParser.java b/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/BooleanParser.java new file mode 100644 index 0000000..dfdca2d --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/BooleanParser.java @@ -0,0 +1,11 @@ +package cc.polyfrost.oneconfig.utils.commands.arguments; + +import org.jetbrains.annotations.Nullable; + +public class BooleanParser extends ArgumentParser { + + @Override + public @Nullable Boolean parse(Arguments arguments) { + return Boolean.parseBoolean(arguments.poll()); + } +} diff --git a/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/DoubleParser.java b/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/DoubleParser.java new file mode 100644 index 0000000..8c85849 --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/DoubleParser.java @@ -0,0 +1,10 @@ +package cc.polyfrost.oneconfig.utils.commands.arguments; + +import org.jetbrains.annotations.Nullable; + +public class DoubleParser extends ArgumentParser { + @Override + public @Nullable Double parse(Arguments arguments) { + return Double.parseDouble(arguments.poll()); + } +} diff --git a/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/FloatParser.java b/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/FloatParser.java new file mode 100644 index 0000000..7053fcb --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/FloatParser.java @@ -0,0 +1,11 @@ +package cc.polyfrost.oneconfig.utils.commands.arguments; + +import org.jetbrains.annotations.Nullable; + +public class FloatParser extends ArgumentParser { + + @Override + public @Nullable Float parse(Arguments arguments) { + return Float.parseFloat(arguments.poll()); + } +} diff --git a/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/IntegerParser.java b/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/IntegerParser.java new file mode 100644 index 0000000..6910d4b --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/IntegerParser.java @@ -0,0 +1,8 @@ +package cc.polyfrost.oneconfig.utils.commands.arguments; + +public class IntegerParser extends ArgumentParser { + @Override + public Integer parse(Arguments arguments) { + return Integer.parseInt(arguments.poll()); + } +} diff --git a/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/StringParser.java b/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/StringParser.java new file mode 100644 index 0000000..6a34722 --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/StringParser.java @@ -0,0 +1,18 @@ +package cc.polyfrost.oneconfig.utils.commands.arguments; + +public class StringParser extends ArgumentParser { + + @Override + public String parse(Arguments arguments) { + if (arguments.greedy) { + StringBuilder builder = new StringBuilder(); + while (arguments.hasNext()) { + String arg = arguments.poll(); + builder.append(arg).append(" "); + } + return builder.toString().trim(); + } else { + return arguments.poll(); + } + } +} -- cgit