Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6320f13

Browse files
committed
feat: Version struct
1 parent df81f72 commit 6320f13

File tree

2 files changed

+730
-0
lines changed

2 files changed

+730
-0
lines changed
Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
//
2+
// Version.swift
3+
// GoodStructs
4+
//
5+
// Created by Filip Šašala on 28/05/2024.
6+
//
7+
8+
import Foundation
9+
import RegexBuilder
10+
11+
// MARK: - Version
12+
13+
@available(iOS 16.0, *)
14+
@available(macOS 13.0, *)
15+
public struct Version {
16+
17+
public var major: UInt
18+
public var minor: UInt
19+
public var patch: UInt
20+
public var stage: ReleaseStage
21+
public var build: UInt
22+
public var suffix: String?
23+
24+
public init(
25+
major: UInt = 1,
26+
minor: UInt = 0,
27+
patch: UInt = 0,
28+
prerelease: ReleaseStage = .release,
29+
build: UInt = 0,
30+
suffix: String? = nil
31+
) {
32+
self.major = major
33+
self.minor = minor
34+
self.patch = patch
35+
self.stage = prerelease
36+
self.build = build
37+
self.suffix = suffix
38+
}
39+
40+
public init(string: String) throws {
41+
let majorVersion = TryCapture {
42+
OneOrMore(.digit)
43+
} transform: { major in
44+
UInt(major)
45+
}
46+
let minorVersion = Optionally {
47+
TryCapture {
48+
OneOrMore(.digit)
49+
} transform: { minor in
50+
UInt(minor)
51+
}
52+
}
53+
let patchVersion = Optionally {
54+
TryCapture {
55+
OneOrMore(.digit)
56+
} transform: { patch in
57+
UInt(patch)
58+
}
59+
}
60+
let releaseStage = Optionally {
61+
TryCapture {
62+
OneOrMore(.word)
63+
} transform: { stage in
64+
ReleaseStage(rawValue: String(stage))
65+
}
66+
}
67+
let buildNumber = Optionally {
68+
TryCapture {
69+
OneOrMore(.digit)
70+
} transform: { build in
71+
UInt(build)
72+
}
73+
}
74+
let suffix = Optionally {
75+
TryCapture {
76+
OneOrMore(.any)
77+
} transform: { suffix in
78+
String(suffix)
79+
}
80+
}
81+
82+
let regex = Regex {
83+
majorVersion
84+
Optionally(".")
85+
minorVersion
86+
Optionally(".")
87+
patchVersion
88+
// Stage
89+
Optionally("-")
90+
releaseStage
91+
// Bulid number
92+
Optionally(".")
93+
buildNumber
94+
// Suffix
95+
Optionally("+")
96+
suffix
97+
}
98+
99+
if let result = string.firstMatch(of: regex)?.output {
100+
let major = result.1
101+
let minor = result.2 ?? 0
102+
let patch = result.3 ?? 0
103+
let stage = result.4 ?? .release
104+
let build = result.5 ?? 0
105+
let suffix = result.6
106+
107+
self.major = major
108+
self.minor = minor
109+
self.patch = patch
110+
self.stage = stage
111+
self.build = build
112+
self.suffix = suffix
113+
} else {
114+
throw VersionError.invalidVersion(string)
115+
}
116+
}
117+
118+
public init(branchName string: String) throws {
119+
let majorVersion = TryCapture {
120+
OneOrMore(.digit)
121+
} transform: { major in
122+
UInt(major)
123+
}
124+
let minorVersion = Optionally {
125+
TryCapture {
126+
OneOrMore(.digit)
127+
} transform: { minor in
128+
UInt(minor)
129+
}
130+
}
131+
let patchVersion = Optionally {
132+
TryCapture {
133+
OneOrMore(.digit)
134+
} transform: { patch in
135+
UInt(patch)
136+
}
137+
}
138+
let releaseStage = TryCapture {
139+
OneOrMore(.any)
140+
} transform: { stage in
141+
ReleaseStage(rawValue: String(stage))
142+
}
143+
let suffix = Optionally {
144+
TryCapture {
145+
OneOrMore(.any)
146+
} transform: { suffix in
147+
String(suffix)
148+
}
149+
}
150+
151+
let regex = Regex {
152+
"release/"
153+
// Release stage
154+
releaseStage
155+
"_"
156+
// Release version
157+
majorVersion
158+
Optionally(".")
159+
minorVersion
160+
Optionally(".")
161+
patchVersion
162+
// Suffix
163+
Optionally("+")
164+
suffix
165+
}
166+
167+
if let result = string.firstMatch(of: regex)?.output {
168+
self.major = result.2
169+
self.minor = result.3 ?? 0
170+
self.patch = result.4 ?? 0
171+
self.stage = result.1
172+
self.build = 0
173+
self.suffix = result.5
174+
} else {
175+
throw VersionError.invalidVersion(string)
176+
}
177+
}
178+
179+
}
180+
181+
// MARK: - CustonStringConvertible
182+
183+
@available(iOS 16.0, *)
184+
@available(macOS 13.0, *)
185+
extension Version: CustomStringConvertible, CustomDebugStringConvertible {
186+
187+
public var description: String {
188+
let usesBuildStages = !(stage == .release && build == 0)
189+
190+
if usesBuildStages {
191+
return debugDescription
192+
} else {
193+
return version
194+
}
195+
}
196+
197+
public var debugDescription: String {
198+
if let suffix, !suffix.isEmpty {
199+
return "\(major).\(minor).\(patch)-\(stage).\(build)+\(suffix)"
200+
} else {
201+
return "\(major).\(minor).\(patch)-\(stage).\(build)"
202+
}
203+
}
204+
205+
public var version: String {
206+
"\(major).\(minor).\(patch)"
207+
}
208+
209+
}
210+
211+
// MARK: - Equatable, Comparable
212+
213+
@available(iOS 16.0, *)
214+
@available(macOS 13.0, *)
215+
extension Version: Equatable, Comparable {
216+
217+
public static func == (lhs: Version, rhs: Version) -> Bool {
218+
lhs.major == rhs.major
219+
&& lhs.minor == rhs.minor
220+
&& lhs.patch == rhs.patch
221+
&& lhs.stage == rhs.stage
222+
&& lhs.build == rhs.build
223+
}
224+
225+
public static func < (lhs: Version, rhs: Version) -> Bool {
226+
if lhs.major != rhs.major {
227+
return lhs.major < rhs.major
228+
} else if lhs.minor != rhs.minor {
229+
return lhs.minor < rhs.minor
230+
} else if lhs.patch != rhs.patch {
231+
return lhs.patch < rhs.patch
232+
} else if lhs.stage.comparableValue != rhs.stage.comparableValue {
233+
return lhs.stage.comparableValue < rhs.stage.comparableValue
234+
} else if lhs.build != rhs.build {
235+
return lhs.build < rhs.build
236+
} else {
237+
return (lhs.suffix ?? "") < (rhs.suffix ?? "")
238+
}
239+
}
240+
241+
}
242+
243+
// MARK: - Codable
244+
245+
@available(iOS 16.0, *)
246+
@available(macOS 13.0, *)
247+
extension Version: Codable {
248+
249+
public init(from decoder: any Decoder) throws {
250+
let container = try decoder.singleValueContainer()
251+
let versionString = try container.decode(String.self)
252+
253+
try self.init(string: versionString)
254+
}
255+
256+
public func encode(to encoder: any Encoder) throws {
257+
var container = encoder.singleValueContainer()
258+
try container.encode(self.description)
259+
}
260+
261+
}
262+
263+
// MARK: - Parser, Formatter
264+
265+
@available(iOS 16.0, *)
266+
@available(macOS 13.0, *)
267+
public struct VersionParseStrategy: ParseStrategy {
268+
269+
public typealias ParseInput = String
270+
public typealias ParseOutput = Version
271+
272+
public func parse(_ value: String) throws -> Version {
273+
try Version(string: value)
274+
}
275+
276+
}
277+
278+
@available(iOS 16.0, *)
279+
@available(macOS 13.0, *)
280+
struct VersionFormatStyle: ParseableFormatStyle {
281+
282+
public typealias Strategy = VersionParseStrategy
283+
public typealias FormatInput = Version
284+
public typealias FormatOutput = String
285+
286+
public var parseStrategy: Strategy {
287+
Strategy()
288+
}
289+
290+
public func format(_ value: Version) -> String {
291+
value.version
292+
}
293+
294+
}
295+
296+
// MARK: - Stage
297+
298+
@available(iOS 16.0, *)
299+
@available(macOS 13.0, *)
300+
public enum ReleaseStage: String {
301+
302+
case alpha
303+
case beta
304+
case release
305+
306+
public init?(rawValue: String) {
307+
switch rawValue {
308+
case "alpha", "bitrise":
309+
self = .alpha
310+
311+
case "beta", "testflight_dev", "testflight":
312+
self = .beta
313+
314+
case "rc", "release", "testflight_release", "appstore":
315+
self = .release
316+
317+
default:
318+
return nil
319+
}
320+
}
321+
322+
public var comparableValue: UInt {
323+
switch self {
324+
case .alpha:
325+
return 1
326+
327+
case .beta:
328+
return 2
329+
330+
case .release:
331+
return 3
332+
}
333+
}
334+
335+
}
336+
337+
// MARK: - Errors
338+
339+
@available(iOS 16.0, *)
340+
@available(macOS 13.0, *)
341+
public enum VersionError: Error {
342+
343+
case invalidVersion(String)
344+
345+
}

0 commit comments

Comments
 (0)