diff --git a/.gitignore b/.gitignore index 5bf6210..e3850ec 100755 --- a/.gitignore +++ b/.gitignore @@ -225,3 +225,4 @@ dependency-reduced-pom.xml TBMC/ /.apt_generated/ +.attach_pid* diff --git a/Chroma-Core/src/main/java/buttondevteam/core/ChromaCommand.java b/Chroma-Core/src/main/java/buttondevteam/core/ChromaCommand.java deleted file mode 100644 index 141ac3c..0000000 --- a/Chroma-Core/src/main/java/buttondevteam/core/ChromaCommand.java +++ /dev/null @@ -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()); - } -} diff --git a/Chroma-Core/src/main/java/buttondevteam/core/ChromaCommand.kt b/Chroma-Core/src/main/java/buttondevteam/core/ChromaCommand.kt new file mode 100644 index 0000000..a91a7fa --- /dev/null +++ b/Chroma-Core/src/main/java/buttondevteam/core/ChromaCommand.kt @@ -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 + } +} diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/ChromaUtils.kt b/Chroma-Core/src/main/java/buttondevteam/lib/ChromaUtils.kt index 3a5ae4e..ec51c9f 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/ChromaUtils.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/ChromaUtils.kt @@ -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) diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt index fa0fe4c..9ca12b3 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt @@ -76,7 +76,6 @@ abstract class Command2, TP : Command2Sender>( ) protected val paramConverters = HashMap, ParamConverter<*>>() - private val commandHelp = ArrayList() //Mainly needed by Discord private val dispatcher = CommandDispatcher() /** @@ -106,14 +105,14 @@ abstract class Command2, 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, TP : Command2Sender>( return Triple(parent, mainCommand, split.last()) } - private fun getSubcommandList(): (Any) -> Array { - return { - arrayOf("TODO") // TODO: Subcommand list - } + fun getCommandList(sender: TP): Array { + return commandNodes.filter { it.data.hasPermission(sender) } + .map { commandChar + it.data.fullPath }.toTypedArray() } /** @@ -333,7 +331,9 @@ abstract class Command2, 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, 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, TP : Command2Sender>( return 0 } - private fun executeGetArguments(sd: SubcommandData, context: CommandContext): MutableList? { + private fun executeGetArguments(sd: SubcommandData, context: CommandContext, sender: TP): MutableList? { val params = mutableListOf() for (argument in sd.argumentsInOrder) { try { @@ -387,9 +387,14 @@ abstract class Command2, 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, TP : Command2Sender>( } abstract fun hasPermission(sender: TP, data: SubcommandData): Boolean - val commandsText: Array 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> - get() = dispatcher.root.children.mapNotNull { it.coreCommand() }.toSet() + val commandNodes: Set> + get() = getSubcommands(true, dispatcher.root).toSet() /** * Get a node that belongs to the given command. @@ -499,7 +503,7 @@ abstract class Command2, TP : Command2Sender>( private fun getSubcommands( deep: Boolean = true, - root: CoreNoOpNode + root: CommandNode ): List> { return root.children.mapNotNull { it.coreExecutable() } + if (deep) root.children.flatMap { child -> child.coreCommand<_, NoOpSubcommandData>()?.let { getSubcommands(deep, it) } ?: emptyList() } else emptyList() diff --git a/Chroma-Core/src/test/kotlin/buttondevteam/lib/chat/test/Command2MCCommands.kt b/Chroma-Core/src/test/kotlin/buttondevteam/lib/chat/test/Command2MCCommands.kt index 1f19512..5d3353b 100644 --- a/Chroma-Core/src/test/kotlin/buttondevteam/lib/chat/test/Command2MCCommands.kt +++ b/Chroma-Core/src/test/kotlin/buttondevteam/lib/chat/test/Command2MCCommands.kt @@ -26,6 +26,11 @@ abstract class Command2MCCommands { @Command2.Subcommand fun playerFail(sender: Command2MCSender, player: TBMCPlayer) { } + + @Command2.Subcommand + fun errorTest(sender: Command2MCSender) { + error("Hmm") + } } @CommandClass diff --git a/Chroma-Core/src/test/kotlin/buttondevteam/lib/chat/test/Command2MCTest.kt b/Chroma-Core/src/test/kotlin/buttondevteam/lib/chat/test/Command2MCTest.kt index ca49f72..1f611d8 100644 --- a/Chroma-Core/src/test/kotlin/buttondevteam/lib/chat/test/Command2MCTest.kt +++ b/Chroma-Core/src/test/kotlin/buttondevteam/lib/chat/test/Command2MCTest.kt @@ -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() 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 }