✨ Add intermediary command executors.
This allows for command executors along the entire command chain, such that `/command`and `/command subcommand` may both be executed.
This commit is contained in:
parent
64fa3430a9
commit
0d44a8c944
8 changed files with 160 additions and 23 deletions
4
build.sh
4
build.sh
|
|
@ -3,4 +3,6 @@
|
|||
# Make sure all submodules are initialized
|
||||
git submodule update --remote
|
||||
# Package all jars
|
||||
./mvnw clean package
|
||||
|
||||
export MAVEN_OPTS="-XX:+TieredCompilation -XX:TieredStopAtLevel=1"
|
||||
./mvnw -T1C clean package -DskipTests=true
|
||||
|
|
|
|||
|
|
@ -226,6 +226,16 @@ public class Command<C> {
|
|||
return this.arguments.get(argument).getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
final StringBuilder stringBuilder = new StringBuilder();
|
||||
for (final CommandArgument<C, ?> argument : this.getArguments()) {
|
||||
stringBuilder.append(argument.getName()).append(' ');
|
||||
}
|
||||
final String build = stringBuilder.toString();
|
||||
return build.substring(0, build.length() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether or not the command is hidden
|
||||
*
|
||||
|
|
@ -274,7 +284,7 @@ public class Command<C> {
|
|||
*/
|
||||
@Nonnull
|
||||
public Builder<C> meta(@Nonnull final String key, @Nonnull final String value) {
|
||||
final CommandMeta commandMeta = SimpleCommandMeta.builder().with(this.commandMeta).build();
|
||||
final CommandMeta commandMeta = SimpleCommandMeta.builder().with(this.commandMeta).with(key, value).build();
|
||||
return new Builder<>(this.commandManager, commandMeta, this.senderType, this.commandArguments,
|
||||
this.commandExecutionHandler, this.commandPermission);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ import javax.annotation.Nonnull;
|
|||
import javax.annotation.Nullable;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -73,12 +74,13 @@ import java.util.function.Function;
|
|||
@SuppressWarnings("unused")
|
||||
public abstract class CommandManager<C> {
|
||||
|
||||
private final Map<Class<? extends Exception>, BiConsumer<C, ? extends Exception>> exceptionHandlers = Maps.newHashMap();
|
||||
private final EnumSet<ManagerSettings> managerSettings = EnumSet.of(ManagerSettings.ENFORCE_INTERMEDIARY_PERMISSIONS);
|
||||
|
||||
private final CommandContextFactory<C> commandContextFactory = new StandardCommandContextFactory<>();
|
||||
private final ServicePipeline servicePipeline = ServicePipeline.builder().build();
|
||||
private final ParserRegistry<C> parserRegistry = new StandardParserRegistry<>();
|
||||
private final Map<Class<? extends Exception>, BiConsumer<C, ? extends Exception>> exceptionHandlers = Maps.newHashMap();
|
||||
private final Collection<Command<C>> commands = Lists.newLinkedList();
|
||||
|
||||
private final CommandExecutionCoordinator<C> commandExecutionCoordinator;
|
||||
private final CommandTree<C> commandTree;
|
||||
|
||||
|
|
@ -552,4 +554,42 @@ public abstract class CommandManager<C> {
|
|||
return new CommandHelpHandler<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a command manager setting
|
||||
*
|
||||
* @param setting Setting
|
||||
* @return {@code true} if the setting is activated or {@code false} if it's not
|
||||
*/
|
||||
public boolean getSetting(@Nonnull final ManagerSettings setting) {
|
||||
return this.managerSettings.contains(setting);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the setting
|
||||
*
|
||||
* @param setting Setting to set
|
||||
* @param value Value
|
||||
*/
|
||||
public void setSetting(@Nonnull final ManagerSettings setting,
|
||||
final boolean value) {
|
||||
if (value) {
|
||||
this.managerSettings.add(setting);
|
||||
} else {
|
||||
this.managerSettings.remove(setting);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configurable command related settings
|
||||
*/
|
||||
public enum ManagerSettings {
|
||||
/**
|
||||
* Do not create a compound permission and do not look greedily
|
||||
* for child permission values, if a preceding command in the tree path
|
||||
* has a command handler attached
|
||||
*/
|
||||
ENFORCE_INTERMEDIARY_PERMISSIONS
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,8 +190,7 @@ public final class CommandTree<C> {
|
|||
if (result.getParsedValue().isPresent()) {
|
||||
parsedArguments.add(child.getValue());
|
||||
return this.parseCommand(parsedArguments, commandContext, commandQueue, child);
|
||||
} /*else if (result.getFailure().isPresent() && root.children.size() == 1) {
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -201,7 +200,21 @@ public final class CommandTree<C> {
|
|||
getChain(root).stream().map(Node::getValue).collect(Collectors.toList()),
|
||||
stringOrEmpty(commandQueue.peek()));
|
||||
}
|
||||
/* We have already traversed the tree */
|
||||
/* If we couldn't match a child, check if there's a command attached and execute it */
|
||||
if (root.getValue() != null && root.getValue().getOwningCommand() != null) {
|
||||
final Command<C> command = root.getValue().getOwningCommand();
|
||||
if (!this.getCommandManager().hasPermission(commandContext.getSender(),
|
||||
command.getCommandPermission())) {
|
||||
throw new NoPermissionException(command.getCommandPermission(),
|
||||
commandContext.getSender(),
|
||||
this.getChain(root)
|
||||
.stream()
|
||||
.map(Node::getValue)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
return Optional.of(root.getValue().getOwningCommand());
|
||||
}
|
||||
/* We know that there's no command and we also cannot match any of the children */
|
||||
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter()
|
||||
.apply(parsedArguments, root),
|
||||
commandContext.getSender(), this.getChain(root)
|
||||
|
|
@ -235,6 +248,20 @@ public final class CommandTree<C> {
|
|||
} else if (!child.getValue().isRequired()) {
|
||||
return Optional.ofNullable(this.cast(child.getValue().getOwningCommand()));
|
||||
} else if (child.isLeaf()) {
|
||||
/* The child is not a leaf, but may have an intermediary executor, attempt to use it */
|
||||
if (root.getValue() != null && root.getValue().getOwningCommand() != null) {
|
||||
final Command<C> command = root.getValue().getOwningCommand();
|
||||
if (!this.getCommandManager().hasPermission(commandContext.getSender(),
|
||||
command.getCommandPermission())) {
|
||||
throw new NoPermissionException(command.getCommandPermission(),
|
||||
commandContext.getSender(),
|
||||
this.getChain(root)
|
||||
.stream()
|
||||
.map(Node::getValue)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
return Optional.of(command);
|
||||
}
|
||||
/* Not enough arguments */
|
||||
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter()
|
||||
.apply(Objects.requireNonNull(
|
||||
|
|
@ -245,13 +272,21 @@ public final class CommandTree<C> {
|
|||
.map(Node::getValue)
|
||||
.collect(Collectors.toList()));
|
||||
} else {
|
||||
/*
|
||||
throw new NoSuchCommandException(commandContext.getSender(),
|
||||
this.getChain(root)
|
||||
.stream()
|
||||
.map(Node::getValue)
|
||||
.collect(Collectors.toList()),
|
||||
"");*/
|
||||
/* The child is not a leaf, but may have an intermediary executor, attempt to use it */
|
||||
if (root.getValue() != null && root.getValue().getOwningCommand() != null) {
|
||||
final Command<C> command = root.getValue().getOwningCommand();
|
||||
if (!this.getCommandManager().hasPermission(commandContext.getSender(),
|
||||
command.getCommandPermission())) {
|
||||
throw new NoPermissionException(command.getCommandPermission(),
|
||||
commandContext.getSender(),
|
||||
this.getChain(root)
|
||||
.stream()
|
||||
.map(Node::getValue)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
return Optional.of(command);
|
||||
}
|
||||
/* Child does not have a command and so we cannot proceed */
|
||||
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter()
|
||||
.apply(parsedArguments, root),
|
||||
commandContext.getSender(), this.getChain(root)
|
||||
|
|
@ -357,8 +392,7 @@ public final class CommandTree<C> {
|
|||
final ArgumentParseResult<?> result = child.getValue().getParser().parse(commandContext, commandQueue);
|
||||
if (result.getParsedValue().isPresent()) {
|
||||
return this.getSuggestions(commandContext, commandQueue, child);
|
||||
} /* else if (result.getFailure().isPresent() && root.children.size() == 1) {
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -407,6 +441,12 @@ public final class CommandTree<C> {
|
|||
node = tempNode;
|
||||
}
|
||||
if (node.getValue() != null) {
|
||||
if (node.getValue().getOwningCommand() != null) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"Duplicate command chains detected. Node '%s' already has an owning command (%s)",
|
||||
node.toString(), node.getValue().getOwningCommand().toString()
|
||||
));
|
||||
}
|
||||
node.getValue().setOwningCommand(command);
|
||||
}
|
||||
// Verify the command structure every time we add a new command
|
||||
|
|
@ -474,7 +514,6 @@ public final class CommandTree<C> {
|
|||
// noinspection all
|
||||
final CommandPermission commandPermission = node.getValue().getOwningCommand().getCommandPermission();
|
||||
/* All leaves must necessarily have an owning command */
|
||||
// noinspection all
|
||||
node.nodeMeta.put("permission", commandPermission);
|
||||
// Get chain and order it tail->head then skip the tail (leaf node)
|
||||
List<Node<CommandArgument<C, ?>>> chain = this.getChain(node);
|
||||
|
|
@ -483,12 +522,25 @@ public final class CommandTree<C> {
|
|||
// Go through all nodes from the tail upwards until a collision occurs
|
||||
for (final Node<CommandArgument<C, ?>> commandArgumentNode : chain) {
|
||||
final CommandPermission existingPermission = (CommandPermission) commandArgumentNode.nodeMeta.get("permission");
|
||||
|
||||
CommandPermission permission;
|
||||
if (existingPermission != null) {
|
||||
commandArgumentNode.nodeMeta.put("permission",
|
||||
OrPermission.of(Arrays.asList(commandPermission, existingPermission)));
|
||||
permission = OrPermission.of(Arrays.asList(commandPermission, existingPermission));
|
||||
} else {
|
||||
commandArgumentNode.nodeMeta.put("permission", commandPermission);
|
||||
permission = commandPermission;
|
||||
}
|
||||
|
||||
/* Now also check if there's a command handler attached to an upper level node */
|
||||
if (commandArgumentNode.getValue() != null && commandArgumentNode.getValue().getOwningCommand() != null) {
|
||||
final Command<C> command = commandArgumentNode.getValue().getOwningCommand();
|
||||
if (this.getCommandManager().getSetting(CommandManager.ManagerSettings.ENFORCE_INTERMEDIARY_PERMISSIONS)) {
|
||||
permission = command.getCommandPermission();
|
||||
} else {
|
||||
permission = OrPermission.of(Arrays.asList(permission, command.getCommandPermission()));
|
||||
}
|
||||
}
|
||||
|
||||
commandArgumentNode.nodeMeta.put("permission", permission);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ class CommandTreeTest {
|
|||
@BeforeAll
|
||||
static void newTree() {
|
||||
manager = new TestCommandManager();
|
||||
|
||||
/* Build general test commands */
|
||||
manager.command(manager.commandBuilder("test", SimpleCommandMeta.empty())
|
||||
.literal("one").build())
|
||||
.command(manager.commandBuilder("test", SimpleCommandMeta.empty())
|
||||
|
|
@ -57,6 +59,8 @@ class CommandTreeTest {
|
|||
.optional("num", EXPECTED_INPUT_NUMBER))
|
||||
.build())
|
||||
.command(manager.commandBuilder("req").withSenderType(SpecificCommandSender.class).build());
|
||||
|
||||
/* Build command to test command proxying */
|
||||
final Command<TestCommandSender> toProxy = manager.commandBuilder("test")
|
||||
.literal("unproxied")
|
||||
.argument(StringArgument.required("string"))
|
||||
|
|
@ -66,6 +70,17 @@ class CommandTreeTest {
|
|||
.build();
|
||||
manager.command(toProxy);
|
||||
manager.command(manager.commandBuilder("proxy").proxies(toProxy).build());
|
||||
|
||||
/* Build command for testing intermediary and final executors */
|
||||
manager.command(manager.commandBuilder("command")
|
||||
.withPermission("command.inner")
|
||||
.literal("inner")
|
||||
.handler(c -> System.out.println("Using inner command"))
|
||||
.build());
|
||||
manager.command(manager.commandBuilder("command")
|
||||
.withPermission("command.outer")
|
||||
.handler(c -> System.out.println("Using outer command"))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -136,6 +151,12 @@ class CommandTreeTest {
|
|||
manager.executeCommand(new TestCommandSender(), "proxy foo 10").join();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIntermediary() {
|
||||
manager.executeCommand(new TestCommandSender(), "command inner").join();
|
||||
manager.executeCommand(new TestCommandSender(), "command").join();
|
||||
}
|
||||
|
||||
|
||||
public static final class SpecificCommandSender extends TestCommandSender {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ public class TestCommandManager extends CommandManager<TestCommandSender> {
|
|||
@Override
|
||||
public final boolean hasPermission(@Nonnull final TestCommandSender sender,
|
||||
@Nonnull final String permission) {
|
||||
System.out.printf("Testing permission: %s\n", permission);
|
||||
return !permission.equalsIgnoreCase("no");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import com.intellectualsites.commands.bukkit.BukkitCommandManager;
|
|||
import com.intellectualsites.commands.bukkit.BukkitCommandMetaBuilder;
|
||||
import com.intellectualsites.commands.bukkit.CloudBukkitCapabilities;
|
||||
import com.intellectualsites.commands.bukkit.parsers.WorldArgument;
|
||||
import com.intellectualsites.commands.exceptions.InvalidSyntaxException;
|
||||
import com.intellectualsites.commands.execution.AsynchronousCommandExecutionCoordinator;
|
||||
import com.intellectualsites.commands.execution.CommandExecutionCoordinator;
|
||||
import com.intellectualsites.commands.extra.confirmation.CommandConfirmationManager;
|
||||
|
|
@ -92,7 +93,7 @@ public final class BukkitTest extends JavaPlugin {
|
|||
mgr);
|
||||
|
||||
try {
|
||||
((PaperCommandManager<CommandSender>) mgr).registerBrigadier();
|
||||
mgr.registerBrigadier();
|
||||
} catch (final Exception e) {
|
||||
getLogger().warning("Failed to initialize Brigadier support: " + e.getMessage());
|
||||
}
|
||||
|
|
@ -116,7 +117,6 @@ public final class BukkitTest extends JavaPlugin {
|
|||
BukkitCommandMetaBuilder.builder().withDescription(p.get(StandardParameters.DESCRIPTION,
|
||||
"No description")).build());
|
||||
annotationParser.parse(this);
|
||||
//noinspection all
|
||||
|
||||
mgr.command(mgr.commandBuilder("gamemode", this.metaWithDescription("Your ugli"), "gajmöde")
|
||||
.argument(EnumArgument.required(GameMode.class, "gamemode"))
|
||||
|
|
@ -148,6 +148,9 @@ public final class BukkitTest extends JavaPlugin {
|
|||
));
|
||||
})
|
||||
.build())
|
||||
.command(mgr.commandBuilder("uuidtest")
|
||||
.handler(c -> c.getSender().sendMessage("Hey yo dum, provide a UUID idiot. Thx!"))
|
||||
.build())
|
||||
.command(mgr.commandBuilder("uuidtest")
|
||||
.argument(UUID.class, "uuid", builder -> builder
|
||||
.asRequired()
|
||||
|
|
@ -201,11 +204,14 @@ public final class BukkitTest extends JavaPlugin {
|
|||
.handler(confirmationManager.createConfirmationExecutionHandler()).build())
|
||||
.command(mgr.commandBuilder("cloud")
|
||||
.literal("help")
|
||||
.withPermission("cloud.help")
|
||||
.argument(StringArgument.<CommandSender>newBuilder("query").greedy()
|
||||
.asOptionalWithDefault("")
|
||||
.build(), Description.of("Help Query"))
|
||||
.handler(c -> minecraftHelp.queryCommands(c.<String>get("query").orElse(""),
|
||||
c.getSender())).build());
|
||||
|
||||
mgr.registerExceptionHandler(InvalidSyntaxException.class, (c, e) -> e.printStackTrace());
|
||||
} catch (final Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
|
@ -222,7 +228,7 @@ public final class BukkitTest extends JavaPlugin {
|
|||
}
|
||||
|
||||
@Confirmation
|
||||
@CommandMethod("cloud debug")
|
||||
@CommandMethod(value = "cloud", permission = "cloud.debug")
|
||||
private void doHelp() {
|
||||
final Set<CloudBukkitCapabilities> capabilities = this.mgr.queryCapabilities();
|
||||
Bukkit.broadcastMessage(ChatColor.GOLD + "" + ChatColor.BOLD + "Capabilities");
|
||||
|
|
|
|||
|
|
@ -155,4 +155,9 @@ final class BukkitCommand<C> extends org.bukkit.command.Command implements Plugi
|
|||
return this.cloudCommand.getCommandPermission().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return this.manager.getCommandSyntaxFormatter().apply(this.cloudCommand.getArguments(), null);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue