mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-06-30 17:34:39 -05:00
Feature/streamline settings (#1614)
* Cleanup graphql setting mutation
* Validate values read from config
* Generate server-reference.conf files from ServerConfig
* Remove unnecessary enum value handling in config value update
Commit df0078b725 introduced the usage of config4k, which handles enums automatically. Thus, this handling is outdated and not needed anymore
* Generate gql SettingsType from ServerConfig
* Extract settings backup logic
* Generate settings backup files
* Move "group" arg to second position
To make it easier to detect and have it at the same position consistently for all settings.
* Remove setting generation from compilation
* Extract setting generation code into new module
* Extract pure setting generation code into new module
* Remove generated settings files from src tree
* Force each setting to set a default value
This commit is contained in:
62
server/server-config-generate/build.gradle.kts
Normal file
62
server/server-config-generate/build.gradle.kts
Normal file
@@ -0,0 +1,62 @@
|
||||
plugins {
|
||||
id(
|
||||
libs.plugins.kotlin.jvm
|
||||
.get()
|
||||
.pluginId,
|
||||
)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Core Kotlin
|
||||
implementation(kotlin("stdlib-jdk8"))
|
||||
implementation(kotlin("reflect"))
|
||||
|
||||
// Config handling
|
||||
implementation(libs.config)
|
||||
implementation(libs.config4k)
|
||||
|
||||
// Logging
|
||||
implementation(libs.slf4japi)
|
||||
implementation(libs.kotlinlogging)
|
||||
|
||||
// Serialization
|
||||
implementation(libs.serialization.json)
|
||||
implementation(libs.serialization.protobuf)
|
||||
|
||||
// Depend on server-config module for access to ServerConfig and SettingsRegistry
|
||||
implementation(projects.server.serverConfig)
|
||||
}
|
||||
|
||||
tasks {
|
||||
register<JavaExec>("generateSettings") {
|
||||
group = "build setup"
|
||||
description = "Generates settings from ServerConfig"
|
||||
|
||||
dependsOn(compileKotlin)
|
||||
|
||||
// Use this module's classpath which includes server-config as dependency
|
||||
classpath = sourceSets.main.get().runtimeClasspath
|
||||
|
||||
mainClass.set("suwayomi.tachidesk.server.settings.generation.SettingsGeneratorKt")
|
||||
|
||||
// Get reference to server project for file paths
|
||||
val serverProject = project(":server")
|
||||
|
||||
// Set working directory to the server module directory
|
||||
workingDir = serverProject.projectDir
|
||||
|
||||
inputs.files(
|
||||
serverProject.sourceSets.main.get().allSource.filter {
|
||||
it.name.contains("ServerConfig") || it.name.contains("Settings")
|
||||
},
|
||||
)
|
||||
|
||||
outputs.files(
|
||||
serverProject.file("build/generated/src/main/resources/server-reference.conf"),
|
||||
serverProject.file("build/generated/src/test/resources/server-reference.conf"),
|
||||
serverProject.file("build/generated/src/main/kotlin/suwayomi/tachidesk/graphql/types/SettingsType.kt"),
|
||||
serverProject.file("build/generated/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupServerSettings.kt"),
|
||||
serverProject.file("build/generated/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/handlers/BackupSettingsHandler.kt"),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
@file:JvmName("SettingsGeneratorKt")
|
||||
|
||||
package suwayomi.tachidesk.server.settings.generation
|
||||
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Main function to generate settings files from ServerConfig
|
||||
* This is called by the generateSettingsFiles Gradle task
|
||||
*/
|
||||
fun main() {
|
||||
println("Generating settings files from ServerConfig registry...")
|
||||
|
||||
try {
|
||||
// Set output directories relative to the current working directory (server module)
|
||||
val outputDir = File("build/generated/src/main/resources")
|
||||
val testOutputDir = File("build/generated/src/test/resources")
|
||||
val graphqlOutputDir = File("build/generated/src/main/kotlin/suwayomi/tachidesk/graphql/types")
|
||||
val backupSettingsOutputDir = File("build/generated/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models")
|
||||
val backupSettingsHandlerOutputDir = File("build/generated/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/handlers")
|
||||
|
||||
SettingsGenerator.generate(
|
||||
outputDir = outputDir,
|
||||
testOutputDir = testOutputDir,
|
||||
graphqlOutputDir = graphqlOutputDir,
|
||||
backupSettingsOutputDir = backupSettingsOutputDir,
|
||||
backupSettingsHandlerOutputDir = backupSettingsHandlerOutputDir,
|
||||
)
|
||||
|
||||
println("✅ Settings files generation completed successfully!")
|
||||
} catch (e: Exception) {
|
||||
println("❌ Error generating settings files: ${e.message}")
|
||||
e.printStackTrace()
|
||||
System.exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package suwayomi.tachidesk.server.settings.generation
|
||||
|
||||
import suwayomi.tachidesk.server.settings.SettingsRegistry
|
||||
|
||||
internal fun String.addIndentation(times: Int): String = this.prependIndent(" ".repeat(times))
|
||||
|
||||
object KotlinFileGeneratorHelper {
|
||||
fun createFileHeader(packageName: String): String =
|
||||
buildString {
|
||||
appendLine("@file:Suppress(\"ktlint\")")
|
||||
appendLine()
|
||||
appendLine("/*")
|
||||
appendLine(" * Copyright (C) Contributors to the Suwayomi project")
|
||||
appendLine(" *")
|
||||
appendLine(" * This Source Code Form is subject to the terms of the Mozilla Public")
|
||||
appendLine(" * License, v. 2.0. If a copy of the MPL was not distributed with this")
|
||||
appendLine(" * file, You can obtain one at https://mozilla.org/MPL/2.0/. */")
|
||||
appendLine()
|
||||
appendLine("package $packageName")
|
||||
appendLine()
|
||||
}
|
||||
|
||||
fun createImports(
|
||||
staticImports: List<String>,
|
||||
settings: List<SettingsRegistry.SettingMetadata>,
|
||||
): String =
|
||||
buildString {
|
||||
staticImports.forEach { appendLine("import $it") }
|
||||
settings
|
||||
.mapNotNull { it.typeInfo.imports }
|
||||
.flatten()
|
||||
.distinct()
|
||||
.forEach { appendLine("import $it") }
|
||||
|
||||
appendLine()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package suwayomi.tachidesk.server.settings.generation
|
||||
|
||||
import suwayomi.tachidesk.server.settings.SettingsRegistry
|
||||
import java.io.File
|
||||
import kotlin.text.appendLine
|
||||
|
||||
object SettingsBackupServerSettingsGenerator {
|
||||
fun generate(
|
||||
settings: Map<String, SettingsRegistry.SettingMetadata>,
|
||||
outputFile: File,
|
||||
) {
|
||||
outputFile.parentFile.mkdirs()
|
||||
|
||||
val settingsToInclude = settings.values
|
||||
|
||||
if (settingsToInclude.isEmpty()) {
|
||||
println("Warning: No settings found to create BackupServerSettings from.")
|
||||
return
|
||||
}
|
||||
|
||||
val sortedSettings = settingsToInclude.sortedBy { it.protoNumber }
|
||||
|
||||
outputFile.writeText(
|
||||
buildString {
|
||||
appendLine(KotlinFileGeneratorHelper.createFileHeader("suwayomi.tachidesk.manga.impl.backup.proto.models"))
|
||||
writeImports(sortedSettings)
|
||||
writeClass(sortedSettings)
|
||||
},
|
||||
)
|
||||
|
||||
println("BackupServerSettingsGenerator generated successfully! Total settings: ${settingsToInclude.size}")
|
||||
}
|
||||
|
||||
private fun StringBuilder.writeImports(settings: List<SettingsRegistry.SettingMetadata>) {
|
||||
appendLine(
|
||||
KotlinFileGeneratorHelper.createImports(
|
||||
listOf(
|
||||
"kotlinx.serialization.Serializable",
|
||||
"kotlinx.serialization.protobuf.ProtoNumber",
|
||||
"suwayomi.tachidesk.graphql.types.Settings",
|
||||
),
|
||||
settings,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun StringBuilder.writeClass(sortedSettings: List<SettingsRegistry.SettingMetadata>) {
|
||||
appendLine("@Serializable")
|
||||
appendLine("data class BackupServerSettings(")
|
||||
|
||||
writeSettings(sortedSettings, indentation = 4)
|
||||
|
||||
appendLine(") : Settings")
|
||||
appendLine()
|
||||
}
|
||||
|
||||
private fun StringBuilder.writeSettings(
|
||||
sortedSettings: List<SettingsRegistry.SettingMetadata>,
|
||||
indentation: Int,
|
||||
) {
|
||||
sortedSettings.forEach { setting ->
|
||||
val deprecated = setting.deprecated
|
||||
if (deprecated != null) {
|
||||
val replaceWithSuffix = deprecated.replaceWith?.let { ", ReplaceWith(\"$it\")" } ?: ""
|
||||
appendLine(
|
||||
"@Deprecated(\"${deprecated.message}\"$replaceWithSuffix)".addIndentation(
|
||||
indentation,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
appendLine(
|
||||
"@ProtoNumber(${setting.protoNumber}) override var ${setting.name}: ${getSettingType(setting)},"
|
||||
.addIndentation(indentation),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSettingType(setting: SettingsRegistry.SettingMetadata): String =
|
||||
setting.typeInfo.backupType
|
||||
?: setting.typeInfo.specificType
|
||||
?: setting.typeInfo.type.simpleName
|
||||
?: throw RuntimeException("Unknown setting type: ${setting.typeInfo}")
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package suwayomi.tachidesk.server.settings.generation
|
||||
|
||||
import suwayomi.tachidesk.server.settings.SettingsRegistry
|
||||
import java.io.File
|
||||
import kotlin.text.appendLine
|
||||
|
||||
object SettingsBackupSettingsHandlerGenerator {
|
||||
fun generate(
|
||||
settings: Map<String, SettingsRegistry.SettingMetadata>,
|
||||
outputFile: File,
|
||||
) {
|
||||
outputFile.parentFile.mkdirs()
|
||||
|
||||
val settingsToInclude = settings.values
|
||||
|
||||
if (settingsToInclude.isEmpty()) {
|
||||
println("Warning: No settings found to create BackupServerSettings from.")
|
||||
return
|
||||
}
|
||||
|
||||
val groupedSettings = settingsToInclude.groupBy { it.group }
|
||||
|
||||
outputFile.writeText(
|
||||
buildString {
|
||||
appendLine(KotlinFileGeneratorHelper.createFileHeader("suwayomi.tachidesk.manga.impl.backup.proto.handlers"))
|
||||
writeImports(groupedSettings.values.flatten())
|
||||
writeHandler(groupedSettings)
|
||||
},
|
||||
)
|
||||
|
||||
println("BackupServerSettings generated successfully! Total settings: ${settingsToInclude.size}")
|
||||
}
|
||||
|
||||
private fun StringBuilder.writeImports(settings: List<SettingsRegistry.SettingMetadata>) {
|
||||
appendLine(
|
||||
KotlinFileGeneratorHelper.createImports(
|
||||
listOf(
|
||||
"suwayomi.tachidesk.graphql.mutations.SettingsMutation",
|
||||
"suwayomi.tachidesk.manga.impl.backup.BackupFlags",
|
||||
"suwayomi.tachidesk.manga.impl.backup.proto.models.BackupServerSettings",
|
||||
"suwayomi.tachidesk.server.serverConfig",
|
||||
"suwayomi.tachidesk.server.settings.SettingsRegistry",
|
||||
),
|
||||
settings,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun StringBuilder.writeHandler(groupedSettings: Map<String, List<SettingsRegistry.SettingMetadata>>) {
|
||||
appendLine("object BackupSettingsHandler {")
|
||||
|
||||
writeBackupFunction(groupedSettings)
|
||||
appendLine()
|
||||
writeRestoreFunction(groupedSettings.values.flatten())
|
||||
|
||||
appendLine("}")
|
||||
appendLine()
|
||||
}
|
||||
|
||||
private fun StringBuilder.writeBackupFunction(groupedSettings: Map<String, List<SettingsRegistry.SettingMetadata>>) {
|
||||
val indentation = 4
|
||||
val contentIndentation = indentation * 2
|
||||
|
||||
appendLine("fun backup(flags: BackupFlags): BackupServerSettings? {".addIndentation(indentation))
|
||||
appendLine("if (!flags.includeServerSettings) { return null }".addIndentation(contentIndentation))
|
||||
appendLine()
|
||||
appendLine("return BackupServerSettings(".addIndentation(contentIndentation))
|
||||
writeSettings(groupedSettings, indentation * 3)
|
||||
appendLine(")".addIndentation(contentIndentation))
|
||||
appendLine("}".addIndentation(indentation))
|
||||
}
|
||||
|
||||
private fun StringBuilder.writeRestoreFunction(settings: List<SettingsRegistry.SettingMetadata>) {
|
||||
val indentation = 4
|
||||
val contentIndentation = indentation * 2
|
||||
|
||||
appendLine("fun restore(backupServerSettings: BackupServerSettings?) {".addIndentation(indentation))
|
||||
appendLine("if (backupServerSettings == null) { return }".addIndentation(contentIndentation))
|
||||
appendLine()
|
||||
appendLine("SettingsMutation().updateSettings(".addIndentation(contentIndentation))
|
||||
appendLine("backupServerSettings.copy(".addIndentation(indentation * 3))
|
||||
|
||||
val deprecatedSettings = settings.filter { it.typeInfo.restoreLegacy != null }
|
||||
deprecatedSettings.forEach { setting ->
|
||||
appendLine(
|
||||
"${setting.name} = SettingsRegistry.get(\"${setting.name}\")!!.typeInfo.restoreLegacy!!(".addIndentation(indentation * 4) +
|
||||
"backupServerSettings.${setting.name}" +
|
||||
") as ${getSettingType(setting, false)},",
|
||||
)
|
||||
}
|
||||
appendLine("),".addIndentation(indentation * 3))
|
||||
appendLine(")".addIndentation(contentIndentation))
|
||||
appendLine("}".addIndentation(indentation))
|
||||
}
|
||||
|
||||
private fun StringBuilder.writeSettings(
|
||||
groupedSettings: Map<String, List<SettingsRegistry.SettingMetadata>>,
|
||||
indentation: Int,
|
||||
) {
|
||||
groupedSettings.forEach { (group, settings) ->
|
||||
appendLine("// $group".addIndentation(indentation))
|
||||
settings.forEach { setting -> writeSetting(setting, indentation) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun StringBuilder.writeSetting(
|
||||
setting: SettingsRegistry.SettingMetadata,
|
||||
indentation: Int,
|
||||
) {
|
||||
appendLine("${setting.name} = ${getConfigAccess(setting)},".addIndentation(indentation))
|
||||
}
|
||||
|
||||
private fun getSettingType(
|
||||
setting: SettingsRegistry.SettingMetadata,
|
||||
asBackup: Boolean,
|
||||
): String {
|
||||
val possibleType = setting.typeInfo.specificType ?: setting.typeInfo.type.simpleName
|
||||
|
||||
val exception = RuntimeException("Unknown setting type: ${setting.typeInfo}")
|
||||
|
||||
if (asBackup) {
|
||||
return setting.typeInfo.backupType ?: possibleType ?: throw exception
|
||||
}
|
||||
|
||||
return possibleType ?: throw exception
|
||||
}
|
||||
|
||||
private fun getConfigAccess(setting: SettingsRegistry.SettingMetadata): String {
|
||||
if (setting.typeInfo.convertToBackupType != null) {
|
||||
return "SettingsRegistry.get(\"${setting.name}\")!!.typeInfo.convertToBackupType!!(" +
|
||||
"serverConfig.${setting.name}.value" +
|
||||
") as ${getSettingType(setting, true)}"
|
||||
}
|
||||
|
||||
return "serverConfig.${setting.name}.value"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package suwayomi.tachidesk.server.settings.generation
|
||||
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import io.github.config4k.toConfig
|
||||
import suwayomi.tachidesk.server.settings.SettingsRegistry
|
||||
import java.io.File
|
||||
|
||||
object SettingsConfigFileGenerator {
|
||||
private const val SERVER_PREFIX = "server."
|
||||
|
||||
fun generate(
|
||||
outputDir: File,
|
||||
testOutputDir: File,
|
||||
settings: Map<String, SettingsRegistry.SettingMetadata>,
|
||||
) {
|
||||
// Config files only include up-to-date settings.
|
||||
val settingsToInclude = settings.filterValues { it.deprecated == null }
|
||||
|
||||
if (settingsToInclude.isEmpty()) {
|
||||
println("Warning: No settings found to write to config files.")
|
||||
return
|
||||
}
|
||||
|
||||
generateServerReferenceConf(settingsToInclude, outputDir)
|
||||
generateServerReferenceConf(settingsToInclude, testOutputDir)
|
||||
|
||||
println("Settings config file generated successfully! Total settings: ${settingsToInclude.size}")
|
||||
println("- Main config: ${outputDir.resolve("server-reference.conf").absolutePath}")
|
||||
println("- Test config: ${testOutputDir.resolve("server-reference.conf").absolutePath}")
|
||||
}
|
||||
|
||||
private fun generateServerReferenceConf(
|
||||
settings: Map<String, SettingsRegistry.SettingMetadata>,
|
||||
outputDir: File,
|
||||
) {
|
||||
outputDir.mkdirs()
|
||||
val outputFile = outputDir.resolve("server-reference.conf")
|
||||
|
||||
val groupedSettings = settings.values.groupBy { it.group }
|
||||
|
||||
// Write the config with comments
|
||||
outputFile.writeText(
|
||||
buildString {
|
||||
writeSettings(groupedSettings)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun StringBuilder.writeSettings(groupedSettings: Map<String, List<SettingsRegistry.SettingMetadata>>) {
|
||||
val renderOptions =
|
||||
ConfigRenderOptions
|
||||
.defaults()
|
||||
.setOriginComments(false)
|
||||
.setComments(false)
|
||||
.setFormatted(true)
|
||||
.setJson(false)
|
||||
|
||||
var isFirstGroup = true
|
||||
groupedSettings.forEach { (groupName, groupSettings) ->
|
||||
// Prevent empty line at start of the file
|
||||
if (!isFirstGroup) {
|
||||
appendLine()
|
||||
}
|
||||
isFirstGroup = false
|
||||
|
||||
appendLine("# $groupName")
|
||||
|
||||
groupSettings.forEach { setting ->
|
||||
writeSetting(setting, renderOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun StringBuilder.writeSetting(
|
||||
setting: SettingsRegistry.SettingMetadata,
|
||||
renderOptions: ConfigRenderOptions,
|
||||
) {
|
||||
val key = "$SERVER_PREFIX${setting.name}"
|
||||
|
||||
val configValue = setting.defaultValue.toConfig("internal").getValue("internal")
|
||||
var renderedValue = configValue.render(renderOptions)
|
||||
|
||||
// Force quotes on all string values for consistency
|
||||
// Check if it's a string value that's not already quoted
|
||||
if (setting.defaultValue is String && !renderedValue.startsWith("\"")) {
|
||||
renderedValue = "\"$renderedValue\""
|
||||
}
|
||||
|
||||
val settingString = "$key = $renderedValue"
|
||||
|
||||
val description = setting.description
|
||||
if (description != null) {
|
||||
val descriptionLines = description.split("\n")
|
||||
|
||||
if (descriptionLines.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
appendLine("$settingString # ${descriptionLines[0]}")
|
||||
descriptionLines.drop(1).forEach { line ->
|
||||
appendLine("# $line")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
appendLine(settingString)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package suwayomi.tachidesk.server.settings.generation
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import suwayomi.tachidesk.server.ServerConfig
|
||||
import suwayomi.tachidesk.server.settings.SettingsRegistry
|
||||
import suwayomi.tachidesk.server.util.ConfigTypeRegistration
|
||||
import java.io.File
|
||||
import kotlin.reflect.KProperty1
|
||||
import kotlin.reflect.full.memberProperties
|
||||
|
||||
/**
|
||||
* Utility to generate settings files from ServerConfig and SettingsRegistry
|
||||
* This can be run as a standalone main function to generate all required files
|
||||
*/
|
||||
object SettingsGenerator {
|
||||
init {
|
||||
// Register custom types for config serialization
|
||||
ConfigTypeRegistration.registerCustomTypes()
|
||||
triggerSettingRegistration()
|
||||
}
|
||||
|
||||
/**
|
||||
* Force registration of all settings without full ServerConfig instantiation
|
||||
*/
|
||||
private fun triggerSettingRegistration() {
|
||||
// This creates a minimal instance just to trigger delegate registration
|
||||
try {
|
||||
val mockConfig =
|
||||
ConfigFactory.parseString(
|
||||
"""
|
||||
server {
|
||||
ip = "0.0.0.0"
|
||||
port = 4567
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
val tempConfig = ServerConfig { mockConfig.getConfig("server") }
|
||||
// Access all properties to trigger delegate registrations
|
||||
tempConfig::class.memberProperties.forEach { prop ->
|
||||
try {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(prop as KProperty1<ServerConfig, Any?>).get(tempConfig)
|
||||
} catch (e: Exception) {
|
||||
// Ignore errors during registration
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Registration failed, but we tried
|
||||
}
|
||||
}
|
||||
|
||||
fun generate(
|
||||
outputDir: File,
|
||||
testOutputDir: File,
|
||||
graphqlOutputDir: File,
|
||||
backupSettingsOutputDir: File,
|
||||
backupSettingsHandlerOutputDir: File,
|
||||
) {
|
||||
val settings = SettingsRegistry.getAll()
|
||||
|
||||
if (settings.isEmpty()) {
|
||||
println("Warning: No settings found in registry. Settings might not be initialized.")
|
||||
return
|
||||
}
|
||||
|
||||
println(" - Total: ${settings.size}")
|
||||
println(" - Deprecated: ${settings.values.count { it.deprecated != null }}")
|
||||
println(" - Require restart: ${settings.values.count { it.requiresRestart }}")
|
||||
|
||||
SettingsConfigFileGenerator.generate(outputDir, testOutputDir, settings)
|
||||
|
||||
val settingsTypeFile = graphqlOutputDir.resolve("SettingsType.kt")
|
||||
SettingsGraphqlTypeGenerator.generate(settings, settingsTypeFile)
|
||||
|
||||
val backupServerSettingsFile = backupSettingsOutputDir.resolve("BackupServerSettings.kt")
|
||||
SettingsBackupServerSettingsGenerator.generate(settings, backupServerSettingsFile)
|
||||
|
||||
val backupSettingsHandlerFile = backupSettingsHandlerOutputDir.resolve("BackupSettingsHandler.kt")
|
||||
SettingsBackupSettingsHandlerGenerator.generate(settings, backupSettingsHandlerFile)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package suwayomi.tachidesk.server.settings.generation
|
||||
|
||||
import suwayomi.tachidesk.server.settings.SettingsRegistry
|
||||
import java.io.File
|
||||
import kotlin.text.appendLine
|
||||
|
||||
object SettingsGraphqlTypeGenerator {
|
||||
fun generate(
|
||||
settings: Map<String, SettingsRegistry.SettingMetadata>,
|
||||
outputFile: File,
|
||||
) {
|
||||
outputFile.parentFile.mkdirs()
|
||||
|
||||
val settingsToInclude = settings.values
|
||||
|
||||
if (settingsToInclude.isEmpty()) {
|
||||
println("Warning: No settings found to create graphql type from.")
|
||||
return
|
||||
}
|
||||
|
||||
val groupedSettings = settingsToInclude.groupBy { it.group }
|
||||
|
||||
outputFile.writeText(
|
||||
buildString {
|
||||
appendLine(KotlinFileGeneratorHelper.createFileHeader("suwayomi.tachidesk.graphql.types"))
|
||||
writeImports(groupedSettings.values.flatten())
|
||||
writeSettingsInterface(groupedSettings)
|
||||
writePartialSettingsType(groupedSettings)
|
||||
writeSettingsType(groupedSettings)
|
||||
},
|
||||
)
|
||||
|
||||
println("Graphql type generated successfully! Total settings: ${settingsToInclude.size}")
|
||||
}
|
||||
|
||||
private fun StringBuilder.writeImports(settings: List<SettingsRegistry.SettingMetadata>) {
|
||||
appendLine(
|
||||
KotlinFileGeneratorHelper.createImports(
|
||||
listOf(
|
||||
"com.expediagroup.graphql.generator.annotations.GraphQLDeprecated",
|
||||
"com.expediagroup.graphql.generator.annotations.GraphQLIgnore",
|
||||
"suwayomi.tachidesk.graphql.server.primitives.Node",
|
||||
"suwayomi.tachidesk.server.ServerConfig",
|
||||
"suwayomi.tachidesk.server.serverConfig",
|
||||
"suwayomi.tachidesk.server.settings.SettingsRegistry",
|
||||
),
|
||||
settings,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun StringBuilder.writeSettingsInterface(groupedSettings: Map<String, List<SettingsRegistry.SettingMetadata>>) {
|
||||
appendLine("interface Settings : Node {")
|
||||
|
||||
writeSettings(groupedSettings, indentation = 4, asType = true, isOverride = false, isNullable = true, isInterface = true)
|
||||
|
||||
appendLine("}")
|
||||
appendLine()
|
||||
}
|
||||
|
||||
private fun StringBuilder.writePartialSettingsType(groupedSettings: Map<String, List<SettingsRegistry.SettingMetadata>>) {
|
||||
appendLine("data class PartialSettingsType(")
|
||||
|
||||
writeSettings(groupedSettings, indentation = 4, asType = true, isOverride = true, isNullable = true, isInterface = false)
|
||||
|
||||
appendLine(") : Settings")
|
||||
appendLine()
|
||||
}
|
||||
|
||||
private fun StringBuilder.writeSettingsType(groupedSettings: Map<String, List<SettingsRegistry.SettingMetadata>>) {
|
||||
appendLine("class SettingsType(")
|
||||
|
||||
writeSettings(groupedSettings, indentation = 4, asType = true, isOverride = true, isNullable = false, isInterface = false)
|
||||
|
||||
appendLine(") : Settings {")
|
||||
|
||||
// Write secondary constructor
|
||||
val indentation = 4
|
||||
appendLine("@Suppress(\"UNCHECKED_CAST\")".addIndentation(indentation))
|
||||
appendLine("constructor(config: ServerConfig = serverConfig) : this(".addIndentation(indentation))
|
||||
|
||||
writeSettings(
|
||||
groupedSettings,
|
||||
indentation = indentation * 2,
|
||||
asType = false,
|
||||
isOverride = false,
|
||||
isNullable = false,
|
||||
isInterface = false,
|
||||
)
|
||||
|
||||
appendLine(")".addIndentation(indentation))
|
||||
|
||||
appendLine("}")
|
||||
appendLine()
|
||||
}
|
||||
|
||||
private fun StringBuilder.writeSettings(
|
||||
groupedSettings: Map<String, List<SettingsRegistry.SettingMetadata>>,
|
||||
indentation: Int,
|
||||
asType: Boolean,
|
||||
isOverride: Boolean,
|
||||
isNullable: Boolean,
|
||||
isInterface: Boolean,
|
||||
) {
|
||||
groupedSettings.forEach { (group, settings) ->
|
||||
appendLine("// $group".addIndentation(indentation))
|
||||
settings.forEach { setting -> writeSetting(setting, indentation, asType, isOverride, isNullable, isInterface) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun StringBuilder.writeSetting(
|
||||
setting: SettingsRegistry.SettingMetadata,
|
||||
indentation: Int,
|
||||
asType: Boolean,
|
||||
isOverride: Boolean,
|
||||
isNullable: Boolean,
|
||||
isInterface: Boolean,
|
||||
) {
|
||||
if (!asType) {
|
||||
appendLine("${getConfigAccess(setting)},".addIndentation(indentation))
|
||||
return
|
||||
}
|
||||
|
||||
if (setting.requiresRestart) {
|
||||
appendLine("@GraphQLIgnore".addIndentation(indentation))
|
||||
}
|
||||
|
||||
val deprecated = setting.deprecated
|
||||
if (deprecated != null) {
|
||||
val replaceWithSuffix = deprecated.replaceWith?.let { ", ReplaceWith(\"$it\")" } ?: ""
|
||||
appendLine(
|
||||
"@GraphQLDeprecated(\"${deprecated.message}\"$replaceWithSuffix)".addIndentation(
|
||||
indentation,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val overridePrefix = if (isOverride) "override " else ""
|
||||
val nullableSuffix = if (isNullable) "?" else ""
|
||||
val commaSuffix = if (isOverride) "," else ""
|
||||
appendLine(
|
||||
"${overridePrefix}val ${setting.name}: ${getGraphQLType(
|
||||
setting,
|
||||
isInterface,
|
||||
)}$nullableSuffix$commaSuffix".addIndentation(indentation),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getGraphQLType(
|
||||
setting: SettingsRegistry.SettingMetadata,
|
||||
isInterface: Boolean,
|
||||
): String {
|
||||
val possibleType = setting.typeInfo.specificType ?: setting.typeInfo.type.simpleName
|
||||
|
||||
val exception = RuntimeException("Unknown setting type: ${setting.typeInfo}")
|
||||
|
||||
if (isInterface) {
|
||||
return setting.typeInfo.interfaceType ?: possibleType ?: throw exception
|
||||
}
|
||||
|
||||
return possibleType ?: throw exception
|
||||
}
|
||||
|
||||
private fun getConfigAccess(setting: SettingsRegistry.SettingMetadata): String {
|
||||
if (setting.typeInfo.convertToGqlType != null) {
|
||||
return "SettingsRegistry.get(\"${setting.name}\")!!.typeInfo.convertToGqlType!!(" +
|
||||
"config.${setting.name}.value" +
|
||||
") as ${getGraphQLType(setting, false)}"
|
||||
}
|
||||
|
||||
return "config.${setting.name}.value"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user