-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathPlistEntry+Access.swift
160 lines (135 loc) · 6.61 KB
/
PlistEntry+Access.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import Foundation
public extension PlistEntry {
// MARK: Dict
enum PlistEntryError: CustomStringConvertible, Error {
case typeMismatch(PlistEntry, expectedType: Any)
case noObjectForKey(entry: PlistEntry, key: String)
public var description: String {
switch self {
case let .typeMismatch(entry, expectedType):
return "Plist entry \(entry) type differs from expected type \(expectedType)"
case let .noObjectForKey(entry, key):
return "Entry \(entry) does not have object for key \(key)"
}
}
}
// MARK: Dict
/// Casts this entry to dict entry and extracts its value.
/// - Throws: Error if this entry is not an dict plist entry.
/// - Returns: Dictionary of `String` to `PlistEntry` elements.
func dictEntry() throws -> [String: PlistEntry] {
try matchType()
}
/// Casts this entry to dict entry and extracts an optional value for a given key.
/// - Parameter key: The key which `PlistEntry` should be provided, is present.
/// - Throws: Error if this entry is not an dict plist entry.
/// - Returns: `PlistEntry` for a given `key`, or `nil` if no value is present for the given key.
func optionalEntry(forKey key: String) throws -> PlistEntry? {
try dictEntry()[key]
}
/// Casts this entry to dict entry and extracts an expected value for a given key.
/// - Parameter key: The key which `PlistEntry` should be provided, if present.
/// - Throws: Error if this entry is not an dict plist entry, or if no value is present for the provided `key`.
/// - Returns: `PlistEntry` for a given `key`.
func entry(forKey key: String) throws -> PlistEntry {
guard let entry = try optionalEntry(forKey: key) else {
throw PlistEntryError.noObjectForKey(entry: self, key: key)
}
return entry
}
/// Casts this entry to dict entry and extracts the optional values for the given keys.
/// - Parameter keys: The keys which `PlistEntry` should be provided, if present.
/// - Throws: Error if this entry is not an dict plist entry.
/// - Returns: A map from key to its plist entry. If the entry is missing for some given key, no error will be thrown.
func optionalEntries(forKeys keys: [String]) throws -> [String: PlistEntry] {
try keys.reduce(into: [String: PlistEntry]()) { result, key in
if let entry = try optionalEntry(forKey: key) {
result[key] = entry
}
}
}
/// Casts this entry to dict entry and extracts the required values for the given keys.
/// - Parameter keys: The keys which `PlistEntry` should be provided, if present.
/// - Throws: Error if this entry is not an dict plist entry, or if entry is missing for any provided key.
/// - Returns: A map from key to its plist entry.
func entries(forKeys keys: [String]) throws -> [String: PlistEntry] {
try keys.reduce(into: [String: PlistEntry]()) { result, key in
result[key] = try entry(forKey: key)
}
}
/// Casts dict entry to a dict whose values are expected to have a single type
/// - Parameter valueType: Expected type of all values of the dictionary
/// - Throws: This method throws error if any value can't be casted to the given type
/// - Returns: Dictionary with string keys and `T` values.
func toTypedDict<T>(_ valueType: T.Type) throws -> [String: T] {
try dictEntry().mapValues { try $0.matchType() }
}
/// Casts this entry to dict entry and returns its keys.
/// - Throws: Error if this entry is not an dict plist entry.
/// - Returns: An array of keys of dict entry.
func allKeys() throws -> [String] {
Array(try dictEntry().keys.map { String($0) })
}
// MARK: Array
/// Casts this entry to array and extracts its value.
/// - Throws: Error if this entry is not an array plist entry.
/// - Returns: Array of `PlistEntry` elements.
func arrayEntry() throws -> [PlistEntry] {
try matchType()
}
/// Casts this entry to array and provides a `PlistEntry` under a given `index`.
/// - Throws: Error if this entry is not an array plist entry.
/// - Returns: `PlistEntry` element under given `index`.
func entry(atIndex index: Int) throws -> PlistEntry {
try arrayEntry()[index]
}
/// Casts array entry to a array whose elements are expected to have a single type `T`.
/// - Parameter valueType: Expected type of all elements of array
/// - Throws: This method throws error if any element can't be casted to the given type
/// - Returns: Array of `T` elements.
func toTypedArray<T>(_ type: T.Type) throws -> [T] {
try arrayEntry().compactMap { try $0.matchType() }
}
// MARK: Leaf Types Supported by Plist
func boolValue() throws -> Bool {
try matchType()
}
func stringValue() throws -> String {
try matchType()
}
func numberValue() throws -> Double {
try matchType()
}
func dateValue() throws -> Date {
try matchType()
}
func dataValue() throws -> Data {
try matchType()
}
// MARK: Casting to any Type
/// Casts this plist entry to a given type `T`.
/// Plist supports a limited set of types: `Array`, `Dictionary` with `String` keys, `Date`, `Data`, `Bool`, `Number` (represented as `Double` here for simplicity), `String`.
/// Attempt to cast to any other type will likely throw an error.
/// Note: array and dict entries will return a value with `PlistEntry` objects. Use array and dict specific shortcut methods to cast the whole collection to a desired type instead.
/// - Throws: `PlistEntryError` if this plist entry type is not castable to `T`.
/// - Returns: A value casted to `T`.
func matchType<T>() throws -> T {
switch self {
case .array(let value):
if let value = value as? T { return value }
case .dict(let value):
if let value = value as? T { return value }
case .bool(let value):
if let value = value as? T { return value }
case .data(let value):
if let value = value as? T { return value }
case .date(let value):
if let value = value as? T { return value }
case .number(let value):
if let value = value as? T { return value }
case .string(let value):
if let value = value as? T { return value }
}
throw PlistEntryError.typeMismatch(self, expectedType: T.self)
}
}