Skip to content

Commit

Permalink
Merge pull request #5 from mxcl/more-tolerant
Browse files Browse the repository at this point in the history
More tolerance
  • Loading branch information
repo-ranger[bot] authored Feb 20, 2019
2 parents aac9b1f + e68bcee commit 087c91f
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 34 deletions.
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ jobs:
- <<: *xcodebuild
osx_image: xcode10.2
xcode_destination: platform=macOS
name: macOS / Swift 5.0
name: macOS / Swift 5.0-beta

- &linux
env: SWIFT_VERSION=4.2.1
env: SWIFT_VERSION=4.2.2
os: linux
name: Linux / Swift 4.2.1
name: Linux / Swift 4.2.2
language: generic
install: eval "$(curl -sL https://swiftenv.fuller.li/install.sh)"
script: swift build
Expand Down
61 changes: 46 additions & 15 deletions Sources/Version.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public struct Version: Hashable {

/**
Create a version object.
- Note: Integers are made absolute since negative integers are not allowed, yet it is conventional Swift to take `Int` over `UInt` where possible.
- Remark: This initializer variant provided for more readable code when initializing with static integers.
*/
@inlinable
Expand All @@ -51,27 +52,14 @@ public struct Version: Hashable {

/**
Creates a version object.
- Note: Integers are made absolute since negative integers are not allowed, yet it is conventional Swift to take `Int` over `UInt` where possible.
- Remark: This initializer variant provided when it would be more readable than the nameless variant.
*/
@inlinable
public init(major: Int, minor: Int, patch: Int, prereleaseIdentifiers: [String] = [], buildMetadataIdentifiers: [String] = []) {
self.init(major, minor, patch, prereleaseIdentifiers: prereleaseIdentifiers, buildMetadataIdentifiers: buildMetadataIdentifiers)
}

/**
Creates a version object.
- Remark: This initializer variant uses a more tolerant parser, eg. `10.1` parses to `Version(10,1,0)`
*/
public init?(tolerant: String) {
if let major = Int(tolerant) {
self.init(major, 0, 0)
} else if let pair = Double(tolerant) {
self.init("\(pair).0")
} else {
self.init(tolerant)
}
}

/// Represents `0.0.0`
public static let null = Version(0,0,0)
}
Expand All @@ -90,7 +78,7 @@ extension Version: LosslessStringConvertible {
let requiredCharacters = string.prefix(upTo: requiredEndIndex)
let requiredComponents = requiredCharacters
.split(separator: ".", maxSplits: 2, omittingEmptySubsequences: false)
.map(String.init).compactMap({ Int($0) }).filter({ $0 >= 0 })
.compactMap{ Int($0) }

guard requiredComponents.count == 3 else { return nil }

Expand Down Expand Up @@ -124,3 +112,46 @@ extension Version: LosslessStringConvertible {
return base
}
}

public extension Version {
/**
Creates a version object.
- Remark: This initializer variant uses a more tolerant parser, eg. `10.1` parses to `Version(10,1,0)`.
- Remark: This initializer will not recognizer builds-metadata-identifiers.
- Remark: Tolerates an initial `v` character.
*/
init?(tolerant: String) {
let string: Substring
if tolerant.first == "v" {
string = tolerant.dropFirst()
} else {
string = Substring(tolerant)
}

let prereleaseStartIndex = string.firstIndex(of: "-")
let requiredEndIndex = prereleaseStartIndex ?? string.endIndex
let requiredCharacters = string.prefix(upTo: requiredEndIndex)
let maybes = requiredCharacters.split(separator: ".", maxSplits: 2, omittingEmptySubsequences: false).map{ Int($0) }

guard !maybes.contains(nil), 1...3 ~= maybes.count else {
return nil
}

var requiredComponents = maybes.map{ $0! }
while requiredComponents.count < 3 {
requiredComponents.append(0)
}

major = requiredComponents[0]
minor = requiredComponents[1]
patch = requiredComponents[2]

if let prereleaseStartIndex = prereleaseStartIndex {
let identifiers = string[string.index(after: prereleaseStartIndex)..<string.endIndex]
prereleaseIdentifiers = identifiers.split(separator: ".").map(String.init)
} else {
prereleaseIdentifiers = []
}
buildMetadataIdentifiers = []
}
}
105 changes: 89 additions & 16 deletions Tests/Case.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,38 @@ class VersionTests: XCTestCase {
}

func testFromString() {
let badStrings = [
"", "1", "1.2", "1.2.3.4", "1.2.3.4.5",
"a", "1.a", "a.2", "a.2.3", "1.a.3", "1.2.a",
"-1.2.3", "1.-2.3", "1.2.-3", ".1.2.3", "v.1.2.3", "1.2..3", "v1.2.3",
]
for str in badStrings {
XCTAssertNil(Version(string: str))
}
XCTAssertNil(Version(string: ""))
XCTAssertNil(Version(string: "1"))
XCTAssertNil(Version(string: "1.2"))
XCTAssertNil(Version(string: "1.2.3.4"))
XCTAssertNil(Version(string: "1.2.3.4.5"))
XCTAssertNil(Version(string: "a"))
XCTAssertNil(Version(string: "1.a"))
XCTAssertNil(Version(string: "a.2"))
XCTAssertNil(Version(string: "a.2.3"))
XCTAssertNil(Version(string: "1.a.3"))
XCTAssertNil(Version(string: "1.2.a"))
XCTAssertNil(Version(string: "-1.2.3"))
XCTAssertNil(Version(string: "1.-2.3"))
XCTAssertNil(Version(string: "1.2.-3"))
XCTAssertNil(Version(string: ".1.2.3"))
XCTAssertNil(Version(string: "v.1.2.3"))
XCTAssertNil(Version(string: "1.2..3"))
XCTAssertNil(Version(string: "v1.2.3"))
XCTAssertNil(Version(string: ".1.2"))
XCTAssertNil(Version(string: ".1"))

XCTAssertNil(Version(string: "-1.1.1"))
XCTAssertNil(Version(string: "1.-1.1"))
XCTAssertNil(Version(string: "1.1.-1"))
XCTAssertNil(Version(string: "1.-1.-1"))
XCTAssertNil(Version(string: "-1.-1.-1"))
XCTAssertNil(Version(string: "10..0.0"))
XCTAssertNil(Version(string: "10.0..0"))
XCTAssertNil(Version(string: "10.0.0."))
XCTAssertNil(Version(string: "10.0.0.."))
XCTAssertNil(Version(string: "10.0.0.0"))
XCTAssertNil(Version(string: "10.0.0.-1"))

XCTAssertEqual(Version(1,2,3), Version(string: "1.2.3"))
XCTAssertEqual(Version(1,2,3), Version(string: "01.002.0003"))
Expand Down Expand Up @@ -337,17 +361,66 @@ class VersionTests: XCTestCase {
}
}

func testAbsolute() {
XCTAssertEqual(Version(-1,1,1), Version(1,1,1))
XCTAssertEqual(Version(1,-1,1), Version(1,1,1))
XCTAssertEqual(Version(1,1,-1), Version(1,1,1))
XCTAssertEqual(Version(1,-1,-1), Version(1,1,1))
XCTAssertEqual(Version(-1,-1,-1), Version(1,1,1))
}

func testInitializers() {
let v1 = Version(1,0,0)
let v2 = Version("1.0.0")
let v3 = Version(major: 1, minor: 0, patch: 0)
let v4 = Version(tolerant: "1.0")
let v5 = Version(tolerant: "1")

let v2 = Version(major: 1, minor: 0, patch: 0)
XCTAssertEqual(v1, v2)
XCTAssertEqual(v2, v3)
XCTAssertEqual(v3, v4)
XCTAssertEqual(v4, v5)
}

func testTolerantIntiliazer() {
XCTAssertEqual(Version(tolerant: "2.3-beta"), Version(2,3,0, prereleaseIdentifiers: ["beta"]))

XCTAssertNil(Version(tolerant: "10..0.0"))
XCTAssertNil(Version(tolerant: "10.0..0"))
XCTAssertNil(Version(tolerant: "10.0.0."))
XCTAssertNil(Version(tolerant: "10.0.0.."))
XCTAssertNil(Version(tolerant: "10.0.0.0"))

XCTAssertNil(Version(tolerant: "10.0.0.-1"))

XCTAssertNil(Version(tolerant: "10beta"))
XCTAssertNil(Version(tolerant: "10d"))
XCTAssertNil(Version(tolerant: "10.-1"))

XCTAssertNil(Version(tolerant: ""))
XCTAssertNil(Version(tolerant: "1.2.3.4"))
XCTAssertNil(Version(tolerant: "1.2.3.4.5"))
XCTAssertNil(Version(tolerant: "a"))
XCTAssertNil(Version(tolerant: "1.a"))
XCTAssertNil(Version(tolerant: "a.2"))
XCTAssertNil(Version(tolerant: "a.2.3"))
XCTAssertNil(Version(tolerant: "1.a.3"))
XCTAssertNil(Version(tolerant: "1.2.a"))
XCTAssertNil(Version(tolerant: "-1.2.3"))
XCTAssertNil(Version(tolerant: "1.-2.3"))
XCTAssertNil(Version(tolerant: "1.2.-3"))
XCTAssertNil(Version(tolerant: ".1.2.3"))
XCTAssertNil(Version(tolerant: ".1.2"))
XCTAssertNil(Version(tolerant: ".1"))
XCTAssertNil(Version(tolerant: "v.1.2.3"))
XCTAssertNil(Version(tolerant: "v.1.2"))
XCTAssertNil(Version(tolerant: "v.1"))
XCTAssertNil(Version(tolerant: "1.2..3"))

XCTAssertNil(Version(string: "1-beta1"))
XCTAssertEqual(Version(tolerant: "1-beta1"), Version(1,0,0, prereleaseIdentifiers: ["beta1"]))
XCTAssertNil(Version(string: "1.0-beta1"))
XCTAssertEqual(Version(tolerant: "1.0-beta1"), Version(1,0,0, prereleaseIdentifiers: ["beta1"]))

XCTAssertEqual(Version(tolerant: "v1"), Version(1,0,0))
XCTAssertEqual(Version(tolerant: "v1.0"), Version(1,0,0))
XCTAssertEqual(Version(tolerant: "v1.0.0"), Version(1,0,0))
XCTAssertEqual(Version(tolerant: "v1-beta1"), Version(1,0,0, prereleaseIdentifiers: ["beta1"]))
XCTAssertEqual(Version(tolerant: "v1.0-beta1"), Version(1,0,0, prereleaseIdentifiers: ["beta1"]))
XCTAssertEqual(Version(tolerant: "v1.0.0-beta1"), Version(1,0,0, prereleaseIdentifiers: ["beta1"]))
}

func testCodable() throws {
Expand Down

0 comments on commit 087c91f

Please sign in to comment.