diff --git a/Sources/PotentYAML/YAMLEncoder.swift b/Sources/PotentYAML/YAMLEncoder.swift index a75049b92..79ee35ddc 100644 --- a/Sources/PotentYAML/YAMLEncoder.swift +++ b/Sources/PotentYAML/YAMLEncoder.swift @@ -32,6 +32,13 @@ public class YAMLEncoder: ValueEncoder, EncodesToStr self.rawValue = rawValue } + /// Attempt to output good looking YAML + /// + /// Aside from selecting the style (flow or block) for mappings + /// and sequences, it limits the output width to 80 characters + /// by using folded strings when necessary. + public static let pretty = OutputFormatting(rawValue: 1 << 0) + /// Produce YAML with dictionary keys sorted in lexicographic order. public static let sortedKeys = OutputFormatting(rawValue: 1 << 1) } @@ -80,6 +87,9 @@ public class YAMLEncoder: ValueEncoder, EncodesToStr /// The output format to produce. Defaults to `[]`. public var outputFormatting: OutputFormatting = [] + /// The preferred output style for sequences and mappings. Defaults to `.any`. + public var preferredCollectionStyle: YAML.CollectionStyle = .any + /// The strategy to use in encoding dates. Defaults to `.deferredToDate`. public var dateEncodingStrategy: DateEncodingStrategy = .deferredToDate @@ -93,6 +103,7 @@ public class YAMLEncoder: ValueEncoder, EncodesToStr dataEncodingStrategy: dataEncodingStrategy, outputFormatting: outputFormatting, keyEncodingStrategy: keyEncodingStrategy, + preferredCollectionStyle: preferredCollectionStyle, userInfo: userInfo ) } @@ -119,6 +130,7 @@ public struct YAMLEncoderTransform: InternalEncoderTransform, InternalValueSeria public let dataEncodingStrategy: YAMLEncoder.DataEncodingStrategy public let outputFormatting: YAMLEncoder.OutputFormatting public let keyEncodingStrategy: KeyEncodingStrategy + public let preferredCollectionStyle: YAML.CollectionStyle public let userInfo: [CodingUserInfoKey: Any] } @@ -358,7 +370,12 @@ public struct YAMLEncoderTransform: InternalEncoderTransform, InternalValueSeria if options.outputFormatting.contains(.sortedKeys) { writingOptions.insert(.sortedKeys) } - return try YAMLSerialization.data(from: value, options: writingOptions) + if options.outputFormatting.contains(.pretty) { + writingOptions.insert(.pretty) + } + return try YAMLSerialization.data(from: value, + preferredCollectionStyle: options.preferredCollectionStyle, + options: writingOptions) } public static func string(from value: YAML, options: Options) throws -> String { @@ -366,7 +383,12 @@ public struct YAMLEncoderTransform: InternalEncoderTransform, InternalValueSeria if options.outputFormatting.contains(.sortedKeys) { writingOptions.insert(.sortedKeys) } - return try YAMLSerialization.string(from: value, options: writingOptions) + if options.outputFormatting.contains(.pretty) { + writingOptions.insert(.pretty) + } + return try YAMLSerialization.string(from: value, + preferredCollectionStyle: options.preferredCollectionStyle, + options: writingOptions) } } diff --git a/Sources/PotentYAML/YAMLSerialization.swift b/Sources/PotentYAML/YAMLSerialization.swift index 0515d831e..1fa6abc7e 100644 --- a/Sources/PotentYAML/YAMLSerialization.swift +++ b/Sources/PotentYAML/YAMLSerialization.swift @@ -72,10 +72,17 @@ public enum YAMLSerialization { } public static let sortedKeys = WritingOptions(rawValue: 1 << 0) + public static let pretty = WritingOptions(rawValue: 1 << 1) } - public static func data(from yaml: YAML, options: WritingOptions = []) throws -> Data { - guard let data = try string(from: yaml, options: options).data(using: .utf8) else { + public static func data(from yaml: YAML, + preferredCollectionStyle: YAML.CollectionStyle = .any, + options: WritingOptions = []) throws -> Data { + guard + let data = try string(from: yaml, + preferredCollectionStyle: preferredCollectionStyle, + options: options).data(using: .utf8) + else { throw DecodingError .dataCorrupted( DecodingError @@ -85,11 +92,17 @@ public enum YAMLSerialization { return data } - public static func string(from yaml: YAML, options: WritingOptions = []) throws -> String { + public static func string(from yaml: YAML, + preferredCollectionStyle: YAML.CollectionStyle = .any, + options: WritingOptions = []) throws -> String { var output = String() - try YAMLWriter.write([yaml], sortedKeys: options.contains(.sortedKeys)) { + try YAMLWriter.write([yaml], + preferredCollectionStyle: preferredCollectionStyle, + pretty: options.contains(.pretty), + width: options.contains(.pretty) ? .normal : .infinite, + sortedKeys: options.contains(.sortedKeys)) { guard let str = $0 else { return } output.append(str) } diff --git a/Sources/PotentYAML/YAMLWriter.swift b/Sources/PotentYAML/YAMLWriter.swift index c9849f693..4eb036f50 100644 --- a/Sources/PotentYAML/YAMLWriter.swift +++ b/Sources/PotentYAML/YAMLWriter.swift @@ -17,7 +17,18 @@ internal struct YAMLWriter { typealias Writer = (String?) -> Void - static func write(_ documents: YAML.Sequence, sortedKeys: Bool = false, writer: @escaping Writer) throws { + enum Width { + case normal + case wide + case infinite + } + + static func write(_ documents: YAML.Sequence, + preferredCollectionStyle: YAML.CollectionStyle = .block, + pretty: Bool = true, + width: Width = .normal, + sortedKeys: Bool = false, + writer: @escaping Writer) throws { func output( emitter: OpaquePointer?, @@ -40,9 +51,15 @@ internal struct YAMLWriter { try withUnsafePointer(to: writer) { writerPtr in - var flags: UInt32 = 0 - if sortedKeys { - flags |= FYECF_SORT_KEYS.rawValue + var flags: UInt32 = pretty ? FYECF_MODE_PRETTY.rawValue : FYECF_DEFAULT.rawValue + + switch width { + case .normal: + flags |= FYECF_WIDTH_80.rawValue + case .wide: + flags |= FYECF_WIDTH_132.rawValue + case .infinite: + flags |= FYECF_WIDTH_INF.rawValue } var emitterCfg = fy_emitter_cfg( @@ -62,7 +79,10 @@ internal struct YAMLWriter { try documents.forEach { emit(emitter: emitter, type: FYET_DOCUMENT_START, args: 0, 0, 0) - try emit(emitter: emitter, value: $0, sortedKeys: sortedKeys) + try emit(emitter: emitter, + value: $0, + preferredCollectionStyle: preferredCollectionStyle, + sortedKeys: sortedKeys) emit(emitter: emitter, type: FYET_DOCUMENT_END) } @@ -72,7 +92,10 @@ internal struct YAMLWriter { } - private static func emit(emitter: OpaquePointer, value: YAML, sortedKeys: Bool) throws { + private static func emit(emitter: OpaquePointer, + value: YAML, + preferredCollectionStyle: YAML.CollectionStyle, + sortedKeys: Bool) throws { switch value { case .null(anchor: let anchor): @@ -92,10 +115,22 @@ internal struct YAMLWriter { emit(emitter: emitter, scalar: bool ? "true" : "false", style: FYSS_PLAIN, anchor: anchor, tag: nil) case .sequence(let sequence, style: let style, tag: let tag, anchor: let anchor): - try emit(emitter: emitter, sequence: sequence, sortedKeys: sortedKeys, style: style, anchor: anchor, tag: tag) + try emit(emitter: emitter, + sequence: sequence, + style: style, + preferredStyle: preferredCollectionStyle, + sortedKeys: sortedKeys, + anchor: anchor, + tag: tag) case .mapping(let mapping, style: let style, tag: let tag, anchor: let anchor): - try emit(emitter: emitter, mapping: mapping, sortedKeys: sortedKeys, style: style, anchor: anchor, tag: tag) + try emit(emitter: emitter, + mapping: mapping, + style: style, + preferredStyle: preferredCollectionStyle, + sortedKeys: sortedKeys, + anchor: anchor, + tag: tag) case .alias(let alias): emit(emitter: emitter, alias: alias) @@ -106,27 +141,28 @@ internal struct YAMLWriter { private static func emit( emitter: OpaquePointer, mapping: YAML.Mapping, - sortedKeys: Bool, style: YAML.CollectionStyle, + preferredStyle: YAML.CollectionStyle, + sortedKeys: Bool, anchor: String?, tag: YAML.Tag? ) throws { - var mapping = mapping emit( emitter: emitter, type: FYET_MAPPING_START, - args: style.nodeStyle.rawValue, + args: style.nodeStyle(preferred: preferredStyle).rawValue, anchor.varArg, (tag?.rawValue).varArg ) + var mapping = mapping if sortedKeys { mapping = mapping.sorted { $0.key.description < $1.key.description } } try mapping.forEach { entry in - try emit(emitter: emitter, value: entry.key, sortedKeys: sortedKeys) - try emit(emitter: emitter, value: entry.value, sortedKeys: sortedKeys) + try emit(emitter: emitter, value: entry.key, preferredCollectionStyle: preferredStyle, sortedKeys: sortedKeys) + try emit(emitter: emitter, value: entry.value, preferredCollectionStyle: preferredStyle, sortedKeys: sortedKeys) } emit(emitter: emitter, type: FYET_MAPPING_END) } @@ -134,20 +170,21 @@ internal struct YAMLWriter { private static func emit( emitter: OpaquePointer, sequence: [YAML], - sortedKeys: Bool, style: YAML.CollectionStyle, + preferredStyle: YAML.CollectionStyle, + sortedKeys: Bool, anchor: String?, tag: YAML.Tag? ) throws { emit( emitter: emitter, type: FYET_SEQUENCE_START, - args: style.nodeStyle.rawValue, + args: style.nodeStyle(preferred: preferredStyle).rawValue, anchor.varArg, (tag?.rawValue).varArg ) try sequence.forEach { element in - try emit(emitter: emitter, value: element, sortedKeys: sortedKeys) + try emit(emitter: emitter, value: element, preferredCollectionStyle: preferredStyle, sortedKeys: sortedKeys) } emit(emitter: emitter, type: FYET_SEQUENCE_END) } @@ -208,11 +245,11 @@ extension Optional where Wrapped == String { extension YAML.CollectionStyle { - var nodeStyle: fy_node_style { - switch self { - case .any: return FYNS_ANY - case .flow: return FYNS_FLOW - case .block: return FYNS_BLOCK + func nodeStyle(preferred: Self) -> fy_node_style { + switch (self, preferred) { + case (.any, .any): return FYNS_ANY + case (.any, .flow), (.flow, _): return FYNS_FLOW + case (.any, .block), (.block, _): return FYNS_BLOCK } }