commit
2fdfdac898
|
@ -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
|
||||
|
|
|
@ -61,3 +61,6 @@ Carthage/Build
|
|||
|
||||
fastlane/report.xml
|
||||
fastlane/screenshots
|
||||
|
||||
# Docs
|
||||
docs
|
||||
|
|
|
@ -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.'
|
|
@ -1,3 +1,10 @@
|
|||
disabled_rules:
|
||||
- identifier_name
|
||||
line_length: 120
|
||||
identifier_name:
|
||||
excluded:
|
||||
- cr
|
||||
- ln
|
||||
- no
|
||||
nesting:
|
||||
type_level: 2
|
||||
|
|
|
@ -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 |
|
57
Gemfile.lock
57
Gemfile.lock
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) })
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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' }
|
||||
|
|
Loading…
Reference in New Issue