diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 4ba5097..1eb1391 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,15 +5,28 @@ - - - + + + - - - - - + + + + + + + + + + + + + + + + + + - { + "keyToString": { + "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", + "Gradle.CommandParserTest.checkArgumentExpected.executor": "Run", + "Gradle.Download Sources.executor": "Run", + "Gradle.KenCommandAPI [:de.kencommand.Main.main()].executor": "Run", + "Gradle.KenCommandAPI [assemble].executor": "Run", + "Gradle.KenCommandAPI [buildEnvironment].executor": "Run", + "Gradle.KenCommandAPI [build].executor": "Run", + "Gradle.KenCommandAPI [clean test].executor": "Run", + "Gradle.KenCommandAPI [clean].executor": "Run", + "Gradle.KenCommandAPI [publishAllPublicationsToScrowRepository].executor": "Run", + "Gradle.KenCommandAPI [publish].executor": "Run", + "Gradle.KenCommandAPI [test].executor": "Run", + "Gradle.KenCommandAPI:kencommandapi-bukkit [build].executor": "Run", + "Gradle.KenCommandAPI:kencommandapi-bukkit [generatePomFileForKenCommandAPIBukkitPublication].executor": "Run", + "Gradle.KenCommandAPI:kencommandapi-bukkit [publishAllPublicationsToScrowRepository].executor": "Run", + "Gradle.KenCommandAPI:kencommandapi-bukkit [publish].executor": "Run", + "Gradle.KenCommandAPI:kencommandapi-velocity [publish].executor": "Run", + "Gradle.kencommandapi-core [build].executor": "Run", + "ModuleVcsDetector.initialDetectionPerformed": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true", + "RunOnceActivity.git.unshallow": "true", + "git-widget-placeholder": "master", + "kotlin-language-version-configured": "true", + "last_opened_file_path": "/home/a/src/scrow/core", + "project.structure.last.edited": "Modules", + "project.structure.proportion": "0.15", + "project.structure.side.proportion": "0.6268199", + "settings.editor.selected.configurable": "preferences.pluginManager" } -}]]> +} @@ -166,11 +186,11 @@ + - @@ -182,7 +202,7 @@ - + + + + + + + true + true + false + false + + - - - - - - true - true - false - false - - + + - - diff --git a/TODO b/TODO index b801d86..8d50a63 100644 --- a/TODO +++ b/TODO @@ -1,4 +1 @@ -- use version catalog -- fix missing ArgumentExpected error at first arg or smth (maybe all args are shifted ???) -- make sure permissions of literals work -- fix suggestions/tab completion \ No newline at end of file +- suggest next literal/argument \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 8c27e8d..b51ec73 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ group = "de.kentoj.scrow" -version = "0.32" +version = "0.33-SNAPSHOT" subprojects { group = rootProject.group 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 119166e..25dbe36 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,7 @@ package de.kentoj.kencommandapi; import de.kentoj.kencommandapi.api.CommandHandler; -import de.kentoj.kencommandapi.api.node.RootCommandNode; +import de.kentoj.kencommandapi.api.literal.RootLiteral; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; @@ -10,18 +10,18 @@ import java.util.Arrays; public class BukkitKenCommand extends Command { - private final RootCommandNode rootNode; + private final RootLiteral rootNode; private final CommandHandler commandHandler; protected BukkitKenCommand( CommandHandler commandHandler, - RootCommandNode rootNode + RootLiteral rootLiteral ) { - super(rootNode.getNames()[0]); + super(rootLiteral.getNames()[0]); this.commandHandler = commandHandler; - this.rootNode = rootNode; - this.setAliases(Arrays.asList(rootNode.getNames())); - this.setPermission(rootNode.getPermission()); + this.rootNode = rootLiteral; + this.setAliases(Arrays.asList(rootLiteral.getNames())); + this.setPermission(rootLiteral.getPermission()); } @Override diff --git a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/CommandAPI.java b/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/CommandAPI.java index 97f8033..9c27132 100644 --- a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/CommandAPI.java +++ b/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/CommandAPI.java @@ -1,8 +1,7 @@ package de.kentoj.kencommandapi; import de.kentoj.kencommandapi.api.CommandHandler; -import de.kentoj.kencommandapi.api.node.RootCommandNode; -import de.kentoj.kencommandapi.api.parser.CommandParser; +import de.kentoj.kencommandapi.api.literal.RootLiteral; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.command.SimpleCommandMap; @@ -17,12 +16,11 @@ import java.util.List; public class CommandAPI { private final CommandHandler commandHandler; - private final List> registeredNodes = new ArrayList<>(); + private final List> registeredNodes = new ArrayList<>(); private final SimpleCommandMap commandMap; public CommandAPI(Plugin plugin) { - this.commandHandler = new CommandHandler<>(new CommandParser<>(Permissible::hasPermission), - CommandSender::sendMessage, Permissible::hasPermission); + this.commandHandler = new CommandHandler<>(CommandSender::sendMessage, Permissible::hasPermission); Bukkit.getPluginManager().registerEvents(new TabCompleteListener(this, commandHandler), plugin); try { @@ -34,7 +32,7 @@ public class CommandAPI { } } - @Nullable RootCommandNode getRegistered(String name) { + @Nullable RootLiteral getRegistered(String name) { for (var node : registeredNodes) { for (String nodeName : node.getNames()) { if (nodeName.equals(name)) return node; @@ -43,7 +41,7 @@ public class CommandAPI { return null; } - public void register(RootCommandNode rootNode) { + public void register(RootLiteral rootNode) { registeredNodes.add(rootNode); commandMap.register(rootNode.getNames()[0], new BukkitKenCommand(commandHandler, rootNode)); } diff --git a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/TabCompleteListener.java b/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/TabCompleteListener.java index 6935201..70c0479 100644 --- a/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/TabCompleteListener.java +++ b/kencommandapi-bukkit/src/main/java/de/kentoj/kencommandapi/TabCompleteListener.java @@ -7,6 +7,8 @@ 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 { @@ -17,11 +19,11 @@ public class TabCompleteListener implements Listener { public void onAsyncTabComplete(AsyncTabCompleteEvent ev) { if (!ev.isCommand()) return; - var args = ev.getBuffer().split(" "); + 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(), args)); + ev.setCompletions(commandHandler.getSuggestions(rootNode, ev.getSender(), Arrays.copyOfRange(args, 1, args.length))); } } 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 index 9f5e5ad..9738558 100644 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/CommandHandler.java +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/CommandHandler.java @@ -1,66 +1,46 @@ package de.kentoj.kencommandapi.api; import de.kentoj.kencommandapi.api.invocation.CommandContext; -import de.kentoj.kencommandapi.api.node.CommandNode; -import de.kentoj.kencommandapi.api.node.RootCommandNode; -import de.kentoj.kencommandapi.api.parser.CommandParser; -import de.kentoj.kencommandapi.api.parser.ParseResult; +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 org.jetbrains.annotations.Nullable; -import java.util.Arrays; import java.util.List; @RequiredArgsConstructor public class CommandHandler { - private final CommandParser commandParser; private final SendMessageMethod sendMessageMethod; - private final HasPermissionMethod hasPermissionMethod; - public void invoke(RootCommandNode rootNode, T sender, String[] args) { - var parseResult = commandParser.parseLiteral(rootNode, sender, args, true); - if (!(parseResult instanceof ParseResult.Success)) { - sendMessageMethod.send(sender, rootNode.getMessageStyle().err(parseResult.getMessage())); - return; - } + private final InvocationCommandParser invocationParser; + private final SuggestionCommandParser suggestionParser; - var ctx = new CommandContext<>(sender, ((ParseResult.Success) parseResult).getParsedArguments()); - assert parseResult.getLiteral().getExecutor() != null; - parseResult.getLiteral().getExecutor().execute(ctx).whenComplete((result, ex) -> { - if (ex != null) - sendMessageMethod.send(sender, rootNode.getMessageStyle().exception(ex)); - result.ifFailure(failure -> - sendMessageMethod.send(sender, rootNode.getMessageStyle().err(failure))); - }); + public CommandHandler(SendMessageMethod sendMessageMethod, HasPermissionMethod hasPermissionMethod) { + this.sendMessageMethod = sendMessageMethod; + invocationParser = new InvocationCommandParser<>(hasPermissionMethod); + suggestionParser = new SuggestionCommandParser<>(hasPermissionMethod); } - public List getSuggestions(RootCommandNode rootNode, T sender, String[] args) { - var parseResult = commandParser.parseLiteral(rootNode, sender, args, false); - - if (parseResult instanceof ParseResult.ArgumentExpected) { - var arg = ((ParseResult.ArgumentExpected) parseResult).getArgument(); - return arg.getSuggestionProvider().suggest(sender); - } - - String filter = null; - if (parseResult instanceof ParseResult.UnknownLiteral) - filter = ((ParseResult.UnknownLiteral) parseResult).getInput(); - else if (parseResult instanceof ParseResult.NoPermission) - filter = ((ParseResult.NoPermission) parseResult).getInput(); - return getSuggestions(sender, parseResult.getLiteral(), filter); + @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)); + }); } - private List getSuggestions(T sender, CommandNode literal, @Nullable String filter) { - var stream = literal.getLiterals().stream() - .filter(lit -> lit.getPermission() != null && hasPermissionMethod.hasPermission(sender, lit.getPermission())) - .map(CommandNode::getNames) - .flatMap(Arrays::stream); - if (filter != null) - stream = stream.filter(f -> f.toLowerCase().contains(filter.toLowerCase())); - - return stream.toList(); + 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/node/CommandNode.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/literal/Literal.java similarity index 58% rename from kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/node/CommandNode.java rename to kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/literal/Literal.java index 4b956b8..531905b 100644 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/node/CommandNode.java +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/literal/Literal.java @@ -1,23 +1,23 @@ -package de.kentoj.kencommandapi.api.node; +package de.kentoj.kencommandapi.api.literal; import de.kentoj.kencommandapi.api.platform.MessageStyle; import de.kentoj.kencommandapi.api.argument.CommandArgument; import de.kentoj.kencommandapi.api.invocation.CommandExecutor; -import de.kentoj.kencommandapi.internal.CommandNodeImpl; -import de.kentoj.kencommandapi.internal.RootCommandNodeImpl; +import de.kentoj.kencommandapi.internal.LiteralImpl; +import de.kentoj.kencommandapi.internal.RootLiteralImpl; 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); +public interface Literal { + static Literal literal(String... name) { + return new LiteralImpl<>(name); } - static RootCommandNode rootNode(MessageStyle messageStyle, String...name) { - return new RootCommandNodeImpl<>(messageStyle, name); + static RootLiteral rootLiteral(MessageStyle messageStyle, String...name) { + return new RootLiteralImpl<>(messageStyle, name); } @NotNull String[] getNames(); @@ -32,7 +32,8 @@ public interface CommandNode { /** * @throws IllegalArgumentException if there is already an argument with the same id - * @throws IllegalStateException if a greedy argument was added to the node previously + * @throws IllegalStateException if a greedy argument was added to the literal previously + * @throws IllegalStateException if the literal already has a child literal assigned to it */ void addArgument(CommandArgument argument); @@ -48,13 +49,14 @@ public interface CommandNode { /** * @throws IllegalArgumentException if there is already a literal with the same name - * @throws IllegalStateException if a greedy argument was added to the node previously + * @throws IllegalStateException if a greedy argument was added to the literal previously + * @throws IllegalStateException if the literal already has an argument assigned to it */ - void addLiteral(@NotNull CommandNode commandNode); + void addLiteral(@NotNull Literal literal); - @Nullable CommandNode getLiteral(@NotNull String name); + @Nullable Literal getLiteral(@NotNull String name); - @NotNull Set> getLiterals(); + @NotNull Set> getLiterals(); @Nullable String getPermission(); diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/node/RootCommandNode.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/literal/RootLiteral.java similarity index 59% rename from kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/node/RootCommandNode.java rename to kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/literal/RootLiteral.java index 68acae4..cefcbee 100644 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/node/RootCommandNode.java +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/literal/RootLiteral.java @@ -1,8 +1,8 @@ -package de.kentoj.kencommandapi.api.node; +package de.kentoj.kencommandapi.api.literal; import de.kentoj.kencommandapi.api.platform.MessageStyle; -public interface RootCommandNode extends CommandNode { +public interface RootLiteral extends Literal { MessageStyle getMessageStyle(); diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/parser/CommandParser.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/parser/CommandParser.java deleted file mode 100644 index 030e4d2..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/parser/CommandParser.java +++ /dev/null @@ -1,103 +0,0 @@ -package de.kentoj.kencommandapi.api.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.invocation.ParsedArgument; -import de.kentoj.kencommandapi.api.node.CommandNode; -import de.kentoj.kencommandapi.api.node.RootCommandNode; -import de.kentoj.kencommandapi.api.platform.HasPermissionMethod; -import de.kentoj.kencommandapi.internal.ParsedArgumentImpl; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -@RequiredArgsConstructor -public class CommandParser { - - private final HasPermissionMethod hasPermissionMethod; - - public ParseResult parseLiteral(RootCommandNode rootNode, T sender, String[] args, boolean parseArgs) { - CommandNode cur = rootNode; - final Iterator argStrIter = Arrays.stream(args).iterator(); - final Map> parsedArguments = new HashMap<>(); - - while (true) { - for (var nodeArg : cur.getArguments()) { - var parsed = parseArgument(rootNode, nodeArg, argStrIter, sender, parseArgs); - if (parsed.hasFailure()) - return parsed.getFailure().orElseThrow(); - if (parseArgs) - parsedArguments.put(nodeArg.getId(), parsed.getSuccess().orElseThrow()); - } - - if (!argStrIter.hasNext()) { - if (cur.getExecutor() == null) - return new ParseResult.LiteralExpected<>(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; - } - } - - @SuppressWarnings("unchecked") - private Result, ParseResult> parseArgument( - CommandNode node, - CommandArgument nodeArg, - Iterator args, - T sender, - boolean parseArgs - ) { - if (!args.hasNext()) { - if (nodeArg.getDefaultValueProvider() == null) - return Results.failure(new ParseResult.ArgumentExpected<>(node, nodeArg)); - var value = nodeArg.getDefaultValueProvider().apply(sender); - return Results.success(toParsed(value, nodeArg)); - } - - Object value = null; - var argStr = getRawArgument(args, nodeArg); - if (parseArgs) { - try { - value = nodeArg.getType().parseInput(argStr); - } catch (IllegalArgumentException ex) { - return Results.failure(new ParseResult.IllegalArgument<>(node, nodeArg, ex.getMessage(), argStr)); - } - } - - var parsedArg = new ParsedArgumentImpl<>(value, (CommandArgument) nodeArg); - return Results.success(parsedArg); - } - - @SuppressWarnings("unchecked") - private ParsedArgument toParsed(Object value, CommandArgument arg) { - return new ParsedArgumentImpl<>(value, (CommandArgument) arg); - } - - private String getRawArgument(Iterator args, CommandArgument arg) { - StringBuilder rawArgument = new StringBuilder(); - - if (arg.getType().isGreedy()) { - while (args.hasNext()) { - rawArgument.append(" "); - rawArgument.append(args.next()); - } - rawArgument.deleteCharAt(0); - } else { - rawArgument = new StringBuilder(args.next()); - } - - return rawArgument.toString(); - } -} \ No newline at end of file diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/parser/ParseResult.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/parser/ParseResult.java deleted file mode 100644 index 4c8d189..0000000 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/parser/ParseResult.java +++ /dev/null @@ -1,114 +0,0 @@ -package de.kentoj.kencommandapi.api.parser; - -import de.kentoj.kencommandapi.api.argument.CommandArgument; -import de.kentoj.kencommandapi.api.invocation.ParsedArgument; -import de.kentoj.kencommandapi.api.node.CommandNode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -import java.util.Map; - -public abstract sealed class ParseResult permits ParseResult.IllegalArgument, ParseResult.ArgumentExpected, - ParseResult.LiteralExpected, ParseResult.NoPermission, ParseResult.Success, ParseResult.UnknownLiteral { - - /** - * @return last successfully literal - */ - public abstract CommandNode getLiteral(); - - public abstract String getMessage(); - - @RequiredArgsConstructor - public static final class ArgumentExpected extends ParseResult { - @Getter - private final CommandNode literal; - @Getter - private final CommandArgument argument; - - @Override - public String getMessage() { - return "Argument expected: " + argument.getId(); - } - } - - @RequiredArgsConstructor - public static final class UnknownLiteral extends ParseResult { - private final CommandNode previousLiteral; - @Getter - private final String input; - - @Override - public String getMessage() { - return "Unknown subcommand: " + input; - } - - @Override - public CommandNode getLiteral() { - return previousLiteral; - } - } - - @RequiredArgsConstructor - public static final class LiteralExpected extends ParseResult { - @Getter - private final CommandNode literal; - - @Override - public String getMessage() { - return "Subcommand expected"; - } - } - - @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; - @Getter - private final String input; - - @Override - public String getMessage() { - return "Unknown subcommand: " + input; - } - } - - @RequiredArgsConstructor - public static final class IllegalArgument extends ParseResult { - @Getter - private final CommandNode literal; - private final CommandArgument argument; - private final String message; - /** - * Input passed to the parser - */ - @Getter - private final String argStr; - - public IllegalArgument(CommandNode literal, CommandArgument argument, Throwable throwable, String argStr) { - this.literal = literal; - this.argument = argument; - this.argStr = argStr; - while (throwable.getMessage() == null && throwable.getCause() != null) - throwable = throwable.getCause(); - message = throwable.getMessage(); - } - - @Override - public String getMessage() { - return "Illegal argument '" + argStr + "' for " + argument.getId() + ": " + message; - } - } -} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/parser2/AbstractCommandParser.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/parser2/AbstractCommandParser.java new file mode 100644 index 0000000..0564e56 --- /dev/null +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/parser2/AbstractCommandParser.java @@ -0,0 +1,39 @@ +package de.kentoj.kencommandapi.api.parser2; + +import com.leakyabstractions.result.api.Result; +import com.leakyabstractions.result.core.Results; +import de.kentoj.kencommandapi.api.argument.CommandArgument; +import de.kentoj.kencommandapi.api.literal.Literal; +import de.kentoj.kencommandapi.api.platform.HasPermissionMethod; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; + +@RequiredArgsConstructor +public abstract class AbstractCommandParser { + + private final HasPermissionMethod hasPermission; + + protected boolean hasPermission(T sender, Literal literal) { + if (literal.getPermission() == null) return true; + return hasPermission.test(sender, literal.getPermission()); + } + + protected String parseInput(String[] args, AtomicInteger index, boolean isGreedy) { + if (!isGreedy) + return args[index.getAndIncrement()]; + + var ret = String.join(" ", Arrays.copyOfRange(args, index.get(), args.length)); + index.set(args.length); + return ret; + } + + protected Result parseArgument(CommandArgument argument, String input) { + try { + return Results.success(argument.getType().parseInput(input)); + } catch (IllegalArgumentException ex) { + return Results.failure("Illegal argument '" + input + "' for " + argument.getId() + ": " + ex.getMessage()); + } + } +} \ No newline at end of file diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/parser2/InvocationCommandParser.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/parser2/InvocationCommandParser.java new file mode 100644 index 0000000..af15745 --- /dev/null +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/parser2/InvocationCommandParser.java @@ -0,0 +1,74 @@ +package de.kentoj.kencommandapi.api.parser2; + +import com.leakyabstractions.result.api.Result; +import com.leakyabstractions.result.core.Results; +import de.kentoj.kencommandapi.api.argument.CommandArgument; +import de.kentoj.kencommandapi.api.invocation.CommandExecutor; +import de.kentoj.kencommandapi.api.invocation.ParsedArgument; +import de.kentoj.kencommandapi.api.literal.Literal; +import de.kentoj.kencommandapi.api.literal.RootLiteral; +import de.kentoj.kencommandapi.api.platform.HasPermissionMethod; +import de.kentoj.kencommandapi.internal.ParsedArgumentImpl; +import lombok.Value; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public class InvocationCommandParser extends AbstractCommandParser { + + public InvocationCommandParser(HasPermissionMethod hasPermission) { + super(hasPermission); + } + + public Result invoke( + RootLiteral rootLiteral, + T sender, + String[] args + ) { + Map> parsedArguments = new HashMap<>(); + Literal currentLiteral = rootLiteral; + AtomicInteger index = new AtomicInteger(0); + + while (true) { + for (var _argument : currentLiteral.getArguments()) { + @SuppressWarnings("unchecked") + var argument = ((CommandArgument) _argument); + + Object value; + if (index.get() >= args.length) { + if (argument.getDefaultValueProvider() == null) + return Results.failure("Argument expected: " + argument.getId()); + value = argument.getDefaultValueProvider().apply(sender); + } else { + var input = parseInput(args, index, argument.getType().isGreedy()); + var parseResult = parseArgument(argument, input); + if (parseResult.hasFailure()) return parseResult.mapSuccess(__ -> null); + value = parseResult.getSuccess().orElseThrow(); + } + + parsedArguments.put(argument.getId(), new ParsedArgumentImpl<>(value, argument)); + } + + if (index.get() >= args.length) { + if (currentLiteral.getExecutor() == null) + return Results.failure("Literal expected"); + break; + } + + var literalName = args[index.getAndIncrement()]; + var nextLiteral = currentLiteral.getLiteral(literalName); + if (nextLiteral == null || !hasPermission(sender, nextLiteral)) + return Results.failure("Unknown literal: " + literalName); + currentLiteral = nextLiteral; + } + + return Results.success(new ExecutionData(currentLiteral.getExecutor(), parsedArguments)); + } + + @Value + public class ExecutionData { + CommandExecutor executor; + Map> arguments; + } +} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/parser2/SuggestionCommandParser.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/parser2/SuggestionCommandParser.java new file mode 100644 index 0000000..c2d3a35 --- /dev/null +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/parser2/SuggestionCommandParser.java @@ -0,0 +1,60 @@ +package de.kentoj.kencommandapi.api.parser2; + +import de.kentoj.kencommandapi.api.literal.Literal; +import de.kentoj.kencommandapi.api.literal.RootLiteral; +import de.kentoj.kencommandapi.api.platform.HasPermissionMethod; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +public class SuggestionCommandParser extends AbstractCommandParser { + + public SuggestionCommandParser(HasPermissionMethod hasPermission) { + super(hasPermission); + } + + public List getSuggestions( + RootLiteral rootNode, + T sender, + String[] args + ) { + Literal currentLiteral = rootNode; + AtomicInteger index = new AtomicInteger(0); + + while (true) { + for (var argument : currentLiteral.getArguments()) { + // TODO somewhere suggest next argument at trailing space + if (index.getAndIncrement() >= args.length) + return argument.getSuggestionProvider().suggest(sender); + if (argument.getType().isGreedy()) + index.set(args.length); + } + + if (index.get() >= args.length) { + // TODO suggest next argument at trailing space + if (currentLiteral.getExecutor() == null) + return suggestions(currentLiteral, sender).toList(); + } + + var input = args[index.getAndIncrement()]; + var nextLiteral = currentLiteral.getLiteral(input); + if (nextLiteral == null || !hasPermission(sender, nextLiteral)) { + var finalInput = input.toLowerCase(); + return suggestions(currentLiteral, sender) + .filter(name -> name.toLowerCase().startsWith(finalInput)) + .toList(); + } + + currentLiteral = nextLiteral; + } + } + + private Stream suggestions(Literal literal, T sender) { + return literal.getLiterals().stream() + .filter(lit -> hasPermission(sender, lit)) + .map(Literal::getNames) + .flatMap(Arrays::stream); + } +} diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/platform/HasPermissionMethod.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/platform/HasPermissionMethod.java index 1e3fd67..324925b 100644 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/platform/HasPermissionMethod.java +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/api/platform/HasPermissionMethod.java @@ -2,5 +2,5 @@ package de.kentoj.kencommandapi.api.platform; @FunctionalInterface public interface HasPermissionMethod { - boolean hasPermission(T sender, String permission); + boolean test(T sender, String permission); } diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/CommandNodeImpl.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/LiteralImpl.java similarity index 81% rename from kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/CommandNodeImpl.java rename to kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/LiteralImpl.java index d34d912..e2c0169 100644 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/CommandNodeImpl.java +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/LiteralImpl.java @@ -1,6 +1,6 @@ package de.kentoj.kencommandapi.internal; -import de.kentoj.kencommandapi.api.node.CommandNode; +import de.kentoj.kencommandapi.api.literal.Literal; import de.kentoj.kencommandapi.api.argument.CommandArgument; import de.kentoj.kencommandapi.api.invocation.CommandExecutor; import lombok.Getter; @@ -13,12 +13,12 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -public class CommandNodeImpl implements CommandNode { +public class LiteralImpl implements Literal { @Getter private final String[] names; @Getter - private final Set> literals = new HashSet<>(); + private final Set> literals = new HashSet<>(); @Getter private final List> arguments = new ArrayList<>(); @@ -34,22 +34,22 @@ public class CommandNodeImpl implements CommandNode { @Setter private @Nullable String permission = null; - public CommandNodeImpl(String[] names) { + public LiteralImpl(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) { + public void addLiteral(@NotNull Literal literal) { if (isGreedy()) throw new IllegalStateException("can't add literal to a node with a greedy argument"); - for (String name : commandNode.getNames()) + for (String name : literal.getNames()) if (getLiteral(name) != null) throw new IllegalArgumentException("literal with same name already exists"); - literals.add(commandNode); + literals.add(literal); } @Override - public CommandNode getLiteral(@NotNull String name) { + public Literal getLiteral(@NotNull String name) { for (var literal : literals) for (String literalName : literal.getNames()) if (literalName.equalsIgnoreCase(name)) return literal; diff --git a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/RootCommandNodeImpl.java b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/RootLiteralImpl.java similarity index 55% rename from kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/RootCommandNodeImpl.java rename to kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/RootLiteralImpl.java index 45112fa..cdbf772 100644 --- a/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/RootCommandNodeImpl.java +++ b/kencommandapi-core/src/main/java/de/kentoj/kencommandapi/internal/RootLiteralImpl.java @@ -1,16 +1,16 @@ package de.kentoj.kencommandapi.internal; import de.kentoj.kencommandapi.api.platform.MessageStyle; -import de.kentoj.kencommandapi.api.node.RootCommandNode; +import de.kentoj.kencommandapi.api.literal.RootLiteral; import lombok.Getter; import lombok.Setter; -public class RootCommandNodeImpl extends CommandNodeImpl implements RootCommandNode { +public class RootLiteralImpl extends LiteralImpl implements RootLiteral { @Setter @Getter private MessageStyle messageStyle; - public RootCommandNodeImpl(MessageStyle messageStyle, String[] names) { + public RootLiteralImpl(MessageStyle messageStyle, String[] names) { super(names); this.messageStyle = messageStyle; } diff --git a/kencommandapi-core/src/test/java/de/kentoj/kencommandapi/api/parser/CommandHandlerTest.java b/kencommandapi-core/src/test/java/de/kentoj/kencommandapi/api/parser/CommandHandlerTest.java index 28d9542..44f4df3 100644 --- a/kencommandapi-core/src/test/java/de/kentoj/kencommandapi/api/parser/CommandHandlerTest.java +++ b/kencommandapi-core/src/test/java/de/kentoj/kencommandapi/api/parser/CommandHandlerTest.java @@ -3,7 +3,7 @@ package de.kentoj.kencommandapi.api.parser; import de.kentoj.kencommandapi.api.CommandHandler; import de.kentoj.kencommandapi.api.argument.CommandArgument; import de.kentoj.kencommandapi.api.argument.types.StringArgumentType; -import de.kentoj.kencommandapi.api.node.CommandNode; +import de.kentoj.kencommandapi.api.literal.Literal; import de.kentoj.kencommandapi.api.platform.MessageStyle; import org.junit.jupiter.api.Test; @@ -14,19 +14,28 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class CommandHandlerTest { @Test - void testSuggestions() { - var rootNode = CommandNode.rootNode(MessageStyle.PLAIN, "someRootNode"); + void suggestArgument() { + var rootNode = Literal.rootLiteral(MessageStyle.PLAIN, "someRootNode"); var arg = CommandArgument.arg("someArg", new StringArgumentType<>()); arg.setSuggestionProvider(__ -> List.of("a", "b", "c")); rootNode.addArgument(arg); rootNode.setExecutor(ENV.EMPTY_EXECUTOR); - var handler = new CommandHandler<>( - new CommandParser<>((__, ___) -> true), - (__, ___) -> {}, - (__, ___) -> true - ); + var handler = new CommandHandler<>((__, ___) -> {}, (__, ___) -> true); var suggestions = handler.getSuggestions(rootNode, ENV.SENDER, new String[0]); assertEquals(List.of("a", "b", "c"), suggestions); } + + @Test + void suggestLiterals() { + var rootNode = Literal.rootLiteral(MessageStyle.PLAIN, "someRootNode"); + var literal1 = Literal.literal("lit1"); + var literal2 = Literal.literal("lit2"); + rootNode.addLiteral(literal1); + rootNode.addLiteral(literal2); + + var handler = new CommandHandler<>((__, ___) -> {}, (__, ___) -> true); + var suggestions = handler.getSuggestions(rootNode, ENV.SENDER, new String[0]); + assertEquals(List.of("lit1", "lit2"), suggestions.stream().sorted().toList()); + } } diff --git a/kencommandapi-core/src/test/java/de/kentoj/kencommandapi/api/parser/CommandParserTest.java b/kencommandapi-core/src/test/java/de/kentoj/kencommandapi/api/parser/CommandParserTest.java deleted file mode 100644 index eb0ef57..0000000 --- a/kencommandapi-core/src/test/java/de/kentoj/kencommandapi/api/parser/CommandParserTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package de.kentoj.kencommandapi.api.parser; - -import com.leakyabstractions.result.core.Results; -import de.kentoj.kencommandapi.api.argument.CommandArgument; -import de.kentoj.kencommandapi.api.argument.types.StringArgumentType; -import de.kentoj.kencommandapi.api.invocation.CommandExecutor; -import de.kentoj.kencommandapi.api.invocation.ParsedArgument; -import de.kentoj.kencommandapi.api.node.CommandNode; -import de.kentoj.kencommandapi.api.node.RootCommandNode; -import de.kentoj.kencommandapi.api.platform.MessageStyle; -import org.junit.jupiter.api.Test; - -import java.util.Map; -import java.util.concurrent.CompletableFuture; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; - -public class CommandParserTest { - - @Test - void checkArgumentExpected() { - RootCommandNode node = CommandNode.rootNode(MessageStyle.PLAIN, "someRootNode"); - var arg = CommandArgument.arg("someArg", new StringArgumentType<>(false)); - node.addArgument(arg); - - var parsed = ENV.PARSER.parseLiteral(node, ENV.SENDER, new String[]{}, false); - assertInstanceOf(ParseResult.ArgumentExpected.class, parsed); - } - - @Test - void checkGreedyStrings() { - RootCommandNode node = CommandNode.rootNode(MessageStyle.PLAIN, "someRootNode"); - var arg = CommandArgument.arg("someArg", new StringArgumentType<>(true)); - node.addArgument(arg); - node.setExecutor(ENV.EMPTY_EXECUTOR); - - var parsed = ENV.PARSER.parseLiteral(node, ENV.SENDER, new String[]{"part1", "part2"}, true); - Map> parsedStr = ((ParseResult.Success) parsed).getParsedArguments(); - String str = (String) parsedStr.get("someArg").getValue(); - assertEquals("part1 part2", str); - } -} diff --git a/kencommandapi-core/src/test/java/de/kentoj/kencommandapi/api/parser/ENV.java b/kencommandapi-core/src/test/java/de/kentoj/kencommandapi/api/parser/ENV.java index 1a7950a..4f1dda3 100644 --- a/kencommandapi-core/src/test/java/de/kentoj/kencommandapi/api/parser/ENV.java +++ b/kencommandapi-core/src/test/java/de/kentoj/kencommandapi/api/parser/ENV.java @@ -1,12 +1,14 @@ package de.kentoj.kencommandapi.api.parser; import com.leakyabstractions.result.core.Results; +import de.kentoj.kencommandapi.api.CommandHandler; import de.kentoj.kencommandapi.api.invocation.CommandExecutor; import java.util.concurrent.CompletableFuture; public class ENV { - public static final CommandParser PARSER = new CommandParser<>((__, ___) -> true); + public static final CommandHandler PARSER = + new CommandHandler<>((__, ___) -> {}, (__, ___) -> true); public static final Object SENDER = new Object(); public static final CommandExecutor EMPTY_EXECUTOR = __ -> CompletableFuture.completedFuture(Results.success(new Object())); diff --git a/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/CommandAPI.java b/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/CommandAPI.java index 1c8a21a..3a05c67 100644 --- a/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/CommandAPI.java +++ b/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/CommandAPI.java @@ -3,8 +3,7 @@ package de.kentoj.kencommandapi; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.ProxyServer; import de.kentoj.kencommandapi.api.CommandHandler; -import de.kentoj.kencommandapi.api.node.RootCommandNode; -import de.kentoj.kencommandapi.api.parser.CommandParser; +import de.kentoj.kencommandapi.api.literal.RootLiteral; import lombok.RequiredArgsConstructor; import java.util.Arrays; @@ -13,13 +12,10 @@ import java.util.Arrays; public class CommandAPI { private final ProxyServer server; - private final CommandHandler commandHandler = new CommandHandler<>( - new CommandParser<>(CommandSource::hasPermission), - CommandSource::sendMessage, - CommandSource::hasPermission - ); + private final CommandHandler commandHandler = + new CommandHandler<>(CommandSource::sendMessage, CommandSource::hasPermission); - public void register(RootCommandNode rootNode) { + public void register(RootLiteral rootNode) { var cm = server.getCommandManager(); var meta = cm.metaBuilder(rootNode.getNames()[0]) .aliases(Arrays.copyOfRange(rootNode.getNames(), 1, rootNode.getNames().length)) 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 0d9e2eb..c640cf8 100644 --- a/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/VelocityCommand.java +++ b/kencommandapi-velocity/src/main/java/de/kentoj/kencommandapi/VelocityCommand.java @@ -3,7 +3,7 @@ package de.kentoj.kencommandapi; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; import de.kentoj.kencommandapi.api.CommandHandler; -import de.kentoj.kencommandapi.api.node.RootCommandNode; +import de.kentoj.kencommandapi.api.literal.RootLiteral; import lombok.RequiredArgsConstructor; import java.util.List; @@ -12,7 +12,7 @@ import java.util.List; public class VelocityCommand implements SimpleCommand { private final CommandHandler commandHandler; - private final RootCommandNode rootNode; + private final RootLiteral rootNode; @Override public void execute(Invocation invocation) {