diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a52a131..1fcc7c8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ 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.64.0] - 2026-08-08 + +- `commandstack` 0.8.0 (G-176 increment 3): `rename_command` accepts a new + leading option `-punkargs ` attaching punk::args + doc blocks to the rename's stack record - e.g documenting a subcommand + the override adds, with space-form ids such as `{::package epoch}`. The + docs are live while the record is on the stack and are removed with it + by ANY removal path (`remove_rename`, `pop_rename`, `remove_renamer`, + `restore_original`). Renames performed before punk::args loads are + supported: definitions are mirrored in the registered + `::commandstack::stackdocs` namespace and load lazily with punk::args; + when punk::args is already present they are defined immediately. The + record carries the deflists under a trailing `punkargs` key (key-order + contract preserved); `commandstack::help` and the `rename_command` + argdoc describe the mechanism. + ## [0.63.0] - 2026-08-08 - `punk::libunknown` 0.3.0 (G-176 increment 2): the `::package` override diff --git a/goals/G-176-commandstack-doc-integration.md b/goals/G-176-commandstack-doc-integration.md index c495233e..fa3558cb 100644 --- a/goals/G-176-commandstack-doc-integration.md +++ b/goals/G-176-commandstack-doc-integration.md @@ -145,6 +145,31 @@ Increments land as separate commits, each leaving all suites green: parent paths and needed no changes. Suites: libunknown tree + loadedlib 42/42; full modules tree green. Project 0.63.0. +- Increment 3 (-punkargs stack-tied docs) landed 2026-08-08: commandstack 0.8.0. + rename_command takes a leading '-punkargs ' (each + element one definitionlist as given to punk::args::define - the same shape as + a PUNKARGS variable element; the leading-option parse is generalised, -renamer + and -punkargs in either order, both leading-only with pointed misplacement + errors). Docs attach on a landed rename only: record gains a TRAILING + 'punkargs' key (key-order contract intact), deflists are mirrored in the new + reload-guarded ::commandstack::stackdocs namespace (registered inert into + ::punk::args::register::NAMESPACES at module load - punk::args need not + exist) and are ALSO defined immediately when punk::args is present (a + namespace already consumed into loaded_packages never lazy-loads later + appends; redefinition idempotent - a define error surfaces to the caller with + the rename installed). Detach lives in remove_rename's doomed-record site - + pop_rename/remove_renamer/restore_original all funnel through it - removing + one mirror occurrence per deflist and undefining via + punk::args::undefine_deflist (deflist-keyed; never-defined deflists skip + silently). One live record per doc id is the documented shape. + commandstack::help + the rename_command argdoc carry the how-to; In-tree + users line gains punk::libunknown. 5 new commandstack.test pins (docs live + with record incl key order, all four removal paths detach, the pre-punk::args + pending path in a bare child loading punk::args afterwards, reload contract + on the mirror + single registration, option positions) + 2 G-160 message pins + updated to the generalised usage/misplacement wording. Suite 46/46. Project + 0.64.0. + ## Follow-ons - Boot-site commandstack pre-sourcing (out of the current Scope - a Scope diff --git a/punkproject.toml b/punkproject.toml index af4ef282..22d82247 100644 --- a/punkproject.toml +++ b/punkproject.toml @@ -1,6 +1,6 @@ [project] name = "punkshell" -version = "0.63.0" +version = "0.64.0" license = "BSD-2-Clause" url = "https://www.gitea1.intx.com.au/jn/punkshell" #packager: declared identity for published artifacts (declarative, not proof - diff --git a/src/modules/commandstack-999999.0a1.0.tm b/src/modules/commandstack-999999.0a1.0.tm index 5d57369d..ac0106a5 100644 --- a/src/modules/commandstack-999999.0a1.0.tm +++ b/src/modules/commandstack-999999.0a1.0.tm @@ -107,6 +107,30 @@ namespace eval commandstack { } } +namespace eval commandstack::stackdocs { + #Doc blocks attached by 'rename_command -punkargs' for LIVE stack records + #(G-176): PUNKARGS mirrors those records' punk::args definitionlists so a + #punk::args that loads AFTER the renames (early-boot renames precede it) + #picks them up lazily via the ::punk::args::register::NAMESPACES mechanism. + #Maintained by rename_command (attach) and remove_rename (detach - every + #removal path funnels through it). Guarded per the reload contract. + variable PUNKARGS + if {![info exists PUNKARGS]} { + set PUNKARGS [list] + } +} +namespace eval ::punk::args::register { + #inert registration - consumed if/when punk::args loads (idempotent across + #re-source; punk::args need not be present) + variable NAMESPACES + if {![info exists NAMESPACES]} { + set NAMESPACES [list] + } + if {"::commandstack::stackdocs" ni $NAMESPACES} { + lappend NAMESPACES ::commandstack::stackdocs + } +} + namespace eval commandstack::util { #note - we can't use something like md5 to ID proc body text because we don't want to require additional packages. #We could store the full text of the body to compare - but we need to identify magic strings from cooperating packages such as packageTrace @@ -220,6 +244,22 @@ namespace eval commandstack { commandstack::next {*}$args The record also carries a trailing did_rename 0|1 verdict. + set record [commandstack::rename_command -renamer -punkargs ] + As above, additionally attaching punk::args doc blocks to the stack + record - each element is one definitionlist exactly as + given to punk::args::define (e.g documenting a subcommand the + override adds, with a space-form id such as {::package epoch}). + The docs are live while the record is on the stack and are removed + with it by ANY removal path (remove_rename, pop_rename, + remove_renamer, restore_original). Renames performed BEFORE + punk::args loads are supported: definitions are mirrored in the + registered ::commandstack::stackdocs namespace and load lazily with + punk::args; when punk::args is already loaded they are defined + immediately. Use one live record per doc id - same-id declarations + in two records shadow each other, and removing either removes the + doc. The record carries the attached deflists under a trailing + punkargs key. + commandstack::remove_rename Undo a rename. Accepts the token from the rename record ([dict get $record token] = { }), @@ -268,8 +308,9 @@ namespace eval commandstack { `i commandstack::rename_command` in punkshell, or `punk::args::usage ::commandstack::rename_command` when punk::args is loaded. - In-tree users: punk::packagepreference, punk::nav::fs, punk (auto_execok), - packagetrace, packagesuppress. + In-tree users: punk::packagepreference, punk::libunknown (the ::package + epoch/forget override), punk::nav::fs, punk (auto_execok), packagetrace, + packagesuppress. } } @@ -458,6 +499,7 @@ namespace eval commandstack { Returns the new stack record - a dict with keys: token renamer next_implementor next_getter implementation did_rename + (plus a trailing punkargs key when -punkargs was given). The tokenid (third token element) is unique and monotonic per (renamer, command) pairing, so repeat renames by the same renamer are individually addressable. When no rename was @@ -475,8 +517,28 @@ namespace eval commandstack { "Identity string recorded for this rename - defaults to the calling namespace. Cooperating packages use their package/namespace name. Note: this flag is recognised - only as the FIRST argument (manual parse) - appearing - in any later position is an error." + only in the leading option positions (manual parse) - + appearing after the positional arguments begin is an + error." + -punkargs -type list -optional 1 -typesynopsis {list-of-definitionlists} -help -& + "punk::args doc blocks attached to this rename's stack + record - each element is one definitionlist exactly as + given to punk::args::define (e.g documenting a + subcommand the override adds, with a space-form id + such as {::package epoch}). The docs are live while + the record is on the stack and are removed with it by + ANY removal path (remove_rename, pop_rename, + remove_renamer, restore_original). Renames performed + BEFORE punk::args loads are supported: the definitions + are mirrored in the registered + ::commandstack::stackdocs namespace and load lazily + with punk::args (keep such deflists free of + dollar-brace tstr substitutions needing your own + namespace context - the lazy load evaluates them in + the stackdocs namespace). + Use one live record per doc id: same-id declarations + in two records shadow each other. Recognised only in + the leading option positions, like -renamer." @values -min 3 -max 3 command -type string -help -& "Command to rename (resolved with 'namespace which' in @@ -493,18 +555,31 @@ namespace eval commandstack { #todo: consider -forcebase 1 or similar to allow this rename to point to bottom of stack (original command) bypassing existing renames # - need to consider that upon removing, that any remaining rename that was higher on the stack should not also be diverted to the base - but rather to the next lower in the stack # - if {[lindex $args 0] eq "-renamer"} { - set renamer [lindex $args 1] - set arglist [lrange $args 2 end] - } else { - set renamer "" - set arglist $args + set renamer "" + set punkargs_defs [list] + set arglist $args + while {[llength $arglist] > 3 && [string match -* [lindex $arglist 0]]} { + switch -- [lindex $arglist 0] { + -renamer { + set renamer [lindex $arglist 1] + set arglist [lrange $arglist 2 end] + } + -punkargs { + set punkargs_defs [lindex $arglist 1] + set arglist [lrange $arglist 2 end] + } + default { + error "commandstack::rename_command unrecognised leading option '[lindex $arglist 0]'. usage: rename_command ?-renamer ? ?-punkargs ? command procargs procbody" + } + } } - if {"-renamer" in $arglist} { - error "commandstack::rename_command -renamer is recognised only as the leading argument. usage: rename_command ?-renamer ? command procargs procbody" + foreach opt {-renamer -punkargs} { + if {$opt in $arglist} { + error "commandstack::rename_command $opt is recognised only in the leading option positions. usage: rename_command ?-renamer ? ?-punkargs ? command procargs procbody" + } } if {[llength $arglist] != 3} { - error "commandstack::rename_command usage: rename_command ?-renamer ? command procargs procbody" + error "commandstack::rename_command usage: rename_command ?-renamer ? ?-punkargs ? command procargs procbody" } lassign $arglist command procargs procbody @@ -646,6 +721,12 @@ namespace eval commandstack { } return [dict create implementation "" did_rename 0] } + if {[llength $punkargs_defs]} { + #additive key - appended after the leading key-order contract keys + #(token idx 0-1, renamer idx 2-3); docs attach only for a rename + #that actually lands (aborted renames returned above) + dict set new_record punkargs $punkargs_defs + } catch {rename ::commandstack::temp::testproc ""} set nextinit [string map [list %command% $command %renamer% $renamer %next_getter% [dict get $new_record next_getter] %original_implementation% [dict get $new_record implementation]] { #IMPLEMENTOR_%renamer%! (mechanism: 'commandstack::rename_command -renamer %renamer% %command% ) @@ -661,10 +742,52 @@ namespace eval commandstack { uplevel 1 [list rename ::commandstack::temp::testproc $command] dict lappend all_stacks $command $new_record dict set token_implementations [dict get $nextinfo token] [dict get $nextinfo next_target] + if {[llength $punkargs_defs]} { + Stackdocs_attach $punkargs_defs + } return $new_record } + #G-176: doc blocks attached to stack records via 'rename_command -punkargs'. + #Attach appends each definitionlist to the commandstack::stackdocs PUNKARGS + #mirror (consumed lazily if punk::args loads later - the namespace is + #registered inert at module load) and defines immediately when punk::args + #is already present (a namespace already consumed into punk::args' + #loaded_packages never lazy-loads later appends; redefining an unchanged id + #is idempotent - a define error surfaces to the rename_command caller with + #the rename already installed, inspectable via show_stack). Detach removes + #ONE mirror occurrence per deflist and undefines via + #punk::args::undefine_deflist (deflist-keyed; silently skips never-defined + #deflists). One live record per doc id is the supported shape - two records + #declaring the same id shadow each other and removing either removes the + #doc. + proc Stackdocs_attach {deflists} { + foreach deflist $deflists { + lappend ::commandstack::stackdocs::PUNKARGS $deflist + if {[llength [info commands ::punk::args::define]]} { + punk::args::define {*}$deflist + } + } + return + } + proc Stackdocs_detach {deflists} { + variable debug + upvar 0 ::commandstack::stackdocs::PUNKARGS docmirror + foreach deflist $deflists { + set posn [lsearch -exact $docmirror $deflist] + if {$posn > -1} { + set docmirror [lreplace $docmirror $posn $posn] + } + if {[llength [info commands ::punk::args::undefine_deflist]]} { + if {[catch {punk::args::undefine_deflist $deflist} errM] && $debug} { + puts stderr "(commandstack::Stackdocs_detach) WARNING: undefine_deflist failed: $errM" + } + } + } + return + } + namespace eval argdoc { lappend PUNKARGS [list { @id -id ::commandstack::next @@ -800,6 +923,12 @@ namespace eval commandstack { set stack [lreplace $stack $doomed_posn $doomed_posn] dict set all_stacks $command $stack dict unset token_implementations [dict get $doomed_record token] + if {[dict exists $doomed_record punkargs]} { + #G-176: docs attached with 'rename_command -punkargs' die with + #the record. Every removal path (pop_rename/remove_renamer/ + #restore_original) funnels through here. + Stackdocs_detach [dict get $doomed_record punkargs] + } } return $stack diff --git a/src/modules/commandstack-buildversion.txt b/src/modules/commandstack-buildversion.txt index f4c218bf..a3d03a55 100644 --- a/src/modules/commandstack-buildversion.txt +++ b/src/modules/commandstack-buildversion.txt @@ -1,6 +1,7 @@ -0.7.1 +0.8.0 #First line must be a tm version number #all other lines are ignored. +#0.8.0 - G-176 increment 3: rename_command accepts a new leading option '-punkargs ' attaching punk::args doc blocks to the rename's stack record (e.g documenting a subcommand the override adds, space-form ids like {::package epoch} supported). The docs are LIVE while the record is on the stack and are removed with it by ANY removal path - the detach hook sits in remove_rename, which pop_rename/remove_renamer/restore_original all funnel through. Works whether or not punk::args is loaded at rename time: deflists are mirrored in a new registered ::commandstack::stackdocs namespace (inert ::punk::args::register::NAMESPACES registration at module load - punk::args need not be present) so a later-loading punk::args picks them up lazily; when punk::args is already present they are ALSO defined immediately (a namespace already consumed into loaded_packages never lazy-loads later appends; redefinition is idempotent). Detach removes one mirror occurrence per deflist and undefines via punk::args::undefine_deflist (deflist-keyed - commandstack never parses ids; silently skips never-defined deflists). Record carries the deflists under a trailing 'punkargs' key (append-only key-order contract preserved: token idx 0-1, renamer idx 2-3). Supported shape: one live record per doc id (same-id declarations in two records shadow; removing either removes the doc). Leading-option parse generalised (-renamer/-punkargs in either order, both leading-only). stackdocs state follows the reload contract (info-exists guarded). help + rename_command argdoc document the how-to; In-tree users line gains punk::libunknown (the increment-2 migration). Pins: commandstack.test -punkargs lifecycle (docs live with record + removed by all four removal paths + pre-punk::args pending path in a bare child that loads punk::args afterwards + key-order/reload guards). #0.7.1 - G-160 follow-on: reload contract - known_renamers and debug are now # info-exists guarded like the rest of the module state (all_stacks, # renamer_command_tokens, token_implementations): a module re-source no diff --git a/src/tests/modules/AGENTS.md b/src/tests/modules/AGENTS.md index db2137bb..47d244cf 100644 --- a/src/tests/modules/AGENTS.md +++ b/src/tests/modules/AGENTS.md @@ -40,7 +40,7 @@ Unit tests for editable source modules under `src/modules/`, `src/modules_tcl8/` - `opunk/console/` — ::opunk::Console backend subclass tests (`testsuites/console/backends.test`, G-001): virtual dispatch of subclass overrides through base-class calls and punk::console::console_spec_resolve (both unchanged), TestConsole determinism + probe-free at_eof, SshConsole capability/eof + the flagship size-via-ANSI-query-over-socket case (a scripted remote terminal answers CSI 6n), TkConsole widget size/eof (gated behind env PUNK_TEST_TK=1 - Tk in the shared testinterp has side effects; also verifiable standalone under a tk-capable kit e.g `punk91 src