diff --git a/.bazelrc b/.bazelrc
new file mode 100644
index 0000000..b6254e5
--- /dev/null
+++ b/.bazelrc
@@ -0,0 +1,7 @@
+build --java_language_version=25
+build --java_runtime_version=25
+build --tool_java_language_version=25
+build --tool_java_runtime_version=25
+build --define=maven_repo=https://repo.lab0x13.site/snapshots
+build --remote_cache=https://cache.lab0x13.site
+build --remote_upload_local_results=false
\ No newline at end of file
diff --git a/.forgejo/workflows/publish-artifact.yml b/.forgejo/workflows/publish-artifact.yml
new file mode 100644
index 0000000..8423916
--- /dev/null
+++ b/.forgejo/workflows/publish-artifact.yml
@@ -0,0 +1,15 @@
+on:
+ push:
+ branches: [master]
+
+jobs:
+ build:
+ runs-on: docker
+ container:
+ image: "git.lab0x13.site/scrow/bazel-java:0.5"
+ steps:
+ - uses: actions/checkout@v7
+ - id: build
+ run: |
+ bazel build //bukkit:bukkit.publish //core:core.publish //velocity:velocity.publish \
+ --remote_cache='https://${{ secrets.CACHE_USERNAME }}:${{ secrets.CACHE_PASSWORD }}@cache.lab0x13.site' --remote_upload_local_results
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 1fac4d5..bcca68a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,10 @@
-.gradle
-build/
-!gradle/wrapper/gradle-wrapper.jar
-!**/src/main/**/build/
-!**/src/test/**/build/
-.kotlin
+/.idea/inspectionProfiles
+/MODULE.bazel.lock
+/.bazelbsp
+/bazel-bin
+/bazel-out
+/bazel-KenCommandAPI
+/bazel-testlogs
### IntelliJ IDEA ###
.idea/modules.xml
diff --git a/.idea/bazelVersionCache.xml b/.idea/bazelVersionCache.xml
new file mode 100644
index 0000000..8501e9b
--- /dev/null
+++ b/.idea/bazelVersionCache.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index a0486d1..3e3960b 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -1,34 +1,4 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/jsonSchemas.xml b/.idea/jsonSchemas.xml
new file mode 100644
index 0000000..270275b
--- /dev/null
+++ b/.idea/jsonSchemas.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 03872dd..6b56b62 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..de9b6c3
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 0b33847..35eb1dd 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -2,6 +2,5 @@
-
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 1eb1391..6fbbbcb 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -3,30 +3,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -85,19 +104,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -118,6 +124,7 @@
+
@@ -136,6 +143,14 @@
{
"keyToString": {
+ "Bazel.//bukkit:bukkit.publish.executor": "Run",
+ "Bazel.//core:core.publish.executor": "Run",
+ "Bazel.//kencommandapi-velocity:velocity.publish.executor": "Run",
+ "Bazel.//velocity:velocity.publish.executor": "Run",
+ "Bazel.Run //kencommandapi-bukkit:bukkit.publish.executor": "Run",
+ "Bazel.Run //kencommandapi-core:core.publish.executor": "Run",
+ "Bazel.Run //kencommandapi-core:export.publish.executor": "Run",
+ "Bazel.Run //kencommandapi-velocity:velocity.publish.executor": "Run",
"Gradle./home/a/src/kento2/KenCommandAPI/kencommandapi-core [:kencommandapi-core:de.kentoj.kencommandapi.Main.main()].executor": "Run",
"Gradle./home/a/src/kento2/KenCommandAPI/kencommandapi-core [build].executor": "Run",
"Gradle./home/a/src/kento2/KenCommandAPI/kencommandapi-core [publish].executor": "Run",
@@ -147,6 +162,7 @@
"Gradle.KenCommandAPI [build].executor": "Run",
"Gradle.KenCommandAPI [clean test].executor": "Run",
"Gradle.KenCommandAPI [clean].executor": "Run",
+ "Gradle.KenCommandAPI [javadoc].executor": "Run",
"Gradle.KenCommandAPI [publishAllPublicationsToScrowRepository].executor": "Run",
"Gradle.KenCommandAPI [publish].executor": "Run",
"Gradle.KenCommandAPI [test].executor": "Run",
@@ -160,12 +176,21 @@
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
"RunOnceActivity.git.unshallow": "true",
+ "RunOnceActivity.typescript.service.memoryLimit.init": "true",
+ "SHARE_PROJECT_CONFIGURATION_FILES": "true",
+ "bazel.project.detector.is.bazel.project": "true",
+ "codeWithMe.voiceChat.enabledByDefault": "false",
+ "com.intellij.ide.FileNotInSourceRootChecker.no.check": "true",
"git-widget-placeholder": "master",
"kotlin-language-version-configured": "true",
- "last_opened_file_path": "/home/a/src/scrow/core",
+ "last_opened_file_path": "/home/a/src/random/rules_jvm_external",
+ "node.js.detected.package.jshint": "true",
+ "node.js.selected.package.jshint": "",
+ "nodejs_package_manager_path": "npm",
"project.structure.last.edited": "Modules",
"project.structure.proportion": "0.15",
"project.structure.side.proportion": "0.6268199",
+ "remote.dev.show.docker.config.missing.jdk.notification": "false",
"settings.editor.selected.configurable": "preferences.pluginManager"
}
}
@@ -178,19 +203,26 @@
+
+
+
-
+
+
+
+
+
+
-
@@ -200,34 +232,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- false
- true
- false
- true
+
+
+
+ @//bukkit:bukkit.publish
+
+
-
+
+
+ @//core:core.publish
+
+
+
+
+
+
+ @//kencommandapi-velocity:velocity.publish
+
+
+
+
+
+
+ @//velocity:velocity.publish
+
+
+
+
+
@@ -238,94 +298,49 @@
-
+
true
true
+ false
false
false
+ false
+ false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
- true
- false
- false
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
- true
- false
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
- true
- false
- false
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -561,6 +576,9 @@
+
+
+
@@ -579,10 +597,17 @@
-
+
+
-
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MODULE.bazel b/MODULE.bazel
new file mode 100644
index 0000000..a792429
--- /dev/null
+++ b/MODULE.bazel
@@ -0,0 +1,30 @@
+bazel_dep(name = "rules_java", version = "9.7.0")
+bazel_dep(name = "rules_jvm_external", version = "7.1")
+
+git_override(
+ module_name = "rules_jvm_external",
+ remote = "http://forge.kentoj.de/scrow/rules_jvm_external.git",
+)
+
+maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
+maven.install(
+ name = "maven",
+ artifacts = [
+ "com.leakyabstractions:result-api:1.0.1.0",
+ "com.leakyabstractions:result:1.0.2.0",
+ "org.jetbrains:annotations:26.0.2-1",
+ "com.google.guava:guava:33.5.0-jre",
+ "org.projectlombok:lombok:1.18.46",
+ "org.slf4j:slf4j-api:2.0.18",
+ "org.slf4j:slf4j-simple:2.0.18",
+ "net.kyori:adventure-api:5.2.0",
+ "io.papermc.paper:paper-api:[26.2.build,)",
+ "com.velocitypowered:velocity-api:4.1.0-20260802.091736-8",
+ ],
+ repositories = [
+ "https://repo.papermc.io/repository/maven-public/",
+ ],
+ known_contributing_modules = ["protobuf"],
+)
+
+use_repo(maven, "maven")
\ No newline at end of file
diff --git a/README.md b/README.md
index 4dd5331..c66dba4 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,97 @@
-example usage:
-http://forge.kentoj.de/scrow/core/src/commit/a55d9857738425e099fe9650cc1a705ceb2ece00/core-bukkit-impl/src/main/java/de/kentoj/scrow/bukkit/command/economy
\ No newline at end of file
+## TODO
+
+CI/CD to publish automatically
+
+## Build
+
+after installing bazel:
+```shell
+bazel build //bukkit
+bazel build //core
+bazel build //velocity
+```
+
+## Limitations
+
+- argument's can't require specific permissions.
+
+## Design
+
+Commands are trees of Literals.
+Literals can have arguments.
+Literals can also have Executors.
+
+arguments can be optional by giving them a default value.
+arguments can have suggestions.
+
+`/feed `:
+- feed is the root literal
+- `` is an argument: it's value is a User Object
+
+To allow the use of `/feed` without giving a user's name, i.e. to make the argument optional, we need to assign the
+user-argument a default value.
+
+### An example command
+
+Let's say we want to make a command that allows seeing how many coins another User has.
+If we don't specify a user, it should instead show the coins the sender has.
+
+### As a tree
+
+Visualized as a tree, we have a Node/Literal `coins` with an alias `balance` with an optional argument called `user`.
+To make an argument optional, it must have a default value.
+
+```text
+literal:coins/balance
+- has argument:user
+- has executor:getCoins
+```
+
+### As code
+
+```java
+import de.kentoj.kencommandapi.api.literal.RootLiteral;
+
+/**
+ * Show the amount coins a user has.
+ * If no user is given, it should show the amount the sender of the command has.
+ */
+public class CoinsCommand {
+
+ private final RootLiteral rootLiteral;
+ private final CommandArgumentSpec userArg;
+
+ public CoinsCommand() {
+ userArg = CommandArgumentSpec.builder("user", new UserArgumentType())
+ .withRequirement(p -> !p.hasPermission("economy.immune.getcoins"),
+ "you can't see that player's balance")
+ .withDefaultValue(CommandContext::sender)
+ .build();
+ rootLiteral = Literal.builder("coins", "balance")
+ .withPermission("economy.command.coins")
+ .withArgument(userArg)
+ .withSyncExecutor(this::getCoins)
+ .asRootLiteral(MessageStyle.PLAIN);
+ }
+
+ private Result getCoins(CommandContext ctx) {
+ var user = ctx.getArg(userArg);
+ ctx.getSender().sendMessage(user.name() + " has " + user.coins() + " coins");
+ return Results.success(new Object());
+ }
+
+ public RootLiteral rootLiteral() {
+ return rootLiteral;
+ }
+}
+```
+
+If we wanted to report an error, we could do for example `return Results.failure("That user doesn't exist")`.
+However, if we have a smart UserArgumentType, it automatically makes sure that the user exists.
+Additionally, we can also a Requirement in the CommandArgumentSpec.
+
+The registration of the command is platform-dependant. Typically, a class called CommandAPI can be used.
+```java
+CommandAPI api = new CommandAPI();
+register(new CoinsCommand().rootLiteral());
+```
\ No newline at end of file
diff --git a/TODO b/TODO
deleted file mode 100644
index f3e61d5..0000000
--- a/TODO
+++ /dev/null
@@ -1,10 +0,0 @@
-- suggest next literal/argument
-- fix:
-```
-[22:05:39 ERROR]: Could not pass event AsyncTabCompleteEvent to CoreBukkit v1.0.0-SNAPSHOT
-java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
- at core-bukkit-impl-all-1783368113525.jar//de.kentoj.kencommandapi.api.parser2.SuggestionCommandParser.getSuggestions(SuggestionCommandParser.java:39) ~[?:?]
- at core-bukkit-impl-all-1783368113525.jar//de.kentoj.kencommandapi.api.CommandHandler.getSuggestions(CommandHandler.java:44) ~[?:?]
- at core-bukkit-impl-all-1783368113525.jar//de.kentoj.kencommandapi.TabCompleteListener.onAsyncTabComplete(TabCompleteListener.java:27) ~[?:?]
- at co.aikar.timings.TimedEventExecutor.execute(TimedEventExecutor.java:80) ~[paper-api-1.21.11-R0.1-SNAPSHOT.jar:?]
-```
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
deleted file mode 100644
index b51ec73..0000000
--- a/build.gradle.kts
+++ /dev/null
@@ -1,7 +0,0 @@
-group = "de.kentoj.scrow"
-version = "0.33-SNAPSHOT"
-
-subprojects {
- group = rootProject.group
- version = rootProject.version
-}
diff --git a/bukkit/BUILD b/bukkit/BUILD
new file mode 100644
index 0000000..47dc8a8
--- /dev/null
+++ b/bukkit/BUILD
@@ -0,0 +1,15 @@
+load("@rules_jvm_external//:defs.bzl", "artifact", "java_export")
+
+java_export(
+ name = "bukkit",
+ maven_coordinates = "de.kentoj.scrow:kencommandapi-bukkit:0.34-SNAPSHOT",
+ srcs = glob(["src/main/**/*.java"]),
+ deps = [
+ "//core",
+ artifact("org.jetbrains:annotations"),
+ artifact("io.papermc.paper:paper-api"),
+ ],
+ exports = [
+ "//core",
+ ],
+)
\ No newline at end of file
diff --git a/bukkit/src/main/java/de/kentoj/kencommandapi/BukkitKenCommand.java b/bukkit/src/main/java/de/kentoj/kencommandapi/BukkitKenCommand.java
new file mode 100644
index 0000000..410b141
--- /dev/null
+++ b/bukkit/src/main/java/de/kentoj/kencommandapi/BukkitKenCommand.java
@@ -0,0 +1,40 @@
+package de.kentoj.kencommandapi;
+
+import de.kentoj.kencommandapi.api.CommandHandler;
+import de.kentoj.kencommandapi.api.literal.RootLiteral;
+import de.kentoj.kencommandapi.api.platform.MessageStyle;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+public class BukkitKenCommand extends Command {
+
+ private final RootLiteral rootLiteral;
+ private final CommandHandler commandHandler;
+ private final Class senderType;
+
+ protected BukkitKenCommand(CommandHandler commandHandler, RootLiteral rootLiteral, Class senderType) {
+ super(rootLiteral.names()[0]);
+ this.commandHandler = commandHandler;
+ this.rootLiteral = rootLiteral;
+ this.senderType = senderType;
+
+ this.setAliases(Arrays.asList(rootLiteral.names()));
+ this.setPermission(rootLiteral.permission());
+ }
+
+ @Override
+ public boolean execute(@NotNull CommandSender commandSender, @NotNull String commandLabel, @NotNull String[] args) {
+ if (!senderType.isInstance(commandSender)) {
+ commandSender.sendMessage(MessageStyle.PLAIN.err("You can't use that command"));
+ return true;
+ }
+ T sender = senderType.cast(commandSender);
+
+ commandHandler.invoke(rootLiteral, sender, Stream.of(args).filter(str -> !str.isEmpty()));
+ return true;
+ }
+}
diff --git a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/CommandAPI.java b/bukkit/src/main/java/de/kentoj/kencommandapi/CommandAPI.java
similarity index 66%
rename from kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/CommandAPI.java
rename to bukkit/src/main/java/de/kentoj/kencommandapi/CommandAPI.java
index 9c27132..8ea24c5 100644
--- a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/CommandAPI.java
+++ b/bukkit/src/main/java/de/kentoj/kencommandapi/CommandAPI.java
@@ -13,15 +13,17 @@ import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
-public class CommandAPI {
+public class CommandAPI {
- private final CommandHandler commandHandler;
- private final List> registeredNodes = new ArrayList<>();
+ private final CommandHandler commandHandler;
+ private final List> registeredNodes = new ArrayList<>();
private final SimpleCommandMap commandMap;
+ private final Class senderType;
- public CommandAPI(Plugin plugin) {
+ public CommandAPI(Plugin plugin, Class senderType) {
+ this.senderType = senderType;
this.commandHandler = new CommandHandler<>(CommandSender::sendMessage, Permissible::hasPermission);
- Bukkit.getPluginManager().registerEvents(new TabCompleteListener(this, commandHandler), plugin);
+ Bukkit.getPluginManager().registerEvents(new TabCompleteListener<>(this, commandHandler, senderType), plugin);
try {
Field field = Bukkit.getPluginManager().getClass().getDeclaredField("commandMap");
@@ -32,17 +34,17 @@ public class CommandAPI {
}
}
- @Nullable RootLiteral getRegistered(String name) {
+ @Nullable RootLiteral getRegistered(String name) {
for (var node : registeredNodes) {
- for (String nodeName : node.getNames()) {
+ for (String nodeName : node.names()) {
if (nodeName.equals(name)) return node;
}
}
return null;
}
- public void register(RootLiteral rootNode) {
+ public void register(RootLiteral rootNode) {
registeredNodes.add(rootNode);
- commandMap.register(rootNode.getNames()[0], new BukkitKenCommand(commandHandler, rootNode));
+ commandMap.register(rootNode.names()[0], new BukkitKenCommand<>(commandHandler, rootNode, senderType));
}
}
diff --git a/bukkit/src/main/java/de/kentoj/kencommandapi/TabCompleteListener.java b/bukkit/src/main/java/de/kentoj/kencommandapi/TabCompleteListener.java
new file mode 100644
index 0000000..6d6e080
--- /dev/null
+++ b/bukkit/src/main/java/de/kentoj/kencommandapi/TabCompleteListener.java
@@ -0,0 +1,46 @@
+package de.kentoj.kencommandapi;
+
+import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent;
+import de.kentoj.kencommandapi.api.CommandHandler;
+import org.bukkit.command.CommandSender;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+
+import java.util.Arrays;
+
+public class TabCompleteListener implements Listener {
+
+ private final CommandAPI commandAPI;
+ private final CommandHandler commandHandler;
+ private final Class senderType;
+
+ public TabCompleteListener(CommandAPI commandAPI, CommandHandler commandHandler, Class senderType) {
+ this.commandAPI = commandAPI;
+ this.commandHandler = commandHandler;
+ this.senderType = senderType;
+ }
+
+ @EventHandler(ignoreCancelled = true)
+ public void onAsyncTabComplete(AsyncTabCompleteEvent ev) {
+ if (!ev.isCommand()) return;
+ var args = ev.getBuffer().substring(1).split(" ");
+ if (args.length == 0) return;
+
+ var label = args[0];
+ {
+ // commands can be called with a ':' prefix. strip it.
+ int l = label.indexOf(':');
+ if (l >= 0)
+ label = label.substring(l + 1);
+ }
+ var literal = commandAPI.getRegistered(label);
+ if (literal == null) return;
+
+ if (!senderType.isInstance(ev.getSender()))
+ return;
+
+ T sender = senderType.cast(ev.getSender());
+ ev.setHandled(true);
+ ev.setCompletions(commandHandler.getSuggestions(literal, sender, Arrays.copyOfRange(args, 1, args.length), ev.getBuffer().endsWith(" ")));
+ }
+}
diff --git a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/suggestionprovider/PlayerSuggestionProvider.java b/bukkit/src/main/java/de/kentoj/kencommandapi/suggestionprovider/PlayerSuggestionProvider.java
similarity index 65%
rename from kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/suggestionprovider/PlayerSuggestionProvider.java
rename to bukkit/src/main/java/de/kentoj/kencommandapi/suggestionprovider/PlayerSuggestionProvider.java
index 3db53a4..c6880c6 100644
--- a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/suggestionprovider/PlayerSuggestionProvider.java
+++ b/bukkit/src/main/java/de/kentoj/kencommandapi/suggestionprovider/PlayerSuggestionProvider.java
@@ -2,14 +2,13 @@ package de.kentoj.kencommandapi.suggestionprovider;
import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
import org.bukkit.Bukkit;
-import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.List;
-public class PlayerSuggestionProvider implements SuggestionProvider {
+public final class PlayerSuggestionProvider implements SuggestionProvider {
@Override
- public List suggest(CommandSender __) {
+ public List suggest(T __) {
return Bukkit.getOnlinePlayers().stream()
.map(Player::getName)
.toList();
diff --git a/bukkit/src/main/java/de/kentoj/kencommandapi/type/OfflinePlayerArgumentType.java b/bukkit/src/main/java/de/kentoj/kencommandapi/type/OfflinePlayerArgumentType.java
new file mode 100644
index 0000000..5b17dd0
--- /dev/null
+++ b/bukkit/src/main/java/de/kentoj/kencommandapi/type/OfflinePlayerArgumentType.java
@@ -0,0 +1,34 @@
+package de.kentoj.kencommandapi.type;
+
+import de.kentoj.kencommandapi.api.argument.ArgumentType;
+import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
+import de.kentoj.kencommandapi.suggestionprovider.PlayerSuggestionProvider;
+import org.bukkit.Bukkit;
+import org.bukkit.OfflinePlayer;
+
+import java.util.UUID;
+
+public class OfflinePlayerArgumentType implements ArgumentType {
+
+ @Override
+ public OfflinePlayer parseInputUnchecked(String string) {
+ // UUIDs are always 36 chars
+ if (string.length() == 36) {
+ try {
+ return Bukkit.getOfflinePlayer(UUID.fromString(string));
+ } catch (IllegalArgumentException ex) {
+ throw new IllegalArgumentException("Invalid UUID");
+ }
+ }
+
+ OfflinePlayer player = Bukkit.getPlayerExact(string);
+ if (player == null || (!player.hasPlayedBefore() && !player.isOnline()))
+ throw new IllegalArgumentException("Player not found");
+ return player;
+ }
+
+ @Override
+ public SuggestionProvider getDefaultSuggestionProvider() {
+ return new PlayerSuggestionProvider<>();
+ }
+}
diff --git a/bukkit/src/main/java/de/kentoj/kencommandapi/type/PlayerArgumentType.java b/bukkit/src/main/java/de/kentoj/kencommandapi/type/PlayerArgumentType.java
new file mode 100644
index 0000000..2b4065e
--- /dev/null
+++ b/bukkit/src/main/java/de/kentoj/kencommandapi/type/PlayerArgumentType.java
@@ -0,0 +1,22 @@
+package de.kentoj.kencommandapi.type;
+
+import de.kentoj.kencommandapi.api.argument.ArgumentType;
+import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
+import de.kentoj.kencommandapi.suggestionprovider.PlayerSuggestionProvider;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+
+public class PlayerArgumentType implements ArgumentType {
+
+ @Override
+ public Player parseInputUnchecked(String string) {
+ var player = Bukkit.getPlayer(string);
+ if (player == null) throw new IllegalArgumentException("player not online");
+ return player;
+ }
+
+ @Override
+ public SuggestionProvider getDefaultSuggestionProvider() {
+ return new PlayerSuggestionProvider<>();
+ }
+}
diff --git a/core/BUILD b/core/BUILD
new file mode 100644
index 0000000..03f0de0
--- /dev/null
+++ b/core/BUILD
@@ -0,0 +1,16 @@
+load("@rules_jvm_external//:defs.bzl", "artifact", "java_export")
+
+java_export(
+ name = "core",
+ maven_coordinates = "de.kentoj.scrow:kencommandapi-core:0.34-SNAPSHOT",
+ srcs = glob(["src/main/**/*.java"]),
+ visibility = ["//visibility:public"],
+ deps = [
+ artifact("com.leakyabstractions:result-api"),
+ artifact("com.leakyabstractions:result"),
+ artifact("org.jetbrains:annotations"),
+ artifact("com.google.guava:guava"),
+ artifact("org.slf4j:slf4j-api"),
+ artifact("net.kyori:adventure-api"),
+ ],
+)
\ No newline at end of file
diff --git a/core/src/main/java/de/kentoj/kencommandapi/api/CommandHandler.java b/core/src/main/java/de/kentoj/kencommandapi/api/CommandHandler.java
new file mode 100644
index 0000000..f728250
--- /dev/null
+++ b/core/src/main/java/de/kentoj/kencommandapi/api/CommandHandler.java
@@ -0,0 +1,47 @@
+package de.kentoj.kencommandapi.api;
+
+import de.kentoj.kencommandapi.api.literal.RootLiteral;
+import de.kentoj.kencommandapi.api.platform.HasPermissionMethod;
+import de.kentoj.kencommandapi.api.platform.SendMessageMethod;
+import de.kentoj.kencommandapi.internal.parser.CommandParseHelper;
+import de.kentoj.kencommandapi.internal.parser.InvocationCommandParser;
+import de.kentoj.kencommandapi.internal.parser.SuggestionCommandParser;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Stream;
+
+public class CommandHandler {
+
+ private final SendMessageMethod sendMessageMethod;
+ private final InvocationCommandParser invocationParser;
+ private final SuggestionCommandParser suggestionParser;
+
+ public CommandHandler(SendMessageMethod sendMessageMethod, HasPermissionMethod hasPermissionMethod) {
+ this.sendMessageMethod = sendMessageMethod;
+ var parseHelper = new CommandParseHelper<>(hasPermissionMethod);
+ invocationParser = new InvocationCommandParser<>(parseHelper);
+ suggestionParser = new SuggestionCommandParser<>(parseHelper);
+ }
+
+ public void invoke(RootLiteral rootLiteral, T sender, Stream args) {
+ invocationParser.parse(rootLiteral, sender, args.toList().iterator())
+ .ifSuccessOrElse(data -> exec(rootLiteral, data),
+ err -> sendMessageMethod.send(sender, rootLiteral.messageStyle().err(err)));
+ }
+
+ private void exec(RootLiteral rootLiteral, InvocationCommandParser.ExecutionData data) {
+ var ctx = data.context();
+ data.executor().execute(ctx)
+ .whenComplete((result, ex) -> {
+ if (ex != null)
+ sendMessageMethod.send(ctx.sender(), rootLiteral.messageStyle().exception(ex));
+ result.ifFailure(err ->
+ sendMessageMethod.send(ctx.sender(), rootLiteral.messageStyle().err(err)));
+ });
+ }
+
+ public List getSuggestions(RootLiteral rootNode, T sender, String[] args, boolean trailingSpace) {
+ return suggestionParser.suggest(rootNode, sender, Arrays.stream(args).iterator(), trailingSpace);
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/de/kentoj/kencommandapi/api/argument/ArgumentRequirement.java b/core/src/main/java/de/kentoj/kencommandapi/api/argument/ArgumentRequirement.java
new file mode 100644
index 0000000..47a8976
--- /dev/null
+++ b/core/src/main/java/de/kentoj/kencommandapi/api/argument/ArgumentRequirement.java
@@ -0,0 +1,12 @@
+package de.kentoj.kencommandapi.api.argument;
+
+import java.util.function.Predicate;
+
+/**
+ * @param type of the parsed argument value
+ */
+public record ArgumentRequirement(
+ Predicate predicate,
+ String message
+) {
+}
diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/ArgumentType.java b/core/src/main/java/de/kentoj/kencommandapi/api/argument/ArgumentType.java
similarity index 51%
rename from kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/ArgumentType.java
rename to core/src/main/java/de/kentoj/kencommandapi/api/argument/ArgumentType.java
index 00cda59..360a3fc 100644
--- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/ArgumentType.java
+++ b/core/src/main/java/de/kentoj/kencommandapi/api/argument/ArgumentType.java
@@ -2,21 +2,31 @@ package de.kentoj.kencommandapi.api.argument;
import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
+import java.util.function.Predicate;
+
public interface ArgumentType {
/**
+ * parses the given input and returns it.
+ * Unlike {@link CommandArgumentSpec#parseInput(String)}, this does not check any additional requirements.
+ *
* @throws IllegalArgumentException if string invalid
+ * @see CommandArgumentSpec#parseInput(String)
+ * @see CommandArgumentSpecBuilder#withRequirement(Predicate, String)
*/
- V parseInput(String string);
+ V parseInputUnchecked(String input) throws IllegalArgumentException;
- SuggestionProvider defaultSuggestionProvider();
+ /**
+ * @see CommandArgumentSpec#suggestionProvider()
+ */
+ SuggestionProvider getDefaultSuggestionProvider();
/**
* @return whether this argument is greedy. Greedy arguments consume all remaining input instead of a single word.
* For example, given the 2 words "hello" "world" as input, a greedy argument parses "hello world" as a single value.
* Greedy arguments may not be followed by another argument or a literal
*/
- default boolean isGreedy() {
+ default boolean greedy() {
return false;
}
}
diff --git a/core/src/main/java/de/kentoj/kencommandapi/api/argument/CommandArgument.java b/core/src/main/java/de/kentoj/kencommandapi/api/argument/CommandArgument.java
new file mode 100644
index 0000000..8e79a06
--- /dev/null
+++ b/core/src/main/java/de/kentoj/kencommandapi/api/argument/CommandArgument.java
@@ -0,0 +1,8 @@
+package de.kentoj.kencommandapi.api.argument;
+
+public interface CommandArgument {
+
+ V value();
+
+ CommandArgumentSpec argument();
+}
diff --git a/core/src/main/java/de/kentoj/kencommandapi/api/argument/CommandArgumentImpl.java b/core/src/main/java/de/kentoj/kencommandapi/api/argument/CommandArgumentImpl.java
new file mode 100644
index 0000000..71bd78a
--- /dev/null
+++ b/core/src/main/java/de/kentoj/kencommandapi/api/argument/CommandArgumentImpl.java
@@ -0,0 +1,7 @@
+package de.kentoj.kencommandapi.api.argument;
+
+public record CommandArgumentImpl(
+ V value,
+ CommandArgumentSpec argument
+) implements CommandArgument {
+}
diff --git a/core/src/main/java/de/kentoj/kencommandapi/api/argument/CommandArgumentSpec.java b/core/src/main/java/de/kentoj/kencommandapi/api/argument/CommandArgumentSpec.java
new file mode 100644
index 0000000..726ad14
--- /dev/null
+++ b/core/src/main/java/de/kentoj/kencommandapi/api/argument/CommandArgumentSpec.java
@@ -0,0 +1,32 @@
+package de.kentoj.kencommandapi.api.argument;
+
+import de.kentoj.kencommandapi.api.invocation.CommandContext;
+import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.function.Function;
+
+public sealed interface CommandArgumentSpec permits CommandArgumentSpecImpl {
+
+ static CommandArgumentSpecBuilder builder(String id, ArgumentType type) {
+ return new CommandArgumentSpecBuilder<>(id, type);
+ }
+
+ String id();
+
+ ArgumentType type();
+
+ /**
+ * @throws IllegalArgumentException if string invalid or if a requirement is not met
+ */
+ V parseInput(String rawInput);
+
+ @Nullable Function, V> defaultValueProvider();
+
+ /**
+ * @return the explicitly set SuggestionProvider or the default suggestions for the type if unset/null
+ */
+ /* explicit @NotNull to avoid confusion */
+ @NotNull SuggestionProvider suggestionProvider();
+}
diff --git a/core/src/main/java/de/kentoj/kencommandapi/api/argument/CommandArgumentSpecBuilder.java b/core/src/main/java/de/kentoj/kencommandapi/api/argument/CommandArgumentSpecBuilder.java
new file mode 100644
index 0000000..fa767e0
--- /dev/null
+++ b/core/src/main/java/de/kentoj/kencommandapi/api/argument/CommandArgumentSpecBuilder.java
@@ -0,0 +1,47 @@
+package de.kentoj.kencommandapi.api.argument;
+
+import de.kentoj.kencommandapi.api.invocation.CommandContext;
+import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * @param type of the command sender
+ * @param type of the value
+ */
+public class CommandArgumentSpecBuilder {
+
+ private final String id;
+ private final ArgumentType type;
+ private @Nullable Function, V> defaultValueProvider;
+ private @Nullable SuggestionProvider suggestionProvider;
+ private final List> requirements = new ArrayList<>();
+
+ public CommandArgumentSpecBuilder(String id, ArgumentType type) {
+ this.id = id;
+ this.type = type;
+ }
+
+ public CommandArgumentSpecBuilder withRequirement(Predicate predicate, String message) {
+ requirements.add(new ArgumentRequirement<>(predicate, message));
+ return this;
+ }
+
+ public CommandArgumentSpecBuilder withDefaultValue(@Nullable Function, V> defaultValueProvider) {
+ this.defaultValueProvider = defaultValueProvider;
+ return this;
+ }
+
+ public CommandArgumentSpecBuilder withSuggestionProvider(@Nullable SuggestionProvider suggestionProvider) {
+ this.suggestionProvider = suggestionProvider;
+ return this;
+ }
+
+ public CommandArgumentSpec build() {
+ return new CommandArgumentSpecImpl<>(id, type, requirements, defaultValueProvider, suggestionProvider);
+ }
+}
diff --git a/core/src/main/java/de/kentoj/kencommandapi/api/argument/CommandArgumentSpecImpl.java b/core/src/main/java/de/kentoj/kencommandapi/api/argument/CommandArgumentSpecImpl.java
new file mode 100644
index 0000000..2f80387
--- /dev/null
+++ b/core/src/main/java/de/kentoj/kencommandapi/api/argument/CommandArgumentSpecImpl.java
@@ -0,0 +1,38 @@
+package de.kentoj.kencommandapi.api.argument;
+
+import de.kentoj.kencommandapi.api.invocation.CommandContext;
+import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.function.Function;
+
+public record CommandArgumentSpecImpl(
+ String id,
+ ArgumentType type,
+ List> requirements,
+ Function, V> defaultValueProvider,
+ @Nullable SuggestionProvider suggestionProvider
+) implements CommandArgumentSpec {
+
+ @Override
+ public V parseInput(String rawInput) {
+ var parsed = type.parseInputUnchecked(rawInput);
+ for (var requirement : requirements) {
+ if (!requirement.predicate().test(parsed))
+ throw new IllegalArgumentException(requirement.message());
+ }
+ return parsed;
+ }
+
+ @Override
+ public @Nullable Function, V> defaultValueProvider() {
+ return defaultValueProvider;
+ }
+
+ @Override
+ public @NotNull SuggestionProvider suggestionProvider() {
+ return suggestionProvider != null ? suggestionProvider : type.getDefaultSuggestionProvider();
+ }
+}
diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/types/IntegerArgumentType.java b/core/src/main/java/de/kentoj/kencommandapi/api/argument/types/IntegerArgumentType.java
similarity index 60%
rename from kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/types/IntegerArgumentType.java
rename to core/src/main/java/de/kentoj/kencommandapi/api/argument/types/IntegerArgumentType.java
index 80d6ee8..5f81e03 100644
--- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/types/IntegerArgumentType.java
+++ b/core/src/main/java/de/kentoj/kencommandapi/api/argument/types/IntegerArgumentType.java
@@ -2,17 +2,16 @@ package de.kentoj.kencommandapi.api.argument.types;
import de.kentoj.kencommandapi.api.argument.ArgumentType;
import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
-import de.kentoj.kencommandapi.api.suggestion.NoSuggestionProvider;
public class IntegerArgumentType implements ArgumentType {
@Override
- public Integer parseInput(String string) {
+ public Integer parseInputUnchecked(String string) {
return Integer.parseInt(string);
}
@Override
- public SuggestionProvider defaultSuggestionProvider() {
- return new NoSuggestionProvider<>();
+ public SuggestionProvider getDefaultSuggestionProvider() {
+ return SuggestionProvider.none();
}
}
diff --git a/core/src/main/java/de/kentoj/kencommandapi/api/argument/types/StringArgumentType.java b/core/src/main/java/de/kentoj/kencommandapi/api/argument/types/StringArgumentType.java
new file mode 100644
index 0000000..6e0ee6a
--- /dev/null
+++ b/core/src/main/java/de/kentoj/kencommandapi/api/argument/types/StringArgumentType.java
@@ -0,0 +1,17 @@
+package de.kentoj.kencommandapi.api.argument.types;
+
+import de.kentoj.kencommandapi.api.argument.ArgumentType;
+import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
+
+public record StringArgumentType(boolean greedy) implements ArgumentType {
+
+ @Override
+ public String parseInputUnchecked(String string) {
+ return string;
+ }
+
+ @Override
+ public SuggestionProvider getDefaultSuggestionProvider() {
+ return SuggestionProvider.none();
+ }
+}
diff --git a/core/src/main/java/de/kentoj/kencommandapi/api/invocation/CommandContext.java b/core/src/main/java/de/kentoj/kencommandapi/api/invocation/CommandContext.java
new file mode 100644
index 0000000..471b14a
--- /dev/null
+++ b/core/src/main/java/de/kentoj/kencommandapi/api/invocation/CommandContext.java
@@ -0,0 +1,37 @@
+package de.kentoj.kencommandapi.api.invocation;
+
+import de.kentoj.kencommandapi.api.argument.CommandArgument;
+import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
+
+import java.util.Map;
+
+public interface CommandContext {
+
+ T sender();
+
+ CommandArgument getParsedArgument(String id);
+
+ /**
+ * @return the value of the argument with the specified id
+ * @param the type of the value of the argument with the specified id
+ */
+ @SuppressWarnings("unchecked")
+ default V getArg(String id) {
+ return (V) getParsedArgument(id).value();
+ }
+
+ /**
+ * @param arg the argument whose value to return
+ * @return the value of the argument
+ * @param the type of the value of the argument
+ */
+ default V getArg(CommandArgumentSpec arg) {
+ return getArg(arg.id());
+ }
+
+ default CommandArgument getParsedArgument(CommandArgumentSpec arg) {
+ return getParsedArgument(arg.id());
+ }
+
+ Map> parsedArguments();
+}
diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/invocation/CommandExecutor.java b/core/src/main/java/de/kentoj/kencommandapi/api/invocation/CommandExecutor.java
similarity index 100%
rename from kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/invocation/CommandExecutor.java
rename to core/src/main/java/de/kentoj/kencommandapi/api/invocation/CommandExecutor.java
diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/invocation/SyncCommandExecutor.java b/core/src/main/java/de/kentoj/kencommandapi/api/invocation/SyncCommandExecutor.java
similarity index 100%
rename from kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/invocation/SyncCommandExecutor.java
rename to core/src/main/java/de/kentoj/kencommandapi/api/invocation/SyncCommandExecutor.java
diff --git a/core/src/main/java/de/kentoj/kencommandapi/api/literal/Literal.java b/core/src/main/java/de/kentoj/kencommandapi/api/literal/Literal.java
new file mode 100644
index 0000000..57d9d76
--- /dev/null
+++ b/core/src/main/java/de/kentoj/kencommandapi/api/literal/Literal.java
@@ -0,0 +1,31 @@
+package de.kentoj.kencommandapi.api.literal;
+
+import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
+import de.kentoj.kencommandapi.api.invocation.CommandExecutor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+public interface Literal {
+ static LiteralBuilder builder(String... name) {
+ return new LiteralBuilder<>(name);
+ }
+
+ @NotNull String[] names();
+
+ /**
+ * @return what the literal does
+ */
+ @Nullable String description();
+
+ List> arguments();
+
+ @Nullable CommandExecutor executor();
+
+ @Nullable Literal getLiteral(@NotNull String name);
+
+ @NotNull List> literals();
+
+ @Nullable String permission();
+}
diff --git a/core/src/main/java/de/kentoj/kencommandapi/api/literal/LiteralBuilder.java b/core/src/main/java/de/kentoj/kencommandapi/api/literal/LiteralBuilder.java
new file mode 100644
index 0000000..ec18fbb
--- /dev/null
+++ b/core/src/main/java/de/kentoj/kencommandapi/api/literal/LiteralBuilder.java
@@ -0,0 +1,91 @@
+package de.kentoj.kencommandapi.api.literal;
+
+import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
+import de.kentoj.kencommandapi.api.invocation.CommandExecutor;
+import de.kentoj.kencommandapi.api.invocation.SyncCommandExecutor;
+import de.kentoj.kencommandapi.api.platform.MessageStyle;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+public final class LiteralBuilder {
+
+ private final String[] names;
+ private @Nullable String description;
+ private @Nullable CommandExecutor executor;
+ private @Nullable String permission;
+ private final List> arguments = new ArrayList<>();
+ private final List> literals = new ArrayList<>();
+
+ LiteralBuilder(String... names) {
+ checkArgument(names.length > 0, "at least one name is required");
+ this.names = names;
+ }
+
+ /**
+ * @param description what the literal does
+ */
+ public LiteralBuilder withDescription(@Nullable String description) {
+ this.description = description;
+ return this;
+ }
+
+ public LiteralBuilder withExecutor(@Nullable CommandExecutor executor) {
+ this.executor = executor;
+ return this;
+ }
+
+ public LiteralBuilder withSyncExecutor(@Nullable SyncCommandExecutor executor) {
+ return withExecutor(executor);
+ }
+
+ public LiteralBuilder withPermission(@Nullable String permission) {
+ this.permission = permission;
+ return this;
+ }
+
+ /**
+ * @throws IllegalArgumentException if there is already an argument with the same id
+ * @throws IllegalStateException if a greedy argument was added to the literal previously
+ */
+ public LiteralBuilder withArgument(CommandArgumentSpec argument) {
+ checkArgument(!hasGreedyArg(), "cannot add an argument after a greedy argument");
+ if (arguments.stream().anyMatch(arg -> arg.id().equals(argument.id())))
+ throw new IllegalArgumentException("cannot add multiple arguments with the same id");
+
+ this.arguments.add(argument);
+ return this;
+ }
+
+ /**
+ * @throws IllegalArgumentException if there is already a literal with the same name
+ * @throws IllegalStateException if a greedy argument was added to the literal previously
+ */
+ public LiteralBuilder withLiteral(Literal literal) {
+ checkArgument(!hasGreedyArg(), "cannot add a literal after a greedy argument");
+ for (String name : literal.names())
+ for (var l : literals)
+ if (l.getLiteral(name) != null)
+ throw new IllegalArgumentException("literal with same name already exists");
+
+ this.literals.add(literal);
+ return this;
+ }
+
+ public RootLiteral asRootLiteral(MessageStyle messageStyle) {
+ return new RootLiteralImpl<>(names, description, literals, arguments, executor, permission, messageStyle);
+ }
+
+ public Literal build() {
+ return new LiteralImpl<>(names, description, literals, arguments, executor, permission);
+ }
+
+ private boolean hasGreedyArg() {
+ if (arguments.isEmpty()) return false;
+ var arg = arguments.getLast();
+ return arg != null && arg.type().greedy();
+ }
+}
diff --git a/core/src/main/java/de/kentoj/kencommandapi/api/literal/LiteralImpl.java b/core/src/main/java/de/kentoj/kencommandapi/api/literal/LiteralImpl.java
new file mode 100644
index 0000000..0a26c1c
--- /dev/null
+++ b/core/src/main/java/de/kentoj/kencommandapi/api/literal/LiteralImpl.java
@@ -0,0 +1,32 @@
+package de.kentoj.kencommandapi.api.literal;
+
+import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
+import de.kentoj.kencommandapi.api.invocation.CommandExecutor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+/**
+ * @param description what the literal does
+ */
+record LiteralImpl(
+ String[] names,
+ String description,
+ List> literals,
+ List> arguments,
+ CommandExecutor executor,
+ @Nullable String permission
+) implements Literal {
+
+ @Override
+ public @Nullable Literal getLiteral(@NotNull String name) {
+ for (Literal literal : literals) {
+ for (var literalName : literal.names()) {
+ if (literalName.equalsIgnoreCase(name))
+ return literal;
+ }
+ }
+ return null;
+ }
+}
diff --git a/core/src/main/java/de/kentoj/kencommandapi/api/literal/RootLiteral.java b/core/src/main/java/de/kentoj/kencommandapi/api/literal/RootLiteral.java
new file mode 100644
index 0000000..42321f6
--- /dev/null
+++ b/core/src/main/java/de/kentoj/kencommandapi/api/literal/RootLiteral.java
@@ -0,0 +1,8 @@
+package de.kentoj.kencommandapi.api.literal;
+
+import de.kentoj.kencommandapi.api.platform.MessageStyle;
+
+public sealed interface RootLiteral extends Literal permits RootLiteralImpl {
+
+ MessageStyle messageStyle();
+}
diff --git a/core/src/main/java/de/kentoj/kencommandapi/api/literal/RootLiteralImpl.java b/core/src/main/java/de/kentoj/kencommandapi/api/literal/RootLiteralImpl.java
new file mode 100644
index 0000000..d49b511
--- /dev/null
+++ b/core/src/main/java/de/kentoj/kencommandapi/api/literal/RootLiteralImpl.java
@@ -0,0 +1,29 @@
+package de.kentoj.kencommandapi.api.literal;
+
+import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
+import de.kentoj.kencommandapi.api.invocation.CommandExecutor;
+import de.kentoj.kencommandapi.api.platform.MessageStyle;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+record RootLiteralImpl(
+ String[] names,
+ String description,
+ List> literals,
+ List> arguments,
+ CommandExecutor executor,
+ @Nullable String permission,
+ MessageStyle messageStyle
+) implements RootLiteral {
+ @Override
+ public @Nullable Literal getLiteral(@NotNull String name) {
+ for (var l : literals) {
+ for (var lName : l.names())
+ if (lName.equalsIgnoreCase(name))
+ return l;
+ }
+ return null;
+ }
+}
diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/platform/HasPermissionMethod.java b/core/src/main/java/de/kentoj/kencommandapi/api/platform/HasPermissionMethod.java
similarity index 100%
rename from kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/platform/HasPermissionMethod.java
rename to core/src/main/java/de/kentoj/kencommandapi/api/platform/HasPermissionMethod.java
diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/platform/MessageStyle.java b/core/src/main/java/de/kentoj/kencommandapi/api/platform/MessageStyle.java
similarity index 100%
rename from kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/platform/MessageStyle.java
rename to core/src/main/java/de/kentoj/kencommandapi/api/platform/MessageStyle.java
diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/platform/PlainMessageStyle.java b/core/src/main/java/de/kentoj/kencommandapi/api/platform/PlainMessageStyle.java
similarity index 86%
rename from kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/platform/PlainMessageStyle.java
rename to core/src/main/java/de/kentoj/kencommandapi/api/platform/PlainMessageStyle.java
index ef35a0c..52a98bc 100644
--- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/platform/PlainMessageStyle.java
+++ b/core/src/main/java/de/kentoj/kencommandapi/api/platform/PlainMessageStyle.java
@@ -2,7 +2,7 @@ package de.kentoj.kencommandapi.api.platform;
import net.kyori.adventure.text.Component;
-public class PlainMessageStyle implements MessageStyle {
+class PlainMessageStyle implements MessageStyle {
@Override
public Component ok(Component msg) {
return msg;
diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/platform/SendMessageMethod.java b/core/src/main/java/de/kentoj/kencommandapi/api/platform/SendMessageMethod.java
similarity index 100%
rename from kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/platform/SendMessageMethod.java
rename to core/src/main/java/de/kentoj/kencommandapi/api/platform/SendMessageMethod.java
diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/suggestion/NoSuggestionProvider.java b/core/src/main/java/de/kentoj/kencommandapi/api/suggestion/NoSuggestionProvider.java
similarity index 63%
rename from kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/suggestion/NoSuggestionProvider.java
rename to core/src/main/java/de/kentoj/kencommandapi/api/suggestion/NoSuggestionProvider.java
index 89a9ea7..44bb856 100644
--- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/suggestion/NoSuggestionProvider.java
+++ b/core/src/main/java/de/kentoj/kencommandapi/api/suggestion/NoSuggestionProvider.java
@@ -2,10 +2,8 @@ package de.kentoj.kencommandapi.api.suggestion;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.CompletableFuture;
-
-public class NoSuggestionProvider implements SuggestionProvider {
+class NoSuggestionProvider implements SuggestionProvider {
@Override
public List suggest(T __) {
return Collections.emptyList();
diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/suggestion/SuggestionProvider.java b/core/src/main/java/de/kentoj/kencommandapi/api/suggestion/SuggestionProvider.java
similarity index 75%
rename from kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/suggestion/SuggestionProvider.java
rename to core/src/main/java/de/kentoj/kencommandapi/api/suggestion/SuggestionProvider.java
index 68c7d58..135f3aa 100644
--- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/suggestion/SuggestionProvider.java
+++ b/core/src/main/java/de/kentoj/kencommandapi/api/suggestion/SuggestionProvider.java
@@ -1,10 +1,13 @@
package de.kentoj.kencommandapi.api.suggestion;
import java.util.List;
-import java.util.concurrent.CompletableFuture;
public interface SuggestionProvider {
+ static SuggestionProvider none() {
+ return new NoSuggestionProvider<>();
+ }
+
/**
* @return an unfiltered(no permission checks or filtering by input) list of strings that may be used for the argument/literal
*/
diff --git a/core/src/main/java/de/kentoj/kencommandapi/internal/CommandContextImpl.java b/core/src/main/java/de/kentoj/kencommandapi/internal/CommandContextImpl.java
new file mode 100644
index 0000000..5b8db98
--- /dev/null
+++ b/core/src/main/java/de/kentoj/kencommandapi/internal/CommandContextImpl.java
@@ -0,0 +1,22 @@
+package de.kentoj.kencommandapi.internal;
+
+import de.kentoj.kencommandapi.api.argument.CommandArgument;
+import de.kentoj.kencommandapi.api.invocation.CommandContext;
+
+import java.util.Map;
+
+public record CommandContextImpl(
+ T sender,
+ Map> parsedArguments
+) implements CommandContext {
+
+ @Override
+ public CommandArgument getParsedArgument(String id) {
+ try {
+ //noinspection unchecked
+ return (CommandArgument) parsedArguments.get(id);
+ } catch (ClassCastException e) {
+ throw new RuntimeException("requested argument with wrong type", e);
+ }
+ }
+}
diff --git a/core/src/main/java/de/kentoj/kencommandapi/internal/parser/CommandParseHelper.java b/core/src/main/java/de/kentoj/kencommandapi/internal/parser/CommandParseHelper.java
new file mode 100644
index 0000000..17b7774
--- /dev/null
+++ b/core/src/main/java/de/kentoj/kencommandapi/internal/parser/CommandParseHelper.java
@@ -0,0 +1,28 @@
+package de.kentoj.kencommandapi.internal.parser;
+
+import de.kentoj.kencommandapi.api.literal.Literal;
+import de.kentoj.kencommandapi.api.platform.HasPermissionMethod;
+
+import java.util.Iterator;
+
+public final class CommandParseHelper {
+ private final HasPermissionMethod hasPermission;
+
+ public CommandParseHelper(HasPermissionMethod hasPermission) {
+ this.hasPermission = hasPermission;
+ }
+
+ public boolean hasPermission(T sender, Literal literal) {
+ if (literal.permission() == null) return true;
+ return hasPermission.test(sender, literal.permission());
+ }
+
+ public String parseInput(Iterator tokenIter, boolean isGreedy) {
+ if (!isGreedy)
+ return tokenIter.next();
+ var builder = new StringBuilder();
+ tokenIter.forEachRemaining(tok -> builder.append(" ").append(tok));
+ builder.deleteCharAt(0);
+ return builder.toString();
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/de/kentoj/kencommandapi/internal/parser/InvocationCommandParser.java b/core/src/main/java/de/kentoj/kencommandapi/internal/parser/InvocationCommandParser.java
new file mode 100644
index 0000000..8809dea
--- /dev/null
+++ b/core/src/main/java/de/kentoj/kencommandapi/internal/parser/InvocationCommandParser.java
@@ -0,0 +1,108 @@
+package de.kentoj.kencommandapi.internal.parser;
+
+import com.leakyabstractions.result.api.Result;
+import com.leakyabstractions.result.core.Results;
+import de.kentoj.kencommandapi.api.argument.CommandArgument;
+import de.kentoj.kencommandapi.api.argument.CommandArgumentImpl;
+import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
+import de.kentoj.kencommandapi.api.invocation.CommandContext;
+import de.kentoj.kencommandapi.api.invocation.CommandExecutor;
+import de.kentoj.kencommandapi.api.literal.Literal;
+import de.kentoj.kencommandapi.internal.CommandContextImpl;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import static java.util.Objects.requireNonNull;
+
+public class InvocationCommandParser {
+
+ private final CommandParseHelper helper;
+
+ public InvocationCommandParser(CommandParseHelper helper) {
+ this.helper = helper;
+ }
+
+ public Result parse(
+ Literal rootLiteral,
+ T sender,
+ Iterator tokenIter
+ ) {
+ Literal currentLiteral = rootLiteral;
+ var ctx = new CommandContextImpl<>(sender, new HashMap<>());
+
+ while (true) {
+ {
+ var result = processArguments(currentLiteral, ctx, tokenIter)
+ .ifSuccess(ctx.parsedArguments()::putAll);
+ if (result.hasFailure())
+ return result.mapSuccess(_ -> null);
+ }
+
+ if (!tokenIter.hasNext()) {
+ if (currentLiteral.executor() == null)
+ return Results.failure("Literal expected");
+ break;
+ }
+
+ var literalName = tokenIter.next();
+ var nextLiteral = currentLiteral.getLiteral(literalName);
+ var isIncompleteOrUnknown = nextLiteral == null || !helper.hasPermission(sender, nextLiteral);
+ if (isIncompleteOrUnknown)
+ return Results.failure("Unknown literal: " + literalName);
+ currentLiteral = nextLiteral;
+ }
+
+ return Results.success(new ExecutionData(currentLiteral.executor(), ctx));
+ }
+
+ private Result>, String> processArguments(
+ Literal literal,
+ CommandContext ctx,
+ Iterator tokenIter
+ ) {
+ Map> parsedArguments = new HashMap<>();
+
+ for (var _argument : literal.arguments()) {
+ @SuppressWarnings("unchecked")
+ var argument = ((CommandArgumentSpec) _argument);
+
+ Object value;
+ if (!tokenIter.hasNext()) {
+ if (argument.defaultValueProvider() == null)
+ return Results.failure("Argument expected: " + argument.id());
+ value = requireNonNull(argument.defaultValueProvider()).apply(ctx);
+ } else {
+ var input = helper.parseInput(tokenIter, argument.type().greedy());
+ try {
+ value = argument.parseInput(input);
+ } catch (IllegalArgumentException ex) {
+ return Results.failure("Illegal argument '" + input + "' for " + argument.id() + ": " + ex.getMessage());
+ }
+ }
+
+ parsedArguments.put(argument.id(), new CommandArgumentImpl<>(value, argument));
+ }
+
+ return Results.success(parsedArguments);
+ }
+
+ public class ExecutionData {
+ private final CommandExecutor executor;
+ private final CommandContext context;
+
+ public ExecutionData(CommandExecutor executor, CommandContext context) {
+ this.executor = executor;
+ this.context = context;
+ }
+
+ public CommandExecutor executor() {
+ return executor;
+ }
+
+ public CommandContext context() {
+ return context;
+ }
+ }
+}
diff --git a/core/src/main/java/de/kentoj/kencommandapi/internal/parser/SuggestionCommandParser.java b/core/src/main/java/de/kentoj/kencommandapi/internal/parser/SuggestionCommandParser.java
new file mode 100644
index 0000000..088a740
--- /dev/null
+++ b/core/src/main/java/de/kentoj/kencommandapi/internal/parser/SuggestionCommandParser.java
@@ -0,0 +1,99 @@
+package de.kentoj.kencommandapi.internal.parser;
+
+import de.kentoj.kencommandapi.api.literal.Literal;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Stream;
+
+public class SuggestionCommandParser {
+
+ private final CommandParseHelper helper;
+
+ public SuggestionCommandParser(CommandParseHelper helper) {
+ this.helper = helper;
+ }
+
+ public List suggest(
+ Literal rootLiteral,
+ T sender,
+ Iterator tokenIter,
+ boolean suggestNext
+ ) {
+ Literal currentLiteral = rootLiteral;
+ Literal previousLiteral = null;
+
+ while (true) {
+ var argumentSuggestions = processArguments(currentLiteral, tokenIter, sender, suggestNext);
+ if (argumentSuggestions != null)
+ return argumentSuggestions;
+
+ if (!tokenIter.hasNext()) {
+ if (suggestNext || previousLiteral == null)
+ return suggestions(currentLiteral, sender).toList();
+ return suggestions(previousLiteral, sender).toList();
+ }
+
+ var input = tokenIter.next();
+ var nextLiteral = currentLiteral.getLiteral(input);
+ var isIncompleteOrUnknown = nextLiteral == null || !helper.hasPermission(sender, nextLiteral);
+ if (isIncompleteOrUnknown) {
+ var finalInput = input.toLowerCase();
+ return suggestions(currentLiteral, sender)
+ .filter(name -> name.toLowerCase().startsWith(finalInput))
+ .toList();
+ }
+
+ previousLiteral = currentLiteral;
+ currentLiteral = nextLiteral;
+ }
+ }
+
+ private @Nullable List processArguments(
+ Literal literal,
+ Iterator tokenIter,
+ T sender,
+ boolean suggestNext
+ ) {
+ var argsIter = literal.arguments().iterator();
+
+ /*
+ for each argument:
+ if no next token: suggest current
+ parse token
+ if not last token: skip
+ if nextToken:
+ if has next argument: suggest next argument
+ else: return null
+ return current argument's suggestions filtered
+
+ */
+
+ while (argsIter.hasNext()) {
+ var argument = argsIter.next();
+ if (!tokenIter.hasNext())
+ return argument.suggestionProvider().suggest(sender);
+ var input = helper.parseInput(tokenIter, argument.type().greedy()).toLowerCase();
+ if (tokenIter.hasNext())
+ continue;
+ if (suggestNext) {
+ if (argsIter.hasNext())
+ return argsIter.next().suggestionProvider().suggest(sender);
+ return null;
+ }
+ return argument.suggestionProvider().suggest(sender).stream()
+ .filter(suggestion -> suggestion.toLowerCase().startsWith(input))
+ .toList();
+ }
+ return null;
+ }
+
+ private Stream suggestions(Literal literal, T sender) {
+ return literal.literals().stream()
+ .filter(lit -> helper.hasPermission(sender, lit))
+ .map(Literal::names)
+ .flatMap(Arrays::stream);
+ }
+}
diff --git a/kencommandapi-core/src/test/java/de/kentoj/kencommandapi/api/parser/ENV.java b/core/src/test/java/de/kentoj/kencommandapi/api/parser/ENV.java
similarity index 100%
rename from kencommandapi-core/src/test/java/de/kentoj/kencommandapi/api/parser/ENV.java
rename to core/src/test/java/de/kentoj/kencommandapi/api/parser/ENV.java
diff --git a/core/src/test/java/de/kentoj/kencommandapi/api/parser/SuggestionCommandParserTest.java b/core/src/test/java/de/kentoj/kencommandapi/api/parser/SuggestionCommandParserTest.java
new file mode 100644
index 0000000..37c5725
--- /dev/null
+++ b/core/src/test/java/de/kentoj/kencommandapi/api/parser/SuggestionCommandParserTest.java
@@ -0,0 +1,88 @@
+package de.kentoj.kencommandapi.api.parser;
+
+import de.kentoj.kencommandapi.api.argument.CommandArgument;
+import de.kentoj.kencommandapi.api.argument.types.StringArgumentType;
+import de.kentoj.kencommandapi.api.literal.Literal;
+import de.kentoj.kencommandapi.api.literal.Literal;
+import de.kentoj.kencommandapi.api.platform.MessageStyle;
+import de.kentoj.kencommandapi.internal.parser.SuggestionCommandParser;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class SuggestionCommandParserTest {
+
+ private final SuggestionCommandParser suggestionParser;
+ private final Literal rootLiteral;
+
+ public SuggestionCommandParserTest() {
+ rootLiteral = Literal.rootLiteral(MessageStyle.PLAIN, "someRootNode");
+ suggestionParser = new SuggestionCommandParser<>((__, ___) -> true);
+ }
+
+ @Test
+ void suggestNextArgument() {
+ var arg = CommandArgument.arg("someArg", new StringArgumentType<>());
+ arg.setSuggestionProvider(__ -> List.of("foo", "bar"));
+ rootLiteral.addArgument(arg);
+
+ var suggestions = suggestionParser.getSuggestions(rootLiteral, ENV.SENDER, args(""));
+ assertEquals(Stream.of("foo", "bar").sorted().toList(), suggestions.stream().sorted().toList());
+ }
+
+ @Test
+ void suggestNextArgumentAtSpace() {
+ var uselessArg = CommandArgument.arg("someUselessArg", new StringArgumentType<>());
+ var arg = CommandArgument.arg("someArg", new StringArgumentType<>());
+ arg.setSuggestionProvider(__ -> List.of("foo", "bar"));
+ rootLiteral.addArgument(uselessArg);
+ rootLiteral.addArgument(arg);
+
+ var suggestions = suggestionParser.getSuggestions(rootLiteral, ENV.SENDER, args("hi "));
+ assertEquals(Stream.of("foo", "bar").sorted().toList(), suggestions.stream().sorted().toList());
+ }
+
+ @Test
+ void suggestCurrentArgument() {
+ var arg = CommandArgument.arg("someArg", new StringArgumentType<>());
+ arg.setSuggestionProvider(__ -> List.of("foo", "bar"));
+ rootLiteral.addArgument(arg);
+
+ var suggestions = suggestionParser.getSuggestions(rootLiteral, ENV.SENDER, args("f"));
+ assertEquals(List.of("foo"), suggestions);
+ }
+
+ @Test
+ void suggestNextLiteral() {
+ var literalFoo = Literal.literal("foo");
+ var literalBar = Literal.literal("bar");
+ rootLiteral.addLiteral(literalFoo);
+ rootLiteral.addLiteral(literalBar);
+
+ var suggestions = suggestionParser.getSuggestions(rootLiteral, ENV.SENDER, args(""));
+ assertEquals(Stream.of("foo", "bar").sorted().toList(), suggestions.stream().sorted().toList());
+ }
+
+ @Test
+ void suggestCurrentLiteral() {
+ var literalFoo = Literal.literal("foo");
+ var literalBar = Literal.literal("bar");
+ rootLiteral.addLiteral(literalFoo);
+ rootLiteral.addLiteral(literalBar);
+
+ var suggestions = suggestionParser.getSuggestions(rootLiteral, ENV.SENDER, args("f"));
+ assertEquals(List.of("foo"), suggestions);
+ }
+
+ private String[] args(String input) {
+ List args = new ArrayList<>(Arrays.asList(input.split(" ")));
+ if (input.endsWith(" "))
+ args.add("");
+ return args.toArray(new String[0]);
+ }
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 249e583..0000000
Binary files a/gradle/wrapper/gradle-wrapper.jar and /dev/null differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 19ca496..0000000
--- a/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-#Tue Apr 14 21:48:06 CEST 2026
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
deleted file mode 100755
index 1b6c787..0000000
--- a/gradlew
+++ /dev/null
@@ -1,234 +0,0 @@
-#!/bin/sh
-
-#
-# Copyright © 2015-2021 the original authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-##############################################################################
-#
-# Gradle start up script for POSIX generated by Gradle.
-#
-# Important for running:
-#
-# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
-# noncompliant, but you have some other compliant shell such as ksh or
-# bash, then to run this script, type that shell name before the whole
-# command line, like:
-#
-# ksh Gradle
-#
-# Busybox and similar reduced shells will NOT work, because this script
-# requires all of these POSIX shell features:
-# * functions;
-# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
-# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
-# * compound commands having a testable exit status, especially «case»;
-# * various built-in commands including «command», «set», and «ulimit».
-#
-# Important for patching:
-#
-# (2) This script targets any POSIX shell, so it avoids extensions provided
-# by Bash, Ksh, etc; in particular arrays are avoided.
-#
-# The "traditional" practice of packing multiple parameters into a
-# space-separated string is a well documented source of bugs and security
-# problems, so this is (mostly) avoided, by progressively accumulating
-# options in "$@", and eventually passing that to Java.
-#
-# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
-# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
-# see the in-line comments for details.
-#
-# There are tweaks for specific operating systems such as AIX, CygWin,
-# Darwin, MinGW, and NonStop.
-#
-# (3) This script is generated from the Groovy template
-# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
-# within the Gradle project.
-#
-# You can find Gradle at https://github.com/gradle/gradle/.
-#
-##############################################################################
-
-# Attempt to set APP_HOME
-
-# Resolve links: $0 may be a link
-app_path=$0
-
-# Need this for daisy-chained symlinks.
-while
- APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
- [ -h "$app_path" ]
-do
- ls=$( ls -ld "$app_path" )
- link=${ls#*' -> '}
- case $link in #(
- /*) app_path=$link ;; #(
- *) app_path=$APP_HOME$link ;;
- esac
-done
-
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
-
-APP_NAME="Gradle"
-APP_BASE_NAME=${0##*/}
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD=maximum
-
-warn () {
- echo "$*"
-} >&2
-
-die () {
- echo
- echo "$*"
- echo
- exit 1
-} >&2
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-nonstop=false
-case "$( uname )" in #(
- CYGWIN* ) cygwin=true ;; #(
- Darwin* ) darwin=true ;; #(
- MSYS* | MINGW* ) msys=true ;; #(
- NONSTOP* ) nonstop=true ;;
-esac
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD=$JAVA_HOME/jre/sh/java
- else
- JAVACMD=$JAVA_HOME/bin/java
- fi
- if [ ! -x "$JAVACMD" ] ; then
- die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
- fi
-else
- JAVACMD=java
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
- case $MAX_FD in #(
- max*)
- MAX_FD=$( ulimit -H -n ) ||
- warn "Could not query maximum file descriptor limit"
- esac
- case $MAX_FD in #(
- '' | soft) :;; #(
- *)
- ulimit -n "$MAX_FD" ||
- warn "Could not set maximum file descriptor limit to $MAX_FD"
- esac
-fi
-
-# Collect all arguments for the java command, stacking in reverse order:
-# * args from the command line
-# * the main class name
-# * -classpath
-# * -D...appname settings
-# * --module-path (only if needed)
-# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
-
-# For Cygwin or MSYS, switch paths to Windows format before running java
-if "$cygwin" || "$msys" ; then
- APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
- CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
-
- JAVACMD=$( cygpath --unix "$JAVACMD" )
-
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- for arg do
- if
- case $arg in #(
- -*) false ;; # don't mess with options #(
- /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
- [ -e "$t" ] ;; #(
- *) false ;;
- esac
- then
- arg=$( cygpath --path --ignore --mixed "$arg" )
- fi
- # Roll the args list around exactly as many times as the number of
- # args, so each arg winds up back in the position where it started, but
- # possibly modified.
- #
- # NB: a `for` loop captures its iteration list before it begins, so
- # changing the positional parameters here affects neither the number of
- # iterations, nor the values presented in `arg`.
- shift # remove old arg
- set -- "$@" "$arg" # push replacement arg
- done
-fi
-
-# Collect all arguments for the java command;
-# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
-# shell script including quotes and variable substitutions, so put them in
-# double quotes to make sure that they get re-expanded; and
-# * put everything else in single quotes, so that it's not re-expanded.
-
-set -- \
- "-Dorg.gradle.appname=$APP_BASE_NAME" \
- -classpath "$CLASSPATH" \
- org.gradle.wrapper.GradleWrapperMain \
- "$@"
-
-# Use "xargs" to parse quoted args.
-#
-# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
-#
-# In Bash we could simply go:
-#
-# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
-# set -- "${ARGS[@]}" "$@"
-#
-# but POSIX shell has neither arrays nor command substitution, so instead we
-# post-process each arg (as a line of input to sed) to backslash-escape any
-# character that might be a shell metacharacter, then use eval to reverse
-# that process (while maintaining the separation between arguments), and wrap
-# the whole thing up as a single "set" statement.
-#
-# This will of course break if any of these variables contains a newline or
-# an unmatched quote.
-#
-
-eval "set -- $(
- printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
- xargs -n1 |
- sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
- tr '\n' ' '
- )" '"$@"'
-
-exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
deleted file mode 100644
index ac1b06f..0000000
--- a/gradlew.bat
+++ /dev/null
@@ -1,89 +0,0 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
diff --git a/kencommandapi-bukkit/build.gradle.kts b/kencommandapi-bukkit/build.gradle.kts
deleted file mode 100644
index 8a44f16..0000000
--- a/kencommandapi-bukkit/build.gradle.kts
+++ /dev/null
@@ -1,28 +0,0 @@
-plugins {
- id("java")
- id("scrow.scrow-publish")
- id("scrow.base-dependencies")
- id("scrow.java-library")
-}
-
-repositories {
- maven("https://repo.papermc.io/repository/maven-public/") {
- name = "papermc"
- }
-}
-
-dependencies {
- compileOnly(project(":kencommandapi-core"))
- compileOnly("io.papermc.paper:paper-api:1.21.11-rc3-R0.1-SNAPSHOT")
-}
-
-publishing {
- publications {
- create("KenCommandAPI") {
- groupId = rootProject.group.toString()
- version = rootProject.version.toString()
- artifactId = project.name
- from(components["java"])
- }
- }
-}
diff --git a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/BukkitKenCommand.java b/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/BukkitKenCommand.java
deleted file mode 100644
index 25dbe36..0000000
--- a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/BukkitKenCommand.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package de.kentoj.kencommandapi;
-
-import de.kentoj.kencommandapi.api.CommandHandler;
-import de.kentoj.kencommandapi.api.literal.RootLiteral;
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandSender;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Arrays;
-
-public class BukkitKenCommand extends Command {
-
- private final RootLiteral rootNode;
- private final CommandHandler commandHandler;
-
- protected BukkitKenCommand(
- CommandHandler commandHandler,
- RootLiteral rootLiteral
- ) {
- super(rootLiteral.getNames()[0]);
- this.commandHandler = commandHandler;
- this.rootNode = rootLiteral;
- this.setAliases(Arrays.asList(rootLiteral.getNames()));
- this.setPermission(rootLiteral.getPermission());
- }
-
- @Override
- public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
- commandHandler.invoke(rootNode, sender, args);
- return true;
- }
-}
diff --git a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/TabCompleteListener.java b/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/TabCompleteListener.java
deleted file mode 100644
index 70c0479..0000000
--- a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/TabCompleteListener.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package de.kentoj.kencommandapi;
-
-import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent;
-import de.kentoj.kencommandapi.api.CommandHandler;
-import lombok.RequiredArgsConstructor;
-import org.bukkit.command.CommandSender;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.Listener;
-
-import java.util.Arrays;
-
-@RequiredArgsConstructor
-public class TabCompleteListener implements Listener {
-
- private final CommandAPI commandAPI;
- private final CommandHandler commandHandler;
-
- @EventHandler(ignoreCancelled = true)
- public void onAsyncTabComplete(AsyncTabCompleteEvent ev) {
- if (!ev.isCommand()) return;
-
- var args = ev.getBuffer().substring(1).split(" ");
- var label = args[0];
- var rootNode = commandAPI.getRegistered(label);
- if (rootNode == null) return;
- ev.setHandled(true);
- ev.setCompletions(commandHandler.getSuggestions(rootNode, ev.getSender(), Arrays.copyOfRange(args, 1, args.length)));
- }
-}
diff --git a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/type/OfflinePlayerArgumentType.java b/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/type/OfflinePlayerArgumentType.java
deleted file mode 100644
index 1b74400..0000000
--- a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/type/OfflinePlayerArgumentType.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package de.kentoj.kencommandapi.type;
-
-import de.kentoj.kencommandapi.api.argument.ArgumentType;
-import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
-import de.kentoj.kencommandapi.suggestionprovider.PlayerSuggestionProvider;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import org.bukkit.Bukkit;
-import org.bukkit.OfflinePlayer;
-import org.bukkit.command.CommandSender;
-
-import java.util.UUID;
-
-@NoArgsConstructor(access = AccessLevel.PRIVATE)
-public class OfflinePlayerArgumentType implements ArgumentType {
-
- @Getter(lazy = true)
- private static final OfflinePlayerArgumentType instance = new OfflinePlayerArgumentType();
-
- // TODO make work with player names on offline players
- @Override
- public OfflinePlayer parseInput(String string) {
- OfflinePlayer player = Bukkit.getPlayerExact(string);
- if (player == null) {
- try {
- player = Bukkit.getOfflinePlayer(UUID.fromString(string));
- } catch (IllegalArgumentException ex) {
- throw new IllegalArgumentException("not an UUID or online player");
- }
- }
- if (!player.hasPlayedBefore() && !player.isOnline()) throw new IllegalArgumentException("Player not found");
- return player;
- }
-
- @Override
- public SuggestionProvider defaultSuggestionProvider() {
- return new PlayerSuggestionProvider();
- }
-}
diff --git a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/type/PlayerArgumentType.java b/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/type/PlayerArgumentType.java
deleted file mode 100644
index fe75d27..0000000
--- a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/type/PlayerArgumentType.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package de.kentoj.kencommandapi.type;
-
-import de.kentoj.kencommandapi.api.argument.ArgumentType;
-import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
-import de.kentoj.kencommandapi.suggestionprovider.PlayerSuggestionProvider;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import org.bukkit.Bukkit;
-import org.bukkit.command.CommandSender;
-import org.bukkit.entity.Player;
-
-@NoArgsConstructor(access = AccessLevel.PRIVATE)
-public class PlayerArgumentType implements ArgumentType {
-
- @Getter(lazy = true)
- private static final PlayerArgumentType instance = new PlayerArgumentType();
-
- @Override
- public Player parseInput(String string) {
- var player = Bukkit.getPlayer(string);
- if (player == null) throw new IllegalArgumentException("player not online");
- return player;
- }
-
- @Override
- public SuggestionProvider defaultSuggestionProvider() {
- return new PlayerSuggestionProvider();
- }
-}
diff --git a/kencommandapi-core/build.gradle.kts b/kencommandapi-core/build.gradle.kts
deleted file mode 100644
index b88b7c4..0000000
--- a/kencommandapi-core/build.gradle.kts
+++ /dev/null
@@ -1,40 +0,0 @@
-plugins {
- id("java")
- id("scrow.scrow-publish")
- id("scrow.base-dependencies")
- id("scrow.java-library")
-}
-
-repositories {
- mavenCentral()
-}
-
-dependencies {
- // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api
- testImplementation("org.junit.jupiter:junit-jupiter-api:6.1.1")
- // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine
- testImplementation("org.junit.jupiter:junit-jupiter-engine:6.1.1")
- // https://mvnrepository.com/artifact/org.junit.platform/junit-platform-launcher
- testImplementation("org.junit.platform:junit-platform-launcher:6.1.1")
- testImplementation("net.kyori:adventure-api:5.2.0")
-
- compileOnly("net.kyori:adventure-api:5.2.0")
-}
-
-tasks.test {
- useJUnitPlatform()
- testLogging {
- showStandardStreams = true
- }
-}
-
-publishing {
- publications {
- create("KenCommandAPI") {
- groupId = rootProject.group.toString()
- version = rootProject.version.toString()
- artifactId = project.name
- from(components["java"])
- }
- }
-}
diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/CommandHandler.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/CommandHandler.java
deleted file mode 100644
index 9738558..0000000
--- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/CommandHandler.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package de.kentoj.kencommandapi.api;
-
-import de.kentoj.kencommandapi.api.invocation.CommandContext;
-import de.kentoj.kencommandapi.api.literal.RootLiteral;
-import de.kentoj.kencommandapi.api.parser2.InvocationCommandParser;
-import de.kentoj.kencommandapi.api.parser2.SuggestionCommandParser;
-import de.kentoj.kencommandapi.api.platform.HasPermissionMethod;
-import de.kentoj.kencommandapi.api.platform.SendMessageMethod;
-import lombok.RequiredArgsConstructor;
-
-import java.util.List;
-
-@RequiredArgsConstructor
-public class CommandHandler {
-
- private final SendMessageMethod sendMessageMethod;
-
- private final InvocationCommandParser invocationParser;
- private final SuggestionCommandParser suggestionParser;
-
- public CommandHandler(SendMessageMethod sendMessageMethod, HasPermissionMethod hasPermissionMethod) {
- this.sendMessageMethod = sendMessageMethod;
- invocationParser = new InvocationCommandParser<>(hasPermissionMethod);
- suggestionParser = new SuggestionCommandParser<>(hasPermissionMethod);
- }
-
- @SuppressWarnings("CodeBlock2Expr")
- public void invoke(RootLiteral rootLiteral, T sender, String[] args) {
- invocationParser.invoke(rootLiteral, sender, args)
- .ifSuccessOrElse(data -> {
- var ctx = new CommandContext<>(sender, data.getArguments());
- data.getExecutor().execute(ctx).whenComplete((result, ex) -> {
- if (ex != null)
- sendMessageMethod.send(sender, rootLiteral.getMessageStyle().exception(ex));
- result.ifFailure(err ->
- sendMessageMethod.send(sender, rootLiteral.getMessageStyle().err(err)));
- });
- }, err -> {
- sendMessageMethod.send(sender, rootLiteral.getMessageStyle().err(err));
- });
- }
-
- public List getSuggestions(RootLiteral rootNode, T sender, String[] args) {
- return suggestionParser.getSuggestions(rootNode, sender, args);
- }
-}
\ No newline at end of file
diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/CommandArgument.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/CommandArgument.java
deleted file mode 100644
index 45967db..0000000
--- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/CommandArgument.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package de.kentoj.kencommandapi.api.argument;
-
-import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
-import de.kentoj.kencommandapi.internal.CommandArgumentImpl;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.function.Function;
-
-public interface CommandArgument {
-
- static CommandArgument arg(String id, ArgumentType type) {
- return new CommandArgumentImpl<>(id, type);
- }
-
- String getId();
-
- ArgumentType getType();
-
- void setDefaultValueProvider(@Nullable Function defaultValueProvider);
-
- @Nullable Function getDefaultValueProvider();
-
- void setSuggestionProvider(@Nullable SuggestionProvider suggestionProvider);
-
- /**
- * @return the explicitly set SuggestionProvider or the default suggestions for the type if unset/null
- */
- @NotNull SuggestionProvider getSuggestionProvider();
-}
diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/types/StringArgumentType.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/types/StringArgumentType.java
deleted file mode 100644
index 5ea49d5..0000000
--- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/types/StringArgumentType.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package de.kentoj.kencommandapi.api.argument.types;
-
-import de.kentoj.kencommandapi.api.argument.ArgumentType;
-import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
-import de.kentoj.kencommandapi.api.suggestion.NoSuggestionProvider;
-import lombok.RequiredArgsConstructor;
-
-@RequiredArgsConstructor
-public class StringArgumentType implements ArgumentType {
-
- private final boolean isGreedy;
-
- public StringArgumentType() {
- this(false);
- }
-
- @Override
- public String parseInput(String string) {
- return string;
- }
-
- @Override
- public SuggestionProvider defaultSuggestionProvider() {
- return new NoSuggestionProvider<>();
- }
-
- @Override
- public boolean isGreedy() {
- return this.isGreedy;
- }
-}
diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/invocation/CommandContext.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/invocation/CommandContext.java
deleted file mode 100644
index 8d8a248..0000000
--- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/invocation/CommandContext.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package de.kentoj.kencommandapi.api.invocation;
-
-import de.kentoj.kencommandapi.api.argument.CommandArgument;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-
-import java.util.Map;
-
-@RequiredArgsConstructor
-public class CommandContext {
- @Getter
- private final T sender;
- @Getter
- private final Map> parsedArguments;
-
- public ParsedArgument getParsedArgument(String name) {
- try {
- //noinspection unchecked
- return (ParsedArgument) parsedArguments.get(name);
- } catch (ClassCastException e) {
- throw new RuntimeException("requested argument with wrong type", e);
- }
- }
-
- @SuppressWarnings("unchecked")
- public V getArg(String name) {
- return (V) getParsedArgument(name).getValue();
- }
-
- public V getArg(CommandArgument arg) {
- return getArg(arg.getId());
- }
-
- public ParsedArgument getParsedArgument(CommandArgument