Rewrite config to use Konf (4): Features

The entire config parsing is now rewritten. This entirely removes toml4j
in favor of Konf. It also removes all remaining RawConfig logic.
This commit is contained in:
kageru 2019-10-18 21:56:31 +02:00
parent bb03474bf5
commit d6492bae8f
Signed by: kageru
GPG Key ID: 8282A2BEA4ADA3D2
12 changed files with 71 additions and 119 deletions

View File

@ -36,7 +36,6 @@ val test by tasks.getting(Test::class) {
dependencies {
implementation("com.uchuhimo:konf-core:0.20.0")
implementation("com.uchuhimo:konf-toml:0.20.0")
implementation("com.moandjiezana.toml:toml4j:0.7.2")
implementation(kotlin("stdlib-jdk8"))
implementation("org.javacord:javacord:3.0.4")
implementation("org.mapdb:mapdb:3.0.7")

View File

@ -3,7 +3,6 @@ package moe.kageru.kagebot
import moe.kageru.kagebot.Util.checked
import moe.kageru.kagebot.config.Config
import moe.kageru.kagebot.config.ConfigParser
import moe.kageru.kagebot.config.RawConfig
import moe.kageru.kagebot.cron.CronD
import moe.kageru.kagebot.persistence.Dao
import org.javacord.api.DiscordApiBuilder
@ -44,7 +43,7 @@ object Kagebot {
val api = DiscordApiBuilder().setToken(secret).login().join()
Globals.api = api
try {
ConfigParser.initialLoad(RawConfig.DEFAULT_CONFIG_PATH)
ConfigParser.initialLoad(ConfigParser.DEFAULT_CONFIG_PATH)
} catch (e: IllegalArgumentException) {
println("Config error:\n$e,\n${e.message},\n${e.stackTrace.joinToString("\n")}")
exitProcess(1)

View File

@ -10,11 +10,15 @@ object Config {
val systemSpec = Config { addSpec(SystemSpec) }.from.toml
val localeSpec = Config { addSpec(LocalizationSpec) }.from.toml
val commandSpec = Config { addSpec(CommandSpec) }.from.toml
val featureSpec = Config { addSpec(FeatureSpec) }.from.toml
lateinit var system: Config
lateinit var localization: Config
lateinit var server: Server
lateinit var commandConfig: Config
lateinit var features: Features
lateinit var featureConfig: Config
lateinit var server: Server
// for easier access
val features: Features get() = featureConfig[FeatureSpec.features]
val commands: List<Command> get() = commandConfig[CommandSpec.command]
}

View File

@ -2,26 +2,29 @@ package moe.kageru.kagebot.config
import moe.kageru.kagebot.Globals
import moe.kageru.kagebot.config.SystemSpec.serverId
import moe.kageru.kagebot.features.Features
import java.io.File
object ConfigParser {
val configFile: File = File(RawConfig.DEFAULT_CONFIG_PATH)
internal const val DEFAULT_CONFIG_PATH = "config.toml"
val configFile: File = File(DEFAULT_CONFIG_PATH)
fun initialLoad(file: String) {
val rawConfig = RawConfig.read(file)
val configFile = RawConfig.getFile(file)
val configFile = getFile(file)
val config = Config.systemSpec.file(configFile)
Config.system = config
Config.server = Globals.api.getServerById(config[serverId])
.orElseThrow { IllegalArgumentException("Invalid server configured.") }
Config.localization = Config.localeSpec.file(configFile)
reloadFeatures(rawConfig)
Config.featureConfig = Config.featureSpec.file(configFile)
Config.commandConfig = Config.commandSpec.file(configFile)
}
fun reloadFeatures(rawConfig: RawConfig) {
Config.features = rawConfig.features?.let(::Features)
?: Features(RawFeatures(null, null))
private fun getFile(path: String): File {
val file = File(path)
if (file.isFile) {
return file
}
println("Config not found, falling back to defaults...")
return File(this::class.java.classLoader.getResource(path)!!.toURI())
}
}

View File

@ -0,0 +1,28 @@
package moe.kageru.kagebot.config
import com.uchuhimo.konf.ConfigSpec
import moe.kageru.kagebot.command.Command
import moe.kageru.kagebot.config.Config.system
import moe.kageru.kagebot.features.Features
import java.awt.Color
object SystemSpec : ConfigSpec() {
private val rawColor by optional("#1793d0", name = "color")
val serverId by required<String>()
val color by kotlin.lazy { Color.decode(system[rawColor])!! }
}
object LocalizationSpec : ConfigSpec() {
val permissionDenied by optional("You do not have the permission to use this command.")
val redirectedMessage by optional("says")
val messageDeleted by optional("Your message was deleted.")
val timeout by optional("You have been timed out for @@ minutes.")
}
object CommandSpec : ConfigSpec(prefix = "") {
val command by optional(emptyList<Command>())
}
object FeatureSpec : ConfigSpec(prefix = "") {
val features by optional(Features.DEFAULT, name = "feature")
}

View File

@ -1,48 +0,0 @@
package moe.kageru.kagebot.config
import com.google.gson.annotations.SerializedName
import com.moandjiezana.toml.Toml
import com.uchuhimo.konf.ConfigSpec
import moe.kageru.kagebot.command.Command
import moe.kageru.kagebot.config.Config.system
import java.awt.Color
import java.io.File
class RawConfig(@SerializedName("feature") val features: RawFeatures?) {
companion object {
const val DEFAULT_CONFIG_PATH = "config.toml"
fun readFromString(tomlContent: String): RawConfig = Toml().read(tomlContent).to(RawConfig::class.java)
fun getFile(path: String): File {
val file = File(path)
if (file.isFile) {
return file
}
println("Config not found, falling back to defaults...")
return File(this::class.java.classLoader.getResource(path)!!.toURI())
}
fun read(path: String = DEFAULT_CONFIG_PATH): RawConfig {
val toml: Toml = Toml().read(getFile(path))
return toml.to(RawConfig::class.java)
}
}
}
object SystemSpec : ConfigSpec() {
private val rawColor by optional("#1793d0", name = "color")
val serverId by required<String>()
val color by kotlin.lazy { Color.decode(system[rawColor])!! }
}
object LocalizationSpec : ConfigSpec() {
val permissionDenied by optional("You do not have the permission to use this command.")
val redirectedMessage by optional("says")
val messageDeleted by optional("Your message was deleted.")
val timeout by optional("You have been timed out for @@ minutes.")
}
object CommandSpec : ConfigSpec(prefix = "") {
val command by optional(emptyList<Command>())
}

View File

@ -1,5 +0,0 @@
package moe.kageru.kagebot.config
class RawFeatures(val welcome: RawWelcomeFeature?, val timeout: RawTimeoutFeature?)
class RawWelcomeFeature(val content: List<String>?, val fallbackChannel: String?, val fallbackMessage: String?)
class RawTimeoutFeature(val role: String?)

View File

@ -1,23 +1,10 @@
package moe.kageru.kagebot.features
import moe.kageru.kagebot.config.RawFeatures
class Features(
val welcome: WelcomeFeature?,
debug: DebugFeature,
help: HelpFeature,
getConfig: GetConfigFeature,
setConfig: SetConfigFeature,
val timeout: TimeoutFeature?
) {
constructor(rawFeatures: RawFeatures) : this(
rawFeatures.welcome?.let(::WelcomeFeature),
DebugFeature(),
HelpFeature(),
GetConfigFeature(),
SetConfigFeature(),
rawFeatures.timeout?.let(::TimeoutFeature)
)
class Features(val welcome: WelcomeFeature?, val timeout: TimeoutFeature?) {
private val debug = DebugFeature()
private val help = HelpFeature()
private val getConfig = GetConfigFeature()
private val setConfig = SetConfigFeature()
private val all = listOf(welcome, debug, help, getConfig, setConfig, timeout)
private val featureMap = mapOf(
@ -31,4 +18,8 @@ class Features(
fun findByString(feature: String) = featureMap[feature]
fun eventFeatures() = all.filterIsInstance<EventFeature>()
companion object {
val DEFAULT = Features(null, null)
}
}

View File

@ -1,12 +1,10 @@
package moe.kageru.kagebot.features
import moe.kageru.kagebot.Log
import moe.kageru.kagebot.MessageUtil.sendEmbed
import moe.kageru.kagebot.config.Config
import moe.kageru.kagebot.config.ConfigParser
import moe.kageru.kagebot.config.RawConfig
import org.javacord.api.entity.channel.TextChannel
import org.javacord.api.event.message.MessageCreateEvent
import java.lang.reflect.InvocationTargetException
class SetConfigFeature : MessageFeature {
@ExperimentalStdlibApi
@ -16,33 +14,16 @@ class SetConfigFeature : MessageFeature {
return
}
val newConfig = message.messageAttachments[0].url.openStream().readAllBytes().decodeToString()
val rawConfig = try {
RawConfig.readFromString(newConfig)
} catch (e: IllegalStateException) {
reportError(message.channel, e)
return
}
try {
Config.localization = Config.localeSpec.string(newConfig)
ConfigParser.reloadFeatures(rawConfig)
Config.featureConfig = Config.featureSpec.string(newConfig)
Config.commandConfig = Config.commandSpec.string(newConfig)
ConfigParser.configFile.writeText(newConfig)
message.channel.sendMessage("Config reloaded.")
} catch (e: IllegalArgumentException) {
} catch (e: Exception) {
message.channel.sendEmbed {
addField("Error", "```${e.message}```")
}
}
}
private fun reportError(message: TextChannel, e: IllegalStateException) {
message.sendEmbed {
addField(
"An unexpected error occured. This is probably caused by a malformed config file. Perhaps this can help:",
"```$e: ${e.message}"
)
}
Log.info("Could not parse new config: $e: ${e.message}")
return
}
}

View File

@ -1,5 +1,6 @@
package moe.kageru.kagebot.features
import com.fasterxml.jackson.annotation.JsonProperty
import moe.kageru.kagebot.Log
import moe.kageru.kagebot.MessageUtil.sendEmbed
import moe.kageru.kagebot.Util.findRole
@ -7,16 +8,14 @@ import moe.kageru.kagebot.Util.findUser
import moe.kageru.kagebot.Util.ifNotEmpty
import moe.kageru.kagebot.config.Config
import moe.kageru.kagebot.config.LocalizationSpec
import moe.kageru.kagebot.config.RawTimeoutFeature
import moe.kageru.kagebot.persistence.Dao
import org.javacord.api.entity.permission.Role
import org.javacord.api.event.message.MessageCreateEvent
import java.time.Duration
import java.time.Instant
class TimeoutFeature(raw: RawTimeoutFeature) : MessageFeature {
private val timeoutRole: Role = raw.role?.let(::findRole)
?: throw IllegalArgumentException("No timeout role defined")
class TimeoutFeature(@JsonProperty("role") role: String) : MessageFeature {
private val timeoutRole: Role = findRole(role)
override fun handle(message: MessageCreateEvent) {
val timeout = message.readableMessageContent.split(' ', limit = 4).let { args ->

View File

@ -5,14 +5,19 @@ import moe.kageru.kagebot.MessageUtil
import moe.kageru.kagebot.Util
import moe.kageru.kagebot.Util.checked
import moe.kageru.kagebot.Util.failed
import moe.kageru.kagebot.config.RawWelcomeFeature
import org.javacord.api.DiscordApi
import org.javacord.api.entity.channel.TextChannel
import org.javacord.api.entity.message.embed.EmbedBuilder
import org.javacord.api.event.message.MessageCreateEvent
import org.javacord.api.event.server.member.ServerMemberJoinEvent
class WelcomeFeature(rawWelcome: RawWelcomeFeature) : MessageFeature, EventFeature {
class WelcomeFeature(
content: List<String>?,
fallbackChannel: String?,
private val fallbackMessage: String?
) : MessageFeature, EventFeature {
val embed: EmbedBuilder? by lazy { content?.let(MessageUtil::listToEmbed) }
override fun register(api: DiscordApi) {
api.addServerMemberJoinListener { event ->
checked { welcomeUser(event) }
@ -40,14 +45,10 @@ class WelcomeFeature(rawWelcome: RawWelcomeFeature) : MessageFeature, EventFeatu
private fun hasFallback(): Boolean = fallbackChannel != null && fallbackMessage != null
val embed: EmbedBuilder? by lazy {
rawWelcome.content?.let(MessageUtil::listToEmbed)
}
private val fallbackChannel: TextChannel? = rawWelcome.fallbackChannel?.let {
requireNotNull(rawWelcome.fallbackMessage) {
private val fallbackChannel: TextChannel? = fallbackChannel?.let {
requireNotNull(fallbackMessage) {
"[feature.welcome.fallbackMessage] must not be null if fallbackChannel is defined"
}
Util.findChannel(it)
}
private val fallbackMessage: String? = rawWelcome.fallbackMessage
}

View File

@ -17,7 +17,7 @@ class ConfigTest : ShouldSpec({
"should properly parse test config" {
Config.system[SystemSpec.serverId] shouldNotBe null
SystemSpec.color shouldBe Color.decode("#1793d0")
Config.features shouldNotBe null
Config.features.welcome!!.embed shouldNotBe null
Config.commands.size shouldBe 3
}