forked from nsoperations/Carthage
-
Notifications
You must be signed in to change notification settings - Fork 0
/
BinaryProject.swift
110 lines (93 loc) · 4.34 KB
/
BinaryProject.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
import Foundation
import Result
public struct BinaryProjectFile: Equatable {
let url: URL
let configuration: String?
let swiftVersion: PinnedVersion?
init(url: URL, configuration: String?, swiftVersion: PinnedVersion?) {
self.url = url
self.configuration = configuration
self.swiftVersion = swiftVersion
}
}
/// Represents a binary dependency
public struct BinaryProject: Equatable {
private let definitions: [PinnedVersion: [BinaryProjectFile]]
public init(definitions: [PinnedVersion: [BinaryProjectFile]]) {
self.definitions = definitions
}
public init(urls: [PinnedVersion: URL]) {
self.definitions = urls.reduce(into: [PinnedVersion: [BinaryProjectFile]](), { dict, entry in
dict[entry.key] = [BinaryProjectFile(url: entry.value, configuration: nil, swiftVersion: nil)]
})
}
public var versions: [PinnedVersion] {
return Array(definitions.keys)
}
public func binaryURL(for version: PinnedVersion, configuration: String, swiftVersion: PinnedVersion) -> URL? {
guard let binaryProjectFiles = definitions[version] else {
return nil
}
return binaryProjectFiles.first(where: { binaryProjectFile -> Bool in
(binaryProjectFile.configuration.map{ $0 == configuration } ?? true) && (binaryProjectFile.swiftVersion.map { $0 == swiftVersion } ?? true)
}).map {
$0.url
}
}
public static func from(jsonData: Data) -> Result<BinaryProject, BinaryJSONError> {
do {
let result = try parse(jsonData: jsonData)
return .success(result)
} catch let error as BinaryJSONError {
return .failure(error)
} catch {
return .failure(BinaryJSONError.invalidJSON(error.localizedDescription))
}
}
private static func parse(jsonData: Data) throws -> BinaryProject {
guard let json = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else {
throw BinaryJSONError.invalidJSON("Root level JSON object should be a dictionary")
}
var definitions = [PinnedVersion: [BinaryProjectFile]]()
for (key, value) in json {
let pinnedVersion: PinnedVersion
switch SemanticVersion.from(Scanner(string: key)) {
case .success:
pinnedVersion = PinnedVersion(key)
case let .failure(error):
throw BinaryJSONError.invalidVersion(error)
}
if let stringValue = value as? String {
let binaryURL = try parseURL(stringValue: stringValue)
let projectFile = BinaryProjectFile(url: binaryURL, configuration: nil, swiftVersion: nil)
definitions[pinnedVersion, default: [BinaryProjectFile]()].append(projectFile)
} else if let dictValues = value as? [[String: String]] {
for dictValue in dictValues {
var swiftVersion: PinnedVersion?
guard let urlString = dictValue["url"] else {
throw BinaryJSONError.invalidJSON("No url property found for version: \(pinnedVersion)")
}
let binaryURL = try parseURL(stringValue: urlString)
let configuration = dictValue["configuration"]
if let versionString = dictValue["swiftVersion"] {
swiftVersion = PinnedVersion(versionString)
}
let projectFile = BinaryProjectFile(url: binaryURL, configuration: configuration, swiftVersion: swiftVersion)
definitions[pinnedVersion, default: [BinaryProjectFile]()].append(projectFile)
}
} else {
throw BinaryJSONError.invalidJSON("Value should either be a string or a dictionary containing the properties 'url', 'configuration' and 'swiftVersion'")
}
}
return BinaryProject(definitions: definitions)
}
private static func parseURL(stringValue: String) throws -> URL {
guard let binaryURL = URL(string: stringValue) else {
throw BinaryJSONError.invalidURL(stringValue)
}
guard binaryURL.scheme == "file" || binaryURL.scheme == "https" else {
throw BinaryJSONError.nonHTTPSURL(binaryURL)
}
return binaryURL
}
}