Skip to content

Commit

Permalink
Keep toml file comments
Browse files Browse the repository at this point in the history
Do a crude pass for locating comments and write them out to the toml file.
Comments are associated with toml table declarations and keys within the tables, so they can move around and be removed if a key or a whole table is removed.

Fixes #18
  • Loading branch information
hvisser committed May 13, 2022
1 parent 0d9dc86 commit 0236fbd
Show file tree
Hide file tree
Showing 6 changed files with 275 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,70 @@
package nl.littlerobots.vcu

import com.fasterxml.jackson.dataformat.toml.TomlMapper
import nl.littlerobots.vcu.model.Comments
import nl.littlerobots.vcu.model.Library
import nl.littlerobots.vcu.model.Plugin
import nl.littlerobots.vcu.model.VersionCatalog
import nl.littlerobots.vcu.model.VersionDefinition
import java.io.BufferedReader
import java.io.InputStream
import java.io.StringReader

private val TABLE_REGEX = Regex("\\[\\s?(versions|libraries|bundles|plugins)\\s?].*")
private val KEY_REGEX = Regex("^(.*?)=.*")
private const val TABLE_VERSIONS = "versions"
private const val TABLE_LIBRARIES = "libraries"
private const val TABLE_BUNDLES = "bundles"
private const val TABLE_PLUGINS = "plugins"

class VersionCatalogParser {

@Suppress("UNCHECKED_CAST")
fun parse(inputStream: InputStream): VersionCatalog {
val content = inputStream.bufferedReader().use {
it.readText()
}
val mapper = TomlMapper()
val catalog = inputStream.use { mapper.readValue(it, Map::class.java) as Map<String, Any> }
val catalog = mapper.readValue(content, Map::class.java) as Map<String, Any>

val versions = catalog.getTable("versions")?.toVersionDefinitionMap() ?: emptyMap()
val libraries = catalog.getTable("libraries")?.toDependencyMap() ?: emptyMap()
val bundles = catalog.getTable("bundles")?.toTypedMap<List<String>>() ?: emptyMap()
val plugins = catalog.getTable("plugins")?.toPluginDependencyMap() ?: emptyMap()
val versions = catalog.getTable(TABLE_VERSIONS)?.toVersionDefinitionMap() ?: emptyMap()
val libraries = catalog.getTable(TABLE_LIBRARIES)?.toDependencyMap() ?: emptyMap()
val bundles = catalog.getTable(TABLE_BUNDLES)?.toTypedMap<List<String>>() ?: emptyMap()
val plugins = catalog.getTable(TABLE_PLUGINS)?.toPluginDependencyMap() ?: emptyMap()

return processComments(content, VersionCatalog(versions, libraries, bundles, plugins))
}

return VersionCatalog(versions, libraries, bundles, plugins)
private fun processComments(content: String, versionCatalog: VersionCatalog): VersionCatalog {
val comments = mutableMapOf<String, Comments>()
val currentComment = mutableListOf<String>()
var currentTable: String? = null
val reader = BufferedReader(StringReader(content))
do {
val line = reader.readLine() ?: break
when {
line.startsWith("#") -> currentComment.add(line)
line.trim().matches(TABLE_REGEX) -> {
val table = TABLE_REGEX.find(line)!!.groupValues[1]
currentTable = table
comments[table] = Comments(currentComment.toList(), emptyMap())
currentComment.clear()
}
line.matches(KEY_REGEX) && currentTable != null && currentComment.isNotEmpty() -> {
val key = KEY_REGEX.find(line)!!.groupValues[1].trim()
val currentComments = comments[currentTable] ?: error("Should have an entry")
comments[currentTable] =
currentComments.copy(entryComments = currentComments.entryComments + mapOf(key to currentComment.toList()))
currentComment.clear()
}
}
} while (true)
return versionCatalog.copy(
versionComments = comments[TABLE_VERSIONS] ?: Comments(),
libraryComments = comments[TABLE_LIBRARIES] ?: Comments(),
bundleComments = comments[TABLE_BUNDLES] ?: Comments(),
pluginComments = comments[TABLE_PLUGINS] ?: Comments()
)
}
}

