Internal functionality implementation.

This commit is contained in:
Jonathan Bernard
2025-06-14 14:44:05 -05:00
parent d8d66c19d1
commit 131eb8df16
3 changed files with 211 additions and 1 deletions
+2
View File
@@ -0,0 +1,2 @@
.*sw?
update_version
+208
View File
@@ -0,0 +1,208 @@
import std/[dirs, json, paths, sequtils, strutils, syncio, tables]
import std/nre except toSeq
import docopt, zero_functional
const USAGE = """Usage:
update_nim_package_version bump <part> [<src-file> ...] [options]
update_nim_package_version set <new-version> [<src-file> ...] [options]
update_nim_package_version get [<src-file> ...] [options]
update_nim_package_version interactive [<src-file> ...] [options]
update_nim_package_version test
Options:
-l, --lang <language> Choose the language/ecosystem to consider. Valid
values are: 'nim' or 'node'. If not provided, this
is auto-detected by the presence of either a *.nimble
or package.json file.
Details:
bump
Assuming the project with a semver-like versioning scheme, update one of
the version parts by one. <version-part> must be one of 'major', 'minor',
'patch', or 'last'. Semver looks like 'major.minor.patch' Last is a special
case to support looser version strings that end in '.x' like 'alpha.1' or
'1.5' and matches '.*(\.\d+)$'
The matched portion is interpreted as an integer and incremented by one.
set
Update the version string entirely with a new string. In this case, the
versioning scheme of the project doesn't matter
get
Just return the existing version.
interactive
Update the version interactively.
<src-file>
Sometimes it is useful to encode the version in source files via constants.
Passing arguments as <src-files> instructs update_version to modify these
source files directly. It does so by matching constant assignments via
regex and replacing the previous version string with the new version
string. When inspecting the provided files, update_version matches the
following patterns for constanst assignments to consider:
const <package-name>_VERSION* = "<prev-version-string>"
export const <package-name>_VERSION = "<prev-version-string>"
Specifically it uses the following regexes:
TODO
"""
const UV_VERSION = "1.0.0"
type
LangType = enum lNim, lNode
SemVerParts = enum major, minor, patch, prerelease, buildmetadata
type PackageVersion = object
packageFile: Path
version: string
name: string
case lang: LangType
of lNim:
discard
of lNode:
nodePackage: JsonNode
# Taken from
# https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
#
# See also:
# https://regex101.com/r/Ly7O1x/3/
#
let SemVerPattern =
re"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
proc parseNimblePackage(dir: Path): PackageVersion =
for fe in walkDir(dir):
if fe.kind == pcFile and
fe.path.splitFile.ext == ".nimble":
let content = readFile($fe.path)
for v in content.splitLines:
if v.startsWith("version"):
return PackageVersion(
lang: lNim,
packageFile: fe.path,
version: v.split("=")[^1].strip(chars = {' ', '"'}),
name: $fe.path.splitFile.name)
raise newException(IOError, "No valid .nimble file found in $#" % [$dir])
proc parseNodePackage(dir: Path): PackageVersion =
result.packageFile = dir / Path("package.json")
result.lang = lNode
result.nodePackage = parseFile($result.packageFile)
if not result.nodePackage.hasKey("name") or
not result.nodePackage.hasKey("version") or
result.nodePackage["name"].kind != JString or
result.nodePackage["version"].kind != JString:
raise newException(ValueError,
"package.json does not have valid 'name' and 'version' fields.")
result.name = result.nodePackage{"name"}.getStr
result.version = result.nodePackage{"version"}.getStr
proc incrementLastVersionPart(version: string): string =
let versionParts = toSeq(findIter(version, re"([^\d.]+)?\.?(\d+)"))
let lastVersionPartMatch = versionParts[^1]
let lastVersionPartInt = parseInt(lastVersionPartMatch.captures[1])
return
version[0..<lastVersionPartMatch.captureBounds[1].a] &
$(lastVersionPartInt + 1) &
version[lastVersionPartMatch.captureBounds[1].b+1 .. ^1]
let VERSION_DEFAULTS: TableRef[SemVerParts, string] = newTable([
(major, "0"),
(minor, "0"),
(patch, "0"),
(prerelease, ""),
(buildmetadata, "")])
proc incrementSemverPart(
version: string,
part: SemVerParts,
defaults = VERSION_DEFAULTS): string =
result = ""
let matchOpt = match(version, SemVerPattern)
if matchOpt.isNone:
raise newException(ValueError,
"Version [$#] is not a valid Semantic Version number" % version)
let m = matchOpt.get
let versionParts = newTable[SemVerParts, string]()
for p in SemVerParts.items:
if p == part:
if m.captures.contains($p):
versionParts[p] = incrementLastVersionPart(m.captures[$p])
elif p < prerelease or defaults[p].len > 0:
versionParts[p] = defaults[p]
# if this part specifically has been requested to be incremented but
# the default is empty, we are still going to give it *something*
elif p == prerelease: versionParts[p] = "prerelease.0"
elif p == buildmetadata: versionParts[p] = "build.0"
elif not m.captures.contains($p) or p > part: versionParts[p] = defaults[p]
else: versionParts[p] = m.captures[$p]
result = "$#.$#.$#" % [
versionParts[major], versionParts[minor], versionParts[patch] ]
if versionParts[prerelease].len > 0:
result &= "-" & versionParts[prerelease]
if versionParts[buildmetadata].len > 0:
result &= "+" & versionParts[buildmetadata]
when isMainModule:
let args = docopt(USAGE, version = UV_VERSION)
if args["bump"]:
discard
elif args["set"]:
discard
elif args["get"]:
discard
elif args["interactive"]:
discard
elif args["test"]:
# incrementLastVersionPart
assert incrementLastVersionPart("1.0.0") == "1.0.1"
assert incrementLastVersionPart("1.0.0-alpha.1") == "1.0.0-alpha.2"
assert incrementLastVersionPart("cicd_alphe.1-prerelease") == "cicd_alphe.2-prerelease"
assert incrementLastVersionPart("2024.04.1") == "2024.04.2"
# incrementSemverPart
assert incrementSemverPart("1.0.0", major) == "2.0.0"
assert incrementSemverPart("1.0.0", minor) == "1.1.0"
assert incrementSemverPart("1.0.0", patch) == "1.0.1"
assert incrementSemverPart("1.5.10", patch) == "1.5.11"
assert incrementSemverPart("1.5.10", minor) == "1.6.0"
assert incrementSemverPart("1.5.10-alpha.1", prerelease) == "1.5.10-alpha.2"
assert incrementSemverPart("1.5.10-alpha.1+build.10", prerelease) == "1.5.10-alpha.2"
assert incrementSemverPart("1.5.10-alpha.1+build.10", buildmetadata) == "1.5.10-alpha.1+build.11"
assert incrementSemverPart("1.5.10", buildmetadata) == "1.5.10+build.0"
echo "All tests passed."
+1 -1
View File
@@ -10,4 +10,4 @@ bin = @["update_version"]
# Dependencies
requires @["nim >= 1.0.4", "docopt >= 0.7.1", "nimble"]
requires @["nim >= 1.0.4", "docopt >= 0.7.1", "zero_functional"]