mirror of
https://github.com/GayPizzaSpecifications/darwin-apk.git
synced 2025-08-03 21:41:31 +00:00
Implement dependency wrapping & version spec
This commit is contained in:
117
Sources/apk/Common/ApkRequirement.swift
Normal file
117
Sources/apk/Common/ApkRequirement.swift
Normal file
@ -0,0 +1,117 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import Foundation
|
||||
|
||||
internal struct ApkRequirement {
|
||||
static func extract(blob extract: String) throws(ParseError) -> (String, ApkVersionSpecification) {
|
||||
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 {
|
||||
//
|
||||
if !comparer.contains(.conflict) {
|
||||
comparer.formUnion(.any)
|
||||
}
|
||||
(nameEnd, versionStart) = (dependStr.endIndex, dependStr.endIndex)
|
||||
}
|
||||
|
||||
// Parse version specification
|
||||
let spec = try ApkVersionSpecification(comparer, version: dependStr[versionStart...])
|
||||
let name = String(dependStr[..<nameEnd])
|
||||
return (name, spec)
|
||||
}
|
||||
}
|
||||
|
||||
extension ApkRequirement {
|
||||
enum ParseError: Error, LocalizedError {
|
||||
case brokenSpec
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .brokenSpec: "Invalid version specification"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Private Implementation
|
||||
|
||||
fileprivate extension ApkRequirement {
|
||||
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 {
|
||||
init(_ bits: ApkRequirement.ComparatorBits, version: Substring) throws(ApkRequirement.ParseError) {
|
||||
if bits == [ .conflict ] {
|
||||
self = .conflict
|
||||
} else {
|
||||
if bits.contains(.conflict) {
|
||||
throw .brokenSpec
|
||||
} else if bits == [ .any ] {
|
||||
self = .any
|
||||
} else {
|
||||
self = .constraint(op: try .init(bits), version: String(version))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension ApkVersionSpecification.Operator {
|
||||
init(_ bits: ApkRequirement.ComparatorBits) throws(ApkRequirement.ParseError) {
|
||||
self = switch bits.subtracting(.conflict) {
|
||||
case .equals: .equals
|
||||
case .less: .less
|
||||
case .greater: .greater
|
||||
//case .checksum: .checksum
|
||||
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
|
||||
}
|
||||
}
|
46
Sources/apk/Common/ApkVersionSpecification.swift
Normal file
46
Sources/apk/Common/ApkVersionSpecification.swift
Normal file
@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
enum ApkVersionSpecification: Equatable {
|
||||
case any
|
||||
case constraint(op: Operator, version: String)
|
||||
case conflict
|
||||
}
|
||||
|
||||
extension ApkVersionSpecification: CustomStringConvertible {
|
||||
var description: String {
|
||||
switch self {
|
||||
case .any: ""
|
||||
case .conflict: "!"
|
||||
case .constraint(let op, let version): "\(op)\(version)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ApkVersionSpecification {
|
||||
enum Operator: Equatable {
|
||||
case equals
|
||||
case fuzzyEquals
|
||||
case greater
|
||||
case less
|
||||
case greaterEqual
|
||||
case lessEqual
|
||||
case greaterFuzzy
|
||||
case lessFuzzy
|
||||
}
|
||||
}
|
||||
|
||||
extension ApkVersionSpecification.Operator: CustomStringConvertible {
|
||||
var description: String {
|
||||
switch self {
|
||||
//case .checksum: "><"
|
||||
case .lessEqual: "<="
|
||||
case .greaterEqual: ">="
|
||||
case .lessFuzzy: "<~"
|
||||
case .greaterFuzzy: ">~"
|
||||
case .equals: "="
|
||||
case .less: "<"
|
||||
case .greater: ">"
|
||||
case .fuzzyEquals: "~"
|
||||
}
|
||||
}
|
||||
}
|
22
Sources/apk/Index/ApkIndexDependency.swift
Normal file
22
Sources/apk/Index/ApkIndexDependency.swift
Normal file
@ -0,0 +1,22 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import Foundation
|
||||
|
||||
struct ApkIndexDependency: ApkIndexRequirementRef {
|
||||
let name: String
|
||||
let versionSpec: ApkVersionSpecification
|
||||
|
||||
init(extract: String) throws(ApkRequirement.ParseError) {
|
||||
(self.name, self.versionSpec) = try ApkRequirement.extract(blob: extract)
|
||||
}
|
||||
}
|
||||
|
||||
extension ApkIndexDependency: CustomStringConvertible {
|
||||
var description: String {
|
||||
switch self.versionSpec {
|
||||
case .any: self.name
|
||||
case .conflict: "!\(self.name)"
|
||||
case .constraint(let op, let version): "\(self.name)\(op)\(version)"
|
||||
}
|
||||
}
|
||||
}
|
10
Sources/apk/Index/ApkIndexInstallIf.swift
Normal file
10
Sources/apk/Index/ApkIndexInstallIf.swift
Normal file
@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
struct ApkIndexInstallIf: ApkIndexRequirementRef {
|
||||
let name: String
|
||||
let versionSpec: ApkVersionSpecification
|
||||
|
||||
init(extract: String) throws(ApkRequirement.ParseError) {
|
||||
(self.name, self.versionSpec) = try ApkRequirement.extract(blob: extract)
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct ApkIndexPackage {
|
||||
struct ApkIndexPackage: Hashable {
|
||||
let indexChecksum: String //TODO: Decode cus why not
|
||||
let name: String
|
||||
let version: String
|
||||
@ -17,9 +17,9 @@ struct ApkIndexPackage {
|
||||
let buildTime: Date?
|
||||
let commit: String?
|
||||
let providerPriority: UInt16?
|
||||
let dependencies: [String] //TODO: stuff
|
||||
let provides: [String] //TODO: stuff
|
||||
let installIf: [String] //TODO: stuff
|
||||
let dependencies: [ApkIndexDependency]
|
||||
let provides: [ApkIndexProvides]
|
||||
let installIf: [ApkIndexInstallIf]
|
||||
|
||||
var downloadFilename: String { "\(self.name)-\(version).apk" }
|
||||
|
||||
@ -39,9 +39,9 @@ extension ApkIndexPackage {
|
||||
var packageSize: UInt64? = nil
|
||||
var installedSize: UInt64? = nil
|
||||
|
||||
var dependencies = [String]()
|
||||
var provides = [String]()
|
||||
var installIf = [String]()
|
||||
var dependencies = [ApkIndexDependency]()
|
||||
var provides = [ApkIndexProvides]()
|
||||
var installIf = [ApkIndexInstallIf]()
|
||||
|
||||
// Optional fields
|
||||
var architecture: String? = nil
|
||||
@ -67,7 +67,8 @@ extension ApkIndexPackage {
|
||||
case "A":
|
||||
architecture = record.value
|
||||
case "D":
|
||||
dependencies = record.value.components(separatedBy: " ")
|
||||
do { dependencies = try ApkIndexDependency.extract(record.value) }
|
||||
catch { throw .badValue(key: record.key) }
|
||||
case "C":
|
||||
indexChecksum = record.value // base64-encoded SHA1 hash prefixed with "Q1"
|
||||
case "S":
|
||||
@ -81,9 +82,11 @@ extension ApkIndexPackage {
|
||||
}
|
||||
installedSize = value
|
||||
case "p":
|
||||
provides = record.value.components(separatedBy: " ")
|
||||
do { provides = try ApkIndexProvides.extract(record.value) }
|
||||
catch { throw .badValue(key: record.key) }
|
||||
case "i":
|
||||
installIf = record.value.components(separatedBy: " ")
|
||||
do { installIf = try ApkIndexInstallIf.extract(record.value) }
|
||||
catch { throw .badValue(key: record.key) }
|
||||
case "o":
|
||||
origin = record.value
|
||||
case "m":
|
||||
|
9
Sources/apk/Index/ApkIndexProvides.swift
Normal file
9
Sources/apk/Index/ApkIndexProvides.swift
Normal file
@ -0,0 +1,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
struct ApkIndexProvides: ApkIndexRequirementRef {
|
||||
let name: String
|
||||
|
||||
init(extract: String) throws(ApkRequirement.ParseError) {
|
||||
(self.name, _) = try ApkRequirement.extract(blob: extract)
|
||||
}
|
||||
}
|
30
Sources/apk/Index/ApkIndexRequirementRef.swift
Normal file
30
Sources/apk/Index/ApkIndexRequirementRef.swift
Normal file
@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
protocol ApkIndexRequirementRef: Equatable, Hashable {
|
||||
var name: String { get }
|
||||
var invert: Bool { get }
|
||||
|
||||
init(extract: String) throws(ApkRequirement.ParseError)
|
||||
|
||||
func satisfied(by other: ApkIndexPackage) -> Bool
|
||||
}
|
||||
|
||||
extension ApkIndexRequirementRef {
|
||||
var invert: Bool { false }
|
||||
func satisfied(by _: ApkIndexPackage) -> Bool { true }
|
||||
|
||||
static func == (lhs: Self, rhs: Self) -> Bool {
|
||||
return !(lhs.name != rhs.name && !lhs.invert)
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
self.name.hash(into: &hasher)
|
||||
}
|
||||
|
||||
static func extract<T: ApkIndexRequirementRef>(_ blob: String) throws(ApkRequirement.ParseError) -> [T] {
|
||||
return try blob.components(separatedBy: " ")
|
||||
.map { token throws(ApkRequirement.ParseError) in
|
||||
try .init(extract: token)
|
||||
}
|
||||
}
|
||||
}
|
@ -47,7 +47,7 @@ public struct ApkIndexUpdater {
|
||||
}
|
||||
|
||||
for package in index.packages {
|
||||
print(package)
|
||||
print("\(package.name):", package.dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user