Expand Down Expand Up @@ -82,7 +127,8 @@ private fun Map<String, Any>.toDependencyMap(): Map<String, Library> = toTypedMa
val group = value["group"] as? String
val name = value["name"] as? String
val version = value["version"]?.let {
it.toDependencyVersion() ?: throw IllegalStateException("Could not parse version or version.ref for ${entry.key}")
it.toDependencyVersion()
?: throw IllegalStateException("Could not parse version or version.ref for ${entry.key}")
} ?: VersionDefinition.Unspecified
if (module == null && (group == null || name == null)) {
throw IllegalStateException("${entry.key} should define module or group/name")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,44 @@ class VersionCatalogWriter {
fun write(versionCatalog: VersionCatalog, writer: Writer) {
val printWriter = PrintWriter(writer)
if (versionCatalog.versions.isNotEmpty()) {
for (line in versionCatalog.versionComments.tableComments) {
printWriter.println(line)
}
printWriter.println("[versions]")
for (version in versionCatalog.versions) {
for (comment in versionCatalog.versionComments.getCommentsForKey(version.key)) {
printWriter.println(comment)
}
printWriter.println("""${version.key} = ${formatVersion(version.value)}""")
}
}
if (versionCatalog.libraries.isNotEmpty()) {
if (versionCatalog.versions.isNotEmpty()) {
printWriter.println()
}
for (line in versionCatalog.libraryComments.tableComments) {
printWriter.println(line)
}
printWriter.println("[libraries]")
for (library in versionCatalog.libraries) {
for (comment in versionCatalog.libraryComments.getCommentsForKey(library.key)) {
printWriter.println(comment)
}
printWriter.println("""${library.key} = ${formatLibrary(library.value)}""")
}
}
if (versionCatalog.bundles.isNotEmpty()) {
if (versionCatalog.versions.isNotEmpty() || versionCatalog.libraries.isNotEmpty()) {
printWriter.println()
}
for (comment in versionCatalog.bundleComments.tableComments) {
printWriter.println(comment)
}
printWriter.println("[bundles]")
for (bundle in versionCatalog.bundles) {
for (comment in versionCatalog.bundleComments.getCommentsForKey(bundle.key)) {
printWriter.println(comment)
}
printWriter.println(
"${bundle.key} = [${
bundle.value.joinToString(
Expand All @@ -59,8 +77,14 @@ class VersionCatalogWriter {
if (versionCatalog.versions.isNotEmpty() || versionCatalog.bundles.isNotEmpty() || versionCatalog.libraries.isNotEmpty()) {
printWriter.println()
}
for (comment in versionCatalog.pluginComments.tableComments) {
printWriter.println(comment)
}
printWriter.println("[plugins]")
for (plugin in versionCatalog.plugins) {
for (comment in versionCatalog.pluginComments.getCommentsForKey(plugin.key)) {
printWriter.println(comment)
}
printWriter.println("${plugin.key} = ${formatPlugin(plugin.value)}")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ data class VersionCatalog(
val versions: Map<String, VersionDefinition>,
val libraries: Map<String, Library>,
val bundles: Map<String, List<String>>,
val plugins: Map<String, Plugin>
val plugins: Map<String, Plugin>,
val versionComments: Comments = Comments(emptyList(), emptyMap()),
val libraryComments: Comments = Comments(emptyList(), emptyMap()),
val bundleComments: Comments = Comments(emptyList(), emptyMap()),
val pluginComments: Comments = Comments(emptyList(), emptyMap()),
) {
/**
* The effective version definition of a [HasVersion], resolving [VersionDefinition.Reference]
Expand All @@ -40,6 +44,13 @@ data class VersionCatalog(
}
}

data class Comments(
val tableComments: List<String> = emptyList(),
val entryComments: Map<String, List<String>> = emptyMap()
) {
fun getCommentsForKey(key: String) = entryComments[key] ?: emptyList()
}

/**
* Create an updated [VersionCatalog] by combining with [catalog]
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,4 +265,28 @@ class VersionCatalogParserTest {
result.libraries["lib"]!!
)
}

@Test
fun `records comments on tables and entries`() {
val toml = """
# Nice comment
[libraries] # valid, but not retained
# Comment for lib
# Even more comments
lib = { module = "nl.littlerobots.test:test" }
[bundles]
# test bundle comment
test = ["lib"]
# this is a trailing comment
""".trimIndent()

val parser = VersionCatalogParser()
val result = parser.parse(toml.byteInputStream())

assertEquals(listOf("# Nice comment"), result.libraryComments.tableComments)
assertEquals(listOf("# Comment for lib", "# Even more comments"), result.libraryComments.entryComments["lib"])
assertEquals(listOf("# test bundle comment"), result.bundleComments.entryComments["test"])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -231,4 +231,57 @@ class VersionCatalogWriterTest {
writer.toString()
)
}

@Test
fun `writes out comments`() {
val catalogWriter = VersionCatalogWriter()
val writer = StringWriter()

val toml = """
[versions]
# version comment
myversion = "1.0.0"
#something else
test = "2.0"
# Nice comment
[libraries] # valid, but not retained
# Comment for lib
# Even more comments
lib = { module = "nl.littlerobots.test:test", version = "1.0" }
# Table comment for bundles
[bundles]
# test bundle comment
test = ["lib"]
# Table comment for plugins
[plugins]
""".trimIndent()

val catalog = VersionCatalogParser().parse(toml.byteInputStream())
catalogWriter.write(catalog, writer)

assertEquals(
"""
[versions]
# version comment
myversion = "1.0.0"
#something else
test = "2.0"
# Nice comment
[libraries]
# Comment for lib
# Even more comments
lib = "nl.littlerobots.test:test:1.0"
# Table comment for bundles
[bundles]
# test bundle comment
test = ["lib"]
""".trimIndent(),
writer.toString()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -794,4 +794,113 @@ class VersionCatalogUpdatePluginTest {
libs
)
}

@Test
fun `table and key comments are retained`() {
val reportJson = tempDir.newFile()

buildFile.writeText(
"""
plugins {
id "nl.littlerobots.version-catalog-update"
}
tasks.named("versionCatalogUpdate").configure {
it.reportJson = file("${reportJson.name}")
}
// keep everything so that we can run an empty report
versionCatalogUpdate {
keep {
keepUnusedLibraries = true
keepUnusedVersions = true
keepUnusedPlugins = true
}
}
""".trimIndent()
)

val toml = """
# Versions comment
[versions]
# Version key comment
bbb = "1.2.3"
aaa = "4.5.6"
# Libraries
# multiline
# comment
[libraries]
bbb = "example:library:1.0"
#comment for key aaa
aaa = "some:library:2.0"
#Bundles comment
[bundles]
bbb = ["bbb"]
#For key aaa
aaa = ["aaa"]
# plugins table comment
[plugins]
# plugin bbb
bbb = "some.id:1.2.3"
# plugin aaa
#
aaa = "another.id:1.0.0"
""".trimIndent()

reportJson.writeText(
"""
{
}
""".trimIndent()
)
File(tempDir.root, "gradle").mkdir()
File(tempDir.root, "gradle/libs.versions.toml").writeText(toml)

GradleRunner.create()
.withProjectDir(tempDir.root)
.withArguments("versionCatalogUpdate")
.withDebug(true)
.withPluginClasspath()
.build()

val libs = File(tempDir.root, "gradle/libs.versions.toml").readText()

assertEquals(
"""
# Versions comment
[versions]
aaa = "4.5.6"
# Version key comment
bbb = "1.2.3"
# Libraries
# multiline
# comment
[libraries]
#comment for key aaa
aaa = "some:library:2.0"
bbb = "example:library:1.0"
#Bundles comment
[bundles]
#For key aaa
aaa = ["aaa"]
bbb = ["bbb"]
# plugins table comment
[plugins]
# plugin aaa
#
aaa = "another.id:1.0.0"
# plugin bbb
bbb = "some.id:1.2.3"
""".trimIndent(),
libs
)
}
}

0 comments on commit 0236fbd

Please sign in to comment.