From 131eb8df1647b7189c22edd856596cb483ab6e95 Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Sat, 14 Jun 2025 14:44:05 -0500 Subject: [PATCH] Internal functionality implementation. --- .gitignore | 2 + update_version.nim | 208 ++++++++++++++++++++++++++++++++++++++++++ update_version.nimble | 2 +- 3 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 update_version.nim diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..746fa9d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.*sw? +update_version diff --git a/update_version.nim b/update_version.nim new file mode 100644 index 0000000..2f824a5 --- /dev/null +++ b/update_version.nim @@ -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 [ ...] [options] + update_nim_package_version set [ ...] [options] + update_nim_package_version get [ ...] [options] + update_nim_package_version interactive [ ...] [options] + update_nim_package_version test + +Options: + + -l, --lang 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. 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. + + + + Sometimes it is useful to encode the version in source files via constants. + Passing arguments as 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 _VERSION* = "" + export const _VERSION = "" + + 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"^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?: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[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.. 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." diff --git a/update_version.nimble b/update_version.nimble index 90c79a4..3796f44 100644 --- a/update_version.nimble +++ b/update_version.nimble @@ -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"]