Add the ability to directly set version parts. #2

Merged
jdb merged 1 commits from allow-direct-version-part-updates into main 2025-06-18 01:54:06 +00:00
4 changed files with 219 additions and 74 deletions
Showing only changes of commit 8f71630e78 - Show all commits
+2 -2
View File
@@ -26,9 +26,9 @@ jobs:
- name: Build unittests
run: |
printf "🔨\033[0;32m Compiling unit tests...\033[0m\n" >&1
nimble build
nimble c tupdate_version.nim
- name: Run unittests
run: |
printf "🧪\033[0;32m Running unit tests...\033[0m\n" >&1
./update_version test
./tupdate_version
+1
View File
@@ -1,2 +1,3 @@
.*sw?
update_version
tupdate_version
+98
View File
@@ -0,0 +1,98 @@
import std/[paths, tables, unittest]
import ./update_version
suite "update_version":
test "incrementLastVersionPart":
check:
incrementLastVersionPart("1.0.0") == "1.0.1"
incrementLastVersionPart("1.0.0-alpha.1") == "1.0.0-alpha.2"
incrementLastVersionPart("cicd_alphe.1-prerelease") == "cicd_alphe.2-prerelease"
incrementLastVersionPart("2024.04.1") == "2024.04.2"
test "incrementSemverPart":
check:
incrementSemverPart("1.0.0", major) == "2.0.0"
incrementSemverPart("1.0.0", minor) == "1.1.0"
incrementSemverPart("1.0.0", patch) == "1.0.1"
incrementSemverPart("1.5.10", patch) == "1.5.11"
incrementSemverPart("1.5.10", minor) == "1.6.0"
incrementSemverPart("1.5.10-alpha.1", prerelease) == "1.5.10-alpha.2"
incrementSemverPart("1.5.10-alpha.1+build.10", prerelease) == "1.5.10-alpha.2"
incrementSemverPart("1.5.10-alpha.1+build.10", buildmetadata) == "1.5.10-alpha.1+build.11"
incrementSemverPart("1.5.10", buildmetadata) == "1.5.10+build.0"
test "fmtSemver":
check:
fmtSemver(newTable([
(major, "5"), (minor, "2"), (patch, "62"),
(prerelease, "alpha.59"), (buildmetadata, "githash_123098")])) ==
"5.2.62-alpha.59+githash_123098"
test "setLastVersionPart":
check:
setLastVersionPart("1.0.0", "5") == "1.0.5"
setLastVersionPart("cicd.2", "3-alpha.1") == "cicd.3-alpha.1"
test "setSemverPart":
check:
setSemverPart("1.4.2-alpha.5+a3e4b69", minor, "7") == "1.7.0"
setSemverPart("1.4.2-alpha.5+a3e4b69", major, "3") == "3.0.0"
setSemverPart("1.4.2-alpha.5+a3e4b69", prerelease, "beta.1") == "1.4.2-beta.1"
setSemverPart("1.4.2-alpha.5+a3e4b69", buildmetadata, "testbuild") == "1.4.2-alpha.5+testbuild"
test "replaceVersionInSource - Nim":
let pkgVersion = PackageVersion(
lang: lNim,
file: Path("./testpackage.nimble"),
version: "1.0.3",
name: "testpackage")
let source = """
import std/[sequtils, strutils]
const IGNORED_VERSION* = "1.0.0"
const OTHER_PKG_VERSION* = "4.2.1"
const TESTPKG_VERSION* = "1.0.2"
when isMainModule:
echo "sample file"
"""
check replaceVersionInSource(source, pkgVersion, "1.0.2") == """
import std/[sequtils, strutils]
const IGNORED_VERSION* = "1.0.0"
const OTHER_PKG_VERSION* = "4.2.1"
const TESTPKG_VERSION* = "1.0.3"
when isMainModule:
echo "sample file"
"""
test "replaceVersionInSource - JavaScript":
let pkgVersion = PackageVersion(
lang: lNode,
file: Path("./package.json"),
version: "1.0.3",
name: "testpackage")
let source = """
import { createApp } from 'vue'
import './styles/main.css'
import App from './App.vue'
export const TESTPACKAGE_VERSION = "1.0.2"
createApp(App)
"""
check replaceVersionInSource(source, pkgVersion, "1.0.2") == """
import { createApp } from 'vue'
import './styles/main.css'
import App from './App.vue'
export const TESTPACKAGE_VERSION = "1.0.3"
createApp(App)
"""
+117 -71
View File
@@ -3,11 +3,10 @@ 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 bump [<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:
@@ -18,22 +17,28 @@ Options:
values are: 'nim' or 'node'. If not provided, this
is auto-detected by the presence of either a *.nimble
or package.json file.
-p, --part <part> Choose which part of the version string to set or
bump. Valid options are: 'major', 'minor', 'patch',
'prerelease', 'buildmetadata', and 'last'. Defaults
to 'last'
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 version parts by one. <part> must be one of 'major', 'minor', 'patch',
'prerelease', 'buildmetadata', or 'last'. Semver looks like
'major.minor.patch-prerelease+buildmetadata' 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
Update the version string with a new string. If --part is supplied, only
that part is updated. Otherwise the entire version string is replaced.
get
@@ -61,18 +66,18 @@ Details:
Node: ^\s*export\s+const\s+\S*VERSION\S*\s*=\s*"(<old-version>)"\s*;?$
"""
const UV_VERSION = "1.0.2"
const UV_VERSION* = "1.0.2"
type
LangType = enum lNim, lNode
SemVerParts = enum major, minor, patch, prerelease, buildmetadata
LangType* = enum lNim, lNode
SemverParts* = enum major, minor, patch, prerelease, buildmetadata
type PackageVersion = object
file: Path
version: string
name: string
type PackageVersion* = object
file*: Path
version*: string
name*: string
case lang: LangType
case lang*: LangType
of lNim:
lines: seq[string]
versionLine: int
@@ -86,13 +91,29 @@ type PackageVersion = object
# See also:
# https://regex101.com/r/Ly7O1x/3/
#
let SemVerRegex =
let SemverRegex =
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-]+)*))?$"
let NimConstPattern = "^\\s*const\\s+\\S*VERSION\\S*\\*?\\s*=\\s*\"($#)\"\\s*$"
let NodeConstPattern =
"^\\s*export\\s+const\\s+\\S*VERSION\\S*\\s*=\\s*\"($#)\"\\s*;?$"
let VERSION_DEFAULTS: TableRef[SemverParts, string] = newTable([
(major, "0"),
(minor, "0"),
(patch, "0"),
(prerelease, ""),
(buildmetadata, "")])
proc parseSemverPart(str: string): SemverParts =
try: parseEnum[SemverParts](str)
except:
stderr.writeLine(
"update_version: Invalid --part value: '" & str & "'. Valid values are:" &
"\n\tmajor, minor, patch, prerelease, buildmetadata, last")
quit(QuitFailure)
proc parseNimblePackage(dir: Path): PackageVersion =
result = PackageVersion(lang: lNim)
@@ -151,14 +172,18 @@ proc writePackage(pkg: PackageVersion) =
writeFile($pkg.file, pkg.nodePackage.pretty)
proc replaceVersionInFile(file: Path, pkg: PackageVersion, vOld: string) =
proc replaceVersionInSource*(
source: string,
pkg: PackageVersion,
vOld: string): string =
let rgx =
case pkg.lang
of lNim: re(NimConstPattern % [vOld.replace(".", "\\.")])
of lNode: re(NodeConstPattern % [vOld.replace(".", "\\.")])
var newLines = newSeq[string]()
for l in lines($file):
for l in source.splitLines():
let mOpt = l.match(rgx)
if mOpt.isSome:
let m = mOpt.get
@@ -168,9 +193,60 @@ proc replaceVersionInFile(file: Path, pkg: PackageVersion, vOld: string) =
l[m.captureBounds[0].b+1 .. ^1])
else: newLines.add(l)
writeFile($file, newLines.join("\p"))
return newLines.join("\p")
proc incrementLastVersionPart(version: string): string =
proc replaceVersionInFile(file: Path, pkg: PackageVersion, vOld: string) =
writeFile($file, replaceVersionInSource(readFile($file), pkg, vOld))
proc fmtSemver*(semver: TableRef[SemverParts, string]): string =
result = "$#.$#.$#" % [
semver[major], semver[minor], semver[patch] ]
if semver[prerelease].len > 0:
result &= "-" & semver[prerelease]
if semver[buildmetadata].len > 0:
result &= "+" & semver[buildmetadata]
proc setLastVersionPart*(version: string, newVersion: string): string =
let versionParts = toSeq(findIter(version, re"([^\d.]+)?\.?(\d+)"))
let lastVersionPartMatch = versionParts[^1]
return
version[0..<lastVersionPartMatch.captureBounds[1].a] &
newVersion &
version[lastVersionPartMatch.captureBounds[1].b+1 .. ^1]
proc setSemverPart*(
version: string,
part: SemverParts,
newVersionPart: string,
defaults = VERSION_DEFAULTS): string =
let matchOpt = match(version, SemverRegex)
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: versionParts[p] = newVersionPart
elif not m.captures.contains($p) or p > part: versionParts[p] = defaults[p]
else: versionParts[p] = m.captures[$p]
result = fmtSemver(versionParts)
if match(result, SemverRegex).isNone:
raise newException(ValueError,
"Refusing to update: Version [$#] would not be a valid Semantic Version number" % result)
proc incrementLastVersionPart*(version: string): string =
let versionParts = toSeq(findIter(version, re"([^\d.]+)?\.?(\d+)"))
let lastVersionPartMatch = versionParts[^1]
let lastVersionPartInt = parseInt(lastVersionPartMatch.captures[1])
@@ -180,22 +256,12 @@ proc incrementLastVersionPart(version: string): string =
version[lastVersionPartMatch.captureBounds[1].b+1 .. ^1]
let VERSION_DEFAULTS: TableRef[SemVerParts, string] = newTable([
(major, "0"),
(minor, "0"),
(patch, "0"),
(prerelease, ""),
(buildmetadata, "")])
proc incrementSemverPart(
proc incrementSemverPart*(
version: string,
part: SemVerParts,
part: SemverParts,
defaults = VERSION_DEFAULTS): string =
result = ""
let matchOpt = match(version, SemVerRegex)
let matchOpt = match(version, SemverRegex)
if matchOpt.isNone:
raise newException(ValueError,
@@ -203,8 +269,8 @@ proc incrementSemverPart(
let m = matchOpt.get
let versionParts = newTable[SemVerParts, string]()
for p in SemVerParts.items:
let versionParts = newTable[SemverParts, string]()
for p in SemverParts.items:
if p == part:
if m.captures.contains($p):
versionParts[p] = incrementLastVersionPart(m.captures[$p])
@@ -217,42 +283,11 @@ proc incrementSemverPart(
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]
return fmtSemver(versionParts)
when isMainModule:
let args = docopt(USAGE, version = UV_VERSION)
if 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."
quit(QuitSuccess)
let dir =
if args["--dir"]: Path($args["--dir"])
else: Path(".")
@@ -268,10 +303,14 @@ when isMainModule:
let oldVersion = pkg.version
if args["bump"]:
if $args["<part>"] == "last":
let partName =
if args["--part"]: $args["--part"]
else: "last"
if partName == "last":
pkg.version = incrementLastVersionPart(pkg.version)
else:
let part = parseEnum[SemVerParts]($args["<part>"])
let part = parseSemverPart(partName)
pkg.version = incrementSemverPart(pkg.version, part)
writePackage(pkg)
@@ -282,7 +321,14 @@ when isMainModule:
echo pkg.version
elif args["set"]:
pkg.version = $args["<new-version>"]
if not args["--part"]:
pkg.version = $args["<new-version>"]
elif $args["--part"] == "last":
pkg.version = setLastVersionPart(pkg.version, $args["<new-version>"])
else:
let part = parseSemverPart($args["--part"])
pkg.version = setSemverPart(pkg.version, part, $args["<new-version>"])
for filePath in args["<src-file>"]:
replaceVersionInFile(Path(filePath), pkg, oldVersion)