Browse Source
Spec compiler (resolve) accepts and stores the new relation keys: - per-argument -conflicts <list>: parsekeys or flag names that must not be RECEIVED together with this argument; checked against received args only at parse time (defaults never conflict) - parse-time optionconflict enforcement lands in a later G-083 increment. - @opts-level -parsekeymode override|error: per named OPT_GROUPS group; error = distinct-member co-occurrence within a shared-parsekey group raises optionconflict at parse; override = legacy last-wins, the default. Both keys are cross-validated at resolve: -conflicts targets must name a defined argname or declared -parsekey; -parsekeymode requires -group and a value of override|error. Define-time integrity hole closed: a -parsekey colliding with a distinct defined argument's name (one not sharing that -parsekey) is now a resolve error instead of silently forming an implicit shared-key group (parsekey_collides_with_defined_optname_GAP flipped in parsekey.test to a define-time error pin, plus redundant-self-name and deliberate-shared-group guards). Design settled (recorded in the goal detail): -parsekeymode is per named group (OPT_GROUPS entry, keyed by the @opts line's -group), not form-wide. Rationale: lsearch uses per-arg -conflicts only (partial conflicts, groups stay default override); clock clicks uses -parsekeymode error on its single named group (full pairwise exclusivity). Unnamed shared-parsekey groups default override. Legacy untouched by default: -parsekeymode override is the default and absent -conflicts means no check runs; the pinned parsekey_repeat_ordering last-wins / prepend-defaults idiom and the full existing suite pass unchanged. New testsuite relations.test pins the define-time vocabulary (7 tests, matching the longopts.test/mashopts.test error-pin style). buildversion 0.18.0 -> 0.19.0. Verification: full punk/args suite via canonical tclsh90s - 356 total / 353 passed / 3 skipped (punkargsKnownBug) / 0 failed. goals_lint clean. No punkproject.toml bump (define-time only; no shell-level user-visible behaviour yet - the module change, not the shell product). Assisted-by: harness=pi; primary-model=huggingface/zai-org/GLM-5.2; api-location=huggingface.comaster
6 changed files with 243 additions and 20 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,116 @@ |
|||||||
|
package require tcltest |
||||||
|
package require punk::args |
||||||
|
|
||||||
|
#G-083 argument relations: -conflicts (per-arg) and -parsekeymode (per @opts group). |
||||||
|
#This file pins the DEFINE-TIME vocabulary (spec-compiler storage and validation); |
||||||
|
#the parse-time optionconflict enforcement is pinned in validation.test and |
||||||
|
#parsekey.test. Error-shape pins use the first three -errorcode elements where the |
||||||
|
#failure surfaces through parse, and distinctive message substrings for resolve-time |
||||||
|
#definition errors (plain text, no ANSI) - the longopts.test/mashopts.test convention. |
||||||
|
#Definitions are lazy: resolve errors surface at first parse, so the parse path is |
||||||
|
#used to trigger them and the message substring is pinned. |
||||||
|
|
||||||
|
namespace eval ::testspace { |
||||||
|
namespace import ::tcltest::* |
||||||
|
variable common { |
||||||
|
set result "" |
||||||
|
} |
||||||
|
|
||||||
|
#added 2026-08-07 (agent) - G-083 define-time vocabulary characterisation |
||||||
|
test parsekeymode_requires_group {@opts -parsekeymode without -group is a resolve error}\ |
||||||
|
-setup $common -body { |
||||||
|
try { |
||||||
|
punk::args::parse {} withdef {@opts -parsekeymode error} {-x -type none} |
||||||
|
lappend result "UNEXPECTED-accepted" |
||||||
|
} on error {emsg eopts} { |
||||||
|
lappend result [string match "*-parsekeymode requires -group*" $emsg] |
||||||
|
} |
||||||
|
set result |
||||||
|
}\ |
||||||
|
-result [list 1] |
||||||
|
|
||||||
|
test parsekeymode_bad_value {@opts -parsekeymode with a value other than override|error is a resolve error}\ |
||||||
|
-setup $common -body { |
||||||
|
try { |
||||||
|
punk::args::parse {} withdef {@opts -group G -parsekeymode strict} {-x -type none} |
||||||
|
lappend result "UNEXPECTED-accepted" |
||||||
|
} on error {emsg eopts} { |
||||||
|
lappend result [string match "*-parsekeymode must be 'override' or 'error'*" $emsg] |
||||||
|
} |
||||||
|
set result |
||||||
|
}\ |
||||||
|
-result [list 1] |
||||||
|
|
||||||
|
test parsekeymode_stored {@opts -parsekeymode error with -group is accepted and stored on the OPT_GROUPS entry}\ |
||||||
|
-setup $common -body { |
||||||
|
set argd [punk::args::parse {} withdef {@opts -group G -parsekeymode error} {-x -type none}] |
||||||
|
lappend docids [dict get $argd id] |
||||||
|
set spec [punk::args::get_spec [dict get $argd id]] |
||||||
|
dict for {fid fd} [dict get $spec FORMS] { |
||||||
|
if {[dict exists $fd OPT_GROUPS G -parsekeymode]} { |
||||||
|
lappend result [dict get $fd OPT_GROUPS G -parsekeymode] |
||||||
|
} |
||||||
|
} |
||||||
|
set result |
||||||
|
}\ |
||||||
|
-cleanup { |
||||||
|
foreach id $docids {punk::args::undefine $id 1} |
||||||
|
}\ |
||||||
|
-result [list error] |
||||||
|
|
||||||
|
test parsekeymode_default_override {an @opts group without -parsekeymode stores the default override}\ |
||||||
|
-setup $common -body { |
||||||
|
set argd [punk::args::parse {} withdef {@opts -group G} {-x -type none}] |
||||||
|
lappend docids [dict get $argd id] |
||||||
|
set spec [punk::args::get_spec [dict get $argd id]] |
||||||
|
dict for {fid fd} [dict get $spec FORMS] { |
||||||
|
if {[dict exists $fd OPT_GROUPS G -parsekeymode]} { |
||||||
|
lappend result [dict get $fd OPT_GROUPS G -parsekeymode] |
||||||
|
} |
||||||
|
} |
||||||
|
set result |
||||||
|
}\ |
||||||
|
-cleanup { |
||||||
|
foreach id $docids {punk::args::undefine $id 1} |
||||||
|
}\ |
||||||
|
-result [list override] |
||||||
|
|
||||||
|
test conflicts_unknown_target {per-arg -conflicts naming an unknown flag/parsekey is a resolve error}\ |
||||||
|
-setup $common -body { |
||||||
|
try { |
||||||
|
punk::args::parse {} withdef @opts {-x -type none -conflicts -nope} |
||||||
|
lappend result "UNEXPECTED-accepted" |
||||||
|
} on error {emsg eopts} { |
||||||
|
lappend result [string match "*-conflicts for argument '-x' names '-nope' which is neither a defined argument name nor a declared -parsekey*" $emsg] |
||||||
|
} |
||||||
|
set result |
||||||
|
}\ |
||||||
|
-result [list 1] |
||||||
|
|
||||||
|
test conflicts_valid_flagname_target {per-arg -conflicts naming a defined flag is accepted at define time}\ |
||||||
|
-setup $common -body { |
||||||
|
#accepted at define time (parse-time enforcement lands in a later G-083 increment) |
||||||
|
set argd [punk::args::parse {-x -y} withdef @opts {-x -type none -conflicts -y} {-y -type none}] |
||||||
|
lappend docids [dict get $argd id] |
||||||
|
lappend result ok |
||||||
|
set result |
||||||
|
}\ |
||||||
|
-cleanup { |
||||||
|
foreach id $docids {punk::args::undefine $id 1} |
||||||
|
}\ |
||||||
|
-result [list ok] |
||||||
|
|
||||||
|
test conflicts_valid_parsekey_target {per-arg -conflicts naming a declared -parsekey is accepted at define time}\ |
||||||
|
-setup $common -body { |
||||||
|
set argd [punk::args::parse {-x -y} withdef @opts {-x -type none -parsekey -pk -conflicts -y} {-y -type none -parsekey -pk}] |
||||||
|
lappend docids [dict get $argd id] |
||||||
|
lappend result ok |
||||||
|
set result |
||||||
|
}\ |
||||||
|
-cleanup { |
||||||
|
foreach id $docids {punk::args::undefine $id 1} |
||||||
|
}\ |
||||||
|
-result [list ok] |
||||||
|
|
||||||
|
} |
||||||
|
tcltest::cleanupTests ;#needed to produce test summary line. |
||||||
Loading…
Reference in new issue