Browse Source

G-151 achieved: punk::args::parse_report landing report + ansistring TRUNCATE (args 0.18.0, ansi 0.2.0)

User-directed activation and implementation in one arc (proposed -> active at
the user's word with the activation-freshness overlap survey recorded, ->
achieved + archived on acceptance).

punk::args 0.18.0 - new parse_report: the annotated SUCCESS render (the
arg_error usage table's success-side sibling). One row per value-holding
argument (received, or -default in effect) in declaration-section order
(leaders, opts, values), columns Argument | Source | Value; Source carries
received / received xN / default (the plain-mode twin of the colour marking:
CLR(check)/CLR(parsekey_hint)); absent optionals get no row. Entry forms per
acceptance: a punk::args::parse result dict, or '<arglist> withid <id>' (the
parse is performed first - a rejected line raises the same validation error
parse would; errorcode parity pinned). Returns: table (bordered textblock
block, default), tableobject, string (plain lines; also the degraded
no-textblock path), dict (machine form - never elided). The canonical
demonstration pinned: {build punk95 -confirm 0} lands
'args (received x2) = -confirm 0' - a flag-like word consumed as a VALUE
visibly attributed to its consuming argument (the make.tcl dry-run
'kitname = punk91 -confirm 0' class from the G-143 sessions).

Display elision is type-aware, deterministic, always marked: strings
single-line-ized (ansistring VIEW -lf 1 -vt 1 -sp 0 - controls and embedded
ANSI render visibly so a value cannot disturb the table, real spaces stay
real) then grapheme-capped with '...' + ' (len N)'; -multiple/-type list
values element-capped '(+N more)'; -type dict values pair-capped
'(+N pairs)'. Width cascade per the G-149 plumbing precedent: caller
-valuewidth > new @cmd directive key -reportvaluewidth (documented in
define -help) > built-in 48; 0 = no cap; never console-derived (the G-007
emit-then-query lesson). Row data rides the G-049
private::parse_status_build structure - no second status assembler;
display-level bridging recovers the value-in-effect across the parse
result's storage-key folds (-parsekey renames, aliased optionsets'
last-|-member finalopts fold) so received aliased args keep their row -
engine-level rationalization recorded as a G-084 follow-on disposition.
The registered two-form definition (parsed/withid) follows parse_status's
structure - per-form @leaders first, then the shared @opts via a re-widened
@form selection (the resolver rejects leaders declared after a form's
options).

punk::ansi 0.2.0 - new ansistring TRUNCATE ?-ellipsis marker? string
maxgraphemes (design lean 4: the ANSI/grapheme-safe truncate primitive
homes in ansistring beside RANGE/VIEW): fits => byte-identical passthrough
(trailing and non-SGR codes included); truncation => RANGE-extracted prefix
(merged SGR re-application per the contract documented at 0.1.4) + marker
totalling exactly maxgraphemes, with an SGR reset appended before the
marker when the prefix carries codes; the marker is charged against the
budget and itself truncated when it alone meets/exceeds it; maxgraphemes 0
=> empty string. Grapheme-count based (column-aware variant a recorded
follow-on). Strict option handling (deliberately unlike VIEW's pinned
silent tolerance). Ensemble export + PUNKARGS argdoc + doctools comment.

Tests: new args/parsereport.test (13 - canonical attribution, entry
parity, dict shape/row order/row policy, solo+multi xN multiplicity,
per-type elision markers, dict-never-elided, width cascade,
single-line-ization, alias bridging, bordered/tableobject renders, failure
parity, bad calls, own-definition render); ansi/ansistring.test +2
TRUNCATE pins (budget/passthrough/errors; ANSI-reset + CJK +
combining-cluster safety). Full punk/args subtree 331 passed / 3
pre-existing skips / 0 failed and punk/ansi subtree 73/73 - each on native
tclsh90 (9.0.3) AND tclsh87; failure-side rendering unchanged per
acceptance. Minted modules/punk/args-0.18.0.tm + ansi-0.2.0.tm via
make.tcl modules; bootsupport/vfscommon promotion left to the next cycle -
make.tcl keeps its interim one-line report (and its 4-copy 'no annotated
success render yet' comment) until the promotion-gated follow-on lands.

Goals: G-151 entry moved to GOALS-archive.md, detail file to goals/archive/
with verification evidence and 5 Follow-on lines (make.tcl interim-report
retirement open; arg_error value-truncation via the same helpers open;
storage-key rationalization => goal G-084; column-aware TRUNCATE open;
word-oriented inverse view open). Activation-survey Related: lines recorded
(G-166, G-084, G-167 back-pointer); reference sweep updated G-170
(sequencing lean honoured - its dry-run arm consumes this render), G-167
archive marker, G-084 back-pointer note. src/tests/modules AGENTS.md
punk/args + punk/ansi bullets extended. goals_lint clean (83 active / 87
archived).

Claude-Session: https://claude.ai/code/session_0156PuejSCGjgeGb7jiABrDU
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 6 days ago
parent
commit
b0c92567a3
  1. 4
      GOALS-archive.md
  2. 4
      GOALS.md
  3. 9
      goals/G-084-punkargs-parsekey-completeness.md
  4. 72
      goals/G-151-punkargs-annotated-success-render.md
  5. 5
      goals/G-167-punkargs-version-delta-availability.md
  6. 16
      goals/G-170-launcher-help-depth.md
  7. 175
      goals/archive/G-151-punkargs-annotated-success-render.md
  8. 81
      src/modules/punk/ansi-999999.0a1.0.tm
  9. 3
      src/modules/punk/ansi-buildversion.txt
  10. 454
      src/modules/punk/args-999999.0a1.0.tm
  11. 3
      src/modules/punk/args-buildversion.txt
  12. 4
      src/tests/modules/AGENTS.md
  13. 50
      src/tests/modules/punk/ansi/testsuites/ansi/ansistring.test
  14. 309
      src/tests/modules/punk/args/testsuites/args/parsereport.test

4
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-151 [achieved 2026-08-05] punk::args annotated success render (received-args landing report) → detail: goals/archive/G-151-punkargs-annotated-success-render.md
Scope: src/modules/punk/args-999999.0a1.0.tm (parse-result presentation); src/tests/modules/punk/args/testsuites/args/
Acceptance: given a successful parse result (or the words plus definition id), a punk::args surface renders per-argument received-vs-default annotation covering leaders, opts and values including -multiple aggregation - the canonical demonstration being a flag-like word consumed as a VALUE (the 'punk91 -confirm 0'-as-three-values class) visibly attributed to its consuming argument; failure-side rendering is unchanged (existing arg_error pins pass); the behaviour is pinned in the args testsuite. make.tcl retiring its interim one-line 'dry-run: line accepted ...' report in favour of this render is recorded as consumer follow-through after a bootsupport promotion, not part of this acceptance. (Achieved as punk::args::parse_report - result-dict and words+withid entry forms, Argument|Source|Value landing table in table/tableobject/string/dict returns with the dict form never elided; type-aware always-marked display elision over the new ansistring TRUNCATE grapheme primitive (punk::ansi 0.2.0); caller -valuewidth > @cmd -reportvaluewidth > 48 width cascade; punk::args 0.18.0, new parsereport.test, args+ansi subtrees green on tclsh90 and tclsh87.)
### G-149 [achieved 2026-08-05] punk::args: configurable multi-form synopsis hint labels → detail: goals/archive/G-149-punkargs-formhint-labels.md
Scope: src/modules/punk/args-999999.0a1.0.tm (usage/arg_error multi-form Synopsis hint column); src/tests/modules/punk/args/testsuites/args/
Acceptance: a punk::args mechanism (definition directive and/or usage/arg_error caller option) replaces the ' i -form N ...' hint labels with caller-supplied text and can suppress the column entirely; unconfigured definitions render byte-identical to today (existing synopsis/usage/arg_error pins pass unchanged); the mechanism is pinned in the args testsuite. make.tcl adopting it (e.g a 'make.tcl help tool <action>' phrasing) is recorded as consumer follow-through after a bootsupport promotion, not part of this acceptance. (Achieved with both arms: '@cmd -formhint <template>' definition key + '-formhint' caller option on arg_error/usage, %formindex%/%formname% substitution, empty template suppresses; punk::args 0.17.0, new formhint.test, verified tclsh90 + punk86.)

4
GOALS.md

@ -372,10 +372,6 @@ Detail: goals/G-147-libfetch-revision-check.md
Scope: src/modules/punk/mix/ (vendor-sync command surface - discovery beside the G-065 sync); punkproject.toml or sibling vendor manifest (G-065 declaration schema as consumed); src/tests/modules/punk/mix/ (discovery testsuite with local fixture upstreams)
Detail: goals/G-148-vendor-upstream-discovery.md
### G-151 [proposed] punk::args annotated success render (received-args landing report)
Scope: src/modules/punk/args-999999.0a1.0.tm (parse-result presentation); src/tests/modules/punk/args/testsuites/args/
Detail: goals/G-151-punkargs-annotated-success-render.md
### G-156 [proposed] Alpha-window identifier alignment: persisted build-named identities renamed while consumers are none, with derived-project census
Scope: src/modules/punk/mix/cli-999999.0a1.0.tm + src/modules/punk/mix/base-999999.0a1.0.tm (mint-named installer surface, identity migration); src/make.tcl (BUILDCOPY -> BAKECOPY, identity references, buildversion.txt readers); modules/.punkcheck + derived-project .punkcheck ledgers (identity migration or accepted re-record); src/modules/*-buildversion.txt sidecar convention + module-header boilerplate (disposition decided at activation); scriptlib/developer/ (census scanner + committed census report); src/project_layouts + src/modules/punk/mix/#modpod-templates-999999.0a1.0/ (re-seeded identities via established sync channels); machine-wide census over user-confirmed roots (fossil config-db enumeration + bounded filesystem scan, punkorigin.toml + pre-G-087 marker fallbacks, siblings incl c:/repo/jn/tclmodules/tomlish + c:/repo/jn/serversecretsync + c:/repo/jn/punkbin); punkbin repo schema fields (build_id, build_sha1sums.tcl - disposition only)
Detail: goals/G-156-alpha-window-identifier-alignment.md

9
goals/G-084-punkargs-parsekey-completeness.md

@ -48,3 +48,12 @@ _GAP test in src/tests/modules/punk/args/testsuites/args/parsekey.test:
- Related: G-083 (argument relations - shared design context for the
cross-member collection decision), 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
last |-member, tried when the direct ARG_INFO-name value-in-effect lookup
misses; pinned by parsereport.test parsereport_aliased_optionset_row_bridged).
A G-151 follow-on assigns the engine-level rationalization of storage keys
vs definition argument names to this goal - when that lands, the display
bridge in parse_report (and the matching miss noted in
private::parse_status_build's comment) can retire.

72
goals/G-151-punkargs-annotated-success-render.md

@ -1,72 +0,0 @@
# G-151 punk::args annotated success render (received-args landing report)
Status: proposed
Scope: src/modules/punk/args-999999.0a1.0.tm (parse-result presentation); src/tests/modules/punk/args/testsuites/args/
Goal: punk::args can render a SUCCESSFUL parse the way arg_error renders a failed one - the usage table (or a compact per-argument report) annotated with where each received word landed: options consumed, values attributed to the argument that took them, received distinguished from defaulted - so dry-run consumers (make.tcl help today; completion/hinting later) present authoritative landing information instead of hand-assembled summaries.
Acceptance: given a successful parse result (or the words plus definition id), a punk::args surface renders per-argument received-vs-default annotation covering leaders, opts and values including -multiple aggregation - the canonical demonstration being a flag-like word consumed as a VALUE (the 'punk91 -confirm 0'-as-three-values class) visibly attributed to its consuming argument; failure-side rendering is unchanged (existing arg_error pins pass); the behaviour is pinned in the args testsuite. make.tcl retiring its interim one-line 'dry-run: line accepted ...' report in favour of this render is recorded as consumer follow-through after a bootsupport promotion, not part of this acceptance.
## Context
From the G-143 dry-run help sessions (2026-08-01/02 - archived detail file): the failure side has arg_error's annotated tables, but a successful dry-run had nothing showing where the supplied words landed, so make.tcl grew an interim one-line report built from the parse result's 'received' key ('dry-run: line accepted (form build) - action = build | -test = 0 | toolname = punkzip'; before bake kitnames became choice-restricted, the value-swallow clue read 'kitname = punk91 -confirm 0'). The parse result already carries received/leaders/opts/values/multis - this goal is a presentation surface over existing data, at punk::args level where every consumer (and the repl) can share it.
## Notes
- 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.
- Design leans (user-agreed 2026-08-05, ahead of activation):
1. Shape: a COMPACT LANDING TABLE - a separate small arg_error-styled bordered
table (Argument | Source | Value) stacked under the usage render in dry-run
contexts, NOT annotation of the usage table itself (the help column already
dominates width and values are the one input authors cannot bound). The
existing -parsedargs row marking and goodchoice word marking stay as-is and
compose with it. No per-value/per-arg subtables (the -choices subtables earn
their borders as static grouped documentation; received values are transient
echo) - the report itself is the one bordered block; if G-088's footer
mechanism lands later the same content could dock into the usage table's
footer without redesign. Arg-oriented rows in declaration-section order
(leaders/opts/values); a word-oriented inverse view (word -> argument) is a
possible later -return variant, not the primary.
2. Width: deterministic caps, never console-queried (piped contexts + the
emit-then-query flush trap of G-007 (achieved - goals/archive/G-007-console-location-transparency.md);
pinned tests want deterministic output). Value column
capped ~48 for a ~80 total by default; configurable via the G-149 plumbing
precedent (caller option > @cmd definition key > default). Machine-facing
returns (-return dict) never truncate - elision is display-only.
3. Elision is type-aware and always marked (display-hint honesty): strings are
single-line-ized (newlines/tabs as visible escapes) then char-capped with
trailing '...' plus '(len N)'; lists and -multiple aggregations are
element-capped with '(+N more)'; dicts pair-capped with '(+N pairs)'.
Pre-elide at data-prep level rather than relying on the textblock table
class's per-column -maxwidth (recorded header-truncation TODOs there; and
pre-elided strings make the plain twin and test pins trivial).
4. The ANSI/grapheme-safe char-level truncate primitive lives in
punk::ansi::ansistring (beside trim/RANGE/VIEW, per the ANSI-surgery
domain rule); punk::args layers only the element/pair-count elision on
top. Precondition (user direction 2026-08-05): before modifying
ansistring, survey its test coverage against codebase usage and add
missing characterization pins so the modification has a safety net.
Survey DONE 2026-08-05 (usage-weighted: VIEW ~250 sites incl 124 '-lf 1';
VIEWSTYLE/VIEWCODES/NEW ~40 interactive sites; RANGE 8): 8 pins added to
ansistring.test, green 71/71 on tclsh90+tcl87. Findings the implementation
must respect: RANGE re-emits MERGED code stacks (not byte-identical
round-trip) and DROPS codes trailing the last grapheme (source TODO) - a
truncate building on RANGE must fetch trailing state separately (INDEXCODE
end+1 works) if it matters; VIEW -lf 1 is the proven single-line-ization
primitive; VIEW silently tolerates unknown option pairs (overtype's
'-nul 1' sites depend on this - NUL substitution is actually always-on);
VIEWCODES/VIEWSTYLE are broken headless (bare 'a+'/'a' interactive-alias
dependency, known-deficiency pinned) - do not route the truncate through
the class view paths.
5. Serving the failure-side value-truncation todo recorded in the engine
("truncate/summarize values in error messages", args-999999.0a1.0.tm
arg_error region) with the same helper is a FOLLOW-ON, not part of this
goal's acceptance.
6. Row policy: rows only for arguments holding a value post-parse (received,
or default-in-effect - the Source column carries the received/default
distinction and is the plain-mode-safe twin of the colour marking);
absent optionals get no row.

5
goals/G-167-punkargs-version-delta-availability.md

@ -116,4 +116,7 @@ deltas use -choiceunavailable; forms are reserved for true shape deltas.
ns-side G-052 G-085): besides those named above, the family entries are
parser/spec/display mechanisms with no availability interaction - deliberate
non-overlaps (G-050's validity marking is the nearest sibling surface;
reuse its status-aware return conventions if it lands first).
reuse its status-aware return conventions if it lands first). G-151 since
achieved 2026-08-05 (goals/archive/G-151-punkargs-annotated-success-render.md) -
its parse_report landing table is another argument-row surface this goal's
unavailable marking would eventually render over.

16
goals/G-170-launcher-help-depth.md

@ -16,12 +16,16 @@ 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-151 (achieved 2026-08-05 - goals/archive/G-151-punkargs-annotated-success-render.md) -
the dry-run arm's received-args report is G-151's punk::args-level annotated
success render, now landed as punk::args::parse_report (punk::args 0.18.0,
with the ansistring TRUNCATE elision primitive in punk::ansi 0.2.0): the
launcher's dry-run arm consumes it directly instead of hand-assembling a
second interim make.tcl-style one-liner (make.tcl's own interim report
retirement is an open G-151 follow-on gated on a bootsupport promotion).
Cross-link added 2026-08-05 at G-151 drafting (the drafting overlap survey
was path-based and could not surface G-151's args-module scope); sequencing
lean honoured - G-151 landed first.
- 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,

175
goals/archive/G-151-punkargs-annotated-success-render.md

@ -0,0 +1,175 @@
# G-151 punk::args annotated success render (received-args landing report)
Status: achieved 2026-08-05
Scope: src/modules/punk/args-999999.0a1.0.tm (parse-result presentation); src/tests/modules/punk/args/testsuites/args/
Goal: punk::args can render a SUCCESSFUL parse the way arg_error renders a failed one - the usage table (or a compact per-argument report) annotated with where each received word landed: options consumed, values attributed to the argument that took them, received distinguished from defaulted - so dry-run consumers (make.tcl help today; completion/hinting later) present authoritative landing information instead of hand-assembled summaries.
Acceptance: given a successful parse result (or the words plus definition id), a punk::args surface renders per-argument received-vs-default annotation covering leaders, opts and values including -multiple aggregation - the canonical demonstration being a flag-like word consumed as a VALUE (the 'punk91 -confirm 0'-as-three-values class) visibly attributed to its consuming argument; failure-side rendering is unchanged (existing arg_error pins pass); the behaviour is pinned in the args testsuite. make.tcl retiring its interim one-line 'dry-run: line accepted ...' report in favour of this render is recorded as consumer follow-through after a bootsupport promotion, not part of this acceptance.
## Context
From the G-143 dry-run help sessions (2026-08-01/02 - archived detail file): the failure side has arg_error's annotated tables, but a successful dry-run had nothing showing where the supplied words landed, so make.tcl grew an interim one-line report built from the parse result's 'received' key ('dry-run: line accepted (form build) - action = build | -test = 0 | toolname = punkzip'; before bake kitnames became choice-restricted, the value-swallow clue read 'kitname = punk91 -confirm 0'). The parse result already carries received/leaders/opts/values/multis - this goal is a presentation surface over existing data, at punk::args level where every consumer (and the repl) can share it.
## Notes
- 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.
- Activation survey 2026-08-05 (goals_xref score G-151, per the activation-freshness
rule) - relationships not previously recorded:
- Related: G-166 (proposed) - top unlinked hit: cmdinfo/cmdhelp advisory-parse
presentation on the punk::ns side; a sibling presentation surface over parse
outcomes and a possible later consumer of this goal's landing report (cmdhelp
advisory parses of argument tails).
- Related: G-084 (proposed) - -parsekey completeness. The parse result dict this
goal renders from folds aliased optionset keys to their last |-member and
-parsekey renames storage keys; the landing report's value-in-effect lookup
bridges that fold at display level (see Progress) - G-084 is where the
storage-key model itself gets rationalized.
- Related: G-167 (proposed) - back-pointer (G-167 already references this goal):
version-delta availability marking renders over the same argument-row surfaces.
- Design leans (user-agreed 2026-08-05, ahead of activation):
1. Shape: a COMPACT LANDING TABLE - a separate small arg_error-styled bordered
table (Argument | Source | Value) stacked under the usage render in dry-run
contexts, NOT annotation of the usage table itself (the help column already
dominates width and values are the one input authors cannot bound). The
existing -parsedargs row marking and goodchoice word marking stay as-is and
compose with it. No per-value/per-arg subtables (the -choices subtables earn
their borders as static grouped documentation; received values are transient
echo) - the report itself is the one bordered block; if G-088's footer
mechanism lands later the same content could dock into the usage table's
footer without redesign. Arg-oriented rows in declaration-section order
(leaders/opts/values); a word-oriented inverse view (word -> argument) is a
possible later -return variant, not the primary.
2. Width: deterministic caps, never console-queried (piped contexts + the
emit-then-query flush trap of G-007 (achieved - goals/archive/G-007-console-location-transparency.md);
pinned tests want deterministic output). Value column
capped ~48 for a ~80 total by default; configurable via the G-149 plumbing
precedent (caller option > @cmd definition key > default). Machine-facing
returns (-return dict) never truncate - elision is display-only.
3. Elision is type-aware and always marked (display-hint honesty): strings are
single-line-ized (newlines/tabs as visible escapes) then char-capped with
trailing '...' plus '(len N)'; lists and -multiple aggregations are
element-capped with '(+N more)'; dicts pair-capped with '(+N pairs)'.
Pre-elide at data-prep level rather than relying on the textblock table
class's per-column -maxwidth (recorded header-truncation TODOs there; and
pre-elided strings make the plain twin and test pins trivial).
4. The ANSI/grapheme-safe char-level truncate primitive lives in
punk::ansi::ansistring (beside trim/RANGE/VIEW, per the ANSI-surgery
domain rule); punk::args layers only the element/pair-count elision on
top. Precondition (user direction 2026-08-05): before modifying
ansistring, survey its test coverage against codebase usage and add
missing characterization pins so the modification has a safety net.
Survey DONE 2026-08-05 (usage-weighted: VIEW ~250 sites incl 124 '-lf 1';
VIEWSTYLE/VIEWCODES/NEW ~40 interactive sites; RANGE 8): 8 pins added to
ansistring.test, green 71/71 on tclsh90+tcl87. Findings the implementation
must respect: RANGE re-emits MERGED code stacks (not byte-identical
round-trip) and DROPS codes trailing the last grapheme (source TODO) - a
truncate building on RANGE must fetch trailing state separately (INDEXCODE
end+1 works) if it matters; VIEW -lf 1 is the proven single-line-ization
primitive; VIEW silently tolerates unknown option pairs (overtype's
'-nul 1' sites depend on this - NUL substitution is actually always-on);
VIEWCODES/VIEWSTYLE are broken headless (bare 'a+'/'a' interactive-alias
dependency, known-deficiency pinned) - do not route the truncate through
the class view paths.
5. Serving the failure-side value-truncation todo recorded in the engine
("truncate/summarize values in error messages", args-999999.0a1.0.tm
arg_error region) with the same helper is a FOLLOW-ON, not part of this
goal's acceptance.
6. Row policy: rows only for arguments holding a value post-parse (received,
or default-in-effect - the Source column carries the received/default
distinction and is the plain-mode-safe twin of the colour marking);
absent optionals get no row.
## Progress
Achieved 2026-08-05 in one session (user-directed activation and implementation
same day; punk::args 0.18.0 + punk::ansi 0.2.0). All six design leans enacted
as recorded above.
Mechanism landed:
- New `punk::args::parse_report` - the landing report render. Entry forms per
the acceptance: a punk::args::parse result dict (its id key resolved via
real_id/get_spec; the result's form key selects the displayed form), or
`<arglist> withid <id>` (the parse is performed first; a rejected line
raises the same validation error punk::args::parse raises - errorcode
parity pinned). Hand-rolled argument processing in the parse/parse_status
style (self-parse against the registered two-form definition only on the
unhappy path, so a report render never recurses through a second live
parse); the registered definition (forms 'parsed'/'withid') follows
parse_status's shared-block structure - per-form @leaders first, then the
shared @opts, then per-form @values (a shared-then-narrow @leaders ordering
is rejected by the resolver: leaders must precede options per form).
- Row data rides the existing G-049 machinery: private::parse_status_build
-ok 1 -parsedargs <result> supplies per-argument class/received/positions/
hasvalue/value in declaration-section order (leaders, opts, values) - G-151
adds no second status assembler. Display-level bridging recovers the
value-in-effect where the build's direct ARG_INFO-name lookup misses the
parse result's storage-key folds (-parsekey renames; aliased optionsets
stored under their last |-member) so received aliased/parsekey'd arguments
keep their row (G-084 owns the storage-key model itself; pinned via the
-f|-file fixture).
- Returns: table (arg_error-styled bordered textblock block, title default
'Received', columns Argument | Source | Value; colour bootstrap shares
arg_error's CLR base + scheme-override + colour-flip reload pattern),
tableobject, string (plain 'name (source) = value' lines; also the
degraded no-textblock path), dict (machine form id/form/rows - never
elided). Source column: received / received xN / default (CLR(check) /
CLR(parsekey_hint) colouring; words stay the plain-mode distinction).
- Elision (lean 3): strings single-line-ized via ansistring VIEW -lf 1 -vt 1
-sp 0 (controls AND embedded ANSI render visibly, so a value cannot
disturb the table; real spaces stay real) then grapheme-capped via the new
ansistring TRUNCATE with '...' + ' (len N)' (N = raw string length);
-multiple/-type list values element-capped with '(+N more)' (whole
list-quoted elements while they fit, first element char-capped if it alone
overflows); -type dict values pair-capped with '(+N pairs)'; plain-string
fallbacks when punk::ansi is unavailable. Width cascade (lean 2, G-149
plumbing precedent): caller -valuewidth > new @cmd directive key
-reportvaluewidth (documented in define -help) > built-in 48; 0 = no cap;
never console-derived.
- The truncate primitive (lean 4): `ansistring TRUNCATE ?-ellipsis marker?
string maxgraphemes` in punk::ansi 0.2.0 - fits => byte-identical
passthrough (trailing/non-SGR codes included); truncation => RANGE-
extracted prefix (merged SGR re-application per the 0.1.4-documented
contract) + marker totalling exactly maxgraphemes, with an SGR reset
appended before the marker when the prefix carries codes (RANGE re-applies
stacks with no trailing reset - the survey finding); marker charged
against the budget and itself truncated when it alone meets/exceeds it;
grapheme-count based (CJK counts 1/each; column-aware variant recorded as
a follow-on). Strict option handling (deliberately unlike VIEW's pinned
silent tolerance). Ensemble export + argdoc + doctools added.
Verification evidence:
- New suite src/tests/modules/punk/args/testsuites/args/parsereport.test
(13 tests) - the acceptance's canonical demonstration pinned exactly
(fixture parse of {build punk95 -confirm 0}: the flag-like word -confirm
and its 0 land as VALUES attributed 'args (received x2) = -confirm 0'
beside 'kitname (received) = punk95' and '-test (default) = 0'); entry
parity, dict shape/row order/row policy, solo+multi multiplicity, elision
markers by type, dict-never-elided, width cascade, single-line-ization,
alias bridging, bordered/tableobject renders, failure parity, bad-call
errors, own-definition render.
- ansistring.test +2 TRUNCATE pins (budget/passthrough/errors; ANSI-reset +
CJK + combining-cluster safety).
- Full modules/punk/args subtree 331 passed / 3 pre-existing skips / 0
failed, and modules/punk/ansi subtree 73/73, each on BOTH native tclsh90
(Tcl 9.0.3) and tclsh87 - failure-side rendering unchanged per acceptance
(all existing arg_error/usage/synopsis/marking pins pass untouched).
- Minted modules/punk/args-0.18.0.tm + modules/punk/ansi-0.2.0.tm via
make.tcl modules.
- make.tcl's interim one-line report (bootsupport punk::args) is untouched
until a bootsupport promotion carries 0.18.0 - recorded consumer
follow-through below, per the acceptance.
## Follow-ons
Follow-on: make.tcl retires its interim one-line 'dry-run: line accepted ...' report in favour of parse_report (stacked under the usage render per lean 1) after a bootsupport promotion carrying punk::args 0.18.0- and punk::ansi 0.2.0- => open
Follow-on: serve arg_error's failure-side value-truncation todo ('truncate/summarize values in error messages', args-999999.0a1.0.tm arg_error region) with the same report_display_value/TRUNCATE helpers (lean 5 - explicitly excluded from this goal's acceptance) => open
Follow-on: engine-level rationalization of parse-result storage keys vs definition argument names (aliased optionset last-|-member fold, -parsekey renames) so value-in-effect lookups need no display-level bridging => goal G-084
Follow-on: column-aware (display-width) TRUNCATE variant for CJK-heavy values (the grapheme-count cap under-counts double-wide content; noted in the TRUNCATE argdoc) => open
Follow-on: word-oriented inverse view (word -> consuming argument, e.g a -return words variant) as completion/hinting-facing data - lean 1 records it as a possible later variant, not the primary => open

81
src/modules/punk/ansi-999999.0a1.0.tm

@ -10504,7 +10504,7 @@ tcl::namespace::eval punk::ansi::ansistring {
tcl::namespace::path [list ::punk::ansi ::punk::ansi::ta]
tcl::namespace::ensemble create
tcl::namespace::export length trim trimleft trimright INDEX INDEXCODE INDEXCHAR RANGE INSERT COUNT COUNT2 COUNT3 VIEW VIEWCODES VIEWSTYLE INDEXABSOLUTE INDEXCOLUMNS COLUMNINDEX NEW
tcl::namespace::export length trim trimleft trimright INDEX INDEXCODE INDEXCHAR RANGE TRUNCATE INSERT COUNT COUNT2 COUNT3 VIEW VIEWCODES VIEWSTYLE INDEXABSOLUTE INDEXCOLUMNS COLUMNINDEX NEW
#todo - expose _splits_ methods so caller can work efficiently with the splits themselves
#we need to consider whether these can be agnostic towards splits from split_codes vs split_codes_single
@ -11707,6 +11707,85 @@ tcl::namespace::eval punk::ansi::ansistring {
return $rangeresult
}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::punk::ansi::ansistring::TRUNCATE
@cmd -name punk::ansi::ansistring::TRUNCATE\
-summary\
"Truncate to a maximum grapheme count, ANSI-aware, with an ellipsis marker."\
-help\
"Return string unchanged (byte-identical) if its grapheme count
(as for ansistring COUNT) is at most maxgraphemes - otherwise
return the leading graphemes with the ellipsis marker appended,
such that the result's total grapheme count is maxgraphemes.
The kept prefix is extracted as for ansistring RANGE, so SGR
codes in effect over the kept graphemes are re-applied in
merged (canonicalized) form - and when that prefix carries any
ANSI codes an SGR reset is appended before the marker so the
marker and any following text render unstyled.
The marker participates in the budget: the prefix keeps
maxgraphemes minus the marker's grapheme count. If the marker
alone meets or exceeds the budget, the result is the marker
itself truncated to maxgraphemes. maxgraphemes 0 returns an
empty string.
Truncation counts graphemes, not display columns - wide (e.g
CJK) graphemes count 1 each, as for COUNT."
@opts
-ellipsis -type string -default "..." -help\
"Marker appended when truncation occurs.
May itself contain ANSI codes; its grapheme count is charged
against maxgraphemes."
@values -min 2 -max 2
string -type string
maxgraphemes -type integer -help\
"Maximum grapheme count of the returned string (>= 0)."
} ]
}
proc TRUNCATE {args} {
#*** !doctools
#[call [fun TRUNCATE] [opt "-ellipsis marker"] [arg string] [arg maxgraphemes]]
#[para]Returns string unchanged (byte-identical) if its grapheme count (as for COUNT) is at most maxgraphemes - otherwise returns the leading graphemes with the marker (default ...) appended, such that the result's total grapheme count equals maxgraphemes.
#[para]The kept prefix is extracted as for RANGE (merged SGR re-application); when that prefix carries ANSI codes an SGR reset precedes the marker so the marker and any following text render unstyled. The marker's grapheme count is charged against maxgraphemes; a marker that alone meets or exceeds the budget is itself truncated to maxgraphemes. Grapheme-count based, not display-column based.
set ellipsis "..."
set arglist $args
while {[llength $arglist] > 2} {
switch -- [lindex $arglist 0] {
-ellipsis {
set ellipsis [lindex $arglist 1]
set arglist [lrange $arglist 2 end]
}
default {
error "ansistring TRUNCATE unknown option '[lindex $arglist 0]'. Known options: -ellipsis"
}
}
}
if {[llength $arglist] != 2} {
error "ansistring TRUNCATE requires: ?-ellipsis marker? string maxgraphemes"
}
lassign $arglist string maxgraphemes
if {![tcl::string::is integer -strict $maxgraphemes] || $maxgraphemes < 0} {
error "ansistring TRUNCATE maxgraphemes must be an integer >= 0. Received '$maxgraphemes'"
}
if {$maxgraphemes == 0} {
return ""
}
if {[COUNT $string] <= $maxgraphemes} {
return $string
}
set keep [expr {$maxgraphemes - [COUNT $ellipsis]}]
if {$keep <= 0} {
#marker alone meets/exceeds the budget - return as much of the marker as fits
return [RANGE $ellipsis 0 [expr {$maxgraphemes - 1}]]
}
set prefix [RANGE $string 0 [expr {$keep - 1}]]
if {[tcl::string::first \x1b $prefix] >= 0} {
#the prefix carries ANSI codes (RANGE re-applies SGR stacks with no trailing
#reset) - close styling so the marker and any following text render unstyled
append prefix "\x1b\[0m"
}
return $prefix$ellipsis
}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::punk::ansi::ansistring::INSERT

3
src/modules/punk/ansi-buildversion.txt

@ -1,6 +1,7 @@
0.1.4
0.2.0
#First line must be a semantic version number
#all other lines are ignored.
#0.2.0 - G-151: new ansistring TRUNCATE ?-ellipsis marker? string maxgraphemes - ANSI/grapheme-safe display truncation primitive (homed beside RANGE/VIEW per the ANSI-surgery domain rule; punk::args::parse_report 0.18.0 is the driving consumer). Returns string unchanged (byte-identical - trailing and non-SGR codes included) when its grapheme count (as for COUNT) is at most maxgraphemes; otherwise the leading graphemes extracted as for RANGE (merged/canonicalized SGR re-application per the contract documented at 0.1.4) with the marker appended such that the result's total grapheme count equals maxgraphemes. The marker (default '...') charges its grapheme count against the budget; a marker alone meeting/exceeding the budget is itself truncated to maxgraphemes; maxgraphemes 0 returns empty string; negative or non-integer maxgraphemes errors. When the kept prefix carries ANSI codes an SGR reset is appended before the marker (RANGE re-applies stacks with no trailing reset) so the marker and any following text render unstyled. Grapheme-count based, not display-column based (wide CJK graphemes count 1 each - a column-aware variant is a possible follow-on). Strict option handling (unknown option errors - unlike VIEW's pinned silent tolerance). Ensemble export + PUNKARGS argdoc + doctools comment added. New pins in ansi/ansistring.test.
#0.1.4 - documentation-only: ansistring RANGE argdoc + doctools comment now state the pinned contract (tests ansi/ansistring.test, 2026-08-05 coverage survey): merged/canonicalized SGR re-emission (style-equivalent, not byte-identical), SGR-only carriage (non-SGR sequence types not re-emitted), trailing-code exclusion with INDEXCODE end+1 as the retrieval mechanism, and clamping/empty-range semantics. Summary reworded (was 'codes preserved' - overclaimed). Open design questions (trailing codes on literal end, non-SGR retention, charset/gx0 handling) stay as code TODO/review comments and G-151 Notes - deliberately not promised in the doc.
#0.1.3 - documentation-only: grepstr -return summarydict choicelabel corrected - the summary dict's 'linemap' key is always present (the old text claimed it appeared only with -n; actual always-present behaviour pinned in tests ansi/grepstr.test). Reconciliation deferred from the grepstr characterization wave to the punk::ns hygiene pass.
#0.1.2 - documentation-only: PUNKARGS argdoc blocks added for punk::ansi::codetype (is_sgr_reset, has_sgr_leadingreset, is_cursor_move_in_line, has_all_effective, get_effective_types, is_gx/is_gx_open/is_gx_close, sgr_merge), punk::ansi::sequence_type (is_Fe7/is_Fe/is_Fe8, is_Fp, is_Fs, is_nF, is_3Fp, is_code7/is_code8/is_code, classify), punk::ansi::ta (detect_in_list, detectcode, detectcode_in_list, detect_g0, detect_open, detect_st_open, detect_csi, detect_sgr, split_at_codes, split_codes, split_codes_single, get_codes_single) and punk::ansi::ansistring (VIEW, COUNT, length, trimleft/trimright/trim, INDEX, INDEXCHAR, RANGE, INSERT, INDEXABSOLUTE, INDEXCOLUMNS, COLUMNINDEX); ::punk::ansi::sequence_type added to punk::args::register NAMESPACES so its argdocs are discoverable

454
src/modules/punk/args-999999.0a1.0.tm

@ -810,6 +810,13 @@ tcl::namespace::eval punk::args {
which are unreachable from any repl - so the hint can
name that consumer's own navigation instead. Callers of
usage/arg_error may override with their -formhint option.
-reportvaluewidth <int>
Display cap (in graphemes) for the Value column of
punk::args::parse_report's landing table when a
successful parse of this definition is rendered.
0 means no cap. Callers of parse_report may override
with its -valuewidth option; with neither present the
built-in default (48) applies.
%B%@leaders%N% ?opt val...?
(used for leading args that come before switches/opts)
directive-options:
@ -8103,6 +8110,453 @@ tcl::namespace::eval punk::args {
return [private::parse_status_build $spec_dict $fid -ok 1 -status valid -scheme info -parsedargs $r -formstatus $formstatus]
}
lappend PUNKARGS [list {
@id -id ::punk::args::parse_report
@cmd -name punk::args::parse_report\
-summary\
"Landing report for a successful parse - each value-holding argument with its source and value."\
-help\
"Render a compact landing table for a SUCCESSFUL parse - one row
per argument holding a value after the parse (received, or filled
from its -default), in declaration order by section (leaders, then
options, then values), with columns Argument, Source and Value.
This is the success-side sibling of the arg_error usage table:
where arg_error diagnoses a rejected line, parse_report shows
where each word of an accepted line landed - options consumed,
values attributed to the argument that took them (including a
flag-like word consumed as a VALUE by position), -multiple
aggregations, and received distinguished from defaulted via the
Source column (received / received xN / default). Arguments
holding no value (absent optionals with no -default) get no row.
The first argument is either the result dict of a prior successful
punk::args::parse (its id key must resolve to a registered
definition), or a list of words to parse followed by 'withid' and
the definition id. In the words form the parse is performed first,
and a validation failure raises the same error punk::args::parse
would - only successful parses have a landing report (see
arg_error/parse_status for failure presentation).
Value cells are display-elided to fit -valuewidth: strings are
rendered one-line (control characters as visible substitutes, as
for ansistring VIEW) and char-capped with a trailing '...' plus
' (len N)'; -multiple aggregations and -type list values are
element-capped with '(+N more)'; -type dict values are pair-capped
with '(+N pairs)'. Elision is display-only and always marked - the
-return dict form is never elided."
@form -form parsed
@leaders -min 1 -max 1
parsedresult -type dict -help\
"Result dict of a prior successful punk::args::parse
(keys: leaders opts values received solos multis id form)"
@form -form withid
@leaders -min 1 -max 1
arglist -type list -help\
"Words to parse - supplied as a single list"
@form -form {parsed withid}
@opts -prefix 0
-scheme -default info -choices {nocolour info error}
-return -default table -choices {table tableobject string dict} -choicelabels {
table "text of framed landing table"
tableobject "table object cmd"
string "plain lines, no table layout"
dict "machine form - never elided"
}
-title -type string -default "Received" -help\
"Title for the landing table frame."
-valuewidth -type integer -help\
"Display cap (graphemes) for the Value column - 0 means no cap.
When not supplied: a definition-level '@cmd -reportvaluewidth'
applies, else the built-in default 48. Deliberately never derived
from console width - report output is deterministic (and
pipe-safe) for a given parse and configuration. Elision markers
stay intact, so cells can exceed very small caps by the marker
width."
-caller -type string -default "" -help\
"Caller attribution for the validation failure message when the
words form's parse fails (see punk::args::parse -caller)."
@form -form parsed
@values -min 0 -max 0
@form -form withid
@values -min 2 -max 2
withid -type literal(withid) -help\
"The literal value 'withid'"
id -type string -help\
"id of punk::args definition for a command"
}]
proc parse_report {args} {
#G-151 annotated success render. Hand-rolled argument processing in the style of
#parse/parse_status (self-parse against the documented definition only on the
#unhappy path - so a report render never recurses through a second live parse).
if {[llength $args] < 1} {
punk::args::parse $args withid ::punk::args::parse_report
error "punk::args::parse_report - internal argument processing mismatch for '$args'"
}
set payload [lindex $args 0]
set rest [lrange $args 1 end]
set opts [dict create {*}{
-scheme info
-return table
-title Received
-caller {}
}]
#-valuewidth deliberately absent from the defaults: absence selects the
#cascade (caller option > @cmd -reportvaluewidth > built-in 48)
set tail [list]
for {set i 0} {$i < [llength $rest]} {incr i} {
set k [lindex $rest $i]
if {$k eq "withid"} {
set tail [lrange $rest $i end]
break
}
switch -- $k {
-scheme - -return - -title - -valuewidth - -caller {
if {$i + 1 >= [llength $rest]} {
#missing option value - the documented definition raises
punk::args::parse $args withid ::punk::args::parse_report
error "punk::args::parse_report - internal argument processing mismatch for '$args'"
}
incr i
dict set opts $k [lindex $rest $i]
}
default {
#unknown option or stray word - the documented definition raises
punk::args::parse $args withid ::punk::args::parse_report
error "punk::args::parse_report - internal argument processing mismatch for '$args'"
}
}
}
switch -- [dict get $opts -return] {
table - tableobject - string - dict {}
default {
punk::args::parse $args withid ::punk::args::parse_report
error "punk::args::parse_report - internal argument processing mismatch for '$args'"
}
}
set returntype [dict get $opts -return]
switch -- [dict get $opts -scheme] {
nocolour - nocolor {set scheme nocolour}
info {set scheme info}
error {set scheme error}
default {
punk::args::parse $args withid ::punk::args::parse_report
error "punk::args::parse_report - internal argument processing mismatch for '$args'"
}
}
if {[llength $tail]} {
#words form - payload is the words, tail is: withid <id>
if {[llength $tail] != 2} {
punk::args::parse $args withid ::punk::args::parse_report
error "punk::args::parse_report - internal argument processing mismatch for '$args'"
}
set id [lindex $tail 1]
if {[real_id $id] eq ""} {
error "punk::args::parse_report - no such id: $id"
}
set parseopts [list]
if {[dict get $opts -caller] ne ""} {
lappend parseopts -caller [dict get $opts -caller]
}
#a rejected line raises the same validation error punk::args::parse would -
#only successful parses have a landing report
set parsedresult [punk::args::parse $payload {*}$parseopts withid $id]
} else {
set parsedresult $payload
if {[catch {tcl::dict::size $parsedresult}] || ![tcl::dict::exists $parsedresult received]} {
error "punk::args::parse_report - first argument is not a punk::args::parse result dict (and no 'withid <id>' tail was supplied)"
}
}
#resolve the definition and the displayed form from the parse result
set docid [Dict_getdef $parsedresult id ""]
set real [real_id $docid]
if {$real eq ""} {
error "punk::args::parse_report - cannot resolve a registered definition from the parse result's id '$docid' - use the words form: parse_report <arglist> withid <id>"
}
set spec_dict [get_spec $real]
#G-046: expand display-deferred field content before display use (as arg_error does)
if {[tcl::dict::exists $spec_dict DISPLAY_DEFERRED] && [tcl::dict::size [tcl::dict::get $spec_dict DISPLAY_DEFERRED]]} {
set spec_dict [private::expand_display_fields $spec_dict]
}
set fid [Dict_getdef $parsedresult form 0]
if {![tcl::dict::exists $spec_dict FORMS $fid]} {
error "punk::args::parse_report - parse result names form '$fid' which is not present in definition '$real'"
}
set ARG_INFO [tcl::dict::get $spec_dict FORMS $fid ARG_INFO]
#-valuewidth cascade: caller option > @cmd -reportvaluewidth > built-in 48
if {[tcl::dict::exists $opts -valuewidth]} {
set valuewidth [tcl::dict::get $opts -valuewidth]
} else {
set valuewidth [Dict_getdef $spec_dict cmd_info -reportvaluewidth 48]
}
if {![tcl::string::is integer -strict $valuewidth] || $valuewidth < 0} {
error "punk::args::parse_report -valuewidth must be an integer >= 0 (0 means no cap). Received '$valuewidth'"
}
#per-argument landing data - parse_status_build inserts argstatus entries in
#declaration-section order (leaders, opts, values), which is the row order
set PSTAT [private::parse_status_build $spec_dict $fid -ok 1 -status valid -scheme info -parsedargs $parsedresult]
set report_rows [tcl::dict::create]
tcl::dict::for {argname AST} [tcl::dict::get $PSTAT argstatus] {
set class [tcl::dict::get $AST class]
set received [tcl::dict::get $AST received]
set hasvalue [tcl::dict::get $AST hasvalue]
set value [tcl::dict::get $AST value]
if {!$hasvalue} {
#bridge the storage-key folds the build's direct ARG_INFO-name lookup
#misses: -parsekey renames the storage key; aliased optionsets store
#under their last |-member (the finalopts fold in get_dict). G-084 owns
#the storage-key model - this is display-level bridging only.
switch -- $class {
leader {set sectionkey leaders}
option {set sectionkey opts}
default {set sectionkey values}
}
set storekey [Dict_getdef $ARG_INFO $argname -parsekey ""]
if {$storekey eq "" && [tcl::string::first | $argname] >= 0} {
set storekey [lindex [split $argname |] end]
}
if {$storekey ne "" && [tcl::dict::exists $parsedresult $sectionkey $storekey]} {
set hasvalue 1
set value [tcl::dict::get $parsedresult $sectionkey $storekey]
}
}
if {!$hasvalue && $received == 0} {
continue ;#absent optional with no default-in-effect - no row
}
set is_multiple 0
if {[tcl::dict::exists $ARG_INFO $argname -multiple] && [tcl::dict::get $ARG_INFO $argname -multiple]} {
set is_multiple 1
}
tcl::dict::set report_rows $argname [tcl::dict::create\
class $class\
source [expr {$received > 0 ? "received" : "default"}]\
received $received\
positions [tcl::dict::get $AST positions]\
multiple $is_multiple\
hasvalue $hasvalue\
value $value\
]
}
if {$returntype eq "dict"} {
#machine form - never elided
return [tcl::dict::create id $real form $fid rows $report_rows]
}
#display forms - colour bootstrap as for arg_error (proc-local CLR seeded from
#the shared base array, scheme overrides merged into the local copy only;
#forced reload when the colour on/off state has flipped since load)
variable arg_error_CLR
set forcereload 0
if {[info exists arg_error_CLR(testsinglecolour)]} {
set terminal_colour_is_on [expr {[string length [a+ yellow]]}]
set error_colour_is_on [expr {[string length $arg_error_CLR(testsinglecolour)]}]
if {$terminal_colour_is_on ^ $error_colour_is_on} {
set forcereload 1
}
}
private::argerror_load_colours $forcereload
array set CLR [array get arg_error_CLR]
switch -- $scheme {
nocolour {
variable arg_error_CLR_nocolour
array set CLR [array get arg_error_CLR_nocolour]
}
info {
variable arg_error_CLR_info
array set CLR [array get arg_error_CLR_info]
}
error {
variable arg_error_CLR_error
array set CLR [array get arg_error_CLR_error]
}
}
set RST "\x1b\[0m"
set display_rows [list]
tcl::dict::for {argname R} $report_rows {
set received [tcl::dict::get $R received]
if {$received > 0} {
set src received
if {$received > 1} {append src " x$received"}
set src_display "$CLR(check)$src$RST"
} else {
set src default
set src_display "$CLR(parsekey_hint)$src$RST"
}
set value_display ""
if {[tcl::dict::get $R hasvalue]} {
if {[tcl::dict::get $R multiple]} {
set mode list
} else {
set typelist [Dict_getdef $ARG_INFO $argname -type string]
if {"dict" in $typelist} {
set mode dict
} elseif {"list" in $typelist} {
set mode list
} else {
set mode string
}
}
set value_display [private::report_display_value [tcl::dict::get $R value] $mode $valuewidth]
}
lappend display_rows [list $argname $src $src_display $value_display]
}
if {![catch {package require textblock}]} {
set has_textblock 1
} else {
set has_textblock 0
}
if {$has_textblock && $returntype in {table tableobject}} {
set t [textblock::class::table new -title "$CLR(title)[tcl::dict::get $opts -title]$RST"]
$t add_column -headers [list Argument]
$t add_column -headers [list Source]
$t add_column -headers [list Value]
foreach row $display_rows {
lassign $row argname src src_display value_display
$t add_row [list $argname $src_display $value_display]
}
$t configure -show_hseps 0 {*}{
-show_header 1
} -ansibase_body $CLR(ansibase_body) {*}{
} -ansibase_header $CLR(ansibase_header) {*}{
} -ansiborder_header $CLR(ansiborder) {*}{
} -ansiborder_body $CLR(ansiborder)
if {$returntype eq "tableobject"} {
return $t
}
set out [$t print]
$t destroy
return $out
}
#plain lines - the string return, and the degraded path when textblock
#is unavailable
set lines [list]
foreach row $display_rows {
lassign $row argname src src_display value_display
lappend lines "$argname ($src) = $value_display"
}
return [join $lines \n]
}
#G-151 display-cell preparation for parse_report: single-line-ize, then type-aware
#elision with honest markers ('...' + ' (len N)' for strings, '(+N more)' for
#list/-multiple elements, '(+N pairs)' for dicts). budget is a grapheme cap for
#the cell (0 = no cap); markers stay intact, so a cell can exceed a very small
#budget by the marker width - deterministic for a given value and budget, never
#console-derived. ANSI/grapheme machinery via punk::ansi::ansistring when
#available - otherwise plain-string fallbacks (caps by chars, controls as
#backslash escapes).
proc private::report_display_value {value mode budget} {
set has_ansistring [expr {[llength [info commands ::punk::ansi::ansistring]] > 0}]
switch -- $mode {
list - dict {
if {[catch {llength $value} nelem]} {
#not a valid Tcl list - display as a plain string
set mode string
} elseif {$mode eq "dict" && $nelem % 2 != 0} {
#not a valid dict - display as a plain string
set mode string
}
}
}
switch -- $mode {
list {
set reprs [list]
foreach e $value {
lappend reprs [Report_singleline [list $e] $has_ansistring]
}
set full [join $reprs " "]
if {$budget == 0 || [Report_gcount $full $has_ansistring] <= $budget} {
return $full
}
lassign [Report_capped_elements $reprs $budget $has_ansistring] shown kept
return "[join $shown { }] (+[expr {[llength $reprs] - $kept}] more)"
}
dict {
set reprs [list]
foreach {k v} $value {
lappend reprs "[Report_singleline [list $k] $has_ansistring] [Report_singleline [list $v] $has_ansistring]"
}
set full [join $reprs " "]
if {$budget == 0 || [Report_gcount $full $has_ansistring] <= $budget} {
return $full
}
lassign [Report_capped_elements $reprs $budget $has_ansistring] shown kept
return "[join $shown { }] (+[expr {[llength $reprs] - $kept}] pairs)"
}
default {
set rawlen [tcl::string::length $value]
set disp [Report_singleline $value $has_ansistring]
if {$budget == 0 || [Report_gcount $disp $has_ansistring] <= $budget} {
return $disp
}
set lenmark " (len $rawlen)"
set cap [expr {$budget - [tcl::string::length $lenmark]}]
if {$cap < 1} {set cap 1}
return [Report_truncate $disp $cap $has_ansistring]$lenmark
}
}
}
#single-line-ize for report display: linefeed/tab/cr etc as visible substitutes.
#ansistring VIEW also renders any embedded ANSI escapes visibly - neutralizing
#them so a value cannot disturb the table layout. -sp 0: real spaces stay spaces.
proc private::Report_singleline {str has_ansistring} {
if {$has_ansistring} {
return [punk::ansi::ansistring VIEW -lf 1 -vt 1 -sp 0 $str]
}
return [tcl::string::map [list \r\n {\r\n} \n {\n} \r {\r} \t {\t} \x1b {\e}] $str]
}
proc private::Report_gcount {str has_ansistring} {
if {$has_ansistring} {
return [punk::ansi::ansistring COUNT $str]
}
return [tcl::string::length $str]
}
proc private::Report_truncate {str cap has_ansistring} {
if {$has_ansistring} {
return [punk::ansi::ansistring TRUNCATE $str $cap]
}
if {[tcl::string::length $str] <= $cap} {
return $str
}
if {$cap <= 3} {
return [tcl::string::range "..." 0 [expr {$cap - 1}]]
}
return "[tcl::string::range $str 0 [expr {$cap - 4}]]..."
}
#whole-element capping for list/dict display: keep as many whole element reprs as
#fit a budget that reserves nominal room for the trailing count marker; always
#keep at least one (char-capped if it alone overflows). Returns {shown kept}.
proc private::Report_capped_elements {reprs budget has_ansistring} {
set reserve 12
set avail [expr {$budget - $reserve}]
if {$avail < 1} {set avail 1}
set shown [list]
set used 0
foreach repr $reprs {
if {[llength $shown] == 0} {
if {[Report_gcount $repr $has_ansistring] > $avail} {
set repr [Report_truncate $repr $avail $has_ansistring]
}
lappend shown $repr
set used [Report_gcount $repr $has_ansistring]
continue
}
set rlen [Report_gcount $repr $has_ansistring]
if {$used + 1 + $rlen > $avail} {
break
}
lappend shown $repr
incr used [expr {1 + $rlen}]
}
return [list $shown [llength $shown]]
}
#return number of values we can assign to cater for variable length clauses such as:
# {"elseif" expr "?then?" body}
#review - efficiency? each time we call this - we are looking ahead at the same info

3
src/modules/punk/args-buildversion.txt

@ -1,6 +1,7 @@
0.17.0
0.18.0
#First line must be a semantic version number
#all other lines are ignored.
#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 <id>' (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.
#0.17.0 - G-149 configurable multi-form synopsis hint labels: the per-form hint labels rendered beside a multi-form definition's synopsis lines in the usage/arg_error table (hard-coded ' i -form N ...' previously) are now a template substituted per form - %formindex% replaced with the form's 0-based ordinal, %formname% with its name; an empty template suppresses the labels entirely (the cell keeps just the Synopsis: label, as single-form renders do). Precedence: caller option > definition > built-in default. New arg_error/usage option -formhint <template> (supplied-empty means suppress; usage pulls it from arg_error's definition via resolved_def); new @cmd directive key -formhint <template> for definition-supplied hints (rides the existing arbitrary-key cmd_info merge, so resolved_def round-trips it; not display-deferred). Unconfigured definitions render byte-identical to 0.16.0 (default template reproduces the exact prior bytes, verified by capture diff). Motivation (G-143 origin): script-level (script) ids - e.g make.tcl's - are unreachable from any repl, so the repl-oriented ' i -form N ...' default is not actionable there; consumers can now name their own per-form navigation. The string (non-table) renderer never carried hint labels and is unchanged. make.tcl adoption is consumer follow-through after a bootsupport promotion (not part of this change). New testsuite formhint.test; define -help documents the @cmd key beside the other directive-options.
#0.16.0 - G-073 unavailable choices: new per-argument key -choiceunavailable <list> - names that are recognised but not available in the current runtime/context. Display: entries render among the choices under a dedicated 'Unavailable (recognised, not selectable here)' group heading (table title / string-renderer Group heading - the distinguishing mechanism) with their ordinary -choicelabels notes, and join the prefix-calculation pool (display shortest-prefix marking reflects their reserving effect); synopsis choice-literal display and value-in-effect/goodarg marking never include them (structural: they are not in -choices/-choicegroups, and -default may not name one). Parse: choiceword_match gains an optional 8th argument unavailablelist and an 'unavailable' result key - a word landing on an unavailable name (exact, or unique prefix; case per -nocase) reports unavailable 1 with the name as canonical while matched stays 0, so matched-only consumers treat it as an ordinary non-match; a prefix shared between an available and an unavailable name is a plain ambiguous non-match. Validation rejects such a landing with the tailored PUNKARGS VALIDATION {choiceunavailable <name> choices <list>} error (-badarg/-badval; message names the entry, its -choicelabels note - e.g a version boundary - and the available values; private::unavailable_choice_msg shared by all raise surfaces). The G-071 allocation screen honours the list (optional-arg landings recorded as G-082 rejection kind 'unavailable' and preferred at the overflow raise sites); required args are unscreened as before, so their landing reports via final validation identically. Ambiguous-prefix messages annotate unavailable longer matches '(recognised but unavailable)'. -choicerestricted 0 keeps full passthrough (no tailored rejection). Resolve-time validation: requires -choices/-choicegroups; entries must not collide with choices, -choicealiases alias names, or -choiceprefixreservelist entries; -default may not be an unavailable name. form_literal_affinity/formcheck screens pass the list through (uniform pool semantics). In-tree adopter: punk::args::moduledoc::tclcore 'string is' forward classes (dict on 8.6). New testsuite choiceunavailable.test; tclcoreparity.test gains forward-class pins + the user-sanctioned 'di' strictness exemption; define -help documents the key.
#0.15.2 - G-082 follow-on: a REQUIRED typed value or leader whose present word fails the basic-type allocation screen (int/double/bool/number/dict) now reports the pointed typemismatch (message + errorcode with -badarg/-badval, same shape as the G-082 overflow-site selection) instead of the generic missingrequiredvalue/missingrequiredleader shortfall - the in-loop required-arg raise sites consult the rejection record returned by get_dict_can_assign_value for the current word. Unchanged by design: end-of-input shortages (valuemissing/count classes), a word failing a required literal-typed member, and multi-member clause partials (no rejection record - conservatism preserved); required-arg choice words were never screened (G-071 asymmetry) so choiceviolation reporting is untouched. parse_status consequences: this shape's top-level status flips incomplete->invalid with failureclass typemismatch (the formstatus verdict was already 'invalid' - the G-152 probe confirmed each such 'incomplete' away before reporting; one probe re-parse saved); multiform noformmatch classes unchanged (already invalid), per-form failureclass/message now the pointed typemismatch. parse_status argdoc status-key note updated. Pins flipped deliberately: parsestatus.test typed-value allocation failure, formviability.test single-form record (top-status/formstatus divergence demonstration re-anchored on a required-option shape), punk/ns cmdhelp.test badarg message match; new errorselection.test required-position coverage (value + leader) with unchanged-shape guards (end-of-input, literal-typed, clause partial).

4
src/tests/modules/AGENTS.md

@ -42,8 +42,8 @@ Unit tests for editable source modules under `src/modules/`, `src/modules_tcl8/`
- `modpod/` — modpod (vendored zip-based .tm wrapper) tests (`testsuites/modpod/roundtrip.test`, G-111 - the module's first suite): make_zip_modpod wrap emits stub+zip (\x1A separator, PK local header), is_valid_tm_version accept/reject, and child-process load round-trips of per-run generated fixture pods - require from a real-disk module path (stub self-mounts via zipfs, or the vfs::zip fallback on 8.6), the -offsettype file form, the unwrapped #modpod-<pkg>-<ver> redirect form (extracted folder beside the .tm is sourced directly - exact path asserted, no mount signature), a binary payload (dll discovered by a child probe among packages NOT already loaded in a bare child, repo lib_tcl<N> trees offered as auto_path - no committed binaries) loading from the mounted pod in a fresh child, and the tm-residing-on-a-zipfs-path zip-in-zip form (childzipfs-gated: skips on 8.6, the recorded G-034-class limitation). Child spawn probes the kit `script` subcommand form first, then plain script-file dispatch (native tclsh)
- `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)), 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), rendering/indentation characterization, synopsis display characterization (`synopsis.test`: basic italic argname/`<type>` 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/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-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/`<type>` 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)

50
src/tests/modules/punk/ansi/testsuites/ansi/ansistring.test

@ -351,5 +351,55 @@ namespace eval ::testspace {
}\
-result [list 1 1 1 1 1 0]
#added 2026-08-05 (agent, G-151) - TRUNCATE primitive (punk::ansi 0.2.0): grapheme-budget
#display truncation built on COUNT/RANGE (punk::args::parse_report is the driving
#consumer). Fits => byte-identical passthrough (trailing and non-SGR codes included);
#truncation => RANGE-extracted prefix + marker with total grapheme count == the budget;
#marker charged against the budget and itself truncated when it alone meets/exceeds the
#budget; maxgraphemes 0 => empty string; strict option handling (unlike VIEW's pinned
#silent tolerance).
test ansistring_truncate_budget_and_passthrough {TRUNCATE: fits-unchanged byte identity (styled fixture incl its trailing code), grapheme-exact capping with the default marker, marker budget participation, zero budget, custom marker, strict errors}\
-setup $common -body {
variable S
lappend result [expr {[ansistring TRUNCATE abcdefghij 10] eq "abcdefghij"}]
lappend result [ansistring TRUNCATE abcdefghij 7]
lappend result [ansistring COUNT [ansistring TRUNCATE abcdefghij 7]]
lappend result [expr {[ansistring TRUNCATE $S 5] eq $S}] ;#5 graphemes - byte-identical incl the \x1b\[1m code and trailing reset RANGE would merge/drop
lappend result [ansistring TRUNCATE abcdefghij 2]
lappend result [ansistring TRUNCATE abcdefghij 0]
lappend result [ansistring TRUNCATE -ellipsis ~ abcdefghij 5]
lappend result [catch {ansistring TRUNCATE -bogus 1 abcdefghij 5} m]
lappend result [string match "*unknown option '-bogus'*" $m]
lappend result [catch {ansistring TRUNCATE abcdefghij x} m]
lappend result [string match "*must be an integer >= 0*" $m]
}\
-cleanup {
}\
-result [list 1 abcd... 7 1 .. {} abcd~ 1 1 1 1]
#added 2026-08-05 (agent, G-151) - TRUNCATE ANSI/grapheme safety: a truncated prefix
#carrying SGR codes gets a reset appended before the (plain) marker so following text
#renders unstyled; the prefix re-emits per the RANGE contract (merged stacks); CJK
#wide graphemes count 1 each; a decomposed-combining cluster is never split.
test ansistring_truncate_ansi_reset_and_grapheme_safety {TRUNCATE styled/wide/combining content: SGR reset before the marker, RANGE-style prefix, CJK grapheme counting, combining cluster kept whole}\
-setup $common -body {
variable S
#fixture S graphemes: a b <wide> c d; budget 3 with 1-grapheme marker keeps {a b} - red opens before b, reset closes before the marker
lappend result [expr {[ansistring TRUNCATE -ellipsis ~ $S 3] eq "a\x1b\[31mb\x1b\[0m~"}]
#budget 4: keep = 1 grapheme 'a' - no codes in the kept prefix, no reset appended
lappend result [ansistring TRUNCATE $S 4]
#10 CJK wide graphemes capped at 7: 4 kept + 3-dot marker (grapheme count, not columns)
lappend result [expr {[ansistring TRUNCATE 丁丂七丄丅丆万丈三上 7] eq "丁丂七丄..."}]
#decomposed e + combining acute (one grapheme, two chars) + x + y = 3 graphemes;
#budget 2 with 1-grapheme marker keeps the whole cluster (2 chars) + marker
set comb "e[format %c 0x0301]xy"
set tr [ansistring TRUNCATE -ellipsis ~ $comb 2]
lappend result [expr {$tr eq "e[format %c 0x0301]~"}]
lappend result [string length $tr]
}\
-cleanup {
}\
-result [list 1 a... 1 1 3]
}
tcltest::cleanupTests ;#needed to produce test summary.

309
src/tests/modules/punk/args/testsuites/args/parsereport.test

@ -0,0 +1,309 @@
package require tcltest
package require punk::args
package require punk::lib
package require punk::ansi
#punk::args::parse_report (G-151 annotated success render, punk::args 0.18.0) -
#the landing report for a SUCCESSFUL parse: one row per value-holding argument
#(received, or filled from -default) in declaration-section order (leaders,
#opts, values), Source column distinguishing received / received xN / default.
#The canonical demonstration is a flag-like word consumed as a VALUE by
#position, visibly attributed to its consuming argument (the make.tcl dry-run
#'kitname = punk91 -confirm 0' class from the G-143 sessions).
#Pins are colour-state independent: -return string carries no ANSI (the value
#cell is VIEW-neutralized, the source word is plain), -return dict is the
#machine form, and table pins glob on stripped/ASCII content only.
#Display elision is deterministic (never console-derived): strings single-line-
#ized then grapheme-capped with '...' + ' (len N)' (N = raw string length),
#lists/-multiple element-capped with '(+N more)', dicts pair-capped with
#'(+N pairs)'; the -return dict form is never elided.
#Width cascade (G-149 plumbing precedent): caller -valuewidth > @cmd directive
#key -reportvaluewidth > built-in 48; 0 means no cap.
namespace eval ::testspace {
namespace import ::tcltest::*
variable common {
set result ""
}
#make.tcl-shaped fixture: choice leader, defaulted option, optional value +
#-multiple tail able to swallow flag-like words as values
punk::args::define {
@id -id ::testspace::prmk
@cmd -name testspace::prmk
@leaders -min 1 -max 1
action -choices {build info}
@opts
-test -type integer -default 0
-note -type string
@values -min 0 -max -1
kitname -type string -optional 1 -default punk91
args -type any -multiple 1 -optional 1
}
#elision fixture: declared list/dict types + free string value
punk::args::define {
@id -id ::testspace::prel
@cmd -name testspace::prel
@opts
-biglist -type list -default {}
-bigdict -type dict -default {}
@values -min 0 -max 1
text -type string -optional 1
}
#definition-level width key fixture
punk::args::define {
@id -id ::testspace::prrw
@cmd -name testspace::prrw -reportvaluewidth 20
@values -min 0 -max 1
text -type string -optional 1
}
#solo + multiple options fixture
punk::args::define {
@id -id ::testspace::prsolo
@cmd -name testspace::prsolo
@opts
-v -type none -multiple 1
-file -type string -multiple 1
@values -min 0 -max 0
}
#aliased optionset fixture (storage-key fold bridging)
punk::args::define {
@id -id ::testspace::pralias
@cmd -name testspace::pralias
@opts
-f|-file -type string
@values -min 0 -max 0
}
#added 2026-08-05 (agent, G-151) - the acceptance's canonical demonstration:
#flag-like words consumed as VALUES visibly attributed to the -multiple
#argument that took them; received vs default distinguished in Source
test parsereport_canonical_flagword_value_attribution {a flag-like word consumed as a VALUE shows attributed to its consuming argument; received/default distinction; declaration-section row order}\
-setup $common -body {
set argd [punk::args::parse {build punk95 -confirm 0} withid ::testspace::prmk]
punk::args::parse_report $argd -return string
}\
-cleanup {
}\
-result [join [list\
{action (received) = build}\
{-test (default) = 0}\
{kitname (received) = punk95}\
{args (received x2) = -confirm 0}\
] \n]
#added 2026-08-05 (agent, G-151)
test parsereport_parsedresult_and_words_forms_agree {the parse-result entry form and the words+withid entry form produce identical output}\
-setup $common -body {
set argd [punk::args::parse {build punk95 -confirm 0} withid ::testspace::prmk]
set via_result [punk::args::parse_report $argd -return string]
set via_words [punk::args::parse_report {build punk95 -confirm 0} -return string withid ::testspace::prmk]
lappend result [expr {$via_result eq $via_words}]
set d_result [punk::args::parse_report $argd -return dict]
set d_words [punk::args::parse_report {build punk95 -confirm 0} -return dict withid ::testspace::prmk]
lappend result [expr {$d_result eq $d_words}]
}\
-cleanup {
}\
-result [list 1 1]
#added 2026-08-05 (agent, G-151) - machine form: id/form/rows; per-row
#class/source/received/positions/multiple/hasvalue/value; row order is
#declaration-section order; absent optionals (no default) get no row
test parsereport_dict_machine_shape {-return dict carries id/form and ordered rows with per-argument landing data; absent optional -note has no row}\
-setup $common -body {
set argd [punk::args::parse {build punk95 -confirm 0} withid ::testspace::prmk]
set rep [punk::args::parse_report $argd -return dict]
lappend result [dict get $rep id]
lappend result [dict keys [dict get $rep rows]]
lappend result [dict get $rep rows args]
lappend result [dict get $rep rows -test]
lappend result [dict exists $rep rows -note]
}\
-cleanup {
}\
-result [list\
::testspace::prmk\
{action -test kitname args}\
{class value source received received 2 positions {2 3} multiple 1 hasvalue 1 value {-confirm 0}}\
{class option source default received 0 positions {} multiple 0 hasvalue 1 value 0}\
0\
]
#added 2026-08-05 (agent, G-151) - solo flags and -multiple options: the
#Source column carries the aggregation count; values echo the stored
#aggregations
test parsereport_source_multiplicity_solos_and_multis {repeated solo flags and -multiple options report received xN with their aggregated values}\
-setup $common -body {
set argd [punk::args::parse {-v -v -file a.txt -file b.txt} withid ::testspace::prsolo]
punk::args::parse_report $argd -return string
}\
-cleanup {
}\
-result [join [list\
{-v (received x2) = 1 1}\
{-file (received x2) = a.txt b.txt}\
] \n]
#added 2026-08-05 (agent, G-151) - type-aware always-marked elision at the
#default width (48): element cap '(+N more)', pair cap '(+N pairs)', string
#char cap '...' + ' (len N)' citing the raw length
test parsereport_elision_marked_by_type {list values element-capped, dict values pair-capped, strings char-capped - all with honest markers at the default width}\
-setup $common -body {
set biglist [list]
for {set i 0} {$i < 30} {incr i} {lappend biglist elem$i}
set bigdict [dict create]
for {set i 0} {$i < 20} {incr i} {dict set bigdict key$i val$i}
set longtext [string repeat abcdefghij 10]
set argd [punk::args::parse [list -biglist $biglist -bigdict $bigdict $longtext] withid ::testspace::prel]
punk::args::parse_report $argd -return string
}\
-cleanup {
}\
-result [join [list\
{-biglist (received) = elem0 elem1 elem2 elem3 elem4 elem5 (+24 more)}\
{-bigdict (received) = key0 val0 key1 val1 key2 val2 (+17 pairs)}\
{text (received) = abcdefghijabcdefghijabcdefghijabcde... (len 100)}\
] \n]
#added 2026-08-05 (agent, G-151) - the -return dict form is never elided
test parsereport_dict_never_elided {machine rows carry the full untruncated values}\
-setup $common -body {
set biglist [list]
for {set i 0} {$i < 30} {incr i} {lappend biglist elem$i}
set argd [punk::args::parse [list -biglist $biglist] withid ::testspace::prel]
set rep [punk::args::parse_report $argd -return dict]
lappend result [expr {[dict get $rep rows -biglist value] eq $biglist}]
}\
-cleanup {
}\
-result [list 1]
#added 2026-08-05 (agent, G-151) - width cascade: @cmd -reportvaluewidth
#applies when the caller passes no -valuewidth; the caller option overrides;
#0 means no cap
test parsereport_valuewidth_cascade {caller -valuewidth > @cmd -reportvaluewidth > built-in default; 0 uncaps}\
-setup $common -body {
set longtext [string repeat abcdefghij 10]
set argd [punk::args::parse [list $longtext] withid ::testspace::prrw]
lappend result [punk::args::parse_report $argd -return string]
lappend result [punk::args::parse_report $argd -return string -valuewidth 60]
lappend result [punk::args::parse_report $argd -return string -valuewidth 0]
}\
-cleanup {
}\
-result [list\
{text (received) = abcdefg... (len 100)}\
{text (received) = abcdefghijabcdefghijabcdefghijabcdefghijabcdefg... (len 100)}\
"text (received) = [string repeat abcdefghij 10]"\
]
#added 2026-08-05 (agent, G-151) - single-line-ization: newlines/tabs render
#as visible control pictures (as for ansistring VIEW -lf 1) so a value
#cannot break the row; expected string constructed via format to keep this
#file ASCII
test parsereport_multiline_value_single_lined {a value containing newline and tab renders one-line with visible substitutes (defaulted empty-value rows keep their trailing-space line shape)}\
-setup $common -body {
set argd [punk::args::parse [list "line1\nline2\ttabbed"] withid ::testspace::prel]
set rep [punk::args::parse_report $argd -return string]
#the prel fixture's -biglist/-bigdict defaults ({}) hold values, so they row too
set expected "-biglist (default) = \n-bigdict (default) = \ntext (received) = line1[format %c 0x240A]line2[format %c 0x2409]tabbed"
lappend result [expr {$rep eq $expected}]
}\
-cleanup {
}\
-result [list 1]
#added 2026-08-05 (agent, G-151) - aliased optionset storage-key bridging:
#the parse result stores an aliased optionset under its last |-member; the
#report bridges the fold so the received argument keeps its row (G-084 owns
#the storage-key model itself)
test parsereport_aliased_optionset_row_bridged {an option received via its alias keeps its row with the value-in-effect}\
-setup $common -body {
set argd [punk::args::parse {-f x.txt} withid ::testspace::pralias]
lappend result [punk::args::parse_report $argd -return string]
lappend result [dict get [punk::args::parse_report $argd -return dict] rows -f|-file value]
}\
-cleanup {
}\
-result [list {-f|-file (received) = x.txt} x.txt]
#added 2026-08-05 (agent, G-151) - the bordered landing table: title in the
#frame, Argument/Source/Value headers, the canonical attribution row intact
#on one line (ASCII-content globs on the stripped render)
test parsereport_table_bordered_block {-return table renders the arg_error-styled bordered block with title, headers and attribution rows}\
-setup $common -body {
set argd [punk::args::parse {build punk95 -confirm 0} withid ::testspace::prmk]
set out [punk::ansi::ansistrip [punk::args::parse_report $argd]]
set lines [split $out \n]
lappend result [string match "*Received*" [lindex $lines 0]]
lappend result [string match "*Argument*Source*Value*" $out]
set attribution_line ""
foreach ln $lines {
if {[string match "*-confirm 0*" $ln]} {set attribution_line $ln}
}
lappend result [string match "*args*received x2*-confirm 0*" $attribution_line]
lappend result [expr {[llength $lines] >= 8}]
set out2 [punk::ansi::ansistrip [punk::args::parse_report $argd -scheme nocolour -title Landing]]
lappend result [string match "*Landing*" [lindex [split $out2 \n] 0]]
set t [punk::args::parse_report $argd -return tableobject]
lappend result [info object isa object $t]
$t destroy
lappend result [llength [info commands $t]]
}\
-cleanup {
}\
-result [list 1 1 1 1 1 1 0]
#added 2026-08-05 (agent, G-151) - the words form performs the parse: a
#rejected line raises the same validation error punk::args::parse raises
test parsereport_words_form_failure_parity {a rejected words line raises with the same errorcode as punk::args::parse}\
-setup $common -body {
set rc1 [catch {punk::args::parse_report {frobnicate} withid ::testspace::prmk} m1 opts1]
set rc2 [catch {punk::args::parse {frobnicate} withid ::testspace::prmk} m2 opts2]
lappend result $rc1 $rc2
lappend result [expr {[dict get $opts1 -errorcode] eq [dict get $opts2 -errorcode]}]
}\
-cleanup {
}\
-result [list 1 1 1]
#added 2026-08-05 (agent, G-151) - unhappy call shapes: pointed errors for
#non-result-dict payload / unknown id / bad -valuewidth; an unknown option
#routes through the documented definition (a PUNKARGS VALIDATION raise)
test parsereport_bad_calls {pointed errors: payload neither result dict nor words+withid, no such id, bad -valuewidth, unknown option via the documented definition}\
-setup $common -body {
lappend result [catch {punk::args::parse_report "not a result dict"} m]
lappend result [string match "*not a punk::args::parse result dict*" $m]
lappend result [catch {punk::args::parse_report {a b} withid ::testspace::pr_nosuchid} m]
lappend result [string match "*no such id*" $m]
set argd [punk::args::parse {build} withid ::testspace::prmk]
lappend result [catch {punk::args::parse_report $argd -valuewidth -5} m]
lappend result [string match "*must be an integer >= 0*" $m]
lappend result [catch {punk::args::parse_report $argd -bogus 1} m mopts]
lappend result [expr {[lindex [dict get $mopts -errorcode] 0] in [list PUNKARGS TCL]}]
}\
-cleanup {
}\
-result [list 1 1 1 1 1 1 1 1]
#added 2026-08-05 (agent, G-151) - parse_report's own registered definition:
#both forms present and the usage render carries both synopsis shapes
test parsereport_own_definition_renders {the registered ::punk::args::parse_report definition resolves with forms {parsed withid} and renders both synopses}\
-setup $common -body {
lappend result [punk::args::forms ::punk::args::parse_report]
set u [punk::ansi::ansistrip [punk::args::usage ::punk::args::parse_report]]
lappend result [string match "*parse_report parsedresult*" $u]
lappend result [string match "*parse_report arglist*withid id*" $u]
}\
-cleanup {
}\
-result [list {parsed withid} 1 1]
}
tcltest::cleanupTests ;#needed to produce test summary.
Loading…
Cancel
Save