Add the ability to directly set version parts.
PR Validation / Unit Tests (pull_request) Successful in 15s
PR Validation / Unit Tests (pull_request) Successful in 15s
- Move unittests to a dedicated file. - Rework CLI making version part selection an option that can be passed into many commands. - Add support for updates to version constants defined in soruce files.
This commit was merged in pull request #2.
This commit is contained in:
+118
-72
@@ -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)
|
||||
@@ -308,4 +354,4 @@ when isMainModule:
|
||||
|
||||
for filePath in args["<src-file>"]:
|
||||
echo "Updating version definition in " & $filePath
|
||||
replaceVersionInFile(Path(filePath), pkg, oldVersion)
|
||||
replaceVersionInFile(Path(filePath), pkg, oldVersion)
|
||||
|
||||
Reference in New Issue
Block a user