semver.ry
module semver
purpose: Parse, print and compare semantic version numbers such as 1.4.2.
public type Part is Integer where value is at least 0
purpose: One numeric component of a version.
public type Version
purpose: A semantic version without pre-release or build parts.
has major: Part
has minor: Part
has patch: Part
can Compare by major, minor, patch
end
ability ToText for Version
function to_text(self) returns Text
return "{self.major}.{self.minor}.{self.patch}"
end
end
public type VersionError is one of
purpose: Why a text is not a version.
WrongPartCount(input: Text, found: Integer)
NotANumber(input: Text, part: Text)
end
public function parse(input: Text) returns Version or fails with VersionError
purpose: Read "major.minor.patch" into a version.
tags: semver, parsing
example: parse("1.4.2") is Version(major: 1, minor: 4, patch: 2)
example: parse("1.4") fails with WrongPartCount(input: "1.4", found: 2)
example: parse("1.x.2") fails with NotANumber(input: "1.x.2", part: "x")
let parts be input.trim().split(".")
if parts.length() is not 3 then fail with WrongPartCount(input: input, found: parts.length()) end
let major be number_at(parts: parts, index: 0, input: input) otherwise fail
let minor be number_at(parts: parts, index: 1, input: input) otherwise fail
let patch be number_at(parts: parts, index: 2, input: input) otherwise fail
return Version(major: major, minor: minor, patch: patch)
end
function number_at(parts: List of Text, index: Integer, input: Text)
returns Part
or fails with VersionError
let part be parts.at(index) otherwise fail with WrongPartCount(
input: input,
found: parts.length()
)
let number be part.to_integer() otherwise fail with NotANumber(input: input, part: part)
return Part(number) otherwise fail with NotANumber(input: input, part: part)
end
public function satisfies(candidate: Version, required: Version) returns Boolean
purpose: Whether the candidate can stand in for the required version: same major and not older.
example: satisfies(
candidate: Version(major: 1, minor: 5, patch: 0),
required: Version(major: 1, minor: 4, patch: 2)
) is true
example: satisfies(
candidate: Version(major: 2, minor: 0, patch: 0),
required: Version(major: 1, minor: 4, patch: 2)
) is false
return candidate.major is required.major and candidate is at least required
end
public function latest(versions: List of Version) returns maybe Version
purpose: The greatest version in the list, or nothing for an empty list.
example: latest([]) is nothing
return for each version in versions sorted by version descending first
end
test "a version prints as three numbers separated by dots"
check Version(major: 1, minor: 4, patch: 2).to_text() is "1.4.2"
end
test "versions compare by major, then minor, then patch"
let older be parse("1.9.9") otherwise fail
let newer be parse("2.0.0") otherwise fail
check older is less than newer
check latest([older, newer]) is newer
end