separate reading from parsing

This commit is contained in:
kento2 2026-02-21 01:01:56 +01:00
parent 36a13443db
commit aae7a160ac
22 changed files with 137 additions and 78 deletions

View file

@ -1,6 +1,6 @@
package de.kentoj.scrow.bukkit.services.command; package de.kentoj.scrow.bukkit.services.command;
import de.kentoj.scrow.bukkit.services.command.cmds2.node.RootLiteral; import de.kentoj.scrow.bukkit.services.command.cmds2.literal.RootLiteral;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
public interface CommandManager { public interface CommandManager {

View file

@ -0,0 +1,15 @@
package de.kentoj.scrow.bukkit.services.command.cmds2;
import org.jetbrains.annotations.Nullable;
public interface ArgumentData<T> {
T getValue();
CommandArgument<T> getArgument();
/**
* @return null if the default value for the type was used
*/
@Nullable String getRawValue();
}

View file

@ -17,9 +17,20 @@ public interface CommandContext {
* @return Object that is parsed from the raw argument * @return Object that is parsed from the raw argument
* @throws CommandInvocationException if no value given and no default value supplied * @throws CommandInvocationException if no value given and no default value supplied
*/ */
<T> T getArg(String name); <T> ArgumentData<T> getArgData(String name);
@SuppressWarnings("unchecked")
default <T> T getArgData(CommandArgument<T> arg) {
return (T) getArgData(arg.getName());
}
@SuppressWarnings("unchecked")
default <T> T getArg(String name) {
return (T) getArgData(name).getValue();
}
@SuppressWarnings("unchecked")
default <T> T getArg(CommandArgument<T> arg) { default <T> T getArg(CommandArgument<T> arg) {
return getArg(arg.getName()); return (T) getArgData(arg.getName()).getValue();
} }
} }

View file

@ -1,7 +1,7 @@
package de.kentoj.scrow.bukkit.services.command.cmds2; package de.kentoj.scrow.bukkit.services.command.cmds2;
import de.kentoj.scrow.bukkit.services.command.cmds2.node.Literal; import de.kentoj.scrow.bukkit.services.command.cmds2.literal.Literal;
import de.kentoj.scrow.bukkit.services.command.cmds2.node.RootLiteral; import de.kentoj.scrow.bukkit.services.command.cmds2.literal.RootLiteral;
import de.kentoj.scrow.bukkit.services.command.type.ArgumentType; import de.kentoj.scrow.bukkit.services.command.type.ArgumentType;
import java.util.function.Function; import java.util.function.Function;

View file

@ -1,7 +1,7 @@
package de.kentoj.scrow.bukkit.services.command.cmds2; package de.kentoj.scrow.bukkit.services.command.cmds2;
import de.kentoj.scrow.bukkit.services.command.cmds2.node.Literal; import de.kentoj.scrow.bukkit.services.command.cmds2.literal.Literal;
import de.kentoj.scrow.bukkit.services.command.cmds2.node.RootLiteral; import de.kentoj.scrow.bukkit.services.command.cmds2.literal.RootLiteral;
import de.kentoj.scrow.bukkit.services.command.type.ArgumentType; import de.kentoj.scrow.bukkit.services.command.type.ArgumentType;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;

View file

@ -1,4 +1,4 @@
package de.kentoj.scrow.bukkit.services.command.cmds2.node; package de.kentoj.scrow.bukkit.services.command.cmds2.literal;
import de.kentoj.scrow.bukkit.services.command.CommandExecutor; import de.kentoj.scrow.bukkit.services.command.CommandExecutor;
import de.kentoj.scrow.bukkit.services.command.cmds2.CommandArgument; import de.kentoj.scrow.bukkit.services.command.cmds2.CommandArgument;
@ -6,6 +6,7 @@ import org.bukkit.permissions.Permission;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.Set;
public interface Literal { public interface Literal {
@ -23,6 +24,8 @@ public interface Literal {
@Nullable Literal getLiteral(String rawArg); @Nullable Literal getLiteral(String rawArg);
Set<Literal> getLiterals();
boolean hasLiteral(); boolean hasLiteral();
void setExecutor(@Nullable CommandExecutor executor); void setExecutor(@Nullable CommandExecutor executor);

View file

@ -1,4 +1,4 @@
package de.kentoj.scrow.bukkit.services.command.cmds2.node; package de.kentoj.scrow.bukkit.services.command.cmds2.literal;
import java.util.Set; import java.util.Set;

View file

@ -8,12 +8,14 @@ import java.util.Set;
public interface ArgumentType<T> { public interface ArgumentType<T> {
T parseInput(String rawInput);
String readRawInput(ArgumentReader reader);
/** /**
* @throws CommandInvocationException if the given value may not be used * @throws CommandInvocationException if the given value may not be used
*/ */
void checkValue(CommandContext ctx, T value) throws CommandInvocationException; void checkValue(CommandContext ctx, T value) throws CommandInvocationException;
Set<String> getDefaultSuggestions(CommandContext ctx); Set<String> getDefaultSuggestions(CommandContext ctx);
T parseInput(ArgumentReader reader);
} }

View file

@ -18,6 +18,20 @@ public class NumberArgumentType {
Preconditions.checkArgument(min < max, "min is greater than max"); Preconditions.checkArgument(min < max, "min is greater than max");
return new ArgumentType<>() { return new ArgumentType<>() {
@Override
public Integer parseInput(String rawInput) {
try {
return Integer.parseInt(rawInput);
} catch (NumberFormatException ex) {
throw new CommandInvocationException("Invalid number -- " + rawInput);
}
}
@Override
public String readRawInput(ArgumentReader reader) {
return reader.readWord();
}
@Override @Override
public void checkValue(CommandContext ctx, Integer value) throws CommandInvocationException { public void checkValue(CommandContext ctx, Integer value) throws CommandInvocationException {
if (value < min) throw new CommandInvocationException("Value too small -- " + value); if (value < min) throw new CommandInvocationException("Value too small -- " + value);
@ -28,16 +42,6 @@ public class NumberArgumentType {
public Set<String> getDefaultSuggestions(CommandContext ctx) { public Set<String> getDefaultSuggestions(CommandContext ctx) {
return Set.of(); return Set.of();
} }
@Override
public Integer parseInput(ArgumentReader reader) {
final var str = reader.readWord();
try {
return Integer.parseInt(str);
} catch (NumberFormatException ex) {
throw new CommandInvocationException("Invalid number -- " + str);
}
}
}; };
} }
} }

View file

@ -19,39 +19,43 @@ import java.util.stream.Collectors;
public class PlayerArgumentType { public class PlayerArgumentType {
private static final ArgumentType<Player> player = new ArgumentType<>() { private static final ArgumentType<Player> player = new ArgumentType<>() {
@Override
public Player parseInput(String rawInput) {
if (rawInput.length() == 36) {
var uuid = UUID.fromString(rawInput);
return Bukkit.getPlayer(uuid);
} else {
return Bukkit.getPlayerExact(rawInput);
}
}
@Override
public String readRawInput(ArgumentReader reader) {
return reader.readWord();
}
@Override @Override
public void checkValue(CommandContext ctx, Player value) throws CommandInvocationException { public void checkValue(CommandContext ctx, Player value) throws CommandInvocationException {
if (value == null) if (value == null) throw new CommandInvocationException("Player is not online");
throw new CommandInvocationException("Player is not online");
} }
@Override @Override
public Set<String> getDefaultSuggestions(CommandContext ctx) { public Set<String> getDefaultSuggestions(CommandContext ctx) {
return Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toSet()); return Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toSet());
} }
@Override
public Player parseInput(ArgumentReader reader) {
final var word = reader.readWord();
if (word.length() == 36) {
var uuid = UUID.fromString(word);
return Bukkit.getPlayer(uuid);
} else {
return Bukkit.getPlayerExact(word);
}
}
}; };
private static final ArgumentType<OfflinePlayer> offlinePlayer = new ArgumentType<>() { private static final ArgumentType<OfflinePlayer> offlinePlayer = new ArgumentType<>() {
@Override @Override
public OfflinePlayer parseInput(ArgumentReader reader) { public OfflinePlayer parseInput(String rawInput) {
final var str = reader.readWord();
OfflinePlayer player; OfflinePlayer player;
player = Bukkit.getPlayerExact(str); player = Bukkit.getPlayerExact(rawInput);
if (player == null) { if (player == null) {
try { try {
player = Bukkit.getOfflinePlayer(UUID.fromString(str)); player = Bukkit.getOfflinePlayer(UUID.fromString(rawInput));
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
throw new CommandInvocationException("Not a UUID or name of an online player"); throw new CommandInvocationException("Not a UUID or name of an online player");
} }
@ -59,6 +63,11 @@ public class PlayerArgumentType {
return player; return player;
} }
@Override
public String readRawInput(ArgumentReader reader) {
return reader.readWord();
}
@Override @Override
public Set<String> getDefaultSuggestions(CommandContext ctx) { public Set<String> getDefaultSuggestions(CommandContext ctx) {
return Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toSet()); return Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toSet());

View file

@ -5,7 +5,7 @@ import com.mongodb.MongoClientSettings;
import com.mongodb.reactivestreams.client.MongoClient; import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients; import com.mongodb.reactivestreams.client.MongoClients;
import de.kentoj.scrow.bukkit.cmds2.CommandFactoryImpl; import de.kentoj.scrow.bukkit.cmds2.CommandFactoryImpl;
import de.kentoj.scrow.bukkit.cmds2.CommandManagerImpl; import de.kentoj.scrow.bukkit.cmds2.bukkit.BukkitCommandManager;
import de.kentoj.scrow.bukkit.economy.InMemoryEconomyService; import de.kentoj.scrow.bukkit.economy.InMemoryEconomyService;
import de.kentoj.scrow.bukkit.economy.MongoEconomyService; import de.kentoj.scrow.bukkit.economy.MongoEconomyService;
import de.kentoj.scrow.bukkit.services.command.cmds2.Commands; import de.kentoj.scrow.bukkit.services.command.cmds2.Commands;
@ -49,7 +49,7 @@ public class ScrowAPISurface {
); );
Commands.setFactory(new CommandFactoryImpl()); Commands.setFactory(new CommandFactoryImpl());
ScrowAPI.setCommandManager(new CommandManagerImpl()); ScrowAPI.setCommandManager(new BukkitCommandManager());
} }
public static void destroyScrowAPI() { public static void destroyScrowAPI() {

View file

@ -1,4 +1,4 @@
package de.kentoj.scrow.bukkit.cmds2.types; package de.kentoj.scrow.bukkit.cmds2;
import de.kentoj.scrow.bukkit.services.command.cmds2.CommandArgument; import de.kentoj.scrow.bukkit.services.command.cmds2.CommandArgument;
import de.kentoj.scrow.bukkit.services.command.cmds2.CommandContext; import de.kentoj.scrow.bukkit.services.command.cmds2.CommandContext;

View file

@ -1,6 +1,7 @@
package de.kentoj.scrow.bukkit.cmds2; package de.kentoj.scrow.bukkit.cmds2;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import de.kentoj.scrow.bukkit.services.command.cmds2.ArgumentData;
import de.kentoj.scrow.bukkit.services.command.cmds2.CommandContext; import de.kentoj.scrow.bukkit.services.command.cmds2.CommandContext;
import de.kentoj.scrow.bukkit.services.command.exception.CommandInvocationException; import de.kentoj.scrow.bukkit.services.command.exception.CommandInvocationException;
import lombok.Getter; import lombok.Getter;
@ -16,13 +17,13 @@ public class CommandContextImpl implements CommandContext {
@Getter @Getter
private final CommandSender sender; private final CommandSender sender;
private final Map<String, ?> arguments; private final Map<String, ArgumentData<?>> arguments;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T getArg(String name) { public <T> ArgumentData<T> getArgData(String name) {
final T value = (T) arguments.get(name); final var data = (ArgumentData<T>) arguments.get(name);
Preconditions.checkState(value != null, "no value for '" + name + "'-argument"); Preconditions.checkState(data != null, "no data for '" + name + "'-argument");
return value; return data;
} }
@Override @Override

View file

@ -1,13 +1,10 @@
package de.kentoj.scrow.bukkit.cmds2; package de.kentoj.scrow.bukkit.cmds2;
import de.kentoj.scrow.bukkit.cmds2.types.CommandArgumentImpl;
import de.kentoj.scrow.bukkit.cmds2.types.LiteralImpl;
import de.kentoj.scrow.bukkit.cmds2.types.RootLiteralImpl;
import de.kentoj.scrow.bukkit.services.command.cmds2.CommandArgument; import de.kentoj.scrow.bukkit.services.command.cmds2.CommandArgument;
import de.kentoj.scrow.bukkit.services.command.cmds2.CommandContext; import de.kentoj.scrow.bukkit.services.command.cmds2.CommandContext;
import de.kentoj.scrow.bukkit.services.command.cmds2.CommandFactory; import de.kentoj.scrow.bukkit.services.command.cmds2.CommandFactory;
import de.kentoj.scrow.bukkit.services.command.cmds2.node.Literal; import de.kentoj.scrow.bukkit.services.command.cmds2.literal.Literal;
import de.kentoj.scrow.bukkit.services.command.cmds2.node.RootLiteral; import de.kentoj.scrow.bukkit.services.command.cmds2.literal.RootLiteral;
import de.kentoj.scrow.bukkit.services.command.type.ArgumentType; import de.kentoj.scrow.bukkit.services.command.type.ArgumentType;
import java.util.function.Function; import java.util.function.Function;

View file

@ -1,18 +1,15 @@
package de.kentoj.scrow.bukkit.cmds2.types; package de.kentoj.scrow.bukkit.cmds2;
import de.kentoj.scrow.bukkit.services.command.CommandExecutor; import de.kentoj.scrow.bukkit.services.command.CommandExecutor;
import de.kentoj.scrow.bukkit.services.command.cmds2.CommandArgument; import de.kentoj.scrow.bukkit.services.command.cmds2.CommandArgument;
import de.kentoj.scrow.bukkit.services.command.cmds2.node.Literal; import de.kentoj.scrow.bukkit.services.command.cmds2.literal.Literal;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.permissions.Permission; import org.bukkit.permissions.Permission;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Getter @Getter
public class LiteralImpl implements Literal { public class LiteralImpl implements Literal {
@ -51,6 +48,11 @@ public class LiteralImpl implements Literal {
return !literals.isEmpty(); return !literals.isEmpty();
} }
@Override
public Set<Literal> getLiterals() {
return new HashSet<>(literals.values());
}
@Override @Override
public <T> void addArgument(CommandArgument<T> argument) { public <T> void addArgument(CommandArgument<T> argument) {
arguments.add(argument); arguments.add(argument);

View file

@ -1,6 +1,6 @@
package de.kentoj.scrow.bukkit.cmds2.types; package de.kentoj.scrow.bukkit.cmds2;
import de.kentoj.scrow.bukkit.services.command.cmds2.node.RootLiteral; import de.kentoj.scrow.bukkit.services.command.cmds2.literal.RootLiteral;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;

View file

@ -1,6 +1,8 @@
package de.kentoj.scrow.bukkit.cmds2.parsing; package de.kentoj.scrow.bukkit.cmds2.bukkit;
import de.kentoj.scrow.bukkit.services.command.cmds2.node.RootLiteral; import de.kentoj.scrow.bukkit.cmds2.parsing.ArgumentReaderImpl;
import de.kentoj.scrow.bukkit.cmds2.parsing.CommandProcessor;
import de.kentoj.scrow.bukkit.services.command.cmds2.literal.RootLiteral;
import de.kentoj.scrow.bukkit.services.command.exception.CommandInvocationException; import de.kentoj.scrow.bukkit.services.command.exception.CommandInvocationException;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.Command; import org.bukkit.command.Command;

View file

@ -1,17 +1,16 @@
package de.kentoj.scrow.bukkit.cmds2; package de.kentoj.scrow.bukkit.cmds2.bukkit;
import de.kentoj.scrow.bukkit.cmds2.parsing.BukkitCommand;
import de.kentoj.scrow.bukkit.services.command.CommandManager; import de.kentoj.scrow.bukkit.services.command.CommandManager;
import de.kentoj.scrow.bukkit.services.command.cmds2.node.RootLiteral; import de.kentoj.scrow.bukkit.services.command.cmds2.literal.RootLiteral;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandMap; import org.bukkit.command.CommandMap;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
public class CommandManagerImpl implements CommandManager { public class BukkitCommandManager implements CommandManager {
private final CommandMap commandMap; private final CommandMap commandMap;
public CommandManagerImpl() { public BukkitCommandManager() {
try { try {
var field = Bukkit.getPluginManager().getClass().getDeclaredField("commandMap"); var field = Bukkit.getPluginManager().getClass().getDeclaredField("commandMap");
field.setAccessible(true); field.setAccessible(true);

View file

@ -1,6 +1,6 @@
package de.kentoj.scrow.bukkit.cmds2.parsing; package de.kentoj.scrow.bukkit.cmds2.bukkit;
import de.kentoj.scrow.bukkit.services.command.cmds2.node.RootLiteral; import de.kentoj.scrow.bukkit.services.command.cmds2.literal.RootLiteral;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;

View file

@ -0,0 +1,13 @@
package de.kentoj.scrow.bukkit.cmds2.parsing;
import de.kentoj.scrow.bukkit.services.command.cmds2.ArgumentData;
import de.kentoj.scrow.bukkit.services.command.cmds2.CommandArgument;
import lombok.Value;
import org.jetbrains.annotations.Nullable;
@Value
public class ArgumentDataImpl<T> implements ArgumentData<T> {
T value;
CommandArgument<T> argument;
@Nullable String rawValue;
}

View file

@ -2,12 +2,12 @@ package de.kentoj.scrow.bukkit.cmds2.parsing;
import de.kentoj.scrow.bukkit.cmds2.CommandContextImpl; import de.kentoj.scrow.bukkit.cmds2.CommandContextImpl;
import de.kentoj.scrow.bukkit.services.command.ArgumentReader; import de.kentoj.scrow.bukkit.services.command.ArgumentReader;
import de.kentoj.scrow.bukkit.services.command.cmds2.ArgumentData;
import de.kentoj.scrow.bukkit.services.command.cmds2.CommandArgument; import de.kentoj.scrow.bukkit.services.command.cmds2.CommandArgument;
import de.kentoj.scrow.bukkit.services.command.cmds2.node.Literal; import de.kentoj.scrow.bukkit.services.command.cmds2.literal.Literal;
import de.kentoj.scrow.bukkit.services.command.cmds2.node.RootLiteral; import de.kentoj.scrow.bukkit.services.command.cmds2.literal.RootLiteral;
import de.kentoj.scrow.bukkit.services.command.exception.CommandInvocationException; import de.kentoj.scrow.bukkit.services.command.exception.CommandInvocationException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import java.util.HashMap; import java.util.HashMap;
@ -16,7 +16,7 @@ import java.util.Map;
@RequiredArgsConstructor @RequiredArgsConstructor
public class CommandProcessor { public class CommandProcessor {
private final Map<String, Object> arguments = new HashMap<>(); private final Map<String, ArgumentData<?>> arguments = new HashMap<>();
private final CommandSender sender; private final CommandSender sender;
private final ArgumentReader argumentReader; private final ArgumentReader argumentReader;
@ -48,20 +48,21 @@ public class CommandProcessor {
private void processArguments(Literal node) { private void processArguments(Literal node) {
node.getArguments().forEach(arg -> { node.getArguments().forEach(arg -> {
Object parsed = parseArgument(arg); var parsed = parseArgument(arg);
arguments.put(arg.getName(), parsed); arguments.put(arg.getName(), parsed);
}); });
} }
private <T> Object parseArgument(CommandArgument<T> argument) { private <T> ArgumentData<T> parseArgument(CommandArgument<T> argument) {
if (argumentReader.isEOF()) { if (argumentReader.isEOF()) {
var defaultValue = argument.getDefaultValue(new CommandContextImpl(sender, arguments)); var defaultValue = argument.getDefaultValue(new CommandContextImpl(sender, arguments));
if (defaultValue == null) throw new CommandInvocationException("Missing argument -- " + argument.getName()); if (defaultValue == null) throw new CommandInvocationException("Missing argument -- " + argument.getName());
return defaultValue; return new ArgumentDataImpl<>(defaultValue, argument, null);
} }
T value = argument.getType().parseInput(argumentReader); final var rawValue = argument.getType().readRawInput(argumentReader);
T value = argument.getType().parseInput(rawValue);
argument.getType().checkValue(new CommandContextImpl(sender, arguments), value); argument.getType().checkValue(new CommandContextImpl(sender, arguments), value);
return value; return new ArgumentDataImpl<>(value, argument, rawValue);
} }
} }

View file

@ -4,7 +4,7 @@ import de.kentoj.scrow.bukkit.ScrowAPI;
import de.kentoj.scrow.bukkit.services.command.cmds2.CommandArgument; import de.kentoj.scrow.bukkit.services.command.cmds2.CommandArgument;
import de.kentoj.scrow.bukkit.services.command.cmds2.CommandContext; import de.kentoj.scrow.bukkit.services.command.cmds2.CommandContext;
import de.kentoj.scrow.bukkit.services.command.cmds2.Commands; import de.kentoj.scrow.bukkit.services.command.cmds2.Commands;
import de.kentoj.scrow.bukkit.services.command.cmds2.node.RootLiteral; import de.kentoj.scrow.bukkit.services.command.cmds2.literal.RootLiteral;
import de.kentoj.scrow.bukkit.services.command.type.NumberArgumentType; import de.kentoj.scrow.bukkit.services.command.type.NumberArgumentType;
import de.kentoj.scrow.bukkit.services.command.type.PlayerArgumentType; import de.kentoj.scrow.bukkit.services.command.type.PlayerArgumentType;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;