diff --git a/CHANGELOG.md b/CHANGELOG.md index c4f2db4b..fc919c4e 100644 --- a/CHANGELOG.md +++ b/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 ` (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. diff --git a/punkproject.toml b/punkproject.toml index 13f09baf..7aab4a78 100644 --- a/punkproject.toml +++ b/punkproject.toml @@ -1,3 +1,3 @@ [project] name = "punkshell" -version = "0.4.15" +version = "0.5.0" diff --git a/src/modules/punk/args-999999.0a1.0.tm b/src/modules/punk/args-999999.0a1.0.tm index b826d4dc..fdc74cbc 100644 --- a/src/modules/punk/args-999999.0a1.0.tm +++ b/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: min 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 '): + 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|...}? ?-caller ? 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|...}? ?-caller ? 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)" diff --git a/src/modules/punk/args-buildversion.txt b/src/modules/punk/args-buildversion.txt index ba887786..130fdcc9 100644 --- a/src/modules/punk/args-buildversion.txt +++ b/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 diff --git a/src/modules/punk/ns-999999.0a1.0.tm b/src/modules/punk/ns-999999.0a1.0.tm index d8cd7bed..0e6cfcb3 100644 --- a/src/modules/punk/ns-999999.0a1.0.tm +++ b/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" } diff --git a/src/modules/punk/ns-buildversion.txt b/src/modules/punk/ns-buildversion.txt index c87e704f..71a831fc 100644 --- a/src/modules/punk/ns-buildversion.txt +++ b/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 ' could accept words parsing rejects), -choicealiases normalize to their canonical before choiceinfo lookup, and -nocase is honoured in the walk diff --git a/src/tests/modules/AGENTS.md b/src/tests/modules/AGENTS.md index 7e076623..c7d0c552 100644 --- a/src/tests/modules/AGENTS.md +++ b/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 diff --git a/src/tests/modules/punk/args/testsuites/args/parsestatus.test b/src/tests/modules/punk/args/testsuites/args/parsestatus.test new file mode 100644 index 00000000..f9cf52a9 --- /dev/null +++ b/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. diff --git a/src/tests/modules/punk/args/testsuites/args/usagemarking.test b/src/tests/modules/punk/args/testsuites/args/usagemarking.test index b9c82031..21b0765d 100644 --- a/src/tests/modules/punk/args/testsuites/args/usagemarking.test +++ b/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] } diff --git a/src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test b/src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test index 845c6071..23e568e0 100644 --- a/src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test +++ b/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 ') - 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 ' case - walks to cmdhelp's internal parse call and - #shows its raw source text + #top-level call - the interactive 'i ' 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) ------------