Browse Source

punk::args 0.11.1: define doc - registration styles and tstr/defspace interpolation rules (project 0.12.17)

Documentation-only. The define -help gains two sections: 'Registration
styles' (direct define vs deferred lappend-PUNKARGS registration - the
lazy scan/on-demand definition mechanics, why moduledocs and the module
template use it, PUNKARGS_aliases, punk::args::status timings) and
'Interpolation (tstr placeholders) and the defspace' (display-field
deferral vs parse-field expansion vs @dynamic; the defspace rule with
the argdoc-child subtlety; the silent-literal fallback; safe patterns
for load-time-computed values). Motivated by the ::after id-shape
harvest misattribution - corrected record in goals/G-055.

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 2 days ago
parent
commit
f1c0ea10a0
  1. 4
      CHANGELOG.md
  2. 2
      punkproject.toml
  3. 55
      src/modules/punk/args-999999.0a1.0.tm
  4. 3
      src/modules/punk/args-buildversion.txt

4
CHANGELOG.md

@ -5,6 +5,10 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to
Entries are newest-first; one bullet per notable change. See the root `AGENTS.md` Entries are newest-first; one bullet per notable change. See the root `AGENTS.md`
"Project Versioning" section for the bump policy. "Project Versioning" section for the bump policy.
## [0.12.17] - 2026-07-13
- punk::args 0.11.1 (documentation-only): the define help now documents the two definition-registration styles (direct define vs lazy PUNKARGS/register::NAMESPACES registration used by module templates and moduledocs) and the tstr interpolation rules - display-field deferral, parse-field expansion at first resolve, the defspace rule (argdoc child namespace wins when present), the silent-literal fallback for unresolvable placeholders, and the safe patterns for load-time-computed values.
## [0.12.16] - 2026-07-13 ## [0.12.16] - 2026-07-13
- tclcore moduledoc 0.3.0: 'after cancel <word>' and 'after info <id>' now discriminate by the after-id shape, harvested from the running interpreter at load time (user-directed). 'i after cancel someid' resolves cleanly to the cancel-script form exactly as real Tcl treats it (silent script-match no-op), and only genuinely id-shaped words ('after#N') report the cancelid/cancelscript ambiguity - the junction real Tcl itself resolves by id liveness at runtime. Parity pins added (tclcoreparity.test) including the accepted dead-id over-acceptance boundary. - tclcore moduledoc 0.3.0: 'after cancel <word>' and 'after info <id>' now discriminate by the after-id shape, harvested from the running interpreter at load time (user-directed). 'i after cancel someid' resolves cleanly to the cancel-script form exactly as real Tcl treats it (silent script-match no-op), and only genuinely id-shaped words ('after#N') report the cancelid/cancelscript ambiguity - the junction real Tcl itself resolves by id liveness at runtime. Parity pins added (tclcoreparity.test) including the accepted dead-id over-acceptance boundary.

2
punkproject.toml

@ -1,4 +1,4 @@
[project] [project]
name = "punkshell" name = "punkshell"
version = "0.12.16" version = "0.12.17"
license = "BSD-2-Clause" license = "BSD-2-Clause"

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

@ -683,6 +683,61 @@ tcl::namespace::eval punk::args {
can be left entirely as documentation for interactive use with: i <cmd> ... can be left entirely as documentation for interactive use with: i <cmd> ...
and for synopsis generation with: s <cmd> ... and for synopsis generation with: s <cmd> ...
%B%Registration styles%N%
There are two ways for a package to provide definitions:
1) Direct calls to punk::args::define. The raw text is stored
immediately (parsing to the specifications dictionary still
happens lazily on first use).
2) Deferred registration: append definition text-block lists to
a namespace variable named PUNKARGS and add the namespace to the
public list variable punk::args::register::NAMESPACES.
Nothing is processed at load time. On the first id lookup that
misses (e.g punk::args::parse ... withid, or the i/s help
commands - via punk::args::update_definitions) registered
namespaces are scanned cheaply for @id values, and the
namespace's definitions are defined on demand.
This is the style used by the module templates (dev module.new)
and by the punk::args::moduledoc::* packages - which document
OTHER packages' commands (the scan records which namespaces each
registered namespace documents). Prefer it for anything large:
a package with many or lengthy definitions then adds essentially
nothing to load time. Command-id aliases can similarly be
registered in a PUNKARGS_aliases variable (list of
{aliasid id} pairs).
Per-namespace scan/load timings are visible via
punk::args::status.
%B%Interpolation (tstr placeholders) and the defspace%N%
\$\{...\} placeholders in definition text use
punk::args::lib::tstr semantics, but WHEN they are expanded
depends on the field:
- display-only fields (-help of @cmd/@examples/arguments,
@formdisplay -header/-body) are deferred and expanded at
display time.
- all other (parse-relevant) fields are expanded when the
definition is first resolved.
- @dynamic definitions re-expand on every resolve.
Expansion happens in the definition's 'defspace' namespace:
for a direct define call, the calling namespace; for registered
PUNKARGS, the namespace's argdoc child IF IT EXISTS, otherwise
the namespace itself. This applies even when the PUNKARGS
variable itself is in the parent: a variable set in the parent
namespace is not visible to placeholder expansion when an
argdoc child exists.
A placeholder that cannot be resolved is currently left literal
in the field without warning - if a literal \$\{...\} shows up
in a parsed spec or rendered help, check the defspace.
For values computed once at package load (e.g a capability or
enumeration harvested from the runtime), the robust patterns
are: pre-expand the block explicitly
(punk::args::define \[punk::args::lib::tstr -return string {...}\]),
substitute a plain token at build time
(\[string map \[list %TOKEN% $val\] {...}\]),
or set the variable in the argdoc namespace.
Use @dynamic only when the value can change between resolves.
The definition should usually contain an initial line of the form: @id -id ::somecmd The definition should usually contain an initial line of the form: @id -id ::somecmd
Blank lines are ignored at the top level, ie if they are not part of another structure. Blank lines are ignored at the top level, ie if they are not part of another structure.

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

@ -1,6 +1,7 @@
0.11.0 0.11.1
#First line must be a semantic version number #First line must be a semantic version number
#all other lines are ignored. #all other lines are ignored.
#0.11.1 - documentation-only: define -help gains a 'Registration styles' section (direct define vs deferred lappend-PUNKARGS + register::NAMESPACES registration - lazy @id scan and on-demand definition via update_definitions, the module-template/moduledoc style, PUNKARGS_aliases, punk::args::status timings; prefer deferred for anything large) and an 'Interpolation (tstr placeholders) and the defspace' section (display-field deferral vs parse-field expansion at first resolve vs @dynamic re-expansion; defspace rule: direct define = calling namespace, registered PUNKARGS = the argdoc child namespace when it exists even if the PUNKARGS variable is in the parent; unresolvable placeholders left silently literal - check the defspace when a literal ${...} appears; robust patterns for load-time-computed values: explicit tstr pre-expansion, build-time string-map token, or setting the variable in argdoc). Motivated by the ::after id-shape harvest misattribution (2026-07-13, corrected record in goals/G-055).
#0.11.0 - G-041 increment 2 (doc-surface support): synopsis renderer honours the documented @form -synopsis override - the stored override now replaces the auto-calculated synopsis line in punk::args::synopsis full and summary renders (arg_error's synopsis section honoured it already; the per-form dict gains a 'synopsis' key when overridden, FORMARGS unchanged for dict consumers) - forms.test GAP forms_form_synopsis_override_stored_not_rendered_GAP flipped to forms_form_synopsis_override_rendered. Candidate-form ranking extended to choice discriminators: form_literal_affinity now treats a required argument with a RESTRICTED choice set as a discriminator (matched via choiceword_match, the shared G-040 resolver) alongside literal()/literalprefix() types - the tclcore models express subcommand-ish literals as -choices (e.g after's cancel/idle/info), so 'after cancel' now ranks the cancel forms first in noformmatch errors and parse_status best-candidate selection instead of falling back to declaration order. #0.11.0 - G-041 increment 2 (doc-surface support): synopsis renderer honours the documented @form -synopsis override - the stored override now replaces the auto-calculated synopsis line in punk::args::synopsis full and summary renders (arg_error's synopsis section honoured it already; the per-form dict gains a 'synopsis' key when overridden, FORMARGS unchanged for dict consumers) - forms.test GAP forms_form_synopsis_override_stored_not_rendered_GAP flipped to forms_form_synopsis_override_rendered. Candidate-form ranking extended to choice discriminators: form_literal_affinity now treats a required argument with a RESTRICTED choice set as a discriminator (matched via choiceword_match, the shared G-040 resolver) alongside literal()/literalprefix() types - the tclcore models express subcommand-ish literals as -choices (e.g after's cancel/idle/info), so 'after cancel' now ranks the cancel forms first in noformmatch errors and parse_status best-candidate selection instead of falling back to declaration order.
#0.10.0 - G-041 multi-form candidacy: punk::args::parse/get_dict with the default -form * (or any multi-form selection) now attempts every permitted form instead of effectively parsing form 0 only - a clean match against exactly one form is auto-selected, no match raises a 'noformmatch' PUNKARGS VALIDATION error naming each candidate form's first-line failure (candidates ranked best-first: leading-literal affinity with the supplied words, then incomplete before invalid, then declaration order - private::form_selection/form_literal_affinity/rank_form_failures), and several clean matches raise 'multipleformmatches' naming the forms (no silent preference - deliberate, callers pass -form to disambiguate). -form now accepts a list of form names/indices as documented (get_dict, parse, parse_status, arg_error; supplied order preserved - arg_error renders the argument table for the first listed form and marks all listed forms' synopsis entries; parse's error render passes the ranked candidates). Parse results gain a 'form' key (the parsed form; keys appended after 'id' - positional consumers unaffected) and, when candidacy ran, a 'formstatus' key reporting every attempted form's outcome; parse_status 'form' is now the matched/best-candidate form (per-argument statuses and badarg marking built for it) and gains the documented 'formstatus' key (per-form status/failureclass/badarg/message with caller attribution) - the per-form compatibility surface the G-044 completion/hinting consumer needs. Engine body extracted verbatim to private::get_dict_form (single-form parse; argspecs resolved before selection - no caller-frame use). forms.test GAP pins flipped (auto-selection, -form list restriction) + new coverage (shared-prologue arity discrimination, noformmatch ranking/errorcode, multipleformmatches, formstatus in results and parse_status). #0.10.0 - G-041 multi-form candidacy: punk::args::parse/get_dict with the default -form * (or any multi-form selection) now attempts every permitted form instead of effectively parsing form 0 only - a clean match against exactly one form is auto-selected, no match raises a 'noformmatch' PUNKARGS VALIDATION error naming each candidate form's first-line failure (candidates ranked best-first: leading-literal affinity with the supplied words, then incomplete before invalid, then declaration order - private::form_selection/form_literal_affinity/rank_form_failures), and several clean matches raise 'multipleformmatches' naming the forms (no silent preference - deliberate, callers pass -form to disambiguate). -form now accepts a list of form names/indices as documented (get_dict, parse, parse_status, arg_error; supplied order preserved - arg_error renders the argument table for the first listed form and marks all listed forms' synopsis entries; parse's error render passes the ranked candidates). Parse results gain a 'form' key (the parsed form; keys appended after 'id' - positional consumers unaffected) and, when candidacy ran, a 'formstatus' key reporting every attempted form's outcome; parse_status 'form' is now the matched/best-candidate form (per-argument statuses and badarg marking built for it) and gains the documented 'formstatus' key (per-form status/failureclass/badarg/message with caller attribution) - the per-form compatibility surface the G-044 completion/hinting consumer needs. Engine body extracted verbatim to private::get_dict_form (single-form parse; argspecs resolved before selection - no caller-frame use). forms.test GAP pins flipped (auto-selection, -form list restriction) + new coverage (shared-prologue arity discrimination, noformmatch ranking/errorcode, multipleformmatches, formstatus in results and parse_status).
#0.9.0 - G-071: allocation choice screen - get_dict_can_assign_value no longer blanket-accepts any word for an argument with a RESTRICTED choice set; where allocation has an alternative (the argument is -optional, or a further occurrence of a -multiple argument) the candidate word is screened with choiceword_match (the shared G-040 implementation: exact/alias/prefix/nocase semantics; -choicemultiple words screened per list member within min/max), and a non-matching word yields onward to later elements/clauses instead of being consumed and failing validation. Fixes the lseq-class in-form failures: an optional choice noise word between required values plus a trailing optional-member clause ('lseq 0 10 2', '0 10 by 2', '1 5 by 0' now parse; noise-word variants and prefix normalization unchanged; genuinely invalid arglists now get the plain excess-values rejection instead of blaming the unrelated optional). REQUIRED arguments are deliberately not screened - the word must fill them and validation's choiceviolation reporting stays informative. -choicerestricted 0 behaviour unchanged. allocation.test GAPs flipped; parse_status -form documented-order behaviour pinned (a prior mis-premise: -form was already supported before the withid/withdef tail per its synopsis). #0.9.0 - G-071: allocation choice screen - get_dict_can_assign_value no longer blanket-accepts any word for an argument with a RESTRICTED choice set; where allocation has an alternative (the argument is -optional, or a further occurrence of a -multiple argument) the candidate word is screened with choiceword_match (the shared G-040 implementation: exact/alias/prefix/nocase semantics; -choicemultiple words screened per list member within min/max), and a non-matching word yields onward to later elements/clauses instead of being consumed and failing validation. Fixes the lseq-class in-form failures: an optional choice noise word between required values plus a trailing optional-member clause ('lseq 0 10 2', '0 10 by 2', '1 5 by 0' now parse; noise-word variants and prefix normalization unchanged; genuinely invalid arglists now get the plain excess-values rejection instead of blaming the unrelated optional). REQUIRED arguments are deliberately not screened - the word must fill them and validation's choiceviolation reporting stays informative. -choicerestricted 0 behaviour unchanged. allocation.test GAPs flipped; parse_status -form documented-order behaviour pinned (a prior mis-premise: -form was already supported before the withid/withdef tail per its synopsis).

Loading…
Cancel
Save