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 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2018-2021 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
/// A version according to the semantic versioning specification.
///
/// A package version consists of three integers separated by periods, for example `1.0.0`. It must conform to the semantic versioning standard in order to ensure
/// that your package behaves in a predictable manner once developers update their
/// package dependency to a newer version. To achieve predictability, the semantic versioning specification proposes a set of rules and
/// requirements that dictate how version numbers are assigned and incremented. To learn more about the semantic versioning specification, visit
/// [Semantic Versioning 2.0.0](https://semver.org).
///
/// - term The major version: The first digit of a version, or _major version_,
/// signifies breaking changes to the API that require updates to existing
/// clients. For example, the semantic versioning specification considers
/// renaming an existing type, removing a method, or changing a method's
/// signature breaking changes. This also includes any backward-incompatible bug
/// fixes or behavioral changes of the existing API.
///
/// - term The minor version:
/// Update the second digit of a version, or _minor version_, if you add
/// functionality in a backward-compatible manner. For example, the semantic
/// versioning specification considers adding a new method or type without
/// changing any other API to be backward-compatible.
///
/// - term The patch version:
/// Increase the third digit of a version, or _patch version_, if you're making
/// a backward-compatible bug fix. This allows clients to benefit from bugfixes
/// to your package without incurring any maintenance burden.
public struct Version: Sendable {
/// The major version according to the semantic versioning standard.
public let major: Int
/// The minor version according to the semantic versioning standard.
public let minor: Int
/// The patch version according to the semantic versioning standard.
public let patch: Int
/// The pre-release identifier according to the semantic versioning standard, such as `-beta.1`.
public let prereleaseIdentifiers: [String]
/// The build metadata of this version according to the semantic versioning standard, such as a commit hash.
public let buildMetadataIdentifiers: [String]
/// Initializes a version struct with the provided components of a semantic version.
///
/// - Parameters:
/// - major: The major version number.
/// - minor: The minor version number.
/// - patch: The patch version number.
/// - prereleaseIdentifiers: The pre-release identifier.
/// - buildMetaDataIdentifiers: Build metadata that identifies a build.
///
/// - Precondition: `major >= 0 && minor >= 0 && patch >= 0`.
/// - Precondition: `prereleaseIdentifiers` can contain only ASCII alpha-numeric characters and "-".
/// - Precondition: `buildMetaDataIdentifiers` can contain only ASCII alpha-numeric characters and "-".
public init(
_ major: Int,
_ minor: Int,
_ patch: Int,
prereleaseIdentifiers: [String] = [],
buildMetadataIdentifiers: [String] = []
) {
precondition(major >= 0 && minor >= 0 && patch >= 0, "Negative versioning is invalid.")
precondition(
prereleaseIdentifiers.allSatisfy {
$0.allSatisfy { $0.isASCII && ($0.isLetter || $0.isNumber || $0 == "-") }
},
#"Pre-release identifiers can contain only ASCII alpha-numeric characters and "-"."#
)
precondition(
buildMetadataIdentifiers.allSatisfy {
$0.allSatisfy { $0.isASCII && ($0.isLetter || $0.isNumber || $0 == "-") }
},
#"Build metadata identifiers can contain only ASCII alpha-numeric characters and "-"."#
)
self.major = major
self.minor = minor
self.patch = patch
self.prereleaseIdentifiers = prereleaseIdentifiers
self.buildMetadataIdentifiers = buildMetadataIdentifiers
}
}
extension Version: Comparable {
// Although `Comparable` inherits from `Equatable`, it does not provide a new default implementation of `==`, but instead uses `Equatable`'s default synthesised implementation. The compiler-synthesised `==`` is composed of [member-wise comparisons](https://github.com/apple/swift-evolution/blob/main/proposals/0185-synthesize-equatable-hashable.md#implementation-details), which leads to a false `false` when 2 semantic versions differ by only their build metadata identifiers, contradicting SemVer 2.0.0's [comparison rules](https://semver.org/#spec-item-10).
/// Returns a Boolean value indicating whether two values are equal.
///
/// Equality is the inverse of inequality. For any values `a` and `b`, `a ==
/// b` implies that `a != b` is `false`.
///
/// - Parameters:
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
///
/// - Returns: A boolean value indicating the result of the equality test.
@inlinable
public static func == (lhs: Version, rhs: Version) -> Bool {
!(lhs < rhs) && !(lhs > rhs)
}
/// Returns a Boolean value indicating whether the value of the first
/// argument is less than that of the second argument.
///
/// The precedence is determined according to rules described in the [Semantic Versioning 2.0.0](https://semver.org) standard, paragraph 11.
///
/// - Parameters:
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
public static func < (lhs: Version, rhs: Version) -> Bool {
let lhsComparators = [lhs.major, lhs.minor, lhs.patch]
let rhsComparators = [rhs.major, rhs.minor, rhs.patch]
if lhsComparators != rhsComparators {
return lhsComparators.lexicographicallyPrecedes(rhsComparators)
}
guard lhs.prereleaseIdentifiers.count > 0 else {
return false // Non-prerelease lhs >= potentially prerelease rhs
}
guard rhs.prereleaseIdentifiers.count > 0 else {
return true // Prerelease lhs < non-prerelease rhs
}
for (lhsPrereleaseIdentifier, rhsPrereleaseIdentifier) in zip(lhs.prereleaseIdentifiers, rhs.prereleaseIdentifiers) {
if lhsPrereleaseIdentifier == rhsPrereleaseIdentifier {
continue
}
// Check if either of the 2 pre-release identifiers is numeric.
let lhsNumericPrereleaseIdentifier = Int(lhsPrereleaseIdentifier)
let rhsNumericPrereleaseIdentifier = Int(rhsPrereleaseIdentifier)
if let lhsNumericPrereleaseIdentifier,
let rhsNumericPrereleaseIdentifier = rhsNumericPrereleaseIdentifier {
return lhsNumericPrereleaseIdentifier < rhsNumericPrereleaseIdentifier
} else if lhsNumericPrereleaseIdentifier != nil {
return true // numeric pre-release < non-numeric pre-release
} else if rhsNumericPrereleaseIdentifier != nil {
return false // non-numeric pre-release > numeric pre-release
} else {
return lhsPrereleaseIdentifier < rhsPrereleaseIdentifier
}
}
return lhs.prereleaseIdentifiers.count < rhs.prereleaseIdentifiers.count
}
}
extension Version: CustomStringConvertible {
/// A textual description of the version object.
public var description: String {
var base = "\(major).\(minor).\(patch)"
if !prereleaseIdentifiers.isEmpty {
base += "-" + prereleaseIdentifiers.joined(separator: ".")
}
if !buildMetadataIdentifiers.isEmpty {
base += "+" + buildMetadataIdentifiers.joined(separator: ".")
}
return base
}
}
|