From c8072d79455c7d0bee6c188d321a5cd72fa82dfc Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Mon, 6 Jul 2026 03:27:38 +1000 Subject: [PATCH] Extract punkboot::utils module + tests, bump project to 0.2.1 Move project-version helper procs (parse_punkproject_version, read_punkproject_version, read_changelog_latest_version) out of make.tcl inline definitions into a loadable punkboot::utils module so the parsing logic is unit-testable. - src/modules/punkboot/utils-999999.0a1.0.tm: new module (0.1.0) with the 3 procs in punkboot::utils namespace, PUNKARGS argdoc blocks. - src/modules/punkboot/utils-buildversion.txt: initial 0.1.0. - src/tests/modules/punkboot/utils/testsuites/utils/utils.test: 19 tests covering section tracking, missing fields, wrong sections, whitespace, quote styles, changelog header parsing, pre-release suffixes. All pass. - src/make.tcl: inline procs removed; projectversion block now does package require punkboot::utils and calls punkboot::utils:: procs. - Layout make.tcl files (punk.shell-0.1, punk.project-0.1): same edit; punk.basic keeps inline procs (no bootsupport tree). - include_modules.config: added punkboot::utils entry (main + 2 layouts). - Bootsupport snapshots propagated via make.tcl bootsupport. - AGENTS.md: project version bump 0.2.0 -> 0.2.1 (patch: new make.tcl projectversion subcommand is part of the product surface); added clarifying line that make.tcl command interface warrants at least a patch bump; src/project_layouts/ added to directories agents should not directly modify. - CHANGELOG.md: 0.2.1 entry. Assisted-by: harness=opencode; primary-model=openrouter/z-ai/glm-5.2; api-location=openrouter.ai --- AGENTS.md | 3 + CHANGELOG.md | 5 + punkproject.toml | 2 +- .../modules/include_modules.config | 1 + .../modules/punkboot/utils-0.1.0.tm | 132 ++++++++++++ src/make.tcl | 76 ++----- src/modules/punkboot/utils-999999.0a1.0.tm | 132 ++++++++++++ src/modules/punkboot/utils-buildversion.txt | 4 + .../custom/_project/punk.basic/src/make.tcl | 28 ++- .../modules/include_modules.config | 1 + .../modules/punkboot/utils-0.1.0.tm | 132 ++++++++++++ .../_project/punk.project-0.1/src/make.tcl | 76 ++----- .../modules/include_modules.config | 1 + .../modules/punkboot/utils-0.1.0.tm | 132 ++++++++++++ .../_project/punk.shell-0.1/src/make.tcl | 76 ++----- .../utils/testsuites/utils/utils.test | 198 ++++++++++++++++++ 16 files changed, 837 insertions(+), 162 deletions(-) create mode 100644 src/bootsupport/modules/punkboot/utils-0.1.0.tm create mode 100644 src/modules/punkboot/utils-999999.0a1.0.tm create mode 100644 src/modules/punkboot/utils-buildversion.txt create mode 100644 src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punkboot/utils-0.1.0.tm create mode 100644 src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punkboot/utils-0.1.0.tm create mode 100644 src/tests/modules/punkboot/utils/testsuites/utils/utils.test diff --git a/AGENTS.md b/AGENTS.md index a6e7ae42..8ab0a583 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -127,6 +127,8 @@ An agent must bump the `punkproject.toml` version as part of its DOX closeout pa Changes confined to tests, build tooling internals, or non-shipped surfaces do not require a bump. When in doubt, bump patch. +The `make.tcl` command interface is part of the product surface: new subcommands, changed subcommand behaviour, or removed subcommands warrant at least a patch bump. Internal refactors of an existing subcommand's implementation that leave its interface and output unchanged stay exempt as build tooling internals. + ### Changelog Every version bump must add a corresponding entry to `CHANGELOG.md` at the repo root. The latest `## [X.Y.Z]` header in that file must match the `version` field in `punkproject.toml`. Entries are newest-first, one bullet per notable change. @@ -181,6 +183,7 @@ The project version is fully independent of module versions. A module bump (even - `lib/` (root) — Build output target for `tclsh src/make.tcl libs` - `modules_tcl8/`, `modules_tcl9/` — Tcl version-specific build output targets - `lib_tcl8/`, `lib_tcl9/` — Tcl version-specific build output targets + - `src/project_layouts/` — Project layout templates; partly updated by `make.tcl bootsupport` but full synchronisation requires manual work or future `make.tcl` improvements. Agents must not directly edit files under `src/project_layouts/` unless the user explicitly names a path there or specifically requests layout changes. When a `src/make.tcl` change affects layout make.tcl files, note it in the completion report for the user to handle — do not attempt manual synchronisation. ## Repo-wide Notes diff --git a/CHANGELOG.md b/CHANGELOG.md index e05df0ef..a6ba68a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to Entries are newest-first; one bullet per notable change. See the root `AGENTS.md` "Project Versioning" section for the bump policy. +## [0.2.1] - 2026-07-06 + +- `make.tcl projectversion`: new advisory subcommand verifying `CHANGELOG.md` matches `punkproject.toml` and warning if `src/` has commits since the last project-version bump. +- Clarified project versioning policy: the `make.tcl` command interface is part of the product surface. + ## [0.2.0] - 2026-07-05 - Initial tracking of the punkshell project version in `punkproject.toml`. diff --git a/punkproject.toml b/punkproject.toml index e51b93fe..1991c22d 100644 --- a/punkproject.toml +++ b/punkproject.toml @@ -1,3 +1,3 @@ [project] name = "punkshell" -version = "0.2.0" +version = "0.2.1" diff --git a/src/bootsupport/modules/include_modules.config b/src/bootsupport/modules/include_modules.config index 49d0fee9..d3d416a1 100644 --- a/src/bootsupport/modules/include_modules.config +++ b/src/bootsupport/modules/include_modules.config @@ -53,6 +53,7 @@ set bootsupport_modules [list\ modules flagfilter\ modules funcl\ modules punk\ + modules punkboot::utils\ modules punk::pipe\ modules punkapp\ modules punkcheck\ diff --git a/src/bootsupport/modules/punkboot/utils-0.1.0.tm b/src/bootsupport/modules/punkboot/utils-0.1.0.tm new file mode 100644 index 00000000..fa10b93e --- /dev/null +++ b/src/bootsupport/modules/punkboot/utils-0.1.0.tm @@ -0,0 +1,132 @@ +# -*- tcl -*- +# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from -buildversion.txt +# +# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. +# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# (C) 2023 +# +# @@ Meta Begin +# Application punkboot::utils 0.1.0 +# Meta platform tcl +# Meta license BSD +# @@ Meta End + + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Requirements +##e.g package require frobz +package require Tcl 8.6- + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +namespace eval punkboot::utils { + variable version 0.1.0 + namespace export parse_punkproject_version read_punkproject_version read_changelog_latest_version + + + namespace eval argdoc { + lappend PUNKARGS [list { + @id -id ::punkboot::utils::parse_punkproject_version + @cmd -name "::punkboot::utils::parse_punkproject_version"\ + -summary\ + "Parse the [project] version from TOML content string"\ + -help\ + "Scans TOML content for the [project] section and returns + the value of the version key as a string. Returns an empty + string if the section or key is not found." + @leaders + content -type string -optional 0 -help\ + "TOML content string to parse" + }] + } + proc parse_punkproject_version {content} { + set in_project 0 + foreach line [split $content \n] { + set trimmed [string trim $line] + if {[string index $trimmed 0] eq {[} && [string index $trimmed end] eq {]}} { + set in_project [expr {$trimmed eq "\[project\]"}] + continue + } + if {$in_project && [regexp {^version\s*=\s*"([^"]+)"} $trimmed _ v]} { + return $v + } + } + return "" + } + + + namespace eval argdoc { + lappend PUNKARGS [list { + @id -id ::punkboot::utils::read_punkproject_version + @cmd -name "::punkboot::utils::read_punkproject_version"\ + -summary\ + "Read the [project] version from a TOML file"\ + -help\ + "Reads the file at tomlfile and returns the [project] + version value as a string. Returns an empty string if the + file does not exist or the version is not found." + @leaders + tomlfile -type string -optional 0 -help\ + "Path to the TOML file to read" + }] + } + proc read_punkproject_version {tomlfile} { + if {![file exists $tomlfile]} {return ""} + set fh [open $tomlfile r] + try { + set content [read $fh] + } finally { + close $fh + } + return [::punkboot::utils::parse_punkproject_version $content] + } + + + namespace eval argdoc { + lappend PUNKARGS [list { + @id -id ::punkboot::utils::read_changelog_latest_version + @cmd -name "::punkboot::utils::read_changelog_latest_version"\ + -summary\ + "Read the latest ## [X.Y.Z] version header from a changelog file"\ + -help\ + "Scans the changelog file from the top and returns the + version string from the first '## [X.Y.Z]' header line. + Returns an empty string if the file does not exist or no + matching header is found." + @leaders + changelogfile -type string -optional 0 -help\ + "Path to the changelog file to read" + }] + } + proc read_changelog_latest_version {changelogfile} { + if {![file exists $changelogfile]} {return ""} + set fh [open $changelogfile r] + try { + set content [read $fh] + } finally { + close $fh + } + foreach line [split $content \n] { + if {[regexp {^##\s*\[([0-9][^\]]*)\]} $line _ v]} { + return $v + } + } + return "" + } +} + +namespace eval ::punk::args::register { + #use fully qualified so 8.6 doesn't find existing var in global namespace + lappend ::punk::args::register::NAMESPACES ::punkboot::utils ::punkboot::utils::argdoc +} + +## Ready +package provide punkboot::utils [tcl::namespace::eval punkboot::utils { + variable version + #- this version number, exactly 0.1.0, is a literal used in src module folders + #- we refer to this sometimes as the magic version number + set version 0.1.0 +}] +return diff --git a/src/make.tcl b/src/make.tcl index c31f1c75..19e78adb 100644 --- a/src/make.tcl +++ b/src/make.tcl @@ -328,55 +328,6 @@ proc ::punkboot::lib::bootsupport_prompt_yesno {prompt} { return [expr {$answer eq "y" || $answer eq "yes"}] } -#------------------------------------------------------------------------------ -# Project-version helpers — self-contained Tcl, no punk package deps. -# Used by the 'projectversion' subcommand to verify that punkproject.toml -# and CHANGELOG.md stay consistent and that the project version is bumped -# when src/ changes ship user-visible behaviour. Advisory only (warns). -#------------------------------------------------------------------------------ - -# Read the [project] version from a TOML file via a simple line scan. -# Returns the version string (e.g. "0.2.0") or "" if not found. -proc ::punkboot::lib::read_punkproject_version {tomlfile} { - if {![file exists $tomlfile]} {return ""} - set fh [open $tomlfile r] - try { - set content [read $fh] - } finally { - close $fh - } - set in_project 0 - foreach line [split $content \n] { - set trimmed [string trim $line] - if {[string index $trimmed 0] eq {[} && [string index $trimmed end] eq {]}} { - set in_project [expr {$trimmed eq "\[project\]"}] - continue - } - if {$in_project && [regexp {^version\s*=\s*"([^"]+)"} $trimmed _ v]} { - return $v - } - } - return "" -} - -# Read the latest ## [X.Y.Z] version header from a changelog file. -# Returns the version string or "" if no header found. -proc ::punkboot::lib::read_changelog_latest_version {changelogfile} { - if {![file exists $changelogfile]} {return ""} - set fh [open $changelogfile r] - try { - set content [read $fh] - } finally { - close $fh - } - foreach line [split $content \n] { - if {[regexp {^##\s*\[([0-9][^\]]*)\]} $line _ v]} { - return $v - } - } - return "" -} - if {"::try" ni [info commands ::try]} { puts stderr "Tcl interpreter possibly too old - need at least tcl 8.6 - 'try' command not found - aborting" exit 1 @@ -1877,6 +1828,7 @@ if {$::punkboot::command eq "info"} { } if {$::punkboot::command eq "projectversion"} { + package require punkboot::utils set sep [string repeat - 75] puts stdout $sep puts stdout "project version check" @@ -1886,8 +1838,8 @@ if {$::punkboot::command eq "projectversion"} { set cl_file [file join $projectroot CHANGELOG.md] set src_folder [file join $projectroot src] - set pp_version [::punkboot::lib::read_punkproject_version $pp_file] - set cl_version [::punkboot::lib::read_changelog_latest_version $cl_file] + set pp_version [::punkboot::utils::read_punkproject_version $pp_file] + set cl_version [::punkboot::utils::read_changelog_latest_version $cl_file] puts stdout "punkproject.toml version : [expr {[string length $pp_version] ? $pp_version : "(not found)"}]" puts stdout "CHANGELOG.md version : [expr {[string length $cl_version] ? $cl_version : "(not found)"}]" @@ -1906,6 +1858,8 @@ if {$::punkboot::command eq "projectversion"} { # 2. Staleness: warn if src/ has git commits since the last commit # touching punkproject.toml (version bump may be overdue). + # If punkproject.toml has an uncommitted version change, the bump is + # already in progress — report that only a commit is needed. # Best-effort — silently skipped if git is unavailable or the repo # is not under git revision control. set pp_commit "" @@ -1917,8 +1871,24 @@ if {$::punkboot::command eq "projectversion"} { set src_changes [string trim $src_raw] if {[string length $src_changes]} { set n [llength [split $src_changes \n]] - puts stderr "WARNING: $n commit(s) in src/ since last punkproject.toml change — a project-version bump may be overdue." - puts stderr " Review the changes and bump punkproject.toml per the root AGENTS.md 'Project Versioning' section." + + # Check whether a bump is already staged in the working tree + # (punkproject.toml modified but uncommitted). If the working- + # tree version differs from HEAD, the bump is done — only a + # commit is needed. + set head_version "" + if {![catch {exec git -C $projectroot show HEAD:punkproject.toml} head_raw]} { + set head_version [::punkboot::utils::parse_punkproject_version $head_raw] + } + set bump_uncommitted [expr {[string length $head_version] && [string length $pp_version] && $pp_version ne $head_version}] + + if {$bump_uncommitted} { + puts stderr "NOTE: $n commit(s) in src/ since last punkproject.toml commit, but punkproject.toml already bumped ($head_version -> $pp_version, uncommitted)." + puts stderr " Commit punkproject.toml (and CHANGELOG.md) to record the bump and clear this warning." + } else { + puts stderr "WARNING: $n commit(s) in src/ since last punkproject.toml change — a project-version bump may be overdue." + puts stderr " Review the changes and bump punkproject.toml per the root AGENTS.md 'Project Versioning' section." + } puts stderr " (showing first 5 commits below)" set i 0 foreach line [split $src_changes \n] { diff --git a/src/modules/punkboot/utils-999999.0a1.0.tm b/src/modules/punkboot/utils-999999.0a1.0.tm new file mode 100644 index 00000000..67d3db03 --- /dev/null +++ b/src/modules/punkboot/utils-999999.0a1.0.tm @@ -0,0 +1,132 @@ +# -*- tcl -*- +# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from -buildversion.txt +# +# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. +# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# (C) 2023 +# +# @@ Meta Begin +# Application punkboot::utils 999999.0a1.0 +# Meta platform tcl +# Meta license BSD +# @@ Meta End + + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Requirements +##e.g package require frobz +package require Tcl 8.6- + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +namespace eval punkboot::utils { + variable version 0.1.0 + namespace export parse_punkproject_version read_punkproject_version read_changelog_latest_version + + + namespace eval argdoc { + lappend PUNKARGS [list { + @id -id ::punkboot::utils::parse_punkproject_version + @cmd -name "::punkboot::utils::parse_punkproject_version"\ + -summary\ + "Parse the [project] version from TOML content string"\ + -help\ + "Scans TOML content for the [project] section and returns + the value of the version key as a string. Returns an empty + string if the section or key is not found." + @leaders + content -type string -optional 0 -help\ + "TOML content string to parse" + }] + } + proc parse_punkproject_version {content} { + set in_project 0 + foreach line [split $content \n] { + set trimmed [string trim $line] + if {[string index $trimmed 0] eq {[} && [string index $trimmed end] eq {]}} { + set in_project [expr {$trimmed eq "\[project\]"}] + continue + } + if {$in_project && [regexp {^version\s*=\s*"([^"]+)"} $trimmed _ v]} { + return $v + } + } + return "" + } + + + namespace eval argdoc { + lappend PUNKARGS [list { + @id -id ::punkboot::utils::read_punkproject_version + @cmd -name "::punkboot::utils::read_punkproject_version"\ + -summary\ + "Read the [project] version from a TOML file"\ + -help\ + "Reads the file at tomlfile and returns the [project] + version value as a string. Returns an empty string if the + file does not exist or the version is not found." + @leaders + tomlfile -type string -optional 0 -help\ + "Path to the TOML file to read" + }] + } + proc read_punkproject_version {tomlfile} { + if {![file exists $tomlfile]} {return ""} + set fh [open $tomlfile r] + try { + set content [read $fh] + } finally { + close $fh + } + return [::punkboot::utils::parse_punkproject_version $content] + } + + + namespace eval argdoc { + lappend PUNKARGS [list { + @id -id ::punkboot::utils::read_changelog_latest_version + @cmd -name "::punkboot::utils::read_changelog_latest_version"\ + -summary\ + "Read the latest ## [X.Y.Z] version header from a changelog file"\ + -help\ + "Scans the changelog file from the top and returns the + version string from the first '## [X.Y.Z]' header line. + Returns an empty string if the file does not exist or no + matching header is found." + @leaders + changelogfile -type string -optional 0 -help\ + "Path to the changelog file to read" + }] + } + proc read_changelog_latest_version {changelogfile} { + if {![file exists $changelogfile]} {return ""} + set fh [open $changelogfile r] + try { + set content [read $fh] + } finally { + close $fh + } + foreach line [split $content \n] { + if {[regexp {^##\s*\[([0-9][^\]]*)\]} $line _ v]} { + return $v + } + } + return "" + } +} + +namespace eval ::punk::args::register { + #use fully qualified so 8.6 doesn't find existing var in global namespace + lappend ::punk::args::register::NAMESPACES ::punkboot::utils ::punkboot::utils::argdoc +} + +## Ready +package provide punkboot::utils [tcl::namespace::eval punkboot::utils { + variable version + #- this version number, exactly 999999.0a1.0, is a literal used in src module folders + #- we refer to this sometimes as the magic version number + set version 999999.0a1.0 +}] +return diff --git a/src/modules/punkboot/utils-buildversion.txt b/src/modules/punkboot/utils-buildversion.txt new file mode 100644 index 00000000..d0b26fee --- /dev/null +++ b/src/modules/punkboot/utils-buildversion.txt @@ -0,0 +1,4 @@ +0.1.0 +#First line must be a semantic version number +#all other lines are ignored. +#0.1.0 - initial: parse_punkproject_version, read_punkproject_version, read_changelog_latest_version extracted from make.tcl inline procs diff --git a/src/project_layouts/custom/_project/punk.basic/src/make.tcl b/src/project_layouts/custom/_project/punk.basic/src/make.tcl index c31f1c75..a9d28a9b 100644 --- a/src/project_layouts/custom/_project/punk.basic/src/make.tcl +++ b/src/project_layouts/custom/_project/punk.basic/src/make.tcl @@ -345,6 +345,12 @@ proc ::punkboot::lib::read_punkproject_version {tomlfile} { } finally { close $fh } + return [::punkboot::lib::parse_punkproject_version $content] +} + +# Parse the [project] version from TOML content string. +# Returns the version string (e.g. "0.2.0") or "" if not found. +proc ::punkboot::lib::parse_punkproject_version {content} { set in_project 0 foreach line [split $content \n] { set trimmed [string trim $line] @@ -1906,6 +1912,8 @@ if {$::punkboot::command eq "projectversion"} { # 2. Staleness: warn if src/ has git commits since the last commit # touching punkproject.toml (version bump may be overdue). + # If punkproject.toml has an uncommitted version change, the bump is + # already in progress — report that only a commit is needed. # Best-effort — silently skipped if git is unavailable or the repo # is not under git revision control. set pp_commit "" @@ -1917,8 +1925,24 @@ if {$::punkboot::command eq "projectversion"} { set src_changes [string trim $src_raw] if {[string length $src_changes]} { set n [llength [split $src_changes \n]] - puts stderr "WARNING: $n commit(s) in src/ since last punkproject.toml change — a project-version bump may be overdue." - puts stderr " Review the changes and bump punkproject.toml per the root AGENTS.md 'Project Versioning' section." + + # Check whether a bump is already staged in the working tree + # (punkproject.toml modified but uncommitted). If the working- + # tree version differs from HEAD, the bump is done — only a + # commit is needed. + set head_version "" + if {![catch {exec git -C $projectroot show HEAD:punkproject.toml} head_raw]} { + set head_version [::punkboot::lib::parse_punkproject_version $head_raw] + } + set bump_uncommitted [expr {[string length $head_version] && [string length $pp_version] && $pp_version ne $head_version}] + + if {$bump_uncommitted} { + puts stderr "NOTE: $n commit(s) in src/ since last punkproject.toml commit, but punkproject.toml already bumped ($head_version -> $pp_version, uncommitted)." + puts stderr " Commit punkproject.toml (and CHANGELOG.md) to record the bump and clear this warning." + } else { + puts stderr "WARNING: $n commit(s) in src/ since last punkproject.toml change — a project-version bump may be overdue." + puts stderr " Review the changes and bump punkproject.toml per the root AGENTS.md 'Project Versioning' section." + } puts stderr " (showing first 5 commits below)" set i 0 foreach line [split $src_changes \n] { diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/include_modules.config b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/include_modules.config index 49d0fee9..d3d416a1 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/include_modules.config +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/include_modules.config @@ -53,6 +53,7 @@ set bootsupport_modules [list\ modules flagfilter\ modules funcl\ modules punk\ + modules punkboot::utils\ modules punk::pipe\ modules punkapp\ modules punkcheck\ diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punkboot/utils-0.1.0.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punkboot/utils-0.1.0.tm new file mode 100644 index 00000000..fa10b93e --- /dev/null +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punkboot/utils-0.1.0.tm @@ -0,0 +1,132 @@ +# -*- tcl -*- +# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from -buildversion.txt +# +# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. +# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# (C) 2023 +# +# @@ Meta Begin +# Application punkboot::utils 0.1.0 +# Meta platform tcl +# Meta license BSD +# @@ Meta End + + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Requirements +##e.g package require frobz +package require Tcl 8.6- + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +namespace eval punkboot::utils { + variable version 0.1.0 + namespace export parse_punkproject_version read_punkproject_version read_changelog_latest_version + + + namespace eval argdoc { + lappend PUNKARGS [list { + @id -id ::punkboot::utils::parse_punkproject_version + @cmd -name "::punkboot::utils::parse_punkproject_version"\ + -summary\ + "Parse the [project] version from TOML content string"\ + -help\ + "Scans TOML content for the [project] section and returns + the value of the version key as a string. Returns an empty + string if the section or key is not found." + @leaders + content -type string -optional 0 -help\ + "TOML content string to parse" + }] + } + proc parse_punkproject_version {content} { + set in_project 0 + foreach line [split $content \n] { + set trimmed [string trim $line] + if {[string index $trimmed 0] eq {[} && [string index $trimmed end] eq {]}} { + set in_project [expr {$trimmed eq "\[project\]"}] + continue + } + if {$in_project && [regexp {^version\s*=\s*"([^"]+)"} $trimmed _ v]} { + return $v + } + } + return "" + } + + + namespace eval argdoc { + lappend PUNKARGS [list { + @id -id ::punkboot::utils::read_punkproject_version + @cmd -name "::punkboot::utils::read_punkproject_version"\ + -summary\ + "Read the [project] version from a TOML file"\ + -help\ + "Reads the file at tomlfile and returns the [project] + version value as a string. Returns an empty string if the + file does not exist or the version is not found." + @leaders + tomlfile -type string -optional 0 -help\ + "Path to the TOML file to read" + }] + } + proc read_punkproject_version {tomlfile} { + if {![file exists $tomlfile]} {return ""} + set fh [open $tomlfile r] + try { + set content [read $fh] + } finally { + close $fh + } + return [::punkboot::utils::parse_punkproject_version $content] + } + + + namespace eval argdoc { + lappend PUNKARGS [list { + @id -id ::punkboot::utils::read_changelog_latest_version + @cmd -name "::punkboot::utils::read_changelog_latest_version"\ + -summary\ + "Read the latest ## [X.Y.Z] version header from a changelog file"\ + -help\ + "Scans the changelog file from the top and returns the + version string from the first '## [X.Y.Z]' header line. + Returns an empty string if the file does not exist or no + matching header is found." + @leaders + changelogfile -type string -optional 0 -help\ + "Path to the changelog file to read" + }] + } + proc read_changelog_latest_version {changelogfile} { + if {![file exists $changelogfile]} {return ""} + set fh [open $changelogfile r] + try { + set content [read $fh] + } finally { + close $fh + } + foreach line [split $content \n] { + if {[regexp {^##\s*\[([0-9][^\]]*)\]} $line _ v]} { + return $v + } + } + return "" + } +} + +namespace eval ::punk::args::register { + #use fully qualified so 8.6 doesn't find existing var in global namespace + lappend ::punk::args::register::NAMESPACES ::punkboot::utils ::punkboot::utils::argdoc +} + +## Ready +package provide punkboot::utils [tcl::namespace::eval punkboot::utils { + variable version + #- this version number, exactly 0.1.0, is a literal used in src module folders + #- we refer to this sometimes as the magic version number + set version 0.1.0 +}] +return diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl b/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl index c31f1c75..19e78adb 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl @@ -328,55 +328,6 @@ proc ::punkboot::lib::bootsupport_prompt_yesno {prompt} { return [expr {$answer eq "y" || $answer eq "yes"}] } -#------------------------------------------------------------------------------ -# Project-version helpers — self-contained Tcl, no punk package deps. -# Used by the 'projectversion' subcommand to verify that punkproject.toml -# and CHANGELOG.md stay consistent and that the project version is bumped -# when src/ changes ship user-visible behaviour. Advisory only (warns). -#------------------------------------------------------------------------------ - -# Read the [project] version from a TOML file via a simple line scan. -# Returns the version string (e.g. "0.2.0") or "" if not found. -proc ::punkboot::lib::read_punkproject_version {tomlfile} { - if {![file exists $tomlfile]} {return ""} - set fh [open $tomlfile r] - try { - set content [read $fh] - } finally { - close $fh - } - set in_project 0 - foreach line [split $content \n] { - set trimmed [string trim $line] - if {[string index $trimmed 0] eq {[} && [string index $trimmed end] eq {]}} { - set in_project [expr {$trimmed eq "\[project\]"}] - continue - } - if {$in_project && [regexp {^version\s*=\s*"([^"]+)"} $trimmed _ v]} { - return $v - } - } - return "" -} - -# Read the latest ## [X.Y.Z] version header from a changelog file. -# Returns the version string or "" if no header found. -proc ::punkboot::lib::read_changelog_latest_version {changelogfile} { - if {![file exists $changelogfile]} {return ""} - set fh [open $changelogfile r] - try { - set content [read $fh] - } finally { - close $fh - } - foreach line [split $content \n] { - if {[regexp {^##\s*\[([0-9][^\]]*)\]} $line _ v]} { - return $v - } - } - return "" -} - if {"::try" ni [info commands ::try]} { puts stderr "Tcl interpreter possibly too old - need at least tcl 8.6 - 'try' command not found - aborting" exit 1 @@ -1877,6 +1828,7 @@ if {$::punkboot::command eq "info"} { } if {$::punkboot::command eq "projectversion"} { + package require punkboot::utils set sep [string repeat - 75] puts stdout $sep puts stdout "project version check" @@ -1886,8 +1838,8 @@ if {$::punkboot::command eq "projectversion"} { set cl_file [file join $projectroot CHANGELOG.md] set src_folder [file join $projectroot src] - set pp_version [::punkboot::lib::read_punkproject_version $pp_file] - set cl_version [::punkboot::lib::read_changelog_latest_version $cl_file] + set pp_version [::punkboot::utils::read_punkproject_version $pp_file] + set cl_version [::punkboot::utils::read_changelog_latest_version $cl_file] puts stdout "punkproject.toml version : [expr {[string length $pp_version] ? $pp_version : "(not found)"}]" puts stdout "CHANGELOG.md version : [expr {[string length $cl_version] ? $cl_version : "(not found)"}]" @@ -1906,6 +1858,8 @@ if {$::punkboot::command eq "projectversion"} { # 2. Staleness: warn if src/ has git commits since the last commit # touching punkproject.toml (version bump may be overdue). + # If punkproject.toml has an uncommitted version change, the bump is + # already in progress — report that only a commit is needed. # Best-effort — silently skipped if git is unavailable or the repo # is not under git revision control. set pp_commit "" @@ -1917,8 +1871,24 @@ if {$::punkboot::command eq "projectversion"} { set src_changes [string trim $src_raw] if {[string length $src_changes]} { set n [llength [split $src_changes \n]] - puts stderr "WARNING: $n commit(s) in src/ since last punkproject.toml change — a project-version bump may be overdue." - puts stderr " Review the changes and bump punkproject.toml per the root AGENTS.md 'Project Versioning' section." + + # Check whether a bump is already staged in the working tree + # (punkproject.toml modified but uncommitted). If the working- + # tree version differs from HEAD, the bump is done — only a + # commit is needed. + set head_version "" + if {![catch {exec git -C $projectroot show HEAD:punkproject.toml} head_raw]} { + set head_version [::punkboot::utils::parse_punkproject_version $head_raw] + } + set bump_uncommitted [expr {[string length $head_version] && [string length $pp_version] && $pp_version ne $head_version}] + + if {$bump_uncommitted} { + puts stderr "NOTE: $n commit(s) in src/ since last punkproject.toml commit, but punkproject.toml already bumped ($head_version -> $pp_version, uncommitted)." + puts stderr " Commit punkproject.toml (and CHANGELOG.md) to record the bump and clear this warning." + } else { + puts stderr "WARNING: $n commit(s) in src/ since last punkproject.toml change — a project-version bump may be overdue." + puts stderr " Review the changes and bump punkproject.toml per the root AGENTS.md 'Project Versioning' section." + } puts stderr " (showing first 5 commits below)" set i 0 foreach line [split $src_changes \n] { diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/include_modules.config b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/include_modules.config index 49d0fee9..d3d416a1 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/include_modules.config +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/include_modules.config @@ -53,6 +53,7 @@ set bootsupport_modules [list\ modules flagfilter\ modules funcl\ modules punk\ + modules punkboot::utils\ modules punk::pipe\ modules punkapp\ modules punkcheck\ diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punkboot/utils-0.1.0.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punkboot/utils-0.1.0.tm new file mode 100644 index 00000000..fa10b93e --- /dev/null +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punkboot/utils-0.1.0.tm @@ -0,0 +1,132 @@ +# -*- tcl -*- +# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from -buildversion.txt +# +# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. +# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# (C) 2023 +# +# @@ Meta Begin +# Application punkboot::utils 0.1.0 +# Meta platform tcl +# Meta license BSD +# @@ Meta End + + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Requirements +##e.g package require frobz +package require Tcl 8.6- + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +namespace eval punkboot::utils { + variable version 0.1.0 + namespace export parse_punkproject_version read_punkproject_version read_changelog_latest_version + + + namespace eval argdoc { + lappend PUNKARGS [list { + @id -id ::punkboot::utils::parse_punkproject_version + @cmd -name "::punkboot::utils::parse_punkproject_version"\ + -summary\ + "Parse the [project] version from TOML content string"\ + -help\ + "Scans TOML content for the [project] section and returns + the value of the version key as a string. Returns an empty + string if the section or key is not found." + @leaders + content -type string -optional 0 -help\ + "TOML content string to parse" + }] + } + proc parse_punkproject_version {content} { + set in_project 0 + foreach line [split $content \n] { + set trimmed [string trim $line] + if {[string index $trimmed 0] eq {[} && [string index $trimmed end] eq {]}} { + set in_project [expr {$trimmed eq "\[project\]"}] + continue + } + if {$in_project && [regexp {^version\s*=\s*"([^"]+)"} $trimmed _ v]} { + return $v + } + } + return "" + } + + + namespace eval argdoc { + lappend PUNKARGS [list { + @id -id ::punkboot::utils::read_punkproject_version + @cmd -name "::punkboot::utils::read_punkproject_version"\ + -summary\ + "Read the [project] version from a TOML file"\ + -help\ + "Reads the file at tomlfile and returns the [project] + version value as a string. Returns an empty string if the + file does not exist or the version is not found." + @leaders + tomlfile -type string -optional 0 -help\ + "Path to the TOML file to read" + }] + } + proc read_punkproject_version {tomlfile} { + if {![file exists $tomlfile]} {return ""} + set fh [open $tomlfile r] + try { + set content [read $fh] + } finally { + close $fh + } + return [::punkboot::utils::parse_punkproject_version $content] + } + + + namespace eval argdoc { + lappend PUNKARGS [list { + @id -id ::punkboot::utils::read_changelog_latest_version + @cmd -name "::punkboot::utils::read_changelog_latest_version"\ + -summary\ + "Read the latest ## [X.Y.Z] version header from a changelog file"\ + -help\ + "Scans the changelog file from the top and returns the + version string from the first '## [X.Y.Z]' header line. + Returns an empty string if the file does not exist or no + matching header is found." + @leaders + changelogfile -type string -optional 0 -help\ + "Path to the changelog file to read" + }] + } + proc read_changelog_latest_version {changelogfile} { + if {![file exists $changelogfile]} {return ""} + set fh [open $changelogfile r] + try { + set content [read $fh] + } finally { + close $fh + } + foreach line [split $content \n] { + if {[regexp {^##\s*\[([0-9][^\]]*)\]} $line _ v]} { + return $v + } + } + return "" + } +} + +namespace eval ::punk::args::register { + #use fully qualified so 8.6 doesn't find existing var in global namespace + lappend ::punk::args::register::NAMESPACES ::punkboot::utils ::punkboot::utils::argdoc +} + +## Ready +package provide punkboot::utils [tcl::namespace::eval punkboot::utils { + variable version + #- this version number, exactly 0.1.0, is a literal used in src module folders + #- we refer to this sometimes as the magic version number + set version 0.1.0 +}] +return diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl b/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl index c31f1c75..19e78adb 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl @@ -328,55 +328,6 @@ proc ::punkboot::lib::bootsupport_prompt_yesno {prompt} { return [expr {$answer eq "y" || $answer eq "yes"}] } -#------------------------------------------------------------------------------ -# Project-version helpers — self-contained Tcl, no punk package deps. -# Used by the 'projectversion' subcommand to verify that punkproject.toml -# and CHANGELOG.md stay consistent and that the project version is bumped -# when src/ changes ship user-visible behaviour. Advisory only (warns). -#------------------------------------------------------------------------------ - -# Read the [project] version from a TOML file via a simple line scan. -# Returns the version string (e.g. "0.2.0") or "" if not found. -proc ::punkboot::lib::read_punkproject_version {tomlfile} { - if {![file exists $tomlfile]} {return ""} - set fh [open $tomlfile r] - try { - set content [read $fh] - } finally { - close $fh - } - set in_project 0 - foreach line [split $content \n] { - set trimmed [string trim $line] - if {[string index $trimmed 0] eq {[} && [string index $trimmed end] eq {]}} { - set in_project [expr {$trimmed eq "\[project\]"}] - continue - } - if {$in_project && [regexp {^version\s*=\s*"([^"]+)"} $trimmed _ v]} { - return $v - } - } - return "" -} - -# Read the latest ## [X.Y.Z] version header from a changelog file. -# Returns the version string or "" if no header found. -proc ::punkboot::lib::read_changelog_latest_version {changelogfile} { - if {![file exists $changelogfile]} {return ""} - set fh [open $changelogfile r] - try { - set content [read $fh] - } finally { - close $fh - } - foreach line [split $content \n] { - if {[regexp {^##\s*\[([0-9][^\]]*)\]} $line _ v]} { - return $v - } - } - return "" -} - if {"::try" ni [info commands ::try]} { puts stderr "Tcl interpreter possibly too old - need at least tcl 8.6 - 'try' command not found - aborting" exit 1 @@ -1877,6 +1828,7 @@ if {$::punkboot::command eq "info"} { } if {$::punkboot::command eq "projectversion"} { + package require punkboot::utils set sep [string repeat - 75] puts stdout $sep puts stdout "project version check" @@ -1886,8 +1838,8 @@ if {$::punkboot::command eq "projectversion"} { set cl_file [file join $projectroot CHANGELOG.md] set src_folder [file join $projectroot src] - set pp_version [::punkboot::lib::read_punkproject_version $pp_file] - set cl_version [::punkboot::lib::read_changelog_latest_version $cl_file] + set pp_version [::punkboot::utils::read_punkproject_version $pp_file] + set cl_version [::punkboot::utils::read_changelog_latest_version $cl_file] puts stdout "punkproject.toml version : [expr {[string length $pp_version] ? $pp_version : "(not found)"}]" puts stdout "CHANGELOG.md version : [expr {[string length $cl_version] ? $cl_version : "(not found)"}]" @@ -1906,6 +1858,8 @@ if {$::punkboot::command eq "projectversion"} { # 2. Staleness: warn if src/ has git commits since the last commit # touching punkproject.toml (version bump may be overdue). + # If punkproject.toml has an uncommitted version change, the bump is + # already in progress — report that only a commit is needed. # Best-effort — silently skipped if git is unavailable or the repo # is not under git revision control. set pp_commit "" @@ -1917,8 +1871,24 @@ if {$::punkboot::command eq "projectversion"} { set src_changes [string trim $src_raw] if {[string length $src_changes]} { set n [llength [split $src_changes \n]] - puts stderr "WARNING: $n commit(s) in src/ since last punkproject.toml change — a project-version bump may be overdue." - puts stderr " Review the changes and bump punkproject.toml per the root AGENTS.md 'Project Versioning' section." + + # Check whether a bump is already staged in the working tree + # (punkproject.toml modified but uncommitted). If the working- + # tree version differs from HEAD, the bump is done — only a + # commit is needed. + set head_version "" + if {![catch {exec git -C $projectroot show HEAD:punkproject.toml} head_raw]} { + set head_version [::punkboot::utils::parse_punkproject_version $head_raw] + } + set bump_uncommitted [expr {[string length $head_version] && [string length $pp_version] && $pp_version ne $head_version}] + + if {$bump_uncommitted} { + puts stderr "NOTE: $n commit(s) in src/ since last punkproject.toml commit, but punkproject.toml already bumped ($head_version -> $pp_version, uncommitted)." + puts stderr " Commit punkproject.toml (and CHANGELOG.md) to record the bump and clear this warning." + } else { + puts stderr "WARNING: $n commit(s) in src/ since last punkproject.toml change — a project-version bump may be overdue." + puts stderr " Review the changes and bump punkproject.toml per the root AGENTS.md 'Project Versioning' section." + } puts stderr " (showing first 5 commits below)" set i 0 foreach line [split $src_changes \n] { diff --git a/src/tests/modules/punkboot/utils/testsuites/utils/utils.test b/src/tests/modules/punkboot/utils/testsuites/utils/utils.test new file mode 100644 index 00000000..5cb9cf8f --- /dev/null +++ b/src/tests/modules/punkboot/utils/testsuites/utils/utils.test @@ -0,0 +1,198 @@ +# -*- tcl -*- +# Tests for punkboot::utils project-version helpers: +# - parse_punkproject_version: TOML content string -> version string +# - read_changelog_latest_version: changelog file -> latest ## [X.Y.Z] header +# Run: tclsh src/tests/runtests.tcl -report compact -show-passes 0 -include-paths modules/punkboot/utils/** utils.test + +package require tcltest +package require punk::lib +package require punkboot::utils + +namespace eval ::testspace { + namespace import ::tcltest::* + + # ------------------------------------------------------------------------- + # Helper: write content to a temp file. Returns the file path. + # ------------------------------------------------------------------------- + proc write_temp_file {prefix content} { + set dir [punk::lib::tempdir_newfolder -prefix $prefix] + set path [file join $dir data] + set fd [open $path w] + try { puts -nonewline $fd $content } finally { close $fd } + return $path + } + + # ========================================================================= + # parse_punkproject_version + # ========================================================================= + + test parse_ppv_standard {Standard [project] version line} -body { + set toml {[project] +name = "punkshell" +version = "0.2.0" +} + ::punkboot::utils::parse_punkproject_version $toml + } -result "0.2.0" + + test parse_ppv_no_version_field {Missing version key in [project] section} -body { + set toml {[project] +name = "punkshell" +} + ::punkboot::utils::parse_punkproject_version $toml + } -result "" + + test parse_ppv_version_in_wrong_section {Version in a non-[project] section is ignored} -body { + set toml {[project] +name = "punkshell" + +[other] +version = "9.9.9" +} + ::punkboot::utils::parse_punkproject_version $toml + } -result "" + + test parse_ppv_empty_content {Empty string returns empty} -body { + ::punkboot::utils::parse_punkproject_version "" + } -result "" + + test parse_ppv_no_section {No section header at all} -body { + ::punkboot::utils::parse_punkproject_version {version = "1.0.0"} + } -result "" + + test parse_ppv_whitespace_around_equals {Whitespace variations around = are tolerated} -body { + set toml {[project] +version = "0.3.1" +} + ::punkboot::utils::parse_punkproject_version $toml + } -result "0.3.1" + + test parse_ppv_section_after_project {A later section does not override [project] version} -body { + set toml {[project] +version = "0.2.0" + +[dependencies] +version = "1.5.0" +} + ::punkboot::utils::parse_punkproject_version $toml + } -result "0.2.0" + + test parse_ppv_singlequoted_version {Single-quoted value is not matched (TOML requires double quotes)} -body { + set toml {[project] +version = '0.2.0' +} + ::punkboot::utils::parse_punkproject_version $toml + } -result "" + + # ========================================================================= + # read_punkproject_version + # ========================================================================= + + test read_ppv_file_not_found {Non-existent file returns empty} -body { + ::punkboot::utils::read_punkproject_version /nonexistent/path/to/punkproject.toml + } -result "" + + test read_ppv_file_standard {Read a real-ish TOML file} -body { + set path [write_temp_file ppvread {[project] +name = "punkshell" +version = "1.2.3" +}] + set result [::punkboot::utils::read_punkproject_version $path] + file delete -force [file dirname $path] + set result + } -result "1.2.3" + + # ========================================================================= + # read_changelog_latest_version + # ========================================================================= + + test read_clv_standard {Standard changelog with one entry} -body { + set content {# Changelog + +## [0.2.0] - 2026-07-05 + +- Initial tracking. +} + set path [write_temp_file clvstd $content] + set result [::punkboot::utils::read_changelog_latest_version $path] + file delete -force [file dirname $path] + set result + } -result "0.2.0" + + test read_clv_multiple_entries {First (newest) header returned when multiple exist} -body { + set content {# Changelog + +## [0.2.1] - 2026-07-06 + +- New subcommand. + +## [0.2.0] - 2026-07-05 + +- Initial tracking. +} + set path [write_temp_file clvmulti $content] + set result [::punkboot::utils::read_changelog_latest_version $path] + file delete -force [file dirname $path] + set result + } -result "0.2.1" + + test read_clv_file_not_found {Non-existent file returns empty} -body { + ::punkboot::utils::read_changelog_latest_version /nonexistent/path/to/CHANGELOG.md + } -result "" + + test read_clv_empty_file {Empty file returns empty} -body { + set path [write_temp_file clvempty ""] + set result [::punkboot::utils::read_changelog_latest_version $path] + file delete -force [file dirname $path] + set result + } -result "" + + test read_clv_no_headers {File with no ## [X.Y.Z] headers returns empty} -body { + set content {# Changelog + +Some prose without a version header. +} + set path [write_temp_file clvnohdr $content] + set result [::punkboot::utils::read_changelog_latest_version $path] + file delete -force [file dirname $path] + set result + } -result "" + + test read_clv_extra_spaces {Extra whitespace after ## is tolerated} -body { + set content {## [0.5.0] - 2026-08-01} + set path [write_temp_file clvspc $content] + set result [::punkboot::utils::read_changelog_latest_version $path] + file delete -force [file dirname $path] + set result + } -result "0.5.0" + + test read_clv_prerelease_suffix {Version with pre-release suffix is captured} -body { + set content {## [0.2.1-rc1] - 2026-07-06} + set path [write_temp_file clvpre $content] + set result [::punkboot::utils::read_changelog_latest_version $path] + file delete -force [file dirname $path] + set result + } -result "0.2.1-rc1" + + test read_clv_single_hash {Single # header is not matched (only ##)} -body { + set content {# [0.1.0] this is a single hash} + set path [write_temp_file clv1h $content] + set result [::punkboot::utils::read_changelog_latest_version $path] + file delete -force [file dirname $path] + set result + } -result "" + + test read_clv_triple_hash {Triple ### subheader is not matched} -body { + set content {## [0.2.0] - 2026-07-05 + +### Fixed + +- something +} + set path [write_temp_file clv3h $content] + set result [::punkboot::utils::read_changelog_latest_version $path] + file delete -force [file dirname $path] + set result + } -result "0.2.0" + + tcltest::cleanupTests +}