clean up command parsing
This commit is contained in:
parent
c22f707556
commit
2c3307e59e
10 changed files with 89 additions and 183 deletions
|
|
@ -1,14 +1,10 @@
|
|||
package site.lab0x13.scrow.commands;
|
||||
|
||||
import site.lab0x13.scrow.commands.model.CommandContext;
|
||||
import site.lab0x13.scrow.commands.model.literal.LiteralMeta;
|
||||
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.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
|
@ -25,13 +21,16 @@ public class CommandHandler<C extends CommandContext<C>> {
|
|||
}
|
||||
|
||||
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()));
|
||||
var walkResult = nodeTreeWalker.walk(ctx, rootLiteral, args.iterator(), false);
|
||||
var error = walkResult.executor().getFailure();
|
||||
if (error.isPresent()) {
|
||||
platformAdapter.sendMessage(ctx, rootLiteral.style().err(error.get()));
|
||||
return;
|
||||
}
|
||||
requireNonNull(walkResult.lastLiteral().executor())
|
||||
.execute(walkResult.ctx())
|
||||
|
||||
var executor = walkResult.executor().getSuccess().orElseThrow();
|
||||
requireNonNull(executor)
|
||||
.execute(ctx)
|
||||
.exceptionally(ex -> {
|
||||
platformAdapter.sendMessage(ctx, rootLiteral.style().exception(ex));
|
||||
return null;
|
||||
|
|
@ -39,39 +38,10 @@ public class CommandHandler<C extends CommandContext<C>> {
|
|||
}
|
||||
|
||||
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 = filterMetas(ctx, res.lastLiteral().subLiteralMetas());
|
||||
input = res.input();
|
||||
}
|
||||
case WalkResult.LiteralExpected<C> res when trailingSpace ->
|
||||
stream = filterMetas(ctx, res.lastLiteral().subLiteralMetas());
|
||||
case WalkResult.ArgumentIllegal<C> res -> {
|
||||
stream = res.arg().suggestionProvider().suggest(ctx, res.input()).stream();
|
||||
input = res.input();
|
||||
}
|
||||
case WalkResult.ArgumentExpected<C> res when trailingSpace ->
|
||||
stream = res.arg().suggestionProvider().suggest(ctx, "").stream();
|
||||
case WalkResult.Complete<C> res when !trailingSpace -> stream = filterMetas(ctx, res.alternativeLiterals());
|
||||
default -> {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
if (input == null) return stream.toList();
|
||||
final var finalInput = input;
|
||||
return stream
|
||||
.filter(s -> s.startsWith(finalInput))
|
||||
// FIXME trailing space
|
||||
var walkResult = nodeTreeWalker.walk(ctx, rootLiteral, Arrays.stream(args).iterator(), trailingSpace);
|
||||
return walkResult.suggestionProvider().suggest(ctx, walkResult.input()).stream()
|
||||
.filter(s -> s.startsWith(walkResult.input()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private Stream<String> filterMetas(C ctx, List<? extends LiteralMeta> metas) {
|
||||
return metas.stream()
|
||||
.filter(literalMeta -> platformAdapter.checkPermission(ctx, literalMeta.permission()))
|
||||
.map(LiteralMeta::names)
|
||||
.flatMap(Collection::stream);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package site.lab0x13.scrow.commands.model;
|
||||
|
||||
public interface Node<C extends CommandContext<C>> {
|
||||
|
||||
SuggestionProvider<C> suggestionProvider();
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ 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;
|
||||
|
|
@ -18,8 +17,7 @@ public record Argument<C extends CommandContext<C>, V>(
|
|||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,18 +3,14 @@ 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.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @param <C> argument of the command context
|
||||
*/
|
||||
public interface Literal<C extends CommandContext<C>> extends Node<C>, LiteralMeta {
|
||||
public interface Literal<C extends CommandContext<C>> extends LiteralMeta {
|
||||
|
||||
static <C extends CommandContext<C>> StaticLiteralBuilder<C> builder(String... names) {
|
||||
return new StaticLiteralBuilder<>(names);
|
||||
|
|
@ -34,12 +30,4 @@ public interface Literal<C extends CommandContext<C>> extends Node<C>, LiteralMe
|
|||
List<? extends LiteralMeta> subLiteralMetas();
|
||||
|
||||
List<Argument<C, ?>> arguments();
|
||||
|
||||
@Override
|
||||
default SuggestionProvider<C> suggestionProvider() {
|
||||
return (_, _) -> subLiteralMetas().stream()
|
||||
.map(LiteralMeta::names)
|
||||
.flatMap(Collection::stream)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
29
common/site/lab0x13/scrow/commands/parser/Cursor.java
Normal file
29
common/site/lab0x13/scrow/commands/parser/Cursor.java
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package site.lab0x13.scrow.commands.parser;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
public class Cursor {
|
||||
private final Iterator<String> tokenIter;
|
||||
|
||||
public Cursor(Iterator<String> tokenIter) {
|
||||
this.tokenIter = tokenIter;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return tokenIter.hasNext();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,18 @@
|
|||
package site.lab0x13.scrow.commands.parser;
|
||||
|
||||
import com.leakyabstractions.result.core.Results;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import site.lab0x13.scrow.commands.PlatformAdapter;
|
||||
import site.lab0x13.scrow.commands.model.CommandContext;
|
||||
import site.lab0x13.scrow.commands.model.CommandExecutor;
|
||||
import site.lab0x13.scrow.commands.model.SuggestionProvider;
|
||||
import site.lab0x13.scrow.commands.model.argument.Argument;
|
||||
import site.lab0x13.scrow.commands.model.literal.Literal;
|
||||
import site.lab0x13.scrow.commands.model.literal.LiteralMeta;
|
||||
import site.lab0x13.scrow.commands.model.literal.RootLiteral;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
|
|
@ -23,7 +27,8 @@ public class NodeTreeWalker<C extends CommandContext<C>> {
|
|||
public WalkResult<C> walk(
|
||||
C ctx,
|
||||
RootLiteral<C> rootLiteral,
|
||||
Iterator<String> iter
|
||||
Iterator<String> iter,
|
||||
boolean suggestNext
|
||||
) {
|
||||
var cursor = new Cursor(iter);
|
||||
Literal<C> previousLiteral = null;
|
||||
|
|
@ -32,7 +37,7 @@ public class NodeTreeWalker<C extends CommandContext<C>> {
|
|||
while (true) {
|
||||
for (var arg : literal.arguments()) {
|
||||
//noinspection unchecked
|
||||
var res = walkArgument(ctx, cursor, literal, (Argument<C, Object>) arg);
|
||||
var res = walkArgument(ctx, cursor, literal.executor(), (Argument<C, Object>) arg);
|
||||
if (res != null)
|
||||
return res;
|
||||
}
|
||||
|
|
@ -42,57 +47,47 @@ public class NodeTreeWalker<C extends CommandContext<C>> {
|
|||
break;
|
||||
var nextLiteral = literal.getSubLiteral(literalName);
|
||||
if (nextLiteral == null || !platformAdapter.checkPermission(ctx, nextLiteral.permission()))
|
||||
return new WalkResult.LiteralUnknown<>(ctx, literal, literalName);
|
||||
return new WalkResult<>(Results.failure("Unknown literal."), suggestionProvider(literal), literalName);
|
||||
previousLiteral = literal;
|
||||
literal = nextLiteral;
|
||||
}
|
||||
|
||||
if (!literal.subLiteralMetas().isEmpty()) {
|
||||
var isOptional = literal.executor() != null;
|
||||
return new WalkResult.LiteralExpected<>(ctx, literal, isOptional);
|
||||
}
|
||||
|
||||
return new WalkResult.Complete<>(ctx, literal, previousLiteral == null ? Collections.emptyList() : previousLiteral.subLiteralMetas());
|
||||
var suggestionProvider = suggestionProvider(suggestNext ? literal : previousLiteral);
|
||||
if (literal.executor() == null)
|
||||
return new WalkResult<>(Results.failure("Literal expected."), suggestionProvider);
|
||||
return new WalkResult<>(Results.success(requireNonNull(literal.executor())), suggestionProvider);
|
||||
}
|
||||
|
||||
private @Nullable WalkResult<C> walkArgument(
|
||||
C ctx,
|
||||
Cursor cursor,
|
||||
Literal<C> literal,
|
||||
CommandExecutor<C> executor,
|
||||
Argument<C, Object> arg
|
||||
) {
|
||||
var rawInput = arg.type().greedy() ? cursor.remainingTokens() : cursor.nextToken();
|
||||
if (rawInput == null) {
|
||||
var defaultValue = requireNonNull(arg.defaultValueProvider()).getDefault(ctx);
|
||||
ctx.addParsedArgument(arg, "", defaultValue);
|
||||
return new WalkResult.ArgumentExpected<>(ctx, literal, defaultValue != null, arg);
|
||||
if (defaultValue == null)
|
||||
return new WalkResult<>(Results.failure("Argument expected."), arg.suggestionProvider());
|
||||
// default value used. if this is the last, we probably want to suggest this.
|
||||
if (!cursor.hasNext())
|
||||
return new WalkResult<>(Results.success(executor), arg.suggestionProvider());
|
||||
} else {
|
||||
var parseResult = arg.parse(ctx, rawInput);
|
||||
if (parseResult.hasFailure())
|
||||
return new WalkResult<>(parseResult.mapSuccess(_ -> null), arg.suggestionProvider(), rawInput);
|
||||
ctx.addParsedArgument(arg, rawInput, parseResult.getSuccess().orElseThrow().value());
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
private SuggestionProvider<C> suggestionProvider(Literal<C> literal) {
|
||||
if (literal == null) return SuggestionProvider.none();
|
||||
return (ctx, _) -> literal.subLiteralMetas().stream()
|
||||
.filter(meta -> platformAdapter.checkPermission(ctx, meta.permission()))
|
||||
.map(LiteralMeta::names)
|
||||
.flatMap(List::stream)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,84 +1,16 @@
|
|||
package site.lab0x13.scrow.commands.parser;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import com.leakyabstractions.result.api.Result;
|
||||
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.LiteralMeta;
|
||||
import site.lab0x13.scrow.commands.model.CommandExecutor;
|
||||
import site.lab0x13.scrow.commands.model.SuggestionProvider;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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,
|
||||
List<? extends LiteralMeta> alternativeLiterals
|
||||
) implements WalkResult<C> {
|
||||
@Override
|
||||
public boolean isValidUsage() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String message() {
|
||||
return null;
|
||||
}
|
||||
public record WalkResult<C extends CommandContext<C>>(
|
||||
Result<CommandExecutor<C>, String> executor,
|
||||
SuggestionProvider<C> suggestionProvider,
|
||||
String input
|
||||
) {
|
||||
public WalkResult(Result<CommandExecutor<C>, String> executor, SuggestionProvider<C> suggestionProvider) {
|
||||
this(executor, suggestionProvider, "");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ class CommandHandlerTest {
|
|||
@Test
|
||||
void testFailOnBadLiteral() {
|
||||
commandHandler.invoke(rootLiteral, new CommandContextImpl(), Stream.of("BAD_LITERAL"));
|
||||
assertEquals("Unknown literal \"BAD_LITERAL\".\n", outContent.toString(StandardCharsets.UTF_8));
|
||||
assertEquals("Unknown literal.\n", outContent.toString(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue