From aad4143e8ae2b4e46ee1cb7c9f7d62fa7d1e41ff Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 7 Mar 2021 21:41:13 +0100 Subject: [PATCH] Add custom args parser to allow spaces in arguments Fixes #96 where worlds with spaces could not be used with chunkmaster. Signed-off-by: trivernis --- .../commands/CommandChunkmaster.kt | 6 +- .../trivernis/chunkmaster/lib/ArgParser.kt | 77 +++++++++++++++++++ .../chunkmaster/lib/ArgParserTest.kt | 49 ++++++++++++ .../{ => lib}/LanguageManagerTest.kt | 4 +- 4 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/net/trivernis/chunkmaster/lib/ArgParser.kt create mode 100644 src/test/kotlin/net/trivernis/chunkmaster/lib/ArgParserTest.kt rename src/test/kotlin/net/trivernis/chunkmaster/{ => lib}/LanguageManagerTest.kt (89%) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandChunkmaster.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandChunkmaster.kt index 8452673..20ba5d9 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandChunkmaster.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandChunkmaster.kt @@ -1,6 +1,7 @@ package net.trivernis.chunkmaster.commands import net.trivernis.chunkmaster.Chunkmaster +import net.trivernis.chunkmaster.lib.ArgParser import net.trivernis.chunkmaster.lib.Subcommand import org.bukkit.Server import org.bukkit.command.Command @@ -11,6 +12,7 @@ import org.bukkit.command.TabCompleter class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val server: Server) : CommandExecutor, TabCompleter { private val commands = HashMap() + private val argParser = ArgParser() init { registerCommands() @@ -36,7 +38,9 @@ class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val serve /** * /chunkmaster command to handle all commands */ - override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { + override fun onCommand(sender: CommandSender, command: Command, label: String, bukkitArgs: Array): Boolean { + val args = argParser.parseArguments(bukkitArgs.joinToString(" ")) + if (args.isNotEmpty()) { if (sender.hasPermission("chunkmaster.${args[0].toLowerCase()}")) { return if (commands.containsKey(args[0])) { diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/ArgParser.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/ArgParser.kt new file mode 100644 index 0000000..be26a62 --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/ArgParser.kt @@ -0,0 +1,77 @@ +package net.trivernis.chunkmaster.lib + +/** + * Better argument parser for command arguments + */ +class ArgParser { + private var input = "" + private var position = 0 + private var currentChar = ' ' + + /** + * Parses arguments from a string and respects quotes + */ + fun parseArguments(arguments: String): List { + if (arguments.isEmpty()) { + return emptyList() + } + + input = arguments + position = 0 + currentChar = input[position] + val args = ArrayList() + var arg = "" + + while (!endReached()) { + nextCharacter() + + if (currentChar.isWhitespace()) { + if (arg.isNotBlank()) { + args.add(arg) + } + arg = "" + } else if (currentChar == '"') { + if (arg.isNotBlank()) { + args.add(arg) + } + arg = parseString() + if (arg.isNotBlank()) { + args.add(arg) + } + arg = "" + } else { + arg += currentChar + } + } + if (arg.isNotBlank()) { + args.add(arg) + } + return args + } + + /** + * Parses an enquoted string + */ + private fun parseString(): String { + var output = "" + + while (!endReached()) { + nextCharacter() + if (currentChar == '"') { + break + } + output += currentChar + } + return output + } + + private fun nextCharacter() { + if (!endReached()) { + currentChar = input[position++] + } + } + + private fun endReached(): Boolean { + return position >= input.length + } +} \ No newline at end of file diff --git a/src/test/kotlin/net/trivernis/chunkmaster/lib/ArgParserTest.kt b/src/test/kotlin/net/trivernis/chunkmaster/lib/ArgParserTest.kt new file mode 100644 index 0000000..f848754 --- /dev/null +++ b/src/test/kotlin/net/trivernis/chunkmaster/lib/ArgParserTest.kt @@ -0,0 +1,49 @@ +package net.trivernis.chunkmaster.lib + +import io.kotest.matchers.shouldBe +import org.junit.Test + +class ArgParserTest { + var argParser = ArgParser() + + @Test + fun `it parses arguments`() { + argParser.parseArguments("first second third forth").shouldBe(listOf("first", "second", "third", "forth")) + } + + @Test + fun `it parses quoted arguments as one argument`() { + argParser.parseArguments("first \"second with space\" third").shouldBe(listOf("first", "second with space", "third")) + argParser.parseArguments("\"first\" \"second\" \"third\"").shouldBe(listOf("first", "second", "third")) + } + + @Test + fun `it parses single arguments`() { + argParser.parseArguments("one").shouldBe(listOf("one")) + argParser.parseArguments("\"one\"").shouldBe(listOf("one")) + } + + @Test + fun `it parses no arguments`() { + argParser.parseArguments("").shouldBe(emptyList()) + } + + @Test + fun `it parses just whitespace as no arguments`() { + argParser.parseArguments(" ").shouldBe(emptyList()) + argParser.parseArguments("\t\t").shouldBe(emptyList()) + } + + @Test + fun `it parses arguments with weird whitespace`() { + argParser.parseArguments(" first second \t third \n forth ").shouldBe(listOf("first", "second", "third", "forth")) + } + + @Test + fun `it deals predictable with malformed input`() { + argParser.parseArguments("first \"second third fourth").shouldBe(listOf("first", "second third fourth")) + argParser.parseArguments("\"first second \"third\" fourth").shouldBe(listOf("first second ", "third", " fourth")) + argParser.parseArguments("first second third fourth\"").shouldBe(listOf("first", "second", "third", "fourth")) + argParser.parseArguments("\"").shouldBe(emptyList()) + } +} \ No newline at end of file diff --git a/src/test/kotlin/net/trivernis/chunkmaster/LanguageManagerTest.kt b/src/test/kotlin/net/trivernis/chunkmaster/lib/LanguageManagerTest.kt similarity index 89% rename from src/test/kotlin/net/trivernis/chunkmaster/LanguageManagerTest.kt rename to src/test/kotlin/net/trivernis/chunkmaster/lib/LanguageManagerTest.kt index 9f096b9..1ba2d74 100644 --- a/src/test/kotlin/net/trivernis/chunkmaster/LanguageManagerTest.kt +++ b/src/test/kotlin/net/trivernis/chunkmaster/lib/LanguageManagerTest.kt @@ -1,8 +1,8 @@ -package net.trivernis.chunkmaster +package net.trivernis.chunkmaster.lib import io.kotest.matchers.string.shouldNotBeEmpty import io.mockk.every import io.mockk.mockk -import net.trivernis.chunkmaster.lib.LanguageManager +import net.trivernis.chunkmaster.Chunkmaster import org.bukkit.configuration.file.FileConfiguration import org.junit.Test