Import version parser
This commit is contained in:
parent
d2e1ef8006
commit
fdd3c83f39
|
@ -2,13 +2,14 @@
|
|||
# SPDX-FileCopyrightText: 2022 Anna <cyber@sysrq.in>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import os, parseopt, strutils
|
||||
import logging, os, parseopt, strutils
|
||||
|
||||
type
|
||||
Options* = object
|
||||
showHelp*: bool
|
||||
nim*: string # Nim compiler location
|
||||
sourceDir*: string
|
||||
logger*: ConsoleLogger
|
||||
|
||||
const
|
||||
help* = """
|
||||
|
@ -26,6 +27,10 @@ proc writeHelp*() =
|
|||
echo(help)
|
||||
quit(QuitSuccess)
|
||||
|
||||
proc setLogger*(options: var Options) =
|
||||
options.logger = newConsoleLogger()
|
||||
addHandler(options.logger)
|
||||
|
||||
proc getSourceDir*(options: Options): string =
|
||||
return options.sourceDir
|
||||
|
||||
|
|
242
src/nimbs/version.nim
Normal file
242
src/nimbs/version.nim
Normal file
|
@ -0,0 +1,242 @@
|
|||
# SPDX-FileCopyrightText: Copyright (C) Dominik Picheta. All rights reserved.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
## Module for handling versions and version ranges such as ``>= 1.0 & <= 1.5``
|
||||
|
||||
import parseutils, strutils
|
||||
|
||||
type
|
||||
Version* {.requiresInit.} = object
|
||||
version: string
|
||||
|
||||
VersionRangeEnum* = enum
|
||||
verLater, # > V
|
||||
verEarlier, # < V
|
||||
verEqLater, # >= V -- Equal or later
|
||||
verEqEarlier, # <= V -- Equal or earlier
|
||||
verIntersect, # > V & < V
|
||||
verTilde, # ~= V
|
||||
verCaret, # ^= V
|
||||
verEq, # V
|
||||
verAny # *
|
||||
|
||||
VersionRange* = ref VersionRangeObj
|
||||
VersionRangeObj = object
|
||||
case kind*: VersionRangeEnum
|
||||
of verLater, verEarlier, verEqLater, verEqEarlier, verEq:
|
||||
ver*: Version
|
||||
of verIntersect, verTilde, verCaret:
|
||||
verILeft, verIRight: VersionRange
|
||||
of verAny:
|
||||
nil
|
||||
|
||||
ParseVersionError* = object of CatchableError
|
||||
|
||||
const
|
||||
notSetVersion* = Version(version: "-1")
|
||||
|
||||
proc parseVersionError*(msg: string): ref ParseVersionError =
|
||||
result = newException(ParseVersionError, msg)
|
||||
|
||||
template `$`*(ver: Version): string = ver.version
|
||||
|
||||
proc newVersion*(ver: string): Version =
|
||||
if ver.len != 0 and ver[0] notin {'\0'} + Digits:
|
||||
raise parseVersionError("Wrong version: " & ver)
|
||||
return Version(version: ver)
|
||||
|
||||
proc `<`*(ver: Version, ver2: Version): bool =
|
||||
# Handling for normal versions such as "0.1.0" or "1.0".
|
||||
var sVer = ver.version.split('.')
|
||||
var sVer2 = ver2.version.split('.')
|
||||
for i in 0..max(sVer.len, sVer2.len)-1:
|
||||
var sVerI = 0
|
||||
if i < sVer.len:
|
||||
discard parseInt(sVer[i], sVerI)
|
||||
var sVerI2 = 0
|
||||
if i < sVer2.len:
|
||||
discard parseInt(sVer2[i], sVerI2)
|
||||
if sVerI < sVerI2:
|
||||
return true
|
||||
elif sVerI == sVerI2:
|
||||
discard
|
||||
else:
|
||||
return false
|
||||
|
||||
proc `==`*(ver: Version, ver2: Version): bool =
|
||||
var sVer = ver.version.split('.')
|
||||
var sVer2 = ver2.version.split('.')
|
||||
for i in 0..max(sVer.len, sVer2.len)-1:
|
||||
var sVerI = 0
|
||||
if i < sVer.len:
|
||||
discard parseInt(sVer[i], sVerI)
|
||||
var sVerI2 = 0
|
||||
if i < sVer2.len:
|
||||
discard parseInt(sVer2[i], sVerI2)
|
||||
if sVerI == sVerI2:
|
||||
result = true
|
||||
else:
|
||||
return false
|
||||
|
||||
proc `<=`*(ver: Version, ver2: Version): bool =
|
||||
return (ver == ver2) or (ver < ver2)
|
||||
|
||||
proc `==`*(range1: VersionRange, range2: VersionRange): bool =
|
||||
if range1.kind != range2.kind : return false
|
||||
result = case range1.kind
|
||||
of verLater, verEarlier, verEqLater, verEqEarlier, verEq:
|
||||
range1.ver == range2.ver
|
||||
of verIntersect, verTilde, verCaret:
|
||||
range1.verILeft == range2.verILeft and range1.verIRight == range2.verIRight
|
||||
of verAny: true
|
||||
|
||||
proc withinRange*(ver: Version, ran: VersionRange): bool =
|
||||
case ran.kind
|
||||
of verLater:
|
||||
return ver > ran.ver
|
||||
of verEarlier:
|
||||
return ver < ran.ver
|
||||
of verEqLater:
|
||||
return ver >= ran.ver
|
||||
of verEqEarlier:
|
||||
return ver <= ran.ver
|
||||
of verEq:
|
||||
return ver == ran.ver
|
||||
of verIntersect, verTilde, verCaret:
|
||||
return withinRange(ver, ran.verILeft) and withinRange(ver, ran.verIRight)
|
||||
of verAny:
|
||||
return true
|
||||
|
||||
proc contains*(ran: VersionRange, ver: Version): bool =
|
||||
return withinRange(ver, ran)
|
||||
|
||||
proc getNextIncompatibleVersion(version: Version, semver: bool): Version =
|
||||
## try to get next higher version to exclude according to semver semantic
|
||||
var numbers = version.version.split('.')
|
||||
let originalNumberLen = numbers.len
|
||||
while numbers.len < 3:
|
||||
numbers.add("0")
|
||||
var zeros = 0
|
||||
for n in 0 ..< 2:
|
||||
if numbers[n] == "0":
|
||||
inc(zeros)
|
||||
else: break
|
||||
var increasePosition = 0
|
||||
if (semver):
|
||||
if originalNumberLen > 1:
|
||||
case zeros
|
||||
of 0:
|
||||
increasePosition = 0
|
||||
of 1:
|
||||
increasePosition = 1
|
||||
else:
|
||||
increasePosition = 2
|
||||
else:
|
||||
increasePosition = max(0, originalNumberLen - 2)
|
||||
|
||||
numbers[increasePosition] = $(numbers[increasePosition].parseInt() + 1)
|
||||
var zeroPosition = increasePosition + 1
|
||||
while zeroPosition < numbers.len:
|
||||
numbers[zeroPosition] = "0"
|
||||
inc(zeroPosition)
|
||||
result = newVersion(numbers.join("."))
|
||||
|
||||
proc makeRange(version: Version, op: string): VersionRange =
|
||||
if version == notSetVersion:
|
||||
raise parseVersionError("A version needs to accompany the operator.")
|
||||
|
||||
case op
|
||||
of ">":
|
||||
result = VersionRange(kind: verLater, ver: version)
|
||||
of "<":
|
||||
result = VersionRange(kind: verEarlier, ver: version)
|
||||
of ">=":
|
||||
result = VersionRange(kind: verEqLater, ver: version)
|
||||
of "<=":
|
||||
result = VersionRange(kind: verEqEarlier, ver: version)
|
||||
of "", "==":
|
||||
result = VersionRange(kind: verEq, ver: version)
|
||||
of "^=", "~=":
|
||||
let
|
||||
excludedVersion = getNextIncompatibleVersion(
|
||||
version, semver = (op == "^="))
|
||||
left = makeRange(version, ">=")
|
||||
right = makeRange(excludedVersion, "<")
|
||||
|
||||
result =
|
||||
if op == "^=":
|
||||
VersionRange(kind: verCaret, verILeft: left, verIRight: right)
|
||||
else:
|
||||
VersionRange(kind: verTilde, verILeft: left, verIRight: right)
|
||||
else:
|
||||
raise parseVersionError("Invalid operator: " & op)
|
||||
|
||||
proc parseVersionRange*(s: string): VersionRange =
|
||||
# >= 1.5 & <= 1.8
|
||||
if s.len == 0:
|
||||
result = VersionRange(kind: verAny)
|
||||
return
|
||||
|
||||
if s[0] == '#':
|
||||
# Handle normal versions only.
|
||||
result = VersionRange(kind: verAny)
|
||||
return
|
||||
|
||||
var i = 0
|
||||
var op = ""
|
||||
var version = ""
|
||||
while i < s.len:
|
||||
case s[i]
|
||||
of '>', '<', '=', '~', '^':
|
||||
op.add(s[i])
|
||||
of '&':
|
||||
result = VersionRange(kind: verIntersect)
|
||||
result.verILeft = makeRange(newVersion(version), op)
|
||||
|
||||
# Parse everything after &
|
||||
# Recursion <3
|
||||
result.verIRight = parseVersionRange(substr(s, i + 1))
|
||||
|
||||
# Disallow more than one verIntersect. It's pointless and could lead to
|
||||
# major unpredictable mistakes.
|
||||
if result.verIRight.kind == verIntersect:
|
||||
raise parseVersionError(
|
||||
"Having more than one `&` in a version range is pointless")
|
||||
return
|
||||
of '0'..'9', '.':
|
||||
version.add(s[i])
|
||||
|
||||
of ' ':
|
||||
# Make sure '0.9 8.03' is not allowed.
|
||||
if version != "" and i < s.len - 1:
|
||||
if s[i+1] in {'0'..'9', '.'}:
|
||||
raise parseVersionError(
|
||||
"Whitespace is not allowed in a version literal.")
|
||||
else:
|
||||
raise parseVersionError(
|
||||
"Unexpected char in version range '" & s & "': " & s[i])
|
||||
inc(i)
|
||||
result = makeRange(newVersion(version), op)
|
||||
|
||||
proc `$`*(verRange: VersionRange): string =
|
||||
case verRange.kind
|
||||
of verLater:
|
||||
result = "> "
|
||||
of verEarlier:
|
||||
result = "< "
|
||||
of verEqLater:
|
||||
result = ">= "
|
||||
of verEqEarlier:
|
||||
result = "<= "
|
||||
of verEq:
|
||||
result = ""
|
||||
of verIntersect:
|
||||
return $verRange.verILeft & " & " & $verRange.verIRight
|
||||
of verTilde:
|
||||
return " ~= " & $verRange.verILeft
|
||||
of verCaret:
|
||||
return " ^= " & $verRange.verILeft
|
||||
of verAny:
|
||||
return "any version"
|
||||
|
||||
result.add($verRange.ver)
|
|
@ -10,6 +10,7 @@ when isMainModule:
|
|||
var opt = parseCmdLine()
|
||||
if opt.showHelp:
|
||||
writeHelp() # quits
|
||||
opt.setLogger
|
||||
opt.setSourceDir
|
||||
opt.setNimBin
|
||||
opt.setup()
|
||||
|
|
18
tests/version/test_comparison.nim
Normal file
18
tests/version/test_comparison.nim
Normal file
|
@ -0,0 +1,18 @@
|
|||
# SPDX-FileCopyrightText: Copyright (C) Dominik Picheta. All rights reserved.
|
||||
# SPDX-FileCopyrightText: 2022 Anna <cyber@sysrq.in>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import nimbs/version
|
||||
|
||||
assert newVersion("1.0") < newVersion("1.4")
|
||||
assert newVersion("1.0.1") > newVersion("1.0")
|
||||
assert newVersion("1.0.6") <= newVersion("1.0.6")
|
||||
assert not (newVersion("0.1.0") < newVersion("0.1"))
|
||||
assert not (newVersion("0.1.0") > newVersion("0.1"))
|
||||
assert newVersion("0.1.0") < newVersion("0.1.0.0.1")
|
||||
assert newVersion("0.1.0") <= newVersion("0.1")
|
||||
assert newVersion("1") == newVersion("1")
|
||||
assert newVersion("1.0.2.4.6.1.2.123") == newVersion("1.0.2.4.6.1.2.123")
|
||||
assert newVersion("1.0.2") != newVersion("1.0.2.4.6.1.2.123")
|
||||
assert newVersion("1.0.3") != newVersion("1.0.2")
|
||||
assert newVersion("1") == newVersion("1.0")
|
9
tests/version/test_comparison_empty.nim
Normal file
9
tests/version/test_comparison_empty.nim
Normal file
|
@ -0,0 +1,9 @@
|
|||
# SPDX-FileCopyrightText: Copyright (C) Dominik Picheta. All rights reserved.
|
||||
# SPDX-FileCopyrightText: 2022 Anna <cyber@sysrq.in>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import nimbs/version
|
||||
|
||||
assert not (newVersion("") < newVersion("0.0.0"))
|
||||
assert newVersion("") < newVersion("1.0.0")
|
||||
assert newVersion("") < newVersion("0.1.0")
|
9
tests/version/test_newversion_invalid.nim
Normal file
9
tests/version/test_newversion_invalid.nim
Normal file
|
@ -0,0 +1,9 @@
|
|||
# SPDX-FileCopyrightText: Copyright (C) Dominik Picheta. All rights reserved.
|
||||
# SPDX-FileCopyrightText: 2022 Anna <cyber@sysrq.in>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import unittest
|
||||
import nimbs/version
|
||||
|
||||
expect ParseVersionError:
|
||||
discard newVersion(".5")
|
15
tests/version/test_versionranges.nim
Normal file
15
tests/version/test_versionranges.nim
Normal file
|
@ -0,0 +1,15 @@
|
|||
# SPDX-FileCopyrightText: Copyright (C) Dominik Picheta. All rights reserved.
|
||||
# SPDX-FileCopyrightText: 2022 Anna <cyber@sysrq.in>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import nimbs/version
|
||||
|
||||
let versionRange1 = parseVersionRange(">= 1.0 & <= 1.5")
|
||||
let versionRange2 = parseVersionRange("1.0")
|
||||
|
||||
assert parseVersionRange("== 3.4.2") == parseVersionRange("3.4.2")
|
||||
|
||||
assert versionRange1.kind == verIntersect
|
||||
assert versionRange2.kind == verEq
|
||||
# An empty version range should give verAny
|
||||
assert parseVersionRange("").kind == verAny
|
18
tests/version/test_versionranges_invalid.nim
Normal file
18
tests/version/test_versionranges_invalid.nim
Normal file
|
@ -0,0 +1,18 @@
|
|||
# SPDX-FileCopyrightText: Copyright (C) Dominik Picheta. All rights reserved.
|
||||
# SPDX-FileCopyrightText: 2022 Anna <cyber@sysrq.in>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import unittest
|
||||
import nimbs/version
|
||||
|
||||
expect ParseVersionError:
|
||||
discard parseVersionRange("abc")
|
||||
|
||||
expect ParseVersionError:
|
||||
discard parseVersionRange(">> 0.1")
|
||||
|
||||
expect ParseVersionError:
|
||||
discard parseVersionRange("> 0.1 & > 0.2 & < 1.0")
|
||||
|
||||
expect ParseVersionError:
|
||||
discard parseVersionRange("0.9 0.10")
|
20
tests/version/test_withinrange.nim
Normal file
20
tests/version/test_withinrange.nim
Normal file
|
@ -0,0 +1,20 @@
|
|||
# SPDX-FileCopyrightText: Copyright (C) Dominik Picheta. All rights reserved.
|
||||
# SPDX-FileCopyrightText: 2022 Anna <cyber@sysrq.in>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import nimbs/version
|
||||
|
||||
let versionRange1 = parseVersionRange(">= 1.0 & <= 1.5")
|
||||
let versionRange2 = parseVersionRange("> 0.1")
|
||||
|
||||
let version1 = newVersion("0.1.0")
|
||||
let version2 = newVersion("1.5.1")
|
||||
let version3 = newVersion("1.0.2.3.4.5.6.7.8.9.10.11.12")
|
||||
|
||||
assert not withinRange(version1, versionRange2)
|
||||
assert not withinRange(version2, versionRange1)
|
||||
assert withinRange(version3, versionRange1)
|
||||
|
||||
assert version1 notin versionRange2
|
||||
assert version2 notin versionRange1
|
||||
assert version3 in versionRange1
|
Loading…
Reference in New Issue
Block a user