Param converter and command list with permission check
- Displaying and testing error message for param converters - Improved crash testing - Using has permission check for listing (sub)commands - Implemented and tested command list
This commit is contained in:
parent
401a54b078
commit
d16a6a742c
7 changed files with 107 additions and 65 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -225,3 +225,4 @@ dependency-reduced-pom.xml
|
|||
|
||||
TBMC/
|
||||
/.apt_generated/
|
||||
.attach_pid*
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
package buttondevteam.core;
|
||||
|
||||
import buttondevteam.lib.architecture.ButtonPlugin;
|
||||
import buttondevteam.lib.chat.Command2;
|
||||
import buttondevteam.lib.chat.CommandClass;
|
||||
import buttondevteam.lib.chat.ICommand2MC;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
@CommandClass
|
||||
public class ChromaCommand extends ICommand2MC {
|
||||
public ChromaCommand() {
|
||||
getManager().addParamConverter(ButtonPlugin.class, name ->
|
||||
(ButtonPlugin) Optional.ofNullable(Bukkit.getPluginManager().getPlugin(name))
|
||||
.filter(plugin -> plugin instanceof ButtonPlugin).orElse(null),
|
||||
"No Chroma plugin found by that name.", () -> Arrays.stream(Bukkit.getPluginManager().getPlugins())
|
||||
.filter(plugin -> plugin instanceof ButtonPlugin).map(Plugin::getName)::iterator);
|
||||
}
|
||||
|
||||
@Command2.Subcommand
|
||||
public void reload(CommandSender sender, @Command2.OptionalArg ButtonPlugin plugin) {
|
||||
if (plugin == null)
|
||||
plugin = getPlugin();
|
||||
if (plugin.tryReloadConfig())
|
||||
sender.sendMessage("${ChatColor.AQUA}" + plugin.getName() + " config reloaded.");
|
||||
else
|
||||
sender.sendMessage("${ChatColor.RED}Failed to reload config. Check console.");
|
||||
}
|
||||
|
||||
@Command2.Subcommand
|
||||
public void def(CommandSender sender) {
|
||||
sender.sendMessage(ButtonPlugin.getCommand2MC().getCommandsText());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package buttondevteam.core
|
||||
|
||||
import buttondevteam.lib.architecture.ButtonPlugin
|
||||
import buttondevteam.lib.architecture.ButtonPlugin.Companion.command2MC
|
||||
import buttondevteam.lib.chat.Command2.OptionalArg
|
||||
import buttondevteam.lib.chat.Command2.Subcommand
|
||||
import buttondevteam.lib.chat.Command2MCSender
|
||||
import buttondevteam.lib.chat.CommandClass
|
||||
import buttondevteam.lib.chat.ICommand2MC
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.command.CommandSender
|
||||
import java.util.*
|
||||
|
||||
@CommandClass
|
||||
class ChromaCommand : ICommand2MC() {
|
||||
init {
|
||||
manager.addParamConverter(
|
||||
ButtonPlugin::class.java, { name ->
|
||||
Bukkit.getPluginManager().getPlugin(name)
|
||||
?.let { if (it is ButtonPlugin) it else null }
|
||||
},
|
||||
"No Chroma plugin found by that name."
|
||||
) {
|
||||
Iterable {
|
||||
Arrays.stream(Bukkit.getPluginManager().plugins)
|
||||
.filter { it is ButtonPlugin }.map { it.name }.iterator()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subcommand
|
||||
fun reload(sender: CommandSender, @OptionalArg plugin: ButtonPlugin?) {
|
||||
val pl = plugin ?: this.plugin
|
||||
if (pl.tryReloadConfig())
|
||||
sender.sendMessage("${ChatColor.AQUA}${pl.name} config reloaded.")
|
||||
else
|
||||
sender.sendMessage("${ChatColor.RED}Failed to reload config. Check console.")
|
||||
}
|
||||
|
||||
@Subcommand
|
||||
override fun def(sender: Command2MCSender): Boolean {
|
||||
sender.sendMessage("${ChatColor.GOLD}---- Commands ----")
|
||||
sender.sendMessage(command2MC.getCommandList(sender))
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -90,7 +90,7 @@ object ChromaUtils {
|
|||
fun throwWhenTested(exception: Throwable, message: String) {
|
||||
if (isTest) {
|
||||
// Propagate exception back to the tests
|
||||
throw exception
|
||||
throw Exception(message, exception)
|
||||
} else {
|
||||
// Otherwise we don't run the code directly, so we need to handle this here
|
||||
TBMCCoreAPI.SendException(message, exception, MainPlugin.instance)
|
||||
|
|
|
@ -76,7 +76,6 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
|||
)
|
||||
|
||||
protected val paramConverters = HashMap<Class<*>, ParamConverter<*>>()
|
||||
private val commandHelp = ArrayList<String>() //Mainly needed by Discord
|
||||
private val dispatcher = CommandDispatcher<TP>()
|
||||
|
||||
/**
|
||||
|
@ -106,14 +105,14 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
|||
open fun handleCommand(sender: TP, commandline: String): Boolean {
|
||||
val results = dispatcher.parse(commandline.removePrefix("/"), sender)
|
||||
if (results.reader.canRead()) {
|
||||
if (results.context.nodes.isNotEmpty()) {
|
||||
return if (results.context.nodes.isNotEmpty()) {
|
||||
for ((node, ex) in results.exceptions) {
|
||||
sender.sendMessage("${ChatColor.RED}${ex.message}")
|
||||
executeHelpText(results.context.build(results.reader.string))
|
||||
}
|
||||
return true
|
||||
true
|
||||
} else {
|
||||
return false // Unknown command
|
||||
false // Unknown command
|
||||
}
|
||||
}
|
||||
val executeCommand: () -> Unit = {
|
||||
|
@ -252,10 +251,9 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
|||
return Triple(parent, mainCommand, split.last())
|
||||
}
|
||||
|
||||
private fun getSubcommandList(): (Any) -> Array<String> {
|
||||
return {
|
||||
arrayOf("TODO") // TODO: Subcommand list
|
||||
}
|
||||
fun getCommandList(sender: TP): Array<String> {
|
||||
return commandNodes.filter { it.data.hasPermission(sender) }
|
||||
.map { commandChar + it.data.fullPath }.toTypedArray()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -333,7 +331,9 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
|||
val node = context.nodes.lastOrNull()?.node ?: error("No nodes found when executing help text for ${context.input}!")
|
||||
val helpText = node.subcommandDataNoOp()?.getHelpText(context.source) ?: error("No subcommand data found when executing help text for ${context.input}")
|
||||
if (node.isCommand()) {
|
||||
val subs = getSubcommands(node.coreCommandNoOp()!!).map { commandChar + it.data.fullPath }.sorted()
|
||||
val subs = getSubcommands(node.coreCommandNoOp()!!)
|
||||
.filter { it.data.hasPermission(context.source) }
|
||||
.map { commandChar + it.data.fullPath }.sorted()
|
||||
val messages = if (subs.isNotEmpty()) {
|
||||
helpText + "${ChatColor.GOLD}---- Subcommands ----" + subs
|
||||
} else {
|
||||
|
@ -369,7 +369,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
|||
return 0
|
||||
}
|
||||
|
||||
val params = executeGetArguments(sd, context) ?: return executeHelpText(context)
|
||||
val params = executeGetArguments(sd, context, sender) ?: return executeHelpText(context)
|
||||
|
||||
// TODO: Varargs support? (colors?)
|
||||
// TODO: Character handling (strlen)
|
||||
|
@ -378,7 +378,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
|||
return 0
|
||||
}
|
||||
|
||||
private fun executeGetArguments(sd: SubcommandData<TC, TP>, context: CommandContext<TP>): MutableList<Any?>? {
|
||||
private fun executeGetArguments(sd: SubcommandData<TC, TP>, context: CommandContext<TP>, sender: TP): MutableList<Any?>? {
|
||||
val params = mutableListOf<Any?>()
|
||||
for (argument in sd.argumentsInOrder) {
|
||||
try {
|
||||
|
@ -387,9 +387,14 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
|||
params.add(userArgument)
|
||||
} else {
|
||||
val userArgument = context.getArgument(argument.name, String::class.java)
|
||||
val converter = paramConverters[argument.type]?.converter
|
||||
val converter = paramConverters[argument.type]
|
||||
?: error("No suitable converter found for ${argument.type} ${argument.name}")
|
||||
params.add(converter.apply(userArgument))
|
||||
val result = converter.converter.apply(userArgument)
|
||||
if (result == null) {
|
||||
sender.sendMessage("${ChatColor.RED}Error: ${converter.errormsg}")
|
||||
return null
|
||||
}
|
||||
params.add(result)
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
if (ChromaUtils.isTest && e.message?.contains("No such argument '${argument.name}' exists on this command") != true) {
|
||||
|
@ -431,15 +436,14 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
|||
}
|
||||
|
||||
abstract fun hasPermission(sender: TP, data: SubcommandData<TC, TP>): Boolean
|
||||
val commandsText: Array<String> get() = commandHelp.toTypedArray()
|
||||
|
||||
/**
|
||||
* Get all registered command nodes. This returns all registered Chroma commands with all the information about them.
|
||||
*
|
||||
* @return A set of command node objects containing the commands
|
||||
*/
|
||||
val commandNodes: Set<CoreCommandNode<TP, NoOpSubcommandData>>
|
||||
get() = dispatcher.root.children.mapNotNull { it.coreCommand<TP, NoOpSubcommandData>() }.toSet()
|
||||
val commandNodes: Set<CoreExecutableNode<TP, TC>>
|
||||
get() = getSubcommands(true, dispatcher.root).toSet()
|
||||
|
||||
/**
|
||||
* Get a node that belongs to the given command.
|
||||
|
@ -499,7 +503,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
|||
|
||||
private fun getSubcommands(
|
||||
deep: Boolean = true,
|
||||
root: CoreNoOpNode<TP>
|
||||
root: CommandNode<TP>
|
||||
): List<CoreExecutableNode<TP, TC>> {
|
||||
return root.children.mapNotNull { it.coreExecutable<TP, TC>() } +
|
||||
if (deep) root.children.flatMap { child -> child.coreCommand<_, NoOpSubcommandData>()?.let { getSubcommands(deep, it) } ?: emptyList() } else emptyList()
|
||||
|
|
|
@ -26,6 +26,11 @@ abstract class Command2MCCommands {
|
|||
@Command2.Subcommand
|
||||
fun playerFail(sender: Command2MCSender, player: TBMCPlayer) {
|
||||
}
|
||||
|
||||
@Command2.Subcommand
|
||||
fun errorTest(sender: Command2MCSender) {
|
||||
error("Hmm")
|
||||
}
|
||||
}
|
||||
|
||||
@CommandClass
|
||||
|
|
|
@ -38,8 +38,8 @@ class Command2MCTest {
|
|||
fun testRegisterCommand() {
|
||||
TestCommand.register()
|
||||
val nodes = ButtonPlugin.command2MC.commandNodes
|
||||
assert(nodes.size == 1)
|
||||
assert(nodes.first().literal == "test")
|
||||
assertEquals(4, nodes.size)
|
||||
assertEquals("test", nodes.first().literal)
|
||||
val coreExecutable = nodes.first().coreExecutable<Command2MCSender, TestCommand>()
|
||||
assertEquals(TestCommand::class.qualifiedName, coreExecutable?.data?.command?.let { it::class.qualifiedName }, "The command class name doesn't match or command is null")
|
||||
assertEquals("test", coreExecutable?.data?.argumentsInOrder?.firstOrNull()?.name, "Failed to get correct argument name")
|
||||
|
@ -108,11 +108,19 @@ class Command2MCTest {
|
|||
fun runCommandWithReceive(command: String): String {
|
||||
return withMessageReceive { ButtonPlugin.command2MC.handleCommand(this, command) }
|
||||
}
|
||||
|
||||
fun runFailingCommand(command: String) {
|
||||
assert(!ButtonPlugin.command2MC.handleCommand(this, command)) { "Could execute command $command that shouldn't work" }
|
||||
}
|
||||
|
||||
fun runCrashingCommand(command: String, errorCheck: (Throwable) -> Boolean) {
|
||||
assert(errorCheck(assertFails { ButtonPlugin.command2MC.handleCommand(this, command) })) { "Command exception failed test!" }
|
||||
}
|
||||
}
|
||||
sender.runCommand("/test hmm", TestCommand, "hmm")
|
||||
sender.runCommand("/noargtest", NoArgTestCommand, "TestPlayer")
|
||||
assertFails { ButtonPlugin.command2MC.handleCommand(sender, "/noargtest failing") }
|
||||
runFailingCommand(sender, "/erroringtest")
|
||||
sender.runCrashingCommand("/noargtest failing") { it.cause?.cause is IllegalStateException }
|
||||
sender.runFailingCommand("/erroringtest")
|
||||
sender.runCommand("/multiargtest test hmm mhm", MultiArgTestCommand, "hmmmhm")
|
||||
sender.runCommand("/multiargtest test2 true 19", MultiArgTestCommand, "true 19")
|
||||
|
||||
|
@ -123,11 +131,14 @@ class Command2MCTest {
|
|||
sender.runCommand("/multiargtest testoptionalmulti", MultiArgTestCommand, "false null")
|
||||
|
||||
sender.runCommand("/test plugin Chroma-Core", TestCommand, "Chroma-Core")
|
||||
assertFails { ButtonPlugin.command2MC.handleCommand(sender, "/test playerfail TestPlayer") }
|
||||
sender.runCrashingCommand("/test playerfail TestPlayer") { it.cause?.message == "No suitable converter found for class buttondevteam.lib.player.TBMCPlayer param1" }
|
||||
assertEquals("§cError: §cNo Chroma plugin found by that name.", sender.runCommandWithReceive("/test plugin asd"))
|
||||
sender.runCrashingCommand("/test errortest") { it.cause?.cause?.message === "Hmm" }
|
||||
|
||||
assertEquals("Test command\n" +
|
||||
"Used for testing\n" +
|
||||
"§6---- Subcommands ----\n" +
|
||||
"/test errortest\n" +
|
||||
"/test playerfail\n" +
|
||||
"/test plugin", sender.runCommandWithReceive("/test")
|
||||
)
|
||||
|
@ -143,16 +154,28 @@ class Command2MCTest {
|
|||
sender.runCommand("/testparams 12 34 56 78", TestParamsCommand, "12 34 56.0 78.0 Player0")
|
||||
assertEquals("§cExpected integer at position 11: ...estparams <--[HERE]", sender.runCommandWithReceive("/testparams asd 34 56 78"))
|
||||
// TODO: Change test when usage help is added
|
||||
|
||||
assertEquals(
|
||||
"/test\n" +
|
||||
"/noargtest\n" +
|
||||
"/testparams\n" +
|
||||
"/test plugin\n" +
|
||||
"/test playerfail\n" +
|
||||
"/test errortest\n" +
|
||||
"/noargtest failing\n" +
|
||||
"/multiargtest test\n" +
|
||||
"/multiargtest test2\n" +
|
||||
"/multiargtest testoptional\n" +
|
||||
"/multiargtest testoptionalmulti\n" +
|
||||
"/some test cmd\n" +
|
||||
"/some another cmd", ButtonPlugin.command2MC.getCommandList(sender).joinToString("\n")
|
||||
)
|
||||
}
|
||||
|
||||
private fun ICommand2MC.register() {
|
||||
MainPlugin.instance.registerCommand(this)
|
||||
}
|
||||
|
||||
private fun runFailingCommand(sender: Command2MCSender, command: String) {
|
||||
assert(!ButtonPlugin.command2MC.handleCommand(sender, command)) { "Could execute command $command that shouldn't work" }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var initialized = false
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue