Files
darwin-apk/Sources/apk/Version/ApkVersionRequirement.swift

131 lines
3.9 KiB
Swift
Raw Normal View History

2024-11-10 17:51:53 +11:00
/*
* darwin-apk © 2024 Gay Pizza Specifications
* SPDX-License-Identifier: Apache-2.0
*/
import Foundation
internal struct ApkVersionRequirement: Sendable, Hashable {
2024-11-11 21:06:37 +11:00
let name: String
let versionSpec: ApkVersionSpecification
init(name: String, spec: ApkVersionSpecification) {
self.name = name
self.versionSpec = spec
}
init(extract: Substring) throws(ParseError) {
var comparer: ComparatorBits = []
var dependStr = extract[...]
let nameEnd: String.Index, versionStart: String.Index
// Check for bang prefix to indicate a conflict
if dependStr.first == "!" {
comparer.insert(.conflict)
dependStr = dependStr[dependStr.index(after: dependStr.startIndex)...]
}
// Match comparator
if let range = dependStr.firstRange(where: { [ "<", "=", ">", "~" ].contains($0) }) {
for c in dependStr[range] {
switch c {
case "<": comparer.insert(.less)
case "=": comparer.insert(.equals)
case ">": comparer.insert(.greater)
case "~": comparer.formUnion([ .fuzzy, .equals ])
default: break
}
}
(nameEnd, versionStart) = (range.lowerBound, range.upperBound)
} else {
2025-07-05 19:58:57 +10:00
comparer.formUnion(.any)
(nameEnd, versionStart) = (dependStr.endIndex, dependStr.endIndex)
}
// Parse version specification
2024-11-11 21:06:37 +11:00
self.versionSpec = try ApkVersionSpecification(comparer, version: dependStr[versionStart...])
self.name = String(dependStr[..<nameEnd])
}
}
2024-11-22 20:19:49 +11:00
extension ApkVersionRequirement: CustomStringConvertible {
2024-11-11 21:06:37 +11:00
var description: String {
switch self.versionSpec {
2025-07-05 19:58:57 +10:00
case .any(let invert):
"\(invert ? "!" : "")\(self.name)"
case .constraint(let invert, let op, let version):
"\(invert ? "!" : "")\(self.name)\(op)\(version)"
2024-11-11 21:06:37 +11:00
}
}
}
2024-11-22 20:19:49 +11:00
extension ApkVersionRequirement {
enum ParseError: Error, LocalizedError {
case brokenSpec
var errorDescription: String? {
switch self {
case .brokenSpec: "Invalid version specification"
}
}
}
}
//MARK: - Private Implementation
2024-11-22 20:19:49 +11:00
fileprivate extension ApkVersionRequirement {
struct ComparatorBits: OptionSet {
let rawValue: UInt8
static let equals: Self = Self(rawValue: 1 << 0)
static let less: Self = Self(rawValue: 1 << 1)
static let greater: Self = Self(rawValue: 1 << 2)
static let fuzzy: Self = Self(rawValue: 1 << 3)
static let conflict: Self = Self(rawValue: 1 << 4)
static let any: Self = [ .equals, .less, .greater ]
static let checksum: Self = [ .less, .greater ]
}
}
fileprivate extension ApkVersionSpecification {
2024-11-22 20:19:49 +11:00
init(_ bits: ApkVersionRequirement.ComparatorBits, version: Substring) throws(ApkVersionRequirement.ParseError) {
2025-07-05 19:58:57 +10:00
let invert = bits.contains(.conflict)
self = if bits.subtracting(.conflict) == [ .any ] {
.any(invert: invert)
} else {
2025-07-05 19:58:57 +10:00
.constraint(invert: invert, op: try .init(bits), version: String(version))
}
}
}
fileprivate extension ApkVersionSpecification.Operator {
2024-11-22 20:19:49 +11:00
init(_ bits: ApkVersionRequirement.ComparatorBits) throws(ApkVersionRequirement.ParseError) {
self = switch bits.subtracting(.conflict) {
case .equals: .equals
case .less: .less
case .greater: .greater
//case .checksum: .checksum
2025-07-05 19:58:57 +10:00
case [ .equals, .less ]: .lessEqual
case [ .equals, .greater ]: .greaterEqual
case [ .fuzzy, .equals ], .fuzzy: .fuzzyEquals
case [ .fuzzy, .equals, .less ]: .lessFuzzy
case [ .fuzzy, .equals, .greater ]: .greaterFuzzy
default: throw .brokenSpec
}
}
}
fileprivate extension Substring {
func firstRange(where predicate: (Character) throws -> Bool) rethrows -> Range<Self.Index>? {
guard let start = try self.firstIndex(where: predicate) else {
return nil
}
var idx = start
repeat {
idx = self.index(after: idx)
} while try idx != self.endIndex && predicate(self[idx])
return start..<idx
}
}