Add param type test for primitive types and fix things

- Made subcommand list sorted
- Simplified test command registration and running commands
- Fixed primitive type handling where I missed it
This commit is contained in:
Norbi Peti 2023-07-28 00:10:51 +02:00
parent f18c5483d8
commit 79c1cc47f7
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
6 changed files with 75 additions and 44 deletions

View file

@ -219,7 +219,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
if (params.isEmpty() || getArgNodes(node, params.toMutableList())) { if (params.isEmpty() || getArgNodes(node, params.toMutableList())) {
node.executes(::executeCommand) node.executes(::executeCommand)
} }
return node.build().coreExecutable() ?: throw IllegalStateException("Command node should be executable but isn't: $fullPath") return node.build().coreExecutable() ?: error("Command node should be executable but isn't: $fullPath")
} }
/** /**
@ -296,19 +296,21 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
val (lowerLimit, upperLimit) = arg.limits val (lowerLimit, upperLimit) = arg.limits
return if (arg.greedy) StringArgumentType.greedyString() return if (arg.greedy) StringArgumentType.greedyString()
else if (ptype == String::class.java) StringArgumentType.word() else if (ptype == String::class.java) StringArgumentType.word()
else if (ptype == Int::class.javaPrimitiveType || ptype == Int::class.java else if (ptype == Int::class.javaPrimitiveType || ptype == Int::class.javaObjectType
|| ptype == Byte::class.javaPrimitiveType || ptype == Byte::class.java || ptype == Byte::class.javaPrimitiveType || ptype == Byte::class.javaObjectType
|| ptype == Short::class.javaPrimitiveType || ptype == Short::class.java || ptype == Short::class.javaPrimitiveType || ptype == Short::class.javaObjectType
) )
IntegerArgumentType.integer(lowerLimit.toInt(), upperLimit.toInt()) IntegerArgumentType.integer(lowerLimit.toInt(), upperLimit.toInt())
else if (ptype == Long::class.javaPrimitiveType || ptype == Long::class.java) else if (ptype == Long::class.javaPrimitiveType || ptype == Long::class.javaObjectType)
LongArgumentType.longArg(lowerLimit.toLong(), upperLimit.toLong()) LongArgumentType.longArg(lowerLimit.toLong(), upperLimit.toLong())
else if (ptype == Float::class.javaPrimitiveType || ptype == Float::class.java) else if (ptype == Float::class.javaPrimitiveType || ptype == Float::class.javaObjectType)
FloatArgumentType.floatArg(lowerLimit.toFloat(), upperLimit.toFloat()) FloatArgumentType.floatArg(lowerLimit.toFloat(), upperLimit.toFloat())
else if (ptype == Double::class.javaPrimitiveType || ptype == Double::class.java) else if (ptype == Double::class.javaPrimitiveType || ptype == Double::class.javaObjectType)
DoubleArgumentType.doubleArg(lowerLimit, upperLimit) DoubleArgumentType.doubleArg(lowerLimit, upperLimit)
else if (ptype == Char::class.javaPrimitiveType || ptype == Char::class.java) StringArgumentType.word() else if (ptype == Char::class.javaPrimitiveType || ptype == Char::class.javaObjectType)
else if (ptype == Boolean::class.javaPrimitiveType || ptype == Boolean::class.java) BoolArgumentType.bool() StringArgumentType.word()
else if (ptype == Boolean::class.javaPrimitiveType || ptype == Boolean::class.javaObjectType)
BoolArgumentType.bool()
else StringArgumentType.word() else StringArgumentType.word()
} }
@ -320,10 +322,10 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
* @return Vanilla command success level (0) * @return Vanilla command success level (0)
*/ */
private fun executeHelpText(context: CommandContext<TP>): Int { private fun executeHelpText(context: CommandContext<TP>): Int {
val node = context.nodes.lastOrNull()?.node ?: throw IllegalStateException() val node = context.nodes.lastOrNull()?.node ?: error("No nodes found when executing help text for ${context.input}!")
val helpText = node.subcommandDataNoOp()?.getHelpText(context.source) ?: throw IllegalStateException() 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 } val subs = getSubcommands(node.coreCommandNoOp()!!).map { commandChar + it.data.fullPath }.sorted()
context.source.sendMessage(helpText + "${ChatColor.GOLD}---- Subcommands ----" + subs) context.source.sendMessage(helpText + "${ChatColor.GOLD}---- Subcommands ----" + subs)
} }
return 0 return 0
@ -336,7 +338,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
* @return Vanilla command success level (0) * @return Vanilla command success level (0)
*/ */
protected open fun executeCommand(context: CommandContext<TP>): Int { protected open fun executeCommand(context: CommandContext<TP>): Int {
val sd = context.nodes.lastOrNull()?.node?.subcommandData<_, TC>() ?: throw IllegalStateException("Could not find suitable command node for command ${context.input}") val sd = context.nodes.lastOrNull()?.node?.subcommandData<_, TC>() ?: error("Could not find suitable command node for command ${context.input}")
val sender = context.source val sender = context.source
if (!sd.hasPermission(sender)) { if (!sd.hasPermission(sender)) {
@ -348,7 +350,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
val convertedSender = convertSenderType(sender, sd.senderType) val convertedSender = convertSenderType(sender, sd.senderType)
if (convertedSender == null) { if (convertedSender == null) {
//TODO: Should have a prettier display of Command2 classes here //TODO: Should have a prettier display of Command2 classes here
val type = sd.senderType.simpleName.fold("") { s, ch -> s + if (ch.isUpperCase()) " " + ch.lowercase() else ch } val type = sd.senderType.simpleName.fold("") { s, ch -> s + if (ch.isUpperCase()) " " + ch.lowercase() else ch }.trim()
sender.sendMessage("${ChatColor.RED}You need to be a $type to use this command.") sender.sendMessage("${ChatColor.RED}You need to be a $type to use this command.")
executeHelpText(context) //Send what the command is about, could be useful for commands like /member where some subcommands aren't player-only executeHelpText(context) //Send what the command is about, could be useful for commands like /member where some subcommands aren't player-only
return 0 return 0
@ -373,10 +375,14 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
} 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]?.converter
?: throw IllegalStateException("No suitable converter found for ${argument.type} ${argument.name}") ?: error("No suitable converter found for ${argument.type} ${argument.name}")
params.add(converter.apply(userArgument)) params.add(converter.apply(userArgument))
} }
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
if (ChromaUtils.isTest && e.message?.contains("No such argument '${argument.name}' exists on this command") != true) {
println("For command ${sd.fullPath}:")
e.printStackTrace()
}
if (argument.optional) { if (argument.optional) {
params.add(argument.type.getDefaultForEasilyRepresentable()) params.add(argument.type.getDefaultForEasilyRepresentable())
} else { } else {

View file

@ -279,7 +279,7 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
for (suggestion in suggestions) sbuilder.suggest(suggestion) for (suggestion in suggestions) sbuilder.suggest(suggestion)
} }
} }
if (argData.type === Boolean::class.javaPrimitiveType || argData.type === Boolean::class.java) if (argData.type === Boolean::class.javaPrimitiveType || argData.type === Boolean::class.javaObjectType)
sbuilder.suggest("true").suggest("false") sbuilder.suggest("true").suggest("false")
val loweredInput = sbuilder.remaining.lowercase(Locale.getDefault()) val loweredInput = sbuilder.remaining.lowercase(Locale.getDefault())
// The list is automatically ordered, so we need to put the <param> at the end after that // The list is automatically ordered, so we need to put the <param> at the end after that

