Added new Brigadier commands related APIs for bungee/paper/cli + a few javadoc
This commit is contained in:
@@ -32,12 +32,16 @@
|
||||
<artifactId>pandalib-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>fr.pandacube.lib</groupId>
|
||||
<artifactId>pandalib-reflect</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>fr.pandacube.lib</groupId>
|
||||
<artifactId>pandalib-commands</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
@@ -50,12 +54,6 @@
|
||||
<version>${bungeecord.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>fr.pandacube.lib</groupId>
|
||||
<artifactId>pandalib-commands</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
@@ -1,139 +0,0 @@
|
||||
package fr.pandacube.lib.cli;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.mojang.brigadier.arguments.ArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.context.ParsedCommandNode;
|
||||
import com.mojang.brigadier.suggestion.Suggestion;
|
||||
import com.mojang.brigadier.suggestion.SuggestionProvider;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
|
||||
import fr.pandacube.lib.chat.ChatStatic;
|
||||
import fr.pandacube.lib.commands.SuggestionsSupplier;
|
||||
import fr.pandacube.lib.util.Log;
|
||||
import fr.pandacube.lib.reflect.Reflect;
|
||||
|
||||
public abstract class BrigadierCommand extends ChatStatic {
|
||||
|
||||
public BrigadierCommand() {
|
||||
LiteralArgumentBuilder<Object> builder = buildCommand();
|
||||
String[] aliases = getAliases();
|
||||
if (aliases == null)
|
||||
aliases = new String[0];
|
||||
|
||||
LiteralCommandNode<Object> commandNode = BrigadierDispatcher.instance.register(builder);
|
||||
|
||||
|
||||
for (String alias : aliases) {
|
||||
BrigadierDispatcher.instance.register(literal(alias)
|
||||
.requires(commandNode.getRequirement())
|
||||
.executes(commandNode.getCommand())
|
||||
.redirect(commandNode)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected abstract LiteralArgumentBuilder<Object> buildCommand();
|
||||
|
||||
protected String[] getAliases() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static LiteralArgumentBuilder<Object> literal(String name) {
|
||||
return LiteralArgumentBuilder.literal(name);
|
||||
}
|
||||
|
||||
public static <T> RequiredArgumentBuilder<Object, T> argument(String name, ArgumentType<T> type) {
|
||||
return RequiredArgumentBuilder.argument(name, type);
|
||||
}
|
||||
|
||||
|
||||
public static boolean isLiteralParsed(CommandContext<Object> context, String literal) {
|
||||
for (ParsedCommandNode<Object> node : context.getNodes()) {
|
||||
if (!(node.getNode() instanceof LiteralCommandNode))
|
||||
continue;
|
||||
if (((LiteralCommandNode<Object>)node.getNode()).getLiteral().equals(literal))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static <T> T tryGetArgument(CommandContext<Object> context, String argument, Class<T> type) {
|
||||
return tryGetArgument(context, argument, type, null);
|
||||
}
|
||||
|
||||
public static <T> T tryGetArgument(CommandContext<Object> context, String argument, Class<T> type, T deflt) {
|
||||
try {
|
||||
return context.getArgument(argument, type);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return deflt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
protected static SuggestionProvider<Object> wrapSuggestions(SuggestionsSupplier<Object> suggestions) {
|
||||
return (context, builder) -> {
|
||||
Object sender = context.getSource();
|
||||
String message = builder.getInput();
|
||||
try {
|
||||
int tokenStartPos = builder.getStart();
|
||||
|
||||
int firstSpacePos = message.indexOf(" ");
|
||||
String[] args = (firstSpacePos + 1 > tokenStartPos - 1) ? new String[0]
|
||||
: message.substring(firstSpacePos + 1, tokenStartPos - 1).split(" ", -1);
|
||||
args = Arrays.copyOf(args, args.length + 1);
|
||||
args[args.length - 1] = message.substring(tokenStartPos);
|
||||
|
||||
List<String> results = suggestions.getSuggestions(sender, args.length - 1, args[args.length - 1], args);
|
||||
|
||||
for (String s : results) {
|
||||
if (s != null)
|
||||
builder.suggest(s);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Log.severe("Error while tab-completing '" + message/* + "' for " + sender.getName()*/, e);
|
||||
}
|
||||
return completableFutureSuggestionsKeepsOriginalOrdering(builder);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static CompletableFuture<Suggestions> completableFutureSuggestionsKeepsOriginalOrdering(SuggestionsBuilder builder) {
|
||||
return CompletableFuture.completedFuture(
|
||||
BrigadierDispatcher.createSuggestionsOriginalOrdering(builder.getInput(), getSuggestionsFromSuggestionsBuilder(builder))
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<Suggestion> getSuggestionsFromSuggestionsBuilder(SuggestionsBuilder builder) {
|
||||
try {
|
||||
return (List<Suggestion>) Reflect.ofClass(SuggestionsBuilder.class).field("result").getValue(builder);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@@ -1,158 +0,0 @@
|
||||
package fr.pandacube.lib.cli;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.ParseResults;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContextBuilder;
|
||||
import com.mojang.brigadier.context.StringRange;
|
||||
import com.mojang.brigadier.context.SuggestionContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.suggestion.Suggestion;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
|
||||
import fr.pandacube.lib.util.Log;
|
||||
import jline.console.completer.Completer;
|
||||
|
||||
public class BrigadierDispatcher implements Completer {
|
||||
|
||||
public static final BrigadierDispatcher instance = new BrigadierDispatcher();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private final CommandDispatcher<Object> dispatcher;
|
||||
|
||||
private final Object sender = new Object();
|
||||
|
||||
public BrigadierDispatcher() {
|
||||
dispatcher = new CommandDispatcher<>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* package */ LiteralCommandNode<Object> register(LiteralArgumentBuilder<Object> node) {
|
||||
return dispatcher.register(node);
|
||||
}
|
||||
|
||||
|
||||
public int execute(String commandWithoutSlash) {
|
||||
ParseResults<Object> parsed = dispatcher.parse(commandWithoutSlash, sender);
|
||||
|
||||
try {
|
||||
return dispatcher.execute(parsed);
|
||||
} catch (CommandSyntaxException e) {
|
||||
Log.severe("Erreur d’utilisation de la commande : " + e.getMessage());
|
||||
return 0;
|
||||
} catch (Throwable e) {
|
||||
Log.severe("Erreur lors de l’exécution de la commande : ", e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int complete(String buffer, int cursor, List<CharSequence> candidates) {
|
||||
|
||||
String bufferBeforeCursor = buffer.substring(0, cursor);
|
||||
|
||||
Suggestions completeResult = getSuggestions(bufferBeforeCursor);
|
||||
|
||||
completeResult.getList().stream()
|
||||
.map(Suggestion::getText)
|
||||
.forEach(candidates::add);
|
||||
|
||||
return completeResult.getRange().getStart();
|
||||
}
|
||||
|
||||
/* package */ Suggestions getSuggestions(String buffer) {
|
||||
ParseResults<Object> parsed = dispatcher.parse(buffer, sender);
|
||||
try {
|
||||
CompletableFuture<Suggestions> futureSuggestions = buildSuggestionBrigadier(parsed);
|
||||
return futureSuggestions.join();
|
||||
} catch (Throwable e) {
|
||||
Log.severe("Erreur d’exécution des suggestions :\n" + e.getMessage(), e);
|
||||
return Suggestions.empty().join();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
CompletableFuture<Suggestions> buildSuggestionBrigadier(ParseResults<Object> parsed) {
|
||||
int cursor = parsed.getReader().getTotalLength();
|
||||
final CommandContextBuilder<Object> context = parsed.getContext();
|
||||
|
||||
final SuggestionContext<Object> nodeBeforeCursor = context.findSuggestionContext(cursor);
|
||||
final CommandNode<Object> parent = nodeBeforeCursor.parent;
|
||||
final int start = Math.min(nodeBeforeCursor.startPos, cursor);
|
||||
|
||||
final String fullInput = parsed.getReader().getString();
|
||||
final String truncatedInput = fullInput.substring(0, cursor);
|
||||
@SuppressWarnings("unchecked") final CompletableFuture<Suggestions>[] futures = new CompletableFuture[parent.getChildren().size()];
|
||||
int i = 0;
|
||||
for (final CommandNode<Object> node : parent.getChildren()) {
|
||||
CompletableFuture<Suggestions> future = Suggestions.empty();
|
||||
try {
|
||||
future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, start));
|
||||
} catch (final CommandSyntaxException ignored) {
|
||||
}
|
||||
futures[i++] = future;
|
||||
}
|
||||
|
||||
final CompletableFuture<Suggestions> result = new CompletableFuture<>();
|
||||
CompletableFuture.allOf(futures).thenRun(() -> {
|
||||
final List<Suggestions> suggestions = new ArrayList<>();
|
||||
for (final CompletableFuture<Suggestions> future : futures) {
|
||||
suggestions.add(future.join());
|
||||
}
|
||||
result.complete(mergeSuggestionsOriginalOrdering(fullInput, suggestions));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// inspired from com.mojang.brigadier.suggestion.Suggestions#merge, but without the sorting part
|
||||
public static Suggestions mergeSuggestionsOriginalOrdering(String input, Collection<Suggestions> suggestions) {
|
||||
if (suggestions.isEmpty()) {
|
||||
return new Suggestions(StringRange.at(0), new ArrayList<>(0));
|
||||
} else if (suggestions.size() == 1) {
|
||||
return suggestions.iterator().next();
|
||||
}
|
||||
|
||||
final List<Suggestion> texts = new ArrayList<>();
|
||||
for (final Suggestions suggestions1 : suggestions) {
|
||||
texts.addAll(suggestions1.getList());
|
||||
}
|
||||
return createSuggestionsOriginalOrdering(input, texts);
|
||||
}
|
||||
|
||||
// inspired from com.mojang.brigadier.suggestion.Suggestions#create, but without the sorting part
|
||||
public static Suggestions createSuggestionsOriginalOrdering(String command, Collection<Suggestion> suggestions) {
|
||||
if (suggestions.isEmpty()) {
|
||||
return new Suggestions(StringRange.at(0), new ArrayList<>(0));
|
||||
}
|
||||
int start = Integer.MAX_VALUE;
|
||||
int end = Integer.MIN_VALUE;
|
||||
for (final Suggestion suggestion : suggestions) {
|
||||
start = Math.min(suggestion.getRange().getStart(), start);
|
||||
end = Math.max(suggestion.getRange().getEnd(), end);
|
||||
}
|
||||
final StringRange range = new StringRange(start, end);
|
||||
final List<Suggestion> texts = new ArrayList<>(suggestions.size());
|
||||
for (final Suggestion suggestion : suggestions) {
|
||||
texts.add(suggestion.expand(command, range));
|
||||
}
|
||||
return new Suggestions(range, texts);
|
||||
}
|
||||
|
||||
}
|
@@ -3,6 +3,8 @@ package fr.pandacube.lib.cli;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import fr.pandacube.lib.cli.commands.CLIBrigadierDispatcher;
|
||||
import fr.pandacube.lib.cli.log.CLILogger;
|
||||
import jline.console.ConsoleReader;
|
||||
import org.fusesource.jansi.AnsiConsole;
|
||||
|
||||
@@ -47,7 +49,7 @@ public class CLI {
|
||||
reader = new ConsoleReader();
|
||||
reader.setBellEnabled(false);
|
||||
reader.setPrompt("\r>");
|
||||
reader.addCompleter(BrigadierDispatcher.instance);
|
||||
reader.addCompleter(CLIBrigadierDispatcher.instance);
|
||||
|
||||
// configuration du formatteur pour le logger
|
||||
System.setProperty("net.md_5.bungee.log-date-format", "yyyy-MM-dd HH:mm:ss");
|
||||
@@ -78,7 +80,7 @@ public class CLI {
|
||||
if (line.trim().equals(""))
|
||||
continue;
|
||||
String cmdLine = line;
|
||||
new Thread(() -> BrigadierDispatcher.instance.execute(cmdLine), "CLICmdThread #"+(i++)).start();
|
||||
new Thread(() -> CLIBrigadierDispatcher.instance.execute(cmdLine), "CLICmdThread #"+(i++)).start();
|
||||
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
@@ -0,0 +1,81 @@
|
||||
package fr.pandacube.lib.cli.commands;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import com.mojang.brigadier.arguments.ArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.context.ParsedCommandNode;
|
||||
import com.mojang.brigadier.suggestion.SuggestionProvider;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
|
||||
import fr.pandacube.lib.commands.BrigadierCommand;
|
||||
import fr.pandacube.lib.commands.BrigadierSuggestionsUtil;
|
||||
import fr.pandacube.lib.commands.SuggestionsSupplier;
|
||||
import fr.pandacube.lib.util.Log;
|
||||
|
||||
public abstract class CLIBrigadierCommand extends BrigadierCommand<Object> {
|
||||
|
||||
public CLIBrigadierCommand() {
|
||||
LiteralCommandNode<Object> commandNode = buildCommand().build();
|
||||
postBuildCommand(commandNode);
|
||||
String[] aliases = getAliases();
|
||||
if (aliases == null)
|
||||
aliases = new String[0];
|
||||
|
||||
CLIBrigadierDispatcher.instance.register(commandNode);
|
||||
|
||||
|
||||
for (String alias : aliases) {
|
||||
CLIBrigadierDispatcher.instance.register(literal(alias)
|
||||
.requires(commandNode.getRequirement())
|
||||
.executes(commandNode.getCommand())
|
||||
.redirect(commandNode)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract LiteralArgumentBuilder<Object> buildCommand();
|
||||
|
||||
protected String[] getAliases() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public boolean isPlayer(Object sender) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isConsole(Object sender) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Predicate<Object> hasPermission(String permission) {
|
||||
return sender -> true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
protected SuggestionProvider<Object> wrapSuggestions(SuggestionsSupplier<Object> suggestions) {
|
||||
return wrapSuggestions(suggestions, Function.identity());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
package fr.pandacube.lib.cli.commands;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.mojang.brigadier.suggestion.Suggestion;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
|
||||
import fr.pandacube.lib.chat.Chat;
|
||||
import fr.pandacube.lib.commands.BrigadierDispatcher;
|
||||
import fr.pandacube.lib.util.Log;
|
||||
import jline.console.completer.Completer;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
|
||||
public class CLIBrigadierDispatcher extends BrigadierDispatcher<Object> implements Completer {
|
||||
|
||||
public static final CLIBrigadierDispatcher instance = new CLIBrigadierDispatcher();
|
||||
|
||||
|
||||
private static final Object sender = new Object();
|
||||
|
||||
|
||||
public int execute(String commandWithoutSlash) {
|
||||
return execute(sender, commandWithoutSlash);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int complete(String buffer, int cursor, List<CharSequence> candidates) {
|
||||
|
||||
String bufferBeforeCursor = buffer.substring(0, cursor);
|
||||
|
||||
Suggestions completeResult = getSuggestions(bufferBeforeCursor);
|
||||
|
||||
completeResult.getList().stream()
|
||||
.map(Suggestion::getText)
|
||||
.forEach(candidates::add);
|
||||
|
||||
return completeResult.getRange().getStart();
|
||||
}
|
||||
|
||||
public Suggestions getSuggestions(String buffer) {
|
||||
return getSuggestions(sender, buffer);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void sendSenderMessage(Object sender, ComponentLike message) {
|
||||
Log.info(Chat.chatComponent(message).getLegacyText());
|
||||
}
|
||||
}
|
@@ -1,10 +1,12 @@
|
||||
package fr.pandacube.lib.cli;
|
||||
package fr.pandacube.lib.cli.log;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import fr.pandacube.lib.cli.CLI;
|
||||
import fr.pandacube.lib.util.Log;
|
||||
import net.md_5.bungee.log.ColouredWriter;
|
||||
import net.md_5.bungee.log.ConciseFormatter;
|
||||
import net.md_5.bungee.log.LoggingOutputStream;
|
||||
@@ -13,7 +15,7 @@ public class CLILogger {
|
||||
|
||||
private static Logger logger = null;
|
||||
|
||||
/* package */ static synchronized Logger getLogger(CLI cli) {
|
||||
public static synchronized Logger getLogger(CLI cli) {
|
||||
if (logger == null) {
|
||||
logger = Logger.getGlobal();
|
||||
logger.setLevel(Level.ALL);
|
||||
@@ -30,7 +32,8 @@ public class CLILogger {
|
||||
|
||||
System.setErr(new PrintStream(new LoggingOutputStream(logger, Level.SEVERE), true));
|
||||
System.setOut(new PrintStream(new LoggingOutputStream(logger, Level.INFO), true));
|
||||
|
||||
|
||||
Log.setLogger(logger);
|
||||
}
|
||||
return logger;
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package fr.pandacube.lib.cli;
|
||||
package fr.pandacube.lib.cli.log;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
Reference in New Issue
Block a user