initial commit
Some checks failed
Build and Deploy artifact / build (push) Failing after 11s

This commit is contained in:
kento2 2026-08-25 15:37:39 +02:00
commit 7c322707ba
51 changed files with 1488 additions and 0 deletions

View file

@ -0,0 +1,67 @@
package site.lab0x13.scrow.commands;
import site.lab0x13.scrow.commands.model.CommandContext;
import site.lab0x13.scrow.commands.model.literal.RootLiteral;
import site.lab0x13.scrow.commands.parser.NodeTreeWalker;
import site.lab0x13.scrow.commands.parser.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<C extends CommandContext<C>> {
private final NodeTreeWalker<C> nodeTreeWalker;
private final PlatformAdapter<C> platformAdapter;
public CommandHandler(PlatformAdapter<C> platformAdapter) {
this.nodeTreeWalker = new NodeTreeWalker<>(platformAdapter);
this.platformAdapter = platformAdapter;
}
public void invoke(RootLiteral<C> rootLiteral, C ctx, Stream<String> args) {
var walkResult = nodeTreeWalker.walk(ctx, rootLiteral, args.iterator());
if (!walkResult.isValidUsage()) {
platformAdapter.sendMessage(ctx, rootLiteral.style().err(walkResult.message()));
return;
}
requireNonNull(walkResult.lastLiteral().executor())
.execute(walkResult.ctx())
.exceptionally(ex -> {
platformAdapter.sendMessage(ctx, rootLiteral.style().exception(ex));
return null;
});
}
public List<String> suggest(RootLiteral<C> rootLiteral, C ctx, String[] args, boolean trailingSpace) {
var walkResult = nodeTreeWalker.walk(ctx, rootLiteral, Arrays.stream(args).iterator());
Stream<String> stream;
String input = null;
switch (walkResult) {
case WalkResult.LiteralUnknown<C> res -> {
stream = res.lastLiteral().subLiteralNames().stream();
input = res.input();
}
case WalkResult.ArgumentIllegal<C> res -> {
stream = res.arg().suggestionProvider().suggest(ctx, res.input()).stream();
input = res.input();
}
case WalkResult.LiteralExpected<C> res when trailingSpace ->
stream = res.lastLiteral().subLiteralNames().stream();
case WalkResult.ArgumentExpected<C> res when trailingSpace ->
stream = res.arg().suggestionProvider().suggest(ctx, "").stream();
default -> {
return Collections.emptyList();
}
}
if (input == null) return stream.toList();
final var finalInput = input;
return stream
.filter(s -> s.startsWith(finalInput))
.toList();
}
}

View file

@ -0,0 +1,12 @@
package site.lab0x13.scrow.commands;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.commands.model.CommandContext;
import site.lab0x13.scrow.commands.model.literal.RootLiteral;
public interface IScrowCommands<C extends CommandContext<C>> {
void register(RootLiteral<C> rootLiteral);
@Nullable RootLiteral<C> getRootLiteral(String name);
}

View file

@ -0,0 +1,10 @@
package site.lab0x13.scrow.commands;
import net.kyori.adventure.text.Component;
public interface PlatformAdapter<C> {
boolean checkPermission(C ctx, String permission);
void sendMessage(C ctx, Component message);
}

View file

@ -0,0 +1,23 @@
package site.lab0x13.scrow.commands.model;
import site.lab0x13.scrow.commands.model.argument.Argument;
import site.lab0x13.scrow.commands.model.argument.ParsedArgument;
import java.util.HashMap;
import java.util.Map;
public abstract class AbstractCommandContext<C extends CommandContext<C>> implements CommandContext<C> {
private final Map<String, ParsedArgument<?, ?>> parsedArguments = new HashMap<>();
@SuppressWarnings("unchecked")
@Override
public <V> ParsedArgument<C, V> getArgument(String id) {
return (ParsedArgument<C, V>) parsedArguments.get(id);
}
@Override
public <V> void addParsedArgument(Argument<?, V> argument, String rawValue, V value) {
parsedArguments.put(argument.id(), new ParsedArgument<>(argument, rawValue, value));
}
}