View file

@ -7,16 +7,16 @@ import buttondevteam.lib.architecture.Component
abstract class ICommand2MC : ICommand2<Command2MCSender>(command2MC) { abstract class ICommand2MC : ICommand2<Command2MCSender>(command2MC) {
private var _plugin: ButtonPlugin? = null private var _plugin: ButtonPlugin? = null
var plugin: ButtonPlugin var plugin: ButtonPlugin
get() = _plugin ?: throw IllegalStateException("The command is not registered to a Button plugin!") get() = _plugin ?: error("The command is not registered to a Button plugin!")
private set(value) { private set(value) {
if (_plugin != null) throw IllegalStateException("The command is already assigned to a Button plugin!") if (_plugin != null) error("The command is already assigned to a Button plugin!")
_plugin = value _plugin = value
} }
private var _component: Component<*>? = null private var _component: Component<*>? = null
var component: Component<*>? var component: Component<*>?
get() = _component get() = _component
private set(value) { private set(value) {
if (_component != null) throw IllegalStateException("The command is already assigned to a component!") if (_component != null) error("The command is already assigned to a component!")
_component = value _component = value
} }

View file

@ -6,6 +6,7 @@ import buttondevteam.lib.chat.Command2MCSender
import buttondevteam.lib.chat.CommandClass import buttondevteam.lib.chat.CommandClass
import buttondevteam.lib.chat.ICommand2MC import buttondevteam.lib.chat.ICommand2MC
import buttondevteam.lib.player.TBMCPlayer import buttondevteam.lib.player.TBMCPlayer
import org.bukkit.OfflinePlayer
abstract class Command2MCCommands { abstract class Command2MCCommands {
@CommandClass(helpText = ["Test command", "Used for testing"]) @CommandClass(helpText = ["Test command", "Used for testing"])
@ -97,6 +98,16 @@ abstract class Command2MCCommands {
} }
} }
@CommandClass
object TestParamsCommand : ICommand2MC(), ITestCommand2MC {
override var testCommandReceived: String? = null
@Command2.Subcommand
fun def(sender: OfflinePlayer, testInt: Int?, testLong: Long?, testDouble: Double?, testDouble2: Double) {
testCommandReceived = "$testInt $testLong $testDouble $testDouble2 ${sender.name}"
}
}
interface ITestCommand2MC { interface ITestCommand2MC {
var testCommandReceived: String? var testCommandReceived: String?
} }

View file

@ -5,7 +5,9 @@ import buttondevteam.core.MainPlugin
import buttondevteam.core.component.channel.Channel import buttondevteam.core.component.channel.Channel
import buttondevteam.lib.architecture.ButtonPlugin import buttondevteam.lib.architecture.ButtonPlugin
import buttondevteam.lib.chat.Command2MCSender import buttondevteam.lib.chat.Command2MCSender
import buttondevteam.lib.chat.ICommand2MC
import buttondevteam.lib.chat.commands.CommandUtils.coreExecutable import buttondevteam.lib.chat.commands.CommandUtils.coreExecutable
import buttondevteam.lib.chat.test.Command2MCCommands.*
import buttondevteam.lib.player.ChromaGamerBase import buttondevteam.lib.player.ChromaGamerBase
import buttondevteam.lib.player.TBMCPlayer import buttondevteam.lib.player.TBMCPlayer
import org.junit.jupiter.api.MethodOrderer import org.junit.jupiter.api.MethodOrderer
@ -34,22 +36,24 @@ class Command2MCTest {
@Test @Test
@Order(2) @Order(2)
fun testRegisterCommand() { fun testRegisterCommand() {
MainPlugin.instance.registerCommand(Command2MCCommands.TestCommand) TestCommand.register()
val nodes = ButtonPlugin.command2MC.commandNodes val nodes = ButtonPlugin.command2MC.commandNodes
assert(nodes.size == 1) assert(nodes.size == 1)
assert(nodes.first().literal == "test") assert(nodes.first().literal == "test")
val coreExecutable = nodes.first().coreExecutable<Command2MCSender, Command2MCCommands.TestCommand>() val coreExecutable = nodes.first().coreExecutable<Command2MCSender, TestCommand>()
assertEquals(Command2MCCommands.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")
assertEquals(String::class.java, coreExecutable?.data?.arguments?.get("test")?.type, "The argument could not be found or type doesn't match") assertEquals(String::class.java, coreExecutable?.data?.arguments?.get("test")?.type, "The argument could not be found or type doesn't match")
assertEquals(Command2MCSender::class.java, coreExecutable?.data?.senderType, "The sender's type doesn't seem to be stored correctly") assertEquals(Command2MCSender::class.java, coreExecutable?.data?.senderType, "The sender's type doesn't seem to be stored correctly")
MainPlugin.instance.registerCommand(Command2MCCommands.NoArgTestCommand) NoArgTestCommand.register()
assertEquals("No sender parameter for method '${Command2MCCommands.ErroringTestCommand::class.java.getMethod("def")}'", assertFails { MainPlugin.instance.registerCommand(Command2MCCommands.ErroringTestCommand) }.message) val errCmd = ErroringTestCommand
MainPlugin.instance.registerCommand(Command2MCCommands.MultiArgTestCommand) assertEquals("No sender parameter for method '${errCmd::class.java.getMethod("def")}'", assertFails { ErroringTestCommand.register() }.message)
MultiArgTestCommand.register()
MainPlugin.instance.registerCommand(Command2MCCommands.TestNoMainCommand1) TestNoMainCommand1.register()
MainPlugin.instance.registerCommand(Command2MCCommands.TestNoMainCommand2) TestNoMainCommand2.register()
TestParamsCommand.register()
} }
@Test @Test
@ -95,20 +99,20 @@ class Command2MCTest {
return messageReceived return messageReceived
} }
} }
runCommand(sender, "/test hmm", Command2MCCommands.TestCommand, "hmm") sender.runCommand("/test hmm", TestCommand, "hmm")
runCommand(sender, "/noargtest", Command2MCCommands.NoArgTestCommand, "TestPlayer") sender.runCommand("/noargtest", NoArgTestCommand, "TestPlayer")
assertFails { ButtonPlugin.command2MC.handleCommand(sender, "/noargtest failing") } assertFails { ButtonPlugin.command2MC.handleCommand(sender, "/noargtest failing") }
runFailingCommand(sender, "/erroringtest") runFailingCommand(sender, "/erroringtest")
runCommand(sender, "/multiargtest test hmm mhm", Command2MCCommands.MultiArgTestCommand, "hmmmhm") sender.runCommand("/multiargtest test hmm mhm", MultiArgTestCommand, "hmmmhm")
runCommand(sender, "/multiargtest test2 true 19", Command2MCCommands.MultiArgTestCommand, "true 19") sender.runCommand("/multiargtest test2 true 19", MultiArgTestCommand, "true 19")
runCommand(sender, "/multiargtest testoptional", Command2MCCommands.MultiArgTestCommand, "false") sender.runCommand("/multiargtest testoptional", MultiArgTestCommand, "false")
runCommand(sender, "/multiargtest testoptional true", Command2MCCommands.MultiArgTestCommand, "true") sender.runCommand("/multiargtest testoptional true", MultiArgTestCommand, "true")
runCommand(sender, "/multiargtest testoptionalmulti true teszt", Command2MCCommands.MultiArgTestCommand, "true teszt") sender.runCommand("/multiargtest testoptionalmulti true teszt", MultiArgTestCommand, "true teszt")
runCommand(sender, "/multiargtest testoptionalmulti true", Command2MCCommands.MultiArgTestCommand, "true null") sender.runCommand("/multiargtest testoptionalmulti true", MultiArgTestCommand, "true null")
runCommand(sender, "/multiargtest testoptionalmulti", Command2MCCommands.MultiArgTestCommand, "false null") sender.runCommand("/multiargtest testoptionalmulti", MultiArgTestCommand, "false null")
runCommand(sender, "/test plugin Chroma-Core", Command2MCCommands.TestCommand, "Chroma-Core") sender.runCommand("/test plugin Chroma-Core", TestCommand, "Chroma-Core")
assertFails { ButtonPlugin.command2MC.handleCommand(sender, "/test playerfail TestPlayer") } assertFails { ButtonPlugin.command2MC.handleCommand(sender, "/test playerfail TestPlayer") }
assertEquals("Test command\n" + assertEquals("Test command\n" +
@ -117,16 +121,22 @@ class Command2MCTest {
"/test playerfail\n" + "/test playerfail\n" +
"/test plugin", sender.withMessageReceive { ButtonPlugin.command2MC.handleCommand(sender, "/test") }) "/test plugin", sender.withMessageReceive { ButtonPlugin.command2MC.handleCommand(sender, "/test") })
runCommand(sender, "/some test cmd", Command2MCCommands.TestNoMainCommand1, "TestPlayer") sender.runCommand("/some test cmd", TestNoMainCommand1, "TestPlayer")
runCommand(sender, "/some another cmd", Command2MCCommands.TestNoMainCommand2, "TestPlayer") sender.runCommand("/some another cmd", TestNoMainCommand2, "TestPlayer")
assertEquals("§6---- Subcommands ----\n" + assertEquals("§6---- Subcommands ----\n" +
"/some test cmd\n" + "/some another cmd\n" +
"/some another cmd", sender.withMessageReceive { ButtonPlugin.command2MC.handleCommand(sender, "/some") }) "/some test cmd", sender.withMessageReceive { ButtonPlugin.command2MC.handleCommand(sender, "/some") })
sender.runCommand("/testparams 12 34 56 78", TestParamsCommand, "12 34 56.0 78.0 Player0")
} }
private fun runCommand(sender: Command2MCSender, command: String, obj: Command2MCCommands.ITestCommand2MC, expected: String) { private fun ICommand2MC.register() {
assert(ButtonPlugin.command2MC.handleCommand(sender, command)) { "Could not find command $command" } MainPlugin.instance.registerCommand(this)
}
private fun Command2MCSender.runCommand(command: String, obj: ITestCommand2MC, expected: String) {
assert(ButtonPlugin.command2MC.handleCommand(this, command)) { "Could not find command $command" }
assertEquals(expected, obj.testCommandReceived) assertEquals(expected, obj.testCommandReceived)
} }

View file

@ -2,7 +2,7 @@ buttondevteam:
lib: lib:
chat: chat:
test: test:
Command2MCTest: Command2MCCommands:
TestCommand: TestCommand:
def: def:
method: def() method: def()
@ -33,6 +33,10 @@ buttondevteam:
def: def:
method: def() method: def()
params: '' params: ''
TestParamsCommand:
def:
method: def()
params: 'testInt testLong testDouble testDouble2'
core: core:
ComponentCommand: ComponentCommand:
def: def: