aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDeDiamondPro <67508414+DeDiamondPro@users.noreply.github.com>2022-05-28 17:55:02 +0200
committerDeDiamondPro <67508414+DeDiamondPro@users.noreply.github.com>2022-05-28 17:55:02 +0200
commitad94530c89f8358338b9a4034b92b3a444001df6 (patch)
tree9ad6f14ad517523eec76f102ff0f1fc8cf8d85c1
parenta398d1ccf81bd1586dafd15bd0cdd5dd2cb30bc2 (diff)
parent4df2a5f6c815b0dd8cc8b68aaf164a40e63fa57d (diff)
downloadOneConfig-ad94530c89f8358338b9a4034b92b3a444001df6.tar.gz
OneConfig-ad94530c89f8358338b9a4034b92b3a444001df6.tar.bz2
OneConfig-ad94530c89f8358338b9a4034b92b3a444001df6.zip
Merge branch 'master' of github.com:Polyfrost/OneConfig
-rw-r--r--.gitignore367
-rw-r--r--build.gradle.kts11
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/OneConfig.java3
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/test/TestCommand.java39
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/utils/commands/CommandHelper.java19
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/utils/commands/CommandManager.java295
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Command.java115
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Greedy.java20
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Main.java20
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Name.java22
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/Optional.java12
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/utils/commands/annotations/SubCommand.java34
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/ArgumentParser.java12
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/Arguments.java33
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/BooleanParser.java11
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/DoubleParser.java10
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/FloatParser.java11
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/IntegerParser.java8
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/utils/commands/arguments/StringParser.java18
19 files changed, 1058 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
index 38fc82a..6de7fc5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,370 @@ build - Copy.gradle
.vscode
.devauth
.DS_STORE
+
+
+# Created by https://www.toptal.com/developers/gitignore/api/java,gradle,forgegradle,kotlin,macos,intellij,intellij+all,eclipse,visualstudiocode
+# Edit at https://www.toptal.com/developers/gitignore?templates=java,gradle,forgegradle,kotlin,macos,intellij,intellij+all,eclipse,visualstudiocode
+
+### Eclipse ###
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+.recommenders
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# PyDev specific (Python IDE for Eclipse)
+*.pydevproject
+
+# CDT-specific (C/C++ Development Tooling)
+.cproject
+
+# CDT- autotools
+.autotools
+
+# Java annotation processor (APT)
+.factorypath
+
+# PDT-specific (PHP Development Tools)
+.buildpath
+
+# sbteclipse plugin
+.target
+
+# Tern plugin
+.tern-project
+
+# TeXlipse plugin
+.texlipse
+
+# STS (Spring Tool Suite)
+.springBeans
+
+# Code Recommenders
+.recommenders/
+
+# Annotation Processing
+.apt_generated/
+.apt_generated_test/
+
+# Scala IDE specific (Scala & Java development for Eclipse)
+.cache-main
+.scala_dependencies
+.worksheet
+
+# Uncomment this line if you wish to ignore the project description file.
+# Typically, this file would be tracked if it contains build/dependency configurations:
+#.project
+
+### Eclipse Patch ###
+# Spring Boot Tooling
+.sts4-cache/
+
+### ForgeGradle ###
+# Minecraft client/server files
+run/
+
+### Intellij ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Intellij Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+# https://plugins.jetbrains.com/plugin/7973-sonarlint
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator-enh.xml
+.idea/**/markdown-navigator/
+
+# Cache file creation bug
+# See https://youtrack.jetbrains.com/issue/JBR-2257
+.idea/$CACHE_FILE$
+
+# CodeStream plugin
+# https://plugins.jetbrains.com/plugin/12206-codestream
+.idea/codestream.xml
+
+# Azure Toolkit for IntelliJ plugin
+# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
+.idea/**/azureSettings.xml
+
+### Intellij+all ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+
+# AWS User-specific
+
+# Generated files
+
+# Sensitive or high-churn files
+
+# Gradle
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+
+# Mongo Explorer plugin
+
+# File-based project format
+
+# IntelliJ
+
+# mpeltonen/sbt-idea plugin
+
+# JIRA plugin
+
+# Cursive Clojure plugin
+
+# SonarLint plugin
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+
+# Editor-based Rest Client
+
+# Android studio 3.1+ serialized cache file
+
+### Intellij+all Patch ###
+# Ignore everything but code style settings and run configurations
+# that are supposed to be shared within teams.
+
+.idea/*
+
+!.idea/codeStyles
+!.idea/runConfigurations
+
+### Java ###
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+replay_pid*
+
+### Kotlin ###
+# Compiled class file
+
+# Log file
+
+# BlueJ files
+
+# Mobile Tools for Java (J2ME)
+
+# Package Files #
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### macOS Patch ###
+# iCloud generated files
+*.icloud
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+.history
+.ionide
+
+# Support for Project snippet scope
+.vscode/*.code-snippets
+
+# Ignore code-workspaces
+*.code-workspace
+
+### Gradle ###
+.gradle
+**/build/
+!src/**/build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Avoid ignore Gradle wrappper properties
+!gradle-wrapper.properties
+
+# Cache of project
+.gradletasknamecache
+
+# Eclipse Gradle plugin generated files
+# Eclipse Core
+.project
+# JDT-specific (Eclipse Java Development Tools)
+.classpath
+
+# End of https://www.toptal.com/developers/gitignore/api/java,gradle,forgegradle,kotlin,macos,intellij,intellij+all,eclipse,visualstudiocode
diff --git a/build.gradle.kts b/build.gradle.kts
index 290de0b..e21f7b7 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -19,7 +19,6 @@ plugins {
}
java {
- withJavadocJar()
withSourcesJar()
}
@@ -251,6 +250,14 @@ tasks {
jdkVersion.set(8)
}
}
+ doLast {
+ val outputFile = outputDirectory.get().resolve("images/logo-icon.svg")
+ if (outputFile.exists()) {
+ outputFile.delete()
+ }
+ val inputFile = project.rootDir.resolve("src/main/resources/assets/oneconfig/icons/OneConfig.svg")
+ inputFile.copyTo(outputFile)
+ }
}
val dokkaJar = create("dokkaJar", Jar::class.java) {
archiveClassifier.set("dokka")
@@ -258,7 +265,7 @@ tasks {
dependsOn(dokkaHtml)
from(layout.buildDirectory.dir("dokka"))
}
- named("javadocJar").get().dependsOn(dokkaJar)
+ named("sourcesJar").get().dependsOn(dokkaJar)
}
afterEvaluate {
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 <a> <b> <c>
+ 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 <a> <b> <c>
+ 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 <a> <b> <c>
+ 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<Class<?>, 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<InternalCommand.InternalCommandInvoker> 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<InternalCommand.InternalCommandInvoker> 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<Object> 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<InternalCommand.InternalCommandInvoker> 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<InternalCommandInvoker> invokers;
+ public final ArrayList<InternalCommand> children = new ArrayList<>();
+
+ public InternalCommand(String name, String[] aliases, String description, ArrayList<InternalCommandInvoker> 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.
+ * <p>
+ * To start, create a class which is annotated with this annotation, and then a method which is annotated with {@link Main}.
+ * <pre>{@code
+ * @literal @Command(name = "test", description = "A test command", aliases = {"t"})
+ * public class TestCommand {
+ * @literal @Main
+ * public static void mainCommandMethod() {
+ * // do stuff
+ * }
+ * }
+ * }</pre>
+ * <p>
+ * 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 <b><i>must be static</i></b>.
+ * If the methods are not static, the {@link CommandManager} will throw an exception.
+ *
+ * <p>
+ * 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:
+ * <pre>{@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
+ * }
+ * }</pre>
+ * </p>
+ *
+ * <p>
+ * Of course, {@link SubCommand}s can be added and "stacked". For example, the following command class:
+ * <pre>{@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
+ * }
+ * }
+ * }
+ * }
+ * }</pre>
+ * </p>
+ *
+ * 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)}.
+ *
+ * <p>
+ * Note: if you're viewing this in IntelliJ or just see the @literal tag everywhere, please ignore that.
+ * </p>
+ *
+ * @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<T> {
+ private final TypeToken<T> type = new TypeToken<T>(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<Boolean> {
+
+ @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<Double> {
+ @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<Float> {
+
+ @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<Integer> {
+ @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<String> {
+
+ @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();
+ }
+ }
+}