diff --git a/GOALS.md b/GOALS.md index 049fd388..e31b662d 100644 --- a/GOALS.md +++ b/GOALS.md @@ -285,7 +285,7 @@ Scope: src/modules/punk/mix/commandset/doc-999999.0a1.0.tm (doc.* commandset - n Detail: goals/G-081-argdoc-build-pipeline.md -### G-084 [proposed] punk::args -parsekey completeness: cross-member -multiple collection, leaders/values support, defined default precedence +### G-084 [active] punk::args -parsekey completeness: cross-member -multiple collection, leaders/values support, defined default precedence Scope: src/modules/punk/args-999999.0a1.0.tm (parse paths, resolve directive handling for @leaders/@values); src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (sites carrying '#todo - fix -parsekey for leaders and values'); src/tests/modules/punk/args/testsuites/args/parsekey.test + testsuites/dev/parsekey-knownbugs.test (GAP/known-bug flips) Detail: goals/G-084-punkargs-parsekey-completeness.md diff --git a/goals/G-084-punkargs-parsekey-completeness.md b/goals/G-084-punkargs-parsekey-completeness.md index 8351ac31..63df5e83 100644 --- a/goals/G-084-punkargs-parsekey-completeness.md +++ b/goals/G-084-punkargs-parsekey-completeness.md @@ -1,6 +1,6 @@ # G-084 punk::args -parsekey completeness: cross-member -multiple collection, leaders/values support, defined default precedence -Status: proposed +Status: active Scope: src/modules/punk/args-999999.0a1.0.tm (parse paths, resolve directive handling for @leaders/@values); src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (sites carrying '#todo - fix -parsekey for leaders and values'); src/tests/modules/punk/args/testsuites/args/parsekey.test + testsuites/dev/parsekey-knownbugs.test (GAP/known-bug flips) Goal: -parsekey behaves as a complete, position-independent result-keying mechanism: distinct -multiple members sharing a parsekey accumulate under the shared key in received order (no silent value loss), -parsekey on leaders and values renames their result/received keys the same way it does for options (or is rejected at define time everywhere it is unsupported - no silent ignore, no parse-time abort), and the precedence rule for multiple defaulted members of one group is documented behaviour rather than an in-code '? review'. Acceptance: the three punkargsKnownBug tests in testsuites/dev/parsekey-knownbugs.test are enabled and pass (or are amended to the settled design and pass); the corresponding _GAP pins in parsekey.test are flipped or retired; the '#todo - fix -parsekey for leaders and values' moduledoc comment is resolved; the full existing punk::args suite passes untouched. diff --git a/src/modules/punk/args-999999.0a1.0.tm b/src/modules/punk/args-999999.0a1.0.tm index f30a2da2..cb7c94f2 100644 --- a/src/modules/punk/args-999999.0a1.0.tm +++ b/src/modules/punk/args-999999.0a1.0.tm @@ -3697,12 +3697,13 @@ tcl::namespace::eval punk::args { } } else { if {[dict get $FDICT argspace] eq "leaders"} { - if {[dict exists $spec_merged -parsekey]} { - #if parsekey exists, we use that in the required list instead of argname, as that's what the parser will be looking for when it checks for required args - set req_name [dict get $spec_merged -parsekey] - } else { - set req_name $argname + #G-084: -parsekey on a leader is not supported (no live + #caller; the leaders parse loop has no parsekey keying). + #Reject at define time per the goal's no-silent-ignore contract. + if {[dict exists $spec_merged -parsekey] && [dict get $spec_merged -parsekey] ne ""} { + error "punk::args::resolve - -parsekey is not supported on a leader argument ('$argname'). Use -parsekey on an option or a value, or rename the leader argument itself. @id:$DEF_definition_id" } + set req_name $argname if {$req_name ni $upd_LEADER_REQUIRED} { lappend upd_LEADER_REQUIRED $argname } @@ -3736,7 +3737,16 @@ tcl::namespace::eval punk::args { dict set upd_LEADER_DEFAULTS $argname [tcl::dict::get $spec_merged -default] #tcl::dict::set F $fid LEADER_DEFAULTS $argname [tcl::dict::get $spec_merged -default] } else { - dict set upd_VAL_DEFAULTS $argname [tcl::dict::get $spec_merged -default] + #G-084: key VAL_DEFAULTS by parsekey (when present) so the + #default lands on the same parsekey-keyed values_dict slot the + #parse loop stores received values under. Matches VAL_REQUIRED + #which is already parsekey-keyed. + set _valdef_key $argname + if {[dict exists $spec_merged -parsekey]} { + set _pk [dict get $spec_merged -parsekey] + if {$_pk ne ""} {set _valdef_key $_pk} + } + dict set upd_VAL_DEFAULTS $_valdef_key [tcl::dict::get $spec_merged -default] #tcl::dict::set F $fid VAL_DEFAULTS $argname [tcl::dict::get $spec_merged -default] } } @@ -10706,6 +10716,29 @@ tcl::namespace::eval punk::args { set FORMDISPLAY [dict get $formdict FORMDISPLAY] + #G-084: parsekey->argname reverse maps. VAL_REQUIRED/LEADER_REQUIRED and the + #VAL_DEFAULTS/LEADER_DEFAULTS dicts are keyed by parsekey (so they match the + #parsekey-keyed result/received dicts), but ARG_INFO is keyed by argname - so + #any ARG_INFO lookup by a required/default key (e.g the valmin clause-length + #fallback below) must resolve the parsekey back to an argname. Built once per + #form from ARG_INFO; for an arg with no -parsekey the parsekey IS the argname. + set val_pk2name [dict create] + foreach {vn vspec} $ARG_INFO { + if {$vn in $VAL_NAMES} { + set pk [Dict_getdef $vspec -parsekey ""] + if {$pk eq ""} {set pk $vn} + if {![dict exists $val_pk2name $pk]} {dict set val_pk2name $pk $vn} + } + } + set leader_pk2name [dict create] + foreach {vn vspec} $ARG_INFO { + if {$vn in $LEADER_NAMES} { + set pk [Dict_getdef $vspec -parsekey ""] + if {$pk eq ""} {set pk $vn} + if {![dict exists $leader_pk2name $pk]} {dict set leader_pk2name $pk $vn} + } + } + #G-082 single-form error selection: words the allocation screens rejected for an #OPTIONAL argument (restricted choices, basic type), keyed by the word itself. #First rejection per word wins (nearest/earliest defined argument - the one the @@ -10721,12 +10754,15 @@ tcl::namespace::eval punk::args { set valmin 0 #set VAL_MIN 0 foreach v $VAL_REQUIRED { + #G-084: VAL_REQUIRED is keyed by parsekey; resolve to argname for ARG_INFO. + set vname $v + if {[dict exists $val_pk2name $v]} {set vname [dict get $val_pk2name $v]} # todo variable clause lengths (items marked optional in types using leading&trailing questionmarks) # e.g -types {a ?xxx?} #this has one required and one optional set clause_length 0 #for each t in typelist - foreach t [dict get $ARG_INFO $v -type] { + foreach t [dict get $ARG_INFO $vname -type] { if {![string match {\?*\?} $t]} { incr clause_length } @@ -11803,7 +11839,13 @@ tcl::namespace::eval punk::args { if {$parsekey ne $optset} { set tailopt [string trimright [lindex [split $optset |] end] =] if {$tailopt ne $parsekey} { - #defaults for multiple options sharing a -parsekey value ? review + #G-084: settled precedence rule for multiple defaulted members of + #one shared-parsekey group (none received): the LAST-defined member's + #-default wins. This loop visits optsets in definition order and applies + #each unreceived member's default via dict set (last wins), so the + #outcome is deterministic in definition order. (No live caller declares + #multiple defaults on one shared-parsekey group - e.g lsearch's + #MATCHSTYLE group defaults only -glob - so this rules the synthetic case.) dict set ordered_opts $tailopt|$parsekey [dict get $OPT_DEFAULTS $optset] } else { dict set ordered_opts $parsekey [dict get $OPT_DEFAULTS $optset] @@ -12090,10 +12132,15 @@ tcl::namespace::eval punk::args { ## set values_dict $val_defaults set values_dict [dict create] foreach valname [lrange $VAL_NAMES 0 $num_values-1] { - #set ALL valnames to lock in positioning - #note - later we need to unset any optional that had no default and was not received (no phantom default) - dict set values_dict $valname {} - } + #set ALL valnames to lock in positioning. G-084: key by parsekey so received + #values (stored under val_ident=parsekey) land on the same slot, and the + #VAL_DEFAULTS merge (also parsekey-keyed) overlays the default here. + set _vkey $valname + set _vpk [Dict_getdef [dict get $ARG_INFO $valname] -parsekey ""] + if {$_vpk ne ""} {set _vkey $_vpk} + dict set values_dict $_vkey {} + } + unset -nocomplain _vkey _vpk set values_dict [dict merge $values_dict $VAL_DEFAULTS] #------------------------------------------ set nameidx 0 @@ -12217,15 +12264,15 @@ tcl::namespace::eval punk::args { # tcl::dict::lappend values_dict $valname $clauseval #} if {$valname in $valnames_received} { - tcl::dict::lappend values_dict $valname $clauseval + tcl::dict::lappend values_dict $api_valname $clauseval tcl::dict::lappend argument_clause_typestate $valname $newtypelist } else { - tcl::dict::set values_dict $valname [list $clauseval] + tcl::dict::set values_dict $api_valname [list $clauseval] tcl::dict::set argument_clause_typestate $valname [list $newtypelist] } set valname_multiple $valname } else { - tcl::dict::set values_dict $valname $clauseval + tcl::dict::set values_dict $api_valname $clauseval tcl::dict::set argument_clause_typestate $valname [list $newtypelist] ;#list protect set valname_multiple "" incr nameidx @@ -12250,9 +12297,16 @@ tcl::namespace::eval punk::args { lappend clauseval [lindex $values $validx] } } - tcl::dict::lappend values_dict $valname_multiple $clauseval + set _vmkey $valname_multiple + set _vmpk [Dict_getdef [tcl::dict::get $argstate $valname_multiple] -parsekey ""] + if {$_vmpk ne ""} {set _vmkey $_vmpk} + tcl::dict::lappend values_dict $_vmkey $clauseval #name already seen - but must add to valnames_received anyway (as with opts and leaders) lappend valnames_received $valname_multiple + #G-084: continuation occurrences of a -multiple value must also land + #under the parsekey and register in api_valnames_received (the + #parsekey-keyed received/satisfaction tracker) - not just the first. + lappend api_valnames_received $_vmkey } else { if {$VAL_UNNAMED} { tcl::dict::set values_dict $positionalidx $val @@ -12314,7 +12368,7 @@ tcl::namespace::eval punk::args { #review - always trailing - could break? continue } - if {![dict exists $VAL_DEFAULTS $vname] && $vname ni $valnames_received} { + if {![dict exists $VAL_DEFAULTS $vname] && $vname ni $api_valnames_received} { #remove the name with empty-string default we used to establish fixed order of names #The 'values' key in the final result shouldn't contain an entry for an argument that wasn't received and had no default. dict unset values_dict $vname @@ -12484,7 +12538,7 @@ tcl::namespace::eval punk::args { } } if {[llength $VAL_REQUIRED] && !$skip_valuemissing} { - if {[llength [set missing [punk::args::system::punklib_ldiff $VAL_REQUIRED $valnames_received]]]} { + if {[llength [set missing [punk::args::system::punklib_ldiff $VAL_REQUIRED $api_valnames_received]]]} { set msg "Required value missing for %caller%. missing values: '$missing' marked with -optional false - so must be present" return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list valuemissing $missing received $valnames_received] -argspecs $argspecs]] $msg #arg_error "Required value missing for [Get_caller]. missing values: '$missing' marked with -optional false - so must be present" $argspecs @@ -12565,7 +12619,7 @@ tcl::namespace::eval punk::args { unset -nocomplain _g83_received_optsets _g83_n _g83_effpk _g83_pk_to_optsets _g83_optset _g83_p _g83_grp _g83_conflicts _g83_t _g83_t_optsets _g83_t_optset _g83_g _g83_ginfo _g83_g_received _g83_a _g83_b #--------------------------------------------------------------------------------------------- #maintain order of opts $opts values $values as caller may use lassign. - set receivednames [list {*}$leadernames_received {*}$flagsreceived {*}$valnames_received] + set receivednames [list {*}$leadernames_received {*}$flagsreceived {*}$api_valnames_received] if {[llength $receivednames]} { #flat zip of names with overall posn, including opts #set received_posns [concat {*}[lmap a $receivednames b [zero_based_posns [llength $receivednames]] {list $a $b}]] @@ -12644,6 +12698,15 @@ tcl::namespace::eval punk::args { } else { #leader or value. set argname $argname_or_ident + #G-084: values_dict/leaders_dict are keyed by parsekey, but arg_checks/argstate + #are argname-keyed - resolve the parsekey back to its argname. (For a value + #or leader without -parsekey the parsekey IS the argname, so the maps hold a + #self-entry and this is a no-op.) + if {[dict exists $val_pk2name $argname]} { + set argname [dict get $val_pk2name $argname] + } elseif {[dict exists $leader_pk2name $argname]} { + set argname [dict get $leader_pk2name $argname] + } #set pkoverride [Dict_getdef $argstate $argname -parsekey ""] #TODO? if {$pkoverride ne ""} { diff --git a/src/modules/punk/args-buildversion.txt b/src/modules/punk/args-buildversion.txt index 1de9ff59..fec7cd6a 100644 --- a/src/modules/punk/args-buildversion.txt +++ b/src/modules/punk/args-buildversion.txt @@ -1,6 +1,7 @@ -0.22.0 +0.23.0 #First line must be a semantic version number #all other lines are ignored. +#0.23.0 - G-084 increment 1 (parsekey completeness, part 1): value -parsekey is now supported (was: accepted at define time but aborted parse). A value record declaring -parsekey now renames its result `values` slot and its `received` key to the parsekey (e.g `@values {v1 -parsekey renamed -type string}` parses `hello` to `values={renamed hello}`, `received` keyed by `renamed`), and -multiple value members collect under the parsekey (e.g the `variable` setvalues form `?name value...?` pairs collect under `name_value`). The fix keys VAL_DEFAULTS and the values_dict init/storage by parsekey (matching the already-parsekey-keyed VAL_REQUIRED), tracks the parsekey in a new api_valnames_received list (the internal valnames_received stays argname-keyed for the -multiple first/continuation gate and G-053 occurrence enforcement), uses api_valnames_received for the required-satisfaction check and the received dict, and adds a per-form val_pk2name/leader_pk2name reverse map so the valmin clause-length fallback and the post-parse validation loop can resolve a parsekey back to its argname for ARG_INFO/arg_checks lookups. -parsekey on a leader is now REJECTED at define time with a clear message (was: silently ignored) - no live caller uses a leader -parsekey, so the goal's no-silent-ignore contract is met by rejection rather than parallel hot-path surgery; the dead req_name derivation in the leaders resolve branch is removed. The @values directive line still rejects -parsekey (a group-default parsekey is not a feature; per-arg value -parsekey is). The defaulted-members precedence rule for a shared-parsekey group (none received) is now documented as last-defined-member-wins (deterministic in definition order) and the in-code `? review` is removed. The tclcore moduledoc `#todo - fix -parsekey for leaders and values` is resolved (value supported, leader rejected). parsekey.test: the value GAP flipped to parsekey_value_result_key_settled, the leader GAP flipped to parsekey_leader_parsekey_rejected, the @values-line GAP split to parsekey_values_line_rejects_parsekey; the defaults GAP flipped to a settled pin. dev/parsekey-knownbugs.test: the value and leader disabled pins retired (settled in parsekey.test); the cross-member -multiple accumulation pin remains (increment 3). Full punk/args suite 399/0 (1 skipped), punk/ns 125/125, broader punk sweep 989/0. #0.22.0 - G-053 range-valued -multiple (occurrence arity): -multiple now accepts a {min max} range (max -1 = unbounded) alongside the legacy booleans 0/1, so a definition can declare at most once ({0 1}, a second occurrence is a parse error), bounded repetition ({2 4}), or one-or-more ({1 -1}) instead of choosing between silent last-wins (0) and unbounded collection (1). The spec compiler canonicalises once at resolve: the stored -multiple becomes the computed boolean (list-shape collect: true for max>1 or -1, false for legacy 0 and max==1) so every existing collect-vs-replace / scalar-vs-list / leader-value-single-multiple hot-path truth-test stays correct, and the range companions (min/max/maxbounded) live in a separate per-form MULTIPLE_RANGES dict (NOT in ARG_INFO, so they do not ride along when ARG_INFO is round-tripped as a spec via resolved_def copyfrom). Legacy 0/1 (and boolean strings true/false/yes/no) are coerced to the boolean and stay byte-unchanged (no MULTIPLE_RANGES entry for unlimited cases). Resolve validation: max must be positive or -1, min <= max, and the -optional/range-min reconciliation - a non-zero min forces presence so -optional set is a contradiction (reject with a clear message; declare -optional 0). Parse enforcement: a new PUNKARGS VALIDATION occurrencecount failure class (payload count min | max ) fires in a single post-loop pass per section (opts/leaders/values); over-max is a hard contradiction (fires in both normal and viability-probe modes, parse_status_classify maps it to invalid), under-min is pure end-of-input exhaustion (SUPPRESSED in the G-152 viability probe and classified incomplete so a viable form reports incomplete, not invalid). The usage-table Multi column reflects the range (0-1 / 2-4 / 1+ for unbounded-with-floor; the greencheck stays for legacy 1), the string renderer emits MULTI:0-1 etc., and the synopsis distinguishes at-most-once (?arg?, no ellipsis) from repeating (arg...). -multipleunique/-multipleuniqueset compose with max>1 ranges unchanged. define -help documents the range form and the -optional/range-min rule. New testsuite multipleranges.test (28 tests: 13 define-time canonicalisation+validation, 12 parse-time enforcement incl parse_status verdicts and legacy-required-still-trailingvaluecount guard, 3 rendering); full punk/args suite 399/0, punk/ns 125/125. #0.21.0 - G-083 increment 3 (argument-relations usage rendering + lsearch moduledoc adoption): the -conflicts and -parsekeymode error vocabulary now surfaces in usage/arg_error and synopsis output, and the lsearch tclcore moduledoc models its documented option incompatibilities with the new vocabulary. Rendering: (a) a per-arg -conflicts list appends a 'conflicts with: ' hint to the argument's help text in the usage table (targets resolved to display names via lookup_optset, so a parsekey target shows its member flag, not the raw parsekey); (b) a named @opts group marked -parsekeymode error is annotated 'mutually exclusive (distinct members may not be combined)' in its group header, while override (default) groups carry no such annotation; (c) the synopsis one-line form carries no conflict detail (conflicts are a usage-table concern, not a synopsis-line one). Characterized in relations.test (conflicts_usage_hint, parsekeymode_error_usage_group_header, parsekeymode_override_no_header_annotation, conflicts_synopsis_no_hint). lsearch moduledoc: -sorted gains -conflicts {-glob -regexp} and -bisect gains -conflicts {-all -not} (per-arg conflicts, the whole group stays -parsekeymode override so -glob/-regexp remain last-wins); the '(documentation incomplete - punk::args fixes required for grouped mutually exclusive options and prefix calculation)' caveat is dropped from the @cmd -help. Pinned in relations.test (lsearch_sorted_conflicts_glob + lsearch_bisect_conflicts_all raise optionconflict, lsearch_glob_regexp_last_wins stays last-wins, lsearch_caveat_dropped). clock clicks unchanged - its active positional-choice definition already models exclusivity (exactly-one-of) and the acceptance clause names lsearch only. Legacy untouched by default (no -conflicts/parsekeymode-error definition carries no new rendering). Full punk/args suite green; tclcoreparity 10/10. #0.20.0 - G-083 increment 2 (argument-relations parse-time enforcement): the -conflicts and -parsekeymode error vocabulary declared at define time (0.19.0) is now enforced at parse. A new optionconflict failure class joins the PUNKARGS VALIDATION errorcode vocabulary, mirroring optionmissing's shape: {optionconflict received } - it names both offending received arguments, for both per-arg -conflicts violations (any pair, cross-group) and -parsekeymode error group co-occurrence (distinct members of a shared-parsekey group). The check runs in a single post-resolution site in get_dict_form (after the optionmissing/valuemissing block), so the ordinary option path and the mash (short-flag bundling) path share it - both raise identically. Checked against RECEIVED arguments only (defaults never conflict), after prefix/abbreviation resolution, on optset identity (a new optsets_received tracker for options, since flagsreceived collapses shared-parsekey members onto one api_opt). Runs unconditionally - a received conflict is a hard contradiction in any mode, not end-of-input exhaustion, so the candidacy/viability probe reports it as status invalid (parse_status_classify maps optionconflict to invalid, not incomplete). Define-time check added: a group marked -parsekeymode error must also declare a non-empty -parsekey (the strict mode only applies to a shared-parsekey group). Legacy untouched by default: -parsekeymode override is the default and absent -conflicts means no check runs, so the full existing suite (including the pinned parsekey_repeat_ordering last-wins / prepend-defaults idiom) passes unchanged. relations.test extended with 7 parse-time enforcement pins (raise cases, defaults-never-conflict, cross-group, parsekeymode error raise + one-received-ok + override-legacy, parse_status invalid); full punk/args suite green. diff --git a/src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm b/src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm index 6f564c4a..6094d820 100644 --- a/src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm +++ b/src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm @@ -11028,7 +11028,8 @@ tcl::namespace::eval punk::args::moduledoc::tclcore { #@form -form "setvalues" -synopsis "variable ?name value...? ?name?" @form -form "setvalues" @values -min 0 -max -1 - #todo - fix -parsekey for leaders and values + #G-084 (2026-08-08): value -parsekey now supported (name value pairs + #collect under the parsekey name_value). Leaders -parsekey remains pending. "name value" -parsekey name_value -type {string any} -optional 1 -multiple 1 name -type string -optional 1 -multiple 0 diff --git a/src/tests/modules/punk/args/testsuites/args/parsekey.test b/src/tests/modules/punk/args/testsuites/args/parsekey.test index 843b11e0..05cf75df 100644 --- a/src/tests/modules/punk/args/testsuites/args/parsekey.test +++ b/src/tests/modules/punk/args/testsuites/args/parsekey.test @@ -293,7 +293,7 @@ namespace eval ::testspace { #GAP: behaviour unsettled in source (args tm ordered_opts default handling carries a '? review'). #Current behaviour: when more than one member of a shared-parsekey group declares -default, #the default of the LAST-DEFINED defaulted member is returned under the shared key. - test parsekey_shared_key_member_defaults_GAP {GAP: last-defined member default wins when multiple members of a shared-parsekey group have -default}\ + test parsekey_shared_key_member_defaults {settled (G-084): last-defined member default wins when multiple members of a shared-parsekey group have -default and none is received}\ -setup $common -body { set docids [list] @@ -403,34 +403,37 @@ namespace eval ::testspace { # - the @values directive line rejects -parsekey outright (unrecognised key) # - per-arg -parsekey on a leader is silently ignored (result keyed by the leader name) #Desired rename semantics pinned (disabled) in dev/parsekey-knownbugs.test. - test parsekey_values_leaders_GAP {GAP: -parsekey on a value aborts parse, on a leader is ignored, on @values line is rejected}\ + test parsekey_value_result_key_settled {settled (G-084): -parsekey on a value renames the values result key and the received key (no longer aborts parse)}\ + -setup $common -body { + set argd [punk::args::parse {hello} withdef @values {v1 -parsekey renamed -type string}] + lappend result [dict get $argd values] + lappend result [dict exists [dict get $argd received] renamed] + lappend result [dict exists [dict get $argd received] v1] + }\ + -cleanup { + punk::args::undefine [dict get $argd id] 1 + }\ + -result [list\ + {renamed hello} 1 0\ + ] + test parsekey_values_line_rejects_parsekey {settled (G-084): @values directive line rejects -parsekey (group-default parsekey is not a feature)}\ -setup $common -body { - set docid ::testspace::parsekey_values_leaders_GAP - set docids [list $docid] - - punk::args::define [list @id -id $docid] @values {v1 -parsekey renamed -type string} - set err [catch {punk::args::parse {hello} withid $docid} msg] - lappend result $err - set err [catch {punk::args::parse {hello} withdef {@values -parsekey renamed} {v1 -type string}} msg] lappend result $err lappend result [string match "*unrecognised key '-parsekey' in @values line*" $msg] - - set argd [punk::args::parse {hello there} withdef {@leaders -min 1 -max 1} {l1 -parsekey renamed -type string} @values {v1 -type string}] - lappend docids [dict get $argd id] - lappend result [dict get $argd leaders] - lappend result [dict get $argd received] }\ - -cleanup { - foreach id $docids { - punk::args::undefine $id 1 - } + -result [list\ + 1 1\ + ] + + test parsekey_leader_parsekey_rejected {settled (G-084): -parsekey on a leader is rejected at define time (no silent ignore, no parse-time abort)}\ + -setup $common -body { + set err [catch {punk::args::parse {hello there} withdef {@leaders -min 1 -max 1} {l1 -parsekey renamed -type string} @values {v1 -type string}} msg] + lappend result $err + lappend result [string match "*-parsekey is not supported on a leader argument*" $msg] }\ -result [list\ - 1\ 1 1\ - {l1 hello}\ - {l1 0 v1 1} ] } tcltest::cleanupTests ;#needed to produce test summary line. diff --git a/src/tests/modules/punk/args/testsuites/dev/parsekey-knownbugs.test b/src/tests/modules/punk/args/testsuites/dev/parsekey-knownbugs.test index 99f630e7..1a8c3407 100644 --- a/src/tests/modules/punk/args/testsuites/dev/parsekey-knownbugs.test +++ b/src/tests/modules/punk/args/testsuites/dev/parsekey-knownbugs.test @@ -4,6 +4,13 @@ package require tcltest #Desired-behaviour pins for known parsekey gaps. Each has a corresponding _GAP test in #testsuites/args/parsekey.test pinning today's behaviour; when a fix lands, enable the #test here and flip/remove the GAP pin there. +#G-084 (2026-08-08): value -parsekey support landed - the parsekey_value_result_key +#pin moved to testsuites/args/parsekey.test as parsekey_value_result_key_settled +#(now an enabled assertion). The leader case is settled as a define-time rejection +#(parsekey_leader_parsekey_rejected in parsekey.test) - no live caller, so -parsekey +#on a leader is rejected rather than silently ignored. The @values directive line +#still rejects -parsekey (parsekey_values_line_rejects_parsekey). The remaining open +#pin here is the cross-member -multiple accumulation design decision. namespace eval ::testspace { namespace import ::tcltest::* tcltest::testConstraint punkargsKnownBug 0 @@ -12,45 +19,6 @@ namespace eval ::testspace { set result "" } - test parsekey_value_result_key {Known bug: -parsekey on a value should rename the values result key (currently aborts parse)}\ - -constraints punkargsKnownBug\ - -setup $common -body { - set docids [list] - - set argd [punk::args::parse {hello} withdef @values {v1 -parsekey renamed -type string}] - lappend docids [dict get $argd id] - lappend result [dict get $argd values] - lappend result [dict exists [dict get $argd received] renamed] - lappend result [dict exists [dict get $argd received] v1] - }\ - -cleanup { - foreach id $docids { - punk::args::undefine $id 1 - } - }\ - -result [list\ - {renamed hello} 1 0 - ] - - test parsekey_leader_result_key {Known bug: -parsekey on a leader should rename the leaders result key (currently ignored)}\ - -constraints punkargsKnownBug\ - -setup $common -body { - set docids [list] - - set argd [punk::args::parse {hello there} withdef {@leaders -min 1 -max 1} {l1 -parsekey renamed -type string} @values {v1 -type string}] - lappend docids [dict get $argd id] - lappend result [dict get $argd leaders] - lappend result [dict exists [dict get $argd received] renamed] - }\ - -cleanup { - foreach id $docids { - punk::args::undefine $id 1 - } - }\ - -result [list\ - {renamed hello} 1 - ] - #Design-pending rather than settled: the lean is that -multiple 1 members sharing a #-parsekey accumulate under the shared key in received order (today only one member's #collected list survives - see parsekey_shared_key_multiple_collection_GAP). @@ -63,7 +31,7 @@ namespace eval ::testspace { lappend docids [dict get $argd id] lappend result [dict get $argd opts] - set argd [punk::args::parse {-add a -sub b -add c} withdef {@opts -type string -parsekey -op -multiple 1} -add -sub] + set argd [punk::args::parse {-add a -sub b -add c} withdef @opts {-add -type string -parsekey -op -multiple 1} {-sub -type string -parsekey -op -multiple 1}] lappend docids [dict get $argd id] lappend result [dict get $argd opts] }\ @@ -77,4 +45,4 @@ namespace eval ::testspace { {-op {a b c}} ] } -tcltest::cleanupTests ;#needed to produce test summary line. +tcltest::cleanupTests ;#needed to produce test summary line. \ No newline at end of file