diff --git a/build.gradle.kts b/build.gradle.kts index 61d9783..ebe09d8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,62 +1,8 @@ -val scrowToken = providers.gradleProperty("scrowMavenToken") - .orElse(providers.environmentVariable("SCROW_MAVEN_TOKEN")).get() - -plugins { - id("java") - id("io.freefair.lombok") version "9.2.0" apply false -} - group = "de.kentoj.scrow" -version = "0.9" - -fun RepositoryHandler.scrowRepo() = maven { - name = "scrow" - url = uri("http://forge.kentoj.de/api/packages/scrow/maven") - isAllowInsecureProtocol = true - credentials(HttpHeaderCredentials::class) { - name = "Authorization" - value = "token $scrowToken" - } - authentication { - create("header") - } -} +version = "0.10" subprojects { - apply(plugin = "java") - apply(plugin = "maven-publish") - apply(plugin = "io.freefair.lombok") + group = rootProject.group + version = rootProject.version - java { - withSourcesJar() - withJavadocJar() - } - - tasks.withType().configureEach { - (options as StandardJavadocDocletOptions).addStringOption("Xdoclint:all,-missing", "-quiet") - } - - repositories { - mavenCentral() - scrowRepo() - } - - dependencies { - // https://mvnrepository.com/artifact/org.jetbrains/annotations - implementation("org.jetbrains:annotations:26.0.2-1") - } - - extensions.configure { - publications { - create("KenCommandAPI") { - groupId = rootProject.group.toString() - version = rootProject.version.toString() - artifactId = project.name - from(components["java"]) - } - } - repositories { - scrowRepo() - } - } } diff --git a/kencommandapi-bukkit/build.gradle.kts b/kencommandapi-bukkit/build.gradle.kts index 5b6817a..f0113cd 100644 --- a/kencommandapi-bukkit/build.gradle.kts +++ b/kencommandapi-bukkit/build.gradle.kts @@ -1,11 +1,11 @@ plugins { id("java") - id("java-library") + id("maven-publish") + id("de.kentoj.scrow.scrow-repository") + id("de.kentoj.scrow.base-dependencies") + id("de.kentoj.scrow.java-library") } -group = "de.kentoj" -version = rootProject.version - repositories { maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") { name = "spigotmc-repo" @@ -16,3 +16,14 @@ dependencies { api(project(":kencommandapi-core")) compileOnly("org.spigotmc:spigot-api:1.21.11-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/BukkitCommandContext.java b/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/BukkitCommandContext.java deleted file mode 100644 index 2dd50c0..0000000 --- a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/BukkitCommandContext.java +++ /dev/null @@ -1,22 +0,0 @@ -package de.kentoj.kencommandapi; - -import de.kentoj.kencommandapi.api.processing.ParsedArgument; -import de.kentoj.kencommandapi.impl.processing.CommandContextImpl; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -import java.util.Map; - -public class BukkitCommandContext extends CommandContextImpl { - - BukkitCommandContext( - CommandSender sender, - Map> parsedArguments - ) { - super(sender, parsedArguments); - } - - public Player getPlayerSender() { - return ((Player) getSender()); - } -} diff --git a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/BukkitKenCommand.java b/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/BukkitKenCommand.java index 833fe89..38a9afd 100644 --- a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/BukkitKenCommand.java +++ b/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/BukkitKenCommand.java @@ -1,7 +1,9 @@ package de.kentoj.kencommandapi; -import de.kentoj.kencommandapi.api.KenCommandApi; -import de.kentoj.kencommandapi.api.nodes.CommandNode; +import de.kentoj.kencommandapi.api.CommandNode; +import de.kentoj.kencommandapi.api.invocation.CommandContext; +import de.kentoj.kencommandapi.internal.parser.CommandParser; +import de.kentoj.kencommandapi.internal.parser.ParseResult; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; @@ -10,23 +12,37 @@ import java.util.Arrays; public class BukkitKenCommand extends Command { - private final CommandNode rootNode; - private final KenCommandApi commandManager; + private final CommandNode rootNode; + private final CommandParser commandParser; protected BukkitKenCommand( - KenCommandApi commandManager, - CommandNode rootNode + CommandParser commandParser, + CommandNode rootNode ) { super(rootNode.getNames()[0]); + this.commandParser = commandParser; this.rootNode = rootNode; - this.commandManager = commandManager; this.setAliases(Arrays.asList(rootNode.getNames())); this.setPermission(rootNode.getPermission()); } @Override public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { - commandManager.invoke(rootNode, sender, args); - return false; + var parseResult = commandParser.parseLiteral(rootNode, sender, args); + if (parseResult instanceof ParseResult.Success) { + var ctx = new CommandContext<>(sender, ((ParseResult.Success) parseResult).getParsedArguments()); + assert parseResult.getLiteral().getExecutor() != null; + try { + var result = parseResult.getLiteral().getExecutor().execute(ctx); + if (result.isError()) { + sender.sendMessage("§c" + result.getError()); + } + } catch (Exception e) { + sender.sendMessage("§4§lERROR " + e.getMessage()); + } + } else { + sender.sendMessage("§c" + parseResult.getMessage()); + } + return true; } } diff --git a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/BukkitKenCommandApi.java b/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/BukkitKenCommandApi.java deleted file mode 100644 index 0eb967e..0000000 --- a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/BukkitKenCommandApi.java +++ /dev/null @@ -1,45 +0,0 @@ -package de.kentoj.kencommandapi; - -import de.kentoj.kencommandapi.api.processing.CommandException; -import de.kentoj.kencommandapi.api.processing.ParsedArgument; -import de.kentoj.kencommandapi.api.nodes.CommandNode; -import de.kentoj.kencommandapi.impl.AbstractKenCommandApi; -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; -import org.bukkit.command.SimpleCommandMap; - -import java.lang.reflect.Field; -import java.util.Map; - -public class BukkitKenCommandApi extends AbstractKenCommandApi { - - @Override - public BukkitCommandContext createContext( - CommandSender sender, - Map> parsedArguments - ) { - return new BukkitCommandContext(sender, parsedArguments); - } - - @Override - public void sendErrorMessage(CommandSender sender, CommandException ex) { - var cause = (ex.getCause() != null) ? ": " + ex.getCause().getMessage() : ""; - sender.sendMessage("§c" + ex.getMessage() + cause); - } - - @Override - public boolean hasPermission(CommandSender sender, String permission) { - return sender.hasPermission(permission); - } - - public void register(CommandNode rootNode) { - try { - Field field = Bukkit.getPluginManager().getClass().getDeclaredField("commandMap"); - field.setAccessible(true); - SimpleCommandMap commandMap = (SimpleCommandMap) field.get(Bukkit.getPluginManager()); - commandMap.register(rootNode.getNames()[0], new BukkitKenCommand(this, rootNode)); - } catch (NoSuchFieldException | IllegalAccessException e) { - e.printStackTrace(); - } - } -} diff --git a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/CommandAPI.java b/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/CommandAPI.java new file mode 100644 index 0000000..8e57675 --- /dev/null +++ b/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/CommandAPI.java @@ -0,0 +1,27 @@ +package de.kentoj.kencommandapi; + +import de.kentoj.kencommandapi.api.CommandNode; +import de.kentoj.kencommandapi.internal.parser.CommandParser; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.command.SimpleCommandMap; +import org.bukkit.permissions.Permissible; + +import java.lang.reflect.Field; + +public class CommandAPI { + + private final CommandParser commandParser = new CommandParser<>(Permissible::hasPermission); + + public void register(CommandNode rootNode) { + SimpleCommandMap commandMap; + try { + Field field = Bukkit.getPluginManager().getClass().getDeclaredField("commandMap"); + field.setAccessible(true); + commandMap = (SimpleCommandMap) field.get(Bukkit.getPluginManager()); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new IllegalStateException("failed registering command ", e); + } + commandMap.register(rootNode.getNames()[0], new BukkitKenCommand(commandParser, rootNode)); + } +} diff --git a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/suggestionprovider/PlayerSuggestionProvider.java b/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/suggestionprovider/PlayerSuggestionProvider.java index 5940b63..b70f287 100644 --- a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/suggestionprovider/PlayerSuggestionProvider.java +++ b/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/suggestionprovider/PlayerSuggestionProvider.java @@ -1,7 +1,7 @@ package de.kentoj.kencommandapi.suggestionprovider; -import de.kentoj.kencommandapi.BukkitCommandContext; -import de.kentoj.kencommandapi.api.argument.SuggestionProvider; +import de.kentoj.kencommandapi.api.invocation.CommandContext; +import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -10,9 +10,9 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; -public class PlayerSuggestionProvider implements SuggestionProvider { +public class PlayerSuggestionProvider implements SuggestionProvider { @Override - public CompletableFuture> suggest(BukkitCommandContext context) { + public CompletableFuture> suggest(CommandContext context) { var list = Bukkit.getOnlinePlayers().stream() .map(Player::getName) .collect(Collectors.toSet()); 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 index 4211b6f..1e2b423 100644 --- a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/type/OfflinePlayerArgumentType.java +++ b/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/type/OfflinePlayerArgumentType.java @@ -1,10 +1,7 @@ package de.kentoj.kencommandapi.type; -import de.kentoj.kencommandapi.BukkitCommandContext; -import de.kentoj.kencommandapi.api.processing.CommandException; import de.kentoj.kencommandapi.api.argument.ArgumentType; -import de.kentoj.kencommandapi.api.argument.SuggestionProvider; -import de.kentoj.kencommandapi.api.argument.suggestion.NoSuggestionProvider; +import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider; import de.kentoj.kencommandapi.suggestionprovider.PlayerSuggestionProvider; import lombok.AccessLevel; import lombok.Getter; @@ -16,7 +13,7 @@ import org.bukkit.command.CommandSender; import java.util.UUID; @NoArgsConstructor(access = AccessLevel.PRIVATE) -public class OfflinePlayerArgumentType implements ArgumentType { +public class OfflinePlayerArgumentType implements ArgumentType { @Getter(lazy = true) private static final OfflinePlayerArgumentType instance = new OfflinePlayerArgumentType(); @@ -29,15 +26,15 @@ public class OfflinePlayerArgumentType implements ArgumentType defaultSuggestionProvider() { + 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 index 6d7ebc1..dad8474 100644 --- a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/type/PlayerArgumentType.java +++ b/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/type/PlayerArgumentType.java @@ -1,9 +1,7 @@ package de.kentoj.kencommandapi.type; -import de.kentoj.kencommandapi.BukkitCommandContext; import de.kentoj.kencommandapi.api.argument.ArgumentType; -import de.kentoj.kencommandapi.api.argument.SuggestionProvider; -import de.kentoj.kencommandapi.api.processing.CommandException; +import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider; import de.kentoj.kencommandapi.suggestionprovider.PlayerSuggestionProvider; import lombok.AccessLevel; import lombok.Getter; @@ -13,7 +11,7 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @NoArgsConstructor(access = AccessLevel.PRIVATE) -public class PlayerArgumentType implements ArgumentType { +public class PlayerArgumentType implements ArgumentType { @Getter(lazy = true) private static final PlayerArgumentType instance = new PlayerArgumentType(); @@ -21,12 +19,12 @@ public class PlayerArgumentType implements ArgumentType defaultSuggestionProvider() { + public SuggestionProvider defaultSuggestionProvider() { return new PlayerSuggestionProvider(); } } diff --git a/kencommandapi-core/build.gradle.kts b/kencommandapi-core/build.gradle.kts new file mode 100644 index 0000000..0eddbf2 --- /dev/null +++ b/kencommandapi-core/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + id("java") + id("maven-publish") + id("de.kentoj.scrow.scrow-repository") + id("de.kentoj.scrow.base-dependencies") + id("de.kentoj.scrow.java-library") +} + +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/Main.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/Main.java deleted file mode 100644 index ba12bb8..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/Main.java +++ /dev/null @@ -1,57 +0,0 @@ -package de.kentoj.kencommandapi; - -import de.kentoj.kencommandapi.api.processing.CommandException; -import de.kentoj.kencommandapi.api.processing.ParsedArgument; -import de.kentoj.kencommandapi.api.types.IntegerArgumentType; -import de.kentoj.kencommandapi.impl.AbstractKenCommandApi; -import de.kentoj.kencommandapi.impl.processing.CommandContextImpl; - -import java.io.PrintStream; -import java.util.Map; - -public class Main { - - private static class BasicCommandContext extends CommandContextImpl { - public BasicCommandContext(PrintStream sender, Map> parsedArguments) { - super(sender, parsedArguments); - } - - public void respond(String msg) { - getSender().println("|==================|"); - getSender().println(msg); - getSender().println("|==================|"); - } - } - - public static void main(String[] args) { - var api = new AbstractKenCommandApi() { - @Override - public BasicCommandContext createContext(PrintStream sender, Map> parsedArguments) { - return new BasicCommandContext(sender, parsedArguments); - } - - @Override - public void sendErrorMessage(PrintStream printStream, CommandException ex) { - System.err.println(ex.getMessage()); - } - - @Override - public boolean hasPermission(PrintStream printStream, String permission) { - return true; - } - }; - - var root = api.createNode("coins"); - - var addNode = api.createNode("add", "deposit"); - addNode.setExecutor(ctx -> { - int amount = ctx.getArg("amount"); - - ctx.respond("adding " + amount + "$ to your balance"); - }); - - addNode.addArgument(api.createArgument("amount", new IntegerArgumentType<>())); - root.addLiteral(addNode); - api.invoke(root, System.out, new String[]{"add", "100"}); - } -} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/CommandNode.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/CommandNode.java new file mode 100644 index 0000000..322819e --- /dev/null +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/CommandNode.java @@ -0,0 +1,56 @@ +package de.kentoj.kencommandapi.api; + +import de.kentoj.kencommandapi.api.argument.CommandArgument; +import de.kentoj.kencommandapi.api.invocation.CommandExecutor; +import de.kentoj.kencommandapi.internal.CommandNodeImpl; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Set; + +public interface CommandNode { + static CommandNode node(String... name) { + return new CommandNodeImpl<>(name); + } + + @NotNull String[] getNames(); + + void setDescription(@Nullable String description); + + @Nullable String getDescription(); + + /* + * arguments + */ + + /** + * @throws IllegalArgumentException if there is already an argument with the same id + * @throws IllegalStateException if a greedy argument was added to the node previously + */ + void addArgument(CommandArgument argument); + + List> getArguments(); + + /* + * literals + */ + + void setExecutor(@Nullable CommandExecutor executor); + + @Nullable CommandExecutor getExecutor(); + + /** + * @throws IllegalArgumentException if there is already a literal with the same name + * @throws IllegalStateException if a greedy argument was added to the node previously + */ + void addLiteral(@NotNull CommandNode commandNode); + + @Nullable CommandNode getLiteral(@NotNull String name); + + @NotNull Set> getLiterals(); + + @Nullable String getPermission(); + + void setPermission(@Nullable String permission); +} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/KenCommandApi.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/KenCommandApi.java deleted file mode 100644 index 3424a45..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/KenCommandApi.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.kentoj.kencommandapi.api; - -import de.kentoj.kencommandapi.api.processing.CommandContext; -import de.kentoj.kencommandapi.api.argument.ArgumentType; -import de.kentoj.kencommandapi.api.argument.CommandArgument; -import de.kentoj.kencommandapi.api.nodes.CommandNode; - -public interface KenCommandApi> { - - CommandNode createNode(String... name); - - CommandArgument createArgument(String id, ArgumentType type); - - void invoke(CommandNode node, T sender, String[] args); -} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/ArgumentType.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/ArgumentType.java index 58b7c7b..00cda59 100644 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/ArgumentType.java +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/ArgumentType.java @@ -1,13 +1,21 @@ package de.kentoj.kencommandapi.api.argument; -import de.kentoj.kencommandapi.api.processing.CommandContext; +import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider; -public interface ArgumentType, V> { +public interface ArgumentType { + /** + * @throws IllegalArgumentException if string invalid + */ V parseInput(String string); - SuggestionProvider defaultSuggestionProvider(); + SuggestionProvider defaultSuggestionProvider(); + /** + * @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() { return false; } 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 index 9087da5..d227b7c 100644 --- 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 @@ -1,23 +1,30 @@ package de.kentoj.kencommandapi.api.argument; -import de.kentoj.kencommandapi.api.processing.CommandContext; +import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider; +import de.kentoj.kencommandapi.internal.CommandArgumentImpl; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public interface CommandArgument, V> { +import java.util.function.Function; + +public interface CommandArgument { + + static CommandArgument arg(String id, ArgumentType type) { + return new CommandArgumentImpl<>(id, type); + } String getId(); - ArgumentType getType(); + ArgumentType getType(); - void setDefaultValue(@Nullable V defaultValue); + void setDefaultValueProvider(@Nullable Function getDefaultValueProvider); - @Nullable V getDefaultValue(); + @Nullable Function getDefaultValueProvider(); - void setSuggestionProvider(@Nullable SuggestionProvider suggestionProvider); + void setSuggestionProvider(@Nullable SuggestionProvider suggestionProvider); /** * @return the explicitly set SuggestionProvider or the default suggestions for the type if unset/null */ - @NotNull SuggestionProvider getSuggestionProvider(); + @NotNull SuggestionProvider getSuggestionProvider(); } diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/SuggestionProvider.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/SuggestionProvider.java deleted file mode 100644 index 7955f9a..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/SuggestionProvider.java +++ /dev/null @@ -1,11 +0,0 @@ -package de.kentoj.kencommandapi.api.argument; - -import de.kentoj.kencommandapi.api.processing.CommandContext; - -import java.util.Set; -import java.util.concurrent.CompletableFuture; - -public interface SuggestionProvider> { - - CompletableFuture> suggest(S context); -} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/suggestion/NoSuggestionProvider.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/suggestion/NoSuggestionProvider.java deleted file mode 100644 index 082b801..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/suggestion/NoSuggestionProvider.java +++ /dev/null @@ -1,16 +0,0 @@ -package de.kentoj.kencommandapi.api.argument.suggestion; - -import de.kentoj.kencommandapi.api.processing.CommandContext; -import de.kentoj.kencommandapi.api.argument.SuggestionProvider; - -import java.util.Collections; -import java.util.Set; -import java.util.concurrent.CompletableFuture; - -public class NoSuggestionProvider> implements SuggestionProvider { - - @Override - public CompletableFuture> suggest(S ctx) { - return CompletableFuture.completedFuture(Collections.emptySet()); - } -} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/types/IntegerArgumentType.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/types/IntegerArgumentType.java new file mode 100644 index 0000000..80d6ee8 --- /dev/null +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/types/IntegerArgumentType.java @@ -0,0 +1,18 @@ +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) { + return Integer.parseInt(string); + } + + @Override + public SuggestionProvider defaultSuggestionProvider() { + return new NoSuggestionProvider<>(); + } +} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/types/StringArgumentType.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/types/StringArgumentType.java similarity index 50% rename from kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/types/StringArgumentType.java rename to kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/types/StringArgumentType.java index e0887f2..5ea49d5 100644 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/types/StringArgumentType.java +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/argument/types/StringArgumentType.java @@ -1,14 +1,12 @@ -package de.kentoj.kencommandapi.api.types; +package de.kentoj.kencommandapi.api.argument.types; import de.kentoj.kencommandapi.api.argument.ArgumentType; -import de.kentoj.kencommandapi.api.argument.SuggestionProvider; -import de.kentoj.kencommandapi.api.argument.suggestion.NoSuggestionProvider; -import de.kentoj.kencommandapi.api.processing.CommandContext; -import lombok.AllArgsConstructor; +import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider; +import de.kentoj.kencommandapi.api.suggestion.NoSuggestionProvider; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor -public class StringArgumentType> implements ArgumentType { +public class StringArgumentType implements ArgumentType { private final boolean isGreedy; @@ -22,7 +20,7 @@ public class StringArgumentType> implements Ar } @Override - public SuggestionProvider defaultSuggestionProvider() { + public SuggestionProvider defaultSuggestionProvider() { return new NoSuggestionProvider<>(); } 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 new file mode 100644 index 0000000..8d8a248 --- /dev/null +++ b/kencommandapi-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 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 arg) { + return getParsedArgument(arg.getId()); + } +} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/invocation/CommandExecutor.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/invocation/CommandExecutor.java new file mode 100644 index 0000000..f377756 --- /dev/null +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/invocation/CommandExecutor.java @@ -0,0 +1,6 @@ +package de.kentoj.kencommandapi.api.invocation; + +public interface CommandExecutor { + + Result execute(CommandContext ctx); +} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/invocation/ParsedArgument.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/invocation/ParsedArgument.java new file mode 100644 index 0000000..6412823 --- /dev/null +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/invocation/ParsedArgument.java @@ -0,0 +1,10 @@ +package de.kentoj.kencommandapi.api.invocation; + +import de.kentoj.kencommandapi.api.argument.CommandArgument; + +public interface ParsedArgument { + + V getValue(); + + CommandArgument getArgument(); +} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/invocation/Result.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/invocation/Result.java new file mode 100644 index 0000000..774a286 --- /dev/null +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/invocation/Result.java @@ -0,0 +1,24 @@ +package de.kentoj.kencommandapi.api.invocation; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.Nullable; + +@RequiredArgsConstructor +@Getter +public class Result { + + public static Result success() { + return new Result(null); + } + + public static Result error(String error) { + return new Result(error); + } + + private final @Nullable String error; + + public boolean isError() { + return error != null; + } +} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/nodes/ArgumentNode.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/nodes/ArgumentNode.java deleted file mode 100644 index f8bceea..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/nodes/ArgumentNode.java +++ /dev/null @@ -1,13 +0,0 @@ -package de.kentoj.kencommandapi.api.nodes; - -import de.kentoj.kencommandapi.api.argument.CommandArgument; -import de.kentoj.kencommandapi.api.processing.CommandContext; - -import java.util.List; - -public interface ArgumentNode> { - - void addArgument(CommandArgument argument); - - List> getArguments(); -} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/nodes/CommandNode.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/nodes/CommandNode.java deleted file mode 100644 index db00864..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/nodes/CommandNode.java +++ /dev/null @@ -1,22 +0,0 @@ -package de.kentoj.kencommandapi.api.nodes; - -import de.kentoj.kencommandapi.api.processing.CommandContext; -import de.kentoj.kencommandapi.api.processing.CommandExecutor; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * @param type of command context - */ -public interface CommandNode> extends ArgumentNode, LiteralNode { - - @NotNull String[] getNames(); - - void setExecutor(@Nullable CommandExecutor executor); - - @Nullable CommandExecutor getExecutor(); - - void setDescription(@Nullable String description); - - @Nullable String getDescription(); -} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/nodes/LiteralNode.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/nodes/LiteralNode.java deleted file mode 100644 index b5c4270..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/nodes/LiteralNode.java +++ /dev/null @@ -1,18 +0,0 @@ -package de.kentoj.kencommandapi.api.nodes; - -import de.kentoj.kencommandapi.api.processing.CommandContext; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Set; - -public interface LiteralNode> { - - void addLiteral(@NotNull CommandNode commandNode); - - @Nullable CommandNode getLiteral(@NotNull String name); - - @NotNull Set> getLiterals(); - - @Nullable String getPermission(); -} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/processing/CommandContext.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/processing/CommandContext.java deleted file mode 100644 index 6b5839a..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/processing/CommandContext.java +++ /dev/null @@ -1,27 +0,0 @@ -package de.kentoj.kencommandapi.api.processing; - -import de.kentoj.kencommandapi.api.argument.CommandArgument; - -public interface CommandContext> { - - T getSender(); - - /** - * @return Object that is parsed from the raw argument - * @throws RuntimeException if no value given and no default value supplied - */ - ParsedArgument getParsedArgument(String name); - - @SuppressWarnings("unchecked") - default V getArg(String name) { - return (V) getParsedArgument(name).getValue(); - } - - default V getArg(CommandArgument arg) { - return getArg(arg.getId()); - } - - default ParsedArgument getParsedArgument(CommandArgument arg) { - return getParsedArgument(arg.getId()); - } -} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/processing/CommandException.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/processing/CommandException.java deleted file mode 100644 index b72a2a2..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/processing/CommandException.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.kentoj.kencommandapi.api.processing; - -public class CommandException extends RuntimeException { - public CommandException(String message) { - super(message); - } - - public CommandException(String message, Throwable cause) { - super(message, cause); - } - - public CommandException(Throwable cause) { - super(cause); - } -} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/processing/CommandExecutor.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/processing/CommandExecutor.java deleted file mode 100644 index 2838127..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/processing/CommandExecutor.java +++ /dev/null @@ -1,6 +0,0 @@ -package de.kentoj.kencommandapi.api.processing; - -public interface CommandExecutor> { - - void execute(S ctx); -} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/processing/ParsedArgument.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/processing/ParsedArgument.java deleted file mode 100644 index 995d909..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/processing/ParsedArgument.java +++ /dev/null @@ -1,10 +0,0 @@ -package de.kentoj.kencommandapi.api.processing; - -import de.kentoj.kencommandapi.api.argument.CommandArgument; - -public interface ParsedArgument, V> { - - V getValue(); - - CommandArgument getArgument(); -} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/suggestion/NoSuggestionProvider.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/suggestion/NoSuggestionProvider.java new file mode 100644 index 0000000..1cff474 --- /dev/null +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/suggestion/NoSuggestionProvider.java @@ -0,0 +1,15 @@ +package de.kentoj.kencommandapi.api.suggestion; + +import de.kentoj.kencommandapi.api.invocation.CommandContext; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +public class NoSuggestionProvider implements SuggestionProvider { + + @Override + public CompletableFuture> suggest(CommandContext __) { + return CompletableFuture.completedFuture(Collections.emptySet()); + } +} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/suggestion/SuggestionProvider.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/suggestion/SuggestionProvider.java new file mode 100644 index 0000000..c0a79ef --- /dev/null +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/suggestion/SuggestionProvider.java @@ -0,0 +1,11 @@ +package de.kentoj.kencommandapi.api.suggestion; + +import de.kentoj.kencommandapi.api.invocation.CommandContext; + +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +public interface SuggestionProvider { + + CompletableFuture> suggest(CommandContext context); +} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/types/IntegerArgumentType.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/types/IntegerArgumentType.java deleted file mode 100644 index 0fe9834..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/types/IntegerArgumentType.java +++ /dev/null @@ -1,19 +0,0 @@ -package de.kentoj.kencommandapi.api.types; - -import de.kentoj.kencommandapi.api.processing.CommandContext; -import de.kentoj.kencommandapi.api.argument.ArgumentType; -import de.kentoj.kencommandapi.api.argument.SuggestionProvider; -import de.kentoj.kencommandapi.api.argument.suggestion.NoSuggestionProvider; - -public class IntegerArgumentType> implements ArgumentType { - - @Override - public Integer parseInput(String string) { - return Integer.parseInt(string); - } - - @Override - public SuggestionProvider defaultSuggestionProvider() { - return new NoSuggestionProvider<>(); - } -} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/impl/AbstractKenCommandApi.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/impl/AbstractKenCommandApi.java deleted file mode 100644 index e9fcc6b..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/impl/AbstractKenCommandApi.java +++ /dev/null @@ -1,107 +0,0 @@ -package de.kentoj.kencommandapi.impl; - -import de.kentoj.kencommandapi.api.KenCommandApi; -import de.kentoj.kencommandapi.api.argument.ArgumentType; -import de.kentoj.kencommandapi.api.argument.CommandArgument; -import de.kentoj.kencommandapi.api.nodes.CommandNode; -import de.kentoj.kencommandapi.api.processing.CommandContext; -import de.kentoj.kencommandapi.api.processing.CommandException; -import de.kentoj.kencommandapi.api.processing.ParsedArgument; -import de.kentoj.kencommandapi.impl.processing.ParsedArgumentImpl; -import de.kentoj.kencommandapi.impl.structure.CommandArgumentImpl; -import de.kentoj.kencommandapi.impl.structure.CommandNodeImpl; -import lombok.RequiredArgsConstructor; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -@RequiredArgsConstructor -public abstract class AbstractKenCommandApi> implements KenCommandApi { - - public abstract S createContext(T sender, Map> parsedArguments); - - public abstract void sendErrorMessage(T t, CommandException exception); - - public abstract boolean hasPermission(T t, String permission); - - @Override - public CommandNode createNode(String... name) { - return new CommandNodeImpl<>(name); - } - - @Override - public CommandArgument createArgument(String id, ArgumentType type) { - return new CommandArgumentImpl<>(id, type); - } - - @Override - public void invoke(CommandNode node, T sender, String[] args) { - var iter = Arrays.stream(args).iterator(); - - try { - var executableLiteral = processLiterals(sender, node, iter); - var parsedArguments = parseArguments(executableLiteral, iter); - assert executableLiteral.getExecutor() != null; - executableLiteral.getExecutor().execute(createContext(sender, parsedArguments)); - } catch (CommandException ex) { - sendErrorMessage(sender, ex); - } - } - - private CommandNode processLiterals(T sender, CommandNode commandNode, Iterator iter) { - if (!iter.hasNext()) { - // literals/arguments are optional if the node itself is executable - if (commandNode.getExecutor() == null) throw new CommandException("no literal given but required"); - return commandNode; - } - - if (commandNode.getLiterals().isEmpty()) { - return commandNode; - } - - final var arg = iter.next(); - final var literal = commandNode.getLiteral(arg); - if (literal == null) { - throw new CommandException("Invalid literal: " + arg); - } - if (literal.getPermission() != null && !hasPermission(sender, literal.getPermission())) { - throw new CommandException("No permission"); - } - return processLiterals(sender, literal, iter); - } - - private Map> parseArguments(CommandNode commandNode, Iterator iter) { - Map> parsedArguments = new HashMap<>(); - for (var argument : commandNode.getArguments()) { - @SuppressWarnings("unchecked") - var arg = ((CommandArgument) argument); - - if (!iter.hasNext()) { - var defaultValue = arg.getDefaultValue(); - if (defaultValue == null) throw new CommandException("no argument given but required"); - parsedArguments.put(arg.getId(), new ParsedArgumentImpl<>(defaultValue, arg)); - continue; - } - - var rawArgument = getRawArgument(arg, iter); - var parsedArg = arg.getType().parseInput(rawArgument); - parsedArguments.put(arg.getId(), new ParsedArgumentImpl<>(parsedArg, arg)); - } - return parsedArguments; - } - - private String getRawArgument(CommandArgument arg, Iterator iter) { - var rawArgument = ""; - if (arg.getType().isGreedy()) { - while (iter.hasNext()) { - //noinspection StringConcatenationInLoop - rawArgument += iter.next(); - } - } else { - rawArgument = iter.next(); - } - return rawArgument; - } -} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/impl/processing/CommandContextImpl.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/impl/processing/CommandContextImpl.java deleted file mode 100644 index b3fa645..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/impl/processing/CommandContextImpl.java +++ /dev/null @@ -1,26 +0,0 @@ -package de.kentoj.kencommandapi.impl.processing; - -import de.kentoj.kencommandapi.api.processing.CommandContext; -import de.kentoj.kencommandapi.api.processing.ParsedArgument; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -import java.util.Map; - -@RequiredArgsConstructor -public class CommandContextImpl> implements CommandContext { - @Getter - private final T sender; - @Getter - private final Map> parsedArguments; - - @Override - 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); - } - } -} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/impl/processing/ParsedArgumentImpl.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/impl/processing/ParsedArgumentImpl.java deleted file mode 100644 index 0d9d466..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/impl/processing/ParsedArgumentImpl.java +++ /dev/null @@ -1,12 +0,0 @@ -package de.kentoj.kencommandapi.impl.processing; - -import de.kentoj.kencommandapi.api.processing.CommandContext; -import de.kentoj.kencommandapi.api.processing.ParsedArgument; -import de.kentoj.kencommandapi.api.argument.CommandArgument; -import lombok.Value; - -@Value -public class ParsedArgumentImpl, V> implements ParsedArgument { - V value; - CommandArgument argument; -} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/impl/processing/TabCompleter.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/impl/processing/TabCompleter.java deleted file mode 100644 index 1061481..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/impl/processing/TabCompleter.java +++ /dev/null @@ -1,24 +0,0 @@ -package de.kentoj.kencommandapi.impl.processing; - -import de.kentoj.kencommandapi.api.processing.CommandContext; -import de.kentoj.kencommandapi.api.nodes.CommandNode; - -import java.util.Arrays; -import java.util.List; - -public class TabCompleter> { - - private List tabComplete(CommandNode node, String[] args) { - var lastNode = node; - for (int i = 0; i < args.length; i++) { - var tmp = node.getLiteral(args[i]); - if (tmp == null) { - break; - } - lastNode = tmp; - i++; - } - - return node.getLiterals().stream().map(CommandNode::getNames).flatMap(Arrays::stream).toList(); - } -} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/impl/structure/CommandArgumentImpl.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/impl/structure/CommandArgumentImpl.java deleted file mode 100644 index 826d966..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/impl/structure/CommandArgumentImpl.java +++ /dev/null @@ -1,27 +0,0 @@ -package de.kentoj.kencommandapi.impl.structure; - -import de.kentoj.kencommandapi.api.processing.CommandContext; -import de.kentoj.kencommandapi.api.argument.ArgumentType; -import de.kentoj.kencommandapi.api.argument.CommandArgument; -import de.kentoj.kencommandapi.api.argument.SuggestionProvider; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import org.jetbrains.annotations.Nullable; - -@RequiredArgsConstructor -public class CommandArgumentImpl, V> implements CommandArgument { - - @Getter - private final String id; - @Getter - private final ArgumentType type; - - @Getter - @Setter - private @Nullable V defaultValue; - - @Getter - @Setter - private @Nullable SuggestionProvider suggestionProvider; -} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/impl/structure/CommandNodeImpl.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/impl/structure/CommandNodeImpl.java deleted file mode 100644 index e437b9b..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/impl/structure/CommandNodeImpl.java +++ /dev/null @@ -1,71 +0,0 @@ -package de.kentoj.kencommandapi.impl.structure; - -import de.kentoj.kencommandapi.api.argument.CommandArgument; -import de.kentoj.kencommandapi.api.nodes.CommandNode; -import de.kentoj.kencommandapi.api.processing.CommandContext; -import de.kentoj.kencommandapi.api.processing.CommandExecutor; -import lombok.Getter; -import lombok.Setter; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -public class CommandNodeImpl> implements CommandNode { - - @Getter - private final String[] names; - @Getter - private final Set> literals = new HashSet<>(); - @Getter - private final List> arguments = new ArrayList<>(); - - @Getter - @Setter - private @Nullable CommandExecutor executor; - - @Getter - @Setter - private String description; - - @Getter - private @Nullable String permission = null; - - public CommandNodeImpl(String[] names) { - if (names.length == 0) throw new IllegalArgumentException("at least one name is required"); - this.names = names; - } - - @Override - public void addLiteral(@NotNull CommandNode commandNode) { - for (String name : commandNode.getNames()) { - if (getLiteral(name) != null) throw new IllegalArgumentException("literal with same name is already added"); - } - literals.add(commandNode); - } - - @Override - public CommandNode getLiteral(@NotNull String name) { - for (var literal : literals) { - for (String literalName : literal.getNames()) { - if (literalName.equalsIgnoreCase(name)) return literal; - } - } - return null; - } - - @Override - public void addArgument(CommandArgument argument) { - if (arguments.stream() - .anyMatch(arg -> arg.getId().equals(argument.getId())) - ) throw new IllegalArgumentException("argument with same identifier is already added"); - if (!arguments.isEmpty() && arguments.getLast().getType().isGreedy()) - throw new IllegalStateException("can't append argument to a node, whose last argument type is greedy: " - + arguments.getLast().getType().getClass().getCanonicalName()); - - arguments.add(argument); - } -} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/CommandArgumentImpl.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/CommandArgumentImpl.java new file mode 100644 index 0000000..caa0f91 --- /dev/null +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/CommandArgumentImpl.java @@ -0,0 +1,28 @@ +package de.kentoj.kencommandapi.internal; + +import de.kentoj.kencommandapi.api.argument.ArgumentType; +import de.kentoj.kencommandapi.api.argument.CommandArgument; +import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Function; + +@RequiredArgsConstructor +public class CommandArgumentImpl implements CommandArgument { + + @Getter + private final String id; + @Getter + private final ArgumentType type; + + @Getter + @Setter + private @Nullable Function defaultValueProvider; + + @Getter + @Setter + private @Nullable SuggestionProvider suggestionProvider; +} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/CommandNodeImpl.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/CommandNodeImpl.java new file mode 100644 index 0000000..e03e2c7 --- /dev/null +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/CommandNodeImpl.java @@ -0,0 +1,71 @@ +package de.kentoj.kencommandapi.internal; + +import de.kentoj.kencommandapi.api.CommandNode; +import de.kentoj.kencommandapi.api.argument.CommandArgument; +import de.kentoj.kencommandapi.api.invocation.CommandExecutor; +import lombok.Getter; +import lombok.Setter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class CommandNodeImpl implements CommandNode { + + @Getter + private final String[] names; + @Getter + private final Set> literals = new HashSet<>(); + @Getter + private final List> arguments = new ArrayList<>(); + + @Getter + @Setter + private @Nullable CommandExecutor executor; + + @Getter + @Setter + private String description; + + @Getter + @Setter + private @Nullable String permission = null; + + public CommandNodeImpl(String[] names) { + if (names.length == 0) throw new IllegalArgumentException("at least one name is required"); + this.names = names; + } + + @Override + public void addLiteral(@NotNull CommandNode commandNode) { + if (isGreedy()) throw new IllegalStateException("can't add literal to a node with a greedy argument"); + for (String name : commandNode.getNames()) + if (getLiteral(name) != null) + throw new IllegalArgumentException("literal with same name is already exists"); + literals.add(commandNode); + } + + @Override + public CommandNode getLiteral(@NotNull String name) { + for (var literal : literals) + for (String literalName : literal.getNames()) + if (literalName.equalsIgnoreCase(name)) return literal; + return null; + } + + @Override + public void addArgument(CommandArgument argument) { + if (isGreedy()) throw new IllegalStateException("can't add literal to a node with a greedy argument"); + if (arguments.stream().anyMatch(arg -> arg.getId().equals(argument.getId()))) + throw new IllegalArgumentException("argument with same identifier is already added"); + arguments.add(argument); + } + + private boolean isGreedy() { + var arg = arguments.getLast(); + return arg != null && arg.getType().isGreedy(); + } +} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/ParsedArgumentImpl.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/ParsedArgumentImpl.java new file mode 100644 index 0000000..9d98210 --- /dev/null +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/ParsedArgumentImpl.java @@ -0,0 +1,11 @@ +package de.kentoj.kencommandapi.internal; + +import de.kentoj.kencommandapi.api.argument.CommandArgument; +import de.kentoj.kencommandapi.api.invocation.ParsedArgument; +import lombok.Value; + +@Value +public class ParsedArgumentImpl implements ParsedArgument { + V value; + CommandArgument argument; +} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/parser/CommandParser.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/parser/CommandParser.java new file mode 100644 index 0000000..18ea798 --- /dev/null +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/parser/CommandParser.java @@ -0,0 +1,83 @@ +package de.kentoj.kencommandapi.internal.parser; + +import de.kentoj.kencommandapi.api.CommandNode; +import de.kentoj.kencommandapi.api.argument.CommandArgument; +import de.kentoj.kencommandapi.api.invocation.ParsedArgument; +import de.kentoj.kencommandapi.internal.ParsedArgumentImpl; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +@RequiredArgsConstructor +public class CommandParser { + + private final PermissionChecker hasPermissionMethod; + + public ParseResult parseLiteral(CommandNode rootNode, T sender, String[] args) { + var cur = rootNode; + final Iterator argStrIter = Arrays.stream(args).iterator(); + final Map> parsedArguments = new HashMap<>(); + + while (argStrIter.hasNext()) { + for (var nodeArg : cur.getArguments()) { + ParsedArgument parsed; + try { + parsed = parseArgument(argStrIter, sender, nodeArg); + } catch (IllegalArgumentException ex) { + return new ParseResult.IllegalArgument<>(cur, ex.getMessage()); + } + if (parsed == null) return new ParseResult.IncompleteArgument<>(cur, nodeArg); + parsedArguments.put(nodeArg.getId(), parsed); + } + + if (!argStrIter.hasNext()) { + if (cur.getExecutor() == null) + return new ParseResult.IncompleteCommand<>(cur); + return new ParseResult.Success<>(cur, parsedArguments); + } + + var argStr = argStrIter.next(); + var nextNode = cur.getLiteral(argStr); + if (nextNode == null) + return new ParseResult.UnknownLiteral<>(cur, argStr); + if (nextNode.getPermission() != null && !hasPermissionMethod.hasPermission(sender, nextNode.getPermission())) + return new ParseResult.NoPermission<>(cur, argStr); + cur = nextNode; + } + + throw new RuntimeException("code reached unexpected line"); + } + + @SuppressWarnings("unchecked") + private ParsedArgument parseArgument(Iterator args, T sender, CommandArgument nodeArg) { + Object value; + + if (args.hasNext()) { + var argStr = getRawArgument(args, nodeArg); + value = nodeArg.getType().parseInput(argStr); + } else { + if (nodeArg.getDefaultValueProvider() == null) + return null; + value = nodeArg.getDefaultValueProvider().apply(sender); + } + + return new ParsedArgumentImpl<>(value, (CommandArgument) nodeArg); + } + + private String getRawArgument(Iterator args, CommandArgument arg) { + StringBuilder rawArgument = new StringBuilder(); + + if (arg.getType().isGreedy()) { + while (args.hasNext()) { + rawArgument.append(args.next()); + } + } else { + rawArgument = new StringBuilder(args.next()); + } + + return rawArgument.toString(); + } +} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/parser/ParseResult.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/parser/ParseResult.java new file mode 100644 index 0000000..c2a133a --- /dev/null +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/parser/ParseResult.java @@ -0,0 +1,92 @@ +package de.kentoj.kencommandapi.internal.parser; + +import de.kentoj.kencommandapi.api.CommandNode; +import de.kentoj.kencommandapi.api.argument.CommandArgument; +import de.kentoj.kencommandapi.api.invocation.ParsedArgument; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Map; + +public abstract sealed class ParseResult permits ParseResult.IllegalArgument, ParseResult.IncompleteArgument, + ParseResult.IncompleteCommand, ParseResult.NoPermission, ParseResult.Success, ParseResult.UnknownLiteral { + + /** + * @return last successfully literal + */ + public abstract CommandNode getLiteral(); + + public abstract String getMessage(); + + @RequiredArgsConstructor + public static final class IncompleteArgument extends ParseResult { + @Getter + private final CommandNode literal; + private final CommandArgument argument; + + @Override + public String getMessage() { + return "Missing value for argument: " + argument.getId(); + } + } + + @RequiredArgsConstructor + public static final class UnknownLiteral extends ParseResult { + private final CommandNode previousLiteral; + private final String name; + + @Override + public String getMessage() { + return "Unknown literal: " + name; + } + + @Override + public CommandNode getLiteral() { + return previousLiteral; + } + } + + @RequiredArgsConstructor + public static final class IncompleteCommand extends ParseResult { + @Getter + private final CommandNode literal; + + @Override + public String getMessage() { + return "Incomplete command"; + } + } + + @RequiredArgsConstructor + public static final class Success extends ParseResult { + @Getter + private final CommandNode literal; + @Getter + private final Map> parsedArguments; + + @Override + public String getMessage() { + return ""; + } + } + + @RequiredArgsConstructor + public static final class NoPermission extends ParseResult { + @Getter + private final CommandNode literal; + private final String name; + + @Override + public String getMessage() { + return "Unknown literal: " + name; + } + } + + @RequiredArgsConstructor + public static final class IllegalArgument extends ParseResult { + @Getter + private final CommandNode literal; + @Getter + private final String message; + } +} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/parser/PermissionChecker.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/parser/PermissionChecker.java new file mode 100644 index 0000000..9b4ee55 --- /dev/null +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/parser/PermissionChecker.java @@ -0,0 +1,6 @@ +package de.kentoj.kencommandapi.internal.parser; + +@FunctionalInterface +public interface PermissionChecker { + boolean hasPermission(T sender, String permission); +} diff --git a/kencommandapi-velocity/build.gradle.kts b/kencommandapi-velocity/build.gradle.kts index 271bef5..7e7afd7 100644 --- a/kencommandapi-velocity/build.gradle.kts +++ b/kencommandapi-velocity/build.gradle.kts @@ -1,13 +1,12 @@ plugins { id("java") - id("java-library") + id("maven-publish") + id("de.kentoj.scrow.scrow-repository") + id("de.kentoj.scrow.base-dependencies") + id("de.kentoj.scrow.java-library") } -group = "de.kentoj.scrow" -version = rootProject.version - repositories { - mavenCentral() maven { name = "papermc" url = uri("https://repo.papermc.io/repository/maven-public/") @@ -20,4 +19,15 @@ dependencies { compileOnly("com.velocitypowered:velocity-api:3.5.0-SNAPSHOT") api(project(":kencommandapi-core")) -} \ No newline at end of file +} + +publishing { + publications { + create("KenCommandAPI") { + groupId = rootProject.group.toString() + version = rootProject.version.toString() + artifactId = project.name + from(components["java"]) + } + } +} diff --git a/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/CommandAPI.java b/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/CommandAPI.java new file mode 100644 index 0000000..be08240 --- /dev/null +++ b/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/CommandAPI.java @@ -0,0 +1,24 @@ +package de.kentoj.kencommandapi; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.ProxyServer; +import de.kentoj.kencommandapi.api.CommandNode; +import de.kentoj.kencommandapi.internal.parser.CommandParser; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +@RequiredArgsConstructor +public class CommandAPI { + + private final ProxyServer server; + private final CommandParser commandParser = new CommandParser<>(CommandSource::hasPermission); + + public void register(CommandNode rootNode) { + var cm = server.getCommandManager(); + var meta = cm.metaBuilder(rootNode.getNames()[0]) + .aliases(Arrays.copyOfRange(rootNode.getNames(), 1, rootNode.getNames().length - 1)) + .build(); + cm.register(meta, new VelocityCommand(commandParser, rootNode)); + } +} diff --git a/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/VelocityCommand.java b/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/VelocityCommand.java index fc1dc20..27241ec 100644 --- a/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/VelocityCommand.java +++ b/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/VelocityCommand.java @@ -2,22 +2,47 @@ package de.kentoj.kencommandapi; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; -import de.kentoj.kencommandapi.api.nodes.CommandNode; +import de.kentoj.kencommandapi.api.CommandNode; +import de.kentoj.kencommandapi.api.invocation.CommandContext; +import de.kentoj.kencommandapi.api.invocation.CommandExecutor; +import de.kentoj.kencommandapi.internal.parser.CommandParser; +import de.kentoj.kencommandapi.internal.parser.ParseResult; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; @RequiredArgsConstructor public class VelocityCommand implements SimpleCommand { - private final VelocityKenCommandApi commandApi; - private final CommandNode rootNode; + private final CommandParser commandParser; + private final CommandNode rootNode; @Override - public void execute(Invocation ctx) { - commandApi.invoke(rootNode, ctx.source(), ctx.arguments()); + public void execute(Invocation invocation) { + var parseResult = commandParser.parseLiteral(rootNode, invocation.source(), invocation.arguments()); + if (parseResult instanceof ParseResult.Success) { + var ctx = new CommandContext<>(invocation.source(), ((ParseResult.Success) parseResult).getParsedArguments()); + assert parseResult.getLiteral().getExecutor() != null; + + try { + var result = parseResult.getLiteral().getExecutor().execute(ctx); + if (result.getError() != null) + invocation.source().sendMessage(Component.text(result.getError()).color(NamedTextColor.RED)); + } catch (Exception e) { + invocation.source().sendMessage(Component.text("ERROR ") + .append(Component.text(e.getMessage())) + .color(NamedTextColor.DARK_RED) + .decorate(TextDecoration.BOLD)); + } + } else { + invocation.source().sendMessage(Component.text(parseResult.getMessage()).color(NamedTextColor.RED)); + } } @Override public boolean hasPermission(Invocation ctx) { - return commandApi.hasPermission(ctx.source(), rootNode.getPermission()); + if (rootNode.getPermission() == null) return true; + return ctx.source().hasPermission(rootNode.getPermission()); } } diff --git a/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/VelocityCommandContext.java b/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/VelocityCommandContext.java deleted file mode 100644 index 41a9bae..0000000 --- a/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/VelocityCommandContext.java +++ /dev/null @@ -1,22 +0,0 @@ -package de.kentoj.kencommandapi; - -import com.velocitypowered.api.command.CommandSource; -import com.velocitypowered.api.proxy.Player; -import de.kentoj.kencommandapi.api.processing.ParsedArgument; -import de.kentoj.kencommandapi.impl.processing.CommandContextImpl; - -import java.util.Map; - -public class VelocityCommandContext extends CommandContextImpl { - - VelocityCommandContext( - CommandSource sender, - Map> parsedArguments - ) { - super(sender, parsedArguments); - } - - public Player getPlayerSender() { - return (Player) getSender(); - } -} diff --git a/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/VelocityKenCommandApi.java b/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/VelocityKenCommandApi.java deleted file mode 100644 index 06aa692..0000000 --- a/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/VelocityKenCommandApi.java +++ /dev/null @@ -1,44 +0,0 @@ -package de.kentoj.kencommandapi; - -import com.velocitypowered.api.command.CommandSource; -import com.velocitypowered.api.proxy.ProxyServer; -import de.kentoj.kencommandapi.api.nodes.CommandNode; -import de.kentoj.kencommandapi.api.processing.CommandException; -import de.kentoj.kencommandapi.api.processing.ParsedArgument; -import de.kentoj.kencommandapi.impl.AbstractKenCommandApi; -import lombok.RequiredArgsConstructor; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; - -import java.util.Arrays; -import java.util.Map; - -@RequiredArgsConstructor -public class VelocityKenCommandApi extends AbstractKenCommandApi { - - private final ProxyServer server; - - @Override - public VelocityCommandContext createContext(CommandSource sender, Map> parsedArguments) { - return new VelocityCommandContext(sender, parsedArguments); - } - - @Override - public void sendErrorMessage(CommandSource sender, CommandException ex) { - var cause = (ex.getCause() != null) ? ": " + ex.getCause().getMessage() : ""; - sender.sendMessage(Component.text(ex.getMessage() + cause).color(NamedTextColor.RED)); - } - - @Override - public boolean hasPermission(CommandSource commandSource, String permission) { - return commandSource.hasPermission(permission); - } - - public void register(CommandNode rootNode) { - var cm = server.getCommandManager(); - var meta = cm.metaBuilder(rootNode.getNames()[0]) - .aliases(Arrays.copyOfRange(rootNode.getNames(), 1, rootNode.getNames().length - 1)) - .build(); - cm.register(meta, new VelocityCommand(this, rootNode)); - } -} diff --git a/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/suggestionprovider/PlayerSuggestionProvider.java b/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/suggestionprovider/PlayerSuggestionProvider.java index 896c36d..a7ac325 100644 --- a/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/suggestionprovider/PlayerSuggestionProvider.java +++ b/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/suggestionprovider/PlayerSuggestionProvider.java @@ -3,8 +3,8 @@ package de.kentoj.kencommandapi.suggestionprovider; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; -import de.kentoj.kencommandapi.VelocityCommandContext; -import de.kentoj.kencommandapi.api.argument.SuggestionProvider; +import de.kentoj.kencommandapi.api.invocation.CommandContext; +import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider; import lombok.RequiredArgsConstructor; import java.util.Set; @@ -12,12 +12,12 @@ import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @RequiredArgsConstructor -public class PlayerSuggestionProvider implements SuggestionProvider { +public class PlayerSuggestionProvider implements SuggestionProvider { private final ProxyServer proxyServer; @Override - public CompletableFuture> suggest(VelocityCommandContext context) { + public CompletableFuture> suggest(CommandContext context) { var list = proxyServer.getAllPlayers().stream() .map(Player::getUsername) .collect(Collectors.toSet()); diff --git a/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/type/PlayerArgumentType.java b/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/type/PlayerArgumentType.java index 53434db..335ebb1 100644 --- a/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/type/PlayerArgumentType.java +++ b/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/type/PlayerArgumentType.java @@ -3,27 +3,25 @@ package de.kentoj.kencommandapi.type; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; -import de.kentoj.kencommandapi.VelocityCommandContext; import de.kentoj.kencommandapi.api.argument.ArgumentType; -import de.kentoj.kencommandapi.api.argument.SuggestionProvider; -import de.kentoj.kencommandapi.api.processing.CommandException; +import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider; import de.kentoj.kencommandapi.suggestionprovider.PlayerSuggestionProvider; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor -public class PlayerArgumentType implements ArgumentType { +public class PlayerArgumentType implements ArgumentType { private final ProxyServer server; @Override public Player parseInput(String string) { var player = server.getPlayer(string).orElse(null); - if (player == null) throw new CommandException("Player not online -- " + string); + if (player == null) throw new IllegalArgumentException("Player not online -- " + string); return player; } @Override - public SuggestionProvider defaultSuggestionProvider() { + public SuggestionProvider defaultSuggestionProvider() { return new PlayerSuggestionProvider(server); } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 6071d23..3bb6fc9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,6 @@ rootProject.name = "KenCommandAPI" +includeBuild("../build-logic") include("kencommandapi-core") include("kencommandapi-bukkit") include("kencommandapi-velocity") \ No newline at end of file