Improved pandalib-cli
- Custom class to represent command sender - Abstract class to use as a base for CLI application main class - /stop and /admin command that are common to every CLI apps
This commit is contained in:
parent
d5c9876734
commit
efcb155b3d
@ -0,0 +1,103 @@
|
||||
package fr.pandacube.lib.cli;
|
||||
|
||||
import fr.pandacube.lib.cli.commands.CommandAdmin;
|
||||
import fr.pandacube.lib.cli.commands.CommandStop;
|
||||
import fr.pandacube.lib.util.Log;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Main class of a CLI application.
|
||||
*/
|
||||
public abstract class CLIApplication {
|
||||
|
||||
private static CLIApplication instance;
|
||||
|
||||
public static CLIApplication getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public final CLI cli;
|
||||
|
||||
protected CLIApplication() {
|
||||
instance = this;
|
||||
CLI tmpCLI = null;
|
||||
try {
|
||||
tmpCLI = new CLI();
|
||||
Log.setLogger(tmpCLI.getLogger());
|
||||
} catch (Throwable t) {
|
||||
System.err.println("Unable to start application " + getName() + " version " + getClass().getPackage().getImplementationVersion());
|
||||
t.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
cli = tmpCLI;
|
||||
|
||||
try {
|
||||
Log.info("Starting " + getName() + " version " + getClass().getPackage().getImplementationVersion());
|
||||
|
||||
start();
|
||||
|
||||
new CommandAdmin();
|
||||
new CommandStop();
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(this::end));
|
||||
|
||||
cli.start(); // actually starts the CLI thread
|
||||
|
||||
Log.info("Application started.");
|
||||
} catch (Throwable t) {
|
||||
Log.severe("Unable to start application " + getName() + " version " + getClass().getPackage().getImplementationVersion(), t);
|
||||
}
|
||||
}
|
||||
|
||||
public Logger getLogger() {
|
||||
return cli.getLogger();
|
||||
}
|
||||
|
||||
|
||||
private final Object stopLock = new Object();
|
||||
private final AtomicBoolean stopping = new AtomicBoolean(false);
|
||||
|
||||
public final void stop() {
|
||||
synchronized (stopLock) {
|
||||
synchronized (stopping) {
|
||||
if (stopping.get())
|
||||
return;
|
||||
stopping.set(true);
|
||||
}
|
||||
Log.info("Stopping " + getName() + " version " + getClass().getPackage().getImplementationVersion());
|
||||
try {
|
||||
end();
|
||||
} catch (Throwable t) {
|
||||
Log.severe("Error stopping application " + getName() + " version " + getClass().getPackage().getImplementationVersion(), t);
|
||||
} finally {
|
||||
Log.info("Bye bye.");
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean isStopping() {
|
||||
return stopping.get();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public abstract String getName();
|
||||
|
||||
protected abstract void start() throws Exception;
|
||||
|
||||
public abstract void reload();
|
||||
|
||||
protected abstract void end();
|
||||
|
||||
|
||||
}
|
@ -12,13 +12,13 @@ import java.util.function.Predicate;
|
||||
/**
|
||||
* Abstract class that holds the logic of a specific command to be registered in {@link CLIBrigadierDispatcher}.
|
||||
*/
|
||||
public abstract class CLIBrigadierCommand extends BrigadierCommand<Object> {
|
||||
public abstract class CLIBrigadierCommand extends BrigadierCommand<CLICommandSender> {
|
||||
|
||||
/**
|
||||
* Instanciate this command instance.
|
||||
*/
|
||||
public CLIBrigadierCommand() {
|
||||
LiteralCommandNode<Object> commandNode = buildCommand().build();
|
||||
LiteralCommandNode<CLICommandSender> commandNode = buildCommand().build();
|
||||
postBuildCommand(commandNode);
|
||||
String[] aliases = getAliases();
|
||||
if (aliases == null)
|
||||
@ -37,7 +37,7 @@ public abstract class CLIBrigadierCommand extends BrigadierCommand<Object> {
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract LiteralArgumentBuilder<Object> buildCommand();
|
||||
protected abstract LiteralArgumentBuilder<CLICommandSender> buildCommand();
|
||||
|
||||
protected String[] getAliases() {
|
||||
return new String[0];
|
||||
@ -47,16 +47,16 @@ public abstract class CLIBrigadierCommand extends BrigadierCommand<Object> {
|
||||
|
||||
|
||||
|
||||
public boolean isPlayer(Object sender) {
|
||||
return false;
|
||||
public boolean isPlayer(CLICommandSender sender) {
|
||||
return sender.isPlayer();
|
||||
}
|
||||
|
||||
public boolean isConsole(Object sender) {
|
||||
return true;
|
||||
public boolean isConsole(CLICommandSender sender) {
|
||||
return sender.isConsole();
|
||||
}
|
||||
|
||||
public Predicate<Object> hasPermission(String permission) {
|
||||
return sender -> true;
|
||||
public Predicate<CLICommandSender> hasPermission(String permission) {
|
||||
return sender -> sender.hasPermission(permission);
|
||||
}
|
||||
|
||||
|
||||
@ -68,7 +68,7 @@ public abstract class CLIBrigadierCommand extends BrigadierCommand<Object> {
|
||||
* @param suggestions the suggestions to wrap.
|
||||
* @return a {@link SuggestionProvider} generating the suggestions from the provided {@link SuggestionsSupplier}.
|
||||
*/
|
||||
protected SuggestionProvider<Object> wrapSuggestions(SuggestionsSupplier<Object> suggestions) {
|
||||
protected SuggestionProvider<CLICommandSender> wrapSuggestions(SuggestionsSupplier<CLICommandSender> suggestions) {
|
||||
return wrapSuggestions(suggestions, Function.identity());
|
||||
}
|
||||
|
||||
|
@ -1,20 +1,17 @@
|
||||
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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementation of {@link BrigadierDispatcher} that integrates the commands into the JLine CLI interface.
|
||||
*/
|
||||
public class CLIBrigadierDispatcher extends BrigadierDispatcher<Object> implements Completer {
|
||||
public class CLIBrigadierDispatcher extends BrigadierDispatcher<CLICommandSender> implements Completer {
|
||||
|
||||
/**
|
||||
* The instance of {@link CLIBrigadierDispatcher}.
|
||||
@ -22,16 +19,16 @@ public class CLIBrigadierDispatcher extends BrigadierDispatcher<Object> implemen
|
||||
public static final CLIBrigadierDispatcher instance = new CLIBrigadierDispatcher();
|
||||
|
||||
|
||||
private static final Object sender = new Object();
|
||||
private static final CLICommandSender CLI_CONSOLE_COMMAND_SENDER = new CLIConsoleCommandSender();
|
||||
|
||||
|
||||
/**
|
||||
* Executes the provided command.
|
||||
* Executes the provided command as the console.
|
||||
* @param commandWithoutSlash the command, without the eventual slash at the begining.
|
||||
* @return the value returned by the executed command.
|
||||
*/
|
||||
public int execute(String commandWithoutSlash) {
|
||||
return execute(sender, commandWithoutSlash);
|
||||
return execute(CLI_CONSOLE_COMMAND_SENDER, commandWithoutSlash);
|
||||
}
|
||||
|
||||
|
||||
@ -50,17 +47,17 @@ public class CLIBrigadierDispatcher extends BrigadierDispatcher<Object> implemen
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the suggestions for the currently being typed command.
|
||||
* Gets the suggestions for the currently being typed command, as the console.
|
||||
* @param buffer the command that is being typed.
|
||||
* @return the suggestions for the currently being typed command.
|
||||
*/
|
||||
public Suggestions getSuggestions(String buffer) {
|
||||
return getSuggestions(sender, buffer);
|
||||
return getSuggestions(CLI_CONSOLE_COMMAND_SENDER, buffer);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void sendSenderMessage(Object sender, ComponentLike message) {
|
||||
Log.info(Chat.chatComponent(message).getLegacyText());
|
||||
protected void sendSenderMessage(CLICommandSender sender, ComponentLike message) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,45 @@
|
||||
package fr.pandacube.lib.cli.commands;
|
||||
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.audience.MessageType;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
/**
|
||||
* A command sender.
|
||||
*/
|
||||
public interface CLICommandSender extends Audience {
|
||||
/**
|
||||
* Gets the name of the sender.
|
||||
* @return The name of the sender.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Tells if the sender is a player.
|
||||
* @return true if the sender is a player, false otherwise.
|
||||
*/
|
||||
boolean isPlayer();
|
||||
|
||||
/**
|
||||
* Tells if the sender is on the console.
|
||||
* @return true if the sender is on the console, false otherwise.
|
||||
*/
|
||||
boolean isConsole();
|
||||
|
||||
/**
|
||||
* Tells if the sender has the specified permission.
|
||||
* @param permission the permission to test on the sender.
|
||||
* @return true if the sender has the specified permission.
|
||||
*/
|
||||
boolean hasPermission(String permission);
|
||||
|
||||
/**
|
||||
* Sends the provided message to the sender.
|
||||
* @param message the message to send.
|
||||
*/
|
||||
void sendMessage(String message);
|
||||
|
||||
@Override // force implementation of super-interface default method
|
||||
void sendMessage(Identity source, Component message, MessageType type);
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package fr.pandacube.lib.cli.commands;
|
||||
|
||||
import fr.pandacube.lib.chat.Chat;
|
||||
import fr.pandacube.lib.util.Log;
|
||||
import net.kyori.adventure.audience.MessageType;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
/**
|
||||
* The console command sender.
|
||||
*/
|
||||
public class CLIConsoleCommandSender implements CLICommandSender {
|
||||
public String getName() {
|
||||
return "Console";
|
||||
}
|
||||
|
||||
public boolean isPlayer() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isConsole() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean hasPermission(String permission) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void sendMessage(String message) {
|
||||
Log.info(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(Identity source, Component message, MessageType type) {
|
||||
sendMessage(Chat.chatComponent(message).getLegacyText());
|
||||
}
|
||||
}
|
@ -0,0 +1,273 @@
|
||||
package fr.pandacube.lib.cli.commands;
|
||||
|
||||
import com.mojang.brigadier.arguments.ArgumentType;
|
||||
import com.mojang.brigadier.arguments.BoolArgumentType;
|
||||
import com.mojang.brigadier.arguments.DoubleArgumentType;
|
||||
import com.mojang.brigadier.arguments.FloatArgumentType;
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.arguments.LongArgumentType;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.tree.ArgumentCommandNode;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import com.mojang.brigadier.tree.RootCommandNode;
|
||||
import fr.pandacube.lib.chat.Chat;
|
||||
import fr.pandacube.lib.chat.Chat.FormatableChat;
|
||||
import fr.pandacube.lib.chat.ChatTreeNode;
|
||||
import fr.pandacube.lib.cli.CLIApplication;
|
||||
import fr.pandacube.lib.util.Log;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static fr.pandacube.lib.chat.ChatStatic.chat;
|
||||
import static fr.pandacube.lib.chat.ChatStatic.failureText;
|
||||
import static fr.pandacube.lib.chat.ChatStatic.successText;
|
||||
import static fr.pandacube.lib.chat.ChatStatic.text;
|
||||
|
||||
public class CommandAdmin extends CLIBrigadierCommand {
|
||||
|
||||
@Override
|
||||
protected LiteralArgumentBuilder<CLICommandSender> buildCommand() {
|
||||
return literal("admin")
|
||||
.executes(this::version)
|
||||
.then(literal("version")
|
||||
.executes(this::version)
|
||||
)
|
||||
.then(literal("reload")
|
||||
.executes(this::reload)
|
||||
)
|
||||
.then(literal("debug")
|
||||
.executes(this::debug)
|
||||
)
|
||||
.then(literal("commandstruct")
|
||||
.executes(this::commandStruct)
|
||||
.then(argument("path", StringArgumentType.greedyString())
|
||||
.executes(this::commandStruct)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private int version(CommandContext<CLICommandSender> context) {
|
||||
Log.info(chat()
|
||||
.console(context.getSource().isConsole())
|
||||
.infoColor()
|
||||
.thenCenterText(text(CLIApplication.getInstance().getName()))
|
||||
.thenNewLine()
|
||||
.thenText("- Implem. version: ")
|
||||
.thenData(CLIApplication.getInstance().getClass().getPackage().getImplementationVersion())
|
||||
.thenNewLine()
|
||||
.thenText("- Spec. version: ")
|
||||
.thenData(CLIApplication.getInstance().getClass().getPackage().getSpecificationVersion())
|
||||
.getLegacyText());
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private int reload(CommandContext<CLICommandSender> context) {
|
||||
CLIApplication.getInstance().reload();
|
||||
return 1;
|
||||
}
|
||||
|
||||
private int debug(CommandContext<CLICommandSender> context) {
|
||||
Log.setDebug(!Log.isDebugEnabled());
|
||||
Log.info(successText("Mode débug "
|
||||
+ (Log.isDebugEnabled() ? "" : "dés") + "activé").getLegacyText());
|
||||
return 1;
|
||||
}
|
||||
|
||||
private int commandStruct(CommandContext<CLICommandSender> context) {
|
||||
CLICommandSender sender = context.getSource();
|
||||
String[] tokens = tryGetArgument(context, "path", String.class, s -> s.split(" "), new String[0]);
|
||||
|
||||
CommandNode<CLICommandSender> node = CLIBrigadierDispatcher.instance.getDispatcher().findNode(Arrays.asList(tokens));
|
||||
|
||||
if (node == null) {
|
||||
Log.severe(failureText("La commande spécifiée n’a pas été trouvée.").getLegacyText());
|
||||
return 0;
|
||||
}
|
||||
|
||||
Set<CommandNode<CLICommandSender>> scannedNodes = new HashSet<>();
|
||||
DisplayCommandNode displayNode = new DisplayCommandNode();
|
||||
|
||||
// find parent nodes of scanned node to avoid displaying them after redirection and stuff
|
||||
for (int i = 1; i < tokens.length; i++) {
|
||||
CommandNode<CLICommandSender> ignoredNode = CLIBrigadierDispatcher.instance.getDispatcher().findNode(Arrays.asList(Arrays.copyOf(tokens, i)));
|
||||
if (ignoredNode != null) {
|
||||
displayNode.addInline(ignoredNode);
|
||||
scannedNodes.add(ignoredNode);
|
||||
}
|
||||
}
|
||||
|
||||
buildDisplayCommandTree(displayNode, scannedNodes, node);
|
||||
|
||||
ChatTreeNode displayTreeNode = buildDisplayTree(displayNode, sender);
|
||||
for (Chat comp : displayTreeNode.render(true))
|
||||
Log.info(comp.getLegacyText());
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private void buildDisplayCommandTree(DisplayCommandNode displayNode, Set<CommandNode<CLICommandSender>> scannedNodes, CommandNode<CLICommandSender> node) {
|
||||
displayNode.addInline(node);
|
||||
|
||||
scannedNodes.add(node);
|
||||
|
||||
if (node.getRedirect() != null) {
|
||||
if (scannedNodes.contains(node.getRedirect()) || node.getRedirect() instanceof RootCommandNode) {
|
||||
displayNode.addInline(node.getRedirect());
|
||||
}
|
||||
else {
|
||||
buildDisplayCommandTree(displayNode, scannedNodes, node.getRedirect());
|
||||
}
|
||||
}
|
||||
else if (node.getChildren().size() == 1) {
|
||||
buildDisplayCommandTree(displayNode, scannedNodes, node.getChildren().iterator().next());
|
||||
}
|
||||
else if (node.getChildren().size() >= 2) {
|
||||
for (CommandNode<CLICommandSender> child : node.getChildren()) {
|
||||
DisplayCommandNode dNode = new DisplayCommandNode();
|
||||
buildDisplayCommandTree(dNode, scannedNodes, child);
|
||||
displayNode.addChild(dNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private ChatTreeNode buildDisplayTree(DisplayCommandNode displayNode, CLICommandSender sender) {
|
||||
Chat d = chat().then(displayCurrentNode(displayNode.nodes.get(0), false, sender));
|
||||
|
||||
CommandNode<CLICommandSender> prevNode = displayNode.nodes.get(0);
|
||||
for (int i = 1; i < displayNode.nodes.size(); i++) {
|
||||
CommandNode<CLICommandSender> currNode = displayNode.nodes.get(i);
|
||||
if (currNode.equals(prevNode.getRedirect())) {
|
||||
d.then(text(" → ")
|
||||
.hover("Redirects to path: " + CLIBrigadierDispatcher.instance.getDispatcher().getPath(currNode))
|
||||
);
|
||||
d.then(displayCurrentNode(currNode, true, sender));
|
||||
}
|
||||
else {
|
||||
d.thenText(" ");
|
||||
d.then(displayCurrentNode(currNode, false, sender));
|
||||
}
|
||||
prevNode = currNode;
|
||||
}
|
||||
|
||||
|
||||
ChatTreeNode dispTree = new ChatTreeNode(d);
|
||||
|
||||
for (DisplayCommandNode child : displayNode.children) {
|
||||
dispTree.addChild(buildDisplayTree(child, sender));
|
||||
}
|
||||
|
||||
return dispTree;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private BaseComponent displayCurrentNode(CommandNode<CLICommandSender> node, boolean redirectTarget, CLICommandSender sender) {
|
||||
if (node == null)
|
||||
throw new IllegalArgumentException("node must not be null");
|
||||
FormatableChat d;
|
||||
if (node instanceof RootCommandNode) {
|
||||
d = text("(root)").italic()
|
||||
.hover("Root command node");
|
||||
}
|
||||
else if (node instanceof ArgumentCommandNode) {
|
||||
ArgumentType<?> type = ((ArgumentCommandNode<?, ?>) node).getType();
|
||||
String typeStr = type.getClass().getSimpleName();
|
||||
if (type instanceof IntegerArgumentType
|
||||
|| type instanceof LongArgumentType
|
||||
|| type instanceof FloatArgumentType
|
||||
|| type instanceof DoubleArgumentType) {
|
||||
typeStr = type.toString();
|
||||
}
|
||||
else if (type instanceof BoolArgumentType) {
|
||||
typeStr = "bool()";
|
||||
}
|
||||
else if (type instanceof StringArgumentType) {
|
||||
typeStr = "string(" + ((StringArgumentType) type).getType().name().toLowerCase() + ")";
|
||||
}
|
||||
String t = "<" + node.getName() + ">";
|
||||
String h = "Argument command node"
|
||||
+ "\nType: " + typeStr;
|
||||
|
||||
if (node.getCommand() != null) {
|
||||
t += "®";
|
||||
h += "\nThis node has a command";
|
||||
}
|
||||
|
||||
d = text(t);
|
||||
|
||||
if (!node.canUse(sender)) {
|
||||
d.gray();
|
||||
h += "\nPermission not granted for you";
|
||||
}
|
||||
|
||||
d.hover(h);
|
||||
}
|
||||
else if (node instanceof LiteralCommandNode) {
|
||||
String t = node.getName();
|
||||
String h = "Literal command node";
|
||||
|
||||
if (node.getCommand() != null) {
|
||||
t += "®";
|
||||
h += "\nThis node has a command";
|
||||
}
|
||||
|
||||
d = text(t);
|
||||
|
||||
if (!node.canUse(sender)) {
|
||||
d.gray();
|
||||
h += "\nPermission not granted for you";
|
||||
}
|
||||
|
||||
d.hover(h);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unknown command node type: " + node.getClass());
|
||||
}
|
||||
|
||||
if (redirectTarget)
|
||||
d.gray();
|
||||
return d.get();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private static class DisplayCommandNode {
|
||||
List<CommandNode<CLICommandSender>> nodes = new ArrayList<>();
|
||||
List<DisplayCommandNode> children = new ArrayList<>();
|
||||
|
||||
void addInline(CommandNode<CLICommandSender> node) {
|
||||
nodes.add(node);
|
||||
}
|
||||
|
||||
void addChild(DisplayCommandNode child) {
|
||||
children.add(child);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package fr.pandacube.lib.cli.commands;
|
||||
|
||||
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import fr.pandacube.lib.cli.CLIApplication;
|
||||
|
||||
/**
|
||||
* /stop (/end) command.
|
||||
*/
|
||||
public class CommandStop extends CLIBrigadierCommand {
|
||||
|
||||
@Override
|
||||
protected LiteralArgumentBuilder<CLICommandSender> buildCommand() {
|
||||
return literal("stop")
|
||||
.executes(context -> {
|
||||
CLIApplication.getInstance().stop();
|
||||
return 1;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getAliases() {
|
||||
return new String[] { "end" };
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user