Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions Sources/Yams/Anchor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,27 @@
// Created by Adora Lynch on 8/9/24.
// Copyright (c) 2024 Yams. All rights reserved.

#if canImport(FoundationEssentials)
import FoundationEssentials
#elseif canImport(Foundation)
import Foundation
#endif

/// A representation of a YAML tag see: https://yaml.org/spec/1.2.2/
/// Types interested in Encoding and Decoding Anchors should
/// conform to YamlAnchorProviding and YamlAnchorCoding respectively.
public final class Anchor: RawRepresentable, ExpressibleByStringLiteral, Codable, Hashable {

/// A CharacterSet containing only characters which are permitted by the underlying cyaml implementation
public static let permittedCharacters = CharacterSet.lowercaseLetters
.union(.uppercaseLetters)
.union(.decimalDigits)
.union(.init(charactersIn: "-_"))
/// Returns true if and only if `character` is permitted by the underlying cyaml implementation
/// (alphanumeric, hyphen, or underscore).
public static func isPermittedCharacter(_ character: Character) -> Bool {
character.isLetter || character.isNumber || character == "-" || character == "_"
}

/// Returns true if and only if `string` contains only characters which are also in `permittedCharacters`
/// Returns true if and only if `string` contains only characters which are also permitted
/// (alphanumeric, hyphen, or underscore).
public static func is_cyamlAlpha(_ string: String) -> Bool {
Anchor.permittedCharacters.isSuperset(of: .init(charactersIn: string))
string.allSatisfy(isPermittedCharacter)
}

public let rawValue: String
Expand Down
92 changes: 44 additions & 48 deletions Sources/Yams/Constructor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
// Copyright (c) 2016 Yams. All rights reserved.
//

#if canImport(FoundationEssentials)
import FoundationEssentials
#elseif canImport(Foundation)
import Foundation
#endif

/// Constructors are used to translate `Node`s to Swift values.
public final class Constructor {
Expand Down Expand Up @@ -77,7 +81,7 @@ extension Constructor {
// JSON Schema
.bool: Bool.construct,
.float: Double.construct,
.null: NSNull.construct,
.null: YAMLNull.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,
Expand All @@ -101,6 +105,7 @@ extension Constructor {
.pairs: [Any].construct_pairs
] }

#if canImport(ObjectiveC)
/// `Tag.Name` to `Node.Mapping` map that support `NSMutableDictionary` and `NSMutableSet`.
public static var nsMutableMappingMap: MappingMap { [
.map: NSMutableDictionary.construct_mapping,
Expand All @@ -113,6 +118,7 @@ extension Constructor {
.omap: NSMutableArray.construct_omap,
.pairs: NSMutableArray.construct_pairs
] }
#endif
}

// MARK: - ScalarConstructible
Expand Down Expand Up @@ -188,14 +194,11 @@ extension Date: ScalarConstructible {
///
/// - 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))
guard let match = try? timestampPattern.wholeMatch(in: scalar.string) else {
return nil
}
let output = match.output
let components = (1..<output.count).map { output[$0].substring }

var datecomponents = DateComponents()
datecomponents.calendar = gregorianCalendar
Expand Down Expand Up @@ -235,7 +238,8 @@ extension Date: ScalarConstructible {

private static let gregorianCalendar = Calendar(identifier: .gregorian)

private static let timestampPattern: NSRegularExpression = pattern([
// swiftlint:disable:next force_try
private nonisolated(unsafe) static let timestampPattern: Regex<AnyRegexOutput> = try! Regex([
"^([0-9][0-9][0-9][0-9])", // year
"-([0-9][0-9]?)", // month
"-([0-9][0-9]?)", // day
Expand Down Expand Up @@ -278,7 +282,7 @@ extension ScalarConstructible where Self: FloatingPoint & SexagesimalConvertible
case ".nan":
return .nan
default:
let string = scalar.string.replacingOccurrences(of: "_", with: "")
let string = scalar.string.replacing("_", with: "")
if string.contains(":") {
return Self(sexagesimal: string)
}
Expand All @@ -293,7 +297,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
Expand Down Expand Up @@ -405,22 +409,23 @@ extension String: ScalarConstructible {
}
}

// MARK: - Types that can't conform to ScalarConstructible
// MARK: - YAMLNull

/// A type representing a YAML null value.
public struct YAMLNull: Hashable, Sendable, ScalarConstructible {
public init() {}

extension NSNull/*: ScalarConstructible*/ {
/// Construct an instance of `NSNull`, if possible, from the specified scalar.
/// Construct an instance of `YAMLNull`, if possible, from the specified scalar.
///
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type `NSNull`, if possible.
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type `YAMLNull`, if possible.
///
/// - returns: An instance of `NSNull`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> NSNull? {
// When constructing from a Scalar, only plain style scalars should be recognized.
// For example #"key: 'null'"# or #"key: ''"# should not be considered as null.
/// - returns: An instance of `YAMLNull`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> YAMLNull? {
guard case .plain = scalar.style else { return nil }

switch scalar.string {
case "", "~", "null", "Null", "NULL":
return NSNull()
return YAMLNull()
default:
return nil
}
Expand Down Expand Up @@ -451,6 +456,21 @@ private extension Dictionary {
}
}

extension Set {
/// Construct a `Set`, if possible, from the specified mapping.
///
/// - parameter mapping: The `Node.Mapping` from which to extract a `Set`, if possible.
///
/// - returns: An instance of `Set<AnyHashable>`, if one was successfully extracted from the mapping.
public static func construct_set(from mapping: Node.Mapping) -> Set<AnyHashable>? {
// TODO: YAML supports Hashable elements other than str.
return Set<AnyHashable>(mapping.map({ String.construct(from: $0.key)! as AnyHashable }))
// Explicitly declaring the generic parameter as `<AnyHashable>` above is required,
// because this is inside extension of `Set` and Swift can't infer the type without that.
}
}

#if canImport(ObjectiveC)
extension NSMutableDictionary {
/// Construct an `NSMutableDictionary`, if possible, from the specified mapping.
///
Expand All @@ -470,20 +490,6 @@ extension NSMutableDictionary {
}
}

extension Set {
/// Construct a `Set`, if possible, from the specified mapping.
///
/// - parameter mapping: The `Node.Mapping` from which to extract a `Set`, if possible.
///
/// - returns: An instance of `Set<AnyHashable>`, if one was successfully extracted from the mapping.
public static func construct_set(from mapping: Node.Mapping) -> Set<AnyHashable>? {
// TODO: YAML supports Hashable elements other than str.
return Set<AnyHashable>(mapping.map({ String.construct(from: $0.key)! as AnyHashable }))
// Explicitly declaring the generic parameter as `<AnyHashable>` above is required,
// because this is inside extension of `Set` and Swift can't infer the type without that.
}
}

extension NSMutableSet {
/// Construct an `NSMutableSet`, if possible, from the specified mapping.
///
Expand All @@ -500,6 +506,7 @@ extension NSMutableSet {
return result
}
}
#endif

// MARK: Sequence

Expand Down Expand Up @@ -542,6 +549,7 @@ extension Array {
}
}

#if canImport(ObjectiveC)
extension NSMutableArray {
/// Construct an NSMutableArray of `Any` from the specified `sequence`.
///
Expand Down Expand Up @@ -587,19 +595,7 @@ extension NSMutableArray {
return result
}
}

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]
}
}
#endif

// MARK: - SexagesimalConvertible

Expand Down Expand Up @@ -692,7 +688,7 @@ private extension String {
} else {
sign = 1
}
let components = scalar.components(separatedBy: ":")
let components = scalar.split(separator: ":").map(String.init)
let mappedComponents = components.compactMap(T.create)
guard mappedComponents.count == components.count else {
return nil
Expand Down
6 changes: 5 additions & 1 deletion Sources/Yams/Decoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
// Copyright (c) 2017 Yams. All rights reserved.
//

#if canImport(FoundationEssentials)
import FoundationEssentials
#elseif canImport(Foundation)
import Foundation
#endif

/// `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`.
Expand Down Expand Up @@ -372,7 +376,7 @@ extension _Decoder: SingleValueDecodingContainer {

// MARK: - Swift.SingleValueDecodingContainer Methods

func decodeNil() -> Bool { return node.null == NSNull() }
func decodeNil() -> Bool { return node.null == YAMLNull() }
func decode<T>(_ type: T.Type) throws -> T where T: Decodable & ScalarConstructible { return try _decode(type) }
func decode<T>(_ type: T.Type) throws -> T where T: Decodable {return try _decode(type) }

Expand Down
4 changes: 4 additions & 0 deletions Sources/Yams/Emitter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ internal import CYaml
@_implementationOnly import CYaml
#endif
#endif
#if canImport(FoundationEssentials)
import FoundationEssentials
#elseif canImport(Foundation)
import Foundation
#endif

/// Produce a YAML string from objects.
///
Expand Down
10 changes: 7 additions & 3 deletions Sources/Yams/Node.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
// Copyright (c) 2016 Yams. All rights reserved.
//

#if canImport(FoundationEssentials)
import FoundationEssentials
#elseif canImport(Foundation)
import Foundation
#endif

/// YAML Node.
public enum Node: Hashable {
Expand Down Expand Up @@ -115,9 +119,9 @@ 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 a `YAMLNull`, if convertible.
public var null: YAMLNull? {
return scalar.flatMap(YAMLNull.construct)
}

/// This node as an `Int`, if convertible.
Expand Down
4 changes: 4 additions & 0 deletions Sources/Yams/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ internal import CYaml
@_implementationOnly import CYaml
#endif
#endif
#if canImport(FoundationEssentials)
import FoundationEssentials
#elseif canImport(Foundation)
import Foundation
#endif

/// Parse all YAML documents in a String
/// and produce corresponding Swift objects.
Expand Down
Loading