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] |
||||
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 |
||||
#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.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,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