diff --git a/.gitea/workflows/pr-validation.yaml b/.gitea/workflows/pr-validation.yaml index fcd4f93..3d0f1c9 100644 --- a/.gitea/workflows/pr-validation.yaml +++ b/.gitea/workflows/pr-validation.yaml @@ -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 diff --git a/.gitignore b/.gitignore index 746fa9d..3377ab2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .*sw? update_version +tupdate_version diff --git a/tupdate_version.nim b/tupdate_version.nim new file mode 100644 index 0000000..75070a4 --- /dev/null +++ b/tupdate_version.nim @@ -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) +""" diff --git a/update_version.nim b/update_version.nim index cef6201..7c40fc6 100644 --- a/update_version.nim +++ b/update_version.nim @@ -3,11 +3,10 @@ import std/nre except toSeq import docopt, zero_functional const USAGE = """Usage: - update_nim_package_version bump [ ...] [options] + 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: @@ -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 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. 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. 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*"()"\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"^(?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-]+)*))?$" 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.. 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[""] == "last": + let partName = + if args["--part"]: $args["--part"] + else: "last" + + if partName == "last": pkg.version = incrementLastVersionPart(pkg.version) else: - let part = parseEnum[SemVerParts]($args[""]) + 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[""] + + if not args["--part"]: + pkg.version = $args[""] + elif $args["--part"] == "last": + pkg.version = setLastVersionPart(pkg.version, $args[""]) + else: + let part = parseSemverPart($args["--part"]) + pkg.version = setSemverPart(pkg.version, part, $args[""]) for filePath in args[""]: replaceVersionInFile(Path(filePath), pkg, oldVersion) @@ -308,4 +354,4 @@ when isMainModule: for filePath in args[""]: echo "Updating version definition in " & $filePath - replaceVersionInFile(Path(filePath), pkg, oldVersion) \ No newline at end of file + replaceVersionInFile(Path(filePath), pkg, oldVersion)