diff --git a/Sources/PotentYAML/Cfyaml.swift b/Sources/PotentYAML/Cfyaml.swift new file mode 100644 index 000000000..3382678a0 --- /dev/null +++ b/Sources/PotentYAML/Cfyaml.swift @@ -0,0 +1,138 @@ +// +// Cfyaml.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Cfyaml + + +enum Libfyaml { + + internal static func createParser() -> OpaquePointer? { + + guard let diag = createDiag(options: (FYET_ERROR, false, true)) else { + return nil + } + defer { fy_diag_unref(diag) } + + var parseCfg = fy_parse_cfg( + search_path: nil, + flags: FYPCF_QUIET, + userdata: nil, + diag: diag + ) + + return fy_parser_create(&parseCfg) + } + + internal typealias EmitterWriter = (String?) -> Void + + internal typealias EmitterOutput = @convention(c) ( + OpaquePointer?, + fy_emitter_write_type, + UnsafePointer?, + Int32, + UnsafeMutableRawPointer? + ) -> Int32 + + internal static func createEmitter( + flags: fy_emitter_cfg_flags, + output: EmitterOutput, + writer: UnsafePointer + ) -> OpaquePointer? { + + guard let diag = createDiag(options: (FYET_ERROR, false, false)) else { + return nil + } + defer { fy_diag_unref(diag) } + + var cfg = fy_emitter_cfg( + flags: flags, + output: output, + userdata: UnsafeMutableRawPointer(mutating: writer), + diag: diag + ) + + return fy_emitter_create(&cfg) + } + + internal typealias DiagOptions = (level: fy_error_type, showSource: Bool, showPosition: Bool) + + internal static func createDiag(options: DiagOptions) -> OpaquePointer? { + + var diagCfg = fy_diag_cfg( + fp: nil, + output_fn: nil, + user: nil, + level: FYET_ERROR, + module_mask: UInt32.max, + colorize: false, + show_source: options.showSource, + show_position: options.showPosition, + show_type: false, + show_module: false, + source_width: Int32.max, + position_width: 4, + type_width: 0, + module_width: 0 + ) + + guard let diag = fy_diag_create(&diagCfg) else { + return nil + } + + fy_diag_set_collect_errors(diag, true) + + return diag + } + + +} + +extension fy_emitter_cfg_flags { + + private static var indentMask = Self(UInt32(bitPattern: FYECF_INDENT_MASK)) + private static var indentShift = Self(UInt32(bitPattern: FYECF_INDENT_SHIFT)) + + static func indent(_ indent: R) -> Self where R.RawValue == UInt8 { + .indent(indent.rawValue) + } + + static func indent(_ indent: UInt8) -> Self { + precondition(indent >= 0 && indent <= 9) + return (Self(UInt32(indent)) & .indentMask) << .indentShift + } + + private static var widthMask = Self(UInt32(bitPattern: FYECF_WIDTH_MASK)) + private static var widthShift = Self(UInt32(bitPattern: FYECF_WIDTH_SHIFT)) + + static func width(_ width: R) -> Self where R.RawValue == UInt8 { + .width(width.rawValue) + } + + static func width(_ width: UInt8) -> Self { + return (Self(UInt32(width)) & .widthMask) << .widthShift + } + + static prefix func ~ (_ value: Self) -> Self { + return Self(~value.rawValue) + } + + static func & (_ left: Self, _ right: Self) -> Self { + return Self(rawValue: left.rawValue & right.rawValue) + } + + static func | (_ left: Self, _ right: Self) -> Self { + return Self(rawValue: left.rawValue | right.rawValue) + } + + static func << (_ left: Self, _ right: Self) -> Self { + return Self(rawValue: left.rawValue << right.rawValue) + } + +} diff --git a/Sources/PotentYAML/YAMLReader.swift b/Sources/PotentYAML/YAMLReader.swift index 2971f9193..9a7a98d10 100644 --- a/Sources/PotentYAML/YAMLReader.swift +++ b/Sources/PotentYAML/YAMLReader.swift @@ -11,54 +11,28 @@ import Cfyaml import Foundation + internal enum YAMLReader { typealias Error = YAMLSerialization.Error static func read(data: Data) throws -> YAML.Sequence { - var diagCfg = - fy_diag_cfg( - fp: nil, - output_fn: nil, - user: nil, - level: FYET_ERROR, - module_mask: UInt32.max, - colorize: false, - show_source: false, - show_position: true, - show_type: false, - show_module: false, - source_width: Int32.max, - position_width: 4, - type_width: 0, - module_width: 0 - ) - let diag = fy_diag_create(&diagCfg) - fy_diag_set_collect_errors(diag, true) - defer { fy_diag_destroy(diag) } - - var parseCfg = fy_parse_cfg(search_path: nil, flags: FYPCF_QUIET, userdata: nil, diag: diag) - - guard let parser = fy_parser_create(&parseCfg).map({ Parser(rawParser: $0, rawDiag: diag) }) else { + guard let parser = Libfyaml.createParser().map(Parser.init) else { throw Error.unableToCreateParser } - defer { parser.destroy() } return try data.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) in - let bytes = ptr.bindMemory(to: Int8.self) - - fy_parser_set_string(parser.rawParser, bytes.baseAddress, bytes.count) + parser.setInput(ptr.bindMemory(to: CChar.self)) - _ = try parser.expect(eventType: FYET_STREAM_START) + try parser.expect(eventType: FYET_STREAM_START) return try stream(parser: parser) } } - static func stream(parser: Parser) throws -> YAML.Sequence { var documents: YAML.Sequence = [] @@ -91,7 +65,7 @@ internal enum YAMLReader { let document = try value(event: root, parser: parser) - _ = try parser.expect(eventType: FYET_DOCUMENT_END) + try parser.expect(eventType: FYET_DOCUMENT_END) return document } @@ -263,58 +237,72 @@ internal enum YAMLReader { struct Parser { struct Event { - let rawEvent: UnsafeMutablePointer - var type: fy_event_type { rawEvent.pointee.type } + let event: UnsafeMutablePointer + + init(_ event: UnsafeMutablePointer) { + self.event = event + } + + var type: fy_event_type { event.pointee.type } var anchor: String? { - guard let token = rawEvent.pointee.scalar.anchor else { return nil } + guard let token = event.pointee.scalar.anchor else { return nil } return String(token: token) } var tag: String? { - guard let token = rawEvent.pointee.scalar.tag else { return nil } + guard let token = event.pointee.scalar.tag else { return nil } return String(token: token) } var scalar: (String, fy_scalar_style)? { - guard let token = rawEvent.pointee.scalar.value else { return nil } + guard let token = event.pointee.scalar.value else { return nil } return String(token: token).map { ($0, fy_token_scalar_style(token)) } } var style: fy_node_style { - return fy_event_get_node_style(rawEvent) + return fy_event_get_node_style(event) } } - let rawParser: OpaquePointer - let rawDiag: OpaquePointer? + let parser: OpaquePointer - func nextIfPresent() -> Event? { - return fy_parser_parse(rawParser).map { Event(rawEvent: $0) } + func setInput(_ bytes: UnsafeBufferPointer) { + + fy_parser_set_string(parser, bytes.baseAddress, bytes.count) + } + + private func nextIfPresent() -> Event? { + + return fy_parser_parse(parser).map(Event.init) } func next() throws -> Event { + guard let event = nextIfPresent() else { throw error(fallback: .unexpectedEOF) } + return event } - func expect(eventType: fy_event_type) throws -> Event { + func expect(eventType: fy_event_type) throws { let event = try next() + defer { free(event: event) } if event.type != eventType { throw error(fallback: .unexpectedEvent) } - - return event } func error(fallback: Error) -> Error { - guard let diag = rawDiag else { return fallback } + + guard let diag = fy_parser_get_diag(parser) else { + return fallback + } var prev: UnsafeMutableRawPointer? if let error = fy_diag_errors_iterate(diag, &prev) { @@ -327,11 +315,11 @@ internal enum YAMLReader { } func destroy() { - fy_parser_destroy(rawParser) + fy_parser_destroy(parser) } func free(event: Event) { - fy_parser_event_free(rawParser, event.rawEvent) + fy_parser_event_free(parser, event.event) } } diff --git a/Sources/PotentYAML/YAMLSerialization.swift b/Sources/PotentYAML/YAMLSerialization.swift index 0198318f4..1c2735343 100644 --- a/Sources/PotentYAML/YAMLSerialization.swift +++ b/Sources/PotentYAML/YAMLSerialization.swift @@ -22,7 +22,7 @@ public enum YAMLSerialization { /// possible public enum Error: Swift.Error { case unableToCreateEmitter - case emitError + case emitError(message: String) case unableToCreateParser case parserError(message: String, line: Int, column: Int) case unexpectedEOF @@ -74,6 +74,7 @@ public enum YAMLSerialization { public static let sortedKeys = WritingOptions(rawValue: 1 << 0) public static let pretty = WritingOptions(rawValue: 1 << 1) public static let json = WritingOptions(rawValue: 1 << 3) + public static let explictDocumentMarkers = WritingOptions(rawValue: 1 << 4) } public static func data(from yaml: YAML, @@ -102,12 +103,13 @@ public enum YAMLSerialization { var output = String() - try YAMLWriter.write([yaml], - preferredStyles: (preferredCollectionStyle, preferredStringStyle), - json: options.contains(.json), - pretty: options.contains(.pretty), - width: options.contains(.pretty) ? .normal : .infinite, - sortedKeys: options.contains(.sortedKeys)) { + let writerOptions = YAMLWriter.Options( + writingOptions: options, + preferredCollectionStyle: preferredCollectionStyle, + preferredStringStyle: preferredStringStyle + ) + + try YAMLWriter.write([yaml], options: writerOptions) { guard let str = $0 else { return } output.append(str) } @@ -116,3 +118,21 @@ public enum YAMLSerialization { } } + +extension YAMLWriter.Options { + + init( + writingOptions: YAMLSerialization.WritingOptions, + preferredCollectionStyle: YAML.CollectionStyle, + preferredStringStyle: YAML.StringStyle + ) { + self.preferredCollectionStyle = preferredCollectionStyle + self.preferredStringStyle = preferredStringStyle + self.json = writingOptions.contains(.json) + self.pretty = writingOptions.contains(.pretty) + self.width = writingOptions.contains(.pretty) ? .normal : .infinite + self.sortedKeys = writingOptions.contains(.sortedKeys) + self.explicitDocumentMarkers = writingOptions.contains(.explictDocumentMarkers) + } + +} diff --git a/Sources/PotentYAML/YAMLWriter.swift b/Sources/PotentYAML/YAMLWriter.swift index fd14acf81..eb0e62d80 100644 --- a/Sources/PotentYAML/YAMLWriter.swift +++ b/Sources/PotentYAML/YAMLWriter.swift @@ -11,126 +11,105 @@ import Cfyaml import Foundation + internal struct YAMLWriter { typealias Error = YAMLSerialization.Error - typealias Writer = (String?) -> Void + enum Width: UInt8 { + case normal = 80 + case wide = 120 + case infinite = 0xff + } + + struct Options { - enum Width { - case normal - case wide - case infinite + static let `default` = Options() + + var preferredCollectionStyle: YAML.CollectionStyle = .any + var preferredStringStyle: YAML.StringStyle = .any + var json: Bool = false + var pretty: Bool = true + var width: Width = .normal + var sortedKeys: Bool = false + var explicitDocumentMarkers: Bool = false } let emitter: OpaquePointer - let sortedKeys: Bool - let preferredCollectionStyle: YAML.CollectionStyle - let preferredStringStyle: YAML.StringStyle + let options: Options + + static func write(_ documents: YAML.Sequence, options: Options = .default) throws -> String { + var output = "" + try write(documents, options: options) { output += $0 ?? "" } + return output + } static func write(_ documents: YAML.Sequence, - preferredStyles: (collection: YAML.CollectionStyle, string: YAML.StringStyle) = (.block, .plain), - json: Bool = false, - pretty: Bool = true, - width: Width = .normal, - sortedKeys: Bool = false, - writer: @escaping Writer) throws { - - func output( - emitter: OpaquePointer?, - writeType: fy_emitter_write_type, - str: UnsafePointer?, - len: Int32, - userInfo: UnsafeMutableRawPointer? - ) -> Int32 { - guard let writer = userInfo?.assumingMemoryBound(to: Writer.self).pointee else { - fatalError() - } - guard let str = str else { - writer(nil) - return 0 - } - let strPtr = UnsafeMutableRawPointer(mutating: str) - writer(String(bytesNoCopy: strPtr, length: Int(len), encoding: .utf8, freeWhenDone: false)) - return len - } + options: Options = Options(), + writer: @escaping Libfyaml.EmitterWriter) throws { try withUnsafePointer(to: writer) { writerPtr in - var flags = - switch (pretty, json) { - case (true, true): FYECF_MODE_JSON.rawValue - case (true, false): FYECF_MODE_PRETTY.rawValue - case (false, true): FYECF_MODE_JSON_ONELINE.rawValue - default: 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 flags: fy_emitter_cfg_flags + switch (options.pretty, options.json, options.width) { + case (true, true, let width): + flags = FYECF_MODE_JSON | .width(width) + case (true, false, let width): + flags = FYECF_MODE_PRETTY | .width(width) + case (false, true, let width): + flags = FYECF_MODE_JSON_ONELINE | .width(width) + case (false, false, let width): + flags = FYECF_MODE_ORIGINAL | FYECF_INDENT_DEFAULT | .width(width) } - var emitterCfg = fy_emitter_cfg( - flags: fy_emitter_cfg_flags(rawValue: flags), - output: output, - userdata: UnsafeMutableRawPointer(mutating: writerPtr), - diag: nil - ) - - guard let emitter = fy_emitter_create(&emitterCfg) else { + guard let emitter = Libfyaml.createEmitter(flags: flags, output: emitOutput, writer: writerPtr) else { throw Error.unableToCreateEmitter } defer { fy_emitter_destroy(emitter) } - let writer = YAMLWriter( - emitter: emitter, - sortedKeys: sortedKeys, - preferredCollectionStyle: preferredStyles.collection, - preferredStringStyle: preferredStyles.string - ) - - return try writer.emit(documents: documents) + return try YAMLWriter(emitter: emitter, options: options) + .emit(documents: documents) } } private func emit(documents: [YAML]) throws { - emit(type: FYET_STREAM_START) + let implicitDocumentMarkers = !options.explicitDocumentMarkers && documents.count == 1 ? 1 : 0 + + try emit(type: FYET_STREAM_START) for document in documents { - emit(type: FYET_DOCUMENT_START, args: 0, 0, 0) + try emit(type: FYET_DOCUMENT_START, args: implicitDocumentMarkers, 0, 0) try emit(value: document) - emit(type: FYET_DOCUMENT_END) + try emit(type: FYET_DOCUMENT_END, args: implicitDocumentMarkers) } - emit(type: FYET_STREAM_END) + try emit(type: FYET_STREAM_END) + + try failIfError() } private func emit(value: YAML) throws { switch value { case .null(anchor: let anchor): - emit(scalar: "null", style: FYSS_PLAIN, anchor: anchor, tag: nil) + try emit(scalar: "null", style: FYSS_PLAIN, anchor: anchor, tag: nil) case .string(let string, style: let style, tag: let tag, anchor: let anchor): - let stringStyle = style != .any ? style : preferredStringStyle + let stringStyle = style != .any ? style : options.preferredStringStyle let scalarStyle = fy_scalar_style(rawValue: stringStyle.rawValue) - emit(scalar: string, style: scalarStyle, anchor: anchor, tag: tag?.rawValue) + try emit(scalar: string, style: scalarStyle, anchor: anchor, tag: tag?.rawValue) case .integer(let integer, anchor: let anchor): - emit(scalar: integer.value, style: FYSS_PLAIN, anchor: anchor, tag: nil) + try emit(scalar: integer.value, style: FYSS_PLAIN, anchor: anchor, tag: nil) case .float(let float, anchor: let anchor): - emit(scalar: float.value, style: FYSS_PLAIN, anchor: anchor, tag: nil) + try emit(scalar: float.value, style: FYSS_PLAIN, anchor: anchor, tag: nil) case .bool(let bool, anchor: let anchor): - emit(scalar: bool ? "true" : "false", style: FYSS_PLAIN, anchor: anchor, tag: nil) + try emit(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(sequence: sequence, style: style, anchor: anchor, tag: tag) @@ -139,20 +118,20 @@ internal struct YAMLWriter { try emit(mapping: mapping, style: style, anchor: anchor, tag: tag) case .alias(let alias): - emit(alias: alias) + try emit(alias: alias) } } private func emit(mapping: YAML.Mapping, style: YAML.CollectionStyle, anchor: String?, tag: YAML.Tag?) throws { - emit( + try emit( type: FYET_MAPPING_START, - args: style.nodeStyle(preferred: preferredCollectionStyle).rawValue, + args: style.nodeStyle(preferred: options.preferredCollectionStyle).rawValue, anchor.varArg, (tag?.rawValue).varArg ) var mapping = mapping - if sortedKeys { + if options.sortedKeys { mapping = mapping.sorted { $0.key.description < $1.key.description } @@ -161,61 +140,93 @@ internal struct YAMLWriter { try emit(value: entry.key) try emit(value: entry.value) } - emit(type: FYET_MAPPING_END) + try emit(type: FYET_MAPPING_END) } private func emit(sequence: [YAML], style: YAML.CollectionStyle, anchor: String?, tag: YAML.Tag?) throws { - emit( + try emit( type: FYET_SEQUENCE_START, - args: style.nodeStyle(preferred: preferredCollectionStyle).rawValue, + args: style.nodeStyle(preferred: options.preferredCollectionStyle).rawValue, anchor.varArg, (tag?.rawValue).varArg ) try sequence.forEach { element in try emit(value: element) } - emit(type: FYET_SEQUENCE_END) + try emit(type: FYET_SEQUENCE_END) } - private func emit( - scalar: String, - style: fy_scalar_style, - anchor: String?, - tag: String? - ) { - scalar.withCString { scalarPtr in - anchor.withCString { anchorPtr in - tag.withCString { tagPtr in - emit(type: FYET_SCALAR, args: style.rawValue, scalarPtr, FY_NT, anchorPtr, tagPtr) + private func emit(scalar: String, style: fy_scalar_style, anchor: String?, tag: String?) throws { + try scalar.withCString { scalarPtr in + try anchor.withCString { anchorPtr in + try tag.withCString { tagPtr in + try emit(type: FYET_SCALAR, args: style.rawValue, scalarPtr, FY_NT, anchorPtr, tagPtr) } } } } - private func emit( - alias: String - ) { - alias.withCString { aliasPtr in - emit(type: FYET_ALIAS, args: aliasPtr) + private func emit(alias: String) throws { + try alias.withCString { aliasPtr in + try emit(type: FYET_ALIAS, args: aliasPtr) } } - private func emit(type: fy_event_type, args: CVarArg...) { - withVaList(args) { valist in - let event = fy_emit_event_vcreate(emitter, type, valist) - fy_emit_event(emitter, event) + private func emit(type: fy_event_type, args: CVarArg...) throws { + let res = withVaList(args) { valist in + fy_emit_vevent(emitter, type, valist) + } + if res == -1 { + throw error() } } + private func failIfError() throws { + + guard fy_diag_got_error(fy_emitter_get_diag(emitter)) else { + return + } + + throw error() + } + + private func error() -> Error { + if let diag = fy_emitter_get_diag(emitter) { + + var prev: UnsafeMutableRawPointer? + if let error = fy_diag_errors_iterate(diag, &prev) { + + return Error.emitError(message: String(cString: error.pointee.msg)) + } + } + + return .emitError(message: "Unknown emitter error") + } + + private static let emitOutput: Libfyaml.EmitterOutput = { _, _, str, len, userInfo in + + guard let writer = userInfo?.assumingMemoryBound(to: Libfyaml.EmitterWriter.self).pointee else { + fatalError() + } + + guard let str = str else { + writer(nil) + return 0 + } + + let strPtr = UnsafeMutableRawPointer(mutating: str) + writer(String(bytesNoCopy: strPtr, length: Int(len), encoding: .utf8, freeWhenDone: false)) + return len + } } + extension Optional where Wrapped: CVarArg { var varArg: CVarArg { self ?? unsafeBitCast(0, to: OpaquePointer.self) } } - extension Optional where Wrapped == String { func withCString(_ body: (UnsafePointer) throws -> Result) rethrows -> Result { @@ -229,6 +240,7 @@ extension Optional where Wrapped == String { } + extension YAML.CollectionStyle { func nodeStyle(preferred: Self) -> fy_node_style { diff --git a/Tests/YAMLAnyValueTests.swift b/Tests/YAMLAnyValueTests.swift index 01fad6ff2..7d3d32111 100644 --- a/Tests/YAMLAnyValueTests.swift +++ b/Tests/YAMLAnyValueTests.swift @@ -104,7 +104,6 @@ class YAMLAnyValueTests: XCTestCase { let yaml = """ - --- nil: null bool: true string: Hello World! @@ -147,7 +146,6 @@ class YAMLAnyValueTests: XCTestCase { 1: b 4: c 3: d - ... """ diff --git a/Tests/YAMLEncoderTests.swift b/Tests/YAMLEncoderTests.swift index b9ad72ffd..c7b784cef 100644 --- a/Tests/YAMLEncoderTests.swift +++ b/Tests/YAMLEncoderTests.swift @@ -25,9 +25,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- camelCased: Hello World! - ... """ @@ -45,9 +43,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- snake_cased: Hello World! - ... """ @@ -65,9 +61,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- kebab-cased: Hello World! - ... """ @@ -88,10 +82,8 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- int: 5 string: Hello World! - ... """ @@ -109,11 +101,9 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- a: 2 b: 3 c: 1 - ... """ @@ -133,10 +123,8 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- int: 5 string: Hello World! - ... """ @@ -154,11 +142,9 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- a: 2 b: 3 c: 1 - ... """ @@ -184,7 +170,6 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- b: 5 d: Hello World! a: true @@ -196,7 +181,6 @@ class YAMLEncoderTests: XCTestCase { b: 5 d: Hello World! a: true - ... """ @@ -217,9 +201,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- date: \(parsedDate.iso8601EncodedString()) - ... """ @@ -240,9 +222,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- date: \(date.timeIntervalSince1970) - ... """ @@ -263,9 +243,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- date: \(date.timeIntervalSince1970 * 1000.0) - ... """ @@ -286,9 +264,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- date: \(date.timeIntervalSinceReferenceDate) - ... """ @@ -312,9 +288,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- date: \(formatter.string(from: date)) - ... """ @@ -338,9 +312,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- date: \(date.timeIntervalSinceReferenceDate) - ... """ @@ -367,9 +339,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- date: {} - ... """ @@ -392,9 +362,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- data: \(data.base64EncodedString()) - ... """ @@ -415,10 +383,8 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- data: \(data.map { "- \($0)" }.joined(separator: "\n")) - ... """ @@ -439,9 +405,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- data: \(data.hexEncodedString()) - ... """ @@ -465,9 +429,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- data: {} - ... """ @@ -489,9 +451,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- bigInt: \(bigInt) - ... """ @@ -509,9 +469,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- bigInt: \(bigInt) - ... """ @@ -529,9 +487,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- bigUInt: \(bigUInt) - ... """ @@ -549,9 +505,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- dec: \(dec) - ... """ @@ -569,9 +523,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- dec: \(dec) - ... """ @@ -589,9 +541,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- dec: .nan - ... """ @@ -609,9 +559,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- double: \(double) - ... """ @@ -629,9 +577,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- double: \(double) - ... """ @@ -649,9 +595,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- double: .nan - ... """ @@ -669,9 +613,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- double: +.inf - ... """ @@ -689,9 +631,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- double: -.inf - ... """ @@ -709,9 +649,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- float: \(float) - ... """ @@ -729,9 +667,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- float: \(float) - ... """ @@ -749,9 +685,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- float: .nan - ... """ @@ -769,9 +703,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- float: +.inf - ... """ @@ -789,9 +721,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- float: -.inf - ... """ @@ -809,9 +739,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- float: \(float) - ... """ @@ -829,9 +757,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- float: \(float) - ... """ @@ -849,9 +775,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- float: .nan - ... """ @@ -869,9 +793,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- float: +.inf - ... """ @@ -889,9 +811,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- float: -.inf - ... """ @@ -909,9 +829,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- int: \(int) - ... """ @@ -929,9 +847,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- int: \(int) - ... """ @@ -949,9 +865,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- int: \(int) - ... """ @@ -969,9 +883,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- int: \(int) - ... """ @@ -989,9 +901,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- int: \(int) - ... """ @@ -1009,9 +919,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- int: \(int) - ... """ @@ -1029,9 +937,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- int: \(int) - ... """ @@ -1049,9 +955,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- int: \(int) - ... """ @@ -1069,9 +973,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- int: \(int) - ... """ @@ -1089,9 +991,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- int: \(int) - ... """ @@ -1109,9 +1009,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- uint: \(uint) - ... """ @@ -1129,9 +1027,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- uint: \(uint) - ... """ @@ -1149,9 +1045,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- uint: \(uint) - ... """ @@ -1169,9 +1063,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- uint: \(uint) - ... """ @@ -1189,9 +1081,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- uint: \(uint) - ... """ @@ -1209,9 +1099,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- url: \(url.absoluteString) - ... """ @@ -1229,9 +1117,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- uuid: \(uuid.uuidString) - ... """ @@ -1252,9 +1138,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- null: null - ... """ @@ -1268,12 +1152,10 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - --- b: 1 z: 2 n: 3 f: 4 - ... """ diff --git a/Tests/YAMLErrorTests.swift b/Tests/YAMLErrorTests.swift new file mode 100644 index 000000000..e62464621 --- /dev/null +++ b/Tests/YAMLErrorTests.swift @@ -0,0 +1,98 @@ +// +// YAMLErrorTests.swift +// PotentCodables +// +// Copyright © 2019 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation +import PotentYAML +import XCTest + + +/// Tests covering *fixed* errors in libfyaml or its usage. +/// +class YAMLErrorTests: XCTestCase { + public func testSimpleScalarNotOnDocMarkerLinePretty() throws { + + let scalarYaml1: YAML = "simple" + XCTAssertEqual( + try YAMLSerialization.string(from: scalarYaml1, options: [.pretty]), + #""" + --- + simple + ... + + """# + ) + } + + public func testSimpleScalarNotOnDocMarkerLineExplicit() throws { + + let scalarYaml1: YAML = "simple" + XCTAssertEqual( + try YAMLSerialization.string(from: scalarYaml1, options: [.explictDocumentMarkers]), + #""" + --- + simple + ... + + """# + ) + } + + public func testSimpleScalarNoDocMarkers() throws { + + let scalarYaml1: YAML = "simple" + XCTAssertEqual( + try YAMLSerialization.string(from: scalarYaml1, options: []), + #""" + simple + + """# + ) + } + + public func testOutputWhitespaceOnlyString() throws { + + let scalarYaml1: YAML = .string(.init(repeating: " ", count: 1)) + XCTAssertEqual( + try YAMLSerialization.string(from: scalarYaml1), + #""" + " " + + """# + ) + + let scalarYaml2: YAML = .string(.init(repeating: " ", count: 2)) + XCTAssertEqual( + try YAMLSerialization.string(from: scalarYaml2), + #""" + " " + + """# + ) + + let scalarYaml3: YAML = .string(.init(repeating: " ", count: 3)) + XCTAssertEqual( + try YAMLSerialization.string(from: scalarYaml3), + #""" + " " + + """# + ) + + let scalarYaml9: YAML = .string(.init(repeating: " ", count: 9)) + XCTAssertEqual( + try YAMLSerialization.string(from: scalarYaml9), + #""" + " " + + """# + ) + } + +} diff --git a/Tests/YAMLOrderTests.swift b/Tests/YAMLOrderTests.swift index 695fe1e24..8bcca0732 100644 --- a/Tests/YAMLOrderTests.swift +++ b/Tests/YAMLOrderTests.swift @@ -28,11 +28,9 @@ class YAMLOrderTests: XCTestCase { XCTAssertEqual( yaml.description, """ - --- c: 1 a: 2 b: 3 - ... """ ) @@ -49,11 +47,9 @@ class YAMLOrderTests: XCTestCase { XCTAssertEqual( yaml.description, """ - --- a: 2 b: 3 c: 1 - ... """ ) @@ -93,11 +89,9 @@ class YAMLOrderTests: XCTestCase { XCTAssertEqual( yaml.description, """ - --- c: 1 a: 2 b: 3 - ... """ ) diff --git a/Tests/YAMLTests.swift b/Tests/YAMLTests.swift index 2815c863e..916c5d51a 100644 --- a/Tests/YAMLTests.swift +++ b/Tests/YAMLTests.swift @@ -165,11 +165,9 @@ class YAMLTests: XCTestCase { XCTAssertEqual( yaml, """ - --- c: 1 a: 2 b: 3 - ... """ ) @@ -186,11 +184,9 @@ class YAMLTests: XCTestCase { XCTAssertEqual( yaml, """ - --- a: 2 b: 3 c: 1 - ... """ ) @@ -214,7 +210,7 @@ class YAMLTests: XCTestCase { """ - XCTAssertEqual(object, try YAMLSerialization.yaml(from: yaml.data(using: .utf8)!)) + XCTAssertEqual(object, try YAMLSerialization.yaml(from: yaml)) } func testDescriptionOrder() throws { @@ -228,11 +224,9 @@ class YAMLTests: XCTestCase { XCTAssertEqual( yaml.description, """ - --- c: 1 a: 2 b: 3 - ... """ ) @@ -280,9 +274,7 @@ class YAMLTests: XCTestCase { XCTAssertEqual( try YAMLSerialization.string(from: ["plain": "This should be output plain"]), #""" - --- plain: This should be output plain - ... """# ) @@ -291,9 +283,7 @@ class YAMLTests: XCTestCase { try YAMLSerialization.string(from: ["dquoted": "This should be output dquoted"], preferredStringStyle: .doubleQuoted), #""" - --- "dquoted": "This should be output dquoted" - ... """# ) @@ -302,9 +292,7 @@ class YAMLTests: XCTestCase { try YAMLSerialization.string(from: ["squoted": "This should be output squoted"], preferredStringStyle: .singleQuoted), #""" - --- 'squoted': 'This should be output squoted' - ... """# ) @@ -313,9 +301,7 @@ class YAMLTests: XCTestCase { try YAMLSerialization.string(from: [.string("plain", style: .plain): "This should be output squoted"], preferredStringStyle: .singleQuoted), #""" - --- plain: 'This should be output squoted' - ... """# ) @@ -327,9 +313,7 @@ class YAMLTests: XCTestCase { XCTAssertEqual( try YAMLSerialization.string(from: ["block": "This should be output block"]), #""" - --- block: This should be output block - ... """# ) @@ -338,11 +322,9 @@ class YAMLTests: XCTestCase { try YAMLSerialization.string(from: ["flow": "This should be output flow"], preferredCollectionStyle: .flow), #""" - --- { 'flow': 'This should be output flow' } - ... """# ) @@ -352,9 +334,7 @@ class YAMLTests: XCTestCase { .string(from: .mapping([.init(key: "block", value: "This should be output block")], style: .block), preferredCollectionStyle: .flow), #""" - --- block: This should be output block - ... """# ) diff --git a/Tests/YAMLWriterTests.swift b/Tests/YAMLWriterTests.swift index 2d2d5c06e..e42b7b1bf 100644 --- a/Tests/YAMLWriterTests.swift +++ b/Tests/YAMLWriterTests.swift @@ -15,33 +15,200 @@ import XCTest class YAMLWriterTests: XCTestCase { - func testWriteMultipleDocuments() throws { + func testWriteScalarCompact() throws { + + let options = YAMLWriter.Options(pretty: false) + + XCTAssertEqual( + try YAMLWriter.write(["simple"], options: options), + """ + simple + + """ + ) + + } + + func testWriteScalarExplicitDocumentMarkers() throws { - var output = "" - try YAMLWriter.write([.bool(true), .integer(123)]) { output += $0 ?? "" } + let options = YAMLWriter.Options(pretty: false, explicitDocumentMarkers: true) XCTAssertEqual( - output, + try YAMLWriter.write(["simple"], options: options), """ - --- true + --- + simple ... - --- 123 + + """ + ) + + } + + func testWriteScalarPretty() throws { + + let options = YAMLWriter.Options(pretty: true) + + XCTAssertEqual( + try YAMLWriter.write(["simple"], options: options), + """ + --- + simple ... """ ) + } - func testWriteAliases() throws { + func testWriteStringPreferPlain() throws { + + let options = YAMLWriter.Options(preferredStringStyle: .plain, pretty: false) + + XCTAssertEqual( + try YAMLWriter.write(["simple\nstring"], options: options), + """ + simple + + string + + """ + ) + + } + + func testWriteStringPreferSingleQuoted() throws { + + let options = YAMLWriter.Options(preferredStringStyle: .singleQuoted, pretty: false) + + XCTAssertEqual( + try YAMLWriter.write(["simple\nstring"], options: options), + """ + 'simple + + string' + + """ + ) + + } + + func testWriteStringPreferDoubleQuoted() throws { + + let options = YAMLWriter.Options(preferredStringStyle: .doubleQuoted, pretty: false) + + XCTAssertEqual( + try YAMLWriter.write(["simple\nstring"], options: options), + """ + "simple\\nstring" + + """ + ) + + } + + func testWriteStringPreferLiteral() throws { + + let options = YAMLWriter.Options(preferredStringStyle: .literal, pretty: false) + + XCTAssertEqual( + try YAMLWriter.write(["simple\nstring"], options: options), + """ + |- + simple + string + + """ + ) + + } + + func testWriteStringPreferFolded() throws { + + let options = YAMLWriter.Options(preferredStringStyle: .folded, pretty: false) + + XCTAssertEqual( + try YAMLWriter.write(["simple\nstring"], options: options), + """ + >- + simple + \ + + string + + """ + ) + + } + + func testWriteWidthNormal() throws { + + let yamlVal: YAML = + "Long Text 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 1234567890" + + let options = YAMLWriter.Options(width: .normal) + let yamlStr = try YAMLWriter.write([yamlVal], options: options) XCTAssertEqual( - try YAMLSerialization.string(from: .alias("num")), + yamlStr, """ - --- *num + --- + Long Text 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 + 1234567890 ... """ ) + + XCTAssertEqual(try YAMLReader.read(data: Data(yamlStr.utf8)).first, yamlVal) + } + + func testWriteWidthNormalInBlock() throws { + + let yamlVal: YAML = [ + "a": "Long Text 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 1234567890" + ] + + let options = YAMLWriter.Options(preferredCollectionStyle: .block, width: .normal) + let yamlStr = try YAMLWriter.write([yamlVal], options: options) + + XCTAssertEqual( + yamlStr, + """ + a: Long Text 123456789 123456789 123456789 123456789 123456789 123456789 123456789 + 123456789 1234567890 + + """ + ) + + XCTAssertEqual(try YAMLReader.read(data: Data(yamlStr.utf8)).first, yamlVal) + } + + func testWriteMultipleDocuments() throws { + + XCTAssertEqual( + try YAMLWriter.write([.bool(true), .integer(123)]), + """ + --- + true + ... + --- + 123 + ... + + """ + ) + } + + func testWriteAliases() throws { + + XCTAssertEqual( + try YAMLWriter.write([.alias("num")]), + """ + *num + + """ + ) } }