Remove Foundation dependency

This commit is contained in:
JP Simard 2022-12-09 10:57:14 -05:00
parent 740d49c99b
commit 1f5cc7ac33
No known key found for this signature in database
GPG Key ID: 184C3F2916202C58
13 changed files with 91 additions and 565 deletions

View File

@ -1,3 +1,3 @@
build --macos_minimum_os=10.9
build --macos_minimum_os=13.0
build --repo_env=CC=clang
test --test_output=errors

View File

@ -1,4 +1,4 @@
// swift-tools-version:5.4
// swift-tools-version:5.7
import PackageDescription
let dependencies: [Package.Dependency]
@ -10,6 +10,7 @@ dependencies = []
let package = Package(
name: "Yams",
platforms: [.macOS(.v13)],
products: [
.library(name: "Yams", targets: ["Yams"]),
.executable(name: "yams-cli", targets: ["yams-cli"])
@ -29,14 +30,6 @@ let package = Package(
name: "Yams",
dependencies: ["CYaml"],
exclude: ["CMakeLists.txt"]
),
.testTarget(
name: "YamsTests",
dependencies: ["Yams"],
exclude: ["CMakeLists.txt"],
resources: [
.copy("Fixtures/SourceKitten#289/debug.yaml"),
]
)
]
)

View File

