diff --git a/CHANGELOG.md b/CHANGELOG.md index a60a56ca..e3c458ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,30 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to Entries are newest-first; one bullet per notable change. See the root `AGENTS.md` "Project Versioning" section for the bump policy. +## [0.59.0] - 2026-08-08 + +- `punk::args` gains argument-relation vocabulary and parse-time + enforcement (G-083): per-argument `-conflicts ` declares that + named arguments (parsekeys or flag names) must not be received together + with this argument, and `@opts -parsekeymode override|error` (per named + group) makes a shared-parsekey group's distinct-member co-occurrence a + parse-time `optionconflict` (override, the legacy last-wins default, or + error). Both are cross-validated at definition resolve; a colliding + `-parsekey` is now a resolve error. `optionconflict` is a new + `PUNKARGS VALIDATION` failure class, checked against received arguments + only (defaults never conflict), over both the ordinary and mash + (short-flag bundling) parse paths, and classified as `invalid` (not + `incomplete`). Usage/arg_error rendering surfaces the rules: a + `conflicts with: ` hint on each conflicted argument's help row + and a `mutually exclusive (distinct members may not be combined)` group + header annotation for `-parsekeymode error` groups; the synopsis + one-line form carries no conflict detail. The `lsearch` tclcore + moduledoc models its documented `-sorted`/`-glob`/`-regexp` and + `-bisect`/`-all`/`-not` incompatibilities via per-arg `-conflicts` (the + group stays `override` so `-glob`/`-regexp` remain last-wins) and drops + its `punk::args fixes required` caveat. Legacy behaviour is untouched by + default (no `-conflicts`, default `override`, no colliding parsekey). + ## [0.58.0] - 2026-08-07 - `punk::path` gains machine-parsable return modes and a stable conflict verdict diff --git a/GOALS-archive.md b/GOALS-archive.md index 5c0d5fcb..a4b186f0 100644 --- a/GOALS-archive.md +++ b/GOALS-archive.md @@ -20,6 +20,10 @@ Do not edit archived records here except to fix a broken path. Archived detail f ## Archived goals +### G-083 [achieved 2026-08-08] punk::args argument relations: strict mutual exclusivity and parsekey-group integrity → detail: goals/archive/G-083-punkargs-argument-relations.md +Scope: src/modules/punk/args-999999.0a1.0.tm (resolve/spec compiler, parse paths incl mash, arg_error/synopsis/usage renderers, errorcode vocabulary); src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (lsearch, clock clicks dogfood); src/tests/modules/punk/args/testsuites/args/ + testsuites/dev/ (GAP flips + new characterization) +Acceptance: parse raises a usage-style error with a structured errorcode (PUNKARGS VALIDATION optionconflict shape) naming both offending received arguments for -conflicts violations and strict-group co-occurrence, through both the ordinary and mash option paths with prefix abbreviations resolved before the check; define time rejects a -parsekey colliding with a distinct defined argument's name (parsekey_collides_with_defined_optname_GAP flips); legacy behaviour is untouched by default (full existing punk::args suite passes, including the pinned last-wins/prepend-defaults override idiom); the lsearch moduledoc models its documented incompatibilities with the new vocabulary and drops its 'punk::args fixes required for grouped mutually exclusive options' caveat; usage/synopsis output for conflict rules is pinned by characterization tests. + ### G-173 [achieved 2026-08-07] punk::path machine-parsable returns (text/dict/json) with stable conflict verdict → detail: goals/archive/G-173-punkpath-machine-returns.md Scope: src/modules/punk-999999.0a1.0.tm (punk::path proc - add -return modes + -context); src/tests/modules/punk/path/testsuites/tests/ (return-shape + round-trip + ansi-freeness tests) Acceptance: -return gains text, dict and json choices whose output contains no ansi escapes regardless of -highlight; the returned structure is documented in the command's punk::args definition; the default table output is byte-unchanged for existing users (existing path.test cases stay green); -return json round-trips the same data as -return dict (decode via tcllib json, recompare); the conflict verdict is stable independent of how the consumer invoked the proc via a -context option (default the caller's namespace); new tests pin dict/json shape, ansi-freeness, and dict/json round-trip parity; a duplicate-path entry and an overshadowed executable each appear in the dict/json with the fields a machine consumer needs (idx, is_duplicate, duplicate_of, overshadowed, overshadowed_by, tcl_conflicts with match=exact|nocase). diff --git a/GOALS.md b/GOALS.md index 39b2ff74..96e03a5d 100644 --- a/GOALS.md +++ b/GOALS.md @@ -287,9 +287,6 @@ Detail: goals/G-080-ansi2html-cellgrid-mode.md Scope: src/modules/punk/mix/commandset/doc-999999.0a1.0.tm (doc.* commandset - new generation subcommands); src/scriptapps/tools/punkargs_to_doctools.tcl + punkargs_punknative.tcl (prototypes to productize); src/scriptapps/tools/fontprep/ (shared doc-set asset in link mode); src/doc tree conventions; src/tests coverage Detail: goals/G-081-argdoc-build-pipeline.md -### G-083 [active] punk::args argument relations: strict mutual exclusivity and parsekey-group integrity -Scope: src/modules/punk/args-999999.0a1.0.tm (resolve/spec compiler, parse paths incl mash, arg_error/synopsis/usage renderers, errorcode vocabulary); src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (lsearch, clock clicks dogfood); src/tests/modules/punk/args/testsuites/args/ + testsuites/dev/ (GAP flips + new characterization) -Detail: goals/G-083-punkargs-argument-relations.md ### G-084 [proposed] 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) diff --git a/goals/G-053-punkargs-multiple-ranges.md b/goals/G-053-punkargs-multiple-ranges.md index 903658d9..9c36adf1 100644 --- a/goals/G-053-punkargs-multiple-ranges.md +++ b/goals/G-053-punkargs-multiple-ranges.md @@ -68,6 +68,8 @@ rendering gain meaningful bounded-repetition forms (e.g. "0-1", "2-4"). - The runtests `-include-paths` fix (repeatable, accumulate, single-list form still accepted) shipped independently on 2026-07-10 and does not depend on this goal. - Archived-goal references in this file: G-046 achieved 2026-07-10 (goals/archive/G-046-punkargs-deferred-help-and-fixes.md). -- Referenced by G-083 (argument relations - "folding into G-053" considered and - rejected there) and G-084 (-parsekey cross-member -multiple collection rides this - goal's occurrence arity) - recorded 2026-07-24 after overlap review. +- Referenced by G-083 (argument relations - "folding into G-053" + considered and rejected there; achieved - see + goals/archive/G-083-punkargs-argument-relations.md) and G-084 (-parsekey + cross-member -multiple collection rides this goal's occurrence arity) - + recorded 2026-07-24 after overlap review. diff --git a/goals/G-084-punkargs-parsekey-completeness.md b/goals/G-084-punkargs-parsekey-completeness.md index 93ec7046..a01d6665 100644 --- a/goals/G-084-punkargs-parsekey-completeness.md +++ b/goals/G-084-punkargs-parsekey-completeness.md @@ -30,9 +30,11 @@ _GAP test in src/tests/modules/punk/args/testsuites/args/parsekey.test: - Accumulate-in-received-order is the lean for cross-member -multiple collection, pinned (disabled) in dev/parsekey-knownbugs.test as - parsekey_shared_key_multiple_accumulation. Open design decision: once - G-083's relations vocabulary exists, "error on cross-member combination - unless permitted" is a defensible alternative - decide before activating. + parsekey_shared_key_multiple_accumulation. Open design decision: with + G-083's relations vocabulary now landed (achieved - see + goals/archive/G-083-punkargs-argument-relations.md), "error on cross-member + combination unless permitted" is a defensible alternative - decide before + activating. - Leaders/values support should reuse the option path's keying rules (result + received dicts, required-list satisfaction) rather than a parallel mechanism; where support is deliberately not implemented, define time must @@ -46,8 +48,9 @@ _GAP test in src/tests/modules/punk/args/testsuites/args/parsekey.test: src/tests/modules/punk/args/testsuites/dev/parsekey-knownbugs.test - enable and flip the parsekey.test _GAP pins as fixes land. - Related: G-083 (argument relations - shared design context for the - cross-member collection decision), G-053 (occurrence arity of a single - argument). + cross-member collection decision; achieved - see + goals/archive/G-083-punkargs-argument-relations.md), G-053 (occurrence arity + of a single argument). - Related: G-151 (achieved 2026-08-05 - goals/archive/G-151-punkargs-annotated-success-render.md) - its parse_report landing report bridges the parse-result storage-key folds AT DISPLAY LEVEL only (an argument's -parsekey, else an aliased optionset's diff --git a/goals/G-083-punkargs-argument-relations.md b/goals/archive/G-083-punkargs-argument-relations.md similarity index 80% rename from goals/G-083-punkargs-argument-relations.md rename to goals/archive/G-083-punkargs-argument-relations.md index f59c9c9e..787b2d87 100644 --- a/goals/G-083-punkargs-argument-relations.md +++ b/goals/archive/G-083-punkargs-argument-relations.md @@ -1,6 +1,6 @@ # G-083 punk::args argument relations: strict mutual exclusivity and parsekey-group integrity -Status: active +Status: achieved 2026-08-08 Scope: src/modules/punk/args-999999.0a1.0.tm (resolve/spec compiler, parse paths incl mash, arg_error/synopsis/usage renderers, errorcode vocabulary); src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (lsearch, clock clicks dogfood); src/tests/modules/punk/args/testsuites/args/ + testsuites/dev/ (GAP flips + new characterization) Goal: punk::args definitions can express hard mutual exclusivity - a per-argument -conflicts relation (keyed on parsekeys, checked only against received arguments so defaults never conflict) and an @opts-level strict mode making distinct-member co-occurrence within a shared-parsekey group an error - while legacy last-wins remains the default, so lsearch-class commands (grouped styles with pairwise incompatibilities like -sorted vs -glob/-regexp and -bisect vs -all/-not) parse and document faithfully without combinatorial @form explosion. Acceptance: parse raises a usage-style error with a structured errorcode (PUNKARGS VALIDATION optionconflict shape) naming both offending received arguments for -conflicts violations and strict-group co-occurrence, through both the ordinary and mash option paths with prefix abbreviations resolved before the check; define time rejects a -parsekey colliding with a distinct defined argument's name (parsekey_collides_with_defined_optname_GAP flips); legacy behaviour is untouched by default (full existing punk::args suite passes, including the pinned last-wins/prepend-defaults override idiom); the lsearch moduledoc models its documented incompatibilities with the new vocabulary and drops its "punk::args fixes required for grouped mutually exclusive options" caveat; usage/synopsis output for conflict rules is pinned by characterization tests. @@ -131,3 +131,32 @@ Design leans (settle and record here during the work): - Bug found and fixed during increment 2: the group-co-occurrence dedup check initially used `ni` (not-in) where `in` was meant - `ni` returns true on an empty list, so it skipped every member and the check never fired. Corrected to `in`. +- 2026-08-08 increment 3 (usage rendering + lsearch moduledoc adoption, punk::args 0.21.0, + project 0.59.0): the conflict rules now surface in usage/arg_error and synopsis + output, and the lsearch tclcore moduledoc models its documented incompatibilities. + 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). 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 @cmd -help. + clock clicks left unchanged - its active positional-choice definition already + models exclusivity (exactly-one-of) and the acceptance clause names lsearch + only. relations.test extended with 8 increment-3 pins (4 rendering: + conflicts_usage_hint, parsekeymode_error_usage_group_header, + parsekeymode_override_no_header_annotation, conflicts_synopsis_no_hint; 4 + lsearch adoption: lsearch_sorted_conflicts_glob + lsearch_bisect_conflicts_all + raise optionconflict via errorcode substring match, lsearch_glob_regexp_last_wins + stays last-wins, lsearch_caveat_dropped). Legacy untouched by default confirmed: + full punk/args suite 371 total / 368 passed / 3 skipped / 0 failed via tclsh90s; + tclcoreparity 10/10; punk/ns 125/125. All G-083 acceptance criteria met. + +## Follow-ons + +Follow-on: -requires/-implies dependency relations (e.g lsearch -subindices with -index, -bisect implying -sorted) - deliberately deferred during G-083 (the man-page semantics are mostly soft "has no effect unless"); the optionconflict errorcode vocabulary was shaped so a later requires-relation slots in alongside without reshaping it => open diff --git a/punkproject.toml b/punkproject.toml index 6fe88cd9..61223793 100644 --- a/punkproject.toml +++ b/punkproject.toml @@ -1,6 +1,6 @@ [project] name = "punkshell" -version = "0.58.0" +version = "0.59.0" license = "BSD-2-Clause" url = "https://www.gitea1.intx.com.au/jn/punkshell" #packager: declared identity for published artifacts (declarative, not proof - diff --git a/src/modules/punk/args-999999.0a1.0.tm b/src/modules/punk/args-999999.0a1.0.tm index f24d9927..f05bd419 100644 --- a/src/modules/punk/args-999999.0a1.0.tm +++ b/src/modules/punk/args-999999.0a1.0.tm @@ -5927,6 +5927,11 @@ tcl::namespace::eval punk::args { } else { set groupinfo "(common flag group)\nkey:$thisgroup_parsekey" } + #G-083: annotate a strict shared-parsekey group + set _g83_gmode [punk::args::system::Dict_getdef [dict get $form_dict OPT_GROUPS $thisgroup] -parsekeymode override] + if {$_g83_gmode eq "error"} { + append groupinfo "\nmutually exclusive (distinct members may not be combined)" + } if {$use_table} { $t add_row [list " $thisgroup" $groupinfo "" "" $help] if {$arg_status eq "bad"} { @@ -6404,6 +6409,25 @@ tcl::namespace::eval punk::args { } } + #G-083: per-arg -conflicts hint - render the conflicting targets + #by their flag spelling (resolve parsekeys to their optset's last member). + set _g83_conflicts [Dict_getdef $arginfo -conflicts {}] + if {[llength $_g83_conflicts]} { + set _g83_cdisp [list] + foreach _g83_c $_g83_conflicts { + if {[dict exists $form_dict ARG_INFO $_g83_c]} { + lappend _g83_cdisp $_g83_c + } elseif {[dict exists $lookup_optset $_g83_c]} { + lappend _g83_cdisp [dict get $lookup_optset $_g83_c] + } else { + lappend _g83_cdisp $_g83_c + } + } + if {$help ne ""} {append help \n} + append help "conflicts with: [join $_g83_cdisp { }]" + unset _g83_conflicts _g83_c _g83_cdisp + } + # ============================================= #REVIEW if {"-help" ni $unindentedfields} { diff --git a/src/modules/punk/args-buildversion.txt b/src/modules/punk/args-buildversion.txt index 20d96337..dd9d5beb 100644 --- a/src/modules/punk/args-buildversion.txt +++ b/src/modules/punk/args-buildversion.txt @@ -1,6 +1,7 @@ -0.20.0 +0.21.0 #First line must be a semantic version number #all other lines are ignored. +#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. #0.19.0 - G-083 increment 1 (argument-relations define-time vocabulary): new per-argument key -conflicts (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) and new @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 value colliding with a distinct defined argument's name (one that does not share that -parsekey) is now a resolve error instead of silently forming an implicit shared-key group (parsekey_collides_with_defined_optname_GAP flipped to a define-time error pin in parsekey.test). Legacy behaviour untouched by default (no -conflicts, default -parsekeymode override, no colliding parsekey). New testsuite relations.test pins the define-time vocabulary; full punk/args suite green. #0.18.0 - G-151 annotated success render: new punk::args::parse_report - landing report for a SUCCESSFUL parse, one row per value-holding argument (received, or filled from -default) in declaration-section order (leaders, opts, values) with columns Argument | Source | Value - the success-side sibling of the arg_error usage table (a flag-like word consumed as a VALUE by position shows attributed to its consuming argument - the make.tcl dry-run 'kitname = punk91 -confirm 0' class). Input: a punk::args::parse result dict (its id key must resolve to a registered definition), or words + 'withid ' (the parse is performed first; a rejected line raises the same validation error parse would - only successful parses have a landing report). Source column: received / received xN (-multiple aggregation count) / default - the plain-mode-safe twin of the colour marking (received CLR(check), default CLR(parsekey_hint)); absent optionals with no default-in-effect get no row. Returns: table (arg_error-styled bordered block via textblock; the default), tableobject, string (plain lines 'name (source) = value'; also the degraded no-textblock path), dict (machine form: id/form/rows with class/source/received/positions/multiple/hasvalue/value - never elided). Display elision is type-aware, deterministic and always marked (display-hint honesty): strings single-line-ized (ansistring VIEW -lf 1 -vt 1 -sp 0 - embedded controls/ANSI render visibly so a value cannot disturb the table; real spaces stay spaces) then grapheme-capped via the new ansistring TRUNCATE (punk::ansi 0.2.0) with trailing '...' plus ' (len N)' citing the raw value's string length; -multiple aggregations and -type list values element-capped with '(+N more)' (whole list-quoted elements kept while they fit, first element char-capped if it alone overflows); -type dict values pair-capped with '(+N pairs)'; markers stay intact so cells can exceed very small caps by the marker width; plain-string fallbacks when punk::ansi is unavailable. Width cascade per the G-149 plumbing precedent: caller -valuewidth > new @cmd directive key -reportvaluewidth > built-in 48; 0 means no cap; deliberately never console-derived (deterministic and pipe-safe - the G-007 emit-then-query lesson). Value-in-effect lookup bridges storage-key folds at display level (-parsekey renames, aliased optionsets' last-|-member finalopts fold) so received aliased/parsekey'd arguments keep their row - G-084 owns the storage-key model itself. Failure-side rendering (arg_error/usage) unchanged. make.tcl retiring its interim one-line 'dry-run: line accepted ...' report in favour of this render is consumer follow-through after a bootsupport promotion (not part of this change). New testsuite parsereport.test; define -help documents @cmd -reportvaluewidth beside the other directive-options. 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 1dfbeb25..6f564c4a 100644 --- a/src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm +++ b/src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm @@ -7506,10 +7506,7 @@ tcl::namespace::eval punk::args::moduledoc::tclcore { "This command searches the elements of list to see if one of them matches pattern. If so, the command returns the index of the first matching element (unless the options -all or -inline are specified.) If not, the command returns -1 or (if options -all or -inline are specified) the empty string. The option arguments indicates how - the elements of the list are to be matched against pattern. - - (documentation incomplete - punk::args fixes required for grouped mutually exlusive options and prefix calculation) - " + the elements of the list are to be matched against pattern. " @leaders -min 0 -max 0 @opts -type none -parsekey "-MATCHSTYLE" -group "MATCHING STYLE OPTIONS" -grouphelp\ "If all matching style options are omitted, the default matching style is -glob. @@ -7524,13 +7521,15 @@ tcl::namespace::eval punk::args::moduledoc::tclcore { -regexp -typedefaults "-regexp" -help\ "Pattern is treated as a regular expression and matched against each list element using the rules described in the re_syntax reference page." - -sorted -typedefaults "-sorted" -help\ + -sorted -typedefaults "-sorted" -conflicts {-glob -regexp} -help\ "The list elements are in sorted order. If this option is specified, lsearch will use a more efficient searching algorithm to search list. If no other options are specified, list is assumed to be sorted in increasing order, and to contain ASCII strings. This option is mutually exclusive with -glob and -regexp, and is treated exactly like -exact when either -all or -not are specified." - + #G-083: -sorted mutually exclusive with -glob and -regexp (per the man page) - + #modelled with per-arg -conflicts while keeping the MATCHING STYLE OPTIONS group at + #the default override mode (last-wins among -exact/-glob/-regexp). @opts -type string -parsekey "" -group "GENERAL MODIFIER OPTIONS" -all -type none -help\ @@ -7570,7 +7569,7 @@ tcl::namespace::eval punk::args::moduledoc::tclcore { "The list elements are sorted in decreasing order. This option is only meaningful when used with -sorted." -increasing -typedefaults "-increasing" -default "-increasing" -help\ "The list elements are sorted in increasing order. This option is only meaningful when used with -sorted." - -bisect -typedefaults "-bisect" -help\ + -bisect -typedefaults "-bisect" -conflicts {-all -not} -help\ "Inexact search when the list elements are in sorted order. For an increasing list the last index where the element is less than or equal to the pattern is returned. For a decreasing list the last index where the element is greater than or equal to the pattern is returned. If the pattern is before the first diff --git a/src/tests/modules/AGENTS.md b/src/tests/modules/AGENTS.md index 6c0122e3..bcdfab48 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; extended 2026-08-05 by the G-151 pre-modification coverage survey - VIEW -lf 0/1/2 single-line-ization modes + always-on NUL + silent unknown-option tolerance (load-bearing for overtype's '-nul 1' sites) + C1/zero-width visuals, RANGE end-relative/clamping plus the merged-code-stack re-emission and trailing-code-drop semantics a truncate primitive must respect, COUNT-vs-length combining-accent divergence, NEW object basics, and the KNOWN-DEFICIENCY pin that VIEWCODES/VIEWSTYLE raise invalid-command in a plain punk::ansi interp (bare 'a+'/'a' interactive-alias dependency; pinned in a fresh child interp); plus the TRUNCATE primitive pins landed with G-151 (punk::ansi 0.2.0) - fits-unchanged byte identity incl styled fixtures, grapheme-exact capping with marker budget participation and marker-alone truncation, SGR-reset-before-marker on styled prefixes, CJK/combining-cluster grapheme safety, and strict option errors (deliberately unlike VIEW's pinned silent tolerance)), 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 (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), the G-168 launch-definition model parity (`punkexemodel.test`, punkexe moduledoc 0.7.0, id loading updated for the G-169 handover - the script id resolves via app-punkscript-docs from src/lib (auto_path derived from the test file location) while the moduledoc carries the core-owned ids: script/tclsh selection matrices against the real (script)::punkexe ids with the real-side dispatch/app oracle recorded in goals/archive/G-168, the bare-'-e' viable-incomplete verdict, the ./-e fence message, -encoding fall-through statuses, and the sanctioned complete-'-encoding' multipleformmatches LIMIT pins; the formcheck.test punkexe GAPs flipped to discriminated/sanctioned/selection-sound pins in the same arc), the G-151 landing report (`parsereport.test`, punk::args 0.18.0 parse_report: the canonical flag-like-word-consumed-as-VALUE attribution row, parsed-result vs words+withid entry parity, machine dict shape with declaration-section row order and absent-optional row omission, received xN multiplicity for solos/-multiple opts, type-aware always-marked elision at the default width plus the caller > @cmd -reportvaluewidth > built-in width cascade and never-elided dict returns, VIEW-style single-line-ization of control-bearing values, aliased-optionset storage-key row bridging, bordered-table/tableobject renders, words-form failure errorcode parity with parse, and the registered two-form definition's own render), 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, and values/leaders parsekey breakage - desired-behaviour pins disabled behind punkargsKnownBug in `testsuites/dev/parsekey-knownbugs.test`; the parsekey/optname collision GAP is flipped (G-083, 2026-08-07: -parsekey colliding with a distinct defined arg's name is now a resolve error unless that arg shares the parsekey), and the G-083 argument-relations define-time vocabulary is pinned in `relations.test` (per-arg -conflicts target validation, @opts -parsekeymode override|error storage and the requires-group/bad-value/requires-parsekey resolve errors), plus its increment-2 parse-time enforcement (optionconflict raise for -conflicts violations and -parsekeymode error co-occurrence, defaults-never-conflict, cross-group conflicts, parse_status invalid classification)), longopt style-distinction and mash-edge characterisation (`longopts.test`, 2026-08-07 gap-fill: the gnu (`--flag=` inline-only; spaced/solo usage = badoptionformat) vs plain (`--flag` spaced-only; inline `=` = invalidoption) vs mixed (`-f|--file|--file=` both forms incl longopt prefix abbreviation) definition-grammar distinctions, first-`=`-only value splitting and empty inline values, the two `=`-member resolve-time definition errors, single-dash `-flag=value` never split at the `=`, and `@opts -any 1` adhoc passthrough where `--flag=value` splits but `-flag=value` stays a whole adhoc flagname expecting a value; the same gap-fill added to `mashopts.test`: unknown-letter-in-mash invalidoption rejection and the `-any` interplay - defined flags still mash while undefined mash-lookalike tokens are adhoc flags, never mash attempts; error-shape pins use the first three -errorcode elements), 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 appear when the class is live OR is a declared forward unavailable class - dict labels on every runtime since G-073, unicode keeps the live-only rule; the G-073 forward-class adoption invariants and the USER-SANCTIONED 'di' prefix-strictness exemption (the model is deliberately stricter than real 8.6, full words parity-true); and the G-166 pin that an unavailable class's per-class virtual id LEADS its help with the unavailability statement on runtimes lacking the class while modern runtimes generate it unchanged. All expectations are derived from the running interpreter, green on 8.6/8.7/9.0 through runtests.tcl on each - native Tcl 8.6 has been a supported runner interpreter since 2026-07-21) +- `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), the G-168 launch-definition model parity (`punkexemodel.test`, punkexe moduledoc 0.7.0, id loading updated for the G-169 handover - the script id resolves via app-punkscript-docs from src/lib (auto_path derived from the test file location) while the moduledoc carries the core-owned ids: script/tclsh selection matrices against the real (script)::punkexe ids with the real-side dispatch/app oracle recorded in goals/archive/G-168, the bare-'-e' viable-incomplete verdict, the ./-e fence message, -encoding fall-through statuses, and the sanctioned complete-'-encoding' multipleformmatches LIMIT pins; the formcheck.test punkexe GAPs flipped to discriminated/sanctioned/selection-sound pins in the same arc), the G-151 landing report (`parsereport.test`, punk::args 0.18.0 parse_report: the canonical flag-like-word-consumed-as-VALUE attribution row, parsed-result vs words+withid entry parity, machine dict shape with declaration-section row order and absent-optional row omission, received xN multiplicity for solos/-multiple opts, type-aware always-marked elision at the default width plus the caller > @cmd -reportvaluewidth > built-in width cascade and never-elided dict returns, VIEW-style single-line-ization of control-bearing values, aliased-optionset storage-key row bridging, bordered-table/tableobject renders, words-form failure errorcode parity with parse, and the registered two-form definition's own render), 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, and values/leaders parsekey breakage - desired-behaviour pins disabled behind punkargsKnownBug in `testsuites/dev/parsekey-knownbugs.test`; the parsekey/optname collision GAP is flipped (G-083, 2026-08-07: -parsekey colliding with a distinct defined arg's name is now a resolve error unless that arg shares the parsekey), and the G-083 argument-relations define-time vocabulary is pinned in `relations.test` (per-arg -conflicts target validation, @opts -parsekeymode override|error storage and the requires-group/bad-value/requires-parsekey resolve errors), plus its increment-2 parse-time enforcement (optionconflict raise for -conflicts violations and -parsekeymode error co-occurrence, defaults-never-conflict, cross-group conflicts, parse_status invalid classification), and its increment-3 usage/synopsis rendering and lsearch moduledoc adoption (conflicts-with hints appended to per-arg help rows, -parsekeymode error groups annotated 'mutually exclusive' in group headers with override groups unannotated, synopsis one-line form carrying no conflict detail; and the lsearch tclcore moduledoc modelling -sorted/-glob and -bisect/-all incompatibilities via per-arg -conflicts while -glob/-regexp stay last-wins and the 'punk::args fixes required' caveat is dropped))), longopt style-distinction and mash-edge characterisation (`longopts.test`, 2026-08-07 gap-fill: the gnu (`--flag=` inline-only; spaced/solo usage = badoptionformat) vs plain (`--flag` spaced-only; inline `=` = invalidoption) vs mixed (`-f|--file|--file=` both forms incl longopt prefix abbreviation) definition-grammar distinctions, first-`=`-only value splitting and empty inline values, the two `=`-member resolve-time definition errors, single-dash `-flag=value` never split at the `=`, and `@opts -any 1` adhoc passthrough where `--flag=value` splits but `-flag=value` stays a whole adhoc flagname expecting a value; the same gap-fill added to `mashopts.test`: unknown-letter-in-mash invalidoption rejection and the `-any` interplay - defined flags still mash while undefined mash-lookalike tokens are adhoc flags, never mash attempts; error-shape pins use the first three -errorcode elements), 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 appear when the class is live OR is a declared forward unavailable class - dict labels on every runtime since G-073, unicode keeps the live-only rule; the G-073 forward-class adoption invariants and the USER-SANCTIONED 'di' prefix-strictness exemption (the model is deliberately stricter than real 8.6, full words parity-true); and the G-166 pin that an unavailable class's per-class virtual id LEADS its help with the unavailability statement on runtimes lacking the class while modern runtimes generate it unchanged. All expectations are derived from the running interpreter, green on 8.6/8.7/9.0 through runtests.tcl on each - native Tcl 8.6 has been a supported runner interpreter since 2026-07-21) - `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; plus the G-166 availability axis on the flowunavail/flowunavail_nodoc fixtures - the `unavailable` cmdinfo key present on every result, exact and unique-prefix landings on a `-choiceunavailable` name resolving that name's virtual docid ATTRIBUTED rather than resolving nothing, the choice traverse attributing without addressing when no virtual docid exists, and cmdtype staying unchanged throughout since availability is a second axis, never a cmdtype value), 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); the flipped G-051 pins for pseudo-command cmdtype (`doconly`) + space-form docid prefixes (real `string is` pins behind the have_tclcoredocs constraint); the G-166 unavailability marking on the helpstrua fixture and on the real `string is dict` subject - the `-return dict` key, the table/string marking with the info scheme suppressed so a valid argument tail no longer renders as a cleanly usable command line, and the `-return text` leading `UNAVAILABLE:` line (the two deliberate key-list flips - `cmdhelp_cmdinfo_result_shape`, `cmdhelp_return_dict_valid` - landed with it); remaining GAP pins for 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/relations.test b/src/tests/modules/punk/args/testsuites/args/relations.test index be24af26..be5a700e 100644 --- a/src/tests/modules/punk/args/testsuites/args/relations.test +++ b/src/tests/modules/punk/args/testsuites/args/relations.test @@ -1,6 +1,9 @@ package require tcltest package require punk::args +#G-083 lsearch moduledoc adoption tests require the tclcore moduledoc package +tcltest::testConstraint have_tclcoredocs [expr {![catch {package require punk::args::moduledoc::tclcore}]}] + #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 @@ -205,5 +208,107 @@ namespace eval ::testspace { }\ -result [list invalid optionconflict] + #added 2026-08-07 (agent) - G-083 increment 3 rendering + test conflicts_usage_hint {the usage argument table shows a 'conflicts with:' hint on a -conflicts argument row}\ + -setup $common -body { + set id ::testspace::g83_conflicts_usage_hint + punk::args::define [list @id -id $id] @opts {-x -type none -conflicts -y} {-y -type none} + lappend docids $id + set out [punk::ansi::ansistrip [punk::args::usage -return string -scheme nocolour $id]] + #the conflicts hint appears in the table + lappend result [string match "*conflicts with: -y*" $out] + #and appears exactly once (only the -x row carries it, not -y) + lappend result [regexp -all {conflicts with:} $out] + set result + }\ + -cleanup { + foreach id $docids {punk::args::undefine $id 1} + }\ + -result [list 1 1] + + test parsekeymode_error_usage_group_header {the usage table annotates a -parsekeymode error group header as mutually exclusive}\ + -setup $common -body { + set id ::testspace::g83_pkmode_usage + punk::args::define [list @id -id $id] {@opts -group G -parsekey -pk -parsekeymode error} {-a -type none} {-b -type none} + lappend docids $id + set out [punk::ansi::ansistrip [punk::args::usage -return string -scheme nocolour $id]] + lappend result [string match "*mutually exclusive*" $out] + set result + }\ + -cleanup { + foreach id $docids {punk::args::undefine $id 1} + }\ + -result [list 1] + + test parsekeymode_override_no_header_annotation {a default -parsekeymode override group header carries no mutual-exclusion annotation}\ + -setup $common -body { + set id ::testspace::g83_pkmode_override_usage + punk::args::define [list @id -id $id] {@opts -group G -parsekey -pk} {-a -type none} {-b -type none} + lappend docids $id + set out [punk::ansi::ansistrip [punk::args::usage -return string -scheme nocolour $id]] + lappend result [string match "*mutually exclusive*" $out] + set result + }\ + -cleanup { + foreach id $docids {punk::args::undefine $id 1} + }\ + -result [list 0] + + test conflicts_synopsis_no_hint {the one-line synopsis does not carry per-arg conflict detail (it lives in the usage table)}\ + -setup $common -body { + set id ::testspace::g83_syn_probe + punk::args::define [list @id -id $id] @opts {-x -type none -conflicts -y} {-y -type none} + lappend docids $id + set out [punk::ansi::ansistrip [punk::args::synopsis -return full $id]] + lappend result [string match "*conflicts with*" $out] + lappend result [regexp -- {\[-x\] \[-y\]} $out] + set result + }\ + -cleanup { + foreach id $docids {punk::args::undefine $id 1} + }\ + -result [list 0 1] + + #added 2026-08-07 (agent) - G-083 increment 3 lsearch moduledoc adoption + test lsearch_sorted_conflicts_glob {lsearch -sorted and -glob are modelled as conflicting (optionconflict)}\ + -constraints have_tclcoredocs\ + -setup $common -body { + try { + punk::args::parse {-sorted -glob list pat} withid ::lsearch + set result "UNEXPECTED-accepted" + } on error {emsg eopts} { + set result [string match "*PUNKARGS VALIDATION*optionconflict -sorted -glob*" [dict get $eopts -errorcode]] + } + }\ + -result [list 1] + + test lsearch_bisect_conflicts_all {lsearch -bisect and -all are modelled as conflicting (optionconflict)}\ + -constraints have_tclcoredocs\ + -setup $common -body { + try { + punk::args::parse {-bisect -all list pat} withid ::lsearch + set result "UNEXPECTED-accepted" + } on error {emsg eopts} { + set result [string match "*PUNKARGS VALIDATION*optionconflict -bisect -all*" [dict get $eopts -errorcode]] + } + }\ + -result [list 1] + + test lsearch_glob_regexp_last_wins {lsearch -glob and -regexp stay last-wins (no conflict) - the group is NOT -parsekeymode error}\ + -constraints have_tclcoredocs\ + -setup $common -body { + set argd [punk::args::parse {-glob -regexp list pat} withid ::lsearch] + set result [dict get $argd opts -MATCHSTYLE] + }\ + -result [list -regexp] + + test lsearch_caveat_dropped {the lsearch @cmd -help no longer carries the 'punk::args fixes required' caveat}\ + -constraints have_tclcoredocs\ + -setup $common -body { + set spec [punk::args::get_spec ::lsearch] + set result [string match "*punk::args fixes required*" [dict get $spec cmd_info -help]] + }\ + -result [list 0] + } tcltest::cleanupTests ;#needed to produce test summary line. \ No newline at end of file