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`
"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
- 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]
name = "punkshell"
version = "0.12.16"
version = "0.12.17"
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> ...
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
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
#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.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).

Loading…
Cancel
Save