diff --git a/.gitignore b/.gitignore index eb9af14..8f04f70 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ build/ !**/src/main/**/build/ !**/src/test/**/build/ kls_database.db +/notebook.ipynb ### IntelliJ IDEA ### .idea/ diff --git a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/Main.kt b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/Main.kt index cfba5a7..8c2628c 100644 --- a/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/Main.kt +++ b/bot/src/main/kotlin/io/github/null2264/tsukumogami/bot/Main.kt @@ -16,6 +16,6 @@ suspend fun main() { modules(generalModule) - koinModules(appModule) + koin(appModule) }.start() } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotBuilder.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotBuilder.kt index de8d262..b839e4f 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotBuilder.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/BotBuilder.kt @@ -8,7 +8,6 @@ import io.github.null2264.tsukumogami.core.ext.loadModule import io.github.null2264.tsukumogami.core.koin.TsukumogamiKoinContext import io.github.null2264.tsukumogami.core.module.BotModule import kotlin.reflect.KFunction -import kotlinx.coroutines.runBlocking import org.koin.core.module.Module as KoinModule import org.koin.dsl.bind @@ -36,11 +35,11 @@ class BotBuilder internal constructor( prefixes.forEach(bot::addPrefix) } - fun earlyKoinModules(vararg modules: KoinModule) { + fun earlyKoin(vararg modules: KoinModule) { earlyKoinModules.addAll(modules) } - fun koinModules(vararg modules: KoinModule) { + fun koin(vararg modules: KoinModule) { koinModules.addAll(modules) } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt index 3ef48b3..39fbdf0 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/Context.kt @@ -5,6 +5,7 @@ import dev.kord.core.entity.Message import dev.kord.rest.builder.message.AllowedMentionsBuilder import dev.kord.rest.builder.message.allowedMentions import io.github.null2264.tsukumogami.core.commands.Command +import io.github.null2264.tsukumogami.core.commands.StringParser import io.github.null2264.tsukumogami.core.koin.TsukumogamiKoinComponent import org.koin.core.component.inject @@ -35,6 +36,16 @@ class Context( */ var command: Command? = null + val parser: StringParser by lazy { + StringParser( + if (prefix != null && invokedWith != null) { + message.content + .substringAfter("$prefix$invokedWith") + .trim() + } else "" + ) + } + suspend fun send(content: String) = message.channel.createMessage(content) suspend fun reply(content: String, mentionsAuthor: Boolean = false) = message.channel.createMessage { @@ -47,15 +58,4 @@ class Context( } suspend fun typing() = message.channel.type() - - fun parseArguments(): MutableList? = - if (prefix != null && invokedWith != null) { - message.content - .substringAfter("$prefix$invokedWith") - .trim() - .split(' ') - .toMutableList() - } else { - null - } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt index 92a9c04..a4c8c80 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/Arguments.kt @@ -17,11 +17,9 @@ abstract class Arguments { } suspend fun parse(context: Context) { - val values = context.parseArguments()?.toMutableList() ?: return - run { args.forEach { arg -> - arg.converter.consume(context, values) + arg.converter.consume(context, context.parser) } } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/StringParser.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/StringParser.kt new file mode 100644 index 0000000..3001c62 --- /dev/null +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/StringParser.kt @@ -0,0 +1,72 @@ +package io.github.null2264.tsukumogami.core.commands + +class StringParser(private val string: String) { + private var currentIndex = 0 + private var lastPeekIndex = 0 + + fun hasNext() = currentIndex < string.length + + private fun parse(peek: Boolean, naive: Boolean = false): String { + check(hasNext()) + + val currentToken = StringBuilder() + var inQuotes = false + val currentIndexInitial = currentIndex + + while (hasNext()) { + val char = string[currentIndex] + currentIndex++ + + when (char) { + '"' -> { + if (naive) { + currentToken.append(char) + continue + } + + if (inQuotes) { + // Closing quote: add the accumulated token + break + } else { + if (currentToken.isNotEmpty()) { + // Did not start with quote, probably something like: hello"world" + currentToken.append(char) + continue + } + // Opening quote: + inQuotes = true + } + } + + ' ' -> { + if (inQuotes) { + // Space inside quotes: append it + currentToken.append(char) + } else { + // Space outside quotes: separator + if (currentToken.isNotEmpty()) { + break + } + } + } + + else -> { + // Any other character: append it + currentToken.append(char) + } + } + } + //check(!inQuotes) + if (peek) { + lastPeekIndex = currentIndex + currentIndex = currentIndexInitial + } + return currentToken.toString() + } + + fun parseNext(naive: Boolean = false): String = parse(false, naive) + fun peekNext(naive: Boolean = false): String = parse(true, naive) + fun next() { + currentIndex = lastPeekIndex + } +} diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/Converter.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/Converter.kt index 1226476..45a7c1d 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/Converter.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/Converter.kt @@ -3,6 +3,7 @@ package io.github.null2264.tsukumogami.core.commands.converters import io.github.null2264.tsukumogami.core.Context import io.github.null2264.tsukumogami.core.commands.Argument import io.github.null2264.tsukumogami.core.commands.Arguments +import io.github.null2264.tsukumogami.core.commands.StringParser import kotlin.reflect.KProperty abstract class Converter { @@ -13,7 +14,7 @@ abstract class Converter { abstract var parsed: OutputType - abstract suspend fun consume(context: Context, inputs: MutableList): Boolean + abstract suspend fun consume(context: Context, parser: StringParser): Boolean operator fun getValue(thisRef: Arguments, property: KProperty<*>): OutputType { return this.parsed diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/StringConverter.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/StringConverter.kt index 6634b67..1801815 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/StringConverter.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/StringConverter.kt @@ -1,25 +1,26 @@ package io.github.null2264.tsukumogami.core.commands.converters.impl import io.github.null2264.tsukumogami.core.Context +import io.github.null2264.tsukumogami.core.commands.StringParser import io.github.null2264.tsukumogami.core.commands.converters.Converter class StringConverter : Converter() { override var parsed: String = "" - override suspend fun consume(context: Context, inputs: MutableList): Boolean { - this.parsed = if (isGreedy && inputs.size > 1) run { - val limit = inputs.size + override suspend fun consume(context: Context, parser: StringParser): Boolean { + if (!parser.hasNext()) return this.parsed.isEmpty() + + this.parsed = if (isGreedy) run { var buffer = "" var count = 0 - for (i in (1..limit)) { + while (parser.hasNext()) { if (++count > 1) buffer += " " - if (count <= limit) { - buffer += inputs.removeFirstOrNull() ?: "" - } else break + buffer += parser.parseNext(true) } buffer - } else inputs.removeFirstOrNull().orEmpty() + } else parser.parseNext() + return this.parsed.isNotEmpty() } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/URIConverter.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/URIConverter.kt index b34fe1b..a2b386a 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/URIConverter.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/URIConverter.kt @@ -1,6 +1,7 @@ package io.github.null2264.tsukumogami.core.commands.converters.impl import io.github.null2264.tsukumogami.core.Context +import io.github.null2264.tsukumogami.core.commands.StringParser import io.github.null2264.tsukumogami.core.commands.converters.Converter import io.github.null2264.tsukumogami.core.exceptions.CommandException import java.net.URI @@ -13,13 +14,15 @@ class URIConverter : Converter() { override lateinit var parsed: URI - override suspend fun consume(context: Context, inputs: MutableList): Boolean { - val input = inputs.getOrNull(0) ?: return false + override suspend fun consume(context: Context, parser: StringParser): Boolean { + if (!parser.hasNext()) return false + val input = parser.peekNext() this.parsed = try { URI(input) } catch (e: Exception) { throw CommandException("Failed to parse URI", e) } + parser.next() return true } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/URLConverter.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/URLConverter.kt index b59d862..7e16a78 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/URLConverter.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/URLConverter.kt @@ -1,6 +1,7 @@ package io.github.null2264.tsukumogami.core.commands.converters.impl import io.github.null2264.tsukumogami.core.Context +import io.github.null2264.tsukumogami.core.commands.StringParser import io.github.null2264.tsukumogami.core.commands.converters.Converter import io.github.null2264.tsukumogami.core.exceptions.CommandException import java.net.URI @@ -14,13 +15,15 @@ class URLConverter : Converter() { override lateinit var parsed: URL - override suspend fun consume(context: Context, inputs: MutableList): Boolean { - val input = inputs.getOrNull(0) ?: return false + override suspend fun consume(context: Context, parser: StringParser): Boolean { + if (!parser.hasNext()) return false + val input = parser.peekNext() this.parsed = try { URL(input) } catch (e: Exception) { throw CommandException("Failed to parse URL", e) } + parser.next() return true } } diff --git a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/UserConverter.kt b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/UserConverter.kt index 39c6032..fa46692 100644 --- a/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/UserConverter.kt +++ b/core/src/main/kotlin/io/github/null2264/tsukumogami/core/commands/converters/impl/UserConverter.kt @@ -3,6 +3,7 @@ package io.github.null2264.tsukumogami.core.commands.converters.impl import dev.kord.common.entity.Snowflake import dev.kord.core.entity.User import io.github.null2264.tsukumogami.core.Context +import io.github.null2264.tsukumogami.core.commands.StringParser import io.github.null2264.tsukumogami.core.commands.converters.Converter import io.github.null2264.tsukumogami.core.exceptions.CommandException import io.github.null2264.tsukumogami.core.ext.users @@ -16,27 +17,28 @@ class UserConverter : Converter() { override lateinit var parsed: User - override suspend fun consume(context: Context, inputs: MutableList): Boolean { - val input = inputs.getOrNull(0) ?: throw CommandException("User ID is null") + override suspend fun consume(context: Context, parser: StringParser): Boolean { + if (!parser.hasNext()) throw CommandException("User is not specified") + val input = parser.peekNext() if (input.equals("me", true)) { val user = context.author if (user != null) { this.parsed = user - inputs.removeAt(0) + parser.next() return true } } if (input.equals("you", true)) { this.parsed = context.bot.client.getSelf() - inputs.removeAt(0) + parser.next() return true } this.parsed = context.findUser(input) ?: throw CommandException("User not found") - inputs.removeAt(0) + parser.next() return true }