Add ksp to generate MangaSource enum

This commit is contained in:
2022-04-22 20:31:54 +03:00
parent dd9ace35d1
commit 62e43c407a
29 changed files with 254 additions and 83 deletions

View File

@@ -0,0 +1,7 @@
plugins {
id 'org.jetbrains.kotlin.jvm'
}
dependencies {
implementation 'com.google.devtools.ksp:symbol-processing-api:1.6.20-1.0.5'
}

View File

@@ -0,0 +1,142 @@
package org.koitharu.kotatsu.parsers.ksp
import com.google.devtools.ksp.isAbstract
import com.google.devtools.ksp.processing.*
import com.google.devtools.ksp.symbol.ClassKind
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSVisitorVoid
import com.google.devtools.ksp.validate
import java.io.Writer
class ParserProcessor(
private val codeGenerator: CodeGenerator,
private val logger: KSPLogger,
private val options: Map<String, String>,
) : SymbolProcessor {
override fun process(resolver: Resolver): List<KSAnnotated> {
val symbols = resolver
.getSymbolsWithAnnotation("org.koitharu.kotatsu.parsers.MangaSourceParser")
val ret = symbols.filterNot { it.validate() }.toList()
if (!symbols.iterator().hasNext()) {
return ret
}
val dependencies = Dependencies.ALL_FILES
val factoryFile = try {
codeGenerator.createNewFile(
dependencies = dependencies,
packageName = "org.koitharu.kotatsu.parsers",
fileName = "MangaParserFactory",
)
} catch (e: FileAlreadyExistsException) {
logger.warn(e.toString(), null)
null
}
val sourcesFile = try {
codeGenerator.createNewFile(
dependencies = dependencies,
packageName = "org.koitharu.kotatsu.parsers.model",
fileName = "MangaSource",
)
} catch (e: FileAlreadyExistsException) {
logger.warn(e.toString(), null)
null
}
sourcesFile?.writer().use { sourcesWriter ->
factoryFile?.writer().use { factoryWriter ->
writeContent(sourcesWriter, factoryWriter, symbols)
}
}
return ret
}
private fun writeContent(
sourcesWriter: Writer?,
factoryWriter: Writer?,
symbols: Sequence<KSAnnotated>,
) {
if (sourcesWriter == null && factoryWriter == null) {
return
}
factoryWriter?.write(
"""
package org.koitharu.kotatsu.parsers
import org.koitharu.kotatsu.parsers.model.MangaSource
fun MangaSource.newParser(context: MangaLoaderContext): MangaParser = when (this) {
""".trimIndent(),
)
sourcesWriter?.write(
"""
package org.koitharu.kotatsu.parsers.model
import org.koitharu.kotatsu.parsers.model.MangaSource
enum class MangaSource(
val title: String,
val locale: String?,
) {
LOCAL("Local", null),
""".trimIndent(),
)
val visitor = ParserVisitor(sourcesWriter, factoryWriter)
symbols
.filter { it is KSClassDeclaration && it.validate() }
.forEach { it.accept(visitor, Unit) }
factoryWriter?.write(
"""
MangaSource.LOCAL -> throw NotImplementedError("Local manga parser is not supported")
}.also {
require(it.source == this) {
"Cannot instantiate manga parser: ${'$'}name mapped to ${'$'}{it.source}"
}
}
""".trimIndent(),
)
sourcesWriter?.write(
"""
;
}
""".trimIndent(),
)
}
private inner class ParserVisitor(
private val sourcesWriter: Writer?,
private val factoryWriter: Writer?,
) : KSVisitorVoid() {
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
if (classDeclaration.classKind != ClassKind.CLASS || classDeclaration.isAbstract()) {
logger.error("Only non-abstract can be annotated with @MangaSourceParser", classDeclaration)
}
val annotation = classDeclaration.annotations.single { it.shortName.asString() == "MangaSourceParser" }
val name = annotation.arguments.single { it.name?.asString() == "name" }.value as String
val title = annotation.arguments.single { it.name?.asString() == "title" }.value as String
val locale = annotation.arguments.single { it.name?.asString() == "locale" }.value as String
val localeString = if (locale.isEmpty()) "null" else "\"$locale\""
if (name.any { it.isLowerCase() }) {
logger.error("Manga source name must be uppercase: $name")
}
val constructor = classDeclaration.primaryConstructor
if (constructor == null || constructor.parameters.count { !it.hasDefault } != 1) {
logger.error(
"Class with @MangaSourceParser must have a primary constructor with one parameter",
classDeclaration,
)
}
val className = classDeclaration.qualifiedName?.asString()
factoryWriter?.write("\tMangaSource.$name -> $className(context)\n")
sourcesWriter?.write("\t$name(\"$title\", $localeString),\n")
}
}
}

View File

@@ -0,0 +1,16 @@
package org.koitharu.kotatsu.parsers.ksp
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
class ParserProcessorProvider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return ParserProcessor(
codeGenerator = environment.codeGenerator,
logger = environment.logger,
options = environment.options
)
}
}

View File

@@ -0,0 +1 @@
org.koitharu.kotatsu.parsers.ksp.ParserProcessorProvider