nl.littlerobots.vcu.VersionCatalogParser.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of catalog Show documentation
Show all versions of catalog Show documentation
A gradle plugin that updates the version catalog file
The newest version!
/*
* Copyright 2021 Hugo Visser
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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 = mapper.readValue(content, Map::class.java) as Map
val versions = catalog.getTable(TABLE_VERSIONS)?.toVersionDefinitionMap() ?: emptyMap()
val libraries = catalog.getTable(TABLE_LIBRARIES)?.toDependencyMap() ?: emptyMap()
val bundles = catalog.getTable(TABLE_BUNDLES)?.toTypedMap>() ?: emptyMap()
val plugins = catalog.getTable(TABLE_PLUGINS)?.toPluginDependencyMap() ?: emptyMap()
return processComments(content, VersionCatalog(versions, libraries, bundles, plugins))
}
private fun processComments(content: String, versionCatalog: VersionCatalog): VersionCatalog {
val comments = mutableMapOf()
val currentComment = mutableListOf()
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()
)
}
}
private fun Map.toPluginDependencyMap(): Map {
return toTypedMap().mapValues { entry ->
when (val value = entry.value) {
is String -> {
val components = value.split(":")
if (components.size != 2) {
throw IllegalStateException("Invalid plugin definition for ${entry.key}: ${entry.value}")
}
val (id, version) = components
Plugin(id, VersionDefinition.Simple(version))
}
is Map<*, *> -> {
val id = value["id"] as? String
val version = value["version"]?.toDependencyVersion() ?: VersionDefinition.Unspecified
if (id == null) {
throw IllegalStateException("No plugin id defined for ${entry.key}")
}
Plugin(id, version)
}
else -> throw IllegalStateException("Unsupported type ${value::class.java}")
}
}
}
private fun Map.toDependencyMap(): Map = toTypedMap().mapValues { entry ->
when (val value = entry.value) {
is String -> {
val components = value.split(":")
if (components.size != 3) {
throw IllegalStateException("Invalid dependency definition for ${entry.key}: ${entry.value}")
}
val (group, name, version) = components
Library(
group = group,
name = name,
version = VersionDefinition.Simple(version)
)
}
is Map<*, *> -> {
val module = value["module"] as? String
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}")
} ?: VersionDefinition.Unspecified
if (module == null && (group == null || name == null)) {
throw IllegalStateException("${entry.key} should define module or group/name")
}
if (module != null && (group != null || name != null)) {
throw IllegalStateException("${entry.key} should only define module or group/name combination")
}
if (module != null) {
Library(module, version)
} else {
Library(requireNotNull(group), requireNotNull(name), version)
}
}
else -> throw IllegalStateException("Unsupported type ${value::class.java}")
}
}
private fun Map.toVersionDefinitionMap(): Map {
return mapValues {
when (val version = it.value.toDependencyVersion()) {
null -> throw IllegalStateException("Unable to parse version ${it.value}")
is VersionDefinition.Reference -> throw IllegalStateException("Version specified cannot be a reference")
else -> version
}
}
}
@Suppress("UNCHECKED_CAST")
private fun Any.toDependencyVersion(): VersionDefinition? = when (this) {
is String -> VersionDefinition.Simple(this)
is Map<*, *> -> {
val stringMap = this as? Map ?: throw IllegalStateException("Expected string map")
if (size == 1 && stringMap.containsKey("ref")) {
VersionDefinition.Reference(stringMap["ref"] as String)
} else {
VersionDefinition.Condition(stringMap)
}
}
else -> null
}
@Suppress("UNCHECKED_CAST")
private fun Map.getTable(key: String) = get(key) as? Map
private inline fun Map.toTypedMap(): Map = map {
if (it.value !is T) {
throw IllegalStateException("Expected ${T::class.java} for key ${it.key}, but was ${it.value::class.java}")
}
it.key to it.value as T
}.toMap()
© 2015 - 2024 Weber Informatics LLC | Privacy Policy