Build the frontmatter engine with byte-perfect round-trip

Yams validates and reads; writes are surgical line-span edits over
retained raw text, so untouched bytes — unknown keys, comments, odd
formatting, bodies — round-trip identically by construction. Strict
schema/order readers distinguish valid/missing/malformed for the
loader's fail-fast; lenient fields preserve malformed values verbatim;
modified-by is schema-owned. Adds the Yams package and the fixture
folder-reference wiring. 44 tests (105 cases).

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
This commit is contained in:
2026-07-26 15:26:12 -04:00
parent 954c4b351d
commit cb6f6100a7
8 changed files with 1162 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
import Foundation
import Yams
/// A value being written into frontmatter. The engine emits these as minimal YAML scalars
/// it never round-trips a whole document through a YAML emitter.
public enum FrontmatterValue: Sendable, Equatable {
case string(String)
case int(Int)
case double(Double)
case bool(Bool)
case date(Date)
/// Verbatim YAML supplied by the caller (flow collections, block scalars). Not validated.
case raw(String)
/// The scalar text placed after `key: `.
public var yamlText: String {
switch self {
case let .string(value): Self.emitScalar(value)
case let .int(value): String(value)
case let .double(value): Self.emitDouble(value)
case let .bool(value): value ? "true" : "false"
case let .date(value): value.formatted(.iso8601)
case let .raw(value): value
}
}
var parsedValue: YAMLValue {
switch self {
case let .string(value): .string(value)
case let .int(value): .int(value)
case let .double(value): .double(value)
case let .bool(value): .bool(value)
case let .date(value): .date(value)
case let .raw(value):
if let node = try? Yams.compose(yaml: value) { YAMLValue(node) } else { .string(value) }
}
}
}
extension FrontmatterValue {
private static func emitDouble(_ value: Double) -> String {
if value.isNaN { return ".nan" }
if value.isInfinite { return value < 0 ? "-.inf" : ".inf" }
if value == value.rounded(), abs(value) < 1e15 { return String(Int64(value)) }
return String(value)
}
/// Plain when YAML round-trips it back to the same string, double-quoted otherwise.
/// The round-trip check is the authority: it catches leading/trailing space, comment
/// introducers, and anything that would resolve to a bool/int/float/null/timestamp.
static func emitScalar(_ value: String) -> String {
guard !value.isEmpty, !value.unicodeScalars.contains(where: { $0 == "\n" || $0 == "\r" }) else {
return quoted(value)
}
guard let node = try? Yams.compose(yaml: "v: \(value)"),
let mapping = node.mapping,
let scalar = mapping["v"],
Resolver.default.resolveTag(of: scalar) == .str,
scalar.scalar?.string == value
else { return quoted(value) }
return value
}
private static func quoted(_ value: String) -> String {
var out = "\""
for scalar in value.unicodeScalars {
switch scalar {
case "\\": out += "\\\\"
case "\"": out += "\\\""
case "\n": out += "\\n"
case "\r": out += "\\r"
case "\t": out += "\\t"
default:
if scalar.value < 0x20 || scalar.value == 0x7F {
out += String(format: "\\x%02x", scalar.value)
} else {
out.unicodeScalars.append(scalar)
}
}
}
return out + "\""
}
}