Browse Source
Gap-fill from a coverage review of long-option and mashopt parsing: the module's comment blocks specify style distinctions the suite never pinned. All behaviour was live-probed before pinning (characterisation, not aspiration). New testsuites/args/longopts.test (6 tests): - mixed optset -f|--file|--file= accepts inline/spaced/short-alias forms incl unique-prefix abbreviation of the longopt itself (--fi=abc / --fi abc) - gnu-only --flag= splits at the first = only (--filename=a=b -> a=b), accepts an empty inline value, rejects spaced and solo usage (badoptionformat) - plain --flag accepts spaced only; inline =value is invalidoption - the two =-member resolve-time definition errors (-type none with an = member; optional ?type? member without one) surface at first parse - single-dash -flag=value is never split at the = (invalidoption) - @opts -any 1 adhoc lane: undefined --flag=value splits to --flag + value, but single-dash -zz=5 stays a WHOLE adhoc flagname expecting a following value mashopts.test additions (2 tests): a mash containing an undefined letter is rejected whole (invalidoption naming the mashable set); under -any 1 -mash 1 defined flags still mash while undefined mash-lookalike tokens (-xy, -az) are adhoc flags taking the next word as value - never mash attempts. Error-shape pins use the first three -errorcode elements (PUNKARGS VALIDATION + failclass) rather than the ANSI-rendered messages; the two resolve errors are pinned by distinctive message substrings (plain text, no ANSI). Verified: runtests -include-paths modules/punk/args/testsuites/args longopts.test mashopts.test via tclsh90s - 13/13 pass, no warnings. src/tests/modules/AGENTS.md punk/args index entry updated (DOX pass). Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.commaster
3 changed files with 210 additions and 1 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,163 @@
|
||||
|
||||
package require tcltest |
||||
package require punk::args |
||||
|
||||
#Longopt (--flag / --flag=value) parsing characterisation - added 2026-08-07 as part of |
||||
#the longopt/mash coverage gap review. The gnu-style inline-value lane (--flag=value with |
||||
#choices, prefixes and optional ?type? members) is covered in opts.test and synopsis.test; |
||||
#this file pins the style DISTINCTIONS the definition grammar draws: |
||||
# --flag= gnu style - inline =value only; spaced and solo usage rejected |
||||
# --flag plain style - spaced value only; inline =value rejected |
||||
# -f|--file|--file= mixed - both forms accepted (the fossil-style optset) |
||||
#plus the resolve-time definition errors for = members, the rule that single-dash |
||||
#-flag=value tokens are never split at the =, and adhoc (@opts -any 1) passthrough of |
||||
#=value tokens. |
||||
#Error-shape pins use the first three -errorcode elements: PUNKARGS VALIDATION followed |
||||
#by the failclass element, e.g. (badoptionformat --filename= index -1). |
||||
|
||||
namespace eval ::testspace { |
||||
namespace import ::tcltest::* |
||||
variable common { |
||||
set result "" |
||||
} |
||||
|
||||
#added 2026-08-07 (agent) - longopt/mash characterisation gap fill |
||||
test longopts_mixed_optset_forms {a mixed optset -f|--file|--file= accepts inline, spaced and short-alias forms - storing under the last alternative stripped of =}\ |
||||
-setup $common -body { |
||||
#gnu-style inline value |
||||
lappend result [dict get [punk::args::parse {--file=abc} withdef @opts {-f|--file|--file= -type string}] opts] |
||||
#spaced value via the longopt itself - allowed because the plain --file member is present |
||||
lappend result [dict get [punk::args::parse {--file abc} withdef @opts {-f|--file|--file= -type string}] opts] |
||||
#short alias spaced value |
||||
lappend result [dict get [punk::args::parse {-f abc} withdef @opts {-f|--file|--file= -type string}] opts] |
||||
#a unique prefix of the longopt works for both inline and spaced forms |
||||
lappend result [dict get [punk::args::parse {--fi=abc} withdef @opts {-f|--file|--file= -type string}] opts] |
||||
lappend result [dict get [punk::args::parse {--fi abc} withdef @opts {-f|--file|--file= -type string}] opts] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{--file abc}\ |
||||
{--file abc}\ |
||||
{--file abc}\ |
||||
{--file abc}\ |
||||
{--file abc}\ |
||||
] |
||||
|
||||
test longopts_gnu_only_forms {a gnu-only --flag= optset takes inline =value split at the first = only; spaced and solo usage are badoptionformat errors}\ |
||||
-setup $common -body { |
||||
#a value containing = - the split happens at the first = only |
||||
lappend result [dict get [punk::args::parse {--filename=a=b} withdef @opts {--filename= -type string}] opts] |
||||
#an empty inline value is accepted for -type string |
||||
lappend result [dict get [punk::args::parse {--filename=} withdef @opts {--filename= -type string}] opts] |
||||
#a spaced value is not allowed when only the = member exists |
||||
try { |
||||
punk::args::parse {--filename abc} withdef @opts {--filename= -type string} |
||||
lappend result "UNEXPECTED-accepted-spaced-value" |
||||
} on error {emsg eopts} { |
||||
lappend result [lrange [dict get $eopts -errorcode] 0 2] |
||||
} |
||||
#solo usage is not allowed either - the type is not an optional ?type? member |
||||
try { |
||||
punk::args::parse {--filename} withdef @opts {--filename= -type string} |
||||
lappend result "UNEXPECTED-accepted-solo" |
||||
} on error {emsg eopts} { |
||||
lappend result [lrange [dict get $eopts -errorcode] 0 2] |
||||
} |
||||
set result |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{--filename a=b}\ |
||||
{--filename {}}\ |
||||
{PUNKARGS VALIDATION {badoptionformat --filename= index -1}}\ |
||||
{PUNKARGS VALIDATION {badoptionformat --filename= index -1}}\ |
||||
] |
||||
|
||||
test longopts_plain_doubledash_forms {a plain --flag optset accepts a spaced value but rejects inline =value as invalidoption}\ |
||||
-setup $common -body { |
||||
lappend result [dict get [punk::args::parse {--filename abc} withdef @opts {--filename -type string}] opts] |
||||
try { |
||||
punk::args::parse {--filename=abc} withdef @opts {--filename -type string} |
||||
lappend result "UNEXPECTED-accepted-inline-value" |
||||
} on error {emsg eopts} { |
||||
lappend result [lrange [dict get $eopts -errorcode] 0 2] |
||||
} |
||||
set result |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{--filename abc}\ |
||||
{PUNKARGS VALIDATION {invalidoption --filename=abc options --filename}}\ |
||||
] |
||||
|
||||
test longopts_resolve_definition_errors {-type none with an = member and an optional ?type? without an = member are both resolve-time definition errors}\ |
||||
-setup $common -body { |
||||
#definitions are lazy - the resolve error surfaces at first parse |
||||
try { |
||||
punk::args::parse {--flag} withdef @opts {--flag= -type none} |
||||
lappend result "UNEXPECTED-resolved-none-with-eq" |
||||
} on error {emsg eopts} { |
||||
lappend result [string match "*flag type 'none'*not supported when any flag member ends with =*" $emsg] |
||||
} |
||||
try { |
||||
punk::args::parse {-f 3} withdef @opts {-f -type ?int?} |
||||
lappend result "UNEXPECTED-resolved-optional-without-eq" |
||||
} on error {emsg eopts} { |
||||
lappend result [string match "*Optional flag parameter*not supported when no flag member ends with =*" $emsg] |
||||
} |
||||
set result |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
1\ |
||||
1\ |
||||
] |
||||
|
||||
test longopts_singledash_no_inline_value {inline =value splitting applies only to double-dash longopts - a single-dash -flag=value token is not split}\ |
||||
-setup $common -body { |
||||
#-f=abc is not recognised as -f with value abc - the whole token fails as an unknown option |
||||
try { |
||||
punk::args::parse {-f=abc} withdef @opts {-f|--file= -type string} |
||||
lappend result "UNEXPECTED-accepted-singledash-eq" |
||||
} on error {emsg eopts} { |
||||
lappend result [lrange [dict get $eopts -errorcode] 0 2] |
||||
} |
||||
set result |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{PUNKARGS VALIDATION {invalidoption -f=abc options -f|--file=}}\ |
||||
] |
||||
|
||||
test longopts_adhoc_any_passthrough {@opts -any 1 splits an undefined --flag=value at the = but a single-dash -flag=value is a whole adhoc flagname expecting a value}\ |
||||
-setup $common -body { |
||||
#undefined --zz=5 splits to adhoc option --zz with value 5 - and is recorded as received |
||||
set argd [punk::args::parse {--zz=5} withdef {@opts -any 1} {-x -type int} {@values -min 0 -max 0}] |
||||
lappend result [dict get $argd opts] |
||||
lappend result [dict get $argd received] |
||||
#single-dash -zz=5 is NOT split - the whole token is the adhoc flagname and requires a following value |
||||
try { |
||||
punk::args::parse {-zz=5} withdef {@opts -any 1} {-x -type int} {@values -min 0 -max 0} |
||||
lappend result "UNEXPECTED-accepted-bare-singledash-eq" |
||||
} on error {emsg eopts} { |
||||
lappend result [lrange [dict get $eopts -errorcode] 0 2] |
||||
} |
||||
set argd [punk::args::parse {-zz=5 6} withdef {@opts -any 1} {-x -type int} {@values -min 0 -max 0}] |
||||
lappend result [dict get $argd opts] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{--zz 5}\ |
||||
{--zz 0}\ |
||||
{PUNKARGS VALIDATION {missingoptionvalue -zz=5 index 0}}\ |
||||
{-zz=5 6}\ |
||||
] |
||||
|
||||
} |
||||
tcltest::cleanupTests ;#needed to produce test summary line. |
||||
Loading…
Reference in new issue