bump version to 0.38-SNAPSHOT + pass input to SuggestionProvider + rewrite command parsing + ...
All checks were successful
Build and Deploy artifact / build (push) Successful in 1m31s
All checks were successful
Build and Deploy artifact / build (push) Successful in 1m31s
This commit is contained in:
parent
997906cd02
commit
23f7d1a550
26 changed files with 382 additions and 302 deletions
|
|
@ -1,47 +1,72 @@
|
|||
package de.kentoj.kencommandapi.api;
|
||||
|
||||
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.api.platform.SendMessageMethod;
|
||||
import de.kentoj.kencommandapi.internal.parser.CommandParseHelper;
|
||||
import de.kentoj.kencommandapi.internal.parser.InvocationCommandParser;
|
||||
import de.kentoj.kencommandapi.internal.parser.SuggestionCommandParser;
|
||||
import de.kentoj.kencommandapi.internal.NodeTreeWalker;
|
||||
import de.kentoj.kencommandapi.internal.WalkResult;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public class CommandHandler<T> {
|
||||
|
||||
private final SendMessageMethod<T> sendMessageMethod;
|
||||
private final InvocationCommandParser<T> invocationParser;
|
||||
private final SuggestionCommandParser<T> suggestionParser;
|
||||
private final NodeTreeWalker<T> nodeTreeWalker;
|
||||
|
||||
public CommandHandler(SendMessageMethod<T> sendMessageMethod, HasPermissionMethod<T> hasPermissionMethod) {
|
||||
this.sendMessageMethod = sendMessageMethod;
|
||||
var parseHelper = new CommandParseHelper<>(hasPermissionMethod);
|
||||
invocationParser = new InvocationCommandParser<>(parseHelper);
|
||||
suggestionParser = new SuggestionCommandParser<>(parseHelper);
|
||||
nodeTreeWalker = new NodeTreeWalker<>(hasPermissionMethod);
|
||||
}
|
||||
|
||||
public void invoke(RootLiteral<T> rootLiteral, T sender, Stream<String> args) {
|
||||
invocationParser.parse(rootLiteral, sender, args.toList().iterator())
|
||||
.ifSuccessOrElse(data -> exec(rootLiteral, data),
|
||||
err -> sendMessageMethod.send(sender, rootLiteral.messageStyle().err(err)));
|
||||
}
|
||||
var walkResult = nodeTreeWalker.walk(rootLiteral, sender, args.iterator());
|
||||
if (!walkResult.isValidUsage()) {
|
||||
sendMessageMethod.send(sender, rootLiteral.messageStyle().err(walkResult.message()));
|
||||
return;
|
||||
}
|
||||
|
||||
private void exec(RootLiteral<T> rootLiteral, InvocationCommandParser<T>.ExecutionData data) {
|
||||
var ctx = data.context();
|
||||
data.executor().execute(ctx)
|
||||
requireNonNull(walkResult.lastLiteral().executor())
|
||||
.execute(walkResult.ctx())
|
||||
.whenComplete((result, ex) -> {
|
||||
if (ex != null)
|
||||
sendMessageMethod.send(ctx.sender(), rootLiteral.messageStyle().exception(ex));
|
||||
sendMessageMethod.send(sender, rootLiteral.messageStyle().exception(ex));
|
||||
result.ifFailure(err ->
|
||||
sendMessageMethod.send(ctx.sender(), rootLiteral.messageStyle().err(err)));
|
||||
sendMessageMethod.send(sender, rootLiteral.messageStyle().err(err)));
|
||||
});
|
||||
}
|
||||
|
||||
public List<String> getSuggestions(RootLiteral<T> rootNode, T sender, String[] args, boolean trailingSpace) {
|
||||
return suggestionParser.suggest(rootNode, sender, Arrays.stream(args).iterator(), trailingSpace);
|
||||
public List<String> getSuggestions(RootLiteral<T> rootLiteral, T sender, String[] args, boolean trailingSpace) {
|
||||
var walkResult = nodeTreeWalker.walk(rootLiteral, sender, Arrays.stream(args).iterator());
|
||||
Stream<String> stream;
|
||||
String input = null;
|
||||
switch (walkResult) {
|
||||
case WalkResult.LiteralUnknown<T> res -> {
|
||||
stream = res.lastLiteral().literals().stream().map(Literal::names).flatMap(Arrays::stream);
|
||||
input = res.input();
|
||||
}
|
||||
case WalkResult.ArgumentIllegal<T> res -> {
|
||||
stream = res.arg().suggestionProvider().suggest(sender, res.input()).stream();
|
||||
input = res.input();
|
||||
}
|
||||
case WalkResult.LiteralExpected<T> res when trailingSpace ->
|
||||
stream = res.lastLiteral().literals().stream().map(Literal::names).flatMap(Arrays::stream);
|
||||
case WalkResult.ArgumentExpected<T> res when trailingSpace ->
|
||||
stream = res.arg().suggestionProvider().suggest(sender, "").stream();
|
||||
default -> {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
if (input == null) return stream.toList();
|
||||
final var finalInput = input;
|
||||
return stream
|
||||
.filter(s -> s.startsWith(finalInput))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package de.kentoj.kencommandapi.api.argument;
|
||||
|
||||
import com.leakyabstractions.result.api.Result;
|
||||
import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
|
@ -10,11 +11,10 @@ public interface ArgumentType<T, V> {
|
|||
* parses the given input and returns it.
|
||||
* Unlike {@link CommandArgumentSpec#parseInput(String)}, this does not check any additional requirements.
|
||||
*
|
||||
* @throws IllegalArgumentException if string invalid
|
||||
* @see CommandArgumentSpec#parseInput(String)
|
||||
* @see CommandArgumentSpecBuilder#withRequirement(Predicate, String)
|
||||
*/
|
||||
V parseInputUnchecked(String input) throws IllegalArgumentException;
|
||||
Result<V, String> parseInputUnchecked(String input);
|
||||
|
||||
/**
|
||||
* @see CommandArgumentSpec#suggestionProvider()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package de.kentoj.kencommandapi.api.argument;
|
||||
|
||||
import com.leakyabstractions.result.api.Result;
|
||||
import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
||||
import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
|
@ -18,9 +19,9 @@ public sealed interface CommandArgumentSpec<T, V> permits CommandArgumentSpecImp
|
|||
ArgumentType<T, V> type();
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if string invalid or if a requirement is not met
|
||||
* @return Result of either the parsed value or a message describing the error
|
||||
*/
|
||||
V parseInput(String rawInput);
|
||||
Result<V, String> parseInput(String rawInput);
|
||||
|
||||
@Nullable Function<CommandContext<T>, V> defaultValueProvider();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package de.kentoj.kencommandapi.api.argument;
|
||||
|
||||
import com.leakyabstractions.result.api.Result;
|
||||
import com.leakyabstractions.result.core.Results;
|
||||
import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
||||
import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
|
@ -17,13 +19,15 @@ public record CommandArgumentSpecImpl<T, V>(
|
|||
) implements CommandArgumentSpec<T, V> {
|
||||
|
||||
@Override
|
||||
public V parseInput(String rawInput) {
|
||||
var parsed = type.parseInputUnchecked(rawInput);
|
||||
public Result<V, String> parseInput(String rawInput) {
|
||||
var parseResult = type.parseInputUnchecked(rawInput);
|
||||
if (parseResult.hasFailure())
|
||||
return parseResult.mapSuccess(_ -> null);
|
||||
for (var requirement : requirements) {
|
||||
if (!requirement.predicate().test(parsed))
|
||||
throw new IllegalArgumentException(requirement.message());
|
||||
if (!requirement.predicate().test(parseResult.getSuccess().orElseThrow()))
|
||||
return Results.failure(requirement.message());
|
||||
}
|
||||
return parsed;
|
||||
return parseResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package de.kentoj.kencommandapi.api.argument.types;
|
||||
|
||||
import com.leakyabstractions.result.api.Result;
|
||||
import com.leakyabstractions.result.core.Results;
|
||||
import de.kentoj.kencommandapi.api.argument.ArgumentType;
|
||||
import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BooleanArgumentType<T> implements ArgumentType<T, Boolean> {
|
||||
|
||||
@Override
|
||||
public Result<Boolean, String> parseInputUnchecked(String input) {
|
||||
return Results.ofCallable(() -> Boolean.parseBoolean(input))
|
||||
.mapFailure(_ -> "Must be true or false.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SuggestionProvider<T> getDefaultSuggestionProvider() {
|
||||
return (_, _) -> List.of("true", "false");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package de.kentoj.kencommandapi.api.argument.types;
|
||||
|
||||
import com.leakyabstractions.result.api.Result;
|
||||
import com.leakyabstractions.result.core.Results;
|
||||
import de.kentoj.kencommandapi.api.argument.ArgumentType;
|
||||
import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
|
||||
|
||||
public class DoubleArgumentType<T> implements ArgumentType<T, Double> {
|
||||
@Override
|
||||
public Result<Double, String> parseInputUnchecked(String input) {
|
||||
return Results.ofCallable(() -> Double.parseDouble(input))
|
||||
.mapFailure(_ -> "Not a valid double.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SuggestionProvider<T> getDefaultSuggestionProvider() {
|
||||
return SuggestionProvider.none();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,16 @@
|
|||
package de.kentoj.kencommandapi.api.argument.types;
|
||||
|
||||
import com.leakyabstractions.result.api.Result;
|
||||
import com.leakyabstractions.result.core.Results;
|
||||
import de.kentoj.kencommandapi.api.argument.ArgumentType;
|
||||
import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
|
||||
|
||||
public class IntegerArgumentType<T> implements ArgumentType<T, Integer> {
|
||||
|
||||
@Override
|
||||
public Integer parseInputUnchecked(String string) {
|
||||
return Integer.parseInt(string);
|
||||
public Result<Integer, String> parseInputUnchecked(String string) {
|
||||
return Results.ofCallable(() -> Integer.parseInt(string))
|
||||
.mapFailure(_ -> "Not a number.");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package de.kentoj.kencommandapi.api.argument.types;
|
||||
|
||||
import com.leakyabstractions.result.api.Result;
|
||||
import com.leakyabstractions.result.core.Results;
|
||||
import de.kentoj.kencommandapi.api.argument.ArgumentType;
|
||||
import de.kentoj.kencommandapi.api.suggestion.SuggestionProvider;
|
||||
|
||||
|
|
@ -10,8 +12,8 @@ public record StringArgumentType<T>(boolean greedy) implements ArgumentType<T, S
|
|||
}
|
||||
|
||||
@Override
|
||||
public String parseInputUnchecked(String string) {
|
||||
return string;
|
||||
public Result<String, String> parseInputUnchecked(String string) {
|
||||
return Results.success(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import java.util.List;
|
|||
|
||||
class NoSuggestionProvider<T> implements SuggestionProvider<T> {
|
||||
@Override
|
||||
public List<String> suggest(T __) {
|
||||
public List<String> suggest(T __, String ___) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,5 +11,5 @@ public interface SuggestionProvider<T> {
|
|||
/**
|
||||
* @return an unfiltered(no permission checks or filtering by input) list of strings that may be used for the argument/literal
|
||||
*/
|
||||
List<String> suggest(T sender);
|
||||
List<String> suggest(T sender, String input);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
package de.kentoj.kencommandapi.internal;
|
||||
|
||||
import de.kentoj.kencommandapi.api.argument.CommandArgument;
|
||||
import de.kentoj.kencommandapi.api.argument.CommandArgumentImpl;
|
||||
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
|
||||
import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class CommandContextBuilder<T> {
|
||||
private final T sender;
|
||||
private final Map<String, CommandArgument<T, ?>> parsedArgument;
|
||||
|
||||
public CommandContextBuilder(T sender, Map<String, CommandArgument<T, ?>> parsedArgument) {
|
||||
this.sender = sender;
|
||||
this.parsedArgument = parsedArgument;
|
||||
}
|
||||
|
||||
public <S> void addParsed(CommandArgumentSpec<T, S> arg, S value) {
|
||||
parsedArgument.put(arg.id(), new CommandArgumentImpl<>(value, arg));
|
||||
}
|
||||
|
||||
public CommandContext<T> build() {
|
||||
return new CommandContextImpl<>(sender, parsedArgument);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,11 +5,10 @@ import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
public record CommandContextImpl<T>(
|
||||
record CommandContextImpl<T>(
|
||||
T sender,
|
||||
Map<String, CommandArgument<T, ?>> parsedArguments
|
||||
) implements CommandContext<T> {
|
||||
|
||||
@Override
|
||||
public <V> CommandArgument<T, V> getParsedArgument(String id) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
package de.kentoj.kencommandapi.internal;
|
||||
|
||||
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
|
||||
import de.kentoj.kencommandapi.api.literal.Literal;
|
||||
import de.kentoj.kencommandapi.api.literal.RootLiteral;
|
||||
import de.kentoj.kencommandapi.api.platform.HasPermissionMethod;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public class NodeTreeWalker<T> {
|
||||
|
||||
private final HasPermissionMethod<T> hasPermission;
|
||||
|
||||
public NodeTreeWalker(HasPermissionMethod<T> hasPermission) {
|
||||
this.hasPermission = hasPermission;
|
||||
}
|
||||
|
||||
public WalkResult<T> walk(
|
||||
RootLiteral<T> rootLiteral,
|
||||
T sender,
|
||||
Iterator<String> iter
|
||||
) {
|
||||
var cursor = new Cursor(iter);
|
||||
var ctxBuilder = new CommandContextBuilder<>(sender, new HashMap<>());
|
||||
Literal<T> literal = rootLiteral;
|
||||
String token;
|
||||
|
||||
while (true) {
|
||||
for (var arg : literal.arguments()) {
|
||||
@SuppressWarnings("unchecked")
|
||||
var res = walkArgument(cursor, literal, ctxBuilder, (CommandArgumentSpec<T, Object>) arg);
|
||||
if (res != null)
|
||||
return res;
|
||||
}
|
||||
|
||||
token = cursor.nextToken();
|
||||
if (token == null)
|
||||
break;
|
||||
var nextLiteral = literal.getLiteral(token);
|
||||
if (nextLiteral == null)
|
||||
return new WalkResult.LiteralUnknown<>(ctxBuilder.build(), literal, token);
|
||||
if (nextLiteral.permission() != null && hasPermission.test(sender, nextLiteral.permission()))
|
||||
return new WalkResult.LiteralUnknown<>(ctxBuilder.build(), literal, token);
|
||||
literal = nextLiteral;
|
||||
}
|
||||
|
||||
if (!literal.literals().isEmpty()) {
|
||||
var isRequired = literal.executor() == null;
|
||||
return new WalkResult.LiteralExpected<>(ctxBuilder.build(), literal, isRequired);
|
||||
}
|
||||
|
||||
return new WalkResult.Complete<>(ctxBuilder.build(), literal);
|
||||
}
|
||||
|
||||
private @Nullable WalkResult<T> walkArgument(
|
||||
Cursor cursor,
|
||||
Literal<T> literal,
|
||||
CommandContextBuilder<T> ctxBuilder,
|
||||
CommandArgumentSpec<T, Object> arg
|
||||
) {
|
||||
var rawInput = arg.type().greedy() ? cursor.remainingTokens() : cursor.nextToken();
|
||||
if (rawInput == null) {
|
||||
if (arg.defaultValueProvider() == null)
|
||||
return new WalkResult.ArgumentExpected<>(ctxBuilder.build(), literal, false, arg);
|
||||
var value = requireNonNull(arg.defaultValueProvider()).apply(ctxBuilder.build());
|
||||
ctxBuilder.addParsed(arg, value);
|
||||
return null;
|
||||
}
|
||||
|
||||
var parseResult = arg.parseInput(rawInput);
|
||||
if (parseResult.hasFailure())
|
||||
return new WalkResult.ArgumentIllegal<>(ctxBuilder.build(), literal,
|
||||
false, arg, rawInput, parseResult.getFailure().orElseThrow());
|
||||
ctxBuilder.addParsed(arg, parseResult.getSuccess().orElseThrow());
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class Cursor {
|
||||
private final Iterator<String> tokenIter;
|
||||
|
||||
public Cursor(Iterator<String> tokenIter) {
|
||||
this.tokenIter = tokenIter;
|
||||
}
|
||||
|
||||
public @Nullable String nextToken() {
|
||||
if (!tokenIter.hasNext())
|
||||
return null;
|
||||
return tokenIter.next();
|
||||
}
|
||||
|
||||
public @Nullable String remainingTokens() {
|
||||
var builder = new StringBuilder();
|
||||
tokenIter.forEachRemaining(s -> builder.append(' ').append(s));
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package de.kentoj.kencommandapi.internal;
|
||||
|
||||
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
|
||||
import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
||||
import de.kentoj.kencommandapi.api.literal.Literal;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public sealed interface WalkResult<T> {
|
||||
CommandContext<T> ctx();
|
||||
|
||||
Literal<T> lastLiteral();
|
||||
|
||||
boolean isValidUsage();
|
||||
|
||||
@Nullable String message();
|
||||
|
||||
record ArgumentIllegal<T>(
|
||||
CommandContext<T> ctx,
|
||||
Literal<T> lastLiteral,
|
||||
boolean isValidUsage,
|
||||
CommandArgumentSpec<T, ?> arg,
|
||||
String input,
|
||||
String message
|
||||
) implements WalkResult<T> {
|
||||
}
|
||||
|
||||
record ArgumentExpected<T>(
|
||||
CommandContext<T> ctx,
|
||||
Literal<T> lastLiteral,
|
||||
boolean isValidUsage,
|
||||
CommandArgumentSpec<T, ?> arg
|
||||
) implements WalkResult<T> {
|
||||
@Override
|
||||
public String message() {
|
||||
return "Argument expected.";
|
||||
}
|
||||
}
|
||||
|
||||
record LiteralExpected<T>(
|
||||
CommandContext<T> ctx,
|
||||
Literal<T> lastLiteral,
|
||||
boolean isValidUsage
|
||||
) implements WalkResult<T> {
|
||||
@Override
|
||||
public String message() {
|
||||
return "Literal expected.";
|
||||
}
|
||||
}
|
||||
|
||||
record LiteralUnknown<T>(
|
||||
CommandContext<T> ctx,
|
||||
Literal<T> lastLiteral,
|
||||
String input
|
||||
) implements WalkResult<T> {
|
||||
@Override
|
||||
public boolean isValidUsage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String message() {
|
||||
return "Unknown literal \"" + input + "\".";
|
||||
}
|
||||
}
|
||||
|
||||
record Complete<T>(
|
||||
CommandContext<T> ctx,
|
||||
Literal<T> lastLiteral
|
||||
) implements WalkResult<T> {
|
||||
@Override
|
||||
public boolean isValidUsage() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String message() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
package de.kentoj.kencommandapi.internal.parser;
|
||||
|
||||
import de.kentoj.kencommandapi.api.literal.Literal;
|
||||
import de.kentoj.kencommandapi.api.platform.HasPermissionMethod;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
public final class CommandParseHelper<T> {
|
||||
private final HasPermissionMethod<T> hasPermission;
|
||||
|
||||
public CommandParseHelper(HasPermissionMethod<T> hasPermission) {
|
||||
this.hasPermission = hasPermission;
|
||||
}
|
||||
|
||||
public boolean hasPermission(T sender, Literal<T> literal) {
|
||||
if (literal.permission() == null) return true;
|
||||
return hasPermission.test(sender, literal.permission());
|
||||
}
|
||||
|
||||
public String parseInput(Iterator<String> tokenIter, boolean isGreedy) {
|
||||
if (!isGreedy)
|
||||
return tokenIter.next();
|
||||
var builder = new StringBuilder();
|
||||
tokenIter.forEachRemaining(tok -> builder.append(" ").append(tok));
|
||||
builder.deleteCharAt(0);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
package de.kentoj.kencommandapi.internal.parser;
|
||||
|
||||
import com.leakyabstractions.result.api.Result;
|
||||
import com.leakyabstractions.result.core.Results;
|
||||
import de.kentoj.kencommandapi.api.argument.CommandArgument;
|
||||
import de.kentoj.kencommandapi.api.argument.CommandArgumentImpl;
|
||||
import de.kentoj.kencommandapi.api.argument.CommandArgumentSpec;
|
||||
import de.kentoj.kencommandapi.api.invocation.CommandContext;
|
||||
import de.kentoj.kencommandapi.api.invocation.CommandExecutor;
|
||||
import de.kentoj.kencommandapi.api.literal.Literal;
|
||||
import de.kentoj.kencommandapi.internal.CommandContextImpl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public class InvocationCommandParser<T> {
|
||||
|
||||
private final CommandParseHelper<T> helper;
|
||||
|
||||
public InvocationCommandParser(CommandParseHelper<T> helper) {
|
||||
this.helper = helper;
|
||||
}
|
||||
|
||||
public Result<ExecutionData, String> parse(
|
||||
Literal<T> rootLiteral,
|
||||
T sender,
|
||||
Iterator<String> tokenIter
|
||||
) {
|
||||
Literal<T> currentLiteral = rootLiteral;
|
||||
var ctx = new CommandContextImpl<>(sender, new HashMap<>());
|
||||
|
||||
while (true) {
|
||||
{
|
||||
var result = processArguments(currentLiteral, ctx, tokenIter)
|
||||
.ifSuccess(ctx.parsedArguments()::putAll);
|
||||
if (result.hasFailure())
|
||||
return result.mapSuccess(_ -> null);
|
||||
}
|
||||
|
||||
if (!tokenIter.hasNext()) {
|
||||
if (currentLiteral.executor() == null)
|
||||
return Results.failure("Literal expected");
|
||||
break;
|
||||
}
|
||||
|
||||
var literalName = tokenIter.next();
|
||||
var nextLiteral = currentLiteral.getLiteral(literalName);
|
||||
var isIncompleteOrUnknown = nextLiteral == null || !helper.hasPermission(sender, nextLiteral);
|
||||
if (isIncompleteOrUnknown)
|
||||
return Results.failure("Unknown literal: " + literalName);
|
||||
currentLiteral = nextLiteral;
|
||||
}
|
||||
|
||||
return Results.success(new ExecutionData(currentLiteral.executor(), ctx));
|
||||
}
|
||||
|
||||
private Result<Map<String, CommandArgument<T, ?>>, String> processArguments(
|
||||
Literal<T> literal,
|
||||
CommandContext<T> ctx,
|
||||
Iterator<String> tokenIter
|
||||
) {
|
||||
Map<String, CommandArgument<T, ?>> parsedArguments = new HashMap<>();
|
||||
|
||||
for (var _argument : literal.arguments()) {
|
||||
@SuppressWarnings("unchecked")
|
||||
var argument = ((CommandArgumentSpec<T, Object>) _argument);
|
||||
|
||||
Object value;
|
||||
if (!tokenIter.hasNext()) {
|
||||
if (argument.defaultValueProvider() == null)
|
||||
return Results.failure("Argument expected: " + argument.id());
|
||||
value = requireNonNull(argument.defaultValueProvider()).apply(ctx);
|
||||
} else {
|
||||
var input = helper.parseInput(tokenIter, argument.type().greedy());
|
||||
try {
|
||||
value = argument.parseInput(input);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return Results.failure("Illegal argument '" + input + "' for " + argument.id() + ": " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
parsedArguments.put(argument.id(), new CommandArgumentImpl<>(value, argument));
|
||||
}
|
||||
|
||||
return Results.success(parsedArguments);
|
||||
}
|
||||
|
||||
public class ExecutionData {
|
||||
private final CommandExecutor<T> executor;
|
||||
private final CommandContext<T> context;
|
||||
|
||||
public ExecutionData(CommandExecutor<T> executor, CommandContext<T> context) {
|
||||
this.executor = executor;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public CommandExecutor<T> executor() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
public CommandContext<T> context() {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
package de.kentoj.kencommandapi.internal.parser;
|
||||
|
||||
import de.kentoj.kencommandapi.api.literal.Literal;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class SuggestionCommandParser<T> {
|
||||
|
||||
private final CommandParseHelper<T> helper;
|
||||
|
||||
public SuggestionCommandParser(CommandParseHelper<T> helper) {
|
||||
this.helper = helper;
|
||||
}
|
||||
|
||||
public List<String> suggest(
|
||||
Literal<T> rootLiteral,
|
||||
T sender,
|
||||
Iterator<String> tokenIter,
|
||||
boolean suggestNext
|
||||
) {
|
||||
Literal<T> currentLiteral = rootLiteral;
|
||||
Literal<T> previousLiteral = null;
|
||||
|
||||
while (true) {
|
||||
var argumentSuggestions = processArguments(currentLiteral, tokenIter, sender, suggestNext);
|
||||
if (argumentSuggestions != null)
|
||||
return argumentSuggestions;
|
||||
|
||||
if (!tokenIter.hasNext()) {
|
||||
if (suggestNext || previousLiteral == null)
|
||||
return suggestions(currentLiteral, sender).toList();
|
||||
return suggestions(previousLiteral, sender).toList();
|
||||
}
|
||||
|
||||
var input = tokenIter.next();
|
||||
var nextLiteral = currentLiteral.getLiteral(input);
|
||||
var isIncompleteOrUnknown = nextLiteral == null || !helper.hasPermission(sender, nextLiteral);
|
||||
if (isIncompleteOrUnknown) {
|
||||
var finalInput = input.toLowerCase();
|
||||
return suggestions(currentLiteral, sender)
|
||||
.filter(name -> name.toLowerCase().startsWith(finalInput))
|
||||
.toList();
|
||||
}
|
||||
|
||||
previousLiteral = currentLiteral;
|
||||
currentLiteral = nextLiteral;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable List<String> processArguments(
|
||||
Literal<T> literal,
|
||||
Iterator<String> tokenIter,
|
||||
T sender,
|
||||
boolean suggestNext
|
||||
) {
|
||||
var argsIter = literal.arguments().iterator();
|
||||
|
||||
/*
|
||||
for each argument:
|
||||
if no next token: suggest current
|
||||
parse token
|
||||
if not last token: skip
|
||||
if nextToken:
|
||||
if has next argument: suggest next argument
|
||||
else: return null
|
||||
return current argument's suggestions filtered
|
||||
|
||||
*/
|
||||
|
||||
while (argsIter.hasNext()) {
|
||||
var argument = argsIter.next();
|
||||
if (!tokenIter.hasNext())
|
||||
return argument.suggestionProvider().suggest(sender);
|
||||
var input = helper.parseInput(tokenIter, argument.type().greedy()).toLowerCase();
|
||||
if (tokenIter.hasNext())
|
||||
continue;
|
||||
if (suggestNext) {
|
||||
if (argsIter.hasNext())
|
||||
return argsIter.next().suggestionProvider().suggest(sender);
|
||||
return null;
|
||||
}
|
||||
return argument.suggestionProvider().suggest(sender).stream()
|
||||
.filter(suggestion -> suggestion.toLowerCase().startsWith(input))
|
||||
.toList();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Stream<String> suggestions(Literal<T> literal, T sender) {
|
||||
return literal.literals().stream()
|
||||
.filter(lit -> helper.hasPermission(sender, lit))
|
||||
.map(Literal::names)
|
||||
.flatMap(Arrays::stream);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue