will try smth

This commit is contained in:
kento2 2026-04-15 18:39:41 +02:00
parent f5109f9789
commit 7bfbe48729
28 changed files with 150 additions and 111 deletions

View file

@ -0,0 +1,47 @@
val scrowToken = providers.gradleProperty("scrowMavenToken")
.orElse(providers.environmentVariable("SCROW_MAVEN_TOKEN")).get()
plugins {
id("java")
id("maven-publish")
id("io.freefair.lombok") version "9.2.0"
}
group = "de.kentoj"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
// https://mvnrepository.com/artifact/org.jetbrains/annotations
implementation("org.jetbrains:annotations:26.0.2-1")
}
publishing {
publications {
create<MavenPublication>("KenCommandAPI") {
groupId = project.group.toString()
artifactId = project.name
version = project.version.toString()
from(components["java"])
}
}
repositories {
maven {
name = "scrow"
url = uri("http://forge.kentoj.de/api/packages/scrow/maven")
isAllowInsecureProtocol = true
credentials(HttpHeaderCredentials::class) {
name = "Authorization"
value = "token $scrowToken"
}
authentication {
create<HttpHeaderAuthentication>("header")
}
}
}
}

View file

@ -0,0 +1,27 @@
package de.kentoj.kencommandapi;
import de.kentoj.kencommandapi.api.KenCommandApi;
import de.kentoj.kencommandapi.api.structure.argument.types.IntegerArgumentType;
import de.kentoj.kencommandapi.impl.KenCommandApiImpl;
import java.io.PrintStream;
public class Main {
public static void main(String[] args) {
KenCommandApi<PrintStream> api = new KenCommandApiImpl<>();
var root = api.createNode("coins");
var addNode = api.createNode("add", "deposit");
addNode.setExecutor(ctx -> {
int amount = ctx.getArg("amount");
ctx.getSender().println("adding " + amount + "$ to your balance");
});
addNode.addArgument(api.createArgument("amount", IntegerArgumentType.create()));
root.addLiteral(addNode);
api.invoke(root, System.out, new String[]{"add", "100"});
}
}

View file

@ -0,0 +1,14 @@
package de.kentoj.kencommandapi.api;
import de.kentoj.kencommandapi.api.structure.argument.ArgumentType;
import de.kentoj.kencommandapi.api.structure.argument.CommandArgument;
import de.kentoj.kencommandapi.api.structure.node.CommandNode;
public interface KenCommandApi<T> {
CommandNode<T> createNode(String... name);
<V> CommandArgument<T, V> createArgument(String id, ArgumentType<V> type);
void invoke(CommandNode<T> node, T sender, String[] args);
}

View file

@ -0,0 +1,17 @@
package de.kentoj.kencommandapi.api.processing;
public interface CommandContext<T> {
T getSender();
/**
* @return Object that is parsed from the raw argument
* @throws RuntimeException if no value given and no default value supplied
*/
<V> ParsedArgument<T, V> getParsedArgument(String name);
@SuppressWarnings("unchecked")
default <V> V getArg(String name) {
return (V) getParsedArgument(name).getValue();
}
}

View file

@ -0,0 +1,6 @@
package de.kentoj.kencommandapi.api.processing;
public interface CommandExecutor<S> {
void execute(CommandContext<T> ctx);
}

View file

@ -0,0 +1,10 @@
package de.kentoj.kencommandapi.api.processing;
import de.kentoj.kencommandapi.api.structure.argument.CommandArgument;
public interface ParsedArgument<T, V> {
V getValue();
CommandArgument<T, V> getArgument();
}

View file

@ -0,0 +1,8 @@
package de.kentoj.kencommandapi.api.structure.argument;
public interface ArgumentType<V> {
V parseInput(String string);
SuggestionProvider<?> defaultSuggestionProvider();
}

View file

@ -0,0 +1,22 @@
package de.kentoj.kencommandapi.api.structure.argument;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface CommandArgument<T, V> {
String getId();
ArgumentType<V> getType();
void setDefaultValue(@Nullable T defaultValue);
@Nullable T getDefaultValue();
void setSuggestionProvider(@Nullable SuggestionProvider<T> suggestionProvider);
/**
* @return the explicitly set SuggestionProvider or the default suggestions for the type if unset/null
*/
@NotNull SuggestionProvider<T> getSuggestionProvider();
}

View file

@ -0,0 +1,10 @@
package de.kentoj.kencommandapi.api.structure.argument;
import de.kentoj.kencommandapi.api.processing.CommandContext;
import java.util.Set;
public interface SuggestionProvider<T> {
Set<String> suggest(CommandContext<T> context);
}

View file

@ -0,0 +1,25 @@
package de.kentoj.kencommandapi.api.structure.argument.suggestion;
import de.kentoj.kencommandapi.api.processing.CommandContext;
import de.kentoj.kencommandapi.api.structure.argument.SuggestionProvider;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.Set;
@NoArgsConstructor(access = AccessLevel.NONE)
public class NoSuggestionProvider implements SuggestionProvider<Object> {
@Getter(value = AccessLevel.PRIVATE, lazy = true)
private static final SuggestionProvider<Object> instance = new NoSuggestionProvider();
public static SuggestionProvider<Object> create() {
return getInstance();
}
@Override
public Set<String> suggest(CommandContext<Object> context) {
return Set.of();
}
}

View file

@ -0,0 +1,25 @@
package de.kentoj.kencommandapi.api.structure.argument.types;
import de.kentoj.kencommandapi.api.structure.argument.ArgumentType;
import de.kentoj.kencommandapi.api.structure.argument.SuggestionProvider;
import de.kentoj.kencommandapi.api.structure.argument.suggestion.NoSuggestionProvider;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class IntegerArgumentType implements ArgumentType<Integer> {
@Override
public Integer parseInput(String string) {
return Integer.parseInt(string);
}
@Override
public SuggestionProvider<?> defaultSuggestionProvider() {
return NoSuggestionProvider.create();
}
public static IntegerArgumentType create() {
return new IntegerArgumentType();
}
}

View file

@ -0,0 +1,12 @@
package de.kentoj.kencommandapi.api.structure.node;
import de.kentoj.kencommandapi.api.structure.argument.CommandArgument;
import java.util.Set;
public interface ArgumentNode<T> {
<V> void addArgument(CommandArgument<T, V> argument);
Set<CommandArgument<T, ?>> getArguments();
}

View file

@ -0,0 +1,21 @@
package de.kentoj.kencommandapi.api.structure.node;
import de.kentoj.kencommandapi.api.processing.CommandExecutor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @param <T> type of command sender
*/
public interface CommandNode<T> extends ArgumentNode<T>, LiteralNode<T> {
@NotNull String[] getNames();
void setExecutor(@Nullable CommandExecutor<T> executor);
@Nullable CommandExecutor<T> getExecutor();
void setDescription(@Nullable String description);
@Nullable String getDescription();
}

View file

@ -0,0 +1,15 @@
package de.kentoj.kencommandapi.api.structure.node;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
public interface LiteralNode<T> {
void addLiteral(@NotNull CommandNode<T> commandNode);
@Nullable CommandNode<T> getLiteral(@NotNull String name);
@NotNull Set<CommandNode<T>> getLiterals();
}

View file

@ -0,0 +1,78 @@
package de.kentoj.kencommandapi.impl;
import de.kentoj.kencommandapi.api.KenCommandApi;
import de.kentoj.kencommandapi.api.processing.ParsedArgument;
import de.kentoj.kencommandapi.api.structure.argument.ArgumentType;
import de.kentoj.kencommandapi.api.structure.argument.CommandArgument;
import de.kentoj.kencommandapi.api.structure.node.CommandNode;
import de.kentoj.kencommandapi.impl.processing.CommandContextImpl;
import de.kentoj.kencommandapi.impl.processing.ParsedArgumentImpl;
import de.kentoj.kencommandapi.impl.structure.argument.CommandArgumentImpl;
import de.kentoj.kencommandapi.impl.structure.node.CommandNodeImpl;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@RequiredArgsConstructor
public class KenCommandApiImpl<T> implements KenCommandApi<T> {
@Override
public CommandNode<T> createNode(String... name) {
return new CommandNodeImpl<>(name);
}
@Override
public <V> CommandArgument<T, V> createArgument(String id, ArgumentType<V> type) {
return new CommandArgumentImpl<>(id, type);
}
@Override
public void invoke(CommandNode<T> node, T sender, String[] args) {
var iter = Arrays.stream(args).iterator();
var executableLiteral = processLiterals(node, iter);
var parsedArguments = parseArguments(executableLiteral, iter);
executableLiteral.getExecutor().execute(new CommandContextImpl<>(sender, parsedArguments));
}
private CommandNode<T> processLiterals(CommandNode<T> commandNode, Iterator<String> iter) {
if (!iter.hasNext()) {
// literals/arguments are optional if the node itself is executable
if (commandNode.getExecutor() == null) throw new RuntimeException("no literal given but required");
return commandNode;
}
if (commandNode.getLiterals().isEmpty()) {
return commandNode;
}
final var arg = iter.next();
final var literal = commandNode.getLiteral(arg);
if (literal == null) {
throw new RuntimeException("Invalid literal: " + arg);
}
return processLiterals(literal, iter);
}
private Map<String, ParsedArgument<T, ?>> parseArguments(CommandNode<T> commandNode, Iterator<String> iter) {
Map<String, ParsedArgument<T, ?>> parsedArguments = new HashMap<>();
for (var argument : commandNode.getArguments()) {
@SuppressWarnings("unchecked")
var arg = ((CommandArgument<T, Object>) argument);
if (!iter.hasNext()) {
var defaultValue = arg.getDefaultValue();
if (defaultValue == null) throw new RuntimeException("no argument given but required");
parsedArguments.put(arg.getId(), new ParsedArgumentImpl<>(defaultValue, arg));
continue;
}
var parsedArg = arg.getType().parseInput(iter.next());
parsedArguments.put(arg.getId(), new ParsedArgumentImpl<>(parsedArg, arg));
}
return parsedArguments;
}
}

View file

@ -0,0 +1,26 @@
package de.kentoj.kencommandapi.impl.processing;
import de.kentoj.kencommandapi.api.processing.CommandContext;
import de.kentoj.kencommandapi.api.processing.ParsedArgument;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Map;
@RequiredArgsConstructor
public class CommandContextImpl<T> implements CommandContext<T> {
@Getter
private final T sender;
@Getter
private final Map<String, ParsedArgument<T, ?>> parsedArguments;
@Override
public <V> ParsedArgument<T, V> getParsedArgument(String name) {
try {
//noinspection unchecked
return (ParsedArgument<T, V>) parsedArguments.get(name);
} catch (ClassCastException e) {
throw new RuntimeException("requested argument with wrong type", e);
}
}
}

View file

@ -0,0 +1,11 @@
package de.kentoj.kencommandapi.impl.processing;
import de.kentoj.kencommandapi.api.processing.ParsedArgument;
import de.kentoj.kencommandapi.api.structure.argument.CommandArgument;
import lombok.Data;
@Data
public class ParsedArgumentImpl<T, V> implements ParsedArgument<T, V> {
final V value;
final CommandArgument<T, V> argument;
}

View file

@ -0,0 +1,26 @@
package de.kentoj.kencommandapi.impl.structure.argument;
import de.kentoj.kencommandapi.api.structure.argument.ArgumentType;
import de.kentoj.kencommandapi.api.structure.argument.CommandArgument;
import de.kentoj.kencommandapi.api.structure.argument.SuggestionProvider;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.jetbrains.annotations.Nullable;
@RequiredArgsConstructor
public class CommandArgumentImpl<T, V> implements CommandArgument<T, V> {
@Getter
private final String id;
@Getter
private final ArgumentType<V> type;
@Getter
@Setter
private @Nullable T defaultValue;
@Getter
@Setter
private @Nullable SuggestionProvider<T> suggestionProvider;
}

View file

@ -0,0 +1,65 @@
package de.kentoj.kencommandapi.impl.structure.node;
import de.kentoj.kencommandapi.api.processing.CommandExecutor;
import de.kentoj.kencommandapi.api.structure.argument.CommandArgument;
import de.kentoj.kencommandapi.api.structure.node.CommandNode;
import lombok.Getter;
import lombok.Setter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.Set;
public class CommandNodeImpl<T> implements CommandNode<T> {
@Getter
private final String[] names;
public CommandNodeImpl(String[] names) {
if (names.length == 0) throw new IllegalArgumentException("at least one name is required");
this.names = names;
}
@Getter
private final Set<CommandNode<T>> literals = new HashSet<>();
@Getter
private final Set<CommandArgument<T, ?>> arguments = new HashSet<>();
@Getter
@Setter
private @Nullable CommandExecutor<T> executor;
@Getter
@Setter
private String description;
@Override
public void addLiteral(@NotNull CommandNode<T> commandNode) {
for (String name : commandNode.getNames()) {
if (getLiteral(name) != null) throw new IllegalArgumentException("literal with same name is already added");
}
literals.add(commandNode);
}
@Override
public CommandNode<T> getLiteral(@NotNull String name) {
for (var literal : literals) {
for (String literalName : literal.getNames()) {
if (literalName.equalsIgnoreCase(name)) return literal;
}
}
return null;
}
@Override
public <V> void addArgument(CommandArgument<T, V> argument) {
if (arguments.stream()
.anyMatch(arg -> arg.getId().equals(argument.getId()))
) {
throw new IllegalArgumentException("argument with same identifier is already added");
}
arguments.add(argument);
}
}