-
Notifications
You must be signed in to change notification settings - Fork 3
/
SwiftPackageUpdate.swift
95 lines (84 loc) · 3.86 KB
/
SwiftPackageUpdate.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
import Foundation
import Releases
import ShellOut
enum SwiftPackageUpdateError: Error, Equatable {
case loadingFailed(String)
case parsingNumberFailed(String)
case parsingNumberMismatch(String, Int, Int)
case parsingDependencyFailed(String, [String])
}
struct SwiftPackageUpdate {
private static let numberRegex: NSRegularExpression = {
// swiftlint:disable:next force_try
try! NSRegularExpression(pattern: "^([0-9]+) dependenc(y|ies) ha(s|ve) changed(.|:)$", options: [.anchorsMatchLines])
}()
private static let dependencyRegex: NSRegularExpression = {
// swiftlint:disable:next force_try
try! NSRegularExpression(pattern: "^~ ([^\\s]*) ([^\\s]*) -> ([^\\s]*) ([^\\s]*)$", options: [.anchorsMatchLines])
}()
let name: String
let oldVersion: Version
let newVersion: Version
static func checkUpdates(in folder: URL) throws -> [SwiftPackageUpdate] {
let string = try readUpdates(in: folder)
return try parseOutput(string)
}
private static func readUpdates(in folder: URL) throws -> String {
do {
let output = try shellOut(to: "swift", arguments: ["package", "--package-path", "\"\(folder.path)\"", "update", "--dry-run" ])
return output
} catch {
let error = error as! ShellOutError // swiftlint:disable:this force_cast
throw SwiftPackageUpdateError.loadingFailed(error.message)
}
}
private static func parseOutput(_ output: String) throws -> [SwiftPackageUpdate] {
let numberMatches = output.matchingStrings(regex: self.numberRegex)
guard
let match = numberMatches[safe: 0],
let numberString = match[safe: 1],
let number = Int(numberString)
else {
throw SwiftPackageUpdateError.parsingNumberFailed(output)
}
let dependencyMatches = output.matchingStrings(regex: self.dependencyRegex)
guard dependencyMatches.count == number else {
throw SwiftPackageUpdateError.parsingNumberMismatch(output, number, dependencyMatches.count)
}
var result = [SwiftPackageUpdate]()
for dependencyMatch in dependencyMatches {
guard
let name = dependencyMatch[safe: 1],
let oldVersionString = dependencyMatch[safe: 2],
let oldVersion = try? Version(string: oldVersionString),
let name2 = dependencyMatch[safe: 3],
let newVersionString = dependencyMatch[safe: 4],
let newVersion = try? Version(string: newVersionString),
name == name2
else {
throw SwiftPackageUpdateError.parsingDependencyFailed(output, dependencyMatch)
}
result.append(SwiftPackageUpdate(name: name, oldVersion: oldVersion, newVersion: newVersion))
}
return result
}
}
extension SwiftPackageUpdate: CustomStringConvertible {
public var description: String {
"\("\(oldVersion) -> \(newVersion)".yellow) (Without changing requirements)"
}
}
extension SwiftPackageUpdateError: LocalizedError {
public var errorDescription: String? {
switch self {
case let .loadingFailed(error):
return "Could not get package update data, swift package update failed: \(error)"
case let .parsingNumberFailed(output):
return "Could not parse number of package updates from the swift package update output: \(output)"
case let .parsingNumberMismatch(output, number, parsed):
return "The number of package updates (\(number)) from the swift package update output mismatches the number of updates parsed (\(parsed)). Output: \(output)"
case let .parsingDependencyFailed(output, match):
return "Could not parse a dependency (\(match)) from the swift package update output: \(output)"
}
}
}