Browse Source

punk::args 0.5.0 + punk::ns 0.2.0: G-049 parse-status data model + machine-parsable cmdhelp returns

- new punk::args::parse_status: documented status structure from a parse attempt (overall ok / status valid|invalid|incomplete / scheme / message / errorcode minus -argspecs / failureclass / badarg / form / receivednames + per-argument argstatus with class, status ok|bad|unparsed, received count/positions, value-in-effect incl -default fill); validation failures reported in the structure, not raised
- arg_error -parsestatus: both renderers derive goodarg/badarg row marking and choice value-in-effect highlighting from the structure (transient goodargs/badarg locals replaced; built internally from -badarg/-parsedargs when not supplied)
- per-render scheme colour resolution: documented -scheme choice value 'nocolour' now takes effect and scheme renders no longer mutate the shared arg_error_CLR array (strike-only goodarg leak fixed)
- parse: new -caller option overriding the %caller% frame-walk substitution (included in parse cache key); get_dict missingrequiredvalue/missingrequiredleader now carry -badarg (type/allocation failures get badarg marking, not just choice violations)
- punk::ns::cmdhelp: -return dict {origin docid cmdtype args_remaining parsestatus}; advisory parse via parse_status on both alias and main paths; explicit -scheme honoured on the parse-failure render; failure messages name the queried command instead of leaking the internal parse source line at top call depth
- tests: parsestatus.test new (12); usagemarking.test G-049 GAP pins flipped + -parsestatus parity/badarg render tests (17); cmdhelp.test G-049 GAP pins flipped + cmdhelp_return_dict_* (25); punk/args + punk/ns trees green on Tcl 9.0.3 and 8.7; full source-tree suite baseline unchanged (exec-14.3 only)
- punkshell 0.5.0: CHANGELOG entry; src/tests/modules/AGENTS.md test-index blurbs updated

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 5 days ago
parent
commit
ce2ba184cb
  1. 5
      CHANGELOG.md
  2. 2
      punkproject.toml
  3. 462
      src/modules/punk/args-999999.0a1.0.tm
  4. 3
      src/modules/punk/args-buildversion.txt
  5. 137
      src/modules/punk/ns-999999.0a1.0.tm
  6. 3
      src/modules/punk/ns-buildversion.txt
  7. 4
      src/tests/modules/AGENTS.md
  8. 188
      src/tests/modules/punk/args/testsuites/args/parsestatus.test
  9. 95
      src/tests/modules/punk/args/testsuites/args/usagemarking.test
  10. 100
      src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test

