Browse Source
Argument resolution no longer tstr-expands display-only content (-help on @cmd/@examples/argument records, @formdisplay bodies): ${...} there is masked with inert tokens (spec key DISPLAY_DEFERRED) and expanded at display time via private::expand_display_fields (hooks in arg_error/eg/resolved_def/@default copyfrom; separate display cache for non-dynamic defs; @dynamic re-expands per render preserving provider refresh; reentrancy guard substitutes raw sources when expansion of the same id re-enters). First parse of heavily documented commands drops accordingly (punk::ansi::mark_columns first call ~4.3s -> ~12ms; tclcore ::lseq resolve ~184ms -> ~2ms) and -help content that calls punk::args-parsing commands - including against its own definition id - resolves cleanly instead of stalling or looping. -choicelabels stays eager (punk::ns reads it from parse specs in the subcommand walk). Record splitter factored to private::split_definition_records. Also per G-046 acceptance: - @dynamic multiline -help substitutions align at the insertion column (rendering_atdynamic_multiline_help_insertion GAP flipped) - prefix/alias choice normalization keeps single-element-clause values plain-string ({\Deleted} list-wrap shape bug; choicegroups_imap_prefix_shape GAP flipped) - -return string renderer aligns cmd-help continuations under the Description: label (characterization updated) and its Example: line shows the example, not the doc url - punk::ns cmdhelp shows plain info-scheme usage when no argument words are supplied for a leader/value-requiring definition ('i string is', 'i punk::args::define') - advisory parse runs -errorstyle minimal so the discarded error no longer renders a second usage table (timing parity with pre-change: ~5.3s/4.1s on punk91 src) Tests: deferredhelp.test (new); suites green on tcl903 + tcl87; full source-tree run baseline-clean (exec-14.3 only). GOALS.md: G-046 -> achieved 2026-07-10 (activated this session at user direction); design + verification evidence in the detail file. Project 0.4.15. Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.commaster
12 changed files with 796 additions and 179 deletions
@ -1,3 +1,3 @@ |
|||||||
[project] |
[project] |
||||||
name = "punkshell" |
name = "punkshell" |
||||||
version = "0.4.14" |
version = "0.4.15" |
||||||
|
|||||||
@ -1,6 +1,7 @@ |
|||||||
0.1.3 |
0.1.4 |
||||||
#First line must be a semantic version number |
#First line must be a semantic version number |
||||||
#all other lines are ignored. |
#all other lines are ignored. |
||||||
|
#0.1.4 - G-046 item 5: cmdhelp's advisory goodargs parse failing with NO supplied argument words (e.g 'i string is' where the definition requires leaders) now shows plain info-scheme usage instead of the internal-looking "Bad number of leading values for punk::args::parse ..." error output (both the alias path and the main path; error display for supplied-but-invalid words unchanged). The no-supplied-words advisory parse runs with -errorstyle minimal so its failure doesn't render the full usage table inside the discarded error - large argdocs (e.g 'i punk::args::define') render the table once, not twice (verified parity with pre-G-046 timings: ~5.3s first/~4.1s repeat on punk91 src, table construction dominant). Test: cmdhelp.test cmdhelp_leader_required_no_args_plain_usage |
||||||
#0.1.3 - documentation-only: cmdhelp 'subcommand' argument help rewritten to match actual behaviour (was described as ensemble-subcommands-only; also covers tcl::oo methods and argument words, whose validity drives the info/error scheme and received-argument marking of the usage display) |
#0.1.3 - documentation-only: cmdhelp 'subcommand' argument help rewritten to match actual behaviour (was described as ensemble-subcommands-only; also covers tcl::oo methods and argument words, whose validity drives the info/error scheme and received-argument marking of the usage display) |
||||||
#0.1.2 - cmd_traverse subcommand walk resolves choice words via the shared punk::args::choiceword_match resolver (G-040 parity): -choiceprefixdenylist and -choiceprefixreservelist are now honoured in doc lookup (previously ignored - 'i <cmd> <word>' could accept words parsing rejects), -choicealiases normalize to their canonical before choiceinfo lookup, and -nocase is honoured in the walk |
#0.1.2 - cmd_traverse subcommand walk resolves choice words via the shared punk::args::choiceword_match resolver (G-040 parity): -choiceprefixdenylist and -choiceprefixreservelist are now honoured in doc lookup (previously ignored - 'i <cmd> <word>' could accept words parsing rejects), -choicealiases normalize to their canonical before choiceinfo lookup, and -nocase is honoured in the walk |
||||||
#0.1.1 - commented out five development trace puts in the doc-lookup machinery: "PROC auto def"/"ENSEMBLE auto def" (generate_autodef - emitted on STDOUT, polluting 'i'/'s' output in script/exec contexts), "cmd_traverse - skipping to documented subcommand" (space-form id path), "---> cmd_traverse ensembleparam" (ensemble -parameters traversal), and "cmd_traverse 10 ... - review" (fallthrough return). No functional change. |
#0.1.1 - commented out five development trace puts in the doc-lookup machinery: "PROC auto def"/"ENSEMBLE auto def" (generate_autodef - emitted on STDOUT, polluting 'i'/'s' output in script/exec contexts), "cmd_traverse - skipping to documented subcommand" (space-form id path), "---> cmd_traverse ensembleparam" (ensemble -parameters traversal), and "cmd_traverse 10 ... - review" (fallthrough return). No functional change. |
||||||
|
|||||||
@ -0,0 +1,164 @@ |
|||||||
|
package require tcltest |
||||||
|
|
||||||
|
package require punk::args |
||||||
|
package require punk::ansi |
||||||
|
|
||||||
|
#G-046 item 1: display-field deferral. Argument resolution must not expand ${...} |
||||||
|
#content in display-only fields (-help on @cmd/@examples/argument records, |
||||||
|
#@formdisplay bodies) - expansion happens at display time, separately cached. |
||||||
|
#This removes the parse-time cost of heavily documented commands (punk::ansi's |
||||||
|
#mark_columns argdoc: ~4s first parse) and the reentrancy class where -help content |
||||||
|
#calls punk::args-parsing commands (including commands that parse against the very |
||||||
|
#definition being resolved). |
||||||
|
|
||||||
|
namespace eval ::testspace { |
||||||
|
namespace import ::tcltest::* |
||||||
|
variable common { |
||||||
|
set result "" |
||||||
|
} |
||||||
|
|
||||||
|
variable DEFHELP_CALLS 0 |
||||||
|
proc defhelp_probe {} { |
||||||
|
variable DEFHELP_CALLS |
||||||
|
incr DEFHELP_CALLS |
||||||
|
return "PROBE_L1 probe text\nPROBE_L2 probe more" |
||||||
|
} |
||||||
|
|
||||||
|
test deferredhelp_parse_skips_help_expansion {parsing never runs -help ${[...]} substitutions; display runs them once (non-dynamic display cache); parsing after display still doesn't}\ |
||||||
|
-setup $common -body { |
||||||
|
variable DEFHELP_CALLS |
||||||
|
set DEFHELP_CALLS 0 |
||||||
|
punk::args::define { |
||||||
|
@id -id ::testspace::dhelp |
||||||
|
@cmd -name testspace::dhelp -summary\ |
||||||
|
"Deferred help fixture."\ |
||||||
|
-help\ |
||||||
|
"DFIRST line. |
||||||
|
${[::testspace::defhelp_probe]}" |
||||||
|
@values -min 1 -max 1 |
||||||
|
v1 -type string -help\ |
||||||
|
"v1 help." |
||||||
|
} |
||||||
|
#define does not resolve - force resolution via a parse |
||||||
|
punk::args::parse {avalue} withid ::testspace::dhelp |
||||||
|
lappend result $DEFHELP_CALLS ;#0 - parse-time resolution skipped the -help substitution |
||||||
|
#display renders the substituted content |
||||||
|
set r [punk::ansi::ansistrip [punk::args::usage ::testspace::dhelp]] |
||||||
|
lappend result $DEFHELP_CALLS ;#1 - expanded for display |
||||||
|
lappend result [string match "*PROBE_L1*" $r] |
||||||
|
#no internal deferral tokens leak into rendered output |
||||||
|
lappend result [string match "*\x01*" $r] |
||||||
|
#multiline substitution aligns at the insertion column (as eager tstr does) |
||||||
|
set c0 -1 |
||||||
|
set c1 -2 |
||||||
|
foreach ln [split $r \n] { |
||||||
|
if {[set ix [string first PROBE_L1 $ln]] >= 0} {set c0 $ix} |
||||||
|
if {[set ix [string first PROBE_L2 $ln]] >= 0} {set c1 $ix} |
||||||
|
} |
||||||
|
lappend result [expr {$c1 - $c0}] |
||||||
|
#display expansion is cached for non-dynamic definitions |
||||||
|
punk::args::usage ::testspace::dhelp |
||||||
|
lappend result $DEFHELP_CALLS ;#still 1 |
||||||
|
#parsing again still doesn't touch it |
||||||
|
punk::args::parse {bvalue} withid ::testspace::dhelp |
||||||
|
lappend result $DEFHELP_CALLS ;#still 1 |
||||||
|
}\ |
||||||
|
-cleanup { |
||||||
|
punk::args::undefine ::testspace::dhelp 1 |
||||||
|
}\ |
||||||
|
-result [list 0 1 1 0 0 1 1] |
||||||
|
|
||||||
|
variable DHSELF_CALLS 0 |
||||||
|
proc dhself_helper {args} { |
||||||
|
#parses against the definition whose -help invokes this proc |
||||||
|
#(the punk::ansi::mark_columns reentrancy class) |
||||||
|
variable DHSELF_CALLS |
||||||
|
incr DHSELF_CALLS |
||||||
|
set argd [punk::args::parse [list "helper-example"] withid ::testspace::dhself] |
||||||
|
return "HELPER_RESULT [dict get [dict get $argd values] v1]" |
||||||
|
} |
||||||
|
|
||||||
|
test deferredhelp_selfparsing_help_resolves {a -help substitution that parses against its OWN definition id resolves cleanly: never runs at parse time, and at display time the parse inside the helper uses the (already cached) parse spec}\ |
||||||
|
-setup $common -body { |
||||||
|
variable DHSELF_CALLS |
||||||
|
set DHSELF_CALLS 0 |
||||||
|
punk::args::define { |
||||||
|
@id -id ::testspace::dhself |
||||||
|
@cmd -name testspace::dhself -summary\ |
||||||
|
"Self-parsing help fixture."\ |
||||||
|
-help\ |
||||||
|
"example output: |
||||||
|
${[::testspace::dhself_helper]}" |
||||||
|
@values -min 1 -max 1 |
||||||
|
v1 -type string -help\ |
||||||
|
"v1 help." |
||||||
|
} |
||||||
|
punk::args::parse {plainvalue} withid ::testspace::dhself |
||||||
|
lappend result $DHSELF_CALLS ;#0 |
||||||
|
set r [punk::ansi::ansistrip [punk::args::usage ::testspace::dhself]] |
||||||
|
lappend result $DHSELF_CALLS ;#1 |
||||||
|
lappend result [string match "*HELPER_RESULT helper-example*" $r] |
||||||
|
}\ |
||||||
|
-cleanup { |
||||||
|
punk::args::undefine ::testspace::dhself 1 |
||||||
|
}\ |
||||||
|
-result [list 0 1 1] |
||||||
|
|
||||||
|
test deferredhelp_self_display_reentrancy_guard {a -help substitution that renders usage for its OWN id resolves cleanly (raw ${...} sources substituted on the nested render) rather than looping}\ |
||||||
|
-setup $common -body { |
||||||
|
punk::args::define { |
||||||
|
@id -id ::testspace::dhloop |
||||||
|
@cmd -name testspace::dhloop -summary\ |
||||||
|
"Self-rendering help fixture."\ |
||||||
|
-help\ |
||||||
|
"own usage follows: |
||||||
|
${[punk::args::usage ::testspace::dhloop]}" |
||||||
|
@values -min 0 -max 0 |
||||||
|
} |
||||||
|
set r [punk::ansi::ansistrip [punk::args::usage ::testspace::dhloop]] |
||||||
|
#completed without looping; nested render shows the raw source form |
||||||
|
lappend result [expr {[string length $r] > 0}] |
||||||
|
lappend result [string match "*own usage follows:*" $r] |
||||||
|
lappend result [string match "*\x01*" $r] |
||||||
|
}\ |
||||||
|
-cleanup { |
||||||
|
punk::args::undefine ::testspace::dhloop 1 |
||||||
|
}\ |
||||||
|
-result [list 1 1 0] |
||||||
|
|
||||||
|
variable DHDYN_CALLS 0 |
||||||
|
proc dhdyn_provider {} { |
||||||
|
variable DHDYN_CALLS |
||||||
|
incr DHDYN_CALLS |
||||||
|
return "DYNPROBE call$DHDYN_CALLS" |
||||||
|
} |
||||||
|
variable DYN_DHHELP {${[::testspace::dhdyn_provider]}} |
||||||
|
|
||||||
|
test deferredhelp_dynamic_display_refreshes {@dynamic deferred -help content is skipped at parse time and re-expanded on each display (provider refresh semantics preserved)}\ |
||||||
|
-setup $common -body { |
||||||
|
variable DHDYN_CALLS |
||||||
|
set DHDYN_CALLS 0 |
||||||
|
punk::args::define { |
||||||
|
@dynamic |
||||||
|
@id -id ::testspace::dhdyn |
||||||
|
@cmd -name testspace::dhdyn -summary\ |
||||||
|
"Dynamic deferred help fixture."\ |
||||||
|
-help\ |
||||||
|
"${$DYN_DHHELP}" |
||||||
|
@values -min 0 -max 0 |
||||||
|
} |
||||||
|
punk::args::parse {} withid ::testspace::dhdyn |
||||||
|
lappend result $DHDYN_CALLS ;#0 - provider untouched by parsing |
||||||
|
set r1 [punk::ansi::ansistrip [punk::args::usage ::testspace::dhdyn]] |
||||||
|
lappend result [string match "*DYNPROBE call1*" $r1] |
||||||
|
set r2 [punk::ansi::ansistrip [punk::args::usage ::testspace::dhdyn]] |
||||||
|
lappend result [string match "*DYNPROBE call2*" $r2] ;#re-expanded per display |
||||||
|
punk::args::parse {} withid ::testspace::dhdyn |
||||||
|
lappend result $DHDYN_CALLS ;#2 - parsing still doesn't run it |
||||||
|
}\ |
||||||
|
-cleanup { |
||||||
|
punk::args::undefine ::testspace::dhdyn 1 |
||||||
|
}\ |
||||||
|
-result [list 0 1 1 2] |
||||||
|
} |
||||||
|
tcltest::cleanupTests ;#needed to produce test summary line. |
||||||
Loading…
Reference in new issue