From 127ad3687730497b4629aba06ccbe83d285a5bb1 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Wed, 5 Aug 2026 17:22:02 +1000 Subject: [PATCH] punk::args pre-tests for the G-168/G-169/G-170 family + G-170<->G-151 cross-links Engine-primitive pins landed green ahead of activating the launcher definition/documentation/help-depth goals (args subtree 321 tests: 318 pass, 3 known-bug skips, 0 fail on native tclsh90 9.0.3 and tcl87): - forms.test: candidacy fencing/fall-through group (G-168 pre-work) - regexprefail-fenced autoselection fixture pin (args-tier twin of the G-150 cmdhelp.test pins), a -regexprefail rejection on an OPTIONAL value slot is form-fatal with NO re-landing on later permissive slots (contrast optional-choice rejection), unknown-option / dangling-option / option-satisfied-but-values-unmet failures fall through to sibling forms as successful parses, option-VALUE fences participate in candidacy, and the documented per-word limit: a complete '-encoding ' line double-matches a fenced scriptfile form vs a dash-tolerant stdin form (stock's scriptfile-wins-when-complete priority is not expressible per-word - multipleformmatches per the G-041 contract). - formviability.test: dangling-OPTIONAL-option verdict conservatism - the G-164 re-probe re-seats only words naming missing REQUIRED options, so the tclsh '-encoding' dangling class stays invalid although completable. - errorselection.test: a dangling optional value-taking option word seats as a trailing value and overflows a zero-value window as plain toomanyarguments (no -badarg attribution). - formcheck.test: GAP before-state pins of the punkexe launch definitions - unsanctioned structural overlaps for script file/oneliner and tclsh scriptfile/stdin, and every primary launch line double-matching; flipped deliberately when G-168 lands per its Acceptance. - docpackages.test (new): registered-namespace lazy definition loading characterization (G-169 pre-work) - inert registration with scan-vs-load split, id_exists never triggers loading, real_id/usage lazily resolve tag-prefixed script-level id families, duplicate definers resolve last-loaded-wins with only a stderr undefine notice (the engine's recorded detect-duplicates todo), degraded paths clean. Goal notes (non-contract): pre-test findings recorded in G-168 (including the '-e'-must-be-a-required-flag declaration constraint and the residual-overlap design limit) and G-169 (handover must withdraw the moduledoc entry in the same change-set - no duplicate warning surface exists); G-170<->G-151 Related: cross-links added both ways - the dry-run arm renders G-151's punk::args-level landing report, a link the path-based overlap surveys could not surface. src/tests/modules AGENTS.md punk/args bullet extended for the new coverage. goals_lint clean (84 active, 86 archived). Claude-Session: https://claude.ai/code/session_0156PuejSCGjgeGb7jiABrDU Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com --- ...G-151-punkargs-annotated-success-render.md | 3 + ...-168-selection-sound-launch-definitions.md | 37 +++ goals/G-169-app-owned-launch-docs.md | 12 + goals/G-170-launcher-help-depth.md | 6 + src/tests/modules/AGENTS.md | 2 +- .../args/testsuites/args/docpackages.test | 176 +++++++++++++++ .../args/testsuites/args/errorselection.test | 27 +++ .../punk/args/testsuites/args/formcheck.test | 66 ++++++ .../punk/args/testsuites/args/forms.test | 212 ++++++++++++++++++ .../args/testsuites/args/formviability.test | 29 +++ 10 files changed, 569 insertions(+), 1 deletion(-) create mode 100644 src/tests/modules/punk/args/testsuites/args/docpackages.test diff --git a/goals/G-151-punkargs-annotated-success-render.md b/goals/G-151-punkargs-annotated-success-render.md index e84e1401..670c10bf 100644 --- a/goals/G-151-punkargs-annotated-success-render.md +++ b/goals/G-151-punkargs-annotated-success-render.md @@ -13,5 +13,8 @@ From the G-143 dry-run help sessions (2026-08-01/02 - archived detail file): the - Related: G-050 (proposed) - synopsis argument-validity marking; the nearest sibling (status-aware marking over the same argument rows). - Related: G-044 (proposed) - punk::args-driven completion; landing attribution is the same data completion hints from. +- Related: G-170 (proposed) - consumer: its launcher help dry-run arm renders + this goal's landing report for accepted lines (back-pointer added 2026-08-05 + with G-170's cross-link; sequencing lean recorded there). - Related: G-149 (achieved 2026-08-05 - goals/archive/G-149-punkargs-formhint-labels.md) - configurable form-hint labels; sibling punk::args render-surface goal drafted the same session. Its landed shape (caller option on arg_error/usage + @cmd definition key, caller > definition > default precedence) is the plumbing precedent for adding a render option to the same surfaces. - Overlap survey 2026-08-02: goals_xref paths src/modules/punk/args-999999.0a1.0.tm - the punkargs family as recorded in G-149's survey; G-050 (named above) is the closest surface, the remainder are parser/spec mechanisms. diff --git a/goals/G-168-selection-sound-launch-definitions.md b/goals/G-168-selection-sound-launch-definitions.md index 9e7e4636..26a2d3cd 100644 --- a/goals/G-168-selection-sound-launch-definitions.md +++ b/goals/G-168-selection-sound-launch-definitions.md @@ -34,3 +34,40 @@ declaration is the dash-led-form-word precedent). above) is the only substantive live intersection on punkboot_core.tcl; moduledoc-sibling matches (G-055 G-068 G-072 G-083 G-084 G-166 G-167) are parser/spec mechanisms or other moduledocs, no availability interaction. +- 2026-08-05 pre-test findings (engine primitives probed on tclsh 9.0.3 and + pinned green on tclsh90+tcl87: forms.test fencing/fall-through group, + formviability_dangling_optional_option_conservative, + errsel_dangling_option_trailing_value_overflow, formcheck_GAP_punkexe_*): + - A -regexprefail rejection on an OPTIONAL value slot is fatal to that form's + candidacy - the word does NOT re-land on a later permissive slot (contrast + optional-CHOICE rejection, errsel_rejected_word_lands_later) - so per-slot + fencing fences whole forms (forms_fence_optional_slot_no_reland). + - Unknown options, dangling option words and option-satisfied-but-values-unmet + tails each fail only their form: auto-selection falls through to a sibling + as a SUCCESSFUL parse, and an option VALUE's -regexprefail participates in + candidacy too - the stock "-encoding recognised only before a non-dash word" + primitive is expressible (forms_fallthrough_* pins). + - LIMIT for the tclsh model: a complete '-encoding ' line + wordwise-matches both a fenced scriptfile form and any dash-tolerant stdin + form; per-word constraints cannot express stock's "scriptfile wins when + complete" priority, so the no-silent-preference contract (G-041, achieved - + goals/archive/G-041-punkargs-form-matching.md) raises + multipleformmatches (forms_fenced_pair_residual_overlap). The tclsh + declaration must design around this class - form restructuring, an + -overlapallowed sanction (display-only; parse still raises), or the + dispatch-parse decision this goal already tables. + - The {-e}-alone viable-incomplete acceptance rides the re-probe of G-164 + (achieved 2026-08-04 - goals/archive/G-164-viability-alternative-allocation.md), + which re-seats only words naming missing REQUIRED options: declare '-e' as a + required -type none flag plus a separate required script value (the + auto_exec::hash -r/-d shape), NOT as a value-taking option - a stolen word + naming a value-taking or OPTIONAL option keeps verdict invalid + (formviability_reprobe_value_taking_option_conservative and the new + formviability_dangling_optional_option_conservative twin). + - Before-state pinned as GAP: both punkexe multi-form definitions carry + unsanctioned structural formcheck findings and every primary launch line + double-matches ({-e X} / {file args} / {file} / {-encoding enc file}); + empty input selects file/stdin uniquely (formcheck_GAP_punkexe_script_forms_overlap, + formcheck_GAP_punkexe_tclsh_forms_overlap, + formcheck_GAP_punkexe_primary_inputs_double_match - flipped deliberately + by this goal per its Acceptance). diff --git a/goals/G-169-app-owned-launch-docs.md b/goals/G-169-app-owned-launch-docs.md index 1fc796fc..78f7941d 100644 --- a/goals/G-169-app-owned-launch-docs.md +++ b/goals/G-169-app-owned-launch-docs.md @@ -26,3 +26,15 @@ core-owned (tclcore precedent: separation + parity pins). - Overlap survey 2026-08-05: app-punkscript intersects no live goals; the punkboot_core.tcl matches are the G-168 survey's set (G-089 nearest, others incidental). +- 2026-08-05 pre-test pins (new args-tier docpackages.test, green tclsh90+tcl87): + registration in ::punk::args::register::NAMESPACES is inert - the next + update_definitions call id-SCANS the source without evaluating definitions, + id_exists never triggers loading, and real_id/usage lazily load the + registered source on the first query naming the documented namespace, + including tag-prefixed script-level families (the (script)::punkexe::* path + this goal rides). Duplicate ids resolve last-loaded-wins accompanied only by + a stderr undefine notice - the engine carries a recorded detect-duplicates + todo (punk::args update_definitions scan loop) - so the single-definer + handover must withdraw the moduledoc entry in the same change-set that lands + app ownership rather than rely on any warning surface + (docpackages_duplicate_definers_last_registered_wins). diff --git a/goals/G-170-launcher-help-depth.md b/goals/G-170-launcher-help-depth.md index 809bfe7c..2921360d 100644 --- a/goals/G-170-launcher-help-depth.md +++ b/goals/G-170-launcher-help-depth.md @@ -16,6 +16,12 @@ builds the destination, then points at it. - Depends on G-168 (proposed) for the dry-run arm (narrowing arm is standalone). - Related: G-169 (proposed) - the defs' home determines where templates land. +- Related: G-151 (proposed) - the dry-run arm's received-args report is G-151's + punk::args-level annotated success render; landing G-151 first lets the + launcher consume it instead of hand-assembling a second interim + make.tcl-style one-liner (which G-151 already earmarks for retirement). + Cross-link added 2026-08-05: the drafting overlap survey was path-based and + could not surface G-151's args-module scope. - Related: G-044 (proposed) - completion later consumes the same candidacy data. - Archived precedents: G-143 (achieved - goals/archive/G-143-maketcl-multiform-help.md, make.tcl dry-run), G-150 (achieved - goals/archive/G-150-cmdhelp-form-narrowing.md, diff --git a/src/tests/modules/AGENTS.md b/src/tests/modules/AGENTS.md index 39681e01..9dccefa6 100644 --- a/src/tests/modules/AGENTS.md +++ b/src/tests/modules/AGENTS.md @@ -43,7 +43,7 @@ Unit tests for editable source modules under `src/modules/`, `src/modules_tcl8/` - `commandstack/` — commandstack (cooperative command renaming) tests (`testsuites/commandstack/commandstack.test`, 2026-08-03 - characterisation suite + the G-160 hygiene-pass contract at commandstack 0.6.0 + the 0.7.0 convenience removal forms + the 0.7.1 reload-contract state guards (G-160 follow-ons, 2026-08-04); usage-driven from punk::packagepreference/packagetrace/packagesuppress/punk-auto_execok/punk::nav::fs-cd): record shape as a contract (token first/renamer second dict key order for the lsearch -index 1/-index 3 convention, trailing `did_rename` 0|1, `{implementation {} did_rename 0}` no-rename signal), COMMANDSTACKNEXT/COMMANDSTACKNEXT_ORIGINAL delegation + the `commandstack::next` helper (caller-context parity with the manual uplevel convention pinned), unique+monotonic per-(renamer,command) tokenids (same-renamer re-renames chain and are removable by exact token, third rename succeeds), multi-renamer stacking with removal in any order (bottom-removal re-linking), builtin renames (next_implementor `original`), remove_rename's three argument forms + unknown-renamer errors, the 0.7.0 convenience removal forms (pop_rename - command form pops topmost-for-renamer and returns the removed record, bare form searches live stacks with a multi-command ambiguity error; remove_renamer - all of a renamer's entries across live stacks with correct re-linking, Rename_stack-parked stacks skipped, removed records returned keyed by command; restore_original - whole-stack unwind to the original returning the record count, deliberately registering stack-evidenced renamers so it survives a known_renamers reset while the renamer-explicit forms keep the gate; all silent with debug off), the token->implementation map get_next_command resolves through (map/stack consistency pinned across rename/remove; parked stacks keep dispatching), channel discipline (silent full cycle with debug off; warnings only under debug), debug argument validation, -renamer misplacement errors, get_stack raw-key-first retrieval of Rename_stack-parked records + Rename_stack 1/0 returns, Delete_stack live-record guard (errors; empty/missing return 1), get_IMPLEMENTOR classification incl builtin-where-cmdtype-exists (dynamic expectation - undetermined on 8.6), lib::split_body round-trip, lib::splitx, show_stack fallback render, the reload contract (a module re-source preserves ALL state - stacks/token counters/token map/known_renamers/debug each info-exists guarded; delegation and exact-token removal keep working after reload), the help overview, and lazy punk::args registration of the PUNKARGS docs. Behavioural tests run in fresh child interps per test (module sourced by path relative to the test file; a ::puts shim captures module output for silence/warning assertions and keeps runner output clean); descriptions are single-line per the tcltestrun banner-parsing style guidance in src/tests/AGENTS.md (a hard contract until G-161 made the parser multi-line tolerant). Green on tclsh90 (9.0.3) and punk86 (8.6) - `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), grepstr characterization (`grepstr.test`: return modes incl summarydict (linemap pinned as always-present - the -help says -n-only, reconciliation deferred to the planned hygiene pass), exact highlight SGR wrapping, -n line numbering, invert + empty-highlight strip, -C context/breaks, capture groups, and the tab deficiency: warns once per call on stderr, single-pass tab line survives - the multi-pass mangling is pinned at consumer level in punk/ns corp.test), and untabify characterization (`untabify.test`: -stops int/list/terminal, -with spaces/unicode/custom-pair, multiline, errors, plus the EXPERIMENTAL -plastic elastic-tabstop mode deliberately pinned-as-interim and retained for possible repl editbuf use). Console queries (get_tabstops/get_size + punk::console::tabwidth) are mocked per the overtype renderline.test pattern - they emit live terminal queries that block/error headless. 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, synopsis display characterization (`synopsis.test`: basic italic argname/`` styling, longopt `--x=` alias forms, literal/literalprefix/stringstartswith/stringendswith type-alternates rendering unitalicised, option alternate parenthesization, multi-element clause display incl `?type?` members and argname tail-word hints, `-typesynopsis` value-element lists and option passthrough incl documenter ANSI, and the small-restricted-choice-set literal rule: 1-3 restricted choices render as unitalicised `|`-joined literals in leader/option/value positions with choicegroups counted, >3 or `-choicerestricted 0` falling back to italics, `-typesynopsis` taking precedence), 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), 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), -parsekey characterization (`parsekey.test`: result/received/solos/multis keying, shared-key required satisfaction and defaults, mash-path and prefix-abbreviation keying, plus GAP pins for last-defined-member default precedence, cross-member -multiple value loss, parsekey/optname collision conflation, and values/leaders parsekey breakage - desired-behaviour pins disabled behind punkargsKnownBug in `testsuites/dev/parsekey-knownbugs.test`), and tclcore doc/interpreter behavioural parity (`tclcoreparity.test`, G-054, gated on have_tclcoredocs: 'string is' class choices equal the live-harvested set, per-class docids exist, error-vs-ok agreement across the probe matrix, version-note labels conditional on class presence - expectations derived from the running interpreter, green on 8.6/8.7/9.0; under 8.6 run the file directly via a plain tclkit + tcltest driver since runtests' harness needs newer infrastructure) +- `punk/args/` — punk::args tests (`testsuites/args/`): parsing, choices/choicegroups, forms (incl the 2026-08-05 candidacy fencing/fall-through primitive pins ahead of G-168: -regexprefail on optional value slots is form-fatal with no re-landing, unknown-option/dangling-option/option-value-fence failures fall through to sibling forms as successful parses, and the documented per-word limit - a complete option+value+file line double-matches a fenced scriptfile form and a dash-tolerant stdin form), registered-namespace lazy definition loading (`docpackages.test`, G-169 pre-work: inert registration/scan-vs-load split, id_exists never triggers loading, real_id/usage lazy resolution incl tag-prefixed script-level id families, duplicate-definer last-loaded-wins characterization), rendering/indentation characterization, synopsis display characterization (`synopsis.test`: basic italic argname/`` styling, longopt `--x=` alias forms, literal/literalprefix/stringstartswith/stringendswith type-alternates rendering unitalicised, option alternate parenthesization, multi-element clause display incl `?type?` members and argname tail-word hints, `-typesynopsis` value-element lists and option passthrough incl documenter ANSI, and the small-restricted-choice-set literal rule: 1-3 restricted choices render as unitalicised `|`-joined literals in leader/option/value positions with choicegroups counted, >3 or `-choicerestricted 0` falling back to italics, `-typesynopsis` taking precedence), 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), 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), -parsekey characterization (`parsekey.test`: result/received/solos/multis keying, shared-key required satisfaction and defaults, mash-path and prefix-abbreviation keying, plus GAP pins for last-defined-member default precedence, cross-member -multiple value loss, parsekey/optname collision conflation, and values/leaders parsekey breakage - desired-behaviour pins disabled behind punkargsKnownBug in `testsuites/dev/parsekey-knownbugs.test`), and tclcore doc/interpreter behavioural parity (`tclcoreparity.test`, G-054, gated on have_tclcoredocs: 'string is' class choices equal the live-harvested set, per-class docids exist, error-vs-ok agreement across the probe matrix, version-note labels conditional on class presence - expectations derived from the running interpreter, green on 8.6/8.7/9.0; under 8.6 run the file directly via a plain tclkit + tcltest driver since runtests' harness needs newer infrastructure) - `punk/nav/ns/` — punk::nav::ns tests (`testsuites/nav/navns.test`): the n/ n// n/// navigation state machine (ns/ transitions absolute/relative/glob-no-nav, failed-nav state preservation, quad-colon normalization, v-form content selection, ensemble annotation) and the ::punk::nav::ns::ns_current variable contract the repl/codethread/subshell seeding all consume; display content is covered in punk/ns nslist.test - `punk/repl/` — punk::repl tests (`testsuites/repl/`): opunk console backend integration (`consolebackends.test`) and repl current-namespace retention (`nscurrent.test`: real codethread via repl::init driven by synchronous runscript sends - inscope evaluation of ns_current, retention across submissions, n/-navigation retained, auto-create-with-notice for missing namespaces, the 2026-07-14 stray-namespace seeding fix pinned behaviourally plus a source-text guard on repl::start's inline template; the end-to-end piped subshell session is covered at shell level by shell/testsuites/punkexe/shellnavns.test - which found the first-subshell shared-code-interp asymmetry and the piped-inscope gap recorded there) - `punk/ns/` — punk::ns tests (`testsuites/ns/`): cmdwhich/cmdinfo/cmd_traverse doc-lookup flow (`cmdflow.test`, G-040 parity), n/ display machinery characterization (`nslist.test`: tier A get_ns_dicts classification buckets as the machine contract for display reworks - incl package tail/prefix derivation, alias edge cases, usageinfo scan-dependence; tier B per-element layout-agnostic marking - underline/underdouble/underdotted namespace package styles, command type tag colours, exported/imported markers, the punkargs doc icon; tier C REWORK-flagged pins of the current hardcoded 2-col/4-col layout and nspath subtables, to flip deliberately with the planned punk-tables/width-responsive rework; plus the flipped nslist_types_default pin - bare nslist without -types displays all member types since punk::ns 0.7.1 fixed the braced-literal -types default), corp proc-retrieval and syntax/untabify interplay (`corp.test`: name edge cases, -ranges/-n line handling, basic-highlight ansistrip equivalence, -untabify spaces/unicode tab-free output, the KNOWN-DEFICIENCY pin for default -untabify none on tabbed bodies - grepstr warns per pass and brace overlays mangle tabbed lines, deterministic under mocked console tabstops - and a ::tcl::CopyDirectory -untabify spaces smoke test; precursor coverage for the planned punk::ns hygiene pass), cmdtrace characterization (`cmdtrace.test`: -pause 0 non-interactive runs, linedict line-mark keys for flat and 2-word-form nested switches as correct-mark guards, and GAP pins for the upstream nested-switch mismark - core.tcl-lang.org tktview 5d5b1052280c976ea3d4, arm bodies whose split-list index lands on a literal switch-command word report container-relative lines; mark tests gated on have_tclcoredocs because cmdtrace's arm-offset correction parses against the ::switch argdoc; plus the fixed-canary asserting punk::lib::check::has_tclbug_nestedswitch_tracelines still reports the bug - a live behavioural probe, so a fixed Tcl release fails the canary first and triggers the documented flip workflow), 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); plus the G-150 flag-led form-narrowing + selection-soundness pins against punk::auto_exec::hash - bare-parse selection contract (incl the dash-led-name noformmatch cost), no-word/unknown-flag/ambiguity whole-render fallbacks, and hash runtime-behaviour-unchanged), and name/path primitive characterization (`nsprimitives.test`: string pins for nsparts/nsprefix/nstail/nsjoin/nsjoinall incl weird colon-run (`:::`) splitting, the trailing-colon parse ambiguity (`::x:` + `y` joins to the same string as `::x` + `:y` and reparses leading-colon-greedy), and prefix/tail/join round-trip and its absolutizing exceptions (the original twin-divergence pins for nsparts1/nsprefix1/nsprefix_orig/nstail1/nstail_orig served as safe-deletion evidence and were removed with the twins in the punk::ns 0.7.0 hygiene pass - divergence record in this file's git history, commit 0c7168a1); plus nseval fq-requirement/create-on-eval/evaluator-proc caching, the native-vs-punk `p:::x` resolution divergence (native namespace eval reaches child `x`, nseval creates/reaches literal `:x`), nseval_ifexists no-create + error propagation on plain and genuinely weird namespaces, nsexists/nschildren/nstree_raw weird-ns and relative-resolution pins, globmatchns `*`/`**`/`?` semantics (incl `*` matching a single inner colon - a formerly stale 'should be fixed' comment above nsglob_as_re was corrected in the 0.7.0 hygiene pass), and nspath_to_absolute/nspath_here_absolute caller-resolution pins; the nsjoinall error-message wart pin ('nsjoin:' prefix) flipped when punk::ns 0.7.1 fixed it) diff --git a/src/tests/modules/punk/args/testsuites/args/docpackages.test b/src/tests/modules/punk/args/testsuites/args/docpackages.test new file mode 100644 index 00000000..bc87cafb --- /dev/null +++ b/src/tests/modules/punk/args/testsuites/args/docpackages.test @@ -0,0 +1,176 @@ +package require tcltest + +package require punk::args + +#Registered-namespace lazy definition loading (docpackages) characterization - +#added 2026-08-05 ahead of G-169 (app-owned launch-subcommand documentation). +#A package registers its namespace in ::punk::args::register::NAMESPACES; its +#PUNKARGS definition lists are then id-SCANNED (cheap, no definition evaluation) +#on the next punk::args::update_definitions call, and definitions documenting a +#namespace other than the registering one (moduledoc pattern, including +#tag-prefixed script-level families like (script)::punkexe::*) are recorded in +#namespace_docpackages and LOADED only when a query names the documented +#namespace - punk::args::real_id (and the usage/get_spec surfaces that route +#through it) trigger the load; punk::args::id_exists never does (documented). +#Duplicate ids: the engine carries a recorded todo ("detect duplicate ids - +#last will silently win.. should be reported somewhere") - the last-loaded +#definition wins, and a redefinition of an id that is already loaded emits a +#stderr notice from punk::args::undefine ("clearing existing data for id:..."). +#These pins record the mechanism G-169's app-owned registration + single-definer +#handover leans on. +#Fixture registrations mutate the punk::args register state; runtests gives each +#test file its own testinterp/child, so the registrations stay contained here +#(ids are undefined in cleanup; namespace registrations are left in place). + +namespace eval ::testspace { + namespace import ::tcltest::* + variable common { + set result "" + } + + #added 2026-08-05 (agent, G-169) + test docpackages_registration_inert {registering a doc-source namespace is inert: an unrelated update_definitions call scans it (id inventory) but loads no definitions and the id stays invisible to id_exists}\ + -setup $common -body { + namespace eval ::testspace::dpsrc1 { + variable PUNKARGS + lappend PUNKARGS [list { + @id -id (testdoc)::dpexe::sub + @cmd -name "dpexe sub" -summary "DOCPKG-SRC1" + @values -min 0 -max 0 + }] + } + lappend result [punk::args::id_exists (testdoc)::dpexe::sub] + lappend ::punk::args::register::NAMESPACES ::testspace::dpsrc1 + lappend result [punk::args::id_exists (testdoc)::dpexe::sub] + punk::args::update_definitions [list ::testspace::dp_unrelated] + lappend result [expr {"::testspace::dpsrc1" in $::punk::args::register::scanned_packages}] + lappend result [expr {"::testspace::dpsrc1" in $::punk::args::register::loaded_packages}] + lappend result [punk::args::id_exists (testdoc)::dpexe::sub] + }\ + -cleanup { + }\ + -result [list 0 0 1 0 0] + + #added 2026-08-05 (agent, G-169) + test docpackages_query_triggers_load {a real_id query for the documented (tag-prefixed) id family loads the registered doc-source's definitions and the id becomes resolvable}\ + -setup $common -body { + lappend result [punk::args::real_id (testdoc)::dpexe::sub] + lappend result [expr {"::testspace::dpsrc1" in $::punk::args::register::loaded_packages}] + lappend result [punk::args::id_exists (testdoc)::dpexe::sub] + lappend result [string match "*DOCPKG-SRC1*" [punk::args::usage (testdoc)::dpexe::sub]] + }\ + -cleanup { + punk::args::undefine (testdoc)::dpexe::sub 1 + }\ + -result [list (testdoc)::dpexe::sub 1 1 1] + + #added 2026-08-05 (agent, G-169) + test docpackages_usage_alone_resolves {punk::args::usage on a never-loaded registered id resolves it lazily with no prior update_definitions call}\ + -setup $common -body { + namespace eval ::testspace::dpsrc2 { + variable PUNKARGS + lappend PUNKARGS [list { + @id -id (testdoc)::dpexe::uonly + @cmd -name "dpexe uonly" -summary "DOCPKG-UONLY" + @values -min 0 -max 0 + }] + } + lappend ::punk::args::register::NAMESPACES ::testspace::dpsrc2 + lappend result [punk::args::id_exists (testdoc)::dpexe::uonly] + lappend result [string match "*DOCPKG-UONLY*" [punk::args::usage (testdoc)::dpexe::uonly]] + lappend result [punk::args::id_exists (testdoc)::dpexe::uonly] + }\ + -cleanup { + punk::args::undefine (testdoc)::dpexe::uonly 1 + }\ + -result [list 0 1 1] + + #added 2026-08-05 (agent, G-169) - the engine's recorded duplicate-id todo in + #action: two registered sources defining the SAME id resolve to the + #last-registered source's definition (load order follows registration order; + #the overwriting define emits the stderr 'clearing existing data' notice). + #This is the class G-169's single-definer handover must not walk through + #blind - the moduledoc entry is withdrawn when app-ownership lands. + test docpackages_duplicate_definers_last_registered_wins {two registered doc-sources defining the same id: the last-registered source's definition wins}\ + -setup $common -body { + namespace eval ::testspace::dpsrc3 { + variable PUNKARGS + lappend PUNKARGS [list { + @id -id (testdoc)::dpexe::dup + @cmd -name "dpexe dup" -summary "DUP-FROM-EARLIER" + @values -min 0 -max 0 + }] + } + namespace eval ::testspace::dpsrc4 { + variable PUNKARGS + lappend PUNKARGS [list { + @id -id (testdoc)::dpexe::dup + @cmd -name "dpexe dup" -summary "DUP-FROM-LATER" + @values -min 0 -max 0 + }] + } + lappend ::punk::args::register::NAMESPACES ::testspace::dpsrc3 ::testspace::dpsrc4 + set u [punk::args::usage (testdoc)::dpexe::dup] + lappend result [string match "*DUP-FROM-LATER*" $u] + lappend result [string match "*DUP-FROM-EARLIER*" $u] + }\ + -cleanup { + }\ + -result [list 1 0] + + #added 2026-08-05 (agent, G-169) + test docpackages_explicit_define_supersedes_loaded {an explicit punk::args::define of an id already loaded from a registered source replaces that definition}\ + -setup $common -body { + punk::args::define { + @id -id (testdoc)::dpexe::dup + @cmd -name "dpexe dup" -summary "DUP-EXPLICIT" + @values -min 0 -max 0 + } + lappend result [string match "*DUP-EXPLICIT*" [punk::args::usage (testdoc)::dpexe::dup]] + }\ + -cleanup { + punk::args::undefine (testdoc)::dpexe::dup 1 + }\ + -result [list 1] + + #added 2026-08-05 (agent, G-169) + test docpackages_same_source_duplicate_last_entry_wins {two PUNKARGS entries in ONE registered source with the same id: the later entry wins}\ + -setup $common -body { + namespace eval ::testspace::dpsrc5 { + variable PUNKARGS + lappend PUNKARGS [list { + @id -id (testdoc)::dpexe::samesrc + @cmd -name "dpexe samesrc" -summary "SAMESRC-FIRST" + @values -min 0 -max 0 + }] + lappend PUNKARGS [list { + @id -id (testdoc)::dpexe::samesrc + @cmd -name "dpexe samesrc" -summary "SAMESRC-SECOND" + @values -min 0 -max 0 + }] + } + lappend ::punk::args::register::NAMESPACES ::testspace::dpsrc5 + set u [punk::args::usage (testdoc)::dpexe::samesrc] + lappend result [string match "*SAMESRC-SECOND*" $u] + lappend result [string match "*SAMESRC-FIRST*" $u] + }\ + -cleanup { + punk::args::undefine (testdoc)::dpexe::samesrc 1 + }\ + -result [list 1 0] + + #added 2026-08-05 (agent, G-169) + test docpackages_degraded_paths {a registered namespace with no PUNKARGS variable is tolerated by the scan, and an id no source documents resolves to empty (real_id) with usage raising cleanly}\ + -setup $common -body { + namespace eval ::testspace::dpempty {} + lappend ::punk::args::register::NAMESPACES ::testspace::dpempty + lappend result [catch {punk::args::update_definitions [list ::testspace::dp_unrelated2]}] + lappend result [expr {"::testspace::dpempty" in $::punk::args::register::scanned_packages}] + lappend result [punk::args::real_id (testdoc)::dpexe::nosuch] + lappend result [catch {punk::args::usage (testdoc)::dpexe::nosuch}] + }\ + -cleanup { + }\ + -result [list 0 1 {} 1] +} +tcltest::cleanupTests ;#needed to produce test summary line. diff --git a/src/tests/modules/punk/args/testsuites/args/errorselection.test b/src/tests/modules/punk/args/testsuites/args/errorselection.test index 00b560dc..d6912ddd 100644 --- a/src/tests/modules/punk/args/testsuites/args/errorselection.test +++ b/src/tests/modules/punk/args/testsuites/args/errorselection.test @@ -254,5 +254,32 @@ namespace eval ::testspace { -result [list\ 1 {PUNKARGS VALIDATION} noformmatch ] + + #added 2026-08-05 (agent, G-168) - a dangling word naming an OPTIONAL value-taking + #option at end-of-input seats as a trailing VALUE (the trailing-flag-as-value + #rule), so against a zero-value window it reports plain toomanyarguments with no + #-badarg/-badval attribution - not a pointed missing-option-value error. The + #tclsh '-encoding' launch model rides this class (a candidate for pointed-error + #treatment in the G-082 style if a consumer ever needs the attribution). + test errsel_dangling_option_trailing_value_overflow {A dangling optional value-taking option word seats as a trailing value and overflows a zero-value window as plain toomanyarguments}\ + -setup $common -body { + set docid ::testspace::errsel_dangling + set docids [list $docid] + punk::args::define [list @id -id $docid] {@opts} {-encoding -type string -optional 1} {@values -min 0 -max 0} + + set err [catch {punk::args::parse {-encoding} withid $docid} msg opts] + lappend result $err {*}[ecinfo $opts] + #with the option's value supplied there is no trailing word - the parse is clean + set argd [punk::args::parse {-encoding utf-8} withid $docid] + lappend result [dict get $argd opts] + }\ + -cleanup { + foreach id $docids { + punk::args::undefine $id 1 + } + }\ + -result [list\ + 1 {PUNKARGS VALIDATION {toomanyarguments 1 index 0}} {} {-encoding utf-8} + ] } tcltest::cleanupTests ;#needed to produce test summary line. diff --git a/src/tests/modules/punk/args/testsuites/args/formcheck.test b/src/tests/modules/punk/args/testsuites/args/formcheck.test index d81f9219..08b1170e 100644 --- a/src/tests/modules/punk/args/testsuites/args/formcheck.test +++ b/src/tests/modules/punk/args/testsuites/args/formcheck.test @@ -19,6 +19,7 @@ namespace eval ::testspace { } testConstraint have_tclcoredocs [expr {![catch {package require punk::args::moduledoc::tclcore}]}] + testConstraint have_punkexedocs [expr {![catch {package require punk::args::moduledoc::punkexe}]}] #discriminated multiform fixtures - literal/choice/int leading slots (no permissive #alignment) and arity-window separation. formcheck must report NOTHING for these. @@ -187,6 +188,71 @@ namespace eval ::testspace { }\ -result [list multipleformmatches] + #added 2026-08-05 (agent, G-168) - before-state GAP pins of the punkexe launch + #definitions: the script file/oneliner pair and the tclsh scriptfile/stdin pair + #are display-honest but parse-dishonest (no fencing, no '-e' element), so + #formcheck reports an unsanctioned structural overlap for each and every + #primary-use input double-matches. G-168 (selection-sound launch definitions) + #flips these deliberately when the forms become parse-discriminable. + test formcheck_GAP_punkexe_script_forms_overlap {the punkexe script definition's file/oneliner pair reports an unsanctioned structural overlap whose witness parses against both forms}\ + -constraints have_punkexedocs\ + -setup $common -body { + set r [punk::args::formcheck (script)::punkexe::script] + lappend result [dict get $r unsanctioned] + set finding [dict get $r findings {file oneliner}] + lappend result [dict get $finding class] [dict get $finding sanctioned] + set w [dict get $finding witness] + lappend result [dict get [punk::args::parse_status $w -form file withid (script)::punkexe::script] ok] + lappend result [dict get [punk::args::parse_status $w -form oneliner withid (script)::punkexe::script] ok] + }\ + -cleanup { + }\ + -result [list {{file oneliner}} structural 0 1 1] + + #added 2026-08-05 (agent, G-168) + test formcheck_GAP_punkexe_tclsh_forms_overlap {the punkexe tclsh definition's scriptfile/stdin pair reports an unsanctioned structural overlap whose witness parses against both forms}\ + -constraints have_punkexedocs\ + -setup $common -body { + set r [punk::args::formcheck (script)::punkexe::tclsh] + lappend result [dict get $r unsanctioned] + set finding [dict get $r findings {scriptfile stdin}] + lappend result [dict get $finding class] [dict get $finding sanctioned] + set w [dict get $finding witness] + lappend result [dict get [punk::args::parse_status $w -form scriptfile withid (script)::punkexe::tclsh] ok] + lappend result [dict get [punk::args::parse_status $w -form stdin withid (script)::punkexe::tclsh] ok] + }\ + -cleanup { + }\ + -result [list {{scriptfile stdin}} structural 0 1 1] + + #added 2026-08-05 (agent, G-168) - the parse reality behind the findings: the + #primary launch lines double-match today (the definitions are consulted for + #display only, so the defect is latent until declaration-driven consumers - + #help depth, dry-run, completion - parse against them); empty input selects + #the piped-stdin readings uniquely + test formcheck_GAP_punkexe_primary_inputs_double_match {every primary-use launch line double-matches the current punkexe script/tclsh forms (multipleformmatches); empty input selects the stdin readings}\ + -constraints have_punkexedocs\ + -setup $common -body { + foreach {id words} { + (script)::punkexe::script {-e {puts hi}} + (script)::punkexe::script {foo.tcl bar} + (script)::punkexe::tclsh {foo.tcl} + (script)::punkexe::tclsh {-encoding utf-8 foo.tcl} + } { + if {[catch {punk::args::parse $words -errorstyle minimal withid $id} errmsg erroropts]} { + set classinfo [lindex [dict get $erroropts -errorcode] 2] + lappend result [lindex $classinfo 0] + } else { + lappend result UNEXPECTED-parsed + } + } + lappend result [dict get [punk::args::parse {} withid (script)::punkexe::script] form] + lappend result [dict get [punk::args::parse {} withid (script)::punkexe::tclsh] form] + }\ + -cleanup { + }\ + -result [list multipleformmatches multipleformmatches multipleformmatches multipleformmatches file stdin] + #fixture cleanup catch {punk::args::undefine ::testspace::fc_afterish 1} catch {punk::args::undefine ::testspace::fc_sharedform 1} diff --git a/src/tests/modules/punk/args/testsuites/args/forms.test b/src/tests/modules/punk/args/testsuites/args/forms.test index 157a0903..c58e3417 100644 --- a/src/tests/modules/punk/args/testsuites/args/forms.test +++ b/src/tests/modules/punk/args/testsuites/args/forms.test @@ -317,5 +317,217 @@ namespace eval ::testspace { {synoverride --custom-synopsis-text}\ 1\ ] + + # -- --- --- --- --- --- --- --- --- --- --- --- --- --- + #Candidacy fencing / fall-through primitives - probed 2026-08-05 ahead of G-168 + #(selection-sound launch definitions). These pin HOW a form leaves candidacy + #(unknown option, incomplete/dangling option tail, option-value -regexprefail, + #fenced value slot) and what auto-selection then does - the engine facts the + #script/tclsh launch-definition models compose from. + #tclsh-shaped fixture: a scriptfile form (optional value-taking -encoding option + + #dash-fenced required scriptfile value) beside a permissive stdin form whose + #OPTIONAL first slot requires a dash-led word (-regexprefail {^[^-]}). + punk::args::define { + @id -id ::testspace::fenced_launch + @cmd -name testspace::fenced_launch -summary "tclsh-shaped fenced pair" -help "fencing/fall-through fixture" + @form -form scriptfile + @opts + -encoding -type string -optional 1 + @values -min 1 -max -1 + scriptfile -type string -optional 0 -regexprefail {^-} + arg -type any -optional 1 -multiple 1 + @form -form stdin + @values -min 0 -max -1 + first -type string -optional 1 -regexprefail {^[^-]} + rest -type any -optional 1 -multiple 1 + } + + #added 2026-08-05 (agent, G-168) - args-tier fixture pin of the selection behaviour + #cmdhelp.test pins against the live punk::auto_exec::hash declaration (G-150): + #-regexprefail on a plain value participates in form matching, so flag-led lines + #select their flag forms uniquely and plain words select the fenced value form + test forms_fence_regexprefail_autoselect {a flag-excluding -regexprefail on a permissive value makes flag-led lines select flag forms uniquely and plain words select the fenced form}\ + -setup $common -body { + punk::args::define { + @id -id ::testspace::fencedhash + @cmd -name testspace::fencedhash -summary "hash-shaped selection" -help "hash-shaped selection fixture" + @form -form show_or_set + @opts -min 0 -max 0 + @values -min 0 -max -1 + name -type string -optional 1 -multiple 1 -regexprefail {^-} + @form -form rehash + @opts -min 1 -max 1 + -r -type none -optional 0 + @values -min 0 -max 0 + @form -form delete + @opts + -d -type none -optional 0 + @values -min 1 -max -1 + name -type string -optional 0 -multiple 1 + } + set argd [punk::args::parse {-r} withid ::testspace::fencedhash] + lappend result [dict get $argd form] [dict get $argd opts] + set argd [punk::args::parse {somename} withid ::testspace::fencedhash] + lappend result [dict get $argd form] [dict get $argd values] + set argd [punk::args::parse {-d nm} withid ::testspace::fencedhash] + lappend result [dict get $argd form] [dict get $argd values] + set argd [punk::args::parse {} withid ::testspace::fencedhash] + lappend result [dict get $argd form] + }\ + -cleanup { + punk::args::undefine ::testspace::fencedhash 1 + }\ + -result [list rehash {-r 1} show_or_set {name somename} delete {name nm} show_or_set] + + #added 2026-08-05 (agent, G-168) - a -regexprefail rejection on an OPTIONAL value + #slot is fatal to the parse: the rejected word does NOT re-land on a later + #permissive slot (contrast errsel_rejected_word_lands_later, where an optional + #CHOICE arg's rejected word is re-offered to later slots). This is what makes + #per-slot fencing fence the whole FORM. + test forms_fence_optional_slot_no_reland {a word failing an optional slot's -regexprefail does not re-land on a later permissive slot - the rejection stands}\ + -setup $common -body { + punk::args::define { + @id -id ::testspace::noreland + @cmd -name testspace::noreland -summary "fenced optional first + permissive rest" -help "no-reland fixture" + @values -min 0 -max -1 + first -type string -optional 1 -regexprefail {^[^-]} + rest -type any -optional 1 -multiple 1 + } + if {[catch {punk::args::parse {f.tcl} -errorstyle minimal withid ::testspace::noreland} errmsg erroropts]} { + lappend result [lindex [lindex [dict get $erroropts -errorcode] 2] 0] + } else { + lappend result UNEXPECTED-relanded + } + #a dash-led word passes the fence and seats on the fenced slot + set argd [punk::args::parse {-x} withid ::testspace::noreland] + lappend result [dict get $argd values] + }\ + -cleanup { + punk::args::undefine ::testspace::noreland 1 + }\ + -result [list regexprefail {first -x}] + + #added 2026-08-05 (agent, G-168) + test forms_fence_first_slot_selects_file {non-dash-led lines select the scriptfile form uniquely - the stdin form's fenced optional first slot takes it out of candidacy (no re-landing)}\ + -setup $common -body { + set argd [punk::args::parse {f.tcl} withid ::testspace::fenced_launch] + lappend result [dict get $argd form] [dict get $argd values] + set argd [punk::args::parse {f.tcl a b} withid ::testspace::fenced_launch] + lappend result [dict get $argd form] [dict get $argd values] + #a dash-fence excludes only words STARTING with '-': ./-e stays a scriptfile + set argd [punk::args::parse {./-e} withid ::testspace::fenced_launch] + lappend result [dict get $argd form] [dict get $argd values] + #empty input: scriptfile's value minimum is unmet - stdin selected + set argd [punk::args::parse {} withid ::testspace::fenced_launch] + lappend result [dict get $argd form] + }\ + -cleanup { + }\ + -result [list\ + scriptfile {scriptfile f.tcl}\ + scriptfile {scriptfile f.tcl arg {a b}}\ + scriptfile {scriptfile ./-e}\ + stdin\ + ] + + #added 2026-08-05 (agent, G-168) - an unknown option fails only ITS form's + #candidacy: auto-selection falls through to the sibling that accepts the words + test forms_fallthrough_unknown_option {a word unknown as an option in one form fails that form only - candidacy falls through to the sibling form}\ + -setup $common -body { + set argd [punk::args::parse {-foo bar} withid ::testspace::fenced_launch] + lappend result [dict get $argd form] [dict get $argd values] + }\ + -cleanup { + }\ + -result [list stdin {first -foo rest bar}] + + #added 2026-08-05 (agent, G-168) - the tclsh '-encoding' fall-through class (stock: + #an incomplete -encoding form reads stdin with all words left in argv): a dangling + #option word, and an option satisfied with its required value slot then unmet, + #each fail the scriptfile form only - the parse SUCCEEDS via the stdin form + test forms_fallthrough_incomplete_option_tail {a dangling option word and an option-satisfied-but-values-unmet tail each fall through to the sibling form as successful parses}\ + -setup $common -body { + set argd [punk::args::parse {-encoding} withid ::testspace::fenced_launch] + lappend result [dict get $argd form] [dict get $argd values] + set argd [punk::args::parse {-encoding utf-8} withid ::testspace::fenced_launch] + lappend result [dict get $argd form] [dict get $argd values] + #parse_status agrees: stdin valid, scriptfile invalid (see the + #formviability.test dangling-optional-option conservatism pin) + set pstat [punk::args::parse_status {-encoding} withid ::testspace::fenced_launch] + lappend result [dict get $pstat ok] [dict get $pstat form] + lappend result [dict get $pstat formstatus scriptfile status] + }\ + -cleanup { + }\ + -result [list\ + stdin {first -encoding}\ + stdin {first -encoding rest utf-8}\ + 1 stdin invalid\ + ] + + #added 2026-08-05 (agent, G-168) - documented LIMIT of per-word fencing: a COMPLETE + #'-encoding ' line legitimately matches both forms wordwise (scriptfile: + #option + fenced value; stdin: dash-led first + permissive rest), and per-word + #constraints cannot express stock tclsh's "scriptfile wins when complete" priority - + #the G-041 no-silent-preference contract raises multipleformmatches. Any + #selection-sound tclsh model must design around this class (or sanction it). + test forms_fenced_pair_residual_overlap {a complete option+value+file line matches both a fenced scriptfile form and a dash-tolerant stdin form - per-word fencing cannot express completion priority}\ + -setup $common -body { + if {[catch {punk::args::parse {-encoding utf-8 f.tcl} -errorstyle minimal withid ::testspace::fenced_launch} errmsg erroropts]} { + set classinfo [lindex [dict get $erroropts -errorcode] 2] + lappend result [lindex $classinfo 0] + lappend result [lsort [dict get [lrange $classinfo 1 end] forms]] + } else { + lappend result UNEXPECTED-parsed + } + set result + }\ + -cleanup { + }\ + -result [list multipleformmatches {scriptfile stdin}] + + #added 2026-08-05 (agent, G-168) - -regexprefail on an OPTION'S VALUE screens the + #option (single-form: a regexprefail error) and participates in candidacy + #(multiform: the fenced form drops out and the sibling is selected) - the + #primitive for stock tclsh's "-encoding recognised only when the following + #argument does not begin with '-'" + test forms_fallthrough_option_value_fence {a -regexprefail on an option's value rejects the option single-form and falls through to the sibling form in a multiform}\ + -setup $common -body { + punk::args::define { + @id -id ::testspace::ovfence + @cmd -name testspace::ovfence -summary "option value fenced" -help "option-value fence fixture" + @opts + -encoding -type string -optional 1 -regexprefail {^-} + @values -min 0 -max 0 + } + if {[catch {punk::args::parse {-encoding -foo} -errorstyle minimal withid ::testspace::ovfence} errmsg erroropts]} { + lappend result [lindex [lindex [dict get $erroropts -errorcode] 2] 0] + } else { + lappend result UNEXPECTED-accepted-dash-value + } + punk::args::define { + @id -id ::testspace::ovfence2 + @cmd -name testspace::ovfence2 -summary "option-value fence multiform" -help "option-value fence multiform fixture" + @form -form enc + @opts + -encoding -type string -optional 1 -regexprefail {^-} + @values -min 1 -max -1 + scriptfile -type string -optional 0 -regexprefail {^-} + @form -form dash + @values -min 1 -max -1 + first -type string -optional 0 -regexprefail {^[^-]} + rest -type any -optional 1 -multiple 1 + } + set argd [punk::args::parse {-encoding -foo} withid ::testspace::ovfence2] + lappend result [dict get $argd form] [dict get $argd values] + }\ + -cleanup { + punk::args::undefine ::testspace::ovfence 1 + punk::args::undefine ::testspace::ovfence2 1 + }\ + -result [list regexprefail dash {first -encoding rest -foo}] + + #fixture cleanup for the fencing/fall-through group + catch {punk::args::undefine ::testspace::fenced_launch 1} } tcltest::cleanupTests ;#needed to produce test summary line. diff --git a/src/tests/modules/punk/args/testsuites/args/formviability.test b/src/tests/modules/punk/args/testsuites/args/formviability.test index b44e1878..63f46122 100644 --- a/src/tests/modules/punk/args/testsuites/args/formviability.test +++ b/src/tests/modules/punk/args/testsuites/args/formviability.test @@ -429,5 +429,34 @@ namespace eval ::testspace { punk::args::undefine ::testspace::vsflag 1 }\ -result [list incomplete incomplete optionmissing] + + #added 2026-08-05 (agent, G-168) - the G-164 re-probe re-seats a stolen word only + #when it names a missing REQUIRED option: a dangling word naming an OPTIONAL + #value-taking option gets no re-probe, stays seated as the required value, fails + #that value's fence and the verdict is invalid - although a completing suffix + #exists (the tclsh '-encoding' dangling class; stock reads stdin there, so the + #conservative direction matches the launch model's fall-through selection) + test formviability_dangling_optional_option_conservative {a dangling word naming an OPTIONAL value-taking option earns no re-probe - the verdict stays invalid although a completing suffix exists (documented conservatism)}\ + -setup $common -body { + punk::args::define { + @id -id ::testspace::vdangle + @cmd -name testspace::vdangle -summary "optional value-taking option + fenced value" -help "dangling optional-option fixture" + @opts + -encoding -type string -optional 1 + @values -min 1 -max -1 + scriptfile -type string -optional 0 -regexprefail {^-} + arg -type any -optional 1 -multiple 1 + } + set pstat [punk::args::parse_status {-encoding} withid ::testspace::vdangle] + lappend result [dict get $pstat status] [dict get $pstat failureclass] [dict get $pstat formstatus _default status] + #the completing suffix genuinely parses - the invalid verdict is the + #documented conservatism, not a truth about appendability + set argd [punk::args::parse {-encoding utf-8 f.tcl} withid ::testspace::vdangle] + lappend result [dict get $argd opts] [dict get $argd values] + }\ + -cleanup { + punk::args::undefine ::testspace::vdangle 1 + }\ + -result [list invalid regexprefail invalid {-encoding utf-8} {scriptfile f.tcl}] } tcltest::cleanupTests ;#needed to produce test summary line.