Files
lanework/Kanban/Storage/FrontmatterValue.swift
T
rzen d5ad21c3da The board wears a picture — background becomes a mapping, and the window chrome follows it under a thin frost
background is {color:, image:} and only a mapping at every level; the board's image paints the full window under a transparent title bar, with a thin-material frost strip keeping the chrome legible and the standard accommodations intact.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-07 10:15:03 -04:00

90 lines
3.4 KiB
Swift

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
}
/// The double-quoted form unconditionally — what a value inside a **flow collection** takes
/// (`FrontmatterDocument.setStyleValue`), where `emitScalar`'s block-context round trip is not
/// the right question: `,`, `]`, `}` and `: ` end a plain scalar in flow context and not on a
/// line of its own.
static func emitQuoted(_ value: String) -> String { quoted(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 + "\""
}
}