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
79 lines
2.7 KiB
Swift
79 lines
2.7 KiB
Swift
import Foundation
|
|
import Yams
|
|
|
|
/// A `Sendable` snapshot of a parsed YAML value. Mappings keep document order; this is a
|
|
/// read-only view — the engine never serializes through it (see `FrontmatterDocument`).
|
|
public enum YAMLValue: Sendable, Equatable {
|
|
case null
|
|
case bool(Bool)
|
|
case int(Int)
|
|
case double(Double)
|
|
case string(String)
|
|
case date(Date)
|
|
case sequence([YAMLValue])
|
|
case mapping([Pair])
|
|
|
|
public struct Pair: Sendable, Equatable {
|
|
public let key: YAMLValue
|
|
public let value: YAMLValue
|
|
|
|
public init(key: YAMLValue, value: YAMLValue) {
|
|
self.key = key
|
|
self.value = value
|
|
}
|
|
}
|
|
}
|
|
|
|
extension YAMLValue {
|
|
init(_ node: Node, resolver: Resolver = .default) {
|
|
switch node {
|
|
case let .scalar(scalar):
|
|
self = YAMLValue(scalar: scalar, tag: resolver.resolveTag(of: node))
|
|
case let .sequence(sequence):
|
|
self = .sequence(sequence.map { YAMLValue($0, resolver: resolver) })
|
|
case let .mapping(mapping):
|
|
self = .mapping(mapping.map {
|
|
Pair(key: YAMLValue($0.key, resolver: resolver), value: YAMLValue($0.value, resolver: resolver))
|
|
})
|
|
case .alias:
|
|
self = .null
|
|
}
|
|
}
|
|
|
|
private init(scalar: Node.Scalar, tag: Tag.Name) {
|
|
switch tag {
|
|
case .null:
|
|
self = .null
|
|
case .bool:
|
|
self = Bool.construct(from: scalar).map(YAMLValue.bool) ?? .string(scalar.string)
|
|
case .int:
|
|
self = Int.construct(from: scalar).map(YAMLValue.int)
|
|
?? Double.construct(from: scalar).map(YAMLValue.double)
|
|
?? .string(scalar.string)
|
|
case .float:
|
|
self = Double.construct(from: scalar).map(YAMLValue.double) ?? .string(scalar.string)
|
|
case .timestamp:
|
|
self = Date.construct(from: scalar).map(YAMLValue.date) ?? .string(scalar.string)
|
|
default:
|
|
self = .string(scalar.string)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension YAMLValue: CustomStringConvertible {
|
|
/// Diagnostic rendering only — never used to write files.
|
|
public var description: String {
|
|
switch self {
|
|
case .null: "null"
|
|
case let .bool(value): value ? "true" : "false"
|
|
case let .int(value): String(value)
|
|
case let .double(value): String(value)
|
|
case let .string(value): value
|
|
case let .date(value): value.formatted(.iso8601)
|
|
case let .sequence(values): "[" + values.map(\.description).joined(separator: ", ") + "]"
|
|
case let .mapping(pairs):
|
|
"{" + pairs.map { "\($0.key.description): \($0.value.description)" }.joined(separator: ", ") + "}"
|
|
}
|
|
}
|
|
}
|