You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

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:

  1. occurrence arity - how many times the argument may be supplied
  2. overflow policy - what happens beyond the limit: silent replace (legacy -multiple 0 last-wins) or error
  3. 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 idiom punk::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 == 1 forms stay scalar (they are strict single-valued variants of legacy 0); max > 1 or -1 yield the occurrence list. Whatever is chosen must be pinned by the characterization tests.
  • -optional vs range-min reconciliation: lean - -optional governs 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: -multiple is 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 / -multipleuniqueset remain 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-paths fix (repeatable, accumulate, single-list form still accepted) shipped independently on 2026-07-10 and does not depend on this goal.
  • Archived-goal references in this file: G-046 achieved 2026-07-10 (goals/archive/G-046-punkargs-deferred-help-and-fixes.md).