fix(config): SimpleToml multi-line string arrays
The line-based parser treated 'conventions = [' as the complete value,
binding conventions to listOf("[") and dropping every entry on the
following lines. Accumulate value lines until the top-level bracket
closes (quote-aware), skipping comments inside the array.
This commit is contained in:
@@ -17,8 +17,9 @@ internal object SimpleToml {
|
|||||||
val rootKeys = mutableMapOf<String, Any>()
|
val rootKeys = mutableMapOf<String, Any>()
|
||||||
val sections = mutableMapOf<String, MutableMap<String, Any>>()
|
val sections = mutableMapOf<String, MutableMap<String, Any>>()
|
||||||
|
|
||||||
for (line in lines) {
|
var i = 0
|
||||||
val trimmed = line.trim()
|
while (i < lines.size) {
|
||||||
|
val trimmed = lines[i].trim()
|
||||||
when {
|
when {
|
||||||
trimmed.isEmpty() || trimmed.startsWith("#") -> Unit
|
trimmed.isEmpty() || trimmed.startsWith("#") -> Unit
|
||||||
trimmed.startsWith("[") && !trimmed.startsWith("[[") && trimmed.endsWith("]") -> {
|
trimmed.startsWith("[") && !trimmed.startsWith("[[") && trimmed.endsWith("]") -> {
|
||||||
@@ -29,7 +30,14 @@ internal object SimpleToml {
|
|||||||
val eqIndex = trimmed.indexOf("=")
|
val eqIndex = trimmed.indexOf("=")
|
||||||
if (eqIndex > 0) {
|
if (eqIndex > 0) {
|
||||||
val key = trimmed.substring(0, eqIndex).trim()
|
val key = trimmed.substring(0, eqIndex).trim()
|
||||||
val valueStr = trimmed.substring(eqIndex + 1).trim()
|
var valueStr = trimmed.substring(eqIndex + 1).trim()
|
||||||
|
// Multi-line array: accumulate lines until the top-level bracket closes.
|
||||||
|
while (valueStr.startsWith("[") && !arrayClosed(valueStr) && i + 1 < lines.size) {
|
||||||
|
i++
|
||||||
|
val next = lines[i].trim()
|
||||||
|
if (next.isEmpty() || next.startsWith("#")) continue
|
||||||
|
valueStr += " $next"
|
||||||
|
}
|
||||||
val parsed = parseSimpleValue(valueStr)
|
val parsed = parseSimpleValue(valueStr)
|
||||||
if (currentSection.isEmpty()) {
|
if (currentSection.isEmpty()) {
|
||||||
rootKeys[key] = parsed
|
rootKeys[key] = parsed
|
||||||
@@ -39,10 +47,27 @@ internal object SimpleToml {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
i++
|
||||||
}
|
}
|
||||||
return Pair(rootKeys, sections)
|
return Pair(rootKeys, sections)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** True when every `[` opened outside quotes has been balanced by a `]`. */
|
||||||
|
private fun arrayClosed(value: String): Boolean {
|
||||||
|
var depth = 0
|
||||||
|
var inQuotes = false
|
||||||
|
var quoteChar = ' '
|
||||||
|
for (ch in value) {
|
||||||
|
when {
|
||||||
|
(ch == '"' || ch == '\'') && !inQuotes -> { inQuotes = true; quoteChar = ch }
|
||||||
|
ch == quoteChar && inQuotes -> inQuotes = false
|
||||||
|
ch == '[' && !inQuotes -> depth++
|
||||||
|
ch == ']' && !inQuotes -> depth--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return depth <= 0
|
||||||
|
}
|
||||||
|
|
||||||
fun asString(value: Any?, default: String = ""): String =
|
fun asString(value: Any?, default: String = ""): String =
|
||||||
if (value is String) value else default
|
if (value is String) value else default
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,30 @@ class ProjectProfileLoaderTest {
|
|||||||
assertEquals(mapOf("build" to "./gradlew build", "test" to "./gradlew check"), p.commands)
|
assertEquals(mapOf("build" to "./gradlew build", "test" to "./gradlew check"), p.commands)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `multi-line conventions array parses all entries`() {
|
||||||
|
// Idiomatic TOML splits long string arrays across lines; the line-based parser used
|
||||||
|
// to swallow everything after the opening bracket (conventions became listOf("[")).
|
||||||
|
val root = tempRoot()
|
||||||
|
Files.writeString(
|
||||||
|
Paths.get(root, ".correx", "project.toml"),
|
||||||
|
"""
|
||||||
|
about = "kernel"
|
||||||
|
conventions = [
|
||||||
|
"no bare try-catch",
|
||||||
|
# comment inside the array
|
||||||
|
"events are the only source of truth",
|
||||||
|
]
|
||||||
|
|
||||||
|
[commands]
|
||||||
|
test = "./gradlew check"
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
val p = ProjectProfileLoader.load(root)
|
||||||
|
assertEquals(listOf("no bare try-catch", "events are the only source of truth"), p.conventions)
|
||||||
|
assertEquals(mapOf("test" to "./gradlew check"), p.commands)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `malformed file returns default without throwing`() {
|
fun `malformed file returns default without throwing`() {
|
||||||
val root = tempRoot()
|
val root = tempRoot()
|
||||||
|
|||||||
Reference in New Issue
Block a user