BoxCodeFormat.kt
src/jvmMain/kotlin/afield/examples/BoxCodeFormat.kt · 258 lines
package afield.examples
import afield.runtime.kotlin.formatBoxKotlin
import afield.runtime.kotlin.formatCodeProperty
import org.yaml.snakeyaml.DumperOptions
import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.nodes.MappingNode
import org.yaml.snakeyaml.nodes.Node
import org.yaml.snakeyaml.nodes.ScalarNode
import org.yaml.snakeyaml.nodes.SequenceNode
import org.yaml.snakeyaml.nodes.Tag
import java.io.File
import java.io.StringReader
import kotlin.system.exitProcess
object BoxCodeFormat
Formats the box code in transcripts and *_code_.kt sidecars with the formatter the editor uses
(formatCodeProperty: ktfmt for code, Field's GLSL formatter for shader properties), in place.
A transcript holds code in YAML block scalars (code: |, fragment: |, ...). Each block is
de-indented, formatted, and re-indented to the column it had, so nothing else in the file moves.
A block the formatter cannot parse is left exactly as it was and named on stdout: a format pass
that guessed would be worse than one that skipped.
./gradlew formatBoxCode --args="<file>...", or with no arguments every top-level transcript.
--check reports what would change and exits 1 if anything would. --imports also removes each
code block's redundant and unused imports (formatBoxKotlin); --k2 adds the code-use sidecar's
resolution evidence to that. Follow a real run with ./gradlew checkBoxCodeCompiles.
A .md argument formats the Kotlin in its diffstatic / diffstatic_runner fences (formatMarkdown);
--docs adds every .md page directly under public/docs. Follow a docs run with a docs build.
--all takes every corpus at once (allCorpora); --guard --check is ./gradlew checkBoxCodeFormat,
which landing runs on the files a branch touches.
object BoxCodeFormat {
private var imports = false
private var k2 = false
private val BLOCK_KEY = Regex("""^(\s*)(?:-\s+)?(["']?)(\w+)\2:\s*\|[-+]?\s*(?:#.*)?$""")
fun main(args: Array<String>)
body — 41 lines, from line 41
@JvmStatic
fun main(args: Array<String>) {
val check = "--check" in args
imports = "--imports" in args
k2 = "--k2" in args
val docs = if ("--docs" in args) docsPages() else emptyList()
val explicit = args.filter { !it.startsWith("--") }.map(::File)
val files = when {
"--all" in args || "--guard" in args -> guardScope(allCorpora(), "--guard" in args)
explicit.isNotEmpty() -> explicit + docs
docs.isNotEmpty() -> docs
else -> transcripts()
}
var changed = 0
for (file in files) {
val before = file.readText()
val after = when {
file.name.endsWith("_code_.kt") -> formatOrNull("code", before, file.path) ?: before
file.extension == "yaml" || file.extension == "yml" -> formatYaml(before, file.path)
file.extension == "md" -> formatMarkdown(before, file.path)
else -> before
}
if (after != before) {
changed++
println("${if (check) "would format" else "formatted"} ${file.path}")
if (!check) file.writeText(after)
}
}
println("BoxCodeFormat: ${files.size} file(s), $changed ${if (check) "need formatting" else "formatted"}")
exitProcess(if (check && changed > 0) 1 else 0)
}
private fun transcripts(): List<File> {
val dir = DemoTranscriptExecSmokeTest.locateTranscriptsDir()
return (dir.listFiles { f -> f.isFile && f.extension == "yaml" } ?: emptyArray()).sortedBy { it.name }
}
private fun docsPages(): List<File> =
(File("public/docs").listFiles { f -> f.isFile && f.extension == "md" } ?: emptyArray())
.filter { f -> RECORDED.none { "public/docs/${f.name}".startsWith(it) } }
.sortedBy { it.name }
Copies regenerated from the transcripts (regenerateAgentTranscriptStartingPoints); formatting them by hand would only be overwritten, and a guard over them would blame the wrong file.
private const val GENERATED_ROOT = "public/docs/assets/agent_transcripts/"
Recorded artifacts whose bytes are pinned to a saved revision (package_function_conversation.py asserts the final source equals studies/function_source/r9-*.kt), so a format would break the record. The page's listings are those same bytes, written by that packager.
private val RECORDED = listOf(
"dev_docs/agents/demos/function_conversation_example/",
"public/docs/function_conversation.md",
"public/docs/rectsl_conversation.md",
"public/docs/stacksl_conversation.md",
"public/docs/stackauto_conversation.md",
)
fun allCorpora(): List<File>
Every corpus the formatter owns: transcripts, demo build scripts, tracked *_code_.kt sidecars outside GENERATED_ROOT, docs pages.
fun allCorpora(): List<File> {
val tracked = ProcessBuilder("git", "ls-files", "*_code_.kt").redirectErrorStream(true).start()
.inputStream.bufferedReader().readLines()
.filter { it.isNotBlank() && !it.startsWith(GENERATED_ROOT) && RECORDED.none(it::startsWith) }
.map(::File)
// A demo's build.yaml / verify.yaml holds the same box code as its saved document's sidecars
// (test_demo_build_scripts.py compares them), so both are formatted together.
val demoScripts = (File("dev_docs/agents/demos").listFiles() ?: emptyArray()).sortedBy { it.name }
.flatMap { d -> listOf("build.yaml", "verify.yaml").map { File(d, it) }.filter(File::isFile) }
.filter { f -> RECORDED.none { "dev_docs/agents/demos/${f.parentFile.name}/".startsWith(it) } }
return transcripts() + demoScripts + tracked + docsPages()
}
--guard limits the corpus to the paths the lander says a branch touched, the same
AFIELD_GUARD_CHANGED_PATHS_FILE listing checkBoxIdioms reads. Without it the whole corpus is checked.
private fun guardScope(corpus: List<File>, guard: Boolean): List<File> {
val listing = System.getenv("AFIELD_GUARD_CHANGED_PATHS_FILE")?.takeIf { guard && it.isNotBlank() } ?: return corpus
val changed = File(listing).readLines().map(String::trim).filter(String::isNotEmpty).toSet()
val root = File("").absoluteFile
return corpus.filter { it.absoluteFile.relativeTo(root).invariantSeparatorsPath in changed }
}
fun formatYaml(text: String, label: String): String
fun formatYaml(text: String, label: String): String {
val lines = expandQuotedCode(text, label).split("\n")
val out = ArrayList<String>(lines.size)
var i = 0
while (i < lines.size) {
val key = BLOCK_KEY.matchEntire(lines[i])
out += lines[i]
i++
if (key == null) continue
val keyIndent = key.groupValues[1].length
val start = i
while (i < lines.size && (lines[i].isBlank() || indentOf(lines[i]) > keyIndent)) i++
// Trailing blank lines belong to the YAML between entries, not to the block.
var end = i
while (end > start && lines[end - 1].isBlank()) end--
val block = lines.subList(start, end)
if (block.isEmpty()) { out += lines.subList(end, i); continue }
val indent = block.filter(String::isNotBlank).minOf(::indentOf)
val body = block.joinToString("\n") { if (it.isBlank()) "" else it.substring(indent) }
val formatted = formatOrNull(key.groupValues[3], body, "$label:${start + 1}")
if (formatted == null) {
out += block
} else {
val pad = " ".repeat(indent)
formatted.trimEnd().split("\n").forEach { out += if (it.isBlank()) "" else pad + it }
}
out += lines.subList(end, i)
}
return out.joinToString("\n")
}
Older fixtures encode complete programs as escaped YAML strings. Decode those through YAML, then use the same literal-block path as authored transcripts. Metadata stays in place.
source — 59 lines, from line 154
private fun expandQuotedCode(text: String, label: String): String {
val documents = try {
Yaml().composeAll(StringReader(text)).toList()
} catch (_: RuntimeException) {
return text // Keep the existing handling of malformed YAML; never guess at its values.
}
data class Edit(val start: Int, val end: Int, val replacement: String)
val edits = mutableListOf<Edit>()
val visited = mutableSetOf<Node>()
fun visit(node: Node, inFlow: Boolean) {
if (!visited.add(node)) return
when (node) {
is MappingNode -> {
val flow = inFlow || node.flowStyle == DumperOptions.FlowStyle.FLOW
for (entry in node.value) {
val key = entry.keyNode as? ScalarNode
val value = entry.valueNode as? ScalarNode
if (key != null && value != null && value.tag == Tag.STR &&
(key.value == "code" || formatCodeProperty(key.value, "") != null) &&
value.scalarStyle !in setOf(DumperOptions.ScalarStyle.LITERAL, DumperOptions.ScalarStyle.FOLDED)
) {
if (flow || value.anchor != null || value.startMark.index < key.endMark.index) {
println("skipped $label:${key.startMark.line + 1} (${key.value}): quoted code in a flow collection or alias")
} else {
val formatted = formatOrNull(key.value, value.value, "$label:${key.startMark.line + 1}")
if (formatted != null) {
// SnakeYAML marks count Unicode code points; JVM string offsets
// count UTF-16 units. Emoji before or inside code change the offset.
val start = text.offsetByCodePoints(0, value.startMark.index)
val end = text.offsetByCodePoints(0, value.endMark.index)
val lineEnd = text.indexOf('\n', end).let { if (it < 0) text.length else it }
val suffix = text.substring(end, lineEnd)
// Keep an inline YAML comment on the new block's header.
if (suffix.isBlank() || suffix.trimStart().startsWith("#")) {
val comment = if (suffix.isBlank()) "" else suffix.trimEnd()
val indent = " ".repeat(key.startMark.column + 2)
val body = formatted.trimEnd('\n', '\r').lineSequence()
.joinToString("\n") { if (it.isBlank()) "" else indent + it }
edits += Edit(start, lineEnd, "|-" + comment + "\n" + body)
}
}
}
}
visit(entry.valueNode, flow)
}
}
is SequenceNode -> node.value.forEach { visit(it, inFlow || node.flowStyle == DumperOptions.FlowStyle.FLOW) }
}
}
documents.filterNotNull().forEach { visit(it, false) }
var result = text
for (edit in edits.sortedByDescending { it.start }) {
result = result.replaceRange(edit.start, edit.end, edit.replacement)
}
return result
}
private val FENCE_OPEN = Regex("""^(`{3,})(diffstatic_runner|diffstatic)(?![\w-])(.*)$""")
private val FENCE_LANG = Regex("""\b(?:data-lang|data-language|lang|language)\s*=\s*["']?([\w+-]+)""")
What the docs runner reads out of a fence body, or what a reformat would move away from the
line a page's prose points at: section and fold markers, fragment breaks, region comments and
the runner's own FIELD_DOCS_ directives. A fence holding any of them is left as written.
private val RUNNER_MARKER = Regex("""^\s*(:::|//\s*%-|//\s*(?:end)?region\b|//\s*#(?:end)?region\b)|FIELD_DOCS_""")
fun formatMarkdown(text: String, label: String): String
Format the Kotlin in a docs page's diffstatic / diffstatic_runner fences, in place.
Only unindented fences whose language is Kotlin (no data-lang, or kotlin/kt/kts) are
touched; diff fences hold before/after sections and are never formatted. A fence that does not
parse, or that carries a RUNNER_MARKER, is left as written. The fence lines stay as they are.
fun formatMarkdown(text: String, label: String): String {
val lines = text.split("\n")
val out = ArrayList<String>(lines.size)
var i = 0
while (i < lines.size) {
val open = FENCE_OPEN.matchEntire(lines[i])
out += lines[i]
i++
if (open == null) continue
val ticks = open.groupValues[1]
val start = i
while (i < lines.size && lines[i].trimEnd() != ticks) i++
if (i >= lines.size) { out += lines.subList(start, i); break }
val body = lines.subList(start, i)
val lang = FENCE_LANG.find(open.groupValues[3])?.groupValues?.get(1)?.lowercase()
val kotlin = lang == null || lang in setOf("kotlin", "kt", "kts")
val formatted = if (!kotlin || body.isEmpty() || body.any { RUNNER_MARKER.containsMatchIn(it) }) null
else formatOrNull("code", body.joinToString("\n"), "$label:$start")
if (formatted == null) out += body else out += formatted.trimEnd().split("\n")
}
return out.joinToString("\n")
}
private fun formatOrNull(property: String, code: String, label: String): String? = try {
if (imports && property == "code") formatBoxKotlin(code, useK2 = k2) else formatCodeProperty(property, code)
} catch (t: Throwable) {
println("skipped $label ($property): ${t.javaClass.simpleName}: ${t.message?.lineSequence()?.firstOrNull()}")
null
}
private fun indentOf(line: String) = line.length - line.trimStart().length
}