5
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.5.0] - 2026-07-10
- punk::args 0.5.0 (G-049): new `punk::args::parse_status` - runs a parse attempt and returns a documented parse-status structure instead of raising on validation failure: overall `ok`/`status` (valid|invalid|incomplete)/`scheme`/`message`/`errorcode`/`failureclass`/`badarg`/`form`/`receivednames` plus per-argument `argstatus` entries (class, status ok|bad|unparsed, received count/positions, value-in-effect including `-default` fill). `arg_error` accepts the structure via `-parsestatus` and both its renderers derive goodarg/badarg row marking and choice value-in-effect highlighting from it. Scheme colours now resolve per-render: the documented `-scheme` value `nocolour` takes effect (previously it fell through to whatever colours the last scheme render left behind) and scheme renders no longer mutate the shared colour array (the strike-only goodarg style no longer leaks into later renders). `missingrequiredvalue`/`missingrequiredleader` failures now carry `-badarg`, and `punk::args::parse` gained `-caller` to control the `%caller%` attribution in failure messages.
- punk::ns 0.2.0 (G-049): `i <cmd> <args...>` (cmdhelp) gains `-return dict` - a machine-parsable dict of resolution info (origin/docid/cmdtype/args_remaining) plus the parse-status structure of the supplied argument words. Display fixes: a value failing its `-type` check now badarg-marks the unfillable argument's row (previously only choice violations marked anything), an explicit `-scheme` is honoured on the parse-failure path, and parse-failure messages name the queried command instead of leaking cmdhelp's internal parse source line at top call depth.
## [0.4.15] - 2026-07-10
- punk::args 0.4.2 (G-046): argument resolution no longer expands display-only `${...}` content (`-help` on `@cmd`/`@examples`/argument records, `@formdisplay` bodies) - expansion is deferred to help-display time with its own cache. First use of heavily documented commands is dramatically faster (`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. Also fixed: `@dynamic` multiline `-help` substitutions now align at their insertion column; prefix/alias-normalized choice values keep the same plain-string shape as exact input (`\Deleted`-style values); the `-return string` renderer aligns cmd-help continuation lines under the `Description:` label and its `Example:` line shows the example reference instead of the doc url.

2
punkproject.toml

@ -1,3 +1,3 @@
[project]
name = "punkshell"
version = "0.4.15"
version = "0.5.0"

462
src/modules/punk/args-999999.0a1.0.tm

@ -4624,8 +4624,17 @@ tcl::namespace::eval punk::args {
-badarg -type string -help\
"name of an argument to highlight"
-parsedargs -type dict -help\
"Result of successful punk::pargs::parse
(currently only looks at 'received')"
"Result of successful punk::args::parse.
Together with -badarg this is converted internally to the
parse-status structure the renderers consume (received argument
rows get the goodarg style, choice words matching an argument's
value-in-effect are highlighted)."
-parsestatus -type dict -help\
"Parse-status structure as returned by punk::args::parse_status.
The renderers derive goodarg/badarg row marking and choice
value-in-effect highlighting from it. Takes precedence over
-badarg/-parsedargs. Should be built for the same form the
display renders (see -form)."
-aserror -type boolean -help\
"If true, the usage table is raised as an error message,
otherwise it is returned as a value."
@ -4770,17 +4779,22 @@ tcl::namespace::eval punk::args {
}
#set arg_error_CLR(testsinglecolour) [a+ brightred]
upvar ::punk::args::arg_error_CLR CLR
variable arg_error_CLR
set forcereload 0 ;#no need for forcereload to be true for initial run - empty array will trigger initial load
if {[info exists CLR(testsinglecolour)]} {
if {[info exists arg_error_CLR(testsinglecolour)]} {
set terminal_colour_is_on [expr {[string length [a+ yellow]]}]
set error_colour_is_on [expr {[string length $CLR(testsinglecolour)]}]
set error_colour_is_on [expr {[string length $arg_error_CLR(testsinglecolour)]}]
if {$terminal_colour_is_on ^ $error_colour_is_on} {
#results differ
set forcereload 1
}
}
private::argerror_load_colours $forcereload
#per-render colour resolution: CLR is proc-local, seeded from the shared base array;
#a scheme's overrides are merged into this local copy only. (Merging into the shared
#arg_error_CLR leaked scheme overrides into every subsequent render until a colour
#on/off state flip forced an array reload.)
array set CLR [array get arg_error_CLR]
if {[llength $args] %2 != 0} {
set arg_error_isrunning 0
@ -4791,7 +4805,7 @@ tcl::namespace::eval punk::args {
set badarg ""
set parsedargs [dict create] ;#dict with keys: leaders,opts,values,received,solos,multis (as from punk::args::parse)
set goodargs [list]
set parsestatus "" ;#parse-status structure (see ::punk::args::parse_status) - takes precedence over -badarg/-parsedargs
#-----------------------
#todo!! make changeable from config file
#JJJ 2025-07-16
@ -4802,26 +4816,17 @@ tcl::namespace::eval punk::args {
set scheme error
set form 0
dict for {k v} $args {
set fullk [tcl::prefix::match -error "" {-badarg -parsedargs -aserror -return -scheme -form} $k]
set fullk [tcl::prefix::match -error "" {-badarg -parsedargs -parsestatus -aserror -return -scheme -form} $k]
switch -- $fullk {
-badarg {
set badarg $v
}
-parsedargs {
#todo?
dict for {set setinfo} $v {
switch -- $set {
received {
foreach {r rpos} $setinfo {
if {$r ni $goodargs} {
lappend goodargs $r
}
}
}
}
}
set parsedargs $v
}
-parsestatus {
set parsestatus $v
}
-aserror {
if {![string is boolean -strict $v]} {
set arg_error_isrunning 0
@ -4844,13 +4849,15 @@ tcl::namespace::eval punk::args {
}
default {
set arg_error_isrunning 0
error "arg_error invalid option $k. Known_options: -badarg -parsedargs -aserror -scheme -return -form"
error "arg_error invalid option $k. Known_options: -badarg -parsedargs -parsestatus -aserror -scheme -return -form"
}
}
}
#todo - scheme - use config and iterm toml definitions etc
switch -- $scheme {
"" - -nocolor - -nocolour {
"" - nocolor - nocolour - -nocolor - -nocolour {
#the documented choice value is 'nocolour' - dash spellings and 'nocolor'
#accepted for backward compatibility
set scheme nocolour
}
info - error {}
@ -4883,7 +4890,7 @@ tcl::namespace::eval punk::args {
#hack some basics for now.
#for coloured schemes - use bold as well as brightcolour in case colour off.
#CLR has been defaulted to arg_error_CLR for its defaults
#CLR is a proc-local copy of arg_error_CLR (see above)
#The nocolour,info,error arrays have overrides for some keys.
switch -- $scheme {
nocolour {
@ -4902,6 +4909,18 @@ tcl::namespace::eval punk::args {
}
}
#parse-status structure the renderers derive goodarg/badarg row marking and choice
#value-in-effect highlighting from (see ::punk::args::parse_status for the shape).
#An explicit -parsestatus takes precedence; otherwise build one for the displayed
#form from the -parsedargs/-badarg primitives.
if {$parsestatus ne ""} {
set PSTAT $parsestatus
} else {
set PSTAT [private::parse_status_build $spec_dict [lindex $selected_forms 0] -badarg $badarg -parsedargs $parsedargs]
}
set argstatusd [dict get $PSTAT argstatus]
set receivednames [dict get $PSTAT receivednames]
#set RST [a]
set RST "\x1b\[0m"
@ -5192,17 +5211,9 @@ tcl::namespace::eval punk::args {
#goodargs
}
}
set full_goodargs [list]
#goodargs may have simplified entries for received opts of form -alias1|-alias2|-realname
#map -realname to full argname
foreach g $goodargs {
if {[string match -* $g] && [dict exists $lookup_optset $g]} {
lappend full_goodargs [dict get $lookup_optset $g]
} else {
lappend full_goodargs $g
}
}
set goodargs $full_goodargs
#received-name normalization to optionset names (formerly done here on the
#transient goodargs local) lives in private::parse_status_build - the
#renderers below consume $argstatusd/$receivednames from the structure
if {![catch {package require punk::trie}]} {
#todo - reservelist for future options - or just to affect the prefix calculation
# (similar to -choiceprefixreservelist)
@ -5304,18 +5315,25 @@ tcl::namespace::eval punk::args {
set leading_val_names_hints {}
set trailing_val_names_display $trailing_val_names
set trailing_val_names_hints {}
#puts "--> parsedargs: $parsedargs"
set parsed_leaders [Dict_getdef $parsedargs leaders {}]
set parsed_opts [Dict_getdef $parsedargs opts {}]
set parsed_values [Dict_getdef $parsedargs values {}]
#display options first then values
foreach argumentclassinfo [list [list leaders $leading_val_names_display $leading_val_names_hints $leading_val_names $parsed_leaders] [list opts $opt_names_display $opt_names_hints $opt_names $parsed_opts] [list values $trailing_val_names_display $trailing_val_names_hints $trailing_val_names $parsed_values]] {
lassign $argumentclassinfo argumentclass argnames_display argnames_hints argnames parsedvalues
foreach argumentclassinfo [list [list leaders $leading_val_names_display $leading_val_names_hints $leading_val_names] [list opts $opt_names_display $opt_names_hints $opt_names] [list values $trailing_val_names_display $trailing_val_names_hints $trailing_val_names]] {
lassign $argumentclassinfo argumentclass argnames_display argnames_hints argnames
set lastgroup ""
set lastgroup_parsekey ""
foreach argshow $argnames_display hint $argnames_hints arg $argnames {
set arginfo [dict get $form_dict ARG_INFO $arg]
#this argument's entry in the parse-status structure: row marking from
#its status, choice value-in-effect highlighting from hasvalue/value
if {[dict exists $argstatusd $arg]} {
set arg_status [dict get $argstatusd $arg status]
set arg_hasvalue [dict get $argstatusd $arg hasvalue]
set arg_value [dict get $argstatusd $arg value]
} else {
set arg_status unparsed
set arg_hasvalue 0
set arg_value ""
}
if {$argumentclass eq "opts"} {
set thisgroup [dict get $arginfo -group]
@ -5354,9 +5372,9 @@ tcl::namespace::eval punk::args {
}
if {$use_table} {
$t add_row [list " $thisgroup" $groupinfo "" "" $help]
if {$arg eq $badarg} {
if {$arg_status eq "bad"} {
$t configure_row [expr {[$t row_count]-1}] -ansibase $A_BADARG
} elseif {$arg in $goodargs || $thisgroup_parsekey in $goodargs} {
} elseif {$arg_status eq "ok" || $thisgroup_parsekey in $receivednames} {
$t configure_row [expr {[$t row_count]-1}] -ansibase $A_GOODARG
}
} else {
@ -5364,9 +5382,9 @@ tcl::namespace::eval punk::args {
#set arghelp "[a+ bold] $thisgroup$RST $groupinfo"
set arghelp [textblock::join -- "[a+ bold] $thisgroup$RST" " " $groupinfo]
append arghelp \n
if {$arg eq $badarg} {
if {$arg_status eq "bad"} {
set arghelp [punk::ansi::ansiwrap -rawansi $A_BADARG $arghelp]
} elseif {$arg in $goodargs} {
} elseif {$arg_status eq "ok"} {
set arghelp [punk::ansi::ansiwrap -rawansi $A_GOODARG $arghelp]
}
foreach ln [split $help \n] {
@ -5527,9 +5545,9 @@ tcl::namespace::eval punk::args {
}
append cdisplay \n $ctext
}
if {[dict exists $parsedvalues $arg] && [dict get $parsedvalues $arg] eq $c} {
if {$arg_hasvalue && $arg_value eq $c} {
dict lappend formattedchoices $groupname [punk::ansi::ansiwrap reverse $cdisplay]
} elseif {[dict exists $parsedvalues $arg] && $is_multiple && $c in [dict get $parsedvalues $arg]} {
} elseif {$arg_hasvalue && $is_multiple && $c in $arg_value} {
dict lappend formattedchoices $groupname [punk::ansi::ansiwrap reverse $cdisplay]
} else {
dict lappend formattedchoices $groupname $cdisplay
@ -5547,9 +5565,9 @@ tcl::namespace::eval punk::args {
} else {
set cdisplay $c
}
if {[dict exists $parsedvalues $arg] && [dict get $parsedvalues $arg] eq $c} {
if {$arg_hasvalue && $arg_value eq $c} {
dict lappend formattedchoices $groupname [punk::ansi::ansiwrap reverse $cdisplay]
} elseif {[dict exists $parsedvalues $arg] && $is_multiple && $c in [dict get $parsedvalues $arg]} {
} elseif {$arg_hasvalue && $is_multiple && $c in $arg_value} {
dict lappend formattedchoices $groupname [punk::ansi::ansiwrap reverse $cdisplay]
} else {
dict lappend formattedchoices $groupname $cdisplay
@ -5613,9 +5631,9 @@ tcl::namespace::eval punk::args {
append cdisplay \n $ctext
}
#puts "-- parsed:$parsedvalues arg:$arg c:$c"
if {[dict exists $parsedvalues $arg] && [dict get $parsedvalues $arg] eq $c} {
if {$arg_hasvalue && $arg_value eq $c} {
dict lappend formattedchoices $groupname [punk::ansi::ansiwrap reverse $cdisplay]
} elseif {[dict exists $parsedvalues $arg] && $is_multiple && $c in [dict get $parsedvalues $arg]} {
} elseif {$arg_hasvalue && $is_multiple && $c in $arg_value} {
dict lappend formattedchoices $groupname [punk::ansi::ansiwrap reverse $cdisplay]
} else {
dict lappend formattedchoices $groupname $cdisplay
@ -5640,7 +5658,7 @@ tcl::namespace::eval punk::args {
append cdisplay \n [dict get $choicelabeldict $c]
}
if {[dict exists $parsedvalues $arg] && [dict get $parsedvalues $arg] eq $c} {
if {$arg_hasvalue && $arg_value eq $c} {
dict lappend formattedchoices $groupname [punk::ansi::ansiwrap reverse $cdisplay]
} else {
dict lappend formattedchoices $groupname $cdisplay
@ -5657,9 +5675,9 @@ tcl::namespace::eval punk::args {
} else {
set cdisplay $c
}
if {[dict exists $parsedvalues $arg] && [dict get $parsedvalues $arg] eq $c} {
if {$arg_hasvalue && $arg_value eq $c} {
dict lappend formattedchoices $groupname [punk::ansi::ansiwrap reverse $cdisplay]
} elseif {[dict exists $parsedvalues $arg] && $is_multiple && $c in [dict get $parsedvalues $arg]} {
} elseif {$arg_hasvalue && $is_multiple && $c in $arg_value} {
dict lappend formattedchoices $groupname [punk::ansi::ansiwrap reverse $cdisplay]
} else {
dict lappend formattedchoices $groupname $cdisplay
@ -5809,9 +5827,9 @@ tcl::namespace::eval punk::args {
set col1 $argshow
}
$t add_row [list $col1 $typeshow $default $multiple $help]
if {$arg eq $badarg} {
if {$arg_status eq "bad"} {
$t configure_row [expr {[$t row_count]-1}] -ansibase $A_BADARG
} elseif {$arg in $goodargs} {
} elseif {$arg_status eq "ok"} {
$t configure_row [expr {[$t row_count]-1}] -ansibase $A_GOODARG
}
} else {
@ -5823,9 +5841,9 @@ tcl::namespace::eval punk::args {
set arghelp "[a+ bold]$argshow$RST $linetail"
}
append arghelp \n
if {$arg eq $badarg} {
if {$arg_status eq "bad"} {
set arghelp [punk::ansi::ansiwrap -rawansi $A_BADARG $arghelp]
} elseif {$arg in $goodargs} {
} elseif {$arg_status eq "ok"} {
set arghelp [punk::ansi::ansiwrap -rawansi $A_GOODARG $arghelp]
}
foreach ln [split $help \n] {
@ -6116,6 +6134,13 @@ tcl::namespace::eval punk::args {
#default to enhanced errorstyle despite slow 'catch' (unhappy path) performance
#todo - configurable per interp/namespace
-errorstyle -type string -default enhanced -choices {enhanced standard basic minimal}
-caller -type string -default "" -help\
"Caller attribution for validation error messages.
When non-empty, this string replaces the %caller% placeholder in
validation failure messages instead of the automatic call-frame
walk - use it when parsing on behalf of another command (e.g a
usage/help display naming the queried command rather than the
internal parse call site)."
-cache -type boolean -default 0 -help\
{Use sparingly.
This caches the entire parse result or formatted validation error for the
@ -6200,6 +6225,7 @@ tcl::namespace::eval punk::args {
-form {*}
-errorstyle standard
-cache 0
-caller {}
}]
#todo - load override_errorstyle from configuration
@ -6208,7 +6234,7 @@ tcl::namespace::eval punk::args {
set opts [dict merge $defaultopts $opts]
dict for {k v} $opts {
switch -- $k {
-form - -errorstyle - -cache {
-form - -errorstyle - -cache - -caller {
}
default {
#punk::args::usage $args withid ::punk::args::parse ??
@ -6246,7 +6272,7 @@ tcl::namespace::eval punk::args {
set result [punk::args::get_dict $deflist $parseargs -form [dict get $opts -form]]
} else {
variable parse_cache
set key [list $parseargs $deflist [dict get $opts -form]]
set key [list $parseargs $deflist [dict get $opts -form] [dict get $opts -caller]]
if {[dict exists $parse_cache $key]} {
set cached [dict get $parse_cache $key]
if {[dict get $cached type] eq "result"} {
@ -6272,7 +6298,13 @@ tcl::namespace::eval punk::args {
set ecode [dict get $erroropts -errorcode]
#punk ecode is of form PUNKARGS VALIDATION {description..} -key val ...
set msg [string map [list %caller% [Get_caller]] $msg]
if {[dict get $opts -caller] ne ""} {
#explicit caller attribution supplied (e.g punk::ns::cmdhelp usage display
#naming the queried command) - frame walking would name internal call sites.
set msg [string map [list %caller% [dict get $opts -caller]] $msg]
} else {
set msg [string map [list %caller% [Get_caller]] $msg]
}
switch -- $matched_errorstyle {
minimal {
#return -options [list -code error -errorcode $ecode] $msg
@ -6337,7 +6369,7 @@ tcl::namespace::eval punk::args {
}
}
set key [list $parseargs $deflist [dict get $opts -form]]
set key [list $parseargs $deflist [dict get $opts -form] [dict get $opts -caller]]
dict set parse_cache $key [dict create type "error" value $elist]
return {*}$elist
} trap {PUNKARGS} {msg erropts} {
@ -6352,6 +6384,312 @@ tcl::namespace::eval punk::args {
return $result
}
#classify a PUNKARGS VALIDATION failure class-word into the parse-status overall status
#payload = elements of the errorcode description after the class word
proc private::parse_status_classify {failureclass payload} {
switch -- $failureclass {
missingrequiredleader - missingrequiredvalue - leadermissing - optionmissing - valuemissing - missingoptionvalue {
return incomplete
}
leadingvaluecount - trailingvaluecount {
#payload: <num> min <min> max <max>
set num [lindex $payload 0]
set min [Dict_getdef [lrange $payload 1 end] min ""]
if {[string is integer -strict $num] && [string is integer -strict $min] && $num < $min} {
return incomplete
}
return invalid
}
default {
return invalid
}
}
}
#build the parse-status structure documented in the ::punk::args::parse_status definition.
#fid selects the form whose argument set the per-argument statuses cover (the form usage
#displays render). Options are unvalidated internal inputs - see parse_status/arg_error.
proc private::parse_status_build {spec_dict fid args} {
set defaults [dict create {*}{
-ok ""
-status ""
-scheme ""
-message ""
-errorcode ""
-failureclass ""
-badarg ""
-parsedargs {}
}]
set opts [dict merge $defaults $args]
set parsedargs [dict get $opts -parsedargs]
set badarg [dict get $opts -badarg]
set form_dict [dict get $spec_dict FORMS $fid]
#map each member of an -alias1|-alias2|-realname optionset to the optionset (the ARG_INFO key)
set lookup_optset [dict create]
foreach optionset [dict get $form_dict OPT_NAMES] {
foreach o [split $optionset |] {
dict set lookup_optset $o $optionset
}
}
#received argument names normalized to definition argument names.
#received is dict-like but may repeat keys for -multiple arguments; values are clause ordinals.
set receivednames [list]
set positions [dict create] ;#argname -> list of clause ordinals
foreach {r rpos} [Dict_getdef $parsedargs received {}] {
if {[string match -* $r] && [dict exists $lookup_optset $r]} {
set r [dict get $lookup_optset $r]
}
if {$r ni $receivednames} {
lappend receivednames $r
}
dict lappend positions $r $rpos
}
if {$badarg ne "" && [string match -* $badarg] && [dict exists $lookup_optset $badarg]} {
set badarg [dict get $lookup_optset $badarg]
}
set parsed_leaders [Dict_getdef $parsedargs leaders {}]
set parsed_opts [Dict_getdef $parsedargs opts {}]
set parsed_values [Dict_getdef $parsedargs values {}]
set argstatus [dict create]
foreach {names class parsedvalues} [list {*}{
} [dict get $form_dict LEADER_NAMES] leader {*}{
} $parsed_leaders {*}{
} [dict get $form_dict OPT_NAMES] option {*}{
} $parsed_opts {*}{
} [dict get $form_dict VAL_NAMES] value {*}{
} $parsed_values {*}{
}] {
foreach arg $names {
set argpositions [Dict_getdef $positions $arg {}]
set received [llength $argpositions]
if {$arg eq $badarg} {
set status bad
set argfailclass [dict get $opts -failureclass]
} elseif {$received > 0} {
set status ok
set argfailclass ""
} else {
set status unparsed
set argfailclass ""
}
#value-in-effect (includes values the parse filled from -default).
#Direct lookup by definition argument name, matching the display renderers:
#aliased optionsets store under their canonical name and are not resolved here.
if {[dict exists $parsedvalues $arg]} {
set hasvalue 1
set value [dict get $parsedvalues $arg]
} else {
set hasvalue 0
set value ""
}
dict set argstatus $arg [dict create class $class status $status received $received positions $argpositions hasvalue $hasvalue value $value failureclass $argfailclass]
}
}
return [dict create {*}{
} ok [dict get $opts -ok] {*}{
} status [dict get $opts -status] {*}{
} scheme [dict get $opts -scheme] {*}{
} message [dict get $opts -message] {*}{
} errorcode [dict get $opts -errorcode] {*}{
} failureclass [dict get $opts -failureclass] {*}{
} badarg $badarg {*}{
} id [Dict_getdef $spec_dict id ""] {*}{
} form $fid {*}{
} receivednames $receivednames {*}{
} argstatus $argstatus {*}{
}]
}
#resolve the form name whose argument set parse-status per-argument entries cover
#(first form matching the -form selection - mirrors arg_error's display-form resolution)
proc private::parse_status_formname {spec_dict formselection} {
set formnames [dict get $spec_dict form_names]
set first [lindex $formselection 0]
if {$formselection eq "*" || $formselection eq ""} {
return [lindex $formnames 0]
}
if {[string is integer -strict $first]} {
set fid [lindex $formnames $first]
if {$fid ne ""} {
return $fid
}
return [lindex $formnames 0]
}
set fmatch [tcl::prefix::match -error "" $formnames $first]
if {$fmatch ne ""} {
return $fmatch
}
return [lindex $formnames 0]
}
lappend PUNKARGS [list {
@id -id ::punk::args::parse_status
@cmd -name punk::args::parse_status\
-summary\
"Parse-status structure from a parse attempt (validation failures reported, not raised)."\
-help\
"Run a parse attempt of arglist against a punk::args definition and
return a parse-status dict describing the outcome - for success AND
for validation failure (a validation failure is reported in the
returned structure instead of being raised as an error; other errors
such as an unknown id still raise).
This is the machine-parsable form of the information behind the
usage-display argument marking (punk::ns::cmdhelp / 'i <cmd> <args...>'):
which supplied arguments validated, which argument failed and why,
and which display scheme applies. punk::args::arg_error accepts the
structure via its -parsestatus option and derives its goodarg/badarg
row marking and choice value-in-effect highlighting from it.
Overall keys:
ok 1 parse succeeded, 0 validation failure
status valid | invalid | incomplete
invalid - a supplied word failed validation
incomplete - required arguments missing (a count or
allocation shortfall). Note a supplied word
failing its -type check can also surface as
an allocation shortfall (missingrequiredvalue)
- badarg and the per-argument statuses carry
the specifics in both situations.
scheme suggested display scheme: info (ok) | error (failure)
message empty | the validation failure message
errorcode empty | the -errorcode of the validation failure with
the bulky -argspecs payload removed
failureclass empty | first word of the errorcode description
(e.g choiceviolation, typemismatch, missingrequiredvalue)
badarg empty | name of the offending/unfillable argument
id the definition id
form form name the per-argument statuses were built for
(the first form matching -form - the form usage
displays render)
receivednames received argument names normalized to definition
argument names (opt aliases folded to their
-alias|-name optionset)
argstatus dict keyed by definition argument name, each value a
dict with keys:
class leader | option | value
status ok (received and validated) |
bad (the offending argument) |
unparsed (not received)
received count of received clauses (0 if unreceived)
positions ordinal positions of received clauses
hasvalue 1 if a value-in-effect is known
value value-in-effect - includes values the
parse filled from -default; empty when
hasvalue is 0. No values are reported
for a failed parse.
"
@form -form {withid withdef}
@leaders -min 1 -max 1
arglist -type list -optional 0 -help\
"Arguments to parse - supplied as a single list"
@opts -prefix 0
-form -type list -default * -help\
"Restrict parsing to the set of forms listed (see punk::args::parse).
The per-argument statuses are built for the first matching form."
-caller -type string -default "" -help\
"Caller attribution for the validation failure message
(see punk::args::parse -caller). When empty, defaults to the
definition's @cmd -name (or its id) rather than the call-frame
walk parse would perform."
@values -min 2
@form -form withid -synopsis "parse_status arglist ?-form {int|<formname>...}? ?-caller <string>? withid $id"
@values -max 2
withid -type literal(withid) -help\
"The literal value 'withid'"
id -type string -help\
"id of punk::args definition for a command"
@form -form withdef -synopsis "parse_status arglist ?-form {int|<formname>...}? ?-caller <string>? withdef $def ?$def?"
withdef -type literal(withdef) -help\
"The literal value 'withdef'"
def -type string -multiple 1 -optional 0 -help\
"Each remaining argument is a block of text
defining argument definitions."
}]
proc parse_status {args} {
if {[llength $args] < 3} {
punk::args::parse $args withid ::punk::args::parse_status
}
set opts_and_vals $args
set arglist [lpop opts_and_vals 0]
set opts [dict create {*}{
-form {*}
-caller {}
}]
while {[llength $opts_and_vals] && [string match -* [lindex $opts_and_vals 0]]} {
set k [lpop opts_and_vals 0]
switch -- $k {
-form - -caller {
if {![llength $opts_and_vals]} {
punk::args::parse $args withid ::punk::args::parse_status
}
dict set opts $k [lpop opts_and_vals 0]
}
default {
punk::args::parse $args withid ::punk::args::parse_status
}
}
}
set tailtype [lpop opts_and_vals 0]
switch -- $tailtype {
withid {
if {[llength $opts_and_vals] != 1} {
punk::args::parse $args withid ::punk::args::parse_status
}
set id [lindex $opts_and_vals 0]
set spec_dict [get_spec $id]
if {$spec_dict eq ""} {
error "punk::args::parse_status - no such id: $id"
}
}
withdef {
if {[llength $opts_and_vals] < 1} {
punk::args::parse $args withid ::punk::args::parse_status
}
set spec_dict [resolve {*}$opts_and_vals]
}
default {
punk::args::parse $args withid ::punk::args::parse_status
}
}
set fid [private::parse_status_formname $spec_dict [dict get $opts -form]]
set caller [dict get $opts -caller]
if {$caller eq ""} {
#the frame walk parse would do by default names parse_status's own internal
#parse call - the definition's command name is the meaningful attribution here
set caller [Dict_getdef $spec_dict cmd_info -name [Dict_getdef $spec_dict id ""]]
}
if {[catch {
punk::args::parse $arglist -form [dict get $opts -form] -errorstyle minimal -caller $caller $tailtype {*}$opts_and_vals
} r ropts]} {
set ecode [Dict_getdef $ropts -errorcode ""]
if {[lrange $ecode 0 1] ne [list PUNKARGS VALIDATION]} {
#not an argument validation failure (e.g definition error) - propagate
return -options $ropts $r
}
set classinfo [lindex $ecode 2]
set failureclass [lindex $classinfo 0]
set payload [lrange $classinfo 1 end]
set customdict [lrange $ecode 3 end]
set badarg [Dict_getdef $customdict -badarg ""]
set status [private::parse_status_classify $failureclass $payload]
#the -argspecs payload (the whole resolved spec) is display machinery - too bulky
#for a status structure; keep the classification and the small custom keys
set ecode_slim [lrange $ecode 0 2]
foreach {k v} $customdict {
if {$k ne "-argspecs"} {
lappend ecode_slim $k $v
}
}
return [private::parse_status_build $spec_dict $fid -ok 0 -status $status -scheme error -message $r -errorcode $ecode_slim -failureclass $failureclass -badarg $badarg]
}
return [private::parse_status_build $spec_dict $fid -ok 1 -status valid -scheme info -parsedargs $r]
}
#return number of values we can assign to cater for variable length clauses such as:
# {"elseif" expr "?then?" body}
#review - efficiency? each time we call this - we are looking ahead at the same info
@ -10202,7 +10540,7 @@ tcl::namespace::eval punk::args {
if {$leadername ni $leadernames_received} {
#puts stderr "private::get_dict_can_assign_value $ldridx $values $nameidx $VAL_NAMES"
set msg "Bad number of leaders for %caller%. Not enough remaining values to assign to required arguments (fail on $leadername)."
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list missingrequiredleader $leadername ] -argspecs $argspecs]] $msg
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list missingrequiredleader $leadername ] -badarg $leadername -argspecs $argspecs]] $msg
} else {
puts stderr "get_dict cannot assign val:$ldr to leadername:$leadername (222)"
#return -options [list -code error -errorcode [list PUNKARGS UNCONSUMED -argspecs $argspecs]] "private::get_dict_can_assign_value consumed 0 unexpected 2?"
@ -10413,7 +10751,7 @@ tcl::namespace::eval punk::args {
if {$valname ni $valnames_received} {
#puts stderr "private::get_dict_can_assign_value $validx $values $nameidx $VAL_NAMES"
set msg "Bad number of values for %caller%. Not enough remaining values to assign to required arguments (fail on $valname)."
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list missingrequiredvalue $valname ] -argspecs $argspecs]] $msg
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list missingrequiredvalue $valname ] -badarg $valname -argspecs $argspecs]] $msg
} else {
#error 444
puts stderr "get_dict cannot assign val:$val to valname:$valname (444)"

3
src/modules/punk/args-buildversion.txt

@ -1,6 +1,7 @@
0.4.2
0.5.0
#First line must be a semantic version number
#all other lines are ignored.
#0.5.0 - G-049 parse-status data model: new punk::args::parse_status - runs a parse attempt (withid/withdef) and returns a documented status structure instead of raising on validation failure (overall ok/status valid|invalid|incomplete/scheme/message/errorcode-minus-argspecs/failureclass/badarg/id/form/receivednames + per-argument argstatus with class/status ok|bad|unparsed/received/positions/hasvalue/value-in-effect incl -default fill). arg_error: new -parsestatus option - both renderers (table and string) now derive goodarg/badarg row marking and choice value-in-effect highlighting from the structure (built internally from -badarg/-parsedargs when not supplied), replacing the transient goodargs/badarg locals; scheme colours resolve per-render into a local array (scheme renders no longer mutate the shared arg_error_CLR array - the -nocolour leak) and the DOCUMENTED -scheme choice value 'nocolour' (and 'nocolor') now takes effect instead of falling through to leftover colours. parse: new -caller option overriding the %caller% frame-walk substitution in validation failure messages (parse_status defaults it to the definition's @cmd -name). get_dict: missingrequiredvalue/missingrequiredleader allocation failures now carry -badarg (type-failed words get badarg marking, not just choice violations). Tests: parsestatus.test (new), usagemarking.test nocolour/leak GAP pins flipped + -parsestatus render parity
#0.4.2 - G-046: display-field deferral - resolve no longer tstr-expands display-only content during argument resolution: ${...} in -help (@cmd/@examples/argument records) and @formdisplay -header/-body is masked with inert tokens (spec key DISPLAY_DEFERRED) and expanded on demand at display time (arg_error/eg/resolved_def/@default-copyfrom hooks; separate argdefcache_display cache for non-dynamic defs; @dynamic display content re-expands per render preserving provider refresh). First parse of heavily documented commands drops accordingly (punk::ansi::mark_columns ~4.3s -> ~12ms; tclcore ::lseq resolve ~184ms -> ~2ms) and -help content that calls punk::args-parsing commands (including against its own id) no longer stalls or loops - plus a display-time reentrancy guard (raw ${...} source substituted on nested expansion of the same id). Record splitter factored to private::split_definition_records. Also: @dynamic second-round multiline substitutions into deferred fields now get the 'line' paramindents alignment (rendering_atdynamic_multiline_help_insertion GAP flipped); prefix/alias choice normalization writeback no longer list-quotes single-element-clause values ({\Deleted} shape bug, choicegroups_imap_prefix_shape GAP flipped); -return string renderer aligns cmd-help continuations under the Description label and its Example line shows the example instead of the doc url. Tests: deferredhelp.test (new), rendering.test/choicegroups.test updated per G-046 acceptance
#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

137
src/modules/punk/ns-999999.0a1.0.tm

@ -5503,7 +5503,20 @@ y" {return quirkykeyscript}
usage information as this is inherently risky. (could create a file,
exit the interp etc)
"
-return -type string -default table -choices {string table tableobject}
-return -type string -default table -choices {string table tableobject dict} -choicelabels {
string "no table layout"
table "full table layout"
tableobject "table object cmd"
dict "machine-parsable resolution + parse-status"
} -help\
"Return form of the usage information.
'dict' returns a dict with keys origin, docid, cmdtype,
args_remaining and parsestatus - where parsestatus is the
parse-status structure of the supplied argument words against
the resolved definition (see punk::args::parse_status for the
documented structure; empty if the command is undocumented).
Its per-argument statuses distinguish a fully-valid, an invalid
and an incomplete argument set machine-parsably."
} {${[punk::args::resolved_def -types opts ::punk::args::arg_error -scheme]}} {
@ -5564,17 +5577,6 @@ y" {return quirkykeyscript}
set opt_grepstr [dict get $opts -grepstr]
set opt_form [dict get $opts -form]
set opt_return [dict get $opts -return]
switch -- $opt_return {
string {
set estyle "basic"
}
tableobject {
set estyle "minimal"
}
default {
set estyle "standard"
}
}
set nextopts [dict remove $opts -grepstr]
#JJJ
@ -5597,35 +5599,37 @@ y" {return quirkykeyscript}
if {[llength $testresolved] == 1} {
#only the command itself is in the args_resolved list - so we can't resolve to a deeper subcommand
ledit queryargs -1 -1 {*}$args_remaining ;#prepend
#advisory goodargs parse (marking of supplied words). With NO supplied args
#a failure only reflects missing required leaders/values - nothing to mark and
#nothing wrong with the user's (absent) input, so we show plain usage in the
#info scheme instead of the internal-looking parse error (G-046 item 5).
#Use -errorstyle minimal for that case: the $estyle rendering would build the
#full usage table INSIDE the raised error only for it to be discarded here -
#doubling display time for large argdocs (e.g 'i punk::args::define').
if {![llength $queryargs]} {
set advisory_estyle minimal
} else {
set advisory_estyle $estyle
}
if {[catch {punk::args::parse $queryargs -form $opt_form -errorstyle $advisory_estyle withid $rootdoc} parseresult]} {
if {![llength $queryargs]} {
if {!$scheme_received} {
dict set nextopts -scheme info
}
set result [punk::args::arg_error "" [punk::args::get_spec $rootdoc] {*}$nextopts -aserror 0]
} elseif {$opt_return eq "tableobject"} {
set result [punk::args::arg_error "$parseresult" [punk::args::get_spec $rootdoc] {*}$nextopts -aserror 0]
} else {
set result $parseresult
#advisory goodargs parse of the supplied words via parse_status (validation
#failures are reported in the structure, not raised; internally
#-errorstyle minimal so no usage table is built inside a discarded error).
#-caller attributes any failure message to the queried command rather than
#an internal parse call site.
set pstatus [punk::args::parse_status $queryargs -form $opt_form -caller $querycommand withid $rootdoc]
#With NO supplied args a failure only reflects missing required
#leaders/values - nothing to mark and nothing wrong with the user's (absent)
#input, so we show plain usage in the info scheme instead of the
#internal-looking parse error (G-046 item 5).
if {$opt_return eq "dict"} {
if {$scheme_received} {
dict set pstatus scheme [dict get $opts -scheme]
} elseif {![dict get $pstatus ok] && ![llength $queryargs]} {
dict set pstatus scheme info
}
} else {
return [dict create origin $rootorigin docid $rootdoc cmdtype $rootorigintype args_remaining $queryargs parsestatus $pstatus]
}
if {[dict get $pstatus ok]} {
#show usage - with goodargs marked
if {!$scheme_received} {
dict set nextopts -scheme info
}
set result [punk::args::arg_error "" [punk::args::get_spec $rootdoc] {*}$nextopts -aserror 0 -parsedargs $parseresult]
set result [punk::args::arg_error "" [punk::args::get_spec $rootdoc] {*}$nextopts -aserror 0 -parsestatus $pstatus]
} elseif {![llength $queryargs]} {
if {!$scheme_received} {
dict set nextopts -scheme info
}
set result [punk::args::arg_error "" [punk::args::get_spec $rootdoc] {*}$nextopts -aserror 0]
} else {
set result [punk::args::arg_error [dict get $pstatus message] [punk::args::get_spec $rootdoc] {*}$nextopts -aserror 0 -parsestatus $pstatus]
}
if {$opt_grepstr ne ""} {
if {[llength $opt_grepstr] == 1} {
@ -5695,36 +5699,44 @@ y" {return quirkykeyscript}
}
}
if {$origindoc ne ""} {
#important not to use "-cache 1" for this parse - need to reflect dynamically updated ensembles etc
#advisory goodargs parse (marking of supplied words). With NO supplied trailing args
#a failure only reflects missing required leaders/values (e.g 'i string is') - nothing
#to mark and nothing wrong with the user's (absent) input, so we show plain usage in
#the info scheme instead of the internal-looking parse error (G-046 item 5).
#Use -errorstyle minimal for that case: the $estyle rendering would build the full
#usage table INSIDE the raised error only for it to be discarded here - doubling
#display time for large argdocs (e.g 'i punk::args::define').
if {![llength $args_remaining]} {
set advisory_estyle minimal
} else {
set advisory_estyle $estyle
}
if {[catch {punk::args::parse $args_remaining -cache 0 -form $opt_form -errorstyle $advisory_estyle withid $origindoc} parseresult]} {
if {![llength $args_remaining]} {
if {!$scheme_received} {
dict set nextopts -scheme info
}
set result [punk::args::arg_error "" [punk::args::get_spec $origindoc] {*}$nextopts -aserror 0]
} elseif {$opt_return eq "tableobject"} {
set result [punk::args::arg_error "$parseresult" [punk::args::get_spec $origindoc] {*}$nextopts -aserror 0]
} else {
set result $parseresult
}
#caller attribution for any validation failure message: the queried command plus
#the subcommand words consumed by resolution (not the trailing argument words)
if {[llength $args_remaining]} {
set consumedwords [lrange $queryargs 0 end-[llength $args_remaining]]
} else {
set consumedwords $queryargs
}
set caller_display [string trim "$querycommand [join $consumedwords { }]"]
#advisory goodargs parse of the supplied words via parse_status (validation
#failures are reported in the structure, not raised; internally uncached and
#-errorstyle minimal so no usage table is built inside a discarded error and
#dynamically updated ensembles are reflected).
set pstatus [punk::args::parse_status $args_remaining -form $opt_form -caller $caller_display withid $origindoc]
#With NO supplied trailing args a failure only reflects missing required
#leaders/values (e.g 'i string is') - nothing to mark and nothing wrong with the
#user's (absent) input, so we show plain usage in the info scheme instead of the
#internal-looking parse error (G-046 item 5).
if {$opt_return eq "dict"} {
if {$scheme_received} {
dict set pstatus scheme [dict get $opts -scheme]
} elseif {![dict get $pstatus ok] && ![llength $args_remaining]} {
dict set pstatus scheme info
}
return [dict create origin $origin docid $origindoc cmdtype $origintype args_remaining $args_remaining parsestatus $pstatus]
}
if {[dict get $pstatus ok]} {
#show usage - with goodargs marked
if {!$scheme_received} {
dict set nextopts -scheme info
}
set result [punk::args::arg_error "" [punk::args::get_spec $origindoc] {*}$nextopts -aserror 0 -parsedargs $parseresult]
set result [punk::args::arg_error "" [punk::args::get_spec $origindoc] {*}$nextopts -aserror 0 -parsestatus $pstatus]
} elseif {![llength $args_remaining]} {
if {!$scheme_received} {
dict set nextopts -scheme info
}
set result [punk::args::arg_error "" [punk::args::get_spec $origindoc] {*}$nextopts -aserror 0]
} else {
set result [punk::args::arg_error [dict get $pstatus message] [punk::args::get_spec $origindoc] {*}$nextopts -aserror 0 -parsestatus $pstatus]
}
if {$opt_grepstr ne ""} {
if {[llength $opt_grepstr] == 1} {
@ -5735,6 +5747,9 @@ y" {return quirkykeyscript}
}
return $result
} else {
if {$opt_return eq "dict"} {
return [dict create origin $origin docid "" cmdtype $origintype args_remaining $args_remaining parsestatus {}]
}
return "Undocumented command $origin. Type: $origintype"
}

3
src/modules/punk/ns-buildversion.txt

@ -1,6 +1,7 @@
0.1.4
0.2.0
#First line must be a semantic version number
#all other lines are ignored.
#0.2.0 - G-049: cmdhelp -return dict - machine-parsable return carrying resolution info (origin/docid/cmdtype/args_remaining) plus the parse-status structure of the supplied argument words (punk::args::parse_status shape; empty for undocumented commands); the scheme field reflects an explicit -scheme and the G-046-item-5 no-supplied-words suppression. cmdhelp's advisory parse now runs via punk::args::parse_status on both the alias path and the main path: an explicit -scheme is honoured on the parse-failure render (previously only on success/tableobject - failures returned parse's internally rendered error with the default error scheme), failure renders consume the structure via arg_error -parsestatus (badarg marking now covers type/allocation failures via the structure), and the failure message names the queried command (parse -caller: querycommand + consumed subcommand words) instead of whatever the %caller% frame walk found - at top call depth that was cmdhelp's own raw 'punk::args::parse $args_remaining ...' source text. Tests: cmdhelp.test G-049 GAP pins flipped + cmdhelp_return_dict_* added
#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

4
src/tests/modules/AGENTS.md

@ -39,8 +39,8 @@ Unit tests for editable source modules under `src/modules/`, `src/modules_tcl8/`
- `punkcheck/` — punkcheck module tests (install, summarize_install_resultdict, installtrack)
- `punk/ansi/` — punk::ansi tests (`testsuites/ansi/`): ansistrip/ansimerge, plus characterization of the ANSI-at-position mechanisms (`ansistring.test`: INDEX/INDEXCODE/INDEXCHAR/RANGE/INSERT grapheme indexing with SGR-prefix merging, INDEXCOLUMNS/COLUMNINDEX double-wide column mapping, trim/VIEW), code splitting invariants (`ta.test`: detect/detectcode distinction, split_codes/split_codes_single/split_at_codes shapes and round-trip) and single-code/effective-state semantics (`codetype.test`: is_sgr_reset/has_sgr_leadingreset, has_any/all_effective, sgr_merge, sequence_type classify). ANSI codes in these tests are literal escape strings so results are colour-state independent
- `punk/args/` — punk::args tests (`testsuites/args/`): parsing, choices/choicegroups, forms, rendering/indentation characterization, and usage-marking characterization (`usagemarking.test`: -parsedargs/-badarg/-scheme marking primitives plus goodchoice highlighting of selected/default-in-effect choice words, asserted by SGR-parameter subset against the live colour arrays; GAP pins for the nocolour-scheme fallthrough and shared colour-array leakage that G-049 will flip)
- `punk/ns/` — punk::ns tests (`testsuites/ns/`): cmdwhich/cmdinfo/cmd_traverse doc-lookup flow (`cmdflow.test`, G-040 parity) and cmdhelp usage-rendering integration (`cmdhelp.test`: scheme selection, goodarg/badarg marking, goodchoice highlighting of supplied/default choice words, alias path, cmdinfo result shape; GAP pins for badarg coverage/scheme-on-failure/caller attribution (G-049), pseudo-command cmdtype + space-form docid prefixes (G-051, real `string is` pins behind the have_tclcoredocs constraint), TclOO undocumented-method fallback (G-052), and synopsis marking absence (G-050))
- `punk/args/` — punk::args tests (`testsuites/args/`): parsing, choices/choicegroups, forms, rendering/indentation characterization, usage-marking characterization (`usagemarking.test`: -parsedargs/-badarg/-parsestatus/-scheme marking primitives plus goodchoice highlighting of selected/default-in-effect choice words, asserted by SGR-parameter subset against the live colour arrays; the G-049 nocolour/colour-leak GAP pins flipped 2026-07-10 to scheme-statelessness assertions), and the G-049 parse-status structure (`parsestatus.test`: punk::args::parse_status overall/per-argument statuses, badarg for type/allocation failures, -caller attribution, errorcode -argspecs stripping)
- `punk/ns/` — punk::ns tests (`testsuites/ns/`): cmdwhich/cmdinfo/cmd_traverse doc-lookup flow (`cmdflow.test`, G-040 parity) and cmdhelp usage-rendering integration (`cmdhelp.test`: scheme selection, goodarg/badarg marking incl type/allocation failures, goodchoice highlighting of supplied/default choice words, alias path, cmdinfo result shape, queried-command failure attribution, and `-return dict` parse-status returns (G-049 - its GAP pins flipped 2026-07-10); remaining GAP pins for pseudo-command cmdtype + space-form docid prefixes (G-051, real `string is` pins behind the have_tclcoredocs constraint), TclOO undocumented-method fallback (G-052), and synopsis marking absence (G-050))
- `punk/mix/` — punk::mix::cli tests (prune helpers, punkcheck virtual sources) and punk::mix::commandset::repo fossil move/rename characterization tests (`testsuites/repo/`, FOSSIL_HOME-isolated; GAP-marked tests pin behaviour G-022 will change)
- `punk/libunknown/` — .tm same-version shadowing pin-tests (`testsuites/shadowing/`): tcl::tm::add prepend rule, head-of-tm-list wins exact-version ties, version beats order, punk::libunknown parity — shipped behaviour depends on these (runtests tm ordering, punk_main package-mode precedence, G-033); mixed .tm/pkgIndex.tcl characterization is goal G-035

188
src/tests/modules/punk/args/testsuites/args/parsestatus.test

@ -0,0 +1,188 @@
package require tcltest
package require punk::args
#Unit coverage of the G-049 parse-status data model: punk::args::parse_status runs a
#parse attempt and returns the documented status structure (validation failures are
#reported in the structure, not raised). The rendering side - arg_error consuming the
#structure via -parsestatus and the goodarg/badarg/goodchoice marking derived from it -
#is covered in usagemarking.test; integration through punk::ns::cmdhelp -return dict in
#src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test.
namespace eval ::testspace {
namespace import ::tcltest::*
variable common {
set result ""
}
#--- fixtures ----------------------------------------------------------------------------
punk::args::define {
@id -id ::testspace::psfix
@cmd -name testspace::psfix -summary "psfix summary" -help "psfix help"
@leaders -min 1 -max 1
lvarname -type string
@opts
-sh|--shape -type string -default square
-flag -type none
@values -min 2 -max 2
firstval -type int
lastval -type int
}
punk::args::define {
@id -id ::testspace::pschoosy
@cmd -name testspace::pschoosy -summary "pschoosy summary" -help "pschoosy help"
@leaders -min 1 -max 1
animal -choices {cat dog emu}
@opts
-shade -choices {light dark} -default dark
@values -min 1 -max 1
volume -type int
}
#--- success -----------------------------------------------------------------------------
test parsestatus_valid_overall {a fully-valid argument set reports ok/valid/info with empty failure fields}\
-setup $common -body {
set ps [punk::args::parse_status {v1 -flag 0 1} withid ::testspace::psfix]
lappend result [dict get $ps ok] [dict get $ps status] [dict get $ps scheme]
lappend result [dict get $ps message] [dict get $ps errorcode] [dict get $ps failureclass] [dict get $ps badarg]
lappend result [dict get $ps id] [dict get $ps form]
}\
-cleanup {
}\
-result [list 1 valid info {} {} {} {} ::testspace::psfix _default]
test parsestatus_valid_argstatus {received arguments report status ok with positions and value-in-effect; an unreceived opt reports unparsed}\
-setup $common -body {
set ps [punk::args::parse_status {v1 -flag 0 1} withid ::testspace::psfix]
set as [dict get $ps argstatus]
lappend result [dict keys $as]
lappend result [dict get $as lvarname status] [dict get $as lvarname class] [dict get $as lvarname value]
lappend result [dict get $as firstval status] [dict get $as firstval value] [dict get $as firstval positions]
#-sh|--shape not received: unparsed, received count 0
lappend result [dict get $as -sh|--shape status] [dict get $as -sh|--shape received]
}\
-cleanup {
}\
-result [list {lvarname -sh|--shape -flag firstval lastval} ok leader v1 ok 0 2 unparsed 0]
test parsestatus_optionset_alias_folding {an opt received via an alias of an -alias|--fullname optionset is folded to the optionset name}\
-setup $common -body {
set ps [punk::args::parse_status {v1 -sh circle 0 1} withid ::testspace::psfix]
lappend result [expr {"-sh|--shape" in [dict get $ps receivednames]}]
lappend result [dict get $ps argstatus -sh|--shape status]
}\
-cleanup {
}\
-result [list 1 ok]
test parsestatus_default_value_in_effect {an unsupplied argument filled from -default carries the value-in-effect with status unparsed}\
-setup $common -body {
set ps [punk::args::parse_status {cat 5} withid ::testspace::pschoosy]
set shade [dict get $ps argstatus -shade]
lappend result [dict get $shade status] [dict get $shade hasvalue] [dict get $shade value]
}\
-cleanup {
}\
-result [list unparsed 1 dark]
#--- validation failures -----------------------------------------------------------------
test parsestatus_choiceviolation_invalid {a choice violation reports invalid with the offending argument status bad and its failure class}\
-setup $common -body {
set ps [punk::args::parse_status {horse 5} withid ::testspace::pschoosy]
lappend result [dict get $ps ok] [dict get $ps status] [dict get $ps scheme]
lappend result [dict get $ps badarg] [dict get $ps failureclass]
lappend result [dict get $ps argstatus animal status] [dict get $ps argstatus animal failureclass]
lappend result [dict get $ps argstatus volume status]
lappend result [expr {[dict get $ps message] ne ""}]
}\
-cleanup {
}\
-result [list 0 invalid error animal choiceviolation bad choiceviolation unparsed 1]
test parsestatus_typed_value_allocation_failure {a supplied word failing its -type check surfaces as an allocation shortfall WITH the unfillable argument as badarg}\
-setup $common -body {
set ps [punk::args::parse_status {v1 x x} withid ::testspace::psfix]
lappend result [dict get $ps ok] [dict get $ps failureclass] [dict get $ps badarg]
lappend result [dict get $ps argstatus firstval status]
lappend result [dict get $ps argstatus lastval status]
}\
-cleanup {
}\
-result [list 0 missingrequiredvalue firstval bad unparsed]
test parsestatus_count_shortfall_incomplete {too few values reports incomplete with no offending argument}\
-setup $common -body {
set ps [punk::args::parse_status {v1} withid ::testspace::psfix]
lappend result [dict get $ps ok] [dict get $ps status] [dict get $ps failureclass] [dict get $ps badarg]
}\
-cleanup {
}\
-result [list 0 incomplete trailingvaluecount {}]
test parsestatus_count_excess_invalid {too many values reports invalid (the count classes only classify as incomplete below the minimum)}\
-setup $common -body {
set ps [punk::args::parse_status {v1 0 1 2} withid ::testspace::psfix]
lappend result [dict get $ps ok] [dict get $ps status]
}\
-cleanup {
}\
-result [list 0 invalid]
test parsestatus_errorcode_argspecs_stripped {the reported errorcode keeps classification and custom keys but not the bulky -argspecs payload}\
-setup $common -body {
set ps [punk::args::parse_status {horse 5} withid ::testspace::pschoosy]
set ecode [dict get $ps errorcode]
lappend result [lrange $ecode 0 1]
lappend result [lindex [lindex $ecode 2] 0]
lappend result [expr {"-argspecs" in $ecode}]
lappend result [expr {"-badarg" in $ecode}]
}\
-cleanup {
}\
-result [list {PUNKARGS VALIDATION} choiceviolation 0 1]
#--- caller attribution ------------------------------------------------------------------
test parsestatus_caller_attribution {-caller replaces %caller% in the failure message; the default is the definition's @cmd -name}\
-setup $common -body {
set ps [punk::args::parse_status {v1} -caller "my queried command" withid ::testspace::psfix]
lappend result [string match "*my queried command*" [dict get $ps message]]
set ps [punk::args::parse_status {v1} withid ::testspace::psfix]
lappend result [string match "*testspace::psfix*" [dict get $ps message]]
lappend result [string match {*punk::args::parse*} [dict get $ps message]]
}\
-cleanup {
}\
-result [list 1 1 0]
#--- withdef form ------------------------------------------------------------------------
test parsestatus_withdef {parse_status accepts an inline definition via withdef}\
-setup $common -body {
set ps [punk::args::parse_status {-x 5} withdef {
@id -id ::testspace::psinline
@opts
-x -type int
}]
lappend result [dict get $ps ok] [dict get $ps argstatus -x status] [dict get $ps argstatus -x value]
}\
-cleanup {
}\
-result [list 1 ok 5]
#--- non-validation errors still raise ---------------------------------------------------
test parsestatus_unknown_id_raises {an unknown definition id raises rather than being reported in the structure}\
-setup $common -body {
lappend result [catch {punk::args::parse_status {a b} withid ::testspace::no_such_definition} emsg]
lappend result [string match "*no such id*" $emsg]
}\
-cleanup {
}\
-result [list 1 1]
}
tcltest::cleanupTests ;#needed to produce test summary line.

95
src/tests/modules/punk/args/testsuites/args/usagemarking.test

@ -195,9 +195,9 @@ namespace eval ::testspace {
punk::args::arg_error "" [punk::args::get_spec ::testspace::choicefix] -aserror 0 {*}$args
}
#trigger the initial colour-array load, then capture the pristine styles the tests
#assert against (the shared arg_error_CLR array is mutated in place by scheme renders -
#see the leakage GAP pins at the end of this file - so capture before anything else)
#trigger the initial colour-array load, then capture the styles the tests assert
#against (since G-049 scheme renders resolve colours per-render and no longer mutate
#the shared arrays - the statelessness tests at the end of this file pin that)
render_markfix
variable GOOD [sgr_params $::punk::args::arg_error_CLR(goodarg)]
variable BAD [sgr_params $::punk::args::arg_error_CLR(badarg)]
@ -369,63 +369,94 @@ namespace eval ::testspace {
}\
-result [list 1 0 0]
#--- GAP pins: the nocolour scheme and shared colour-array statefulness ------------------
#arg_error normalizes its -scheme value with a switch whose nocolour arm matches "",
#"-nocolor" and "-nocolour" (leading dash) - so the DOCUMENTED choice value "nocolour"
#falls through to the catch-all "na" scheme: no override array is merged and the render
#uses whatever colours the most recent info/error render left in the shared
#arg_error_CLR array. The dash spelling "-nocolour" does merge the nocolour overrides -
#and because the merge writes the shared array in place, its strike-only goodarg/badarg
#styles then LEAK into all subsequent renders (nothing restores them until a colour
#on/off state flip forces an array reload).
#Pinned 2026-07-10 as characterization - a fix (accept the documented choice value +
#per-render colour resolution, G-049 candidates) should flip these pins.
test usagemarking_GAP_scheme_nocolour_renders_with_leftover_colours {the documented choice value 'nocolour' is treated as an unknown scheme and renders with the previous scheme's colours}\
#--- scheme statelessness: nocolour choice value + per-render colour resolution ----------
#Flipped from GAP pins 2026-07-10 by G-049: arg_error's scheme switch previously only
#matched dash spellings for the nocolour arm (the DOCUMENTED choice value 'nocolour'
#fell through to the catch-all "na" scheme and rendered with whatever colours the most
#recent scheme render left behind), and scheme overrides were merged into the SHARED
#arg_error_CLR array in place - leaking into all subsequent renders until a colour
#on/off state flip forced a reload. Colours are now resolved per-render into a local
#array and the documented choice value takes effect.
test usagemarking_scheme_nocolour_documented_choice {the documented choice value 'nocolour' takes effect regardless of which scheme rendered previously}\
-setup $common -body {
variable INFOBORDER
#prime the shared array with the info scheme, then render 'nocolour'
#prime with the info scheme, then render 'nocolour' - no info border may remain
render_markfix -scheme info
set out [render_markfix -scheme nocolour]
lappend result [has_sgr_with $out $INFOBORDER]
#prime with the error scheme instead - the same call now renders differently
#prime with the error scheme instead - the same call renders identically
render_markfix -scheme error
set out [render_markfix -scheme nocolour]
lappend result [has_sgr_with $out $INFOBORDER]
}\
-cleanup {
punk::args::private::argerror_load_colours 1
}\
-result [list 1 0]
-result [list 0 0]
test usagemarking_GAP_dash_nocolour_leaks_into_shared_array {a -scheme -nocolour (dash spelling) render overwrites goodarg in the shared colour array}\
test usagemarking_scheme_render_shared_array_not_mutated {a -scheme -nocolour (dash spelling) render leaves the shared colour array pristine}\
-setup $common -body {
variable GOOD
variable NCGOOD
render_markfix -scheme -nocolour
set leaked [sgr_params $::punk::args::arg_error_CLR(goodarg)]
#the shared array now holds the strike-only nocolour style, not the pristine one
lappend result [expr {$leaked eq $NCGOOD}]
lappend result [expr {$leaked eq $GOOD}]
set goodnow [sgr_params $::punk::args::arg_error_CLR(goodarg)]
#the shared array keeps the pristine goodarg style, not the strike-only nocolour override
lappend result [expr {$goodnow eq $NCGOOD}]
lappend result [expr {$goodnow eq $GOOD}]
}\
-cleanup {
punk::args::private::argerror_load_colours 1
}\
-result [list 1 0]
-result [list 0 1]
test usagemarking_GAP_dash_nocolour_leak_affects_later_info_render {after a -scheme -nocolour render, an info-scheme render marks goodargs with the leaked strike-only style}\
test usagemarking_scheme_render_isolation_later_info_render {after a -scheme -nocolour render, an info-scheme render still marks goodargs with the pristine goodarg style}\
-setup $common -body {
variable GOOD
variable NCGOOD
render_markfix -scheme -nocolour
set argd [punk::args::parse {v1 0 1} withid ::testspace::markfix]
set out [render_markfix -scheme info -parsedargs $argd]
#marking present as strike (9) without the green (32) of the pristine goodarg style
lappend result [any_line_has_sgr $out lvarname $NCGOOD [lremove_params $GOOD $NCGOOD]]
lappend result [any_line_has_sgr $out lvarname $GOOD]
}\
-cleanup {
punk::args::private::argerror_load_colours 1
}\
-result [list 1]
test usagemarking_scheme_repeat_renders_identical {repeated renders of the same call are byte-identical regardless of prior scheme renders}\
-setup $common -body {
set argd [punk::args::parse {v1 0 1} withid ::testspace::markfix]
set first [render_markfix -scheme info -parsedargs $argd]
render_markfix -scheme error
render_markfix -scheme -nocolour
render_markfix -scheme nocolour
set again [render_markfix -scheme info -parsedargs $argd]
lappend result [expr {$again eq $first}]
}\
-cleanup {
}\
-result [list 1]
#--- arg_error -parsestatus (G-049 parse-status structure consumption) -------------------
test usagemarking_parsestatus_render_parity {rendering via -parsestatus of a parse attempt matches the -parsedargs render byte for byte}\
-setup $common -body {
set argd [punk::args::parse {v1 -sh circle 0 1} withid ::testspace::markfix]
set via_parsedargs [render_markfix -scheme info -parsedargs $argd]
set pstatus [punk::args::parse_status {v1 -sh circle 0 1} withid ::testspace::markfix]
set via_parsestatus [render_markfix -scheme info -parsestatus $pstatus]
lappend result [expr {$via_parsestatus eq $via_parsedargs}]
}\
-cleanup {
}\
-result [list 1]
test usagemarking_parsestatus_badarg_render {a failed parse's status structure badarg-marks the offending argument row}\
-setup $common -body {
variable BAD
set pstatus [punk::args::parse_status {v1 x x} withid ::testspace::markfix]
set out [render_markfix -parsestatus $pstatus]
lappend result [any_line_has_sgr $out firstval $BAD]
lappend result [any_line_has_sgr $out lastval $BAD]
}\
-cleanup {
}\
-result [list 1 0]
}

100
src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test

@ -7,9 +7,12 @@ package require punk::ansi
#Characterization of punk::ns::cmdhelp usage rendering ('i <cmd> <args...>') - added
#2026-07-10 ahead of G-049..G-052: scheme selection (error scheme for a failed parse of
#the supplied arguments, info scheme with goodarg marking for a successful one), badarg
#marking, the -return string renderer, the alias path, cmdinfo result shape, and GAP pins
#for the pseudo-command cmdtype (G-051), space-delimited-docid prefix matching (G-051),
#TclOO undocumented-method fallback (G-052) and synopsis marking absence (G-050).
#marking, the -return string renderer, the -return dict parse-status structure (G-049),
#the alias path, cmdinfo result shape, and GAP pins for the pseudo-command cmdtype
#(G-051), space-delimited-docid prefix matching (G-051), TclOO undocumented-method
#fallback (G-052) and synopsis marking absence (G-050). The G-049 GAP pins (badarg
#coverage, scheme-on-failure, caller attribution) were flipped 2026-07-10 when G-049
#shipped the fixes.
#The marking primitives themselves (punk::args::arg_error -parsedargs/-badarg/-scheme)
#are pinned in src/tests/modules/punk/args/testsuites/args/usagemarking.test - this file
#pins that cmdhelp drives them correctly.
@ -328,23 +331,28 @@ namespace eval ::testspace {
}\
-result [list 1 1 0]
#--- GAP pins: scheme/badarg asymmetries (G-049 candidates) -------------------------------
#Pinned 2026-07-10 as characterization of current behaviour - a fix should flip these.
#--- badarg coverage / scheme-on-failure / caller attribution -----------------------------
#Flipped from GAP pins 2026-07-10 by G-049: badarg marking now covers type/allocation
#failures (missingrequiredvalue/missingrequiredleader carry -badarg), an explicit
#-scheme reaches the parse-failure render (cmdhelp renders failures itself from the
#parse-status structure instead of returning parse's internally rendered error), and
#the failure message names the queried command via parse -caller instead of whatever
#the call-frame walk found.
test cmdhelp_GAP_no_badarg_marking_for_failed_typed_value {a value failing its -type check reports 'missingrequiredvalue' with NO badarg marking - only choice violations mark the bad argument}\
test cmdhelp_badarg_marking_failed_typed_value {a value failing its -type check (surfacing as an allocation shortfall) badarg-marks the unfillable argument's row}\
-setup $common -body {
variable BAD
set out [punk::ns::cmdhelp ::testspace::helpfix v1 x x]
#the message names the failing argument but no row is badarg-marked
#the message names the failing argument and its row is badarg-marked
lappend result [string match "*fail on firstval*" [punk::ansi::ansistrip [lindex [split $out \n] 0]]]
lappend result [any_line_has_sgr $out firstval $BAD]
lappend result [any_line_has_sgr $out lastval $BAD]
}\
-cleanup {
}\
-result [list 1 0 0]
-result [list 1 1 0]
test cmdhelp_GAP_explicit_scheme_ignored_on_failure {an explicit -scheme is honoured on the success path but IGNORED on the parse-failure path (the rendered error keeps the error scheme)}\
test cmdhelp_explicit_scheme_honoured_on_failure {an explicit -scheme is honoured on the parse-failure path as well as the success path}\
-setup $common -body {
variable INFOBORDER
set out [punk::ns::cmdhelp -scheme info ::testspace::helpfix v1]
@ -352,25 +360,79 @@ namespace eval ::testspace {
}\
-cleanup {
}\
-result [list 0]
-result [list 1]
test cmdhelp_GAP_errormsg_leaks_internal_source {called at the global level (as from a repl), the parse-failure message shows cmdhelp's own unsubstituted source line as the caller instead of the queried command}\
test cmdhelp_errormsg_names_queried_command {the parse-failure message names the queried command at any invocation depth - never cmdhelp's internal parse source}\
-setup $common -body {
#caller attribution depends on the invocation depth: from a nested context the
#message names the user's cmdhelp invocation (correct), but a top-level call -
#the interactive 'i <cmd>' case - walks to cmdhelp's internal parse call and
#shows its raw source text
#top-level call - the interactive 'i <cmd>' case
set out [uplevel #0 [list punk::ns::cmdhelp ::testspace::helpfix v1]]
set line1 [punk::ansi::ansistrip [lindex [split $out \n] 0]]
lappend result [string match {*punk::args::parse $args_remaining*} $line1]
#from a nested context the same call attributes correctly
lappend result [string match {*punk::args::parse*} $line1]
lappend result [string match {*::testspace::helpfix*} $line1]
#nested context attributes identically
set out [punk::ns::cmdhelp ::testspace::helpfix v1]
set line1 [punk::ansi::ansistrip [lindex [split $out \n] 0]]
lappend result [string match {*punk::ns::cmdhelp*} $line1]
lappend result [string match {*::testspace::helpfix*} $line1]
}\
-cleanup {
}\
-result [list 0 1 1]
#--- -return dict (G-049 machine-parsable returns) -----------------------------------------
test cmdhelp_return_dict_valid {a fully-valid argument set returns ok/valid/info with received arguments status ok}\
-setup $common -body {
set d [punk::ns::cmdhelp -return dict ::testspace::helpfix v1 0 1]
lappend result [dict keys $d]
lappend result [dict get $d docid] [dict get $d cmdtype]
set ps [dict get $d parsestatus]
lappend result [dict get $ps ok] [dict get $ps status] [dict get $ps scheme]
lappend result [dict get $ps argstatus lvarname status]
lappend result [dict get $ps argstatus firstval status] [dict get $ps argstatus firstval value]
#-flag was not supplied
lappend result [dict get $ps argstatus -flag status]
}\
-cleanup {
}\
-result [list {origin docid cmdtype args_remaining parsestatus} ::testspace::helpfix proc 1 valid info ok ok 0 unparsed]
test cmdhelp_return_dict_invalid {an invalid argument set (choice violation) returns ok 0/invalid/error with the offending argument status bad}\
-setup $common -body {
set d [punk::ns::cmdhelp -return dict ::testspace::choosy horse 5]
set ps [dict get $d parsestatus]
lappend result [dict get $ps ok] [dict get $ps status] [dict get $ps scheme]
lappend result [dict get $ps badarg] [dict get $ps failureclass]
lappend result [dict get $ps argstatus animal status]
lappend result [dict get $ps argstatus volume status]
lappend result [expr {[dict get $ps message] ne ""}]
}\
-cleanup {
}\
-result [list 0 invalid error animal choiceviolation bad unparsed 1]
test cmdhelp_return_dict_incomplete {an incomplete argument set returns ok 0/incomplete with no offending argument}\
-setup $common -body {
set d [punk::ns::cmdhelp -return dict ::testspace::helpfix v1]
set ps [dict get $d parsestatus]
lappend result [dict get $ps ok] [dict get $ps status] [dict get $ps scheme] [dict get $ps badarg]
}\
-cleanup {
}\
-result [list 0 incomplete error {}]
test cmdhelp_return_dict_scheme {the dict scheme field reflects an explicit -scheme, and the no-supplied-words suppression (G-046 item 5) reports info}\
-setup $common -body {
set d [punk::ns::cmdhelp -return dict -scheme error ::testspace::helpfix v1 0 1]
lappend result [dict get [dict get $d parsestatus] scheme]
#leader-requiring definition with no argument words - failure reflects only the
#absent input, reported with the info scheme the display path would use
set d [punk::ns::cmdhelp -return dict ::testspace::helpstr]
set ps [dict get $d parsestatus]
lappend result [dict get $ps ok] [dict get $ps scheme]
}\
-cleanup {
}\
-result [list 1 1]
-result [list error 0 info]
#--- GAP pins: pseudo-command cmdtype + space-delimited docid prefixes (G-051) ------------

Loading…
Cancel
Save