Skip to content

Instantly share code, notes, and snippets.

@hisaac
Last active April 30, 2021 13:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hisaac/292bfe673d026817e3e1b52408cdf97c to your computer and use it in GitHub Desktop.
Save hisaac/292bfe673d026817e3e1b52408cdf97c to your computer and use it in GitHub Desktop.
SemVer struct for Swift
/********************
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
}
}
@toshi0383
Copy link

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"))

@hisaac
Copy link
Author

hisaac commented Apr 30, 2021

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