diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/admin.xcuserdatad/IDEFindNavigatorScopes.plist b/.swiftpm/xcode/package.xcworkspace/xcuserdata/admin.xcuserdatad/IDEFindNavigatorScopes.plist new file mode 100644 index 0000000..5dd5da8 --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/xcuserdata/admin.xcuserdatad/IDEFindNavigatorScopes.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/admin.xcuserdatad/UserInterfaceState.xcuserstate b/.swiftpm/xcode/package.xcworkspace/xcuserdata/admin.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..7c6985b Binary files /dev/null and b/.swiftpm/xcode/package.xcworkspace/xcuserdata/admin.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/.swiftpm/xcode/xcuserdata/admin.xcuserdatad/xcschemes/xcschememanagement.plist b/.swiftpm/xcode/xcuserdata/admin.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..528e890 --- /dev/null +++ b/.swiftpm/xcode/xcuserdata/admin.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,57 @@ + + + + + SchemeUserState + + Builder.xcscheme_^#shared#^_ + + orderHint + 1 + + DataStructures.xcscheme_^#shared#^_ + + orderHint + 2 + + Transform.xcscheme_^#shared#^_ + + orderHint + 3 + + swift-extension-Package.xcscheme_^#shared#^_ + + orderHint + 0 + + + SuppressBuildableAutocreation + + Builder + + primary + + + DataStructures + + primary + + + Transform + + primary + + + UnitTests + + primary + + + swift-extension + + primary + + + + + diff --git a/Package.resolved b/Package.resolved index 7ed0c62..0ae1d41 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,61 +1,77 @@ { - "object": { - "pins": [ - { - "package": "CwlCatchException", - "repositoryURL": "https://github.com/mattgallagher/CwlCatchException.git", - "state": { - "branch": null, - "revision": "35f9e770f54ce62dd8526470f14c6e137cef3eea", - "version": "2.1.1" - } - }, - { - "package": "CwlPreconditionTesting", - "repositoryURL": "https://github.com/mattgallagher/CwlPreconditionTesting.git", - "state": { - "branch": null, - "revision": "fb7a26374e8570ff5c68142e5c83406d6abae0d8", - "version": "2.0.2" - } - }, - { - "package": "Nimble", - "repositoryURL": "https://github.com/Quick/Nimble.git", - "state": { - "branch": null, - "revision": "c93f16c25af5770f0d3e6af27c9634640946b068", - "version": "9.2.1" - } - }, - { - "package": "Quick", - "repositoryURL": "https://github.com/Quick/Quick.git", - "state": { - "branch": null, - "revision": "bd86ca0141e3cfb333546de5a11ede63f0c4a0e6", - "version": "4.0.0" - } - }, - { - "package": "SwiftDocCPlugin", - "repositoryURL": "https://github.com/apple/swift-docc-plugin", - "state": { - "branch": null, - "revision": "9b1258905c21fc1b97bf03d1b4ca12c4ec4e5fda", - "version": "1.2.0" - } - }, - { - "package": "SymbolKit", - "repositoryURL": "https://github.com/apple/swift-docc-symbolkit", - "state": { - "branch": null, - "revision": "b45d1f2ed151d057b54504d653e0da5552844e34", - "version": "1.0.0" - } - } - ] - }, - "version": 1 + "pins" : [ + { + "identity" : "cwlcatchexception", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mattgallagher/CwlCatchException.git", + "state" : { + "revision" : "3ef6999c73b6938cc0da422f2c912d0158abb0a0", + "version" : "2.2.0" + } + }, + { + "identity" : "cwlpreconditiontesting", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mattgallagher/CwlPreconditionTesting.git", + "state" : { + "revision" : "2ef56b2caf25f55fa7eef8784c30d5a767550f54", + "version" : "2.2.1" + } + }, + { + "identity" : "nimble", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Quick/Nimble.git", + "state" : { + "revision" : "c93f16c25af5770f0d3e6af27c9634640946b068", + "version" : "9.2.1" + } + }, + { + "identity" : "quick", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Quick/Quick.git", + "state" : { + "revision" : "bd86ca0141e3cfb333546de5a11ede63f0c4a0e6", + "version" : "4.0.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb", + "version" : "1.1.0" + } + }, + { + "identity" : "swift-docc-plugin", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-docc-plugin", + "state" : { + "revision" : "26ac5758409154cc448d7ab82389c520fa8a8247", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-docc-symbolkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-docc-symbolkit", + "state" : { + "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", + "version" : "1.0.0" + } + }, + { + "identity" : "swift-identified-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-identified-collections", + "state" : { + "revision" : "d1e45f3e1eee2c9193f5369fa9d70a6ddad635e8", + "version" : "1.0.0" + } + } + ], + "version" : 2 } diff --git a/Package.swift b/Package.swift index 894d322..ab06910 100644 --- a/Package.swift +++ b/Package.swift @@ -22,19 +22,27 @@ let package = Package( .package(url: "https://github.com/Quick/Quick.git", from: "4.0.0"), .package(url: "https://github.com/Quick/Nimble.git", from: "9.2.0"), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.1.0"), + .package(url: "https://github.com/apple/swift-collections", from: "1.0.2"), + .package(url: "https://github.com/pointfreeco/swift-identified-collections", from: "1.0.0"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "Transform", - dependencies: []), + dependencies: [ + .product(name: "IdentifiedCollections", package: "swift-identified-collections"), + .product(name: "OrderedCollections", package: "swift-collections"), + ]), .target( name: "DataStructures", dependencies: []), .target( name: "Builder", - dependencies: []), + dependencies: [ + .product(name: "IdentifiedCollections", package: "swift-identified-collections"), + .product(name: "OrderedCollections", package: "swift-collections"), + ]), .testTarget( name: "UnitTests", dependencies: ["DataStructures", "Transform", "Quick", "Nimble"]), diff --git a/Sources/Builder/IdentifiedArrayBuilder.swift b/Sources/Builder/IdentifiedArrayBuilder.swift new file mode 100644 index 0000000..8e6f6af --- /dev/null +++ b/Sources/Builder/IdentifiedArrayBuilder.swift @@ -0,0 +1,14 @@ +import IdentifiedCollections +import Foundation + +@available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) +public extension IdentifiedArray where Element: Identifiable { + init(@ArrayBuilder builder: () -> [Element]) where ID == Element.ID { + var identifiedArray: IdentifiedArrayOf = [] + let items = builder() + for item in items { + identifiedArray.updateOrAppend(item) + } + self = identifiedArray + } +} diff --git a/Sources/Transform/Collection/Dictionary+.swift b/Sources/Transform/Collection/Dictionary+.swift index 13f14cf..1802acb 100644 --- a/Sources/Transform/Collection/Dictionary+.swift +++ b/Sources/Transform/Collection/Dictionary+.swift @@ -22,4 +22,14 @@ public extension Dictionary where Value: Hashable { func has(key: Self.Key) -> Bool { self[key] != nil } + + func pick(keys: [Key]) -> [Key: Value] { + keys.reduce(into: [Key: Value]()) { partialResult, item in + partialResult[item] = self[item] + } + } + + func keys(for value: Value) -> [Key] { + return keys.filter({ self[$0] == value}) + } } diff --git a/Sources/Transform/Collection/IdentifiedArray+.swift b/Sources/Transform/Collection/IdentifiedArray+.swift new file mode 100644 index 0000000..70ecd34 --- /dev/null +++ b/Sources/Transform/Collection/IdentifiedArray+.swift @@ -0,0 +1,17 @@ +import IdentifiedCollections + +public extension IdentifiedArray { + func toArray() -> [Element] { + var array: [Element] = [] + for value in self { + array.append(value) + } + return array + } +} + +public extension IdentifiedArray where Element: Hashable { + func toSet() -> Set { + toArray().toSet() + } +} diff --git a/Sources/Transform/Fundamental/Bool+.swift b/Sources/Transform/Fundamental/Bool+.swift new file mode 100644 index 0000000..ad349b4 --- /dev/null +++ b/Sources/Transform/Fundamental/Bool+.swift @@ -0,0 +1,10 @@ +public extension Bool { + + func toInt() -> Int { + self ? 1 : 0 + } + + func toString() -> String { + self ? "true" : "false" + } +} diff --git a/Sources/Transform/Fundamental/Double+.swift b/Sources/Transform/Fundamental/Double+.swift index 1432a31..ed3eb07 100644 --- a/Sources/Transform/Fundamental/Double+.swift +++ b/Sources/Transform/Fundamental/Double+.swift @@ -20,4 +20,20 @@ public extension Double { func toFloat() -> Float { toString().toFloat()! } + + func toNano() -> Double { + self * 1_000_000_000 + } + + func toGiga() -> Double { + self / 1_000_000_000 + } + + var nano: Double { + self * 1_000_000_000 + } + + var giga: Double { + self / 1_000_000_000 + } } diff --git a/Sources/Transform/Fundamental/Int+.swift b/Sources/Transform/Fundamental/Int+.swift index eac52b5..c687a6b 100644 --- a/Sources/Transform/Fundamental/Int+.swift +++ b/Sources/Transform/Fundamental/Int+.swift @@ -25,4 +25,13 @@ public extension Int { return UInt(self) } + func toNano() -> Double { + self.toDouble()/1_000_000_000 + } +} + +extension UInt64 { + public init(seconds: Double) { + self.init(seconds.nano) + } } diff --git a/Sources/Transform/Fundamental/URL+.swift b/Sources/Transform/Fundamental/URL+.swift index e683829..69db52a 100644 --- a/Sources/Transform/Fundamental/URL+.swift +++ b/Sources/Transform/Fundamental/URL+.swift @@ -4,4 +4,15 @@ public extension URL { func toString() -> String { absoluteString } + + func toQueryParameters() -> [String: String]? { + guard let queryItems = URLComponents(url: self, resolvingAgainstBaseURL: false)?.queryItems else { + return nil + } + return Dictionary(queryItems.lazy.compactMap { + guard let value = $0.value else { return nil } + return ($0.name, value) + }) { first, _ in first } + } + } diff --git a/Sources/Transform/Important/ArrayExt.swift b/Sources/Transform/Important/ArrayExt.swift index 3c295f0..577f25d 100644 --- a/Sources/Transform/Important/ArrayExt.swift +++ b/Sources/Transform/Important/ArrayExt.swift @@ -1,7 +1,17 @@ import Foundation +import IdentifiedCollections + +public extension Array { + @discardableResult + func appending(value element: Element) -> Self { + var copy = self + copy.append(element) + return copy + } +} public extension Array where Element: Equatable { - var unique: [Element] { + var removedDuplicates: [Element] { var uniqueValues: [Element] = [] forEach { item in guard !uniqueValues.contains(item) else { return } @@ -10,3 +20,25 @@ public extension Array where Element: Equatable { return uniqueValues } } + +public extension Array where Element == [String: Any] { + + func toData(options opt: JSONSerialization.WritingOptions = []) -> Data? { + try? JSONSerialization.data(withJSONObject: self, options: opt) + } +} + + +public extension Array where Element: Codable { + + func toData(options opt: JSONSerialization.WritingOptions = []) -> Data? { + compactMap({$0.toDictionary()}).toData(options: opt) + } +} + +public extension IdentifiedArray where Element: Codable { + + func toData(options opt: JSONSerialization.WritingOptions = []) -> Data? { + toArray().toData(options: opt) + } +} diff --git a/Sources/Transform/Important/DataExt.swift b/Sources/Transform/Important/DataExt.swift index 6109e4f..5fd3ddd 100644 --- a/Sources/Transform/Important/DataExt.swift +++ b/Sources/Transform/Important/DataExt.swift @@ -19,7 +19,6 @@ public extension Data { } } -#if os(iOS) func toData(keyPath: String? = nil) -> Self { guard let keyPath = keyPath else { return self @@ -38,5 +37,35 @@ public extension Data { } return self } -#endif + + func hasData(keyPath: String? = nil) -> Bool { + guard let keyPath = keyPath else { return true } + do { + let json = try JSONSerialization.jsonObject(with: self, options: []) + if let nestedJson = (json as AnyObject).value(forKeyPath: keyPath) { + guard JSONSerialization.isValidJSONObject(nestedJson) else { + return false + } + let _ = try JSONSerialization.data(withJSONObject: nestedJson) + return false + } + } catch { + return false + } + return false + } + + subscript(_ keyPath: String? = nil) -> Self? { + toData(keyPath: keyPath) + } + + func toDataPrettyPrinted() -> Self { + do { + let dataAsJSON = try JSONSerialization.jsonObject(with: self) + let prettyData = try JSONSerialization.data(withJSONObject: dataAsJSON, options: .prettyPrinted) + return prettyData + } catch { + return self // fallback to original data if it can't be serialized. + } + } } diff --git a/Sources/Transform/Important/IdentifiedArrayExt.swift b/Sources/Transform/Important/IdentifiedArrayExt.swift new file mode 100644 index 0000000..372c21b --- /dev/null +++ b/Sources/Transform/Important/IdentifiedArrayExt.swift @@ -0,0 +1,54 @@ +import IdentifiedCollections +import Foundation + +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) +public extension IdentifiedArray where Element: Identifiable { + + init(_ builder: () -> IdentifiedArray) { + self = builder() + } + + init(_ builder: () -> Array) where ID == Element.ID { + self = builder().toIdentifiedArray() + } +} + +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) +public extension Array where Element: Identifiable { + func toIdentifiedArray() -> IdentifiedArrayOf { + var identifiedArray: IdentifiedArrayOf = [] + for value in self { + identifiedArray.updateOrAppend(value) + } + return identifiedArray + } +} + +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) +public extension IdentifiedArray where Element: Identifiable { + + @discardableResult + mutating func updateOrAppend(_ items: Self) -> Self { + for item in items { + self.updateOrAppend(item) + } + return self + } + + @discardableResult + mutating func updateOrAppend(_ items: [Element]) -> Self { + for item in items { + self.updateOrAppend(item) + } + return self + } + + @discardableResult + mutating func updateOrAppend(ifLet item: Element?) -> Self { + guard let item = item else { + return self + } + self.updateOrAppend(item) + return self + } +} diff --git a/Sources/Transform/Important/MutableCollectionExt.swift b/Sources/Transform/Important/MutableCollectionExt.swift deleted file mode 100644 index b93fd65..0000000 --- a/Sources/Transform/Important/MutableCollectionExt.swift +++ /dev/null @@ -1,14 +0,0 @@ -import Foundation - -public extension MutableCollection { - subscript(safe index: Index) -> Element? { - get { - indices.contains(index) ? self[index] : nil - } - mutating set { - if indices.contains(index), let value = newValue { - self[index] = value - } - } - } -} diff --git a/Sources/Transform/Important/SetExt.swift b/Sources/Transform/Important/SetExt.swift index 8aa716d..b7ec423 100644 --- a/Sources/Transform/Important/SetExt.swift +++ b/Sources/Transform/Important/SetExt.swift @@ -1,5 +1,13 @@ import Foundation public extension Set { - + @discardableResult + mutating func toggle(_ element: Element) -> Element { + if self.contains(element) { + self.remove(element) + } else { + self.insert(element) + } + return element + } } diff --git a/Sources/Transform/Important/StringExt.swift b/Sources/Transform/Important/StringExt.swift index 5875f5c..6cc3974 100644 --- a/Sources/Transform/Important/StringExt.swift +++ b/Sources/Transform/Important/StringExt.swift @@ -1,7 +1,7 @@ import Foundation public extension String { - func toData(using:String.Encoding = .utf8) -> Data? { + func toData(using: String.Encoding = .utf8) -> Data? { return self.data(using: using) }