PureYAML/Sources/Emitting/Dumper.swift

103 lines
2.8 KiB
Swift

public extension PureYAML.Emitting {
/// Block-style YAML serializer for ``PureYAML/Model/Value``.
struct Dumper: Sendable {
public init() {}
public func dump(_ value: PureYAML.Model.Value) -> String {
render(value, indent: 0).joined(separator: "\n") + "\n"
}
}
}
extension PureYAML.Emitting.Dumper {
func render(
_ value: PureYAML.Model.Value,
indent: Int,
) -> [String] {
switch value {
case let .mapping(mapping):
renderMapping(mapping, indent: indent)
case let .sequence(values):
renderSequence(values, indent: indent)
default:
[String(repeating: " ", count: indent) + renderScalar(value)]
}
}
func renderMapping(
_ mapping: PureYAML.Model.Mapping,
indent: Int,
) -> [String] {
let prefix = String(repeating: " ", count: indent)
return mapping.pairs.flatMap { pair in
switch pair.value {
case .mapping, .sequence:
[prefix + escapeKey(pair.key) + ":"] + render(pair.value, indent: indent + 2)
default:
[prefix + escapeKey(pair.key) + ": " + renderScalar(pair.value)]
}
}
}
func renderSequence(
_ values: [PureYAML.Model.Value],
indent: Int,
) -> [String] {
let prefix = String(repeating: " ", count: indent)
return values.flatMap { value in
switch value {
case .mapping, .sequence:
[prefix + "-"] + render(value, indent: indent + 2)
default:
[prefix + "- " + renderScalar(value)]
}
}
}
func renderScalar(_ value: PureYAML.Model.Value) -> String {
switch value {
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):
quote(value)
case .sequence, .mapping:
""
}
}
func escapeKey(_ key: String) -> String {
if key.isEmpty || key.contains(":") || key.contains("#") || key.contains("\n") {
return quote(key)
}
return key
}
func quote(_ value: String) -> String {
var output = "\""
for character in value {
switch character {
case "\"":
output += "\\\""
case "\\":
output += "\\\\"
case "\n":
output += "\\n"
case "\r":
output += "\\r"
case "\t":
output += "\\t"
default:
output.append(character)
}
}
output += "\""
return output
}
}