4.6 KiB
G-053 punk::args range-valued -multiple: occurrence arity with strict duplicate handling
Status: proposed Scope: src/modules/punk/args-999999.0a1.0.tm (spec compiler, parse, arg_error/synopsis renderers), src/tests/modules/punk/args/testsuites/args/ Goal: -multiple accepts a {min max} occurrence range (mirroring -choicemultiple; max -1 unbounded) alongside the legacy booleans - so a definition can declare "at most once, repeat is an error" ({0 1}) or bounded repetition ({2 4}) instead of choosing between silent last-wins (0) and unbounded collection (1) - with boolean semantics preserved exactly, including the prepend-defaults/last-wins override idiom. Acceptance: parse raises a usage-style arity error naming the argument for occurrences outside a declared range; boolean -multiple 0/1 behaviour is unchanged (full existing punk::args suite passes untouched); the -optional/range-min reconciliation rule is documented and enforced at define time; the usage table Multi column and synopsis reflect declared ranges; -multipleunique/-multipleuniqueset compose with max>1 ranges unchanged; characterization tests cover the new forms and the value-shape rule.
Context
Boolean -multiple conflates three axes:
- occurrence arity - how many times the argument may be supplied
- overflow policy - what happens beyond the limit: silent replace (legacy
-multiple 0last-wins) or error - value shape - scalar vs list-of-occurrences
The motivating incident (2026-07-10): runtests.tcl's -include-paths was a
non-multiple list option, so repeated -include-paths flags silently last-won -
quietly narrowing a test run while reporting green, and defeating even a recorded
memory note about the gotcha. Repeatable accumulating flags are the dominant
convention in shell-facing CLIs (gcc -I, curl -H, rsync --exclude), so this misuse
recurs. runtests was fixed by making that option -multiple 1, but the general
fix for "repeat should be an error" has no expression today.
A separate -duplicates deny|replace policy flag was considered and rejected: with
-multiple 1 a duplicates policy is meaningless (duplicates ARE the collected
payload), so the flag's validity would depend on another flag's setting, and it
would blur into the existing -multipleunique/-multipleuniqueset territory.
punk::args already has the shape precedent in its own vocabulary: -choicemultiple
is a {min max} pair, not a boolean.
Approach
-multiple accepts a boolean (legacy, semantics preserved exactly) or a
{min max} range (max -1 = unbounded):
-multiple 0- legacy: single-valued, repeats silently replace (last wins), scalar shape. Unchanged - this preserves the prepend-defaults override idiompunk::args::parse [list -flag default {*}$userargs].-multiple 1- legacy: unbounded collection, list shape. Unchanged.-multiple {0 1}- at most once; a second occurrence is a parse (arity) error. This is the "duplicates deny" case.-multiple {2 4},{1 -1}etc - bounded/lower-bounded repetition, collected.
Design decisions to settle (record here when made):
- Value shape for range forms: lean -
max == 1forms stay scalar (they are strict single-valued variants of legacy 0);max > 1or-1yield the occurrence list. Whatever is chosen must be pinned by the characterization tests. - -optional vs range-min reconciliation: lean -
-optionalgoverns presence, the range governs occurrence count when present; contradictory combinations (e.g. range min >= 1 with -optional 1 intended as "required") rejected at define time with a clear message. - Hot-path canonicalization:
-multipleis truth-tested in many parse/render sites; a raw"0 1"value would fail expr boolean coercion. The spec compiler should canonicalize once into internal min/max/policy fields (alongside the existing ARG_INFO/ARG_CHECKS structures) so runtime checks stay cheap.
Display benefits: the usage table's Multi column and the synopsis ?arg...?
rendering gain meaningful bounded-repetition forms (e.g. "0-1", "2-4").
Notes
-multipleunique/-multipleuniquesetremain the uniqueness knobs and only make sense for max > 1; they compose with ranges unchanged.- Related: G-045 (authoring ergonomics), G-046 (parse-time performance - the canonicalization must not regress the hot path).
- The runtests
-include-pathsfix (repeatable, accumulate, single-list form still accepted) shipped independently on 2026-07-10 and does not depend on this goal. - Archived-goal references in this file: G-046 achieved 2026-07-10 (goals/archive/G-046-punkargs-deferred-help-and-fixes.md).