View file

@ -0,0 +1,27 @@
package site.lab0x13.scrow.commands.model;
import site.lab0x13.scrow.commands.model.argument.Argument;
import site.lab0x13.scrow.commands.model.argument.ParsedArgument;
/**
* @param <C> argument of this
*/
public interface CommandContext<C extends CommandContext<C>> {
<V> ParsedArgument<C, V> getArgument(String id);
default <V> V getArg(String id) {
ParsedArgument<C, V> arg = getArgument(id);
return arg.value();
}
default <V> V getArg(Argument<C, V> arg) {
return getArg(arg.id());
}
default <V> ParsedArgument<C, V> getArgument(Argument<C, V> arg) {
return getArgument(arg.id());
}
<V> void addParsedArgument(Argument<?, V> argument, String rawValue, V value);
}

View file

@ -0,0 +1,22 @@
package site.lab0x13.scrow.commands.model;
import java.util.concurrent.CompletableFuture;
/**
* @param <C> argument of command context
*/
public interface CommandExecutor<C extends CommandContext<C>> {
CompletableFuture<?> execute(C ctx);
interface Sync<C extends CommandContext<C>> extends CommandExecutor<C> {
void executeSync(C ctx);
@Override
default CompletableFuture<?> execute(C ctx) {
executeSync(ctx);
return CompletableFuture.completedFuture(Void.TYPE);
}
}
}

View file

@ -0,0 +1,6 @@
package site.lab0x13.scrow.commands.model;
public interface Node<C extends CommandContext<C>> {
SuggestionProvider<C> suggestionProvider();
}

View file

@ -0,0 +1,16 @@
package site.lab0x13.scrow.commands.model;
import java.util.Collections;
import java.util.List;
/**
* @param <C> argument of command context
*/
public interface SuggestionProvider<C extends CommandContext<C>> {
static <C extends CommandContext<C>> SuggestionProvider<C> none() {
return (_, _) -> Collections.emptyList();
}
List<String> suggest(C ctx, String input);
}

View file

@ -0,0 +1,41 @@
package site.lab0x13.scrow.commands.model.argument;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import site.lab0x13.scrow.commands.model.CommandContext;
import site.lab0x13.scrow.commands.model.Node;
import site.lab0x13.scrow.commands.model.SuggestionProvider;
import java.util.List;
/**
* @param <C> argument of command context
* @param <V> argument of argument value
*/
public record Argument<C extends CommandContext<C>, V>(
String id,
ArgumentType<C, V> type,
List<ArgumentRequirement<C, V>> requirements,
SuggestionProvider<C> suggestionProvider,
DefaultValueProvider<C, V> defaultValueProvider
) implements Node<C> {
public static <C extends CommandContext<C>, V> ArgumentBuilder<C, V> builder(String id, ArgumentType<C, V> type) {
return new ArgumentBuilder<>(id, type);
}
public Result<ParsedArgument<C, V>, String> parse(C ctx, String rawInput) {
var parseResult = type.parseInput(rawInput);
if (parseResult.hasFailure())
return parseResult.mapSuccess(_ -> null);
var value = parseResult.getSuccess().orElseThrow();
for (var requirement : requirements) {
var isAllowed = requirement.predicate().check(ctx, value);
if (!isAllowed)
return Results.failure(requirement.errorMessage());
}
return Results.success(new ParsedArgument<>(this, rawInput, value));
}
}

View file

@ -0,0 +1,55 @@
package site.lab0x13.scrow.commands.model.argument;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.commands.model.CommandContext;
import site.lab0x13.scrow.commands.model.SuggestionProvider;
import java.util.ArrayList;
import java.util.List;
/**
* @param <C> argument of command context
* @param <V> argument of argument value
*/
public final class ArgumentBuilder<C extends CommandContext<C>, V> {
private final String id;
private final ArgumentType<C, V> type;
private DefaultValueProvider<C, V> defaultValueProvider = DefaultValueProvider.none();
private @Nullable SuggestionProvider<C> suggestionProvider = null;
private final List<ArgumentRequirement<C, V>> requirements = new ArrayList<>();
ArgumentBuilder(String id, ArgumentType<C, V> type) {
this.id = id;
this.type = type;
}
public ArgumentBuilder<C, V> withRequirement(ArgumentPredicate<C, V> predicate, String errorMessage) {
this.requirements.add(new ArgumentRequirement<>(predicate, errorMessage));
return this;
}
public ArgumentBuilder<C, V> withDefaultValue(@Nullable DefaultValueProvider<C, V> defaultValue) {
this.defaultValueProvider = defaultValue;
return this;
}
/**
* @param suggestionProvider if null, use the argument's suggestion provider
*/
public ArgumentBuilder<C, V> withSuggestionProvider(@Nullable SuggestionProvider<C> suggestionProvider) {
this.suggestionProvider = suggestionProvider;
return this;
}
public Argument<C, V> build() {
return new Argument<>(
id,
type,
new ArrayList<>(requirements),
suggestionProvider != null ? suggestionProvider : type.defaultSuggestionProvider(),
defaultValueProvider
);
}
}

View file

@ -0,0 +1,12 @@
package site.lab0x13.scrow.commands.model.argument;
import site.lab0x13.scrow.commands.model.CommandContext;
/**
* @param <C> argument of command context
* @param <V> argument of argument value
*/
@FunctionalInterface
public interface ArgumentPredicate<C extends CommandContext<C>, V> {
boolean check(C ctx, V value);
}

View file

@ -0,0 +1,13 @@
package site.lab0x13.scrow.commands.model.argument;
import site.lab0x13.scrow.commands.model.CommandContext;
/**
* @param <C> argument of command context
* @param <V> argument of argument value
*/
public record ArgumentRequirement<C extends CommandContext<C>, V>(
ArgumentPredicate<C, V> predicate,
String errorMessage
) {
}

View file

@ -0,0 +1,28 @@
package site.lab0x13.scrow.commands.model.argument;
import com.leakyabstractions.result.api.Result;
import site.lab0x13.scrow.commands.model.CommandContext;
import site.lab0x13.scrow.commands.model.SuggestionProvider;
/**
* @param <C> argument of command context
* @param <V> argument of parsed argument value
*/
public interface ArgumentType<C extends CommandContext<C>, V> {
Result<V, String> parseInput(String input);
/**
* @return default suggestion provider for the argument, if not overwritten by the argument
*/
SuggestionProvider<C> 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 greedy() {
return false;
}
}

View file

@ -0,0 +1,14 @@
package site.lab0x13.scrow.commands.model.argument;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.commands.model.CommandContext;
@FunctionalInterface
public interface DefaultValueProvider<C extends CommandContext<C>, V> {
static <V, C extends CommandContext<C>> DefaultValueProvider<C, V> none() {
return _ -> null;
}
@Nullable V getDefault(C ctx);
}

View file

@ -0,0 +1,14 @@
package site.lab0x13.scrow.commands.model.argument;
import site.lab0x13.scrow.commands.model.CommandContext;
/**
* @param <C> argument of command context
* @param <V> argument of argument value
*/
public record ParsedArgument<C extends CommandContext<C>, V>(
Argument<C, V> argument,
String rawValue,
V value
) {
}

View file

@ -0,0 +1,23 @@
package site.lab0x13.scrow.commands.model.argument.types;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import site.lab0x13.scrow.commands.model.CommandContext;
import site.lab0x13.scrow.commands.model.SuggestionProvider;
import site.lab0x13.scrow.commands.model.argument.ArgumentType;
import java.util.List;
public class BooleanArgumentType<C extends CommandContext<C>> implements ArgumentType<C, Boolean> {
@Override
public Result<Boolean, String> parseInput(String input) {
return Results.ofCallable(() -> Boolean.parseBoolean(input))
.mapFailure(_ -> "Must be true or false.");
}
@Override
public SuggestionProvider<C> defaultSuggestionProvider() {
return (_, _) -> List.of("true", "false");
}
}

View file

@ -0,0 +1,20 @@
package site.lab0x13.scrow.commands.model.argument.types;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import site.lab0x13.scrow.commands.model.CommandContext;
import site.lab0x13.scrow.commands.model.SuggestionProvider;
import site.lab0x13.scrow.commands.model.argument.ArgumentType;
public class DoubleArgumentType<C extends CommandContext<C>> implements ArgumentType<C, Double> {
@Override
public Result<Double, String> parseInput(String input) {
return Results.ofCallable(() -> Double.parseDouble(input))
.mapFailure(_ -> "Not a valid double.");
}
@Override
public SuggestionProvider<C> defaultSuggestionProvider() {
return SuggestionProvider.none();
}
}

View file

@ -0,0 +1,21 @@
package site.lab0x13.scrow.commands.model.argument.types;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import site.lab0x13.scrow.commands.model.CommandContext;
import site.lab0x13.scrow.commands.model.SuggestionProvider;
import site.lab0x13.scrow.commands.model.argument.ArgumentType;
public class IntegerArgumentType<C extends CommandContext<C>> implements ArgumentType<C, Integer> {
@Override
public Result<Integer, String> parseInput(String string) {
return Results.ofCallable(() -> Integer.parseInt(string))
.mapFailure(_ -> "Not a number.");
}
@Override
public SuggestionProvider<C> defaultSuggestionProvider() {
return SuggestionProvider.none();
}
}

View file

@ -0,0 +1,25 @@
package site.lab0x13.scrow.commands.model.argument.types;
import com.leakyabstractions.result.api.Result;
import com.leakyabstractions.result.core.Results;
import site.lab0x13.scrow.commands.model.CommandContext;
import site.lab0x13.scrow.commands.model.SuggestionProvider;
import site.lab0x13.scrow.commands.model.argument.ArgumentType;
public record StringArgumentType<C extends CommandContext<C>>(boolean greedy)
implements ArgumentType<C, String> {
public StringArgumentType() {
this(false);
}
@Override
public Result<String, String> parseInput(String string) {
return Results.success(string);
}
@Override
public SuggestionProvider<C> defaultSuggestionProvider() {
return SuggestionProvider.none();
}
}

View file

@ -0,0 +1,46 @@
package site.lab0x13.scrow.commands.model.literal;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.commands.model.CommandContext;
import site.lab0x13.scrow.commands.model.CommandExecutor;
import site.lab0x13.scrow.commands.model.argument.Argument;
import java.util.List;
import static com.google.common.base.Preconditions.checkArgument;
public abstract class DynamicLiteral<C extends CommandContext<C>> implements Literal<C>{
private final List<String> names;
private final @Nullable CommandExecutor<C> executor;
private final List<Argument<C, ?>> arguments;
private final String permission;
public DynamicLiteral(List<String> names, @Nullable CommandExecutor<C> executor, List<Argument<C, ?>> arguments, String permission) {
checkArgument(!names.isEmpty(), "at least one name expected");
this.names = names;
this.executor = executor;
this.arguments = arguments;
this.permission = permission;
}
@Override
public List<String> names() {
return names;
}
@Override
public @Nullable CommandExecutor<C> executor() {
return executor;
}
@Override
public List<Argument<C, ?>> arguments() {
return arguments;
}
@Override
public String permission() {
return permission;
}
}

View file

@ -0,0 +1,55 @@
package site.lab0x13.scrow.commands.model.literal;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.commands.model.CommandContext;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
public final class DynamicLiteralBuilder<C extends CommandContext<C>> extends LiteralBuilder<C, DynamicLiteralBuilder<C>> {
private Supplier<List<String>> subLiteralNamesSupplier = Collections::emptyList;
private Function<String, @Nullable Literal<C>> subLiteralProvider = _ -> null;
DynamicLiteralBuilder(String... names) {
super(names);
}
@Override
protected DynamicLiteralBuilder<C> getThis() {
return this;
}
@Override
public Literal<C> build() {
return new DynamicLiteral<>(
Arrays.asList(this.names),
this.executor,
this.arguments,
this.permission
) {
@Override
public @Nullable Literal<C> getSubLiteral(String name) {
return subLiteralProvider.apply(name);
}
@Override
public List<String> subLiteralNames() {
return subLiteralNamesSupplier.get();
}
};
}
public DynamicLiteralBuilder<C> withSubLiteralNames(Supplier<List<String>> names) {
subLiteralNamesSupplier = names;
return this;
}
public DynamicLiteralBuilder<C> withSubLiteralProvider(Function<String, @Nullable Literal<C>> literalProvider) {
this.subLiteralProvider = literalProvider;
return this;
}
}

View file

@ -0,0 +1,44 @@
package site.lab0x13.scrow.commands.model.literal;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.commands.model.CommandContext;
import site.lab0x13.scrow.commands.model.CommandExecutor;
import site.lab0x13.scrow.commands.model.Node;
import site.lab0x13.scrow.commands.model.SuggestionProvider;
import site.lab0x13.scrow.commands.model.argument.Argument;
import java.util.List;
/**
* @param <C> argument of the command context
*/
public interface Literal<C extends CommandContext<C>> extends Node<C> {
static <C extends CommandContext<C>> StaticLiteralBuilder<C> builder(String... names) {
return new StaticLiteralBuilder<>(names);
}
static <C extends CommandContext<C>> DynamicLiteralBuilder<C> dynamic(String... names) {
return new DynamicLiteralBuilder<>(names);
}
List<String> names();
@Nullable CommandExecutor<C> executor();
@Nullable Literal<C> getSubLiteral(String name);
/**
* @return aliases/names of literals
*/
List<String> subLiteralNames();
List<Argument<C, ?>> arguments();
String permission();
@Override
default SuggestionProvider<C> suggestionProvider() {
return (_, _) -> subLiteralNames();
}
}

View file

@ -0,0 +1,53 @@
package site.lab0x13.scrow.commands.model.literal;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.commands.model.CommandContext;
import site.lab0x13.scrow.commands.model.CommandExecutor;
import site.lab0x13.scrow.commands.model.argument.Argument;
import java.util.ArrayList;
import java.util.List;
/**
* @param <C> argument of command context
* @param <B> argument of this/builder
*/
public abstract class LiteralBuilder<C extends CommandContext<C>,B extends LiteralBuilder<C, B>> {
protected final String[] names;
protected @Nullable CommandExecutor<C> executor;
protected @Nullable String permission;
protected final List<Argument<C, ?>> arguments = new ArrayList<>();
protected abstract B getThis();
public abstract Literal<C> build();
protected LiteralBuilder(String... names) {
this.names = names;
}
public final RootLiteral<C> asRootLiteral(MessageStyle style) {
return new RootLiteralWrapper<>(build(), style);
}
public final B withExecutor(@Nullable CommandExecutor<C> executor) {
this.executor = executor;
return getThis();
}
public final B withSyncExecutor(@Nullable CommandExecutor.Sync<C> executor) {
this.executor = executor;
return getThis();
}
public final B withPermission(@Nullable String permission) {
this.permission = permission;
return getThis();
}
public final B withArgument(Argument<C, ?> argument) {
arguments.add(argument);
return getThis();
}
}

View file

@ -0,0 +1,32 @@
package site.lab0x13.scrow.commands.model.literal;
import net.kyori.adventure.text.Component;
public interface MessageStyle {
MessageStyle PLAIN = new PlainMessageStyle();
Component ok(Component msg);
Component err(Component msg);
Component exception(Component msg);
default Component ok(String msg) {
return ok(Component.text(msg));
}
default Component err(String msg) {
return err(Component.text(msg));
}
default Component exception(String msg) {
return exception(Component.text(msg));
}
default Component exception(Throwable ex) {
while (ex.getMessage() == null && ex.getCause() != null)
ex = ex.getCause();
return exception(ex.getMessage());
}
}

View file

