diff --git a/core/config/src/main/kotlin/com/correx/core/config/ConfigLoader.kt b/core/config/src/main/kotlin/com/correx/core/config/ConfigLoader.kt index 01898d4e..9a81338d 100644 --- a/core/config/src/main/kotlin/com/correx/core/config/ConfigLoader.kt +++ b/core/config/src/main/kotlin/com/correx/core/config/ConfigLoader.kt @@ -17,8 +17,9 @@ internal object SimpleToml { val rootKeys = mutableMapOf() val sections = mutableMapOf>() - for (line in lines) { - val trimmed = line.trim() + var i = 0 + while (i < lines.size) { + val trimmed = lines[i].trim() when { trimmed.isEmpty() || trimmed.startsWith("#") -> Unit trimmed.startsWith("[") && !trimmed.startsWith("[[") && trimmed.endsWith("]") -> { @@ -29,7 +30,14 @@ internal object SimpleToml { val eqIndex = trimmed.indexOf("=") if (eqIndex > 0) { 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) if (currentSection.isEmpty()) { rootKeys[key] = parsed @@ -39,10 +47,27 @@ internal object SimpleToml { } } } + i++ } 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 = if (value is String) value else default diff --git a/core/config/src/test/kotlin/com/correx/core/config/ProjectProfileLoaderTest.kt b/core/config/src/test/kotlin/com/correx/core/config/ProjectProfileLoaderTest.kt index 77169c28..0f70c217 100644 --- a/core/config/src/test/kotlin/com/correx/core/config/ProjectProfileLoaderTest.kt +++ b/core/config/src/test/kotlin/com/correx/core/config/ProjectProfileLoaderTest.kt @@ -38,6 +38,30 @@ class ProjectProfileLoaderTest { 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 fun `malformed file returns default without throwing`() { val root = tempRoot()