Command-Line Arguments (std.args)

← Back to Language Guide · I/O · OS · Hash Map


Overview

std.args is a small, dependency-free command-line parser inspired by Python’s argparse. It takes a declarative spec plus an argv list and returns an immutable hash map keyed by symbol — including a distinguished 'positional key for non-option arguments.

It is intentionally narrow in scope:

What it deliberately omits: sub-commands, mutually-exclusive groups, auto-generated short-flag negation, environment-variable fallback, configuration files. Compose those with std.os, std.json, and std.hashmap when needed.


Quick start

(import std.args std.io)

(define spec
  '((verbose (--verbose -v) flag)
    (out     (--out -o)     string "a.out")
    (jobs    (--jobs -j)    int    1)
    (tag     (--tag)        list)))

(define r (args:parse spec
            '("-v" "--out=demo" "-j" "4"
              "--tag" "risk" "--tag" "stress"
              "--" "input.csv")))

(args:get r 'verbose)     ;; => #t
(args:get r 'out)         ;; => "demo"
(args:get r 'jobs)        ;; => 4
(args:get r 'tag)         ;; => ("risk" "stress")
(args:get r 'positional)  ;; => ("input.csv")

For the runnable demo see cookbook/basics/args.eta; for tested behaviour see stdlib/tests/args.test.eta.


API

FunctionPurpose
(args:parse spec argv) -> hash-mapParse an explicit argv list
(args:parse-command-line spec) -> hash-mapParse (os:command-line-arguments)
(args:get parsed key [default]) -> valueAlias for hash-map-ref — symbol-keyed lookup
(args:help spec [program-name]) -> stringRender a usage / help string

The parsed hash map always contains:


Spec syntax

Each entry is a list:

(key (flag …) type)
(key (flag …) type default)
(key (flag …) type opts)
(key (flag …) type default opts)

Where:

Optional opts keys

KeyMeaning
required?#t to error if the flag is absent.
helpOne-line description shown by args:help.
metavarPlaceholder name (e.g. "FILE") — currently informational.
choicesList of allowed values; rejected values raise.
parse(lambda (raw-string) value) — overrides the built-in coercion.
validate(lambda (value) bool-or-raise) — runs after coercion / parse.
actionset (default for scalars), append (default for list), or count (flag-only counter).

Worked spec

(define parse-level
  (lambda (raw)
    (let ((n (string->number raw)))
      (if (and (number? n) (integer? n))
          n
          (error "level must be an integer")))))

(define level-in-range?
  (lambda (n) (and (>= n 0) (<= n 5))))

(define spec
  (list
    '(v (--verbose -v) flag)
    '(o (--out -o)     string "report.txt")
    '(n (--num)        int    0)
    '(t (--tag)        list)
    (list 'level
          (list '--level)
          'int
          1
          (list (cons 'parse    parse-level)
                (cons 'validate level-in-range?)))))

Tokenisation rules

Token formMeaning
--name valueOption --name consumes the next token as its value.
--name=valueInline value; value may be empty.
-x value / -x=valueSame, for short flags.
--nameBare option. For flag types ⇒ #t (or +1 if action: count).
--Terminator. Every later token is positional, even if it starts with -.
anything elsePositional argument.

Negative numbers

Tokens that look like options (-3, -1.5) are accepted as values when the target spec has type int or float, so this works:

(args:parse '((n (--num -n) int)) '("--num" "-3"))
;; => hash-map with (n . -3)

Bundled short flags (-vj4) are not supported.

Missing values are rejected

(catch 'runtime.error
  (args:parse '((n (--num) int) (m (--msg) string))
              '("--num" "--msg" "ok")))
;; => (runtime-error "args: missing value for option" …)

Defaults & access patterns

args:get is hash-map-ref, so the optional default applies only when the key was removed from the map — declared spec keys always have a value (their default):

(args:get r 'unknown 'fallback)   ;; => 'fallback
(args:get r 'verbose)             ;; => #f when --verbose absent

Iterate over everything by treating the result as a normal hash map:

(import std.hashmap)
(for-each
  (lambda (k) (println (list k '=> (args:get r k))))
  (hash-map-keys r))

Help text

(args:help spec ["my-tool"]) returns a usage string that lists every declared option with its flags, type, default, requirement marker, and help text:

usage: my-tool [options] [--] [positional ...]
  --verbose, -v  : flag        (default #f)  - chatty output
  --out, -o      : string      (default "report.txt")
  --num          : int         (default 0)
  --tag          : list<string> (default ())
  --level        : int         (default 1)

A typical CLI entry point:

(defun main ()
  (let ((r (args:parse-command-line spec)))
    (when (args:get r 'help)
      (display (args:help spec "my-tool"))
      (os:exit 0))
    ;; …main logic…
    ))

Composing with other stdlib modules


Error handling

Every parse error raises a runtime error with a tagged message. To turn errors into a friendly exit:

(let ((r (catch 'runtime.error
           (args:parse-command-line spec))))
  (if (and (pair? r) (eq? (car r) 'runtime-error))
      (begin (eprintln (cadr r))
             (display (args:help spec "my-tool"))
             (os:exit 2))
      (run r)))

Reference summary

args:parse                spec argv                 -> hash-map
args:parse-command-line   spec                      -> hash-map
args:get                  hash-map key [default]    -> value
args:help                 spec [name]               -> string

Spec entry shapes (any of):

(key (flag …) type)
(key (flag …) type default)
(key (flag …) type opts)
(key (flag …) type default opts)

type ∈ { flag | string | int | float | list | (list scalar) } opts keys ⊆ { required? help metavar choices parse validate action } action ∈ { set | append | count }