Last active
April 30, 2021 13:35
-
-
Save hisaac/292bfe673d026817e3e1b52408cdf97c to your computer and use it in GitHub Desktop.
SemVer struct for Swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************** | |
WARNING: | |
There are issues with this implementation (see [@toshi0383's comment below](https://gist.github.com/hisaac/292bfe673d026817e3e1b52408cdf97c#gistcomment-3726069). | |
Feel free to use this how you wish, but know that it's not 100% accurate. This was just a little experimentation I did while learning Swift, so there are bound to be issues. | |
********************/ | |
// | |
// SemVer.swift | |
// | |
// Created by Isaac Halvorson on 12/12/17. | |
// Copyright © 2017 Levelsoft. All rights reserved. | |
// | |
import Foundation | |
/** | |
A struct for handling [SemVer](https://semver.org) comparisons 🤓 | |
- `major`: when you make incompatible API changes | |
- `minor`: when you add functionality in a backwards-compatible manner | |
- `patch`: when you make backwards-compatible bug fixes | |
*/ | |
struct SemVer { | |
let major: Int | |
let minor: Int | |
let patch: Int | |
/** | |
Can be initialized with a string. It will split the string at any `"."`, and assign the values | |
to the `major`, `minor`, and `patch` variables in order. | |
If no `minor` or `patch` value is provided, it will default to `0`. | |
- Parameter semVerString: A string in the format `"0"`, `"0.0"`, or `"0.0.0"` | |
*/ | |
init(_ semVerString: String) { | |
let splitSemVer = semVerString.components(separatedBy: ".") | |
major = Int(splitSemVer[0]) ?? 0 | |
minor = Int(splitSemVer[1]) ?? 0 | |
patch = Int(splitSemVer[2]) ?? 0 | |
} | |
/** | |
Can be initialized with a single Int, which will be interpreted as the major version number | |
- Parameter majorInt: An integer that will be interpreted as the major version number | |
*/ | |
init(_ majorInt: Int) { | |
self.init(String(majorInt)) | |
} | |
/** | |
Can be initialized with a Double, which will make the first number the `major`, and the second | |
will be the `minor`, assigning 0 to the `patch` | |
- Parameter majorMinorDouble: A double to be used as the first two numbers in the SemVer | |
*/ | |
init(_ majorMinorDouble: Double) { | |
self.init(String(majorMinorDouble)) | |
} | |
} | |
/// Implement `CustomStringConvertible` and `CustomDebugStringConvertible` to convert a SemVer back to a string | |
extension SemVer: CustomStringConvertible, CustomDebugStringConvertible { | |
var description: String { | |
return "\(major).\(minor).\(patch)" | |
} | |
var debugDescription: String { | |
return description | |
} | |
} | |
/// Implement `Comparable` for comparisons | |
extension SemVer: Comparable { | |
/** | |
A SemVer is equal to another if all of their properties match | |
- Parameter lhs: The first SemVer to be compared | |
- Parameter rhs: The second SemVer to be compared | |
- Returns: A Bool denoting whether or not the two SemVers are equal | |
*/ | |
static func ==(lhs: SemVer, rhs: SemVer) -> Bool { | |
return | |
lhs.major == rhs.major && | |
lhs.minor == rhs.minor && | |
lhs.patch == rhs.patch | |
} | |
/** | |
A SemVer is greater than another SemVer in three circumstances: | |
1. The first SemVer's `major` value is greater than the second SemVer's | |
2. The first SemVer's `major` value is equal to the second SemVer's, | |
and the first SemVer's `minor` is greater than the second SemVer's | |
3. The first Semver's `major` and `minor` values are equal to th second SemVer's, | |
and the first SemVer's `patch` value is greater than the second SemVer's | |
- Parameter lhs: The first SemVer to be compared | |
- Parameter rhs: The second SemVer to be compared | |
- Returns: A Bool denoting whether or not the first SemVer is greater than the second | |
*/ | |
static func >(lhs: SemVer, rhs: SemVer) -> Bool { | |
return | |
lhs.major > rhs.major || | |
(lhs.major == rhs.major && lhs.minor > rhs.minor) || | |
(lhs.major == rhs.major && lhs.minor == rhs.minor && lhs.patch > rhs.patch) | |
} | |
/** | |
A SemVer is greater than or equal to another SemVer in four circumstances: | |
1. The first SemVer and second SemVer are equal | |
2. The first SemVer's `major` value is greater than or equal to the second SemVer's | |
3. The first SemVer's `major` value is equal to the second SemVer's, | |
and the first SemVer's `minor` is greater than or equal to the second SemVer's | |
4. The first Semver's `major` and `minor` values are equal to th second SemVer's, | |
and the first SemVer's `patch` value is greater than or equal to the second SemVer's | |
- Parameter lhs: The first SemVer to be compared | |
- Parameter rhs: The second SemVer to be compared | |
- Returns: A Bool denoting whether or not the first SemVer is greater than or equal to the second | |
*/ | |
static func >=(lhs: SemVer, rhs: SemVer) -> Bool { | |
return | |
lhs == rhs || | |
lhs.major >= rhs.major || | |
(lhs.major == rhs.major && lhs.minor >= rhs.minor) || | |
(lhs.major == rhs.major && lhs.minor == rhs.minor && lhs.patch >= rhs.patch) | |
} | |
/** | |
A SemVer is less than another SemVer in three circumstances: | |
1. The first SemVer's `major` value is less than the second SemVer's | |
2. The first SemVer's `major` value is equal to the second SemVer's, | |
and the first SemVer's `minor` is less than the second SemVer's | |
3. The first Semver's `major` and `minor` values are equal to th second SemVer's, | |
and the first SemVer's `patch` value is less than the second SemVer's | |
- Parameter lhs: The first SemVer to be compared | |
- Parameter rhs: The second SemVer to be compared | |
- Returns: A Bool denoting whether or not the first SemVer is less than the second | |
*/ | |
static func <(lhs: SemVer, rhs: SemVer) -> Bool { | |
return | |
lhs.major < rhs.major || | |
(lhs.major == rhs.major && lhs.minor < rhs.minor) || | |
(lhs.major == rhs.major && lhs.minor == rhs.minor && lhs.patch < rhs.patch) | |
} | |
/** | |
A SemVer is lless than or equal to another SemVer in three circumstances: | |
1. The first SemVer and second SemVer are equal | |
1. The first SemVer's `major` value is less than or equal to the second SemVer's | |
2. The first SemVer's `major` value is equal to the second SemVer's, | |
and the first SemVer's `minor` is less than or equal to the second SemVer's | |
3. The first Semver's `major` and `minor` values are equal to th second SemVer's, | |
and the first SemVer's `patch` value is less than or equal to the second SemVer's | |
- Parameter lhs: The first SemVer to be compared | |
- Parameter rhs: The second SemVer to be compared | |
- Returns: A Bool denoting whether or not the first SemVer is greater than or equal to the second | |
*/ | |
static func <=(lhs: SemVer, rhs: SemVer) -> Bool { | |
return | |
lhs == rhs || | |
lhs.major <= rhs.major || | |
(lhs.major == rhs.major && lhs.minor <= rhs.minor) || | |
(lhs.major == rhs.major && lhs.minor == rhs.minor && lhs.patch <= rhs.patch) | |
} | |
} | |
/// Less granular comparisons | |
extension SemVer { | |
/// Granularity enum for the `approximatelyEqual` function | |
enum SemVerGranularity { | |
case major | |
case minor | |
} | |
/** | |
Checks a SemVer's equality with less granularity. Able to check at the `major` or `minor` level. | |
- Parameter other: The SemVer to be compared | |
- Parameter granularity: The granularity at which to compare the two SemVers | |
- Returns: A Bool denoting whether or not the two SemVers are equal at the specified granularity | |
*/ | |
func approximatelyEqual(to other: SemVer, granularity: SemVerGranularity) -> Bool { | |
// If specified granularity is `.major`, compare the two SemVers' `major` values | |
if granularity == .major { | |
return major == other.major | |
} | |
// If specified granularity is `.minor`, compare the two SemVers' `major` and `minor` values | |
else if granularity == .minor { | |
return major == other.major && minor == other.minor | |
} | |
return false | |
} | |
} |
Good catch @toshi0383. I don't plan to update this right now — it's not something I actively use anymore. I'm going to add a note at the top of the file about the issue. Thanks for the heads up.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
SemVer("5.2.9") <= SemVer("5.2.8")
returns true which looks like a bug.I think it should look like this for this behavior.
SemVer("5.2.9").isCompatible(with: SemVer("5.2.8"))