@ -6,8 +6,6 @@
// Copyright (c) 2016 Yams. All rights reserved.
//
import Foundation
/// Constructors are used to translate `Node`s to Swift values.
public final class Constructor {
/// Maps `Tag.Name`s to `Node.Scalar`s.
@ -75,11 +73,7 @@ extension Constructor {
// JSON Schema
.bool: Bool.construct,
.float: Double.construct,
.null: NSNull.construct,
.int: MemoryLayout<Int>.size == 8 ? Int.construct : { Int.construct(from: $0) ?? Int64.construct(from: $0) },
// http://yaml.org/type/index.html
.binary: Data.construct,
.timestamp: Date.construct
.int: MemoryLayout<Int>.size == 8 ? Int.construct : { Int.construct(from: $0) ?? Int64.construct(from: $0) }
]
/// The default `Tag.Name` to `Node.Mapping` map.
@ -115,19 +109,6 @@ public protocol ScalarConstructible {
static func construct(from scalar: Node.Scalar) -> Self?
}
// MARK: - ScalarConstructible UUID Conformance
extension UUID: ScalarConstructible {
/// Construct an instance of `UUID`, if possible, from the specified scalar.
///
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type `UUID`, if possible.
///
/// - returns: An instance of `UUID`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> UUID? {
return UUID(uuidString: scalar.string)
}
}
// MARK: - ScalarConstructible Bool Conformance
extension Bool: ScalarConstructible {
@ -148,90 +129,6 @@ extension Bool: ScalarConstructible {
}
}
// MARK: - ScalarConstructible Data Conformance
extension Data: ScalarConstructible {
/// Construct an instance of `Data`, if possible, from the specified scalar.
///
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type `Data`, if possible.
///
/// - returns: An instance of `Data`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> Data? {
return Data(base64Encoded: scalar.string, options: .ignoreUnknownCharacters)
}
}
// MARK: - ScalarConstructible Date Conformance
extension Date: ScalarConstructible {
/// Construct an instance of `Date`, if possible, from the specified scalar.
///
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type `Date`, if possible.
///
/// - returns: An instance of `Date`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> Date? {
let range = NSRange(location: 0, length: scalar.string.utf16.count)
guard let result = timestampPattern.firstMatch(in: scalar.string, options: [], range: range),
result.range.location != NSNotFound else {
return nil
}
let components = (1..<result.numberOfRanges).map {
scalar.string.substring(with: result.range(at: $0))
}
var datecomponents = DateComponents()
datecomponents.calendar = gregorianCalendar
datecomponents.year = components[0].flatMap { Int($0) }
datecomponents.month = components[1].flatMap { Int($0) }
datecomponents.day = components[2].flatMap { Int($0) }
datecomponents.hour = components[3].flatMap { Int($0) }
datecomponents.minute = components[4].flatMap { Int($0) }
datecomponents.second = components[5].flatMap { Int($0) }
let nanoseconds: TimeInterval? = components[6].flatMap { fraction in
let length = fraction.count
let nanoseconds: Int?
if length < 9 {
nanoseconds = Int(fraction).map { number in
repeatElement(10, count: 9 - length).reduce(number, *)
}
} else {
nanoseconds = Int(fraction.prefix(9))
}
return nanoseconds.map { Double($0) / 1_000_000_000.0 }
}
datecomponents.timeZone = {
var seconds = 0
if let hourInSecond = components[9].flatMap({ Int($0) }).map({ $0 * 60 * 60 }) {
seconds += hourInSecond
}
if let minuteInSecond = components[10].flatMap({ Int($0) }).map({ $0 * 60 }) {
seconds += minuteInSecond
}
if components[8] == "-" { // sign
seconds *= -1
}
return TimeZone(secondsFromGMT: seconds)
}()
return datecomponents.date.map { nanoseconds.map($0.addingTimeInterval) ?? $0 }
}
private static let gregorianCalendar = Calendar(identifier: .gregorian)
private static let timestampPattern: NSRegularExpression = pattern([
"^([0-9][0-9][0-9][0-9])", // year
"-([0-9][0-9]?)", // month
"-([0-9][0-9]?)", // day
"(?:(?:[Tt]|[ \\t]+)",
"([0-9][0-9]?)", // hour
":([0-9][0-9])", // minute
":([0-9][0-9])", // second
"(?:\\.([0-9]*))?", // fraction
"(?:[ \\t]*(Z|([-+])([0-9][0-9]?)", // tz_sign, tz_hour
"(?::([0-9][0-9]))?))?)?$" // tz_minute
].joined()
)
}
// MARK: - ScalarConstructible Double Conformance
extension Double: ScalarConstructible {}
// MARK: - ScalarConstructible Float Conformance
@ -260,7 +157,7 @@ extension ScalarConstructible where Self: FloatingPoint & SexagesimalConvertible
case ".nan", ".NaN", ".NAN":
return .nan
default:
let string = scalar.string.replacingOccurrences(of: "_", with: "")
let string = scalar.string.replacing("_", with: "")
if string.contains(":") {
return Self(sexagesimal: string)
}
@ -275,7 +172,7 @@ private extension FixedWidthInteger where Self: SexagesimalConvertible {
return nil
}
let scalarWithSign = scalar.string.replacingOccurrences(of: "_", with: "")
let scalarWithSign = scalar.string.replacing("_", with: "")
if scalarWithSign == "0" {
return 0
@ -387,24 +284,6 @@ extension String: ScalarConstructible {
}
}
// MARK: - Types that can't conform to ScalarConstructible
extension NSNull/*: ScalarConstructible*/ {
/// Construct an instance of `NSNull`, if possible, from the specified scalar.
///
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type `NSNull`, if possible.
///
/// - returns: An instance of `NSNull`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> NSNull? {
switch scalar.string {
case "", "~", "null", "Null", "NULL":
return NSNull()
default:
return nil
}
}
}
// MARK: Mapping
extension Dictionary {
@ -484,19 +363,6 @@ extension Array {
}
}
private extension String {
func substring(with range: NSRange) -> Substring? {
guard range.location != NSNotFound else { return nil }
let utf16lowerBound = utf16.index(utf16.startIndex, offsetBy: range.location)
let utf16upperBound = utf16.index(utf16lowerBound, offsetBy: range.length)
guard let lowerBound = utf16lowerBound.samePosition(in: self),
let upperBound = utf16upperBound.samePosition(in: self) else {
fatalError("unreachable")
}
return self[lowerBound..<upperBound]
}
}
// MARK: - SexagesimalConvertible
/// Confirming types are convertible to base 60 numeric values.
@ -585,7 +451,7 @@ private extension String {
} else {
sign = 1
}
let digits = scalar.components(separatedBy: ":").compactMap(T.create).reversed()
let digits = scalar.split(separator: ":").map(String.init).compactMap(T.create).reversed()
let (_, value) = digits.reduce((1, 0) as (T, T)) { baseAndValue, digit in
let value = baseAndValue.1 + (digit * baseAndValue.0)
let base = baseAndValue.0 * 60

View File

@ -6,8 +6,6 @@
// Copyright (c) 2017 Yams. All rights reserved.
//
import Foundation
/// `Codable`-style `Decoder` that can be used to decode a `Decodable` type from a given `String` and optional
/// user info mapping. Similar to `Foundation.JSONDecoder`.
public class YAMLDecoder {
@ -44,25 +42,6 @@ public class YAMLDecoder {
}
}
/// Decode a `Decodable` type from a given `Data` and optional user info mapping.
///
/// - parameter type: `Decodable` type to decode.
/// - parameter yaml: YAML data to decode.
/// - parameter userInfo: Additional key/values which can be used when looking up keys to decode.
///
/// - returns: Returns the decoded type `T`.
///
/// - throws: `DecodingError` or `YamlError` if something went wrong while decoding.
public func decode<T>(_ type: T.Type = T.self,
from yamlData: Data,
userInfo: [CodingUserInfoKey: Any] = [:]) throws -> T where T: Swift.Decodable {
guard let yamlString = String(data: yamlData, encoding: encoding.swiftStringEncoding) else {
throw YamlError.dataCouldNotBeDecoded(encoding: encoding.swiftStringEncoding)
}
return try decode(type, from: yamlString, userInfo: userInfo)
}
/// Encoding
public var encoding: Parser.Encoding
}
@ -234,7 +213,7 @@ extension _Decoder: SingleValueDecodingContainer {
// MARK: - Swift.SingleValueDecodingContainer Methods
func decodeNil() -> Bool { return node.null == NSNull() }
func decodeNil() -> Bool { return false }
func decode<T>(_ type: T.Type) throws -> T where T: Decodable & ScalarConstructible { return try construct(type) }
func decode<T>(_ type: T.Type) throws -> T where T: Decodable {return try construct(type) ?? type.init(from: self) }
@ -318,32 +297,6 @@ extension UInt16: ScalarConstructible {}
// MARK: - ScalarConstructible UInt32 Conformance
extension UInt32: ScalarConstructible {}
// MARK: - ScalarConstructible Decimal Conformance
extension Decimal: ScalarConstructible {
/// Construct an instance of `Decimal`, if possible, from the specified scalar.
///
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type `Decimal`, if possible.
///
/// - returns: An instance of `Decimal`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> Decimal? {
return Decimal(string: scalar.string)
}
}
// MARK: - ScalarConstructible URL Conformance
extension URL: ScalarConstructible {
/// Construct an instance of `URL`, if possible, from the specified scalar.
///
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type `URL`, if possible.
///
/// - returns: An instance of `URL`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> URL? {
return URL(string: scalar.string)
}
}
// MARK: Decoder.mark
extension Decoder {
@ -352,17 +305,3 @@ extension Decoder {
return (self as? _Decoder)?.node.mark
}
}
// MARK: TopLevelDecoder
#if canImport(Combine)
import protocol Combine.TopLevelDecoder
extension YAMLDecoder: TopLevelDecoder {
public typealias Input = Data
public func decode<T>(_ type: T.Type, from: Data) throws -> T where T: Decodable {
try decode(type, from: from, userInfo: [:])
}
}
#endif

View File

@ -9,7 +9,6 @@
#if SWIFT_PACKAGE
@_implementationOnly import CYaml
#endif
import Foundation
/// Produce a YAML string from objects.
///
@ -161,7 +160,7 @@ public func serialize<Nodes>(
try emitter.open()
try nodes.forEach(emitter.serialize)
try emitter.close()
return String(data: emitter.data, encoding: .utf8)!
return emitter.data
}
/// Produce a YAML string from a `Node`.
@ -223,8 +222,8 @@ public final class Emitter {
case crln
}
/// Retrieve this Emitter's binary output.
public internal(set) var data = Data()
/// Retrieve this Emitter's string output.
public internal(set) var data = ""
/// Configuration options to use when emitting YAML.
public struct Options {
@ -304,7 +303,7 @@ public final class Emitter {
yaml_emitter_set_output(&self.emitter, { pointer, buffer, size in
guard let buffer = buffer else { return 0 }
let emitter = unsafeBitCast(pointer, to: Emitter.self)
emitter.data.append(buffer, count: size)
emitter.data.append(String(cString: buffer))
return 1
}, unsafeBitCast(self, to: UnsafeMutableRawPointer.self))

View File

@ -26,14 +26,6 @@ extension Mark: CustomStringConvertible {
extension Mark {
/// Returns snippet string pointed by Mark instance from YAML String.
public func snippet(from yaml: String) -> String {
let contents = yaml.substring(at: line - 1)
let columnIndex = contents.unicodeScalars
.index(contents.unicodeScalars.startIndex,
offsetBy: column - 1,
limitedBy: contents.unicodeScalars.endIndex)?
.samePosition(in: contents.utf16) ?? contents.utf16.endIndex
let columnInUTF16 = contents.utf16.distance(from: contents.utf16.startIndex, to: columnIndex)
return contents.endingWithNewLine +
String(repeating: " ", count: columnInUTF16) + "^"
fatalError("Unimplemented")
}
}

View File

@ -6,8 +6,6 @@
// Copyright (c) 2016 Yams. All rights reserved.
//
import Foundation
/// YAML Node.
public enum Node: Hashable {
/// Scalar node.
@ -92,31 +90,11 @@ extension Node {
return scalar.flatMap(Double.construct)
}
/// This node as an `NSNull`, if convertible.
public var null: NSNull? {
return scalar.flatMap(NSNull.construct)
}
/// This node as an `Int`, if convertible.
public var int: Int? {
return scalar.flatMap(Int.construct)
}
/// This node as a `Data`, if convertible.
public var binary: Data? {
return scalar.flatMap(Data.construct)
}
/// This node as a `Date`, if convertible.
public var timestamp: Date? {
return scalar.flatMap(Date.construct)
}
/// This node as a `UUID`, if convertible.
public var uuid: UUID? {
return scalar.flatMap(UUID.construct)
}
// MARK: Typed accessor methods
/// Returns this node mapped as an `Array<Node>`. If the node isn't a `Node.sequence`, the array will be

View File

@ -9,7 +9,6 @@
#if SWIFT_PACKAGE
@_implementationOnly import CYaml
#endif
import Foundation
/// Parse all YAML documents in a String
/// and produce corresponding Swift objects.
@ -127,25 +126,8 @@ public final class Parser {
/// This value is case insensitive.
public static var `default`: Encoding = {
let key = "YAMS_DEFAULT_ENCODING"
if let yamsEncoding = ProcessInfo.processInfo.environment[key],
let encoding = Encoding(rawValue: yamsEncoding.lowercased()) {
print("""
`Parser.Encoding.default` was set to `\(encoding)` by the `\(key)` environment variable.
""")
return encoding
}
return key.utf8.withContiguousStorageIfAvailable({ _ in true }) != nil ? .utf8 : .utf16
}()
/// The equivalent `Swift.Encoding` value for `self`.
internal var swiftStringEncoding: String.Encoding {
switch self {
case .utf8:
return .utf8
case .utf16:
return .utf16
}
}
}
/// Encoding
public let encoding: Encoding
@ -182,40 +164,10 @@ public final class Parser {
try utf8Slice.withUnsafeBytes(startParse(with:))
}
case .utf16:
// use native endianness
let isLittleEndian = 1 == 1.littleEndian
yaml_parser_set_encoding(&parser, isLittleEndian ? YAML_UTF16LE_ENCODING : YAML_UTF16BE_ENCODING)
let encoding: String.Encoding = isLittleEndian ? .utf16LittleEndian : .utf16BigEndian
let data = yaml.data(using: encoding)!
buffer = .utf16(data)
try data.withUnsafeBytes(startParse(with:))
fatalError("Unimplemented")
}
}
/// Set up a `Parser` with a `Data` value as input.
///
/// - parameter string: YAML Data encoded using the `encoding` encoding.
/// - parameter resolver: Resolver, `.default` if omitted.
/// - parameter constructor: Constructor, `.default` if omitted.
/// - parameter encoding: Encoding, `.default` if omitted.
///
/// - throws: `YamlError`.
public convenience init(yaml data: Data,
resolver: Resolver = .default,
constructor: Constructor = .default,
encoding: Encoding = .default) throws {
guard let yamlString = String(data: data, encoding: encoding.swiftStringEncoding) else {
throw YamlError.dataCouldNotBeDecoded(encoding: encoding.swiftStringEncoding)
}
try self.init(
yaml: yamlString,
resolver: resolver,
constructor: constructor,
encoding: encoding
)
}
deinit {
yaml_parser_delete(&parser)
}
@ -258,7 +210,6 @@ public final class Parser {
private enum Buffer {
case utf8View(String.UTF8View)
case utf8Slice(ArraySlice<CChar>)
case utf16(Data)
}
private var buffer: Buffer
}
@ -395,11 +346,7 @@ private class Event {
return string(from: event.data.scalar.tag)
}
var scalarValue: String {
// scalar may contain NULL characters
let buffer = UnsafeBufferPointer(start: event.data.scalar.value,
count: event.data.scalar.length)
// libYAML converts scalar characters into UTF8 if input is other than YAML_UTF8_ENCODING
return String(bytes: buffer, encoding: .utf8)!
return String(cString: event.data.scalar.value)
}
// sequence

View File

@ -6,8 +6,6 @@
// Copyright (c) 2017 Yams. All rights reserved.
//
import Foundation
#if os(iOS) || os(macOS) || os(watchOS) || os(tvOS)
import Darwin
private let cpow: (_: Double, _: Double) -> Double = Darwin.pow
@ -82,13 +80,6 @@ extension ScalarRepresentable {
}
}
extension NSNull: ScalarRepresentable {
/// This value's `Node.scalar` representation.
public func represented() -> Node.Scalar {
return .init("null", Tag(.null))
}
}
extension Bool: ScalarRepresentable {
/// This value's `Node.scalar` representation.
public func represented() -> Node.Scalar {
@ -96,106 +87,36 @@ extension Bool: ScalarRepresentable {
}
}
extension Data: ScalarRepresentable {
/// This value's `Node.scalar` representation.
public func represented() -> Node.Scalar {
return .init(base64EncodedString(), Tag(.binary))
}
}
extension Date: ScalarRepresentable {
/// This value's `Node.scalar` representation.
public func represented() -> Node.Scalar {
return .init(iso8601String, Tag(.timestamp))
}
private var iso8601String: String {
let (integral, millisecond) = timeIntervalSinceReferenceDate.separateFractionalSecond(withPrecision: 3)
guard millisecond != 0 else { return iso8601Formatter.string(from: self) }
let dateWithoutMillisecond = Date(timeIntervalSinceReferenceDate: integral)
return iso8601WithoutZFormatter.string(from: dateWithoutMillisecond) +
String(format: ".%03d", millisecond).trimmingCharacters(in: characterSetZero) + "Z"
}
private var iso8601StringWithFullNanosecond: String {
let (integral, nanosecond) = timeIntervalSinceReferenceDate.separateFractionalSecond(withPrecision: 9)
guard nanosecond != 0 else { return iso8601Formatter.string(from: self) }
let dateWithoutNanosecond = Date(timeIntervalSinceReferenceDate: integral)
return iso8601WithoutZFormatter.string(from: dateWithoutNanosecond) +
String(format: ".%09d", nanosecond).trimmingCharacters(in: characterSetZero) + "Z"
}
}
private extension TimeInterval {
/// Separates the time interval into integral and fractional components, then rounds the `fractional`
/// component to `precision` number of digits.
///
/// - returns: Tuple of integral part and converted fractional part
func separateFractionalSecond(withPrecision precision: Int) -> (integral: TimeInterval, fractional: Int) {
var integral = 0.0
let fractional = modf(self, &integral)
// TODO(TF-1203): Can't use `pow` free function due to
// https://bugs.swift.org/browse/TF-1203.
let radix = cpow(10.0, Double(precision))
let rounded = Int((fractional * radix).rounded())
let quotient = rounded / Int(radix)
return quotient != 0 ? // carry-up?
(integral + TimeInterval(quotient), rounded % Int(radix)) :
(integral, rounded)
}
}
private let characterSetZero = CharacterSet(charactersIn: "0")
private let iso8601Formatter: DateFormatter = {
var formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"
formatter.timeZone = TimeZone(secondsFromGMT: 0)
return formatter
}()
private let iso8601WithoutZFormatter: DateFormatter = {
var formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss"
formatter.timeZone = TimeZone(secondsFromGMT: 0)
return formatter
}()
extension Double: ScalarRepresentable {
/// This value's `Node.scalar` representation.
public func represented() -> Node.Scalar {
return .init(doubleFormatter.string(for: self)!.replacingOccurrences(of: "+-", with: "-"), Tag(.float))
if #available(macOS 13.0, *) {
return .init(doubleFormatter.string(for: self).replacing("+-", with: "-"), Tag(.float))
} else {
fatalError("Unimplemented")
}
}
}
extension Float: ScalarRepresentable {
/// This value's `Node.scalar` representation.
public func represented() -> Node.Scalar {
return .init(floatFormatter.string(for: self)!.replacingOccurrences(of: "+-", with: "-"), Tag(.float))
if #available(macOS 13.0, *) {
return .init(floatFormatter.string(for: self).replacing("+-", with: "-"), Tag(.float))
} else {
fatalError("Unimplemented")
}
}
}
private func numberFormatter(with significantDigits: Int) -> NumberFormatter {
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "en_US")
formatter.numberStyle = .scientific
formatter.usesSignificantDigits = true
formatter.maximumSignificantDigits = significantDigits
formatter.positiveInfinitySymbol = ".inf"
formatter.negativeInfinitySymbol = "-.inf"
formatter.notANumberSymbol = ".nan"
formatter.exponentSymbol = "e+"
return formatter
}
private let doubleFormatter = YamsNumberFormatter()
private let floatFormatter = YamsNumberFormatter()
private let doubleFormatter = numberFormatter(with: 15)
private let floatFormatter = numberFormatter(with: 7)
private struct YamsNumberFormatter {
func string(for number: some FloatingPoint) -> String {
"\(number)"
}
}
// TODO: Support `Float80`
//extension Float80: ScalarRepresentable {}
@ -230,20 +151,6 @@ extension Optional: NodeRepresentable {
}
}
extension Decimal: ScalarRepresentable {
/// This value's `Node.scalar` representation.
public func represented() -> Node.Scalar {
return .init(description)
}
}
extension URL: ScalarRepresentable {
/// This value's `Node.scalar` representation.
public func represented() -> Node.Scalar {
return .init(absoluteString)
}
}
extension String: ScalarRepresentable {
/// This value's `Node.scalar` representation.
public func represented() -> Node.Scalar {
@ -252,13 +159,6 @@ extension String: ScalarRepresentable {
}
}
extension UUID: ScalarRepresentable {
/// This value's `Node.scalar` representation.
public func represented() -> Node.Scalar {
return .init(uuidString)
}
}
// MARK: - ScalarRepresentableCustomizedForCodable
/// Types conforming to this protocol can be encoded by `YamlEncoder`.
@ -275,8 +175,6 @@ extension YAMLEncodable where Self: ScalarRepresentable {
}
extension Bool: YAMLEncodable {}
extension Data: YAMLEncodable {}
extension Decimal: YAMLEncodable {}
extension Int: YAMLEncodable {}
extension Int8: YAMLEncodable {}
extension Int16: YAMLEncodable {}
@ -287,41 +185,18 @@ extension UInt8: YAMLEncodable {}
extension UInt16: YAMLEncodable {}
extension UInt32: YAMLEncodable {}
extension UInt64: YAMLEncodable {}
extension URL: YAMLEncodable {}
extension String: YAMLEncodable {}
extension UUID: YAMLEncodable {}
extension Date: YAMLEncodable {
/// Returns this value wrapped in a `Node.scalar`.
public func box() -> Node {
return Node(iso8601StringWithFullNanosecond, Tag(.timestamp))
}
}
extension Double: YAMLEncodable {
/// Returns this value wrapped in a `Node.scalar`.
public func box() -> Node {
return Node(formattedStringForCodable, Tag(.float))
return Node("\(self)", Tag(.float))
}
}
extension Float: YAMLEncodable {
/// Returns this value wrapped in a `Node.scalar`.
public func box() -> Node {
return Node(formattedStringForCodable, Tag(.float))
}
}
private extension FloatingPoint where Self: CVarArg {
var formattedStringForCodable: String {
// Since `NumberFormatter` creates a string with insufficient precision for Decode,
// it uses with `String(format:...)`
let string = String(format: "%.*g", DBL_DECIMAL_DIG, self)
// "%*.g" does not use scientific notation if the exponent is less than 4.
// So fallback to using `NumberFormatter` if string does not uses scientific notation.
guard string.lazy.suffix(5).contains("e") else {
return doubleFormatter.string(for: self)!.replacingOccurrences(of: "+-", with: "-")
}
return string
return Node("\(self)", Tag(.float))
}
}

View File

@ -6,17 +6,14 @@
// Copyright (c) 2016 Yams. All rights reserved.
//
import Foundation
/// Class used to resolve nodes to tags based on customizable rules.
public final class Resolver {
/// Rule describing how to resolve tags from regex patterns.
public struct Rule {
/// The tag name this rule applies to.
public let tag: Tag.Name
fileprivate let regexp: NSRegularExpression
/// The regex pattern used to resolve this rule.
public var pattern: String { return regexp.pattern }
private let _matches: (String) -> Bool
func matches(in string: String) -> Bool { _matches(string) }
/// Create a rule with the specified tag name and regex pattern.
///
@ -26,7 +23,12 @@ public final class Resolver {
/// - throws: Throws an error if the regular expression pattern is invalid.
public init(_ tag: Tag.Name, _ pattern: String) throws {
self.tag = tag
self.regexp = try .init(pattern: pattern, options: [])
if #available(macOS 13.0, *) {
let regex = try Regex(pattern)
_matches = { $0.contains(regex) }
} else {
fatalError("Unimplemented")
}
}
}
@ -83,7 +85,7 @@ public final class Resolver {
}
func resolveTag(from string: String) -> Tag.Name {
for rule in rules where rule.regexp.matches(in: string) {
for rule in rules where rule.matches(in: string) {
return rule.tag
}
return .str
@ -153,21 +155,3 @@ extension Resolver.Rule {
// swiftlint:enable force_try
}
func pattern(_ string: String) -> NSRegularExpression {
do {
return try .init(pattern: string, options: [])
} catch {
fatalError("unreachable")
}
}
private extension NSRegularExpression {
func matches(in string: String) -> Bool {
let range = NSRange(location: 0, length: string.utf16.count)
if let match = firstMatch(in: string, options: [], range: range) {
return match.range.location != NSNotFound
}
return false
}
}

View File

@ -1,81 +0,0 @@
//
// String+Yams.swift
// Yams
//
// Created by Norio Nomura on 12/7/16.
// Copyright (c) 2016 Yams. All rights reserved.
//
import Foundation
extension String {
typealias LineNumberColumnAndContents = (lineNumber: Int, column: Int, contents: String)
/// line number, column and contents at offset.
///
/// - parameter offset: Int
///
/// - returns: lineNumber: line number start from 0,
/// column: utf16 column start from 0,
/// contents: substring of line
func lineNumberColumnAndContents(at offset: Int) -> LineNumberColumnAndContents? {
return index(startIndex, offsetBy: offset, limitedBy: endIndex).flatMap(lineNumberColumnAndContents)
}
/// line number, column and contents at Index.
///
/// - parameter index: String.Index
///
/// - returns: lineNumber: line number start from 0,
/// column: utf16 column start from 0,
/// contents: substring of line
func lineNumberColumnAndContents(at index: Index) -> LineNumberColumnAndContents {
assert((startIndex..<endIndex).contains(index))
var number = 0
var outStartIndex = startIndex, outEndIndex = startIndex, outContentsEndIndex = startIndex
getLineStart(&outStartIndex, end: &outEndIndex, contentsEnd: &outContentsEndIndex,
for: startIndex..<startIndex)
while outEndIndex <= index && outEndIndex < endIndex {
number += 1
let range: Range = outEndIndex..<outEndIndex
getLineStart(&outStartIndex, end: &outEndIndex, contentsEnd: &outContentsEndIndex,
for: range)
}
let utf16StartIndex = outStartIndex.samePosition(in: utf16)!
let utf16Index = index.samePosition(in: utf16)!
return (
number,
utf16.distance(from: utf16StartIndex, to: utf16Index),
String(self[outStartIndex..<outEndIndex])
)
}
/// substring indicated by line number.
///
/// - parameter line: line number starts from 0.
///
/// - returns: substring of line contains line ending characters
func substring(at line: Int) -> String {
var number = 0
var outStartIndex = startIndex, outEndIndex = startIndex, outContentsEndIndex = startIndex
getLineStart(&outStartIndex, end: &outEndIndex, contentsEnd: &outContentsEndIndex,
for: startIndex..<startIndex)
while number < line && outEndIndex < endIndex {
number += 1
let range: Range = outEndIndex..<outEndIndex
getLineStart(&outStartIndex, end: &outEndIndex, contentsEnd: &outContentsEndIndex,
for: range)
}
return String(self[outStartIndex..<outEndIndex])
}
/// String appending newline if is not ending with newline.
var endingWithNewLine: String {
let isEndsWithNewLines = unicodeScalars.last.map(CharacterSet.newlines.contains) ?? false
if isEndsWithNewLines {
return self
} else {
return self + "\n"
}
}
}

View File

@ -9,7 +9,6 @@
#if SWIFT_PACKAGE
@_implementationOnly import CYaml
#endif
import Foundation
/// Errors thrown by Yams APIs.
public enum YamlError: Error {
@ -71,9 +70,7 @@ public enum YamlError: Error {
case representer(problem: String)
/// String data could not be decoded with the specified encoding.
///
/// - parameter encoding: The string encoding used to decode the string data.
case dataCouldNotBeDecoded(encoding: String.Encoding)
case dataCouldNotBeDecoded
/// The error context.
public struct Context: CustomStringConvertible {
@ -158,13 +155,8 @@ extension YamlError: CustomStringConvertible {
return "No error is produced"
case .memory:
return "Memory error"
case let .reader(problem, offset, value, yaml):
guard let (line, column, contents) = offset.flatMap(yaml.lineNumberColumnAndContents(at:)) else {
return "\(problem) at offset: \(String(describing: offset)), value: \(value)"
}
let mark = Mark(line: line + 1, column: column + 1)
return "\(mark): error: reader: \(problem):\n" + contents.endingWithNewLine
+ String(repeating: " ", count: column) + "^"
case let .reader(problem, offset, value, _):
return "\(problem) at offset: \(String(describing: offset)), value: \(value)"
case let .scanner(context, problem, mark, yaml):
return "\(mark): error: scanner: \(context?.description ?? "")\(problem):\n" + mark.snippet(from: yaml)
case let .parser(context, problem, mark, yaml):
@ -173,8 +165,8 @@ extension YamlError: CustomStringConvertible {
return "\(mark): error: composer: \(context?.description ?? "")\(problem):\n" + mark.snippet(from: yaml)
case let .writer(problem), let .emitter(problem), let .representer(problem):
return problem
case .dataCouldNotBeDecoded(encoding: let encoding):
return "String could not be decoded from data using '\(encoding)' encoding"
case .dataCouldNotBeDecoded:
return "String could not be decoded from data"
}
}
}

View File

@ -1 +1,43 @@
import Yams; print(try Yams.load(yaml: "a: 0")!)
import Yams
let yaml = """
a: 0
b: 1.2
c: [1, 2, 3]
d:
- a
- b
- c
"""
dump(try Yams.load(yaml: yaml)!)
enum Instrument: String, Codable {
case tenorSaxophone = "Tenor Saxophone"
case trumpet = "Trumpet"
}
let bandYAML = """
members:
- name: John Coltrane
age: 27
- name: Miles Davis
age: 23
instrument: Trumpet
"""
struct Person: Codable {
let name: String
let age: Int
let instrument: Instrument?
}
struct Band: Codable {
let members: [Person]
}
let band = try YAMLDecoder().decode(Band.self, from: bandYAML)
dump(band)
print("Back to yaml:")
print(try YAMLEncoder().encode(band))