From 383e4e50572c6fe4d75ee98d0b775a4696351225 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Fri, 10 Jul 2026 14:49:29 +1000 Subject: [PATCH] punk::args 0.4.1: ensemble autodef loads lazily-registered subcommand argdocs before id_exists checks First 'i ' in a fresh shell (e.g 'i ansistring') rendered the autogenerated ensemble help without subcommand-help markers/choicelabels: ensemble_subcommands_definition tested punk::args::id_exists against argdocs whose registered namespace (::punk::args::register::NAMESPACES) had not yet been lazily loaded - the punk::ns doc-lookup entry path only loads the ensemble command's parent namespace. The generator now runs update_definitions for the namespaces its id checks could resolve in. Self-healed on later calls (autodef regenerates per 'i' invocation), which is why the defect went unnoticed; no prior test pinned the load ordering. Tests: punk/args ensembledef.test (new), punk/ns cmdhelp.test cmdhelp_ensemble_lazy_registered_argdocs. Project 0.4.14. Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com --- CHANGELOG.md | 4 + punkproject.toml | 2 +- src/modules/punk/args-999999.0a1.0.tm | 13 +++ src/modules/punk/args-buildversion.txt | 3 +- .../args/testsuites/args/ensembledef.test | 81 +++++++++++++++++++ .../punk/ns/testsuites/ns/cmdhelp.test | 43 ++++++++++ 6 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 src/tests/modules/punk/args/testsuites/args/ensembledef.test diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d302029..4f9d29d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ 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.4.14] - 2026-07-10 + +- punk::args 0.4.1: first `i ` in a fresh shell now shows subcommand-help markers and synopsis choicelabels for documented subcommands whose argdocs are lazily registered (e.g `i ansistring`); previously the autogenerated ensemble help omitted them until another path loaded the ensemble's registered namespace (`punk::args::update_definitions`). + ## [0.4.13] - 2026-07-09 - punk::args 0.4.0 tidy-up (pre G-046): dead developer-test procs removed (`define2`, `parseXXX`, the unused `::punk::args::TEST` definition); the interactive `Get_caller` diagnostics and `lib::tstr_test_one` renamed with leading `__` and unexported; internal parser/renderer helpers moved from the exported `punk::args` namespace into a new internal `punk::args::private` namespace (underscore prefixes dropped); every remaining exported command now carries a PUNKARGS definition, so `i`/`punk::args::usage` render documentation for the whole punk::args API (including `punk::args::lib` utilities and the `helpers`/`argdocbase` ANSI shorthands). diff --git a/punkproject.toml b/punkproject.toml index 60fb15e3..f325b1d6 100644 --- a/punkproject.toml +++ b/punkproject.toml @@ -1,3 +1,3 @@ [project] name = "punkshell" -version = "0.4.13" +version = "0.4.14" diff --git a/src/modules/punk/args-999999.0a1.0.tm b/src/modules/punk/args-999999.0a1.0.tm index a66bd535..2259a37a 100644 --- a/src/modules/punk/args-999999.0a1.0.tm +++ b/src/modules/punk/args-999999.0a1.0.tm @@ -11779,6 +11779,19 @@ tcl::namespace::eval punk::args { } [dict get $cinfo origin] {*}{ } ] + #id_exists only sees definitions already loaded - registered argdocs + #(::punk::args::register::NAMESPACES) load lazily, and callers such as the + #punk::ns doc-lookup path only load the ensemble command's parent namespace. + #Load the namespaces the id_checks could resolve in, or a first call before + #they are loaded (e.g 'i ' in a fresh shell) builds the definition + #without subhelp/choicelabel entries for documented subcommands. + set id_check_namespaces [list] + foreach checkid $id_checks { + set idns [namespace qualifiers $checkid] + if {$idns eq ""} {set idns ::} + if {$idns ni $id_check_namespaces} {lappend id_check_namespaces $idns} + } + punk::args::update_definitions $id_check_namespaces set N [punk::ansi::a+ normal] set RST [punk::ansi::a] foreach checkid $id_checks { diff --git a/src/modules/punk/args-buildversion.txt b/src/modules/punk/args-buildversion.txt index 761ad816..84bd13eb 100644 --- a/src/modules/punk/args-buildversion.txt +++ b/src/modules/punk/args-buildversion.txt @@ -1,6 +1,7 @@ -0.4.0 +0.4.1 #First line must be a semantic version number #all other lines are ignored. +#0.4.1 - fixed ensemble_subcommands_definition building the definition against unloaded argdocs: subhelp/doctype-punkargs choiceinfo entries and synopsis choicelabels were omitted for documented subcommands whose registered namespace (::punk::args::register::NAMESPACES) had not yet been lazily loaded - e.g first 'i ansistring' in a fresh shell rendered the autogenerated ensemble help without subcommand-help markers until something else loaded ::punk::ansi::ansistring. The generator now runs update_definitions for the namespaces its id_exists checks could resolve in before testing them (tests: punk/args ensembledef.test, punk/ns cmdhelp.test cmdhelp_ensemble_lazy_registered_argdocs) #0.4.0 - tidy-up (pre G-046): removed dead developer-test procs define2 and parseXXX and the unused ::punk::args::TEST definition; renamed the interactive Get_caller diagnostics test_get_dict/test_get_by_id/test_callers (+ test1 id) and lib::tstr_test_one with leading double underscores and excluded them from exports (punk::args::lib now exports {[a-z]*} instead of *). new punk::args::private namespace (namespace path to punk::args/lib/system) for internal helpers - _get_dict_can_assign_value, _split_type_expression, _check_clausecolumn, _synopsis_form_arg_display, _argerror_load_colours and lib::_parse_tstr_parts moved there with underscore prefixes dropped. documentation: PUNKARGS definitions added for every remaining exported command (errorstyle, undefine, undefine_deflist, raw_def, resolved_def_values, get_spec, is_dynamic, rawdef_id, rawdef_is_dynamic, id_exists, idaliases, set_idalias, unset_idalias, get_idalias, id_query, real_id, status, choiceword_match, get_dict; lib: string_is_dict, flatzip, zero_based_posns, choiceinfo_marks, indent, undent, undentleader, longestCommonPrefix, lunique; helpers and argdocbase B/N/I/NI); resolve's empty @cmd -help filled in; punk::args::argdocbase added to the registered definition namespaces #0.3.2 - Tcl 9.1 compatibility (TIP 746 removed expr behaviour from lseq operands): zero_based_posns now wraps its count-1 operand in expr #0.3.1 - fixed helpers::example -title path calling bare [a] (only resolvable where a global 'a' alias exists, e.g. punk shells) - now punk::ansi::a; found by the new examples.test running under plain tclsh diff --git a/src/tests/modules/punk/args/testsuites/args/ensembledef.test b/src/tests/modules/punk/args/testsuites/args/ensembledef.test new file mode 100644 index 00000000..0a1d46c9 --- /dev/null +++ b/src/tests/modules/punk/args/testsuites/args/ensembledef.test @@ -0,0 +1,81 @@ +package require tcltest + +package require punk::args +package require punk::ns +package require punk::ansi + +#ensemble_subcommands_definition lazy-argdoc ordering - added 2026-07-10 (defect found +#via 'i ansistring' in a fresh shell: the autogenerated ensemble help rendered without +#subcommand-help markers/choicelabels until punk::args::update_definitions was run for +#::punk::ansi::ansistring by some other path). +#Root cause: punk::args::ensemble_subcommands_definition decides subhelp/doctype-punkargs +#choiceinfo entries and synopsis choicelabels via bare punk::args::id_exists checks, and +#id_exists only sees definitions already loaded. Registered argdocs +#(::punk::args::register::NAMESPACES) load lazily, and the punk::ns doc-lookup entry path +#only loads [namespace qualifiers $ensemblecommand] - the parent namespace - never the +#ensemble's own registered namespace where subcommand argdocs conventionally live. +#The generator must load the namespaces its id checks could resolve in before testing. + +namespace eval ::testspace { + namespace import ::tcltest::* + variable common { + set result "" + } + + #fixture: module-style ensemble whose subcommand argdocs are registered for lazy + #loading (as punk::ansi does for ::punk::ansi::ansistring) but never explicitly + #loaded by this file before the generator runs. + namespace eval ::testspace::ensdeflazy { + namespace export alpha beta + namespace eval argdoc { + variable PUNKARGS + lappend PUNKARGS [list { + @id -id ::testspace::ensdeflazy::alpha + @cmd -name testspace::ensdeflazy::alpha -summary\ + "Lazy ensemble alpha fixture."\ + -help\ + "alpha help." + @values -min 1 -max 1 + zzduration -type integer -help\ + "distinctive argument name for rendered-output assertions." + }] + } + proc alpha {zzduration} {return alpha-$zzduration} + proc beta {} {return beta} + namespace ensemble create + } + lappend ::punk::args::register::NAMESPACES ::testspace::ensdeflazy + + test ensembledef_lazy_registered_argdocs_first_call {first ensemble_subcommands_definition call loads registered-but-unloaded subcommand argdocs itself: documented subcommand gets subhelp/doctype-punkargs choiceinfo, undocumented does not}\ + -setup $common -body { + #precondition: argdoc registered but not yet loaded (id_exists is a pure + #cache lookup with no loading side effect) + lappend result [punk::args::id_exists ::testspace::ensdeflazy::alpha] + set vline [punk::args::ensemble_subcommands_definition -columns 1 ::testspace::ensdeflazy] + #the generator itself must have loaded the registered definitions + lappend result [punk::args::id_exists ::testspace::ensdeflazy::alpha] + #documented subcommand marked + lappend result [string match {*subhelp ::testspace::ensdeflazy::alpha*} $vline] + lappend result [string match {*doctype punkargs*} $vline] + #undocumented subcommand not marked + lappend result [string match {*subhelp ::testspace::ensdeflazy::beta*} $vline] + #choicelabel (synopsis) generated for the documented subcommand + lappend result [string match {*zzduration*} $vline] + }\ + -cleanup { + }\ + -result [list 0 1 1 1 0 1] + + test ensembledef_loaded_argdocs_stable {a repeat call after the definitions are loaded produces the same marked definition (loaded-path regression guard)}\ + -setup $common -body { + punk::args::update_definitions [list ::testspace::ensdeflazy] + set vline [punk::args::ensemble_subcommands_definition -columns 1 ::testspace::ensdeflazy] + lappend result [string match {*subhelp ::testspace::ensdeflazy::alpha*} $vline] + lappend result [string match {*doctype punkargs*} $vline] + lappend result [string match {*zzduration*} $vline] + }\ + -cleanup { + }\ + -result [list 1 1 1] +} +tcltest::cleanupTests ;#needed to produce test summary line. diff --git a/src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test b/src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test index d31b2705..0af2b394 100644 --- a/src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test +++ b/src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test @@ -483,5 +483,48 @@ namespace eval ::testspace { -cleanup { }\ -result [list {{::testspace::helpfix v1} [-flag] firstval lastval}] + + #--- ensemble autodef with lazily-registered subcommand argdocs ---------------------------- + #Integration surface of the defect pinned unit-level in + #src/tests/modules/punk/args/testsuites/args/ensembledef.test ('i ansistring' in a + #fresh shell): the cmdhelp/arginfo entry path only loads the ensemble command's + #PARENT namespace, so the ensemble's own registered namespace (where subcommand + #argdocs conventionally live) is unloaded when the (autodef) definition is built. + + namespace eval ::testspace::lazyintens { + namespace export delta gamma + namespace eval argdoc { + variable PUNKARGS + lappend PUNKARGS [list { + @id -id ::testspace::lazyintens::gamma + @cmd -name testspace::lazyintens::gamma -summary\ + "Lazy integration gamma fixture."\ + -help\ + "gamma help." + @values -min 1 -max 1 + zzintdelay -type integer -help\ + "distinctive argument name for autodef content assertion." + }] + } + proc gamma {zzintdelay} {return gamma-$zzintdelay} + proc delta {} {return delta} + namespace ensemble create + } + lappend ::punk::args::register::NAMESPACES ::testspace::lazyintens + + test cmdhelp_ensemble_lazy_registered_argdocs {first cmdhelp render of an ensemble whose subcommand argdocs are registered but not yet loaded produces an (autodef) carrying the documented subcommand's subhelp marker and synopsis choicelabel}\ + -setup $common -body { + #precondition: argdoc registered but not yet loaded + lappend result [punk::args::id_exists ::testspace::lazyintens::gamma] + set out [punk::ns::cmdhelp -return string ::testspace::lazyintens] + lappend result [expr {[string length $out] > 0}] + set rawdef [punk::args::raw_def (autodef)::testspace::lazyintens] + lappend result [string match {*subhelp ::testspace::lazyintens::gamma*} $rawdef] + lappend result [string match {*zzintdelay*} $rawdef] + lappend result [string match {*subhelp ::testspace::lazyintens::delta*} $rawdef] + }\ + -cleanup { + }\ + -result [list 0 1 1 1 0] } tcltest::cleanupTests ;#needed to produce test summary line.