Add support for escaped whitespace to arg parser

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/109/head
trivernis 4 years ago
parent aad4143e8a
commit ec7af49267
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -9,7 +9,6 @@ import org.bstats.bukkit.Metrics
import org.bukkit.plugin.java.JavaPlugin
import org.bukkit.scheduler.BukkitTask
import org.dynmap.DynmapAPI
import java.lang.IllegalStateException
open class Chunkmaster : JavaPlugin() {
lateinit var sqliteManager: SqliteManager

@ -44,7 +44,7 @@ class CmdCancel(private val chunkmaster: Chunkmaster) : Subcommand {
}
} else {
sender.sendMessage(chunkmaster.langManager.getLocalized("TASK_ID_REQUIRED"));
sender.sendMessage(chunkmaster.langManager.getLocalized("TASK_ID_REQUIRED"))
false
}
}

@ -113,7 +113,7 @@ class CmdGenerate(private val chunkmaster: Chunkmaster) : Subcommand {
)
true
} else if (world == null) {
sender.sendMessage(chunkmaster.langManager.getLocalized("WORLD_NOT_FOUND", worldName));
sender.sendMessage(chunkmaster.langManager.getLocalized("WORLD_NOT_FOUND", worldName))
false
} else {
sender.sendMessage(chunkmaster.langManager.getLocalized("TASK_ALREADY_EXISTS", worldName))

@ -7,7 +7,7 @@ import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
class CmdGetCenter(private val chunkmaster: Chunkmaster) : Subcommand {
override val name = "getCenter";
override val name = "getCenter"
override fun onTabComplete(
sender: CommandSender,
@ -27,7 +27,7 @@ class CmdGetCenter(private val chunkmaster: Chunkmaster) : Subcommand {
if (args.isNotEmpty()) {
args[0]
} else {
sender.world.name;
sender.world.name
}
} else {
if (args.isEmpty()) {

@ -6,7 +6,6 @@ import net.trivernis.chunkmaster.lib.generation.taskentry.TaskEntry
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import kotlin.math.ceil
import kotlin.math.pow
class CmdList(private val chunkmaster: Chunkmaster) : Subcommand {
override val name = "list"
@ -50,7 +49,8 @@ class CmdList(private val chunkmaster: Chunkmaster) : Subcommand {
*/
private fun getGenerationEntry(task: TaskEntry): String {
val genTask = task.generationTask
val progress = genTask.shape.progress(if (genTask.radius < 0) (genTask.world.worldBorder.size / 32).toInt() else null)
val progress =
genTask.shape.progress(if (genTask.radius < 0) (genTask.world.worldBorder.size / 32).toInt() else null)
val percentage = " (%.1f".format(progress * 100) + "%)."
val count = if (genTask.radius > 0) {

@ -7,7 +7,7 @@ import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
class CmdSetCenter(private val chunkmaster: Chunkmaster) : Subcommand {
override val name = "setCenter";
override val name = "setCenter"
override fun onTabComplete(
sender: CommandSender,
@ -21,7 +21,7 @@ class CmdSetCenter(private val chunkmaster: Chunkmaster) : Subcommand {
.map { it.name }.toMutableList()
}
}
return emptyList<String>().toMutableList();
return emptyList<String>().toMutableList()
}
override fun execute(sender: CommandSender, args: List<String>): Boolean {

@ -37,13 +37,16 @@ class CmdStats(private val chunkmaster: Chunkmaster) : Subcommand {
}
private fun getWorldStatsMessage(sender: CommandSender, world: World): String {
var message = """
return """
${chunkmaster.langManager.getLocalized("STATS_WORLD_NAME", world.name)}
${chunkmaster.langManager.getLocalized("STATS_ENTITY_COUNT", world.entities.size)}
${chunkmaster.langManager.getLocalized("STATS_LOADED_CHUNKS", world.loadedChunks.size)}
${chunkmaster.langManager.getLocalized("STATS_GENERATING", chunkmaster.generationManager.tasks.find { it.generationTask.world == world } != null)}
${
chunkmaster.langManager.getLocalized(
"STATS_GENERATING",
chunkmaster.generationManager.tasks.find { it.generationTask.world == world } != null)
}
""".trimIndent()
return message
}
private fun getServerStatsMessage(sender: CommandSender): String {
@ -55,15 +58,22 @@ class CmdStats(private val chunkmaster: Chunkmaster) : Subcommand {
${chunkmaster.langManager.getLocalized("STATS_SERVER")}
${chunkmaster.langManager.getLocalized("STATS_SERVER_VERSION", sender.server.version)}
${chunkmaster.langManager.getLocalized("STATS_PLUGIN_VERSION", chunkmaster.description.version)}
${chunkmaster.langManager.getLocalized(
${
chunkmaster.langManager.getLocalized(
"STATS_MEMORY",
memUsed / 1000000,
runtime.maxMemory() / 1000000,
(memUsed.toFloat() / runtime.maxMemory().toFloat()) * 100
)}
)
}
${chunkmaster.langManager.getLocalized("STATS_CORES", runtime.availableProcessors())}
${chunkmaster.langManager.getLocalized("STATS_PLUGIN_LOADED_CHUNKS", chunkmaster.generationManager.loadedChunkCount)}
${
chunkmaster.langManager.getLocalized(
"STATS_PLUGIN_LOADED_CHUNKS",
chunkmaster.generationManager.loadedChunkCount
)
}
""".trimIndent()
for (world in sender.server.worlds) {
message += "\n\n" + getWorldStatsMessage(sender, world)

@ -38,7 +38,12 @@ class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val serve
/**
* /chunkmaster command to handle all commands
*/
override fun onCommand(sender: CommandSender, command: Command, label: String, bukkitArgs: Array<out String>): Boolean {
override fun onCommand(
sender: CommandSender,
command: Command,
label: String,
bukkitArgs: Array<out String>
): Boolean {
val args = argParser.parseArguments(bukkitArgs.joinToString(" "))
if (args.isNotEmpty()) {

@ -7,6 +7,7 @@ class ArgParser {
private var input = ""
private var position = 0
private var currentChar = ' '
private var escaped = false
/**
* Parses arguments from a string and respects quotes
@ -19,18 +20,24 @@ class ArgParser {
input = arguments
position = 0
currentChar = input[position]
escaped = false
val args = ArrayList<String>()
var arg = ""
while (!endReached()) {
nextCharacter()
if (currentChar.isWhitespace()) {
if (currentChar == '\\' && !escaped) {
escaped = true
continue
}
if (currentChar.isWhitespace() && !escaped) {
if (arg.isNotBlank()) {
args.add(arg)
}
arg = ""
} else if (currentChar == '"') {
} else if (currentChar == '"' && !escaped) {
if (arg.isNotBlank()) {
args.add(arg)
}
@ -42,6 +49,7 @@ class ArgParser {
} else {
arg += currentChar
}
escaped = false
}
if (arg.isNotBlank()) {
args.add(arg)
@ -57,10 +65,17 @@ class ArgParser {
while (!endReached()) {
nextCharacter()
if (currentChar == '"') {
if (currentChar == '\\') {
escaped = !escaped
continue
}
if (currentChar == '"' && !escaped) {
break
}
output += currentChar
escaped = false
}
return output
}

@ -2,4 +2,10 @@ package net.trivernis.chunkmaster.lib.database
import net.trivernis.chunkmaster.lib.generation.ChunkCoordinates
data class CompletedGenerationTask (val id: Int, val world: String, val radius: Int, val center: ChunkCoordinates, val shape: String)
data class CompletedGenerationTask(
val id: Int,
val world: String,
val radius: Int,
val center: ChunkCoordinates,
val shape: String
)

@ -28,7 +28,10 @@ class CompletedGenerationTasks(private val sqliteManager: SqliteManager) {
fun getCompletedTasksForWorld(world: String): CompletableFuture<List<CompletedGenerationTask>> {
val completableFuture = CompletableFuture<List<CompletedGenerationTask>>()
sqliteManager.executeStatement("SELECT * FROM completed_generation_tasks WHERE world = ?", hashMapOf(1 to world)) { res ->
sqliteManager.executeStatement(
"SELECT * FROM completed_generation_tasks WHERE world = ?",
hashMapOf(1 to world)
) { res ->
val tasks = ArrayList<CompletedGenerationTask>()
while (res!!.next()) {
@ -51,16 +54,25 @@ class CompletedGenerationTasks(private val sqliteManager: SqliteManager) {
/**
* Adds a completed task
*/
fun addCompletedTask(id: Int, world: String, radius: Int, center: ChunkCoordinates, shape: String): CompletableFuture<Void> {
fun addCompletedTask(
id: Int,
world: String,
radius: Int,
center: ChunkCoordinates,
shape: String
): CompletableFuture<Void> {
val completableFuture = CompletableFuture<Void>()
sqliteManager.executeStatement("INSERT INTO completed_generation_tasks (id, world, completed_radius, center_x, center_z, shape) VALUES (?, ?, ?, ?, ?, ?)", hashMapOf(
sqliteManager.executeStatement(
"INSERT INTO completed_generation_tasks (id, world, completed_radius, center_x, center_z, shape) VALUES (?, ?, ?, ?, ?, ?)",
hashMapOf(
1 to id,
2 to world,
3 to radius,
4 to center.x,
5 to center.z,
6 to shape,
)) {
)
) {
completableFuture.complete(null)
}
return completableFuture

@ -7,6 +7,7 @@ import kotlin.math.ceil
class PendingChunks(private val sqliteManager: SqliteManager) {
private val insertionCount = 300
/**
* Returns a list of pending chunks for a taskId
*/
@ -38,7 +39,15 @@ class PendingChunks(private val sqliteManager: SqliteManager) {
val statementCount = ceil(pendingChunks.size.toDouble() / insertionCount).toInt()
for (i in 0 until statementCount) {
futures.add(insertPendingChunks(taskId, pendingChunks.subList(i * insertionCount, ((i * insertionCount) + insertionCount).coerceAtMost(pendingChunks.size))))
futures.add(
insertPendingChunks(
taskId,
pendingChunks.subList(
i * insertionCount,
((i * insertionCount) + insertionCount).coerceAtMost(pendingChunks.size)
)
)
)
}
if (futures.size > 0) {

@ -301,7 +301,8 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server
private fun reportGenerationProgress(task: RunningTaskEntry) {
val genTask = task.generationTask
val (speed, chunkSpeed) = task.generationSpeed
val progress = genTask.shape.progress(if (genTask.radius < 0) (genTask.world.worldBorder.size / 32).toInt() else null)
val progress =
genTask.shape.progress(if (genTask.radius < 0) (genTask.world.worldBorder.size / 32).toInt() else null)
val percentage =
"(${"%.2f".format(progress * 100)}%)"

@ -18,7 +18,8 @@ class RunningTaskEntry(
get() {
var generationSpeed: Double? = null
var chunkGenerationSpeed: Double? = null
val progress = generationTask.shape.progress(if (generationTask.radius < 0) (generationTask.world.worldBorder.size / 32).toInt() else null)
val progress =
generationTask.shape.progress(if (generationTask.radius < 0) (generationTask.world.worldBorder.size / 32).toInt() else null)
if (lastProgress != null) {
val progressDiff = progress - lastProgress!!.second
val timeDiff = (System.currentTimeMillis() - lastProgress!!.first).toDouble() / 1000

@ -11,9 +11,18 @@ class ArgParserTest {
argParser.parseArguments("first second third forth").shouldBe(listOf("first", "second", "third", "forth"))
}
@Test
fun `it handles escaped sequences`() {
argParser.parseArguments("first second\\ pt2 third").shouldBe(listOf("first", "second pt2", "third"))
argParser.parseArguments("first \"second\\\" part 2\" third")
.shouldBe(listOf("first", "second\" part 2", "third"))
argParser.parseArguments("first \\\\second third").shouldBe(listOf("first", "\\second", "third"))
}
@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 with space\" third")
.shouldBe(listOf("first", "second with space", "third"))
argParser.parseArguments("\"first\" \"second\" \"third\"").shouldBe(listOf("first", "second", "third"))
}
@ -36,13 +45,15 @@ class ArgParserTest {
@Test
fun `it parses arguments with weird whitespace`() {
argParser.parseArguments(" first second \t third \n forth ").shouldBe(listOf("first", "second", "third", "forth"))
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("first second third fourth\"").shouldBe(listOf("first", "second", "third", "fourth"))
argParser.parseArguments("\"").shouldBe(emptyList())
}

@ -1,4 +1,5 @@
package net.trivernis.chunkmaster.lib
import io.kotest.matchers.string.shouldNotBeEmpty
import io.mockk.every
import io.mockk.mockk
@ -13,9 +14,9 @@ class LanguageManagerTest {
val plugin = mockk<Chunkmaster>()
val config = mockk<FileConfiguration>()
every {plugin.dataFolder} returns createTempDir()
every {plugin.config} returns config
every {config.getString("language")} returns "en"
every { plugin.dataFolder } returns createTempDir()
every { plugin.config } returns config
every { config.getString("language") } returns "en"
langManager = LanguageManager(plugin)
langManager.loadProperties()

@ -46,7 +46,8 @@ class CircleTest {
@Test
fun `it returns the right edges`() {
circle.getShapeEdgeLocations().shouldContainAll(listOf(
circle.getShapeEdgeLocations().shouldContainAll(
listOf(
Pair(2, -1),
Pair(2, 0),
Pair(2, 1),
@ -59,7 +60,8 @@ class CircleTest {
Pair(-1, -2),
Pair(0, -2),
Pair(1, -2),
))
)
)
}
@Test

Loading…
Cancel
Save