import groovy.util.Node import groovy.util.XmlParser import org.gradle.api.GradleException import java.io.File /** * Given an xml of the format * * * * * * * * * * * Outputs a changelog in markdown format */ object ChangelogGenerator { class ChangelogException(message: String) : GradleException(message) private fun fail(message: String): Nothing = throw ChangelogException(message) class ChangelogEntry(val version: String, val items: Array) private fun Node.forEachNode(action: (Node) -> Unit) { children().forEach { action(it as Node) } } @JvmStatic fun read(inputUri: String): List { val input = File(inputUri) if (!input.exists()) { fail("Could not generate changelog from ${input.absolutePath}") } val parser = XmlParser().parse(inputUri) val entries: MutableList = mutableListOf() var version: String? = null val items: MutableList = mutableListOf() fun addEntry() { version?.also { v -> entries.add(ChangelogEntry(v, items.toTypedArray())) items.clear() } } parser.depthFirst().mapNotNull { it as? Node }.forEach { n -> when (n.name()) { "version" -> { addEntry() version = n.attribute("title")?.toString() ?: "" } "item" -> { n.attribute("text")?.toString()?.takeIf(String::isNotBlank)?.let { items.add(it) } } } } addEntry() return entries } @JvmStatic fun generate(inputUri: String, outputUri: String): List { val entries = read(inputUri) val output = File(outputUri) if (output.exists()) { if (output.isDirectory) { fail("Cannot save changelog at directory ${output.absolutePath}") } if (output.isFile && !output.delete()) { fail("Could not delete changelog at ${output.absolutePath}") } } else { output.parentFile.mkdirs() } if (!output.createNewFile()) { fail("Could not create changelog file at ${output.absolutePath}") } val markdown = buildString { append("# Changelog\n") entries.forEach { e -> append("\n## ${e.version}\n") e.items.forEach { append("* $it\n") } } } output.writeText(markdown) println("Generated changelog at ${output.absolutePath}") return entries } }