@ -0,0 +1,20 @@
package site.lab0x13.scrow.commands.model.literal;
import net.kyori.adventure.text.Component;
class PlainMessageStyle implements MessageStyle {
@Override
public Component ok(Component msg) {
return msg;
}
@Override
public Component err(Component msg) {
return msg;
}
@Override
public Component exception(Component msg) {
return Component.text("ERROR: ").append(msg);
}
}

View file

@ -0,0 +1,8 @@
package site.lab0x13.scrow.commands.model.literal;
import site.lab0x13.scrow.commands.model.CommandContext;
public interface RootLiteral<C extends CommandContext<C>> extends Literal<C>{
MessageStyle style();
}

View file

@ -0,0 +1,53 @@
package site.lab0x13.scrow.commands.model.literal;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.commands.model.CommandContext;
import site.lab0x13.scrow.commands.model.CommandExecutor;
import site.lab0x13.scrow.commands.model.argument.Argument;
import java.util.List;
public class RootLiteralWrapper<C extends CommandContext<C>> implements RootLiteral<C> {
private final Literal<C> literal;
private final MessageStyle messageStyle;
public RootLiteralWrapper(Literal<C> literal, MessageStyle messageStyle) {
this.literal = literal;
this.messageStyle = messageStyle;
}
@Override
public MessageStyle style() {
return messageStyle;
}
@Override
public List<String> names() {
return literal.names();
}
@Override
public @Nullable CommandExecutor<C> executor() {
return literal.executor();
}
@Override
public @Nullable Literal<C> getSubLiteral(String name) {
return literal.getSubLiteral(name);
}
@Override
public List<String> subLiteralNames() {
return literal.subLiteralNames();
}
@Override
public List<Argument<C, ?>> arguments() {
return literal.arguments();
}
@Override
public String permission() {
return literal.permission();
}
}

View file

@ -0,0 +1,38 @@
package site.lab0x13.scrow.commands.model.literal;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.commands.model.CommandContext;
import site.lab0x13.scrow.commands.model.CommandExecutor;
import site.lab0x13.scrow.commands.model.argument.Argument;
import java.util.List;
public class StaticLiteral<C extends CommandContext<C>> extends DynamicLiteral<C> {
private final List<Literal<C>> subLiterals;
public StaticLiteral(
List<String> names,
@Nullable CommandExecutor<C> executor,
List<Argument<C, ?>> arguments,
String permission, List<Literal<C>> subLiterals
) {
super(names, executor, arguments, permission);
this.subLiterals = subLiterals;
}
@Override
public @Nullable Literal<C> getSubLiteral(String name) {
return subLiterals.stream()
.filter(literal -> literal.names().contains(name))
.findAny()
.orElse(null);
}
@Override
public List<String> subLiteralNames() {
return subLiterals.stream()
.flatMap(literal -> literal.names().stream())
.toList();
}
}

View file

@ -0,0 +1,37 @@
package site.lab0x13.scrow.commands.model.literal;
import site.lab0x13.scrow.commands.model.CommandContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public final class StaticLiteralBuilder<C extends CommandContext<C>> extends LiteralBuilder<C, StaticLiteralBuilder<C>> {
private final List<Literal<C>> subLiterals = new ArrayList<>();
StaticLiteralBuilder(String... names) {
super(names);
}
@Override
protected StaticLiteralBuilder<C> getThis() {
return this;
}
@Override
public Literal<C> build() {
return new StaticLiteral<>(
Arrays.asList(this.names),
this.executor,
this.arguments,
this.permission,
this.subLiterals
);
}
public StaticLiteralBuilder<C> withSubLiteral(Literal<C> subLiteral) {
subLiterals.add(subLiteral);
return this;
}
}

View file

@ -0,0 +1,100 @@
package site.lab0x13.scrow.commands.parser;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.commands.PlatformAdapter;
import site.lab0x13.scrow.commands.model.CommandContext;
import site.lab0x13.scrow.commands.model.argument.Argument;
import site.lab0x13.scrow.commands.model.literal.Literal;
import site.lab0x13.scrow.commands.model.literal.RootLiteral;
import java.util.Iterator;
import static java.util.Objects.requireNonNull;
public class NodeTreeWalker<C extends CommandContext<C>> {
private final PlatformAdapter<C> platformAdapter;
public NodeTreeWalker(PlatformAdapter<C> platformAdapter) {
this.platformAdapter = platformAdapter;
}
public WalkResult<C> walk(
C ctx,
RootLiteral<C> rootLiteral,
Iterator<String> iter
) {
var cursor = new Cursor(iter);
Literal<C> literal = rootLiteral;
String token;
while (true) {
for (var arg : literal.arguments()) {
//noinspection unchecked
var res = walkArgument(ctx, cursor, literal, (Argument<C, Object>) arg);
if (res != null)
return res;
}
token = cursor.nextToken();
if (token == null)
break;
var nextLiteral = literal.getSubLiteral(token);
if (nextLiteral == null)
return new WalkResult.LiteralUnknown<>(ctx, literal, token);
if (nextLiteral.permission() != null && platformAdapter.checkPermission(ctx, nextLiteral.permission()))
return new WalkResult.LiteralUnknown<>(ctx, literal, token);
literal = nextLiteral;
}
if (!literal.subLiteralNames().isEmpty()) {
var isOptional = literal.executor() != null;
return new WalkResult.LiteralExpected<>(ctx, literal, isOptional);
}
return new WalkResult.Complete<>(ctx, literal);
}
private @Nullable WalkResult<C> walkArgument(
C ctx,
Cursor cursor,
Literal<C> literal,
Argument<C, Object> arg
) {
var rawInput = arg.type().greedy() ? cursor.remainingTokens() : cursor.nextToken();
if (rawInput == null) {
var value = requireNonNull(arg.defaultValueProvider()).getDefault(ctx);
if (value == null)
return new WalkResult.ArgumentExpected<>(ctx, literal, false, arg);
ctx.addParsedArgument(arg, "", value);
return null;
}
var parseResult = arg.parse(ctx, rawInput);
if (parseResult.hasFailure())
return new WalkResult.ArgumentIllegal<>(ctx, literal, false,
parseResult.getFailure().orElseThrow(), arg, rawInput);
ctx.addParsedArgument(arg, rawInput, parseResult.getSuccess().orElseThrow().value());
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();
}
}
}

View file

@ -0,0 +1,80 @@
package site.lab0x13.scrow.commands.parser;
import org.jetbrains.annotations.Nullable;
import site.lab0x13.scrow.commands.model.CommandContext;
import site.lab0x13.scrow.commands.model.argument.Argument;
import site.lab0x13.scrow.commands.model.literal.Literal;
public sealed interface WalkResult<C extends CommandContext<C>> {
C ctx();
Literal<C> lastLiteral();
boolean isValidUsage();
@Nullable String message();
record ArgumentIllegal<C extends CommandContext<C>>(
C ctx,
Literal<C> lastLiteral,
boolean isValidUsage,
String message,
Argument<C, ?> arg,
String input
) implements WalkResult<C> {
}
record ArgumentExpected<C extends CommandContext<C>>(
C ctx,
Literal<C> lastLiteral,
boolean isValidUsage,
Argument<C, ?> arg
) implements WalkResult<C> {
@Override
public String message() {
return "Argument expected.";
}
}
record LiteralExpected<C extends CommandContext<C>>(
C ctx,
Literal<C> lastLiteral,
boolean isValidUsage
) implements WalkResult<C> {
@Override
public String message() {
return "Literal expected.";
}
}
record LiteralUnknown<C extends CommandContext<C>>(
C ctx,
Literal<C> lastLiteral,
String input
) implements WalkResult<C> {
@Override
public boolean isValidUsage() {
return false;
}
@Override
public String message() {
return "Unknown literal \"" + input + "\".";
}
}
record Complete<C extends CommandContext<C>>(
C ctx,
Literal<C> lastLiteral
) implements WalkResult<C> {
@Override
public boolean isValidUsage() {
return true;
}
@Override
public @Nullable String message() {
return null;
}
}
}