@@ -43,6 +43,11 @@ public struct SemanticVersion: Codable, Equatable, Hashable {
4343 self . preRelease = preRelease
4444 self . build = build
4545 }
46+
47+ public enum PreReleaseIdentifier : Equatable {
48+ case alphanumeric( String )
49+ case numeric( Int )
50+ }
4651}
4752
4853
@@ -74,26 +79,81 @@ extension SemanticVersion: Comparable {
7479 if lhs. major != rhs. major { return lhs. major < rhs. major }
7580 if lhs. minor != rhs. minor { return lhs. minor < rhs. minor }
7681 if lhs. patch != rhs. patch { return lhs. patch < rhs. patch }
77- if lhs. preRelease != rhs. preRelease {
78- // Ensure stable versions sort after their betas ...
79- if lhs. isStable { return false }
80- if rhs. isStable { return true }
81- // ... otherwise sort by preRelease
82- return lhs. preRelease < rhs. preRelease
83- }
84- // ... and build
85- return lhs. build < rhs. build
82+
83+ // A stable release takes precedence over a pre-release
84+ if lhs. isStable != rhs. isStable { return rhs. isStable }
85+
86+ // Otherwise compare the pre-releases per section 11.4
87+ // See: https://semver.org/#spec-item-11
88+ return lhs. preReleaseIdentifiers < rhs. preReleaseIdentifiers
89+
90+ // Note that per section 10, buildmetadata MUST not be
91+ // considered when determining precedence
92+ // See: https://semver.org/#spec-item-10
8693 }
8794}
8895
8996
9097extension SemanticVersion {
91- public var isStable : Bool { return preRelease. isEmpty && build . isEmpty }
98+ public var isStable : Bool { return preRelease. isEmpty }
9299 public var isPreRelease : Bool { return !isStable }
93100 public var isMajorRelease : Bool { return isStable && ( major > 0 && minor == 0 && patch == 0 ) }
94101 public var isMinorRelease : Bool { return isStable && ( minor > 0 && patch == 0 ) }
95102 public var isPatchRelease : Bool { return isStable && patch > 0 }
96103 public var isInitialRelease : Bool { return self == . init( 0 , 0 , 0 ) }
104+
105+ public var preReleaseIdentifiers : [ PreReleaseIdentifier ] {
106+ return preRelease
107+ . split ( separator: " . " )
108+ . map { PreReleaseIdentifier ( String ( $0) ) }
109+ }
110+ }
111+
112+ extension SemanticVersion . PreReleaseIdentifier {
113+ init ( _ rawValue: String ) {
114+ if let number = Int ( rawValue) {
115+ self = . numeric( number)
116+ } else {
117+ self = . alphanumeric( rawValue)
118+ }
119+ }
120+ }
121+
122+ extension SemanticVersion . PreReleaseIdentifier : Comparable {
123+ public static func < ( lhs: Self , rhs: Self ) -> Bool {
124+ // These rules are laid out in section 11.4 of the semver spec
125+ // See: https://semver.org/#spec-item-11
126+ switch ( lhs, rhs) {
127+ case ( . numeric, . alphanumeric) :
128+ // 11.4.3 - Numeric identifiers always have lower precedence than non-numeric identifiers
129+ return true
130+ case ( . alphanumeric, . numeric) :
131+ // 11.4.3 - Numeric identifiers always have lower precedence than non-numeric identifiers
132+ return false
133+ case ( . numeric( let lhInt) , . numeric( let rhInt) ) :
134+ // 11.4.1 - Identifiers consisting of only digits are compared numerically
135+ return lhInt < rhInt
136+ case ( . alphanumeric( let lhString) , . alphanumeric( let rhString) ) :
137+ // 11.4.2 - Identifiers with letters or hyphens are compared lexically in ASCII sort order
138+ return lhString < rhString
139+ }
140+ }
141+ }
142+
143+
144+ extension Array : Comparable where Element == SemanticVersion . PreReleaseIdentifier {
145+ public static func < ( lhs: Self , rhs: Self ) -> Bool {
146+ // Per section 11.4 of the semver spec, compare left to right until a
147+ // difference is found.
148+ // See: https://semver.org/#spec-item-11
149+ for (lhIdentifier, rhIdentifier) in zip ( lhs, rhs) {
150+ if lhIdentifier != rhIdentifier { return lhIdentifier < rhIdentifier }
151+ }
152+
153+ // 11.4.4 - A larger set of identifiers will have a higher precendence
154+ // than a smaller set, if all the preceding identifiers are equal.
155+ return lhs. count < rhs. count
156+ }
97157}
98158
99159#if swift(>=5.5)
0 commit comments