Switch to java-poet for codegen. This is a breaking change
This commit is contained in:
parent
50574d5cd4
commit
20cd8e7d7f
|
@ -1,2 +1,2 @@
|
||||||
group = "io.gitlab.jfronny"
|
group = "io.gitlab.jfronny"
|
||||||
version = "1.1-SNAPSHOT"
|
version = "1.2-SNAPSHOT"
|
||||||
|
|
|
@ -4,4 +4,5 @@ plugins {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.eclipse.jgit:org.eclipse.jgit:[6.0, 7.0)")
|
implementation("org.eclipse.jgit:org.eclipse.jgit:[6.0, 7.0)")
|
||||||
|
implementation("com.squareup:javapoet:1.13.0")
|
||||||
}
|
}
|
|
@ -1,21 +1,154 @@
|
||||||
|
@file:Suppress("unused", "HasPlatformType")
|
||||||
|
|
||||||
package io.gitlab.jfronny.scripts
|
package io.gitlab.jfronny.scripts
|
||||||
|
|
||||||
import io.gitlab.jfronny.scripts.codegen.ContentGenerator
|
import com.squareup.javapoet.*
|
||||||
import org.gradle.api.Action
|
import org.gradle.api.Action
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import org.gradle.api.tasks.SourceSet
|
import org.gradle.api.tasks.SourceSet
|
||||||
import org.gradle.kotlin.dsl.extra
|
import org.gradle.kotlin.dsl.extra
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
import java.util.function.Supplier
|
||||||
|
import javax.lang.model.element.Element
|
||||||
|
import javax.lang.model.element.ExecutableElement
|
||||||
|
import javax.lang.model.element.Modifier
|
||||||
|
import javax.lang.model.type.DeclaredType
|
||||||
|
import javax.lang.model.type.TypeMirror
|
||||||
|
import javax.lang.model.util.Types
|
||||||
|
|
||||||
val Project.codegenDir get() = buildDir.resolve("generated/sources/jfCodegen")
|
val Project.codegenDir get() = buildDir.resolve("generated/sources/jfCodegen")
|
||||||
|
|
||||||
fun SourceSet.generate(project: Project, generate: Action<ContentGenerator>) {
|
fun SourceSet.generate(project: Project, generate: Action<ContentGenerator>) {
|
||||||
val generators = project.extra["codeGenerators"] as LinkedHashMap<String, ContentGenerator.Generated>
|
val generators = project.extra["codeGenerators"] as LinkedHashMap<String, ContentGenerator.Generated>
|
||||||
val exists = generators.containsKey(name)
|
val exists = generators.containsKey(name)
|
||||||
val generator = ContentGenerator()
|
val generator = ContentGenerator().run(generate)
|
||||||
generate.execute(generator)
|
|
||||||
generators[name] = if (exists) generators[name]!!.merge(generator.finalize()) else generator.finalize()
|
generators[name] = if (exists) generators[name]!!.merge(generator.finalize()) else generator.finalize()
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
java.srcDir(project.codegenDir.resolve("java/$name"))
|
java.srcDir(project.codegenDir.resolve("java/$name"))
|
||||||
resources.srcDir(project.codegenDir.resolve("resources/$name"))
|
resources.srcDir(project.codegenDir.resolve("resources/$name"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ContentGenerator {
|
||||||
|
private var finalized = false
|
||||||
|
|
||||||
|
data class Generated(val classes: Map<String, String>, val resources: Map<String, ByteArray>) {
|
||||||
|
fun merge(other: Generated): Generated = Generated(classes + other.classes, resources + other.resources)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class ImmutableMap<Key, Value>(private val inner: Map<Key, Value>): Map<Key, Value> by inner
|
||||||
|
|
||||||
|
fun finalize(): Generated {
|
||||||
|
val fin = Generated(ImmutableMap(classes), ImmutableMap(resources))
|
||||||
|
finalized = true
|
||||||
|
return fin
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> ensureMutable(action: () -> T): T {
|
||||||
|
if (finalized) throw IllegalAccessException("Attempted to access CodeGenerator after it was finalized")
|
||||||
|
return action()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val classes: MutableMap<String, String> = LinkedHashMap()
|
||||||
|
private val resources: MutableMap<String, ByteArray> = LinkedHashMap()
|
||||||
|
|
||||||
|
fun `class`(`package`: String, name: String, generator: Action<TypeSpec.Builder>) = ensureMutable {
|
||||||
|
check(`package`, "package", packagePattern)
|
||||||
|
check(name, "class name", classNamePattern)
|
||||||
|
val builder = TypeSpec.classBuilder(ClassName.get(`package`, name))
|
||||||
|
generator.execute(builder)
|
||||||
|
val javaFile: JavaFile = JavaFile.builder(`package`, builder.build())
|
||||||
|
.skipJavaLangImports(true)
|
||||||
|
.indent(" ")
|
||||||
|
.addFileComment("Automatically generated through JfCodegen, do not edit!")
|
||||||
|
.build()
|
||||||
|
classes["${`package`.replace('.', '/')}/$name.java"] = javaFile.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun text(path: String, generator: Action<StringBuilder>) = ensureMutable {
|
||||||
|
check(path, "path", pathPattern)
|
||||||
|
val builder = StringBuilder()
|
||||||
|
generator.execute(builder)
|
||||||
|
resources[path] = builder.toString().toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun blob(path: String, generator: Supplier<ByteArray>) = ensureMutable {
|
||||||
|
check(path, "path", pathPattern)
|
||||||
|
resources[path] = generator.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic private val packagePattern = Regex("(?:[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*)")
|
||||||
|
@JvmStatic private val classNamePattern = Regex("(?:[A-Z][A-Za-z0-9_\$]*)")
|
||||||
|
@JvmStatic private val pathPattern = Regex("(?:([a-z]+/)*[a-zA-Z][a-zA-Z0-9_.]*)")
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
private fun check(name: String, type: String, pattern: Regex) {
|
||||||
|
if (!pattern.matches(name)) throw IllegalArgumentException("$type \"$name\" is not a valid $type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extensions for TypeSpec.Builder
|
||||||
|
fun TypeSpec.Builder.javadoc(`import`: String) = addJavadoc(import)
|
||||||
|
fun TypeSpec.Builder.javadoc(generate: Action<CodeBlock.Builder>) = addJavadoc(CodeBlock.builder().run(generate).build())
|
||||||
|
fun TypeSpec.Builder.annotation(annotation: Class<*>, generate: Action<AnnotationSpec.Builder>? = null) = addAnnotation(AnnotationSpec.builder(annotation).run(generate).build())
|
||||||
|
fun TypeSpec.Builder.annotation(annotation: ClassName, generate: Action<AnnotationSpec.Builder>? = null) = addAnnotation(AnnotationSpec.builder(annotation).run(generate).build())
|
||||||
|
fun TypeSpec.Builder.modifiers(vararg modifiers: Modifier) = addModifiers(*modifiers)
|
||||||
|
fun TypeSpec.Builder.typeVariable(typeVariable: TypeVariableName) = addTypeVariable(typeVariable)
|
||||||
|
fun TypeSpec.Builder.superInterface(superInterface: TypeName) = addSuperinterface(superInterface)
|
||||||
|
fun TypeSpec.Builder.superInterface(superInterface: Type) = addSuperinterface(superInterface)
|
||||||
|
fun TypeSpec.Builder.superInterface(superInterface: Type, avoidNestedTypeNameClashes: Boolean) = addSuperinterface(superInterface, avoidNestedTypeNameClashes)
|
||||||
|
fun TypeSpec.Builder.superInterface(superInterface: TypeMirror) = addSuperinterface(superInterface)
|
||||||
|
fun TypeSpec.Builder.superInterface(superInterface: TypeMirror, avoidNestedTypeNameClashes: Boolean) = addSuperinterface(superInterface, avoidNestedTypeNameClashes)
|
||||||
|
fun TypeSpec.Builder.enumConstant(name: String) = addEnumConstant(name)
|
||||||
|
fun TypeSpec.Builder.enumConstant(name: String, typeSpec: TypeSpec) = addEnumConstant(name, typeSpec)
|
||||||
|
fun TypeSpec.Builder.field(type: Type, name: String, vararg modifiers: Modifier, generate: Action<FieldSpec.Builder>? = null) = addField(FieldSpec.builder(type, name, *modifiers).run(generate).build())
|
||||||
|
fun TypeSpec.Builder.field(type: TypeName, name: String, vararg modifiers: Modifier, generate: Action<FieldSpec.Builder>? = null) = addField(FieldSpec.builder(type, name, *modifiers).run(generate).build())
|
||||||
|
fun TypeSpec.Builder.field(name: String, value: Boolean, vararg modifiers: Modifier) = field(Boolean::class.java, name, *modifiers) { initializer("$value") }
|
||||||
|
fun TypeSpec.Builder.field(name: String, value: Short, vararg modifiers: Modifier) = field(Short::class.java, name, *modifiers) { initializer("$value") }
|
||||||
|
fun TypeSpec.Builder.field(name: String, value: Int, vararg modifiers: Modifier) = field(Int::class.java, name, *modifiers) { initializer("$value") }
|
||||||
|
fun TypeSpec.Builder.field(name: String, value: Long, vararg modifiers: Modifier) = field(Long::class.java, name, *modifiers) { initializer("${value}L") }
|
||||||
|
fun TypeSpec.Builder.field(name: String, value: Float, vararg modifiers: Modifier) = field(Float::class.java, name, *modifiers) { initializer("${value}f") }
|
||||||
|
fun TypeSpec.Builder.field(name: String, value: Double, vararg modifiers: Modifier) = field(Double::class.java, name, *modifiers) { initializer("$value") }
|
||||||
|
fun TypeSpec.Builder.field(name: String, value: String, vararg modifiers: Modifier) = field(String::class.java, name, *modifiers) { initializer("\$S", value) }
|
||||||
|
fun TypeSpec.Builder.static(generate: Action<CodeBlock.Builder>) = addStaticBlock(CodeBlock.builder().run(generate).build())
|
||||||
|
fun TypeSpec.Builder.initializer(generate: Action<CodeBlock.Builder>) = addInitializerBlock(CodeBlock.builder().run(generate).build())
|
||||||
|
fun TypeSpec.Builder.method(name: String, generate: Action<MethodSpec.Builder>) = addMethod(MethodSpec.methodBuilder(name).run(generate).build())
|
||||||
|
fun TypeSpec.Builder.override(element: ExecutableElement, generate: Action<MethodSpec.Builder>) = addMethod(MethodSpec.overriding(element).run(generate).build())
|
||||||
|
fun TypeSpec.Builder.override(element: ExecutableElement, enclosing: DeclaredType, types: Types, generate: Action<MethodSpec.Builder>) = addMethod(MethodSpec.overriding(element, enclosing, types).run(generate).build())
|
||||||
|
fun TypeSpec.Builder.constructor(generate: Action<MethodSpec.Builder>) = addMethod(MethodSpec.constructorBuilder().run(generate).build())
|
||||||
|
fun TypeSpec.Builder.innerEnum(name: String, generate: Action<TypeSpec.Builder>) = addType(TypeSpec.enumBuilder(name).run(generate).build())
|
||||||
|
fun TypeSpec.Builder.innerInterface(name: String, generate: Action<TypeSpec.Builder>) = addType(TypeSpec.interfaceBuilder(name).run(generate).build())
|
||||||
|
fun TypeSpec.Builder.innerClass(name: String, generate: Action<TypeSpec.Builder>) = addType(TypeSpec.classBuilder(name).run(generate).build())
|
||||||
|
fun TypeSpec.Builder.innerAnnotation(name: String, generate: Action<TypeSpec.Builder>) = addType(TypeSpec.annotationBuilder(name).run(generate).build())
|
||||||
|
fun TypeSpec.Builder.innerEnum(name: ClassName, generate: Action<TypeSpec.Builder>) = addType(TypeSpec.enumBuilder(name).run(generate).build())
|
||||||
|
fun TypeSpec.Builder.innerInterface(name: ClassName, generate: Action<TypeSpec.Builder>) = addType(TypeSpec.interfaceBuilder(name).run(generate).build())
|
||||||
|
fun TypeSpec.Builder.innerClass(name: ClassName, generate: Action<TypeSpec.Builder>) = addType(TypeSpec.classBuilder(name).run(generate).build())
|
||||||
|
fun TypeSpec.Builder.innerAnnotation(name: ClassName, generate: Action<TypeSpec.Builder>) = addType(TypeSpec.annotationBuilder(name).run(generate).build())
|
||||||
|
fun TypeSpec.Builder.origin(origin: Element) = addOriginatingElement(origin)
|
||||||
|
|
||||||
|
// Extensions for AnnotationSpec.Builder
|
||||||
|
fun AnnotationSpec.Builder.member(name: String, format: String, vararg args: Any) = addMember(name, format, args)
|
||||||
|
fun AnnotationSpec.Builder.member(name: String, generate: Action<CodeBlock.Builder>) = addMember(name, CodeBlock.builder().run(generate).build())
|
||||||
|
|
||||||
|
// Extensions for MethodSpec.Builder
|
||||||
|
fun MethodSpec.Builder.javadoc(`import`: String) = addJavadoc(import)
|
||||||
|
fun MethodSpec.Builder.javadoc(generate: Action<CodeBlock.Builder>) = addJavadoc(CodeBlock.builder().run(generate).build())
|
||||||
|
fun MethodSpec.Builder.annotation(annotation: Class<*>, generate: Action<AnnotationSpec.Builder>? = null) = addAnnotation(AnnotationSpec.builder(annotation).run(generate).build())
|
||||||
|
fun MethodSpec.Builder.annotation(annotation: ClassName, generate: Action<AnnotationSpec.Builder>? = null) = addAnnotation(AnnotationSpec.builder(annotation).run(generate).build())
|
||||||
|
fun MethodSpec.Builder.modifiers(vararg modifiers: Modifier) = addModifiers(*modifiers)
|
||||||
|
fun MethodSpec.Builder.typeVariable(typeVariable: TypeVariableName) = addTypeVariable(typeVariable)
|
||||||
|
fun MethodSpec.Builder.parameter(type: Type, name: String, vararg modifiers: Modifier, generate: Action<ParameterSpec.Builder>) = addParameter(ParameterSpec.builder(type, name, *modifiers).run(generate).build())
|
||||||
|
fun MethodSpec.Builder.parameter(type: TypeName, name: String, vararg modifiers: Modifier, generate: Action<ParameterSpec.Builder>) = addParameter(ParameterSpec.builder(type, name, *modifiers).run(generate).build())
|
||||||
|
fun MethodSpec.Builder.exception(exception: Type) = addException(exception)
|
||||||
|
fun MethodSpec.Builder.exception(exception: TypeName) = addException(exception)
|
||||||
|
fun MethodSpec.Builder.code(generate: Action<CodeBlock.Builder>) = addCode(CodeBlock.builder().run(generate).build())
|
||||||
|
fun MethodSpec.Builder.defaultValue(generate: Action<CodeBlock.Builder>) = defaultValue(CodeBlock.builder().run(generate).build())
|
||||||
|
|
||||||
|
// Extensions for ParameterSpec.Builder
|
||||||
|
fun ParameterSpec.Builder.javadoc(`import`: String) = addJavadoc(import)
|
||||||
|
fun ParameterSpec.Builder.javadoc(generate: Action<CodeBlock.Builder>) = addJavadoc(CodeBlock.builder().run(generate).build())
|
||||||
|
fun ParameterSpec.Builder.annotation(annotation: Class<*>, generate: Action<AnnotationSpec.Builder>? = null) = addAnnotation(AnnotationSpec.builder(annotation).run(generate).build())
|
||||||
|
fun ParameterSpec.Builder.annotation(annotation: ClassName, generate: Action<AnnotationSpec.Builder>? = null) = addAnnotation(AnnotationSpec.builder(annotation).run(generate).build())
|
||||||
|
fun ParameterSpec.Builder.modifiers(vararg modifiers: Modifier) = addModifiers(*modifiers)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.gitlab.jfronny.scripts
|
package io.gitlab.jfronny.scripts
|
||||||
|
|
||||||
|
import org.gradle.api.Action
|
||||||
import org.gradle.api.DefaultTask
|
import org.gradle.api.DefaultTask
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import org.gradle.api.Task
|
import org.gradle.api.Task
|
||||||
|
@ -26,4 +27,10 @@ val TaskContainer.deployRelease: Task get() = findByName("deployRelease") ?: reg
|
||||||
|
|
||||||
fun Project.prop(name: String, default: String? = null): String =
|
fun Project.prop(name: String, default: String? = null): String =
|
||||||
if (default == null || hasProperty(name)) property(name).toString()
|
if (default == null || hasProperty(name)) property(name).toString()
|
||||||
else default
|
else default
|
||||||
|
|
||||||
|
// Utility to run actions on values
|
||||||
|
fun <T> T.run(action: Action<T>?): T {
|
||||||
|
action?.execute(this!!)
|
||||||
|
return this
|
||||||
|
}
|
|
@ -1,129 +0,0 @@
|
||||||
package io.gitlab.jfronny.scripts.codegen
|
|
||||||
|
|
||||||
class ClassGenerator(`package`: String, val name: String, val indent: String = " ") : Generator<String>() {
|
|
||||||
override fun generateFinalized() = ensureBody { gen.appendLine("}").toString() }
|
|
||||||
private val gen: StringBuilder = StringBuilder("package $`package`;\n\n")
|
|
||||||
private var headerGenerated = false
|
|
||||||
private var extends: String? = null
|
|
||||||
private val implements: MutableList<String> = ArrayList()
|
|
||||||
private var hadImport = false
|
|
||||||
|
|
||||||
private fun <T> ensureHeader(action: () -> T): T = ensureMutable {
|
|
||||||
if (headerGenerated) throw IllegalAccessException("Attempted to generate pre-header statement while in body")
|
|
||||||
action()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T> ensureBody(action: () -> T): T = ensureMutable {
|
|
||||||
if (!headerGenerated) {
|
|
||||||
headerGenerated = true
|
|
||||||
if (hadImport) gen.appendLine()
|
|
||||||
gen.append("public class $name")
|
|
||||||
if (extends != null) gen.append(" extends $extends")
|
|
||||||
if (implements.isNotEmpty()) gen.append(" implements ").append(implements.joinToString(", ") { it })
|
|
||||||
gen.append(" {")
|
|
||||||
gen.appendLine()
|
|
||||||
}
|
|
||||||
action()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun rawHead(toWrite: String): Unit = ensureHeader { gen.appendLine(toWrite.trimIndent().trim('\n').prependIndent(indent)) }
|
|
||||||
fun rawBody(toWrite: String): Unit = ensureBody { gen.appendLine(toWrite.trimIndent().trim('\n').prependIndent(indent)) }
|
|
||||||
|
|
||||||
fun import(`import`: String): Unit = ensureHeader {
|
|
||||||
check(import, "import", importPattern)
|
|
||||||
gen.appendLine("import $import;")
|
|
||||||
hadImport = true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun extends(extends: String): Unit = ensureHeader {
|
|
||||||
check(extends, "extended class", classPattern)
|
|
||||||
if (this.extends != null) throw IllegalAccessException("Attempted to extend multiple classes")
|
|
||||||
this.extends = extends
|
|
||||||
}
|
|
||||||
|
|
||||||
fun implements(implements: String): Unit = ensureHeader {
|
|
||||||
check(implements, "implemented interface", classPattern)
|
|
||||||
this.implements.add(implements)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fieldRaw(name: String, type: String, valueRaw: String, modifiers: String = defaultModifiers): Unit = ensureBody {
|
|
||||||
check(name, "field", classEntryPattern)
|
|
||||||
check(type, "field type", classPattern)
|
|
||||||
check(modifiers, "set of modifiers", modifierPattern)
|
|
||||||
gen.appendLine("$indent$modifiers $type $name = $valueRaw;")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun field(name: String, value: Boolean, modifiers: String = defaultModifiers) = fieldRaw(name, "boolean", value.toString(), modifiers)
|
|
||||||
fun field(name: String, value: Short, modifiers: String = defaultModifiers) = fieldRaw(name, "short", value.toString(), modifiers)
|
|
||||||
fun field(name: String, value: Int, modifiers: String = defaultModifiers) = fieldRaw(name, "int", value.toString(), modifiers)
|
|
||||||
fun field(name: String, value: Long, modifiers: String = defaultModifiers) = fieldRaw(name, "long", value.toString() + "L", modifiers)
|
|
||||||
fun field(name: String, value: Float, modifiers: String = defaultModifiers) = fieldRaw(name, "float", value.toString() + "f", modifiers)
|
|
||||||
fun field(name: String, value: Double, modifiers: String = defaultModifiers) = fieldRaw(name, "double", value.toString(), modifiers)
|
|
||||||
fun field(name: String, value: String, modifiers: String = defaultModifiers) = fieldRaw(name, "String", enquote(value), modifiers)
|
|
||||||
|
|
||||||
fun procedure(name: String, argument: Map<String, String>, modifiers: String = defaultModifiers, body: String, throws: String? = null): Unit = ensureBody {
|
|
||||||
check(name, "procedure", classEntryPattern)
|
|
||||||
if (argument.any { !classEntryPattern.matches(it.key) || !classPattern.matches(it.value) }) throw IllegalArgumentException("invalid argument on procedure")
|
|
||||||
check(modifiers, "set of modifiers", modifierPattern)
|
|
||||||
ensureBrackets(body)
|
|
||||||
if (throws != null) check(throws, "thrown exceptions list", classListPattern)
|
|
||||||
gen.append(indent)
|
|
||||||
gen.append(modifiers)
|
|
||||||
gen.append(" void ")
|
|
||||||
gen.append(name)
|
|
||||||
gen.append("(")
|
|
||||||
gen.append(argument.entries.joinToString(", ") { "${it.key} ${it.value}" })
|
|
||||||
gen.append(") ")
|
|
||||||
if (throws != null) gen.append(throws).append(' ')
|
|
||||||
gen.append('{')
|
|
||||||
gen.appendLine()
|
|
||||||
gen.appendLine(body.trimIndent().prependIndent("$indent "))
|
|
||||||
gen.appendLine("$indent}")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun function(name: String, returnType: String, argument: Map<String, String>, modifiers: String = defaultModifiers, body: String, throws: String? = null): Unit = ensureBody {
|
|
||||||
check(name, "procedure", classEntryPattern)
|
|
||||||
check(returnType, "return type", classPattern)
|
|
||||||
if (argument.any { !classEntryPattern.matches(it.key) || !classPattern.matches(it.value) }) throw IllegalArgumentException("invalid argument on procedure")
|
|
||||||
check(modifiers, "set of modifiers", modifierPattern)
|
|
||||||
ensureBrackets(body)
|
|
||||||
if (throws != null) check(throws, "thrown exceptions list", classListPattern)
|
|
||||||
gen.append(indent)
|
|
||||||
gen.append(modifiers)
|
|
||||||
gen.append(" $returnType ")
|
|
||||||
gen.append(name)
|
|
||||||
gen.append("(")
|
|
||||||
gen.append(argument.entries.joinToString(", ") { "${it.key} ${it.value}" })
|
|
||||||
gen.append(") ")
|
|
||||||
if (throws != null) gen.append(throws).append(' ')
|
|
||||||
gen.append('{')
|
|
||||||
gen.appendLine()
|
|
||||||
gen.appendLine(body.trimIndent().prependIndent("$indent "))
|
|
||||||
gen.appendLine("$indent}")
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmStatic private val defaultModifiers = "public static"
|
|
||||||
@JvmStatic private fun enquote(value: String): String = "\"${value.replace("\\", "\\\\").replace("\"", "\\\"")}\""
|
|
||||||
@JvmStatic private fun ensureBrackets(value: String): Unit {
|
|
||||||
var state = 0 // 0 -> outside of anything, 1 -> inside string, 2 -> inside block quote
|
|
||||||
var brackets = 0
|
|
||||||
value.forEachIndexed { i, c ->
|
|
||||||
when (state) {
|
|
||||||
0 -> {
|
|
||||||
if (c == '"') state = if (i + 2 < value.length && value[i+1] == '"' && value[i+2] == '"') 2 else 1
|
|
||||||
if (c == '{') brackets++
|
|
||||||
if (c == '}') brackets--
|
|
||||||
}
|
|
||||||
1 -> {
|
|
||||||
if (c == '"') state = 0
|
|
||||||
}
|
|
||||||
2 -> {
|
|
||||||
if (c == '"' && i + 2 < value.length && value[i+1] == '"' && value[i+2] == '"') state = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (brackets != 0) throw IllegalArgumentException("Unclosed brackets in method body")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
package io.gitlab.jfronny.scripts.codegen
|
|
||||||
|
|
||||||
import org.gradle.api.Action
|
|
||||||
import java.util.function.Supplier
|
|
||||||
|
|
||||||
class ContentGenerator : Generator<ContentGenerator.Generated>() {
|
|
||||||
private val classes: MutableMap<String, String> = LinkedHashMap()
|
|
||||||
private val resources: MutableMap<String, ByteArray> = LinkedHashMap()
|
|
||||||
|
|
||||||
override fun generateFinalized() = Generated(ImmutableMap(classes), ImmutableMap(resources))
|
|
||||||
data class Generated(val classes: Map<String, String>, val resources: Map<String, ByteArray>) {
|
|
||||||
fun merge(other: Generated): Generated = Generated(classes + other.classes, resources + other.resources)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun `class`(`package`: String, name: String, generator: Action<ClassGenerator>) = ensureMutable {
|
|
||||||
if (!packagePattern.matches(`package`)) throw IllegalArgumentException("package \"$`package`\" is not a valid package")
|
|
||||||
if (!classNamePattern.matches(name)) throw IllegalArgumentException("class name \"$name\" is not a valid class name")
|
|
||||||
val builder = ClassGenerator(`package`, name)
|
|
||||||
generator.execute(builder)
|
|
||||||
classes["${`package`.replace('.', '/')}/$name.java"] = builder.finalize()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun text(path: String, generator: Action<StringBuilder>) = ensureMutable {
|
|
||||||
if (!pathPattern.matches(path)) throw IllegalArgumentException("path \"$path\" is not a valid path")
|
|
||||||
val builder = StringBuilder()
|
|
||||||
generator.execute(builder)
|
|
||||||
resources[path] = builder.toString().toByteArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun blob(path: String, generator: Supplier<ByteArray>) = ensureMutable {
|
|
||||||
if (!pathPattern.matches(path)) throw IllegalArgumentException("path \"$path\" is not a valid path")
|
|
||||||
resources[path] = generator.get()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package io.gitlab.jfronny.scripts.codegen
|
|
||||||
|
|
||||||
abstract class Generator<T> {
|
|
||||||
private var finalized = false
|
|
||||||
protected abstract fun generateFinalized(): T
|
|
||||||
|
|
||||||
fun finalize(): T {
|
|
||||||
val fin = generateFinalized()
|
|
||||||
finalized = true
|
|
||||||
return fin
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun <T> ensureMutable(action: () -> T): T {
|
|
||||||
if (finalized) throw IllegalAccessException("Attempted to access CodeGenerator after it was finalized")
|
|
||||||
return action()
|
|
||||||
}
|
|
||||||
|
|
||||||
protected data class ImmutableMap<Key, Value>(private val inner: Map<Key, Value>): Map<Key, Value> by inner
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmStatic protected val primitivePattern = Regex("(?:byte|short|int|long|float|double|boolean|char)")
|
|
||||||
@JvmStatic protected val singleModifierPattern = Regex("(?:public|private|protected|static|final|abstract|transient|synchronized|volatile)")
|
|
||||||
@JvmStatic protected val modifierPattern = Regex("(?:$singleModifierPattern(?: $singleModifierPattern)*)")
|
|
||||||
|
|
||||||
@JvmStatic protected val packagePattern = Regex("(?:[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*)")
|
|
||||||
@JvmStatic protected val classNamePattern = Regex("(?:[A-Z][A-Za-z0-9_\$]*)")
|
|
||||||
@JvmStatic protected val pathPattern = Regex("(?:([a-z]+/)*[a-zA-Z][a-zA-Z0-9_.]*)")
|
|
||||||
@JvmStatic protected val classPattern = Regex("(?:(?:(?:$packagePattern\\.)?$classNamePattern)|$primitivePattern)")
|
|
||||||
@JvmStatic protected val importPattern = Regex("(?:$packagePattern\\.(?:\\*|$classNamePattern))")
|
|
||||||
@JvmStatic protected val classEntryPattern = Regex("(?:[a-zA-Z][a-zA-Z_\$0-9]*)")
|
|
||||||
@JvmStatic protected val classListPattern = Regex("(?:$classPattern(?:, $classPattern)*)")
|
|
||||||
@JvmStatic protected fun check(name: String, type: String, pattern: Regex): Unit {
|
|
||||||
if (!pattern.matches(name)) throw IllegalArgumentException("$type \"$name\" is not a valid $type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +1,14 @@
|
||||||
import gradle.kotlin.dsl.accessors._72efc76fad8c8cf3476d335fb6323bde.*
|
|
||||||
import io.gitlab.jfronny.scripts.codegen.ContentGenerator.Generated
|
|
||||||
import io.gitlab.jfronny.scripts.*
|
import io.gitlab.jfronny.scripts.*
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
java
|
java
|
||||||
}
|
}
|
||||||
|
|
||||||
extra["codeGenerators"] = LinkedHashMap<String, Generated>()
|
extra["codeGenerators"] = LinkedHashMap<String, ContentGenerator.Generated>()
|
||||||
|
|
||||||
val jfCodegen by tasks.registering {
|
val jfCodegen by tasks.registering {
|
||||||
doLast {
|
doLast {
|
||||||
val generators = project.extra["codeGenerators"] as LinkedHashMap<String, Generated>
|
val generators = project.extra["codeGenerators"] as LinkedHashMap<String, ContentGenerator.Generated>
|
||||||
if (codegenDir.exists()) codegenDir.deleteRecursively()
|
if (codegenDir.exists()) codegenDir.deleteRecursively()
|
||||||
generators.forEach { (name, generated) ->
|
generators.forEach { (name, generated) ->
|
||||||
generated.classes.forEach { (filePath, content) ->
|
generated.classes.forEach { (filePath, content) ->
|
||||||
|
|
Loading…
Reference in New Issue