diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 4622b76c8..157c1b916 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -6,9 +6,13 @@ plugins { repositories { mavenCentral() gradlePluginPortal() + maven { url = uri("https://repo.codemc.org/repository/maven-public") } } dependencies { implementation("gradle.plugin.com.github.jengelman.gradle.plugins:shadow:+") + implementation("org.ow2.asm:asm:9.2") + implementation("org.ow2.asm:asm-tree:9.2") + implementation("com.dfsek.tectonic:common:3.1.0") implementation("org.yaml:snakeyaml:1.27") } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/com/dfsek/terra/tectonicdoc/DocumentedTemplate.kt b/buildSrc/src/main/kotlin/com/dfsek/terra/tectonicdoc/DocumentedTemplate.kt new file mode 100644 index 000000000..c7eac1f83 --- /dev/null +++ b/buildSrc/src/main/kotlin/com/dfsek/terra/tectonicdoc/DocumentedTemplate.kt @@ -0,0 +1,20 @@ +package com.dfsek.terra.tectonicdoc + +class DocumentedTemplate(private val name: String) { + private val template = HashMap() + + fun add(name: String, content: String) { + template[name] = content + } + + fun format(): String { + val builder = StringBuilder("# ").append(name).append("\n\n") + template.forEach { name, content -> + builder + .append("### $name\n\n") + .append(content) + .append("\n\n") + } + return builder.toString() + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/com/dfsek/terra/tectonicdoc/GenerateDocsTask.kt b/buildSrc/src/main/kotlin/com/dfsek/terra/tectonicdoc/GenerateDocsTask.kt new file mode 100644 index 000000000..f822bb306 --- /dev/null +++ b/buildSrc/src/main/kotlin/com/dfsek/terra/tectonicdoc/GenerateDocsTask.kt @@ -0,0 +1,123 @@ +package com.dfsek.terra.tectonicdoc + +import com.dfsek.tectonic.api.config.template.annotations.Description +import com.dfsek.tectonic.api.config.template.annotations.Final +import com.dfsek.tectonic.api.config.template.annotations.Value +import java.io.File +import java.io.FileInputStream +import java.io.InputStream +import java.lang.IllegalArgumentException +import org.gradle.api.DefaultTask +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.tasks.TaskAction +import org.objectweb.asm.ClassReader +import org.objectweb.asm.tree.ClassNode + + +abstract class GenerateDocsTask : DefaultTask() { + @TaskAction + fun generateDocs() { + project.extensions.getByType(JavaPluginExtension::class.java).sourceSets.forEach { sources -> + val classes = HashMap() + sources.java.classesDirectory.get().asFileTree.forEach { + if (it.name.endsWith(".class")) { + val node = createClassNode(FileInputStream(it)) + if (node.fields.stream().anyMatch { + it.visibleAnnotations?.stream()?.anyMatch { + it.desc.equals(descriptor(Value::class.java.canonicalName)) + } == true + }) { + classes[sources + .java + .classesDirectory + .get() + .asFile + .toPath() + .relativize(it.toPath()) + .toString() + .substringBeforeLast('.')] = node + } + } + } + + val docsDir = File(project.buildDir, "tectonic") + docsDir.mkdirs() + + classes.forEach { name, clazz -> + val template = DocumentedTemplate(name.substringAfterLast('/')) + clazz.fields + .stream() + .filter { + it.visibleAnnotations?.stream()?.anyMatch { + it.desc.equals(descriptor(Value::class.java.canonicalName)) + } == true + }.forEach { + val annotations = it.visibleAnnotations + + val description = StringBuilder() + + annotations.stream().filter { + it.desc.equals(descriptor(Description::class.java.canonicalName)) + }.forEach { + description.append(it.values[1]) + } + + val name = StringBuilder() + + if (annotations.stream().anyMatch { it.desc.equals(descriptor(Final::class.java.canonicalName)) }) { + name.append("final ") + } + + name.append(descriptorToHumanReadable(it.desc)) + .append(" ") + .append(it.name) + + template.add(name.toString(), description.toString().ifBlank { + println("No description provided for field " + it.name) + "*No description provided.*" + }) + } + + + val save = File(docsDir, "$name.md") + if (save.exists()) save.delete() + save.parentFile.mkdirs() + save.createNewFile() + save.writeText(template.format()) + } + } + } + + private fun createClassNode(input: InputStream): ClassNode { + val reader = ClassReader(input) + val node = ClassNode() + try { + reader.accept(node, ClassReader.EXPAND_FRAMES) + } catch (e: Exception) { + reader.accept(node, ClassReader.SKIP_FRAMES or ClassReader.SKIP_DEBUG) + } + return node + } + + private fun descriptorToHumanReadable(descriptor: String): String { + if(descriptor.startsWith('L')) { + return descriptor.substring(1).substringBeforeLast(';').replace('/', '.') + } + if(descriptor.startsWith("[")) { + return "${descriptorToHumanReadable(descriptor.substring(1))}[]" + } + return when(descriptor) { + "B" -> "byte" + "C" -> "char" + "I" -> "int" + "D" -> "double" + "F" -> "float" + "J" -> "long" + "S" -> "short" + "Z" -> "boolean" + else -> throw IllegalArgumentException("Invalid descriptor: $descriptor") + } + } + + private fun descriptor(name: String): String = "L${name.replace('.', '/')};" +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/com/dfsek/terra/tectonicdoc/TectonicDocPlugin.kt b/buildSrc/src/main/kotlin/com/dfsek/terra/tectonicdoc/TectonicDocPlugin.kt new file mode 100644 index 000000000..eb13e9bf7 --- /dev/null +++ b/buildSrc/src/main/kotlin/com/dfsek/terra/tectonicdoc/TectonicDocPlugin.kt @@ -0,0 +1,10 @@ +package com.dfsek.terra.tectonicdoc + +import org.gradle.api.Plugin +import org.gradle.api.Project + +class TectonicDocPlugin: Plugin { + override fun apply(project: Project) { + project.tasks.create("generateDocs", GenerateDocsTask::class.java) + } +} \ No newline at end of file