semver
Parse, compare, sort, bump and range-match semantic versions such as 1.4.0-rc.1.
import "std:semver"A semantic version has three numbers and, optionally, extras: 1.4.2, 2.0.0-rc.1, 1.0.0+build.5. The numbers are major, minor and patch. std:semver understands them, so you can compare versions the way a human would, where 1.10.0 is newer than 1.2.0 (compare them as text and you get the wrong answer).
Use it for update checks, plugin compatibility, release tooling, and anything that reads a version from a file or an API.
Most functions return a result, because a string may not be a version at all.
Checking and parsing#
isValid says whether text is a version. parse takes it apart:
import "std:semver"
log semver.isValid("1.4.0-rc.1")
log semver.isValid("1.2")
log semver.isValid("v2.0.1")
auto v = semver.parse("1.4.0-rc.1+build.5").unwrap()
log v.major v.minor v.patch
log v.prerelease
log v.buildThree numbers are required, so 1.2 is not valid. A leading v is accepted and ignored. prerelease and build are null when a version has none.
A bad string is a ParseError:
import "std:semver"
auto bad = semver.parse("latest")
log bad.isErr()
log bad.unwrapErr().kindComparing#
Never compare versions as text. compare returns -1, 0 or 1, and gt, gte, lt, lte, eq and neq return booleans:
import "std:semver"
log "1.10.0" > "1.2.0"
log semver.gt("1.10.0", "1.2.0").unwrap()
log semver.compare("1.2.3", "1.10.0").unwrap()
log semver.lt("1.0.0-alpha", "1.0.0").unwrap()
log semver.eq("1.0.0+a", "1.0.0+b").unwrap()Two rules are worth knowing. A pre-release is lower than its release (1.0.0-alpha comes before 1.0.0). Build metadata after the + is ignored, so the last two versions are equal.
Update checks#
A real use: tell the user whether an update is available. The unwrapOr keeps one bad value from stopping the program:
import "std:semver"
def updateMessage(string current, string latest) string (
if semver.gt(latest, current).unwrapOr(false) (
return "update available: " ++ current ++ " to " ++ latest
)
return "up to date"
)
log updateMessage("1.9.0", "1.10.0")
log updateMessage("2.0.0", "2.0.0")
log updateMessage("2.0.0", "oops")Bumping#
inc(version, part) raises "major", "minor" or "patch" and resets the parts after it:
import "std:semver"
log semver.inc("1.4.2", "patch").unwrap()
log semver.inc("1.4.2", "minor").unwrap()
log semver.inc("1.4.2", "major").unwrap()Sorting and picking#
sort puts a list in ascending order, and max and min return the highest and lowest. They return the strings as you wrote them:
import "std:semver"
string[] tags = ["1.10.0", "1.2.0", "1.2.0-beta", "0.9.9"]
log semver.sort(tags).unwrap()
log semver.max(tags).unwrap()
log semver.min(tags).unwrap()One invalid entry makes the whole call fail with a ParseError, so filter first with tags.filter(t -> semver.isValid(t)) when the list comes from outside.
Ranges#
satisfies(version, range) is how dependencies say what they accept. It uses npm-style ranges:
| Range | Accepts |
|---|---|
^1.2.0 | 1.2.0 up to, but not including, 2.0.0 |
~1.4.0 | 1.4.0 up to, but not including, 1.5.0 |
>=1.2.0 <1.5.0 | both comparisons must hold |
1.2.x | any patch of 1.2 |
1.2.x || 1.3.x | either side |
import "std:semver"
log semver.satisfies("1.4.2", "^1.2.0").unwrap()
log semver.satisfies("2.0.0", "^1.2.0").unwrap()
log semver.satisfies("1.4.9", "~1.4.0").unwrap()
log semver.satisfies("1.5.0", ">=1.2.0 <1.5.0").unwrap()
log semver.satisfies("1.3.0", "1.2.x || 1.3.x").unwrap()Ranges compare by version order, so a pre-release counts as lower than its release: semver.satisfies("1.0.0-beta", "^1.0.0") is false. Unlike npm, satisfies doesn't exclude pre-releases of later versions, so 1.9.0-beta does satisfy ^1.0.0. Filter pre-releases out yourself (v.prerelease == null after semver.parse) if stable-only matters.
Combined, these make a compatibility check:
import "std:semver"
object plugins = {
charts: "1.3.0",
export: "2.0.1",
themes: "1.9.4"
}
string need = "^1.2.0"
for name in plugins.getKeys().toSorted() (
string version = plugins[name]
string verdict = "incompatible"
if semver.satisfies(version, need).unwrapOr(false) ( verdict = "ok" )
log name version verdict
)What does semver.satisfies("1.5.0", "~1.4.0") give?
Newest compatible version
Print the newest version in tags that satisfies ^1.0.0. Right now it prints the newest of all, which is 2.0.0.
1.8.2
API reference#
14 members, generated from the std:semver source of OSL 9.3.0.
Functions
semver.compare(string a, string b) result<int, error>compare returns -1, 0 or 1 as a is lower than, equal to or higher than b in semantic version precedence; build metadata is ignored. Fails with ParseError when either is not a semantic version.
semver.eq(string a, string b) result<boolean, error>eq reports whether versions a and b have equal precedence (build metadata is ignored). Fails with ParseError when either is not a semantic version.
semver.gt(string a, string b) result<boolean, error>gt reports whether version a is higher than b. Fails with ParseError when either is not a semantic version.
semver.gte(string a, string b) result<boolean, error>gte reports whether version a is higher than or equal to b. Fails with ParseError when either is not a semantic version.
semver.inc(string version, string part) result<string, error>inc returns version with part ("major", "minor" or "patch") increased and the lower parts, prerelease and build cleared. Fails with ParseError when version is not a semantic version; throws ValueError for any other part.
semver.isValid(string text) booleanisValid reports whether text is a semantic version semver.parse accepts.
semver.lt(string a, string b) result<boolean, error>lt reports whether version a is lower than b. Fails with ParseError when either is not a semantic version.
semver.lte(string a, string b) result<boolean, error>lte reports whether version a is lower than or equal to b. Fails with ParseError when either is not a semantic version.
semver.max(any versions) result<string, error>max returns the highest of an array of version strings, as written. Fails with ParseError when any item is not a semantic version and ValueError when versions is empty.
semver.min(any versions) result<string, error>min returns the lowest of an array of version strings, as written. Fails with ParseError when any item is not a semantic version and ValueError when versions is empty.
semver.neq(string a, string b) result<boolean, error>neq reports whether versions a and b have different precedence. Fails with ParseError when either is not a semantic version.
semver.parse(string text) result<{build: string?, major: int, minor: int, patch: int, prerelease: string?, valid: boolean (deprecated)}, error>parse reads a semantic version such as "1.4.0-rc.1+build.5"; a leading "v" is allowed. Returns a record {major, minor, patch, prerelease, build} where prerelease and build are strings, or null when absent; the deprecated key valid is always true. Fails with ParseError when text is not a semantic version.
semver.satisfies(string version, string constraint) result<boolean, error>satisfies reports whether version is in the npm-style range: comparators (">=1.2.0 <2"), caret and tilde ranges ("^1.2", "~1.2.3"), x-ranges ("1.x"), hyphen ranges ("1.2 - 1.5") and alternatives joined by "||". Prereleases only match comparators on the same major.minor.patch. Fails with ParseError when version is not a semantic version or the range is malformed.
semver.sort(any versions) result<string[], error>sort returns an array of version strings in ascending precedence as a new array; equal versions keep their order. Throws TypeError when versions is not an array of strings. Fails with ParseError when any item is not a semantic version.