Merge pull request #122 from jpsim/docs

Add API docs
This commit is contained in:
JP Simard 2018-05-09 20:56:16 -07:00 committed by GitHub
commit 2fdfdac898
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1119 additions and 306 deletions

View File

@ -23,6 +23,30 @@ aliases:
- vendor/bundle
- run: bundle exec pod lib lint
- &steps-for-jazzy
- checkout
- run: echo "ruby-2.3" > ~/.ruby-version
- restore_cache:
key: 1-gems-{{ checksum "Gemfile.lock" }}
- run: |
(
export ARCHFLAGS="-arch x86_64"
bundle check || bundle install --path vendor/bundle
)
- save_cache:
key: 1-gems-{{ checksum "Gemfile.lock" }}
paths:
- vendor/bundle
- run: bundle exec jazzy
- store_artifacts:
path: docs
- run: |
if ruby -rjson -e "j = JSON.parse(File.read('docs/undocumented.json')); exit j['warnings'].length != 0"; then
echo "Undocumented declarations:"
cat "$undocumented"
exit 1
fi
- &steps-for-spm
- checkout
- run: swift test
@ -60,6 +84,11 @@ jobs:
xcode: "9.3.0"
<< : *XCODE9
jazzy:
macos:
xcode: "9.3.0"
steps: *steps-for-jazzy
spm_swift_4:
macos:
xcode: "9.0"
@ -108,6 +137,7 @@ workflows:
- xcode_9.1_swift_4.0.2
- xcode_9.2_swift_4.0.3
- xcode_9.3_swift_4.1
- jazzy
- spm_swift_4
- spm_swift_4.0.2
- spm_swift_4.0.3

3
.gitignore vendored
View File

@ -61,3 +61,6 @@ Carthage/Build
fastlane/report.xml
fastlane/screenshots
# Docs
docs

9
.jazzy.yaml Normal file
View File

@ -0,0 +1,9 @@
module: Yams
author: JP Simard, Norio Nomura
author_url: https://jpsim.com
root_url: https://jpsim.com/Yams/
github_url: https://github.com/jpsim/Yams
github_file_prefix: https://github.com/jpsim/Yams/tree/docs
theme: fullwidth
clean: true
copyright: '© 2018 [JP Simard](https://jpsim.com) under MIT.'

View File

@ -1,3 +1,10 @@
disabled_rules:
- identifier_name
line_length: 120
identifier_name:
excluded:
- cr
- ln
- no
nesting:
type_level: 2

209
Docs.md Normal file
View File

@ -0,0 +1,209 @@
# Yams Documentation
For installation instructions, see [README.md](README.md).
API documentation coming soon.
## Usage
### Consume YAML
Here's a simple example parsing a YAML array of strings:
```swift
import Yams
let yamlString = """
- a
- b
- c
"""
do {
let yamlNode = try Yams.load(yaml: yamlString)
if let yamlArray = yamlNode as? [String] {
print(yamlArray)
}
} catch {
print("handle error: \(error)")
}
// Prints:
// ["a", "b", "c"]
```
### Emit YAML
Here's a simple example emitting YAML string from a Swift `Array<String>`:
```swift
import Yams
do {
let yamlString = try Yams.serialize(node: ["a", "b", "c"])
print(yamlString)
} catch {
print("handle error: \(error)")
}
// Prints:
// - a
// - b
// - c
```
You can even customize the style:
```swift
import Yams
var node: Node = ["a", "b", "c"]
node.sequence?.style = .flow
do {
let yamlString = try Yams.serialize(node: node)
print(yamlString)
} catch {
print("handle error: \(error)")
}
// Prints:
// [a, b, c]
```
### Customize Parsing
For example, say you only want the literals `true` and `false` to represent booleans, unlike the
YAML spec compliant boolean which also includes `on`/`off` and many others.
You can customize Yams' Constructor map:
```swift
import Yams
extension Constructor {
public static func withBoolAsTrueFalse() -> Constructor {
var map = defaultMap
map[.bool] = Bool.constructUsingOnlyTrueAndFalse
return Constructor(map)
}
}
private extension Bool {
static func constructUsingOnlyTrueAndFalse(from node: Node) -> Bool? {
assert(node.isScalar)
switch node.scalar!.string.lowercased() {
case "true":
return true
case "false":
return false
default:
return nil
}
}
}
private extension Node {
var isScalar: Bool {
if case .scalar = self {
return true
}
return false
}
}
// Usage:
let yamlString = """
- true
- on
- off
- false
"""
if let array = try? Yams.load(yaml: yamlString, .default, .withBoolAsTrueFalse()) as? [Any] {
print(array)
}
// Prints:
// [true, "on", "off", false]
```
### Expanding Environment Variables
For example:
```swift
import Yams
extension Constructor {
public static func withEnv(_ env: [String: String]) -> Constructor {
var map = defaultMap
map[.str] = String.constructExpandingEnvVars(env: env)
return Constructor(map)
}
}
private extension String {
static func constructExpandingEnvVars(env: [String: String]) -> (_ node: Node) -> String? {
return { (node: Node) -> String? in
assert(node.isScalar)
return node.scalar!.string.expandingEnvVars(env: env)
}
}
func expandingEnvVars(env: [String: String]) -> String {
var result = self
for (key, value) in env {
result = result.replacingOccurrences(of: "${\(key)}", with: value)
}
return result
}
}
// Usage:
let yamlString = """
- first
- ${SECOND}
- SECOND
"""
let env = ["SECOND": "2"]
if let array = try? Yams.load(yaml: yamlString, .default, .withEnv(env)) as? [String] {
print(array)
}
// Prints:
// ["first", "2", "SECOND"]
```
### Converting Between Formats
Because Yams conforms to Swift 4's Codable protocol and provides a YAML Encoder and Decoder,
you can easily convert between YAML and other formats that also provide Swift 4 Encoders and
Decoders, such as JSON and Plist.
### Error Handling
Failable operations in Yams throw Swift errors.
### Types
| Name | Yams Tag | YAML Tag | Swift Types |
|----------------|---------------|-------------------------------|--------------------------------|
| ... | `implicit` | `` | ... |
| ... | `nonSpecific` | `!` | ... |
| String | `str` | `tag:yaml.org,2002:str` | `String` |
| Sequence | `seq` | `tag:yaml.org,2002:seq` | `Array<Any>` |
| Map | `map` | `tag:yaml.org,2002:map` | `Dictionary<AnyHashable, Any>` |
| Boolean | `bool` | `tag:yaml.org,2002:bool` | `Bool` |
| Floating Point | `float` | `tag:yaml.org,2002:float` | ... |
| Null | `null` | `tag:yaml.org,2002:null` | `Void` |
| Integer | `int` | `tag:yaml.org,2002:int` | `FixedWidthInteger` |
| ... | `binary` | `tag:yaml.org,2002:binary` | `Data` |
| ... | `merge` | `tag:yaml.org,2002:merge` | ... |
| ... | `omap` | `tag:yaml.org,2002:omap` | ... |
| ... | `pairs` | `tag:yaml.org,2002:pairs` | ... |
| Set | `set` | `tag:yaml.org,2002:set` | `Set<AnyHashable>` |
| Timestamp | `timestamp` | `tag:yaml.org,2002:timestamp` | `Date` |
| ... | `value` | `tag:yaml.org,2002:value` | ... |
| YAML | `yaml` | `tag:yaml.org,2002:yaml` | Unsupported |

View File

@ -1,3 +1,4 @@
source 'https://rubygems.org'
gem 'cocoapods'
gem "jazzy"

View File

@ -1,7 +1,7 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (2.3.6)
CFPropertyList (3.0.0)
activesupport (4.2.10)
i18n (~> 0.7)
minitest (~> 5.1)
@ -9,12 +9,12 @@ GEM
tzinfo (~> 1.1)
atomos (0.1.2)
claide (1.0.2)
cocoapods (1.4.0)
cocoapods (1.5.2)
activesupport (>= 4.0.2, < 5)
claide (>= 1.0.2, < 2.0)
cocoapods-core (= 1.4.0)
cocoapods-core (= 1.5.2)
cocoapods-deintegrate (>= 1.0.2, < 2.0)
cocoapods-downloader (>= 1.1.3, < 2.0)
cocoapods-downloader (>= 1.2.0, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
cocoapods-stats (>= 1.0.0, < 2.0)
@ -24,16 +24,16 @@ GEM
escape (~> 0.0.4)
fourflusher (~> 2.0.1)
gh_inspector (~> 1.0)
molinillo (~> 0.6.4)
molinillo (~> 0.6.5)
nap (~> 1.0)
ruby-macho (~> 1.1)
xcodeproj (>= 1.5.4, < 2.0)
cocoapods-core (1.4.0)
xcodeproj (>= 1.5.7, < 2.0)
cocoapods-core (1.5.2)
activesupport (>= 4.0.2, < 6)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
cocoapods-deintegrate (1.0.2)
cocoapods-downloader (1.1.3)
cocoapods-downloader (1.2.0)
cocoapods-plugins (1.0.0)
nap
cocoapods-search (1.0.0)
@ -45,32 +45,59 @@ GEM
colored2 (3.1.2)
concurrent-ruby (1.0.5)
escape (0.0.4)
ffi (1.9.23)
fourflusher (2.0.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.2)
gh_inspector (1.1.3)
i18n (0.9.5)
concurrent-ruby (~> 1.0)
jazzy (0.9.3)
cocoapods (~> 1.0)
mustache (~> 0.99)
open4
redcarpet (~> 3.2)
rouge (>= 2.0.6, < 4.0)
sass (~> 3.4)
sqlite3 (~> 1.3)
xcinvoke (~> 0.3.0)
liferaft (0.0.6)
minitest (5.11.3)
molinillo (0.6.4)
nanaimo (0.2.3)
molinillo (0.6.5)
mustache (0.99.8)
nanaimo (0.2.5)
nap (1.1.0)
netrc (0.11.0)
open4 (1.3.4)
rb-fsevent (0.10.3)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
redcarpet (3.4.0)
rouge (3.1.1)
ruby-macho (1.1.0)
sass (3.5.6)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
sqlite3 (1.3.13)
thread_safe (0.3.6)
tzinfo (1.2.5)
thread_safe (~> 0.1)
xcodeproj (1.5.6)
CFPropertyList (~> 2.3.3)
xcinvoke (0.3.0)
liferaft (~> 0.0.6)
xcodeproj (1.5.8)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.2)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.2.3)
nanaimo (~> 0.2.5)
PLATFORMS
ruby
DEPENDENCIES
cocoapods
jazzy
BUNDLED WITH
1.15.4
1.16.0

View File

@ -1,9 +1,9 @@
# Yams
![Yams](yams.jpg)
![Yams](https://raw.githubusercontent.com/jpsim/Yams/master/yams.jpg)
A sweet and swifty [Yaml](http://yaml.org/) parser built on
[libYAML](http://pyyaml.org/wiki/LibYAML).
A sweet and swifty [YAML](http://yaml.org/) parser built on
[LibYAML](https://github.com/yaml/libyaml).
[![CircleCI](https://circleci.com/gh/jpsim/Yams.svg?style=svg)](https://circleci.com/gh/jpsim/Yams)

View File

@ -8,11 +8,20 @@
import Foundation
/// Constructors are used to translate `Node`s to Swift values.
public final class Constructor {
/// Maps `Tag.Name`s to `Node.Scalar`s.
public typealias ScalarMap = [Tag.Name: (Node.Scalar) -> Any?]
/// Maps `Tag.Name`s to `Node.Mapping`s.
public typealias MappingMap = [Tag.Name: (Node.Mapping) -> Any?]
/// Maps `Tag.Name`s to `Node.Sequence`s.
public typealias SequenceMap = [Tag.Name: (Node.Sequence) -> Any?]
/// Initialize a `Constructor` with the specified maps, falling back to default maps.
///
/// - parameter scalarMap: Maps `Tag.Name`s to `Node.Scalar`s.
/// - parameter mappingMap: Maps `Tag.Name`s to `Node.Mapping`s.
/// - parameter sequenceMap: Maps `Tag.Name`s to `Node.Sequence`s.
public init(_ scalarMap: ScalarMap = defaultScalarMap,
_ mappingMap: MappingMap = defaultMappingMap,
_ sequenceMap: SequenceMap = defaultSequenceMap) {
@ -21,6 +30,13 @@ public final class Constructor {
self.sequenceMap = sequenceMap
}
/// Constructs Swift values based on the maps this `Constructor` was initialized with.
///
/// - parameter node: `Node` from which to extract an `Any` Swift value, if one was produced by the Node
/// type's relevant mapping on this `Constructor`.
///
/// - returns: An `Any` Swift value, if one was produced by the Node type's relevant mapping on this
/// `Constructor`.
public func any(from node: Node) -> Any {
switch node {
case .scalar(let scalar):
@ -46,10 +62,13 @@ public final class Constructor {
private let sequenceMap: SequenceMap
}
// MARK: - Default Mappings
extension Constructor {
/// The default `Constructor` to be used with APIs where none is explicitly provided.
public static let `default` = Constructor()
// We can not write extension of map because that is alias of specialized dictionary
/// The default `Tag.Name` to `Node.Scalar` map.
public static let defaultScalarMap: ScalarMap = [
// Failsafe Schema
.str: String.construct,
@ -63,6 +82,7 @@ extension Constructor {
.timestamp: Date.construct
]
/// The default `Tag.Name` to `Node.Mapping` map.
public static let defaultMappingMap: MappingMap = [
.map: [AnyHashable: Any].construct_mapping,
// http://yaml.org/type/index.html
@ -71,6 +91,7 @@ extension Constructor {
// .value is supported in `String.construct` and `[AnyHashable: Any].construct_mapping`.
]
/// The default `Tag.Name` to `Node.Sequence` map.
public static let defaultSequenceMap: SequenceMap = [
.seq: [Any].construct_seq,
// http://yaml.org/type/index.html
@ -80,13 +101,28 @@ extension Constructor {
}
// MARK: - ScalarConstructible
/// Types conforming to this protocol can be extracted `Node.Scalar`s.
public protocol ScalarConstructible {
// We don't use overloading `init?(_ scalar: Node.Scalar)`
// because that causes difficulties on using `init` as closure
/// Construct an instance of `Self`, if possible, from the specified scalar.
///
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type `Self`, if possible.
///
/// - returns: An instance of `Self`, if one was successfully extracted from the scalar.
///
/// - note: We use static constructors to avoid overloading `init?(_ scalar: Node.Scalar)` which would
/// cause callsite ambiguities when using `init` as closure.
static func construct(from scalar: Node.Scalar) -> Self?
}
// MARK: - ScalarConstructible Bool Conformance
extension Bool: ScalarConstructible {
/// Construct an instance of `Bool`, if possible, from the specified scalar.
///
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type `Bool`, if possible.
///
/// - returns: An instance of `Bool`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> Bool? {
switch scalar.string.lowercased() {
case "true", "yes", "on":
@ -99,13 +135,27 @@ 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),
@ -168,10 +218,21 @@ extension Date: ScalarConstructible {
)
}
// MARK: - ScalarConstructible Double Conformance
extension Double: ScalarConstructible {}
// MARK: - ScalarConstructible Float Conformance
extension Float: ScalarConstructible {}
// MARK: - ScalarConstructible FloatingPoint Conformance
extension ScalarConstructible where Self: FloatingPoint & SexagesimalConvertible {
/// Construct an instance of `FloatingPoint & SexagesimalConvertible`, if possible, from the specified
/// scalar.
///
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type
/// `FloatingPoint & SexagesimalConvertible`, if possible.
///
/// - returns: An instance of `FloatingPoint & SexagesimalConvertible`, if one was successfully extracted
/// from the scalar.
public static func construct(from scalar: Node.Scalar) -> Self? {
switch scalar.string {
case ".inf", ".Inf", ".INF", "+.inf", "+.Inf", "+.INF":
@ -223,23 +284,49 @@ private extension FixedWidthInteger where Self: SexagesimalConvertible {
}
}
// MARK: - ScalarConstructible Int Conformance
extension Int: ScalarConstructible {
/// Construct an instance of `Int`, if possible, from the specified scalar.
///
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type `Int`, if possible.
///
/// - returns: An instance of `Int`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> Int? {
return _construct(from: scalar)
}
}
// MARK: - ScalarConstructible UInt Conformance
extension UInt: ScalarConstructible {
/// Construct an instance of `UInt`, if possible, from the specified scalar.
///
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type `UInt`, if possible.
///
/// - returns: An instance of `UInt`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> UInt? {
return _construct(from: scalar)
}
}
// MARK: - ScalarConstructible String Conformance
extension String: ScalarConstructible {
/// Construct an instance of `String`, if possible, from the specified scalar.
///
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type `String`, if possible.
///
/// - returns: An instance of `String`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> String? {
return scalar.string
}
/// Construct an instance of `String`, if possible, from the specified `Node`.
///
/// - parameter node: The `Node` from which to extract a value of type `String`, if possible.
///
/// - returns: An instance of `String`, if one was successfully extracted from the node.
public static func construct(from node: Node) -> String? {
// This will happen while `Dictionary.flatten_mapping()` if `node.tag.name` was `.value`
if case let .mapping(mapping) = node {
@ -254,7 +341,13 @@ 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":
@ -265,8 +358,14 @@ extension NSNull/*: ScalarConstructible*/ {
}
}
// MARK: mapping
// MARK: Mapping
extension Dictionary {
/// Construct a `Dictionary`, if possible, from the specified mapping.
///
/// - parameter mapping: The `Node.Mapping` from which to extract a `Dictionary`, if possible.
///
/// - returns: An instance of `[AnyHashable: Any]`, if one was successfully extracted from the mapping.
public static func construct_mapping(from mapping: Node.Mapping) -> [AnyHashable: Any]? {
return _construct_mapping(from: mapping)
}
@ -316,20 +415,37 @@ 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 3.0.2 can't infer the type without that.
// because this is inside extension of `Set` and Swift 3.0.2 to 4.1.0 can't infer the type without
// that.
}
}
// MARK: sequence
// MARK: Sequence
extension Array {
/// Construct an Array of `Any` from the specified `sequence`.
///
/// - parameter sequence: Sequence to convert to `Array<Any>`.
///
/// - returns: Array of `Any`.
public static func construct_seq(from sequence: Node.Sequence) -> [Any] {
return sequence.map(sequence.tag.constructor.any)
}
/// Construct an "O-map" (array of `(Any, Any)` tuples) from the specified `sequence`.
///
/// - parameter sequence: Sequence to convert to `Array<(Any, Any)>`.
///
/// - returns: Array of `(Any, Any)` tuples.
public static func construct_omap(from sequence: Node.Sequence) -> [(Any, Any)] {
// Note: we do not check for duplicate keys.
return sequence.compactMap { subnode -> (Any, Any)? in
@ -339,6 +455,11 @@ extension Array {
}
}
/// Construct an array of `(Any, Any)` tuples from the specified `sequence`.
///
/// - parameter sequence: Sequence to convert to `Array<(Any, Any)>`.
///
/// - returns: Array of `(Any, Any)` tuples.
public static func construct_pairs(from sequence: Node.Sequence) -> [(Any, Any)] {
// Note: we do not check for duplicate keys.
return sequence.compactMap { subnode -> (Any, Any)? in
@ -375,9 +496,30 @@ private extension StringProtocol {
}
// MARK: - SexagesimalConvertible
/// Confirming types are convertible to base 60 numeric values.
public protocol SexagesimalConvertible: ExpressibleByIntegerLiteral {
/// Creates a sexagesimal numeric value from the given string.
///
/// - parameter string: The string from which to parse a sexagesimal value.
///
/// - returns: A sexagesimal numeric value, if one was successfully parsed.
static func create(from string: String) -> Self?
/// Multiplies two sexagesimal numeric values.
///
/// - parameter lhs: Left hand side multiplier.
/// - parameter rhs: Right hand side multiplier.
///
/// - returns: The result of the multiplication.
static func * (lhs: Self, rhs: Self) -> Self
/// Adds two sexagesimal numeric values.
///
/// - parameter lhs: Left hand side adder.
/// - parameter rhs: Right hand side adder.
///
/// - returns: The result of the addition.
static func + (lhs: Self, rhs: Self) -> Self
}
@ -387,21 +529,39 @@ private extension SexagesimalConvertible {
}
}
// MARK: Default String to `LosslessStringConvertible` conversion.
extension SexagesimalConvertible where Self: LosslessStringConvertible {
/// Creates a sexagesimal numeric value from the given string.
///
/// - parameter string: The string from which to parse a sexagesimal value.
///
/// - returns: A sexagesimal numeric value, if one was successfully parsed.
public static func create(from string: String) -> Self? {
return Self(string)
}
}
// MARK: Default String to `FixedWidthInteger` conversion.
extension SexagesimalConvertible where Self: FixedWidthInteger {
/// Creates a sexagesimal numeric value from the given string.
///
/// - parameter string: The string from which to parse a sexagesimal value.
///
/// - returns: A sexagesimal numeric value, if one was successfully parsed.
public static func create(from string: String) -> Self? {
return Self(string, radix: 10)
}
}
// MARK: - SexagesimalConvertible Double Conformance
extension Double: SexagesimalConvertible {}
// MARK: - SexagesimalConvertible Float Conformance
extension Float: SexagesimalConvertible {}
// MARK: - SexagesimalConvertible Int Conformance
extension Int: SexagesimalConvertible {}
// MARK: - SexagesimalConvertible Int Conformance
extension UInt: SexagesimalConvertible {}
private extension String {

View File

@ -8,8 +8,21 @@
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 {
/// Creates a `YAMLDecoder` instance.
public init() {}
/// Decode a `Decodable` type from a given `String` and optional user info mapping.
///
/// - parameter type: `Decodable` type to decode.
/// - parameter yaml: YAML string 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` if something went wrong while decoding.
public func decode<T>(_ type: T.Type = T.self,
from yaml: String,
userInfo: [CodingUserInfoKey: Any] = [:]) throws -> T where T: Swift.Decodable {
@ -245,34 +258,70 @@ private func _typeMismatch(at codingPath: [CodingKey], expectation: Any.Type, re
return .typeMismatch(expectation, context)
}
// MARK: - ScalarConstructible FixedWidthInteger & SignedInteger Conformance
extension FixedWidthInteger where Self: SignedInteger {
/// Construct an instance of `Self`, if possible, from the specified scalar.
///
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type `Self`, if possible.
///
/// - returns: An instance of `Self`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> Self? {
return Int.construct(from: scalar).flatMap(Self.init(exactly:))
}
}
// MARK: - ScalarConstructible FixedWidthInteger & UnsignedInteger Conformance
extension FixedWidthInteger where Self: UnsignedInteger {
/// Construct an instance of `Self`, if possible, from the specified scalar.
///
/// - parameter scalar: The `Node.Scalar` from which to extract a value of type `Self`, if possible.
///
/// - returns: An instance of `Self`, if one was successfully extracted from the scalar.
public static func construct(from scalar: Node.Scalar) -> Self? {
return UInt.construct(from: scalar).flatMap(Self.init(exactly:))
}
}
extension Int16: ScalarConstructible {}
extension Int32: ScalarConstructible {}
extension Int64: ScalarConstructible {}
// MARK: - ScalarConstructible Int8 Conformance
extension Int8: ScalarConstructible {}
extension UInt16: ScalarConstructible {}
extension UInt32: ScalarConstructible {}
extension UInt64: ScalarConstructible {}
// MARK: - ScalarConstructible Int16 Conformance
extension Int16: ScalarConstructible {}
// MARK: - ScalarConstructible Int32 Conformance
extension Int32: ScalarConstructible {}
// MARK: - ScalarConstructible Int64 Conformance
extension Int64: ScalarConstructible {}
// MARK: - ScalarConstructible UInt8 Conformance
extension UInt8: ScalarConstructible {}
// MARK: - ScalarConstructible UInt16 Conformance
extension UInt16: ScalarConstructible {}
// MARK: - ScalarConstructible UInt32 Conformance
extension UInt32: ScalarConstructible {}
// MARK: - ScalarConstructible UInt64 Conformance
extension UInt64: 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)
}

View File

@ -7,24 +7,26 @@
//
#if SWIFT_PACKAGE
import CYaml
import CYaml
#endif
import Foundation
/// Produce YAML String from objects
/// Produce a YAML string from objects.
///
/// - Parameters:
/// - objects: Sequence of Object
/// - canonical: output should be the "canonical" format as in the YAML specification.
/// - indent: the intendation increment.
/// - width: the preferred line width. @c -1 means unlimited.
/// - allowUnicode: unescaped non-ASCII characters are allowed if true.
/// - lineBreak: preferred line break.
/// - explicitStart: explicit document start `---`
/// - explicitEnd: explicit document end `...`
/// - version: YAML version directive
/// - Returns: YAML String
/// - Throws: `YamlError`
/// - parameter objects: Sequence of Objects.
/// - parameter canonical: Output should be the "canonical" format as in the YAML specification.
/// - parameter indent: The intendation increment.
/// - parameter width: The preferred line width. @c -1 means unlimited.
/// - parameter allowUnicode: Unescaped non-ASCII characters are allowed if true.
/// - parameter lineBreak: Preferred line break.
/// - parameter explicitStart: Explicit document start `---`.
/// - parameter explicitEnd: Explicit document end `...`.
/// - parameter version: YAML version directive.
/// - parameter sortKeys: Whether or not to sort Mapping keys in lexicographic order.
///
/// - returns: YAML string.
///
/// - throws: `YamlError`.
public func dump<Objects>(
objects: Objects,
canonical: Bool = false,
@ -37,40 +39,43 @@ public func dump<Objects>(
version: (major: Int, minor: Int)? = nil,
sortKeys: Bool = false) throws -> String
where Objects: Sequence {
func representable(from object: Any) throws -> NodeRepresentable {
if let representable = object as? NodeRepresentable {
return representable
}
throw YamlError.emitter(problem: "\(object) does not conform to NodeRepresentable!")
func representable(from object: Any) throws -> NodeRepresentable {
if let representable = object as? NodeRepresentable {
return representable
}
let nodes = try objects.map(representable(from:)).map { try $0.represented() }
return try serialize(
nodes: nodes,
canonical: canonical,
indent: indent,
width: width,
allowUnicode: allowUnicode,
lineBreak: lineBreak,
explicitStart: explicitStart,
explicitEnd: explicitEnd,
version: version,
sortKeys: sortKeys)
throw YamlError.emitter(problem: "\(object) does not conform to NodeRepresentable!")
}
let nodes = try objects.map(representable(from:)).map { try $0.represented() }
return try serialize(
nodes: nodes,
canonical: canonical,
indent: indent,
width: width,
allowUnicode: allowUnicode,
lineBreak: lineBreak,
explicitStart: explicitStart,
explicitEnd: explicitEnd,
version: version,
sortKeys: sortKeys
)
}
/// Produce YAML String from object
/// Produce a YAML string from an object.
///
/// - Parameters:
/// - object: Object
/// - canonical: output should be the "canonical" format as in the YAML specification.
/// - indent: the intendation increment.
/// - width: the preferred line width. @c -1 means unlimited.
/// - allowUnicode: unescaped non-ASCII characters are allowed if true.
/// - lineBreak: preferred line break.
/// - explicitStart: explicit document start `---`
/// - explicitEnd: explicit document end `...`
/// - version: YAML version directive
/// - Returns: YAML String
/// - Throws: `YamlError`
/// - parameter object: Object.
/// - parameter canonical: Output should be the "canonical" format as in the YAML specification.
/// - parameter indent: The intendation increment.
/// - parameter width: The preferred line width. @c -1 means unlimited.
/// - parameter allowUnicode: Unescaped non-ASCII characters are allowed if true.
/// - parameter lineBreak: Preferred line break.
/// - parameter explicitStart: Explicit document start `---`.
/// - parameter explicitEnd: Explicit document end `...`.
/// - parameter version: YAML version directive.
/// - parameter sortKeys: Whether or not to sort Mapping keys in lexicographic order.
///
/// - returns: YAML string.
///
/// - throws: `YamlError`.
public func dump(
object: Any?,
canonical: Bool = false,
@ -92,23 +97,26 @@ public func dump(
explicitStart: explicitStart,
explicitEnd: explicitEnd,
version: version,
sortKeys: sortKeys)
sortKeys: sortKeys
)
}
/// Produce YAML String from `Node`
/// Produce a YAML string from a `Node`.
///
/// - Parameters:
/// - nodes: Sequence of `Node`
/// - canonical: output should be the "canonical" format as in the YAML specification.
/// - indent: the intendation increment.
/// - width: the preferred line width. @c -1 means unlimited.
/// - allowUnicode: unescaped non-ASCII characters are allowed if true.
/// - lineBreak: preferred line break.
/// - explicitStart: explicit document start `---`
/// - explicitEnd: explicit document end `...`
/// - version: YAML version directive
/// - Returns: YAML String
/// - Throws: `YamlError`
/// - parameter nodes: Sequence of `Node`s.
/// - parameter canonical: Output should be the "canonical" format as in the YAML specification.
/// - parameter indent: The intendation increment.
/// - parameter width: The preferred line width. @c -1 means unlimited.
/// - parameter allowUnicode: Unescaped non-ASCII characters are allowed if true.
/// - parameter lineBreak: Preferred line break.
/// - parameter explicitStart: Explicit document start `---`.
/// - parameter explicitEnd: Explicit document end `...`.
/// - parameter version: YAML version directive.
/// - parameter sortKeys: Whether or not to sort Mapping keys in lexicographic order.
///
/// - returns: YAML string.
///
/// - throws: `YamlError`.
public func serialize<Nodes>(
nodes: Nodes,
canonical: Bool = false,
@ -121,40 +129,43 @@ public func serialize<Nodes>(
version: (major: Int, minor: Int)? = nil,
sortKeys: Bool = false) throws -> String
where Nodes: Sequence, Nodes.Iterator.Element == Node {
let emitter = Emitter(
canonical: canonical,
indent: indent,
width: width,
allowUnicode: allowUnicode,
lineBreak: lineBreak,
explicitStart: explicitStart,
explicitEnd: explicitEnd,
version: version,
sortKeys: sortKeys)
try emitter.open()
try nodes.forEach(emitter.serialize)
try emitter.close()
#if USE_UTF8
return String(data: emitter.data, encoding: .utf8)!
#else
return String(data: emitter.data, encoding: .utf16)!
#endif
let emitter = Emitter(
canonical: canonical,
indent: indent,
width: width,
allowUnicode: allowUnicode,
lineBreak: lineBreak,
explicitStart: explicitStart,
explicitEnd: explicitEnd,
version: version,
sortKeys: sortKeys
)
try emitter.open()
try nodes.forEach(emitter.serialize)
try emitter.close()
#if USE_UTF8
return String(data: emitter.data, encoding: .utf8)!
#else
return String(data: emitter.data, encoding: .utf16)!
#endif
}
/// Produce YAML String from `Node`
/// Produce a YAML string from a `Node`.
///
/// - Parameters:
/// - node: `Node`
/// - canonical: output should be the "canonical" format as in the YAML specification.
/// - indent: the intendation increment.
/// - width: the preferred line width. @c -1 means unlimited.
/// - allowUnicode: unescaped non-ASCII characters are allowed if true.
/// - lineBreak: preferred line break.
/// - explicitStart: explicit document start `---`
/// - explicitEnd: explicit document end `...`
/// - version: YAML version directive
/// - Returns: YAML String
/// - Throws: `YamlError`
/// - parameter node: `Node`.
/// - parameter canonical: Output should be the "canonical" format as in the YAML specification.
/// - parameter indent: The intendation increment.
/// - parameter width: The preferred line width. @c -1 means unlimited.
/// - parameter allowUnicode: Unescaped non-ASCII characters are allowed if true.
/// - parameter lineBreak: Preferred line break.
/// - parameter explicitStart: Explicit document start `---`.
/// - parameter explicitEnd: Explicit document end `...`.
/// - parameter version: YAML version directive.
/// - parameter sortKeys: Whether or not to sort Mapping keys in lexicographic order.
///
/// - returns: YAML string.
///
/// - throws: `YamlError`.
public func serialize(
node: Node,
canonical: Bool = false,
@ -176,25 +187,30 @@ public func serialize(
explicitStart: explicitStart,
explicitEnd: explicitEnd,
version: version,
sortKeys: sortKeys)
sortKeys: sortKeys
)
}
/// Class responsible for emitting libYAML events.
public final class Emitter {
/// Line break options to use when emitting YAML.
public enum LineBreak {
/// Use CR for line breaks (Mac style).
case cr // swiftlint:disable:this identifier_name
case cr
/// Use LN for line breaks (Unix style).
case ln // swiftlint:disable:this identifier_name
case ln
/// Use CR LN for line breaks (DOS style).
case crln
}
public var data = Data()
/// Retrieve this Emitter's binary output.
public internal(set) var data = Data()
/// Configuration options to use when emitting YAML.
public struct Options {
/// Set if the output should be in the "canonical" format as in the YAML specification.
/// Set if the output should be in the "canonical" format described in the YAML specification.
public var canonical: Bool = false
/// Set the intendation increment.
/// Set the indentation value.
public var indent: Int = 0
/// Set the preferred line width. -1 means unlimited.
public var width: Int = 0
@ -207,19 +223,32 @@ public final class Emitter {
var explicitStart: Bool = false
var explicitEnd: Bool = false
/// The %YAML directive value or nil
/// The `%YAML` directive value or nil.
public var version: (major: Int, minor: Int)?
/// Set if emitter should sort keys in lexicographic order.
public var sortKeys: Bool = false
}
/// Configuration options to use when emitting YAML.
public var options: Options {
didSet {
applyOptionsToEmitter()
}
}
/// Create an `Emitter` with the specified options.
///
/// - parameter canonical: Set if the output should be in the "canonical" format described in the YAML
/// specification.
/// - parameter indent: Set the indentation value.
/// - parameter width: Set the preferred line width. -1 means unlimited.
/// - parameter allowUnicode: Set if unescaped non-ASCII characters are allowed.
/// - parameter lineBreak: Set the preferred line break.
/// - parameter explicitStart: Explicit document start `---`.
/// - parameter explicitEnd: Explicit document end `...`.
/// - parameter version: The `%YAML` directive value or nil.
/// - parameter sortKeys: Set if emitter should sort keys in lexicographic order.
public init(canonical: Bool = false,
indent: Int = 0,
width: Int = 0,
@ -249,27 +278,30 @@ public final class Emitter {
applyOptionsToEmitter()
#if USE_UTF8
yaml_emitter_set_encoding(&emitter, YAML_UTF8_ENCODING)
#else
yaml_emitter_set_encoding(&emitter, isLittleEndian ? YAML_UTF16LE_ENCODING : YAML_UTF16BE_ENCODING)
#endif
#if USE_UTF8
yaml_emitter_set_encoding(&emitter, YAML_UTF8_ENCODING)
#else
yaml_emitter_set_encoding(&emitter, isLittleEndian ? YAML_UTF16LE_ENCODING : YAML_UTF16BE_ENCODING)
#endif
}
deinit {
yaml_emitter_delete(&emitter)
}
/// Open & initialize the emmitter.
///
/// - throws: `YamlError` if the `Emitter` was already opened or closed.
public func open() throws {
switch state {
case .initialized:
var event = yaml_event_t()
#if USE_UTF8
yaml_stream_start_event_initialize(&event, YAML_UTF8_ENCODING)
#else
let encoding = isLittleEndian ? YAML_UTF16LE_ENCODING : YAML_UTF16BE_ENCODING
yaml_stream_start_event_initialize(&event, encoding)
#endif
#if USE_UTF8
yaml_stream_start_event_initialize(&event, YAML_UTF8_ENCODING)
#else
let encoding = isLittleEndian ? YAML_UTF16LE_ENCODING : YAML_UTF16BE_ENCODING
yaml_stream_start_event_initialize(&event, encoding)
#endif
try emit(&event)
state = .opened
case .opened:
@ -279,6 +311,9 @@ public final class Emitter {
}
}
/// Close the `Emitter.`
///
/// - throws: `YamlError` if the `Emitter` hasn't yet been initialized.
public func close() throws {
switch state {
case .initialized:
@ -293,6 +328,11 @@ public final class Emitter {
}
}
/// Ingest a `Node` to include when emitting the YAML output.
///
/// - parameter node: The `Node` to serialize.
///
/// - throws: `YamlError` if the `Emitter` hasn't yet been opened or has been closed.
public func serialize(node: Node) throws {
switch state {
case .initialized:
@ -318,7 +358,7 @@ public final class Emitter {
try emit(&event)
}
// private
// MARK: Private
private var emitter = yaml_emitter_t()
private enum State { case initialized, opened, closed }
@ -337,10 +377,24 @@ public final class Emitter {
}
}
// MARK: - Options Initializer
extension Emitter.Options {
// initializer without exposing internal properties
/// Create `Emitter.Options` with the specified values.
///
/// - parameter canonical: Set if the output should be in the "canonical" format described in the YAML
/// specification.
/// - parameter indent: Set the indentation value.
/// - parameter width: Set the preferred line width. -1 means unlimited.
/// - parameter allowUnicode: Set if unescaped non-ASCII characters are allowed.
/// - parameter lineBreak: Set the preferred line break.
/// - parameter explicitStart: Explicit document start `---`.
/// - parameter explicitEnd: Explicit document end `...`.
/// - parameter version: The `%YAML` directive value or nil.
/// - parameter sortKeys: Set if emitter should sort keys in lexicographic order.
public init(canonical: Bool = false, indent: Int = 0, width: Int = 0, allowUnicode: Bool = false,
lineBreak: Emitter.LineBreak = .ln, version: (major: Int, minor: Int)? = nil, sortKeys: Bool = false) {
lineBreak: Emitter.LineBreak = .ln, version: (major: Int, minor: Int)? = nil,
sortKeys: Bool = false) {
self.canonical = canonical
self.indent = indent
self.width = width
@ -351,7 +405,8 @@ extension Emitter.Options {
}
}
// MARK: implementation details
// MARK: Implementation Details
extension Emitter {
private func emit(_ event: UnsafeMutablePointer<yaml_event_t>) throws {
guard yaml_emitter_emit(&emitter, event) == 1 else {

View File

@ -8,10 +8,26 @@
import Foundation
/// `Codable`-style `Encoder` that can be used to encode an `Encodable` type to a YAML string using optional
/// user info mapping. Similar to `Foundation.JSONEncoder`.
public class YAMLEncoder {
/// Options to use when encoding to YAML.
public typealias Options = Emitter.Options
/// Options to use when encoding to YAML.
public var options = Options()
/// Creates a `YAMLEncoder` instance.
public init() {}
/// Encode a value of type `T` to a YAML string.
///
/// - parameter value: Value to encode.
/// - parameter userInfo: Additional key/values which can be used when looking up keys to encode.
///
/// - returns: The YAML string.
///
/// - throws: `EncodingError` if something went wrong while encoding.
public func encode<T: Swift.Encodable>(_ value: T, userInfo: [CodingUserInfoKey: Any] = [:]) throws -> String {
do {
let encoder = _Encoder(userInfo: userInfo)

View File

@ -8,21 +8,25 @@
import Foundation
/// The pointer position
/// The pointer position.
public struct Mark {
/// line start from 1
/// Line number starting from 1.
public let line: Int
/// column start from 1. libYAML counts columns in `UnicodeScalar`.
/// Column number starting from 1. libYAML counts columns in `UnicodeScalar`.
public let column: Int
}
// MARK: - CustomStringConvertible Conformance
extension Mark: CustomStringConvertible {
/// A textual representation of this instance.
public var description: String { return "\(line):\(column)" }
}
// MARK: Snippet
extension Mark {
/// Returns snippet string pointed by Mark instance from YAML String
/// 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

View File

@ -9,13 +9,18 @@
import Foundation
extension Node {
/// A mapping is the YAML equivalent of a `Dictionary`.
public struct Mapping {
private var pairs: [Pair<Node>]
/// This mapping's `Tag`.
public var tag: Tag
/// The style to use when emitting this `Mapping`.
public var style: Style
/// This mapping's `Mark`.
public var mark: Mark?
public enum Style: UInt32 { // swiftlint:disable:this nesting
/// The style to use when emitting a `Mapping`.
public enum Style: UInt32 {
/// Let the emitter choose the style.
case any
/// The block mapping style.
@ -24,6 +29,12 @@ extension Node {
case flow
}
/// Create a `Node.Mapping` using the specified parameters.
///
/// - parameter pairs: The array of `(Node, Node)` tuples to generate this mapping.
/// - parameter tag: This mapping's `Tag`.
/// - parameter style: The style to use when emitting this `Mapping`.
/// - parameter mark: This mapping's `Mark`.
public init(_ pairs: [(Node, Node)], _ tag: Tag = .implicit, _ style: Style = .any, _ mark: Mark? = nil) {
self.pairs = pairs.map { Pair($0.0, $0.1) }
self.tag = tag
@ -32,6 +43,7 @@ extension Node {
}
}
/// Get or set the `Node.Mapping` value if this node is a `Node.mapping`.
public var mapping: Mapping? {
get {
if case let .mapping(mapping) = self {
@ -48,47 +60,60 @@ extension Node {
}
extension Node.Mapping: Comparable {
/// :nodoc:
public static func < (lhs: Node.Mapping, rhs: Node.Mapping) -> Bool {
return lhs.pairs < rhs.pairs
}
}
extension Node.Mapping: Equatable {
/// :nodoc:
public static func == (lhs: Node.Mapping, rhs: Node.Mapping) -> Bool {
return lhs.pairs == rhs.pairs && lhs.resolvedTag == rhs.resolvedTag
}
}
extension Node.Mapping: ExpressibleByDictionaryLiteral {
/// :nodoc:
public init(dictionaryLiteral elements: (Node, Node)...) {
self.init(elements)
}
}
// MARK: - MutableCollection Conformance
extension Node.Mapping: MutableCollection {
/// :nodoc:
public typealias Element = (key: Node, value: Node)
// Sequence
// MARK: Sequence
/// :nodoc:
public func makeIterator() -> Array<Element>.Iterator {
let iterator = pairs.map(Pair.toTuple).makeIterator()
return iterator
return pairs.map(Pair.toTuple).makeIterator()
}
// Collection
// MARK: Collection
/// The index type for this mapping.
public typealias Index = Array<Element>.Index
/// :nodoc:
public var startIndex: Index {
return pairs.startIndex
}
/// :nodoc:
public var endIndex: Index {
return pairs.endIndex
}
/// :nodoc:
public func index(after index: Index) -> Index {
return pairs.index(after: index)
}
/// :nodoc:
public subscript(index: Index) -> Element {
get {
return (key: pairs[index].key, value: pairs[index].value)
@ -104,15 +129,20 @@ extension Node.Mapping: TagResolvable {
static let defaultTagName = Tag.Name.map
}
// MARK: - Dictionary-like APIs
extension Node.Mapping {
/// This mapping's keys. Similar to `Dictionary.keys`.
public var keys: LazyMapCollection<Node.Mapping, Node> {
return lazy.map { $0.key }
}
/// This mapping's keys. Similar to `Dictionary.keys`.
public var values: LazyMapCollection<Node.Mapping, Node> {
return lazy.map { $0.value }
}
/// Set or get the `Node` for the specified string's `Node` representation.
public subscript(string: String) -> Node? {
get {
return self[Node(string, tag.copy(with: .implicit))]
@ -122,10 +152,10 @@ extension Node.Mapping {
}
}
/// Set or get the specified `Node`.
public subscript(node: Node) -> Node? {
get {
let v = pairs.reversed().first(where: { $0.key == node })
return v?.value
return pairs.reversed().first(where: { $0.key == node })?.value
}
set {
if let newValue = newValue {
@ -142,6 +172,7 @@ extension Node.Mapping {
}
}
/// Get the index of the specified `Node`, if it exists in the mapping.
public func index(forKey key: Node) -> Index? {
return pairs.reversed().index(where: { $0.key == key }).map({ pairs.index(before: $0.base) })
}

View File

@ -8,18 +8,26 @@
import Foundation
// MARK: Node+Scalar
extension Node {
/// Scalar node.
public struct Scalar {
/// This node's string value.
public var string: String {
didSet {
tag = .implicit
}
}
/// This node's tag (its type).
public var tag: Tag
/// The style to be used when emitting this node.
public var style: Style
/// The location for this node.
public var mark: Mark?
public enum Style: UInt32 { // swiftlint:disable:this nesting
/// The style to use when emitting a `Scalar`.
public enum Style: UInt32 {
/// Let the emitter choose the style.
case any = 0
/// The plain scalar style.
@ -36,6 +44,12 @@ extension Node {
case folded
}
/// Create a `Node.Scalar` using the specified parameters.
///
/// - parameter string: The string to generate this scalar.
/// - parameter tag: This scalar's `Tag`.
/// - parameter style: The style to use when emitting this `Scalar`.
/// - parameter mark: This scalar's `Mark`.
public init(_ string: String, _ tag: Tag = .implicit, _ style: Style = .any, _ mark: Mark? = nil) {
self.string = string
self.tag = tag
@ -44,6 +58,7 @@ extension Node {
}
}
/// Get or set the `Node.Scalar` value if this node is a `Node.scalar`.
public var scalar: Scalar? {
get {
if case let .scalar(scalar) = self {
@ -60,12 +75,14 @@ extension Node {
}
extension Node.Scalar: Comparable {
/// :nodoc:
public static func < (lhs: Node.Scalar, rhs: Node.Scalar) -> Bool {
return lhs.string < rhs.string
}
}
extension Node.Scalar: Equatable {
/// :nodoc:
public static func == (lhs: Node.Scalar, rhs: Node.Scalar) -> Bool {
return lhs.string == rhs.string && lhs.resolvedTag == rhs.resolvedTag
}

View File

@ -8,14 +8,21 @@
import Foundation
// MARK: Node+Sequence
extension Node {
/// Sequence node.
public struct Sequence {
private var nodes: [Node]
/// This node's tag (its type).
public var tag: Tag
/// The style to be used when emitting this node.
public var style: Style
/// The location for this node.
public var mark: Mark?
public enum Style: UInt32 { // swiftlint:disable:this nesting
/// The style to use when emitting a `Sequence`.
public enum Style: UInt32 {
/// Let the emitter choose the style.
case any
/// The block sequence style.
@ -24,6 +31,12 @@ extension Node {
case flow
}
/// Create a `Node.Sequence` using the specified parameters.
///
/// - parameter nodes: The array of nodes to generate this sequence.
/// - parameter tag: This sequence's `Tag`.
/// - parameter style: The style to use when emitting this `Sequence`.
/// - parameter mark: This sequence's `Mark`.
public init(_ nodes: [Node], _ tag: Tag = .implicit, _ style: Style = .any, _ mark: Mark? = nil) {
self.nodes = nodes
self.tag = tag
@ -32,6 +45,7 @@ extension Node {
}
}
/// Get or set the `Node.Sequence` value if this node is a `Node.sequence`.
public var sequence: Sequence? {
get {
if case let .sequence(sequence) = self {
@ -50,18 +64,21 @@ extension Node {
// MARK: - Node.Sequence
extension Node.Sequence: Comparable {
/// :nodoc:
public static func < (lhs: Node.Sequence, rhs: Node.Sequence) -> Bool {
return lhs.nodes < rhs.nodes
}
}
extension Node.Sequence: Equatable {
/// :nodoc:
public static func == (lhs: Node.Sequence, rhs: Node.Sequence) -> Bool {
return lhs.nodes == rhs.nodes && lhs.resolvedTag == rhs.resolvedTag
}
}
extension Node.Sequence: ExpressibleByArrayLiteral {
/// :nodoc:
public init(arrayLiteral elements: Node...) {
self.init(elements)
}
@ -69,25 +86,31 @@ extension Node.Sequence: ExpressibleByArrayLiteral {
extension Node.Sequence: MutableCollection {
// Sequence
/// :nodoc:
public func makeIterator() -> Array<Node>.Iterator {
return nodes.makeIterator()
}
// Collection
/// :nodoc:
public typealias Index = Array<Node>.Index
/// :nodoc:
public var startIndex: Index {
return nodes.startIndex
}
/// :nodoc:
public var endIndex: Index {
return nodes.endIndex
}
/// :nodoc:
public func index(after index: Index) -> Index {
return nodes.index(after: index)
}
/// :nodoc:
public subscript(index: Index) -> Node {
get {
return nodes[index]
@ -98,6 +121,7 @@ extension Node.Sequence: MutableCollection {
}
}
/// :nodoc:
public subscript(bounds: Range<Index>) -> Array<Node>.SubSequence {
get {
return nodes[bounds]
@ -108,6 +132,7 @@ extension Node.Sequence: MutableCollection {
}
}
/// :nodoc:
public var indices: Array<Node>.Indices {
return nodes.indices
}
@ -115,25 +140,30 @@ extension Node.Sequence: MutableCollection {
extension Node.Sequence: RandomAccessCollection {
// BidirectionalCollection
/// :nodoc:
public func index(before index: Index) -> Index {
return nodes.index(before: index)
}
// RandomAccessCollection
/// :nodoc:
public func index(_ index: Index, offsetBy num: Int) -> Index {
return nodes.index(index, offsetBy: num)
}
/// :nodoc:
public func distance(from start: Index, to end: Int) -> Index {
return nodes.distance(from: start, to: end)
}
}
extension Node.Sequence: RangeReplaceableCollection {
/// :nodoc:
public init() {
self.init([])
}
/// :nodoc:
public mutating func replaceSubrange<C>(_ subrange: Range<Index>, with newElements: C)
where C: Collection, C.Iterator.Element == Node {
nodes.replaceSubrange(subrange, with: newElements)

View File

@ -8,28 +8,51 @@
import Foundation
/// YAML Node.
public enum Node {
/// Scalar node.
case scalar(Scalar)
/// Mapping node.
case mapping(Mapping)
/// Sequence node.
case sequence(Sequence)
}
extension Node {
/// Create a `Node.scalar` with a string, tag & scalar style.
///
/// - parameter string: String value for this node.
/// - parameter tag: Tag for this node.
/// - parameter style: Style to use when emitting this node.
public init(_ string: String, _ tag: Tag = .implicit, _ style: Scalar.Style = .any) {
self = .scalar(.init(string, tag, style))
}
/// Create a `Node.mapping` with a sequence of node pairs, tag & scalar style.
///
/// - parameter pairs: Pairs of nodes to use for this node.
/// - parameter tag: Tag for this node.
/// - parameter style: Style to use when emitting this node.
public init(_ pairs: [(Node, Node)], _ tag: Tag = .implicit, _ style: Mapping.Style = .any) {
self = .mapping(.init(pairs, tag, style))
}
/// Create a `Node.sequence` with a sequence of nodes, tag & scalar style.
///
/// - parameter nodes: Sequence of nodes to use for this node.
/// - parameter tag: Tag for this node.
/// - parameter style: Style to use when emitting this node.
public init(_ nodes: [Node], _ tag: Tag = .implicit, _ style: Sequence.Style = .any) {
self = .sequence(.init(nodes, tag, style))
}
}
// MARK: - Public Node Members
extension Node {
/// Accessing this property causes the tag to be resolved by tag.resolver.
/// The tag for this node.
///
/// - note: Accessing this property causes the tag to be resolved by tag.resolver.
public var tag: Tag {
switch self {
case let .scalar(scalar): return scalar.resolvedTag
@ -38,6 +61,7 @@ extension Node {
}
}
/// The location for this node.
public var mark: Mark? {
switch self {
case let .scalar(scalar): return scalar.mark
@ -46,54 +70,67 @@ extension Node {
}
}
// MARK: typed accessor properties
// MARK: - Typed accessor properties
/// This node as an `Any`, if convertible.
public var any: Any {
return tag.constructor.any(from: self)
}
/// This node as a `String`, if convertible.
public var string: String? {
return String.construct(from: self)
}
/// This node as a `Bool`, if convertible.
public var bool: Bool? {
return scalar.flatMap(Bool.construct)
}
/// This node as a `Double`, if convertible.
public var float: Double? {
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)
}
// MARK: Typed accessor methods
/// - Returns: Array of `Node`
/// Returns this node mapped as an `Array<Node>`. If the node isn't a `Node.sequence`, the array will be
/// empty.
public func array() -> [Node] {
return sequence.map(Array.init) ?? []
}
/// Typed Array using type parameter: e.g. `array(of: String.self)`
/// Typed Array using type parameter: e.g. `array(of: String.self)`.
///
/// - Parameter type: Type conforms to ScalarConstructible
/// - Returns: Array of `Type`
/// - parameter type: Type conforming to `ScalarConstructible`.
///
/// - returns: Array of `Type`.
public func array<Type: ScalarConstructible>(of type: Type.Type = Type.self) -> [Type] {
return sequence?.compactMap { $0.scalar.flatMap(type.construct) } ?? []
}
/// If the node is a `.sequence` or `.mapping`, set or get the specified `Node`.
/// If the node is a `.scalar`, this is a no-op.
public subscript(node: Node) -> Node? {
get {
switch self {
@ -120,6 +157,9 @@ extension Node {
}
}
/// If the node is a `.sequence` or `.mapping`, set or get the specified parameter's `Node`
/// representation.
/// If the node is a `.scalar`, this is a no-op.
public subscript(representable: NodeRepresentable) -> Node? {
get {
guard let node = try? representable.represented() else { return nil }
@ -131,6 +171,8 @@ extension Node {
}
}
/// If the node is a `.sequence` or `.mapping`, set or get the specified string's `Node` representation.
/// If the node is a `.scalar`, this is a no-op.
public subscript(string: String) -> Node? {
get {
return self[Node(string, tag.copy(with: .implicit))]
@ -142,7 +184,9 @@ extension Node {
}
// MARK: Hashable
extension Node: Hashable {
/// This `Node`'s Hashable `hashValue`.
public var hashValue: Int {
switch self {
case let .scalar(scalar):
@ -154,6 +198,12 @@ extension Node: Hashable {
}
}
/// Returns true if both nodes are equal.
///
/// - parameter lhs: The left hand side Node to compare.
/// - parameter rhs: The right hand side Node to compare.
///
/// - returns: True if both nodes are equal.
public static func == (lhs: Node, rhs: Node) -> Bool {
switch (lhs, rhs) {
case let (.scalar(lhs), .scalar(rhs)):
@ -168,7 +218,15 @@ extension Node: Hashable {
}
}
// MARK: Comparable
extension Node: Comparable {
/// Returns true if `lhs` is ordered before `rhs`.
///
/// - parameter lhs: The left hand side Node to compare.
/// - parameter rhs: The right hand side Node to compare.
///
/// - returns: True if `lhs` is ordered before `rhs`.
public static func < (lhs: Node, rhs: Node) -> Bool {
switch (lhs, rhs) {
case let (.scalar(lhs), .scalar(rhs)):
@ -197,31 +255,37 @@ extension Array where Element: Comparable {
}
// MARK: - ExpressibleBy*Literal
extension Node: ExpressibleByArrayLiteral {
/// Create a `Node.sequence` from an array literal of `Node`s.
public init(arrayLiteral elements: Node...) {
self = .sequence(.init(elements))
}
}
extension Node: ExpressibleByDictionaryLiteral {
/// Create a `Node.mapping` from a dictionary literal of `Node`s.
public init(dictionaryLiteral elements: (Node, Node)...) {
self = Node(elements)
}
}
extension Node: ExpressibleByFloatLiteral {
/// Create a `Node.scalar` from a float literal.
public init(floatLiteral value: Double) {
self.init(String(value), Tag(.float))
}
}
extension Node: ExpressibleByIntegerLiteral {
/// Create a `Node.scalar` from an integer literal.
public init(integerLiteral value: Int) {
self.init(String(value), Tag(.int))
}
}
extension Node: ExpressibleByStringLiteral {
/// Create a `Node.scalar` from a string literal.
public init(stringLiteral value: String) {
self.init(value)
}

View File

@ -7,19 +7,20 @@
//
#if SWIFT_PACKAGE
import CYaml
import CYaml
#endif
import Foundation
/// Parse all YAML documents in a String
/// and produce corresponding Swift objects.
///
/// - Parameters:
/// - yaml: String
/// - resolver: Resolver
/// - constructor: Constructor
/// - Returns: YamlSequence<Any>
/// - Throws: YamlError
/// - parameter yaml: String
/// - parameter resolver: Resolver
/// - parameter constructor: Constructor
///
/// - returns: YamlSequence<Any>
///
/// - throws: YamlError
public func load_all(yaml: String,
_ resolver: Resolver = .default,
_ constructor: Constructor = .default) throws -> YamlSequence<Any> {
@ -30,12 +31,13 @@ public func load_all(yaml: String,
/// Parse the first YAML document in a String
/// and produce the corresponding Swift object.
///
/// - Parameters:
/// - yaml: String
/// - resolver: Resolver
/// - constructor: Constructor
/// - Returns: Any?
/// - Throws: YamlError
/// - parameter yaml: String
/// - parameter resolver: Resolver
/// - parameter constructor: Constructor
///
/// - returns: Any?
///
/// - throws: YamlError
public func load(yaml: String,
_ resolver: Resolver = .default,
_ constructor: Constructor = .default) throws -> Any? {
@ -45,12 +47,13 @@ public func load(yaml: String,
/// Parse all YAML documents in a String
/// and produce corresponding representation trees.
///
/// - Parameters:
/// - yaml: String
/// - resolver: Resolver
/// - constructor: Constructor
/// - Returns: YamlSequence<Node>
/// - Throws: YamlError
/// - parameter yaml: String
/// - parameter resolver: Resolver
/// - parameter constructor: Constructor
///
/// - returns: YamlSequence<Node>
///
/// - throws: YamlError
public func compose_all(yaml: String,
_ resolver: Resolver = .default,
_ constructor: Constructor = .default) throws -> YamlSequence<Node> {
@ -61,22 +64,25 @@ public func compose_all(yaml: String,
/// Parse the first YAML document in a String
/// and produce the corresponding representation tree.
///
/// - Parameters:
/// - yaml: String
/// - resolver: Resolver
/// - constructor: Constructor
/// - Returns: Node?
/// - Throws: YamlError
/// - parameter yaml: String
/// - parameter resolver: Resolver
/// - parameter constructor: Constructor
///
/// - returns: Node?
///
/// - throws: YamlError
public func compose(yaml: String,
_ resolver: Resolver = .default,
_ constructor: Constructor = .default) throws -> Node? {
return try Parser(yaml: yaml, resolver: resolver, constructor: constructor).singleRoot()
}
/// Sequence that holds error
/// Sequence that holds an error.
public struct YamlSequence<T>: Sequence, IteratorProtocol {
/// This sequence's error, if any.
public private(set) var error: Swift.Error?
/// `Swift.Sequence.next()`.
public mutating func next() -> T? {
do {
return try closure()
@ -93,18 +99,22 @@ public struct YamlSequence<T>: Sequence, IteratorProtocol {
private let closure: () throws -> T?
}
/// Parses YAML strings.
public final class Parser {
// MARK: public
/// YAML string.
public let yaml: String
/// Resolver.
public let resolver: Resolver
/// Constructor.
public let constructor: Constructor
/// Set up Parser.
///
/// - Parameter string: YAML
/// - Parameter resolver: Resolver
/// - Parameter constructor: Constructor
/// - Throws: YamlError
/// - parameter string: YAML string.
/// - parameter resolver: Resolver, `.default` if omitted.
/// - parameter constructor: Constructor, `.default` if omitted.
///
/// - throws: `YamlError`.
public init(yaml string: String,
resolver: Resolver = .default,
constructor: Constructor = .default) throws {
@ -113,23 +123,23 @@ public final class Parser {
self.constructor = constructor
yaml_parser_initialize(&parser)
#if USE_UTF8
yaml_parser_set_encoding(&parser, YAML_UTF8_ENCODING)
utf8CString = string.utf8CString
utf8CString.withUnsafeBytes { bytes in
let input = bytes.baseAddress?.assumingMemoryBound(to: UInt8.self)
yaml_parser_set_input_string(&parser, input, bytes.count - 1)
}
#else
// use native endian
let isLittleEndian = 1 == 1.littleEndian
yaml_parser_set_encoding(&parser, isLittleEndian ? YAML_UTF16LE_ENCODING : YAML_UTF16BE_ENCODING)
let encoding: String.Encoding = isLittleEndian ? .utf16LittleEndian : .utf16BigEndian
data = yaml.data(using: encoding)!
data.withUnsafeBytes { bytes in
yaml_parser_set_input_string(&parser, bytes, data.count)
}
#endif
#if USE_UTF8
yaml_parser_set_encoding(&parser, YAML_UTF8_ENCODING)
utf8CString = string.utf8CString
utf8CString.withUnsafeBytes { bytes in
let input = bytes.baseAddress?.assumingMemoryBound(to: UInt8.self)
yaml_parser_set_input_string(&parser, input, bytes.count - 1)
}
#else
// use native endian
let isLittleEndian = 1 == 1.littleEndian
yaml_parser_set_encoding(&parser, isLittleEndian ? YAML_UTF16LE_ENCODING : YAML_UTF16BE_ENCODING)
let encoding: String.Encoding = isLittleEndian ? .utf16LittleEndian : .utf16BigEndian
data = yaml.data(using: encoding)!
data.withUnsafeBytes { bytes in
yaml_parser_set_input_string(&parser, bytes, data.count)
}
#endif
try parse() // Drop YAML_STREAM_START_EVENT
}
@ -139,13 +149,19 @@ public final class Parser {
/// Parse next document and return root Node.
///
/// - Returns: next Node
/// - Throws: YamlError
/// - returns: next Node.
///
/// - throws: `YamlError`.
public func nextRoot() throws -> Node? {
guard !streamEndProduced, try parse().type != YAML_STREAM_END_EVENT else { return nil }
return try loadDocument()
}
/// Parses the document expecting a single root Node and returns it.
///
/// - returns: Single root Node.
///
/// - throws: `YamlError`.
public func singleRoot() throws -> Node? {
guard !streamEndProduced, try parse().type != YAML_STREAM_END_EVENT else { return nil }
let node = try loadDocument()
@ -161,7 +177,8 @@ public final class Parser {
return node
}
// MARK: private
// MARK: - Private Members
private var anchors = [String: Node]()
private var parser = yaml_parser_t()
#if USE_UTF8
@ -171,7 +188,7 @@ public final class Parser {
#endif
}
// MARK: implementation details
// MARK: Implementation Details
private extension Parser {
private var streamEndProduced: Bool {
return parser.stream_end_produced != 0

View File

@ -7,33 +7,38 @@
//
#if SWIFT_PACKAGE
import CYaml
import CYaml
#endif
import CoreFoundation
import Foundation
public extension Node {
/// initialize `Node` with instance of `NodeRepresentable`
/// - Parameter representable: instance of `NodeRepresentable`
/// - Throws: `YamlError`
/// Initialize a `Node` with a value of `NodeRepresentable`.
///
/// - parameter representable: Value of `NodeRepresentable` to represent as a `Node`.
///
/// - throws: `YamlError`.
public init<T: NodeRepresentable>(_ representable: T) throws {
self = try representable.represented()
}
}
// MARK: - NodeRepresentable
/// Type is representabe as `Node`
/// Type is representable as `Node`.
public protocol NodeRepresentable {
/// This value's `Node` representation.
func represented() throws -> Node
}
extension Node: NodeRepresentable {
/// This value's `Node` representation.
public func represented() throws -> Node {
return self
}
}
extension Array: NodeRepresentable {
/// This value's `Node` representation.
public func represented() throws -> Node {
let nodes = try map(represent)
return Node(nodes, Tag(.seq))
@ -41,6 +46,7 @@ extension Array: NodeRepresentable {
}
extension Dictionary: NodeRepresentable {
/// This value's `Node` representation.
public func represented() throws -> Node {
let pairs = try map { (key: try represent($0.0), value: try represent($0.1)) }
return Node(pairs.sorted { $0.key < $1.key }, Tag(.map))
@ -57,30 +63,35 @@ private func represent(_ value: Any) throws -> Node {
}
// MARK: - ScalarRepresentable
/// Type is representabe as `Node.scalar`
/// Type is representable as `Node.scalar`.
public protocol ScalarRepresentable: NodeRepresentable {
/// This value's `Node.scalar` representation.
func represented() -> Node.Scalar
}
extension ScalarRepresentable {
/// This value's `Node.scalar` representation.
public func represented() throws -> Node {
return .scalar(represented())
}
}
extension Bool: ScalarRepresentable {
/// This value's `Node.scalar` representation.
public func represented() -> Node.Scalar {
return .init(self ? "true" : "false", Tag(.bool))
}
}
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))
}
@ -88,18 +99,18 @@ extension Date: ScalarRepresentable {
private var iso8601String: String {
let calendar = Calendar(identifier: .gregorian)
let nanosecond = calendar.component(.nanosecond, from: self)
#if os(Linux)
// swift-corelibs-foundation has bug with nanosecond.
// https://bugs.swift.org/browse/SR-3158
#if os(Linux)
// swift-corelibs-foundation has bug with nanosecond.
// https://bugs.swift.org/browse/SR-3158
return iso8601Formatter.string(from: self)
#else
if nanosecond != 0 {
return iso8601WithFractionalSecondFormatter.string(from: self)
.trimmingCharacters(in: characterSetZero) + "Z"
} else {
return iso8601Formatter.string(from: self)
#else
if nanosecond != 0 {
return iso8601WithFractionalSecondFormatter.string(from: self)
.trimmingCharacters(in: characterSetZero) + "Z"
} else {
return iso8601Formatter.string(from: self)
}
#endif
}
#endif
}
private var iso8601StringWithFullNanosecond: String {
@ -142,12 +153,14 @@ private let iso8601WithFractionalSecondFormatter: DateFormatter = {
}()
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))
}
}
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))
}
@ -173,6 +186,7 @@ private let floatFormatter = numberFormatter(with: 7)
//extension Float80: ScalarRepresentable {}
extension BinaryInteger {
/// This value's `Node.scalar` representation.
public func represented() -> Node.Scalar {
return .init(String(describing: self), Tag(.int))
}
@ -190,6 +204,7 @@ extension UInt64: ScalarRepresentable {}
extension UInt8: ScalarRepresentable {}
extension Optional: NodeRepresentable {
/// This value's `Node.scalar` representation.
public func represented() throws -> Node {
switch self {
case let .some(wrapped):
@ -201,18 +216,21 @@ 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 {
return .init(self)
}
@ -220,11 +238,14 @@ extension String: ScalarRepresentable {
/// MARK: - ScalarRepresentableCustomizedForCodable
/// Types conforming to this protocol can be encoded by `YamlEncoder`.
public protocol YAMLEncodable: Encodable {
/// Returns this value wrapped in a `Node`.
func box() -> Node
}
extension YAMLEncodable where Self: ScalarRepresentable {
/// Returns this value wrapped in a `Node.scalar`.
public func box() -> Node {
return .scalar(represented())
}
@ -247,18 +268,21 @@ extension URL: YAMLEncodable {}
extension String: 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))
}
}
extension Float: YAMLEncodable {
/// Returns this value wrapped in a `Node.scalar`.
public func box() -> Node {
return Node(formattedStringForCodable, Tag(.float))
}

View File

@ -8,22 +8,38 @@
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
let regexp: NSRegularExpression
internal let regexp: NSRegularExpression
/// The regex pattern used to resolve this rule.
public var pattern: String { return regexp.pattern }
/// Create a rule with the specified tag name and regex pattern.
///
/// - parameter tag: The tag name this rule should apply to.
/// - parameter pattern: The regex pattern used to resolve this rule.
///
/// - 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: [])
}
}
/// The rules used by this resolver to resolve nodes to tags.
public let rules: [Rule]
init(_ rules: [Rule] = []) { self.rules = rules }
internal init(_ rules: [Rule] = []) { self.rules = rules }
/// Resolve a tag name from a given node.
///
/// - parameter node: Node whose tag should be resolved.
///
/// - returns: The resolved tag name.
public func resolveTag(of node: Node) -> Tag.Name {
switch node {
case let .scalar(scalar):
@ -74,19 +90,28 @@ public final class Resolver {
}
}
// MARK: Defaults
extension Resolver {
/// Resolver with no rules.
public static let basic = Resolver()
/// Resolver with a default set of rules.
public static let `default` = Resolver([.bool, .int, .float, .merge, .null, .timestamp, .value])
}
// MARK: Default Resolver Rules
extension Resolver.Rule {
// swiftlint:disable:next force_try
// swiftlint:disable force_try
/// Default bool resolver rule.
public static let bool = try! Resolver.Rule(.bool, """
^(?:yes|Yes|YES|no|No|NO\
|true|True|TRUE|false|False|FALSE\
|on|On|ON|off|Off|OFF)$
""")
// swiftlint:disable:next force_try
/// Default int resolver rule.
public static let int = try! Resolver.Rule(.int, """
^(?:[-+]?0b[0-1_]+\
|[-+]?0o?[0-7_]+\
@ -94,7 +119,8 @@ extension Resolver.Rule {
|[-+]?0x[0-9a-fA-F_]+\
|[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$
""")
// swiftlint:disable:next force_try
/// Default float resolver rule.
public static let float = try! Resolver.Rule(.float, """
^(?:[-+]?(?:[0-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?\
|\\.[0-9_]+(?:[eE][-+][0-9]+)?\
@ -102,15 +128,18 @@ extension Resolver.Rule {
|[-+]?\\.(?:inf|Inf|INF)\
|\\.(?:nan|NaN|NAN))$
""")
// swiftlint:disable:next force_try
/// Default merge resolver rule.
public static let merge = try! Resolver.Rule(.merge, "^(?:<<)$")
// swiftlint:disable:next force_try
/// Default null resolver rule.
public static let null = try! Resolver.Rule(.null, """
^(?:~\
|null|Null|NULL\
|)$
""")
// swiftlint:disable:next force_try
/// Default timestamp resolver rule.
public static let timestamp = try! Resolver.Rule(.timestamp, """
^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\
|[0-9][0-9][0-9][0-9]-[0-9][0-9]?-[0-9][0-9]?\
@ -118,8 +147,11 @@ extension Resolver.Rule {
:[0-9][0-9]:[0-9][0-9](?:\\.[0-9]*)?\
(?:[ \\t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$
""")
// swiftlint:disable:next force_try
/// Default value resolver rule.
public static let value = try! Resolver.Rule(.value, "^(?:=)$")
// swiftlint:enable force_try
}
func pattern(_ string: String) -> NSRegularExpression {

View File

@ -13,8 +13,9 @@ extension String {
/// line number, column and contents at utf8 offset.
///
/// - Parameter offset: Int
/// - Returns: lineNumber: line number start from 0,
/// - parameter offset: Int
///
/// - returns: lineNumber: line number start from 0,
/// column: utf16 column start from 0,
/// contents: substring of line
func utf8LineNumberColumnAndContents(at offset: Int) -> LineNumberColumnAndContents? {
@ -26,8 +27,9 @@ extension String {
/// line number, column and contents at utf16 offset.
///
/// - Parameter offset: Int
/// - Returns: lineNumber: line number start from 0,
/// - parameter offset: Int
///
/// - returns: lineNumber: line number start from 0,
/// column: utf16 column start from 0,
/// contents: substring of line
func utf16LineNumberColumnAndContents(at offset: Int) -> LineNumberColumnAndContents? {
@ -39,8 +41,9 @@ extension String {
/// line number, column and contents at Index.
///
/// - Parameter index: String.Index
/// - Returns: lineNumber: line number start from 0,
/// - 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 {
@ -66,8 +69,9 @@ extension String {
/// substring indicated by line number.
///
/// - Parameter line: line number starts from 0.
/// - Returns: substring of line contains line ending characters
/// - 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

View File

@ -7,22 +7,32 @@
//
#if SWIFT_PACKAGE
import CYaml
import CYaml
#endif
import Foundation
/// Tags describe the the _type_ of a Node.
public final class Tag {
/// Tag name.
public struct Name: RawRepresentable {
/// This `Tag.Name`'s raw string value.
public let rawValue: String
/// Create a `Tag.Name` with a raw string value.
public init(rawValue: String) {
self.rawValue = rawValue
}
}
/// Shorthand accessor for `Tag(.implicit)`.
public static var implicit: Tag {
return Tag(.implicit)
}
/// Create a `Tag` with the specified name, resolver and constructor.
///
/// - parameter name: Tag name.
/// - parameter resolver: `Resolver` this tag should use, `.default` if omitted.
/// - parameter constructor: `Constructor` this tag should use, `.default` if omitted.
public init(_ name: Name,
_ resolver: Resolver = .default,
_ constructor: Constructor = .default) {
@ -31,6 +41,15 @@ public final class Tag {
self.name = name
}
/// Lens returning a copy of the current `Tag` with the specified overridden changes.
///
/// - note: Omitting or passing nil for a parameter will preserve the current `Tag`'s value in the copy.
///
/// - parameter name: Overridden tag name.
/// - parameter resolver: Overridden resolver.
/// - parameter constructor: Overridden constructor.
///
/// - returns: A copy of the current `Tag` with the specified overridden changes.
public func copy(with name: Name? = nil, resolver: Resolver? = nil, constructor: Constructor? = nil) -> Tag {
return .init(name ?? self.name, resolver ?? self.resolver, constructor ?? self.constructor)
}
@ -53,32 +72,38 @@ public final class Tag {
}
extension Tag: CustomStringConvertible {
/// A textual representation of this tag.
public var description: String {
return name.rawValue
}
}
extension Tag: Hashable {
/// :nodoc:
public var hashValue: Int {
return name.hashValue
}
/// :nodoc:
public static func == (lhs: Tag, rhs: Tag) -> Bool {
return lhs.name == rhs.name
}
}
extension Tag.Name: ExpressibleByStringLiteral {
/// :nodoc:
public init(stringLiteral value: String) {
self.rawValue = value
}
}
extension Tag.Name: Hashable {
/// :nodoc:
public var hashValue: Int {
return rawValue.hashValue
}
/// :nodoc:
public static func == (lhs: Tag.Name, rhs: Tag.Name) -> Bool {
return lhs.rawValue == rhs.rawValue
}

View File

@ -11,67 +11,70 @@ import CYaml
#endif
import Foundation
/// Errors thrown by Yams APIs.
public enum YamlError: Swift.Error {
// Used in `yaml_emitter_t` and `yaml_parser_t`
/// `YAML_NO_ERROR`. No error is produced.
case no
// swiftlint:disable:previous identifier_name
/// `YAML_MEMORY_ERROR`. Cannot allocate or reallocate a block of memory.
case memory
// Used in `yaml_parser_t`
/// `YAML_READER_ERROR`. Cannot read or decode the input stream.
/// - Parameters:
/// - problem: Error description.
/// - byteOffset: The byte about which the problem occured.
/// - value: The problematic value (-1 is none).
/// - yaml: YAML String which the problem occured while reading.
///
/// - parameter problem: Error description.
/// - parameter byteOffset: The byte about which the problem occured.
/// - parameter value: The problematic value (-1 is none).
/// - parameter yaml: YAML String which the problem occured while reading.
case reader(problem: String, byteOffset: Int, value: Int32, yaml: String)
// line and column start from 1, column is counted by unicodeScalars
/// `YAML_SCANNER_ERROR`. Cannot scan the input stream.
/// - Parameters:
/// - context: Error context.
/// - problem: Error description.
/// - mark: Problem position.
/// - yaml: YAML String which the problem occured while scanning.
///
/// - parameter context: Error context.
/// - parameter problem: Error description.
/// - parameter mark: Problem position.
/// - parameter yaml: YAML String which the problem occured while scanning.
case scanner(context: Context?, problem: String, Mark, yaml: String)
/// `YAML_PARSER_ERROR`. Cannot parse the input stream.
/// - Parameters:
/// - context: Error context.
/// - problem: Error description.
/// - mark: Problem position.
/// - yaml: YAML String which the problem occured while parsing.
///
/// - parameter context: Error context.
/// - parameter problem: Error description.
/// - parameter mark: Problem position.
/// - parameter yaml: YAML String which the problem occured while parsing.
case parser(context: Context?, problem: String, Mark, yaml: String)
/// `YAML_COMPOSER_ERROR`. Cannot compose a YAML document.
/// - Parameters:
/// - context: Error context.
/// - problem: Error description.
/// - mark: Problem position.
/// - yaml: YAML String which the problem occured while composing.
///
/// - parameter context: Error context.
/// - parameter problem: Error description.
/// - parameter mark: Problem position.
/// - parameter yaml: YAML String which the problem occured while composing.
case composer(context: Context?, problem: String, Mark, yaml: String)
// Used in `yaml_emitter_t`
/// `YAML_WRITER_ERROR`. Cannot write to the output stream.
/// - Parameter problem: Error description.
///
/// - parameter problem: Error description.
case writer(problem: String)
/// `YAML_EMITTER_ERROR`. Cannot emit a YAML stream.
/// - Parameter problem: Error description.
///
/// - parameter problem: Error description.
case emitter(problem: String)
/// Used in `NodeRepresentable`
/// - Parameter problem: Error description.
/// Used in `NodeRepresentable`.
///
/// - parameter problem: Error description.
case representer(problem: String)
/// The error context
/// The error context.
public struct Context: CustomStringConvertible {
/// error context
/// Context text.
public let text: String
/// context position
/// Context position.
public let mark: Mark
/// A textual representation of this instance.
public var description: String {
@ -158,13 +161,13 @@ extension YamlError: CustomStringConvertible {
extension YamlError {
private func markAndSnippet(from yaml: String, _ byteOffset: Int) -> (Mark, String)? {
#if USE_UTF8
guard let (line, column, contents) = yaml.utf8LineNumberColumnAndContents(at: byteOffset)
else { return nil }
#else
guard let (line, column, contents) = yaml.utf16LineNumberColumnAndContents(at: byteOffset / 2)
else { return nil }
#endif
#if USE_UTF8
guard let (line, column, contents) = yaml.utf8LineNumberColumnAndContents(at: byteOffset)
else { return nil }
#else
guard let (line, column, contents) = yaml.utf16LineNumberColumnAndContents(at: byteOffset / 2)
else { return nil }
#endif
return (Mark(line: line + 1, column: column + 1), contents)
}
}

View File

@ -7,23 +7,19 @@
//
#if !swift(>=4.1)
extension Sequence {
func compactMap<ElementOfResult>(
_ transform: (Self.Element
) throws -> ElementOfResult?) rethrows -> [ElementOfResult] {
return try flatMap(transform)
}
extension Sequence {
func compactMap<ElementOfResult>(
_ transform: (Self.Element
) throws -> ElementOfResult?) rethrows -> [ElementOfResult] {
return try flatMap(transform)
}
}
#endif
#if os(Linux) && !swift(>=4.2)
extension Substring {
func hasPrefix(_ prefix: String) -> Bool {
return String(self).hasPrefix(prefix)
}
extension Substring {
func hasPrefix(_ prefix: String) -> Bool {
return String(self).hasPrefix(prefix)
}
}
#endif

View File

@ -241,7 +241,7 @@ class EncoderTests: XCTestCase { // swiftlint:disable:this type_body_length
func testNodeTypeMismatch() throws {
// https://github.com/jpsim/Yams/pull/95
struct Sample: Decodable { // swiftlint:disable:this nesting
struct Sample: Decodable {
let values: [String]
}

View File

@ -35,16 +35,16 @@ func timestamp(_ timeZoneHour: Int = 0,
/// AssertEqual for Any
///
/// - Parameters:
/// - lhs: Any
/// - rhs: Any
/// - context: Closure generating String that used on generating assertion
/// - file: file path string
/// - line: line number
/// - Returns: true if lhs is equal to rhs
/// - parameter lhs: Any
/// - parameter rhs: Any
/// - parameter context: Closure generating String that used on generating assertion
/// - parameter file: file path string
/// - parameter line: line number
///
/// - returns: true if lhs is equal to rhs
@discardableResult func YamsAssertEqual(_ lhs: Any?, _ rhs: Any?,
// swiftlint:disable:previous function_body_length identifier_name
// swiftlint:disable:previous function_body_length
_ context: @autoclosure @escaping () -> String = "",
file: StaticString = #file, line: UInt = #line) -> Bool {
// use inner function for capturing `file` and `line`

View File

@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = 'Yams'
s.version = '0.7.0'
s.summary = 'A sweet and swifty Yaml parser.'
s.summary = 'A sweet and swifty YAML parser.'
s.homepage = 'https://github.com/jpsim/Yams'
s.source = { :git => s.homepage + '.git', :tag => s.version }
s.license = { :type => 'MIT', :file => 'LICENSE' }