Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for modern Swift regex, when available #20

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
run-tests:
strategy:
matrix:
swift-version: [5.6, 5.7, 5.8, 5.9]
swift-version: [5.7, 5.8, 5.9]
runs-on: ubuntu-20.04
steps:
- uses: fwal/setup-swift@v1
Expand Down
8 changes: 7 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.6
// swift-tools-version:5.7

// Copyright Dave Verwer, Sven A. Schmidt, and other contributors.
//
Expand All @@ -18,6 +18,12 @@ import PackageDescription

let package = Package(
name: "SemanticVersion",
platforms: [
.iOS(.v16),
.macOS(.v13),
.watchOS(.v9),
.tvOS(.v16),
],
products: [
.library(
name: "SemanticVersion",
Expand Down
48 changes: 0 additions & 48 deletions Sources/SemanticVersion/NSRegularExpression+ext.swift

This file was deleted.

48 changes: 10 additions & 38 deletions Sources/SemanticVersion/SemanticVersion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,14 @@ extension SemanticVersion: LosslessStringConvertible {
/// Initialize a version from a string. Returns `nil` if the string is not a semantic version.
/// - Parameter string: Version string.
public init?(_ string: String) {
let groups = semVerRegex.matchGroups(string)
guard
groups.count == semVerRegex.numberOfCaptureGroups,
let major = Int(groups[0]),
let minor = Int(groups[1]),
let patch = Int(groups[2])
guard let match = string.wholeMatch(of: semVerPattern),
let major = Int(match.output.major),
let minor = Int(match.output.minor),
let patch = Int(match.output.patch)
else { return nil }
self = .init(major, minor, patch, groups[3], groups[4])
let preRelease = match.output.prerelease.flatMap({String($0)}) ?? ""
let build = match.output.buildmetadata.flatMap({String($0)}) ?? ""
self = .init(major, minor, patch, preRelease, build)
}

public var description: String {
Expand Down Expand Up @@ -162,9 +162,9 @@ extension SemanticVersion: Sendable {}

// Source: https://regex101.com/r/Ly7O1x/3/
// Linked from https://semver.org
#if swift(>=5)

let semVerRegex = NSRegularExpression(#"""

let semVerPattern = ##/
^
v? # SPI extension: allow leading 'v'
(?<major>0|[1-9]\d*)
Expand All @@ -186,32 +186,4 @@ v? # SPI extension: allow leading 'v'
*)
)?
$
"""#, options: [.allowCommentsAndWhitespace])

#else

let semVerRegex = NSRegularExpression("""
^
v? # SPI extension: allow leading 'v'
(?<major>0|[1-9]\\d*)
\\.
(?<minor>0|[1-9]\\d*)
\\.
(?<patch>0|[1-9]\\d*)
(?:-
(?<prerelease>
(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)
(?:\\.
(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)
)
*)
)?
(?:\\+
(?<buildmetadata>[0-9a-zA-Z-]+
(?:\\.[0-9a-zA-Z-]+)
*)
)?
$
""", options: [.allowCommentsAndWhitespace])

#endif
/##
147 changes: 74 additions & 73 deletions Tests/SemanticVersionTests/SemanticVersionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,84 +17,85 @@ import XCTest

final class SemanticVersionTests: XCTestCase {

func test_semVerRegex_valid() throws {
XCTAssert(semVerRegex.matches("0.0.4"))
XCTAssert(semVerRegex.matches("1.2.3"))
XCTAssert(semVerRegex.matches("10.20.30"))
XCTAssert(semVerRegex.matches("1.1.2-prerelease+meta"))
XCTAssert(semVerRegex.matches("1.1.2+meta"))
XCTAssert(semVerRegex.matches("1.1.2+meta-valid"))
XCTAssert(semVerRegex.matches("1.0.0-alpha"))
XCTAssert(semVerRegex.matches("1.0.0-beta"))
XCTAssert(semVerRegex.matches("1.0.0-alpha.beta"))
XCTAssert(semVerRegex.matches("1.0.0-alpha.beta.1"))
XCTAssert(semVerRegex.matches("1.0.0-alpha.1"))
XCTAssert(semVerRegex.matches("1.0.0-alpha0.valid"))
XCTAssert(semVerRegex.matches("1.0.0-alpha.0valid"))
XCTAssert(semVerRegex.matches("1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay"))
XCTAssert(semVerRegex.matches("1.0.0-rc.1+build.1"))
XCTAssert(semVerRegex.matches("2.0.0-rc.1+build.123"))
XCTAssert(semVerRegex.matches("1.2.3-beta"))
XCTAssert(semVerRegex.matches("10.2.3-DEV-SNAPSHOT"))
XCTAssert(semVerRegex.matches("1.2.3-SNAPSHOT-123"))
XCTAssert(semVerRegex.matches("1.0.0"))
XCTAssert(semVerRegex.matches("2.0.0"))
XCTAssert(semVerRegex.matches("1.1.7"))
XCTAssert(semVerRegex.matches("2.0.0+build.1848"))
XCTAssert(semVerRegex.matches("2.0.1-alpha.1227"))
XCTAssert(semVerRegex.matches("1.0.0-alpha+beta"))
XCTAssert(semVerRegex.matches("1.2.3----RC-SNAPSHOT.12.9.1--.12+788"))
XCTAssert(semVerRegex.matches("1.2.3----R-S.12.9.1--.12+meta"))
XCTAssert(semVerRegex.matches("1.2.3----RC-SNAPSHOT.12.9.1--.12"))
XCTAssert(semVerRegex.matches("1.0.0+0.build.1-rc.10000aaa-kk-0.1"))
XCTAssert(semVerRegex.matches("99999999999999999999999.999999999999999999.99999999999999999"))
XCTAssert(semVerRegex.matches("1.0.0-0A.is.legal"))
func test_semVerPattern_valid() throws {
XCTAssertNotNil("0.0.4".wholeMatch(of: semVerPattern))
XCTAssertNotNil("0.0.4".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.2.3".wholeMatch(of: semVerPattern))
XCTAssertNotNil("10.20.30".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.1.2-prerelease+meta".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.1.2+meta".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.1.2+meta-valid".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.0.0-alpha".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.0.0-beta".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.0.0-alpha.beta".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.0.0-alpha.beta.1".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.0.0-alpha.1".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.0.0-alpha0.valid".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.0.0-alpha.0valid".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.0.0-rc.1+build.1".wholeMatch(of: semVerPattern))
XCTAssertNotNil("2.0.0-rc.1+build.123".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.2.3-beta".wholeMatch(of: semVerPattern))
XCTAssertNotNil("10.2.3-DEV-SNAPSHOT".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.2.3-SNAPSHOT-123".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.0.0".wholeMatch(of: semVerPattern))
XCTAssertNotNil("2.0.0".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.1.7".wholeMatch(of: semVerPattern))
XCTAssertNotNil("2.0.0+build.1848".wholeMatch(of: semVerPattern))
XCTAssertNotNil("2.0.1-alpha.1227".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.0.0-alpha+beta".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.2.3----RC-SNAPSHOT.12.9.1--.12+788".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.2.3----R-S.12.9.1--.12+meta".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.2.3----RC-SNAPSHOT.12.9.1--.12".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.0.0+0.build.1-rc.10000aaa-kk-0.1".wholeMatch(of: semVerPattern))
XCTAssertNotNil("99999999999999999999999.999999999999999999.99999999999999999".wholeMatch(of: semVerPattern))
XCTAssertNotNil("1.0.0-0A.is.legal".wholeMatch(of: semVerPattern))
}

func test_allow_leading_v() throws {
XCTAssert(semVerRegex.matches("v0.0.4"))
XCTAssert("v0.0.4".contains(semVerPattern))
}

func test_semVerRegex_invalid() throws {
XCTAssertFalse(semVerRegex.matches("1"))
XCTAssertFalse(semVerRegex.matches("1.2"))
XCTAssertFalse(semVerRegex.matches("1.2.3-0123"))
XCTAssertFalse(semVerRegex.matches("1.2.3-0123.0123"))
XCTAssertFalse(semVerRegex.matches("1.1.2+.123"))
XCTAssertFalse(semVerRegex.matches("+invalid"))
XCTAssertFalse(semVerRegex.matches("-invalid"))
XCTAssertFalse(semVerRegex.matches("-invalid+invalid"))
XCTAssertFalse(semVerRegex.matches("-invalid.01"))
XCTAssertFalse(semVerRegex.matches("alpha"))
XCTAssertFalse(semVerRegex.matches("alpha.beta"))
XCTAssertFalse(semVerRegex.matches("alpha.beta.1"))
XCTAssertFalse(semVerRegex.matches("alpha.1"))
XCTAssertFalse(semVerRegex.matches("alpha+beta"))
XCTAssertFalse(semVerRegex.matches("alpha_beta"))
XCTAssertFalse(semVerRegex.matches("alpha."))
XCTAssertFalse(semVerRegex.matches("alpha.."))
XCTAssertFalse(semVerRegex.matches("beta"))
XCTAssertFalse(semVerRegex.matches("1.0.0-alpha_beta"))
XCTAssertFalse(semVerRegex.matches("-alpha."))
XCTAssertFalse(semVerRegex.matches("1.0.0-alpha.."))
XCTAssertFalse(semVerRegex.matches("1.0.0-alpha..1"))
XCTAssertFalse(semVerRegex.matches("1.0.0-alpha...1"))
XCTAssertFalse(semVerRegex.matches("1.0.0-alpha....1"))
XCTAssertFalse(semVerRegex.matches("1.0.0-alpha.....1"))
XCTAssertFalse(semVerRegex.matches("1.0.0-alpha......1"))
XCTAssertFalse(semVerRegex.matches("1.0.0-alpha.......1"))
XCTAssertFalse(semVerRegex.matches("01.1.1"))
XCTAssertFalse(semVerRegex.matches("1.01.1"))
XCTAssertFalse(semVerRegex.matches("1.1.01"))
XCTAssertFalse(semVerRegex.matches("1.2"))
XCTAssertFalse(semVerRegex.matches("1.2.3.DEV"))
XCTAssertFalse(semVerRegex.matches("1.2-SNAPSHOT"))
XCTAssertFalse(semVerRegex.matches("1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788"))
XCTAssertFalse(semVerRegex.matches("1.2-RC-SNAPSHOT"))
XCTAssertFalse(semVerRegex.matches("-1.0.3-gamma+b7718"))
XCTAssertFalse(semVerRegex.matches("+justmeta"))
XCTAssertFalse(semVerRegex.matches("9.8.7+meta+meta"))
XCTAssertFalse(semVerRegex.matches("9.8.7-whatever+meta+meta"))
func test_semVerPattern_invalid() throws {
XCTAssertFalse("1".contains(semVerPattern))
XCTAssertFalse("1.2".contains(semVerPattern))
XCTAssertFalse("1.2.3-0123".contains(semVerPattern))
XCTAssertFalse("1.2.3-0123.0123".contains(semVerPattern))
XCTAssertFalse("1.1.2+.123".contains(semVerPattern))
XCTAssertFalse("+invalid".contains(semVerPattern))
XCTAssertFalse("-invalid".contains(semVerPattern))
XCTAssertFalse("-invalid+invalid".contains(semVerPattern))
XCTAssertFalse("-invalid.01".contains(semVerPattern))
XCTAssertFalse("alpha".contains(semVerPattern))
XCTAssertFalse("alpha.beta".contains(semVerPattern))
XCTAssertFalse("alpha.beta.1".contains(semVerPattern))
XCTAssertFalse("alpha.1".contains(semVerPattern))
XCTAssertFalse("alpha+beta".contains(semVerPattern))
XCTAssertFalse("alpha_beta".contains(semVerPattern))
XCTAssertFalse("alpha.".contains(semVerPattern))
XCTAssertFalse("alpha..".contains(semVerPattern))
XCTAssertFalse("beta".contains(semVerPattern))
XCTAssertFalse("1.0.0-alpha_beta".contains(semVerPattern))
XCTAssertFalse("-alpha.".contains(semVerPattern))
XCTAssertFalse("1.0.0-alpha..".contains(semVerPattern))
XCTAssertFalse("1.0.0-alpha..1".contains(semVerPattern))
XCTAssertFalse("1.0.0-alpha...1".contains(semVerPattern))
XCTAssertFalse("1.0.0-alpha....1".contains(semVerPattern))
XCTAssertFalse("1.0.0-alpha.....1".contains(semVerPattern))
XCTAssertFalse("1.0.0-alpha......1".contains(semVerPattern))
XCTAssertFalse("1.0.0-alpha.......1".contains(semVerPattern))
XCTAssertFalse("01.1.1".contains(semVerPattern))
XCTAssertFalse("1.01.1".contains(semVerPattern))
XCTAssertFalse("1.1.01".contains(semVerPattern))
XCTAssertFalse("1.2".contains(semVerPattern))
XCTAssertFalse("1.2.3.DEV".contains(semVerPattern))
XCTAssertFalse("1.2-SNAPSHOT".contains(semVerPattern))
XCTAssertFalse("1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788".contains(semVerPattern))
XCTAssertFalse("1.2-RC-SNAPSHOT".contains(semVerPattern))
XCTAssertFalse("-1.0.3-gamma+b7718".contains(semVerPattern))
XCTAssertFalse("+justmeta".contains(semVerPattern))
XCTAssertFalse("9.8.7+meta+meta".contains(semVerPattern))
XCTAssertFalse("9.8.7-whatever+meta+meta".contains(semVerPattern))
}

func test_init() throws {
Expand Down
Loading