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/
|
TBMC/
|
||||||
/.apt_generated/
|
/.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) {
|
fun throwWhenTested(exception: Throwable, message: String) {
|
||||||
if (isTest) {
|
if (isTest) {
|
||||||
// Propagate exception back to the tests
|
// Propagate exception back to the tests
|
||||||
throw exception
|
throw Exception(message, exception)
|
||||||
} else {
|
} else {
|
||||||
// Otherwise we don't run the code directly, so we need to handle this here
|
// Otherwise we don't run the code directly, so we need to handle this here
|
||||||
TBMCCoreAPI.SendException(message, exception, MainPlugin.instance)
|
TBMCCoreAPI.SendException(message, exception, MainPlugin.instance)
|
||||||
|
|
|
@ -76,7 +76,6 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
||||||
)
|
)
|
||||||
|
|
||||||
protected val paramConverters = HashMap<Class<*>, ParamConverter<*>>()
|
protected val paramConverters = HashMap<Class<*>, ParamConverter<*>>()
|
||||||
private val commandHelp = ArrayList<String>() //Mainly needed by Discord
|
|
||||||
private val dispatcher = CommandDispatcher<TP>()
|
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 {
|
open fun handleCommand(sender: TP, commandline: String): Boolean {
|
||||||
val results = dispatcher.parse(commandline.removePrefix("/"), sender)
|
val results = dispatcher.parse(commandline.removePrefix("/"), sender)
|
||||||
if (results.reader.canRead()) {
|
if (results.reader.canRead()) {
|
||||||
if (results.context.nodes.isNotEmpty()) {
|
return if (results.context.nodes.isNotEmpty()) {
|
||||||
for ((node, ex) in results.exceptions) {
|
for ((node, ex) in results.exceptions) {
|
||||||
sender.sendMessage("${ChatColor.RED}${ex.message}")
|
sender.sendMessage("${ChatColor.RED}${ex.message}")
|
||||||
executeHelpText(results.context.build(results.reader.string))
|
executeHelpText(results.context.build(results.reader.string))
|
||||||
}
|
}
|
||||||
return true
|
true
|
||||||
} else {
|
} else {
|
||||||
return false // Unknown command
|
false // Unknown command
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val executeCommand: () -> Unit = {
|
val executeCommand: () -> Unit = {
|
||||||
|
@ -252,10 +251,9 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
||||||
return Triple(parent, mainCommand, split.last())
|
return Triple(parent, mainCommand, split.last())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSubcommandList(): (Any) -> Array<String> {
|
fun getCommandList(sender: TP): Array<String> {
|
||||||
return {
|
return commandNodes.filter { it.data.hasPermission(sender) }
|
||||||
arrayOf("TODO") // TODO: Subcommand list
|
.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 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}")
|
val helpText = node.subcommandDataNoOp()?.getHelpText(context.source) ?: error("No subcommand data found when executing help text for ${context.input}")
|
||||||
if (node.isCommand()) {
|
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()) {
|
val messages = if (subs.isNotEmpty()) {
|
||||||
helpText + "${ChatColor.GOLD}---- Subcommands ----" + subs
|
helpText + "${ChatColor.GOLD}---- Subcommands ----" + subs
|
||||||
} else {
|
} else {
|
||||||
|
@ -369,7 +369,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
val params = executeGetArguments(sd, context) ?: return executeHelpText(context)
|
val params = executeGetArguments(sd, context, sender) ?: return executeHelpText(context)
|
||||||
|
|
||||||
// TODO: Varargs support? (colors?)
|
// TODO: Varargs support? (colors?)
|
||||||
// TODO: Character handling (strlen)
|
// TODO: Character handling (strlen)
|
||||||
|
@ -378,7 +378,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
||||||
return 0
|
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?>()
|
val params = mutableListOf<Any?>()
|
||||||
for (argument in sd.argumentsInOrder) {
|
for (argument in sd.argumentsInOrder) {
|
||||||
try {
|
try {
|
||||||
|
@ -387,9 +387,14 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
||||||
params.add(userArgument)
|
params.add(userArgument)
|
||||||
} else {
|
} else {
|
||||||
val userArgument = context.getArgument(argument.name, String::class.java)
|
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}")
|
?: 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) {
|
} catch (e: IllegalArgumentException) {
|
||||||
if (ChromaUtils.isTest && e.message?.contains("No such argument '${argument.name}' exists on this command") != true) {
|
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
|
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.
|
* 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
|
* @return A set of command node objects containing the commands
|
||||||
*/
|
*/
|
||||||
val commandNodes: Set<CoreCommandNode<TP, NoOpSubcommandData>>
|
val commandNodes: Set<CoreExecutableNode<TP, TC>>
|
||||||
get() = dispatcher.root.children.mapNotNull { it.coreCommand<TP, NoOpSubcommandData>() }.toSet()
|
get() = getSubcommands(true, dispatcher.root).toSet()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a node that belongs to the given command.
|
* Get a node that belongs to the given command.
|
||||||
|
@ -499,7 +503,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
||||||
|
|
||||||
private fun getSubcommands(
|
private fun getSubcommands(
|
||||||
deep: Boolean = true,
|
deep: Boolean = true,
|
||||||
root: CoreNoOpNode<TP>
|
root: CommandNode<TP>
|
||||||
): List<CoreExecutableNode<TP, TC>> {
|
): List<CoreExecutableNode<TP, TC>> {
|
||||||
return root.children.mapNotNull { it.coreExecutable<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()
|
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
|
@Command2.Subcommand
|
||||||
fun playerFail(sender: Command2MCSender, player: TBMCPlayer) {
|
fun playerFail(sender: Command2MCSender, player: TBMCPlayer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Command2.Subcommand
|
||||||
|
fun errorTest(sender: Command2MCSender) {
|
||||||
|
error("Hmm")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@CommandClass
|
@CommandClass
|
||||||
|
|
|
@ -38,8 +38,8 @@ class Command2MCTest {
|
||||||
fun testRegisterCommand() {
|
fun testRegisterCommand() {
|
||||||
TestCommand.register()
|
TestCommand.register()
|
||||||
val nodes = ButtonPlugin.command2MC.commandNodes
|
val nodes = ButtonPlugin.command2MC.commandNodes
|
||||||
assert(nodes.size == 1)
|
assertEquals(4, nodes.size)
|
||||||
assert(nodes.first().literal == "test")
|
assertEquals("test", nodes.first().literal)
|
||||||
val coreExecutable = nodes.first().coreExecutable<Command2MCSender, TestCommand>()
|
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(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")
|
assertEquals("test", coreExecutable?.data?.argumentsInOrder?.firstOrNull()?.name, "Failed to get correct argument name")
|
||||||
|
@ -108,11 +108,19 @@ class Command2MCTest {
|
||||||
fun runCommandWithReceive(command: String): String {
|
fun runCommandWithReceive(command: String): String {
|
||||||
return withMessageReceive { ButtonPlugin.command2MC.handleCommand(this, command) }
|
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("/test hmm", TestCommand, "hmm")
|
||||||
sender.runCommand("/noargtest", NoArgTestCommand, "TestPlayer")
|
sender.runCommand("/noargtest", NoArgTestCommand, "TestPlayer")
|
||||||
assertFails { ButtonPlugin.command2MC.handleCommand(sender, "/noargtest failing") }
|
sender.runCrashingCommand("/noargtest failing") { it.cause?.cause is IllegalStateException }
|
||||||
runFailingCommand(sender, "/erroringtest")
|
sender.runFailingCommand("/erroringtest")
|
||||||
sender.runCommand("/multiargtest test hmm mhm", MultiArgTestCommand, "hmmmhm")
|
sender.runCommand("/multiargtest test hmm mhm", MultiArgTestCommand, "hmmmhm")
|
||||||
sender.runCommand("/multiargtest test2 true 19", MultiArgTestCommand, "true 19")
|
sender.runCommand("/multiargtest test2 true 19", MultiArgTestCommand, "true 19")
|
||||||
|
|
||||||
|
@ -123,11 +131,14 @@ class Command2MCTest {
|
||||||
sender.runCommand("/multiargtest testoptionalmulti", MultiArgTestCommand, "false null")
|
sender.runCommand("/multiargtest testoptionalmulti", MultiArgTestCommand, "false null")
|
||||||
|
|
||||||
sender.runCommand("/test plugin Chroma-Core", TestCommand, "Chroma-Core")
|
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" +
|
assertEquals("Test command\n" +
|
||||||
"Used for testing\n" +
|
"Used for testing\n" +
|
||||||
"§6---- Subcommands ----\n" +
|
"§6---- Subcommands ----\n" +
|
||||||
|
"/test errortest\n" +
|
||||||
"/test playerfail\n" +
|
"/test playerfail\n" +
|
||||||
"/test plugin", sender.runCommandWithReceive("/test")
|
"/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")
|
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"))
|
assertEquals("§cExpected integer at position 11: ...estparams <--[HERE]", sender.runCommandWithReceive("/testparams asd 34 56 78"))
|
||||||
// TODO: Change test when usage help is added
|
// 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() {
|
private fun ICommand2MC.register() {
|
||||||
MainPlugin.instance.registerCommand(this)
|
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 {
|
companion object {
|
||||||
private var initialized = false
|
private var initialized = false
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue