Last active
May 23, 2023 11:25
-
-
Save cxmeel/e6eea231c838cb7c9edbe55306f5a72a to your computer and use it in GitHub Desktop.
Basic semver string library implemented in Luau.
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
--!strict | |
--[[ | |
Provides a set of functions for comparing semver strings. | |
See https://semver.org/ for more information. | |
Types: | |
type SemverRecord = { | |
major: number, | |
minor: number, | |
patch: number, | |
prerelease: string?, | |
build: string?, | |
} | |
Functions: | |
In all comparison functions, build strings are ignored, as per the | |
semver spec. | |
- semver.is(input: any): boolean | |
Checks if the given input is a valid semver string or record. | |
- semver.parse(version: any): SemverRecord? | |
Parses the given string into a SemverRecord. Returns nil if the | |
input is not a string. Will throw an error if the input is an | |
invalid semver string. | |
- semver.gte(first: (string | SemverRecord)?, second: (string | SemverRecord)?): boolean | |
Returns true if the first version is greater than or equal to the | |
second version. | |
- semver.lte(first: (string | SemverRecord)?, second: (string | SemverRecord)?): boolean | |
Returns true if the first version is less than or equal to the | |
second version. | |
- semver.gt(first: (string | SemverRecord)?, second: (string | SemverRecord)?): boolean | |
Returns true if the first version is greater than the second | |
version. | |
- semver.lt(first: (string | SemverRecord)?, second: (string | SemverRecord)?): boolean | |
Returns true if the first version is less than the second version. | |
- semver.eq(first: (string | SemverRecord)?, second: (string | SemverRecord)?): boolean | |
Returns true if the first version is exactly equal to the second version, including prerelease. | |
]] | |
local semver = {} | |
type SemverRecord = { | |
major: number, | |
minor: number, | |
patch: number, | |
prerelease: string?, | |
build: string?, | |
} | |
local function equal(first: any, second: any): boolean | |
if first == second then | |
return true | |
end | |
if typeof(first) ~= "table" or typeof(second) ~= "table" then | |
return false | |
end | |
for key, value in first do | |
if second[key] ~= value then | |
return false | |
end | |
end | |
for key, value in second do | |
if first[key] ~= value then | |
return false | |
end | |
end | |
return true | |
end | |
local function isRecord(input: any): boolean | |
return typeof(input) == "table" | |
and typeof(input.major) == "number" | |
and typeof(input.minor) == "number" | |
and typeof(input.patch) == "number" | |
end | |
function semver.is(input: any): boolean | |
local success, result = select(1, pcall(semver.parse, input)) | |
return success and result ~= nil | |
end | |
function semver.parse(version: any): SemverRecord? | |
if isRecord(version) then | |
return version :: SemverRecord | |
end | |
if typeof(version) ~= "string" then | |
return | |
end | |
local major = tonumber(version:match("^v?(%d+)")) | |
if major == nil then | |
error(`Invalid semver string: {version}`) | |
end | |
local minor = tonumber(version:match("^v?%d+%.(%d+)")) or 0 | |
local patch = tonumber(version:match("^v?%d+%.%d+%.(%d+)")) or 0 | |
local prerelease = version:match("^v?%d+%.%d+%.%d+-([%w%-%.]+)") | |
local build = version:match("^v?%d+%.%d+%.%d+%-[%w%-%.]+%+(.+)") | |
return { | |
major = major, | |
minor = minor, | |
patch = patch, | |
prerelease = prerelease, | |
build = build, | |
} | |
end | |
local function prerelease_gte(first: string?, second: string?): boolean | |
if not second then | |
return true | |
end | |
if not first then | |
return false | |
end | |
local firstParts = first:split(".") | |
local secondParts = second:split(".") | |
for index, firstPart in firstParts do | |
local secondPart = secondParts[index] | |
if not secondPart then | |
return true | |
end | |
if firstPart ~= secondPart then | |
return firstPart > secondPart | |
end | |
end | |
return true | |
end | |
function semver.gte(first: (string | SemverRecord)?, second: (string | SemverRecord)?): boolean | |
if not second or equal(first, second) then | |
return true | |
end | |
local firstRecord = semver.parse(first) | |
local secondRecord = semver.parse(second) | |
if not firstRecord or not secondRecord then | |
return false | |
end | |
local fa, fb, fc, fd = firstRecord.major, firstRecord.minor, firstRecord.patch, firstRecord.prerelease | |
local sa, sb, sc, sd = secondRecord.major, secondRecord.minor, secondRecord.patch, secondRecord.prerelease | |
return (fa > sa) | |
or ( | |
fa == sa | |
and ( | |
fb > sb | |
or (fb == sb and (fc > sc or (fc == sc and (fd and not sd or (fd == sd or prerelease_gte(fd, sd)))))) | |
) | |
) | |
end | |
function semver.lte(first: (string | SemverRecord)?, second: (string | SemverRecord)?): boolean | |
return semver.gte(second, first) | |
end | |
function semver.gt(first: (string | SemverRecord)?, second: (string | SemverRecord)?): boolean | |
return not semver.lte(first, second) | |
end | |
function semver.lt(first: (string | SemverRecord)?, second: (string | SemverRecord)?): boolean | |
return not semver.gte(first, second) | |
end | |
function semver.eq(first: (string | SemverRecord)?, second: (string | SemverRecord)?): boolean | |
return semver.gte(first, second) and semver.lte(first, second) | |
end | |
return semver |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment