Improve readability in Parser, parse numeric commands, extract logic into body of abstract parent class
This commit is contained in:
@@ -9,66 +9,70 @@ import org.slf4j.helpers.NOPLogger
|
|||||||
|
|
||||||
class MessageParser(val incoming: Channel<String>) {
|
class MessageParser(val incoming: Channel<String>) {
|
||||||
val incomingParsedMessages = Channel<ServerMessage>()
|
val incomingParsedMessages = Channel<ServerMessage>()
|
||||||
|
|
||||||
private companion object MessageBuilder {
|
|
||||||
fun getTrailingParameterIndex(params: List<String>) = params.indexOfLast { it.startsWith(":") }
|
|
||||||
|
|
||||||
sealed interface Command {
|
|
||||||
fun toServerMessage(): ServerMessage
|
|
||||||
|
|
||||||
class NamedCommand(val name: String, val parameters: List<String>) : Command {
|
|
||||||
override fun toServerMessage(): ServerMessage {
|
|
||||||
logger.debug { "Parsing ${name.toUpperCasePreservingASCIIRules()}, with params $parameters" }
|
|
||||||
|
|
||||||
return when (name.toUpperCasePreservingASCIIRules()) {
|
|
||||||
// PONG :PREFIX COMMAND HOSTNAME :TOKEN
|
|
||||||
"PONG" -> {
|
|
||||||
val tokenIndex = getTrailingParameterIndex(parameters)
|
|
||||||
if (tokenIndex != -1) ServerMessage.Pong(
|
|
||||||
parameters.subList(tokenIndex, parameters.size).joinToString(" ").removePrefix(":")
|
|
||||||
)
|
|
||||||
else ServerMessage.Pong()
|
|
||||||
}
|
|
||||||
// MODE :PREFIX NICK :MODES
|
|
||||||
"MODE" -> {
|
|
||||||
ServerMessage.Mode(parameters.first(), parameters.last().removePrefix(":")) // TODO MODE, CHANMODE, LOCALMODE
|
|
||||||
}
|
|
||||||
|
|
||||||
"NOTICE", "PRIVMSG" -> {
|
|
||||||
val cmd = name.toUpperCasePreservingASCIIRules()
|
|
||||||
val trailingIndex = getTrailingParameterIndex(parameters)
|
|
||||||
if (trailingIndex < 1) {
|
|
||||||
logger.warn { "Received malformed $cmd. Returning unimplemented as fallback." }
|
|
||||||
return ServerMessage.UNIMPLEMENTED("$cmd ${parameters.joinToString(" ")}")
|
|
||||||
}
|
|
||||||
val targets = parameters.subList(0, trailingIndex - 1)
|
|
||||||
if (cmd == "NOTICE")
|
|
||||||
ServerMessage.Notice(targets, "")
|
|
||||||
else
|
|
||||||
ServerMessage.PrivMsg(targets, "")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
logger.warn { "$name left unparsed" }
|
|
||||||
ServerMessage.UNIMPLEMENTED("$name ${parameters.joinToString(" ")}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private val logger = if (Helpers.enableLogging) KotlinLogging.logger {} else KotlinLogging.logger(NOPLogger.NOP_LOGGER)
|
private val logger = if (Helpers.enableLogging) KotlinLogging.logger {} else KotlinLogging.logger(NOPLogger.NOP_LOGGER)
|
||||||
|
|
||||||
class NumericCommand(val number: Int, val parameters: List<String>?) : Command {
|
class MessageBuilder {
|
||||||
override fun toServerMessage(): ServerMessage {
|
|
||||||
return ServerMessage.UNIMPLEMENTED("$number ${parameters?.joinToString(" ")}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var tags: String? = null
|
private var tags: String? = null
|
||||||
private var prefix: String? = null
|
private var prefix: String? = null
|
||||||
private lateinit var command: Command
|
private lateinit var command: Command
|
||||||
|
|
||||||
|
abstract class Command(open val command: String, open val parameters: List<String>) {
|
||||||
|
abstract fun toServerMessage(): ServerMessage
|
||||||
|
fun getTrailingParameterIndex() = parameters.indexOfFirst { it.startsWith(":") }
|
||||||
|
|
||||||
|
fun getTrailingParameterString() =
|
||||||
|
parameters
|
||||||
|
.subList(getTrailingParameterIndex(), parameters.size)
|
||||||
|
.joinToString(" ")
|
||||||
|
.removePrefix(":")
|
||||||
|
|
||||||
|
fun getNonTrailingParameterString() =
|
||||||
|
parameters
|
||||||
|
.subList(0, getTrailingParameterIndex())
|
||||||
|
.joinToString(" ")
|
||||||
|
.removePrefix(":")
|
||||||
|
|
||||||
|
fun getNonTrailingParameters() = getNonTrailingParameterString().split(" ")
|
||||||
|
|
||||||
|
class NamedCommand(override val command: String, override val parameters: List<String>) : Command(command, parameters) {
|
||||||
|
override fun toServerMessage() = when (command.uppercase()) {
|
||||||
|
"PONG" -> ServerMessage.Pong(getTrailingParameterString().removePrefix(":"))
|
||||||
|
"MODE" -> ServerMessage.Mode(
|
||||||
|
parameters.first(),
|
||||||
|
parameters.last().removePrefix(":")
|
||||||
|
) // TODO MODE, CHANMODE, LOCALMODE
|
||||||
|
|
||||||
|
"NOTICE" -> ServerMessage.Notice(
|
||||||
|
getNonTrailingParameters(),
|
||||||
|
getTrailingParameterString()
|
||||||
|
)
|
||||||
|
|
||||||
|
"PRIVMSG" -> ServerMessage.PrivMsg(
|
||||||
|
getNonTrailingParameters(),
|
||||||
|
getTrailingParameterString()
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
ServerMessage.UNIMPLEMENTED("$command ${parameters.joinToString(" ")}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NumericCommand(override val command: String, override val parameters: List<String>) : Command(command, parameters) {
|
||||||
|
override fun toServerMessage() = when (command.toInt()) {
|
||||||
|
5 -> ServerMessage.Numeric.RPL_ISUPPORT(getNonTrailingParameterString())
|
||||||
|
321 -> ServerMessage.Numeric.RPL_LISTSTART()
|
||||||
|
322 -> ServerMessage.Numeric.RPL_LIST(
|
||||||
|
parameters[1],
|
||||||
|
parameters[2].toInt(),
|
||||||
|
getTrailingParameterString()
|
||||||
|
)
|
||||||
|
323 -> ServerMessage.Numeric.RPL_LISTEND()
|
||||||
|
else -> ServerMessage.UNIMPLEMENTED(parameters.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Messages have this format, as rough ABNF:
|
* Messages have this format, as rough ABNF:
|
||||||
*
|
*
|
||||||
@@ -86,15 +90,30 @@ class MessageParser(val incoming: Channel<String>) {
|
|||||||
*
|
*
|
||||||
* parameters: If it exists, data relevant to this specific command.
|
* parameters: If it exists, data relevant to this specific command.
|
||||||
*
|
*
|
||||||
* ignoring tags for now ;-;.. still making space for them
|
* ===================
|
||||||
|
*
|
||||||
|
* Convert line from server into tokens split on strings and remove elements that belong
|
||||||
|
* to different segments of the IRC message e.g. tags, prefixes etc
|
||||||
*/
|
*/
|
||||||
fun build(commandList: List<String>): ServerMessage {
|
fun build(commandList: List<String>): ServerMessage {
|
||||||
val tokens = commandList.toMutableList()
|
val tokens = commandList.toMutableList()
|
||||||
tags = if (tokens.first().startsWith("@")) tokens.removeFirst() else null
|
|
||||||
prefix = if (tokens.first().startsWith(":")) tokens.removeFirst() else null
|
// remove tags from strlist if present
|
||||||
|
tags = if (tokens.first().startsWith("@")) {
|
||||||
|
tokens.removeFirst()
|
||||||
|
} else null
|
||||||
|
|
||||||
|
// remove client prefix (nick&opt hostname) if present
|
||||||
|
prefix = if (tokens.first().startsWith(":")) {
|
||||||
|
tokens.removeFirst()
|
||||||
|
} else null
|
||||||
|
|
||||||
|
// get command e.g. PING
|
||||||
val commandStr = tokens.removeFirst()
|
val commandStr = tokens.removeFirst()
|
||||||
command = if (commandStr.toIntOrNull() != null) Command.NumericCommand(commandStr.toInt(), tokens)
|
|
||||||
else Command.NamedCommand(commandStr, tokens)
|
// Check if command is a numeric (RPL/ERR) or named
|
||||||
|
command = if (commandStr.toIntOrNull() == null) Command.NamedCommand(commandStr, tokens)
|
||||||
|
else Command.NumericCommand(commandStr, tokens)
|
||||||
|
|
||||||
return command.toServerMessage()
|
return command.toServerMessage()
|
||||||
}
|
}
|
||||||
@@ -103,9 +122,11 @@ class MessageParser(val incoming: Channel<String>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun start() {
|
suspend fun start() {
|
||||||
|
|
||||||
|
val builder = MessageBuilder()
|
||||||
for (msg in incoming) {
|
for (msg in incoming) {
|
||||||
incomingParsedMessages.send(build(msg))
|
|
||||||
logger.trace { "Receiving: $msg" }
|
logger.trace { "Receiving: $msg" }
|
||||||
|
incomingParsedMessages.send(builder.build(msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user