Closes two of the four emitter gaps a feature-parity audit against Yams found (PureYAMLYamsComparison's FEATURE-PARITY.md): no configurable indentation and no sorted-key output. indentWidth replaces every hardcoded indent + 2 in Dumper.swift/ Dumper.Mapping.swift, verified with round-trip tests since indentation is purely a rendering choice and must never change what a document means. sortKeys sorts a mapping's pairs by their string key in both block and flow style, but only when every key in that mapping is a plain string; a mapping with any complex (mapping/sequence) key stays in insertion order, since there is no well-defined ordering across mixed key kinds. Off by default, since insertion order is what makes a duplicate-key-preserving Mapping round-trip meaningfully in the first place. width-based line wrapping and canonical mode are each a correctness surface of their own and are deliberately left for later, not folded into this.
97 lines
3.2 KiB
Swift
97 lines
3.2 KiB
Swift
extension PureYAML.Emitting.Dumper {
|
|
func renderMapping(
|
|
_ mapping: PureYAML.Model.Mapping,
|
|
indent: Int,
|
|
) -> [String] {
|
|
let prefix = String(repeating: " ", count: indent)
|
|
guard !mapping.pairs.isEmpty else {
|
|
return [prefix + "{}"]
|
|
}
|
|
|
|
return orderedPairs(mapping).flatMap { pair in
|
|
renderMappingPair(pair, indent: indent)
|
|
}
|
|
}
|
|
|
|
/// `mapping.pairs` in insertion order, or sorted by their rendered
|
|
/// string-keyed form when `options.sortKeys` is set. Only string keys
|
|
/// are sortable this way; a mapping with any non-string key is left in
|
|
/// insertion order regardless of the option, since there is no
|
|
/// well-defined lexicographic ordering across mixed key kinds
|
|
/// (`Model.Key.mapping`/`.sequence`) to sort by.
|
|
func orderedPairs(_ mapping: PureYAML.Model.Mapping) -> [PureYAML.Model.Pair] {
|
|
guard options.sortKeys, mapping.pairs.allSatisfy({ pair in
|
|
if case .string = pair.keyNode {
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}) else {
|
|
return mapping.pairs
|
|
}
|
|
return mapping.pairs.sorted { $0.key < $1.key }
|
|
}
|
|
|
|
func renderMappingPair(
|
|
_ pair: PureYAML.Model.Pair,
|
|
indent: Int,
|
|
) -> [String] {
|
|
switch pair.keyNode {
|
|
case .mapping, .sequence:
|
|
renderComplexMappingPair(pair, indent: indent)
|
|
case .string:
|
|
renderStringKeyedMappingPair(pair, indent: indent)
|
|
}
|
|
}
|
|
|
|
func renderStringKeyedMappingPair(
|
|
_ pair: PureYAML.Model.Pair,
|
|
indent: Int,
|
|
) -> [String] {
|
|
let prefix = String(repeating: " ", count: indent)
|
|
if let emptyCollection = renderEmptyCollectionLiteral(pair.value) {
|
|
return [prefix + escapeKey(pair.key) + ": " + emptyCollection]
|
|
}
|
|
|
|
switch pair.value {
|
|
case .mapping, .sequence:
|
|
return [prefix + escapeKey(pair.key) + ":"] + render(pair.value, indent: indent + options.indentWidth)
|
|
default:
|
|
return renderMappingScalarPair(pair, indent: indent)
|
|
}
|
|
}
|
|
|
|
func renderComplexMappingPair(
|
|
_ pair: PureYAML.Model.Pair,
|
|
indent: Int,
|
|
) -> [String] {
|
|
let prefix = String(repeating: " ", count: indent)
|
|
if let emptyCollection = renderEmptyCollectionLiteral(pair.value) {
|
|
return [prefix + "? " + pair.keyNode.flowDescription]
|
|
+ [prefix + ": " + emptyCollection]
|
|
}
|
|
|
|
return [prefix + "? " + pair.keyNode.flowDescription]
|
|
+ [prefix + ":"]
|
|
+ render(pair.value, indent: indent + options.indentWidth)
|
|
}
|
|
|
|
func renderFlowMapping(_ mapping: PureYAML.Model.Mapping) -> String {
|
|
let pairs = orderedPairs(mapping)
|
|
.map { pair in
|
|
"\(renderFlowKey(pair.keyNode)): \(renderFlow(pair.value))"
|
|
}
|
|
.joined(separator: ", ")
|
|
return "{\(pairs)}"
|
|
}
|
|
|
|
func renderFlowKey(_ key: PureYAML.Model.Key) -> String {
|
|
switch key {
|
|
case .mapping, .sequence:
|
|
"? \(renderFlow(key.value))"
|
|
case let .string(value):
|
|
quote(value)
|
|
}
|
|
}
|
|
}
|