Turmeric scripts can receive command-line arguments via the built-in *args*
cons list. For structured parsing -- flags, named options, subcommands, and
type coercion -- the standard library provides stdlib/args.tur with a
builder-pattern API.
Use -- to separate tur run flags from your script's arguments:
tur run script.tur -- input.txt output.txt --verbose
Everything after -- is forwarded to the script as a cons list bound to
*args*.
*args**args* is a pre-declared cons list of :cstr values. Each element is one
argument string, in order.
;; script.tur
(defn main [] :int
(println "arg 0:" (head *args*))
(println "arg 1:" (head (tail *args*)))
0)
;; Walk all arguments
(defn print-args [args :int] :void
(when (some? args)
(println (head args))
(print-args (tail args))))
(print-args *args*)
For scripts that do not import stdlib, head and tail are available as
stdlib natives automatically:
(defn main [] :int
(let [n (cstr->parse-int (head *args*))]
(println "count:" n)
0))
Note:
cstr->parse-intparses a C string into:int. It is available without any import.
stdlib/args.turFor anything more complex than a positional argument or two, use
stdlib/args.tur. It provides:
--verbose, --help--input=file.txt or --input file.txt with type coercionbuild/test/push dispatch--help output from the spec(load "stdlib/args.tur")
Build a spec with the args/spec-* functions, then call args/parse:
(defn main [] :int
(let [spec (-> (args/spec-new)
(args/spec-prog "mytool")
(args/spec-flag "--verbose")
(args/spec-option "--input" "string" 0) ; required
(args/spec-option "--count" "int" (cstr "1")) ; default 1
(args/spec-option "--output" "string" (cstr "out.txt")))
result (args/parse spec *args*)]
(if (args/error? result)
(do
(println "error:" (args/error-msg result))
1)
(do
(when (args/has? result "--verbose")
(println "verbose mode on"))
(println "input: " (args/get-str result "--input"))
(println "count: " (args/get-int result "--count"))
(println "output:" (args/get-str result "--output"))
0))))
tur run mytool.tur -- --input=data.csv --count=5
| Type string | Accessor | Notes |
|---|---|---|
"string" |
args/get-str |
Returned as :cstr |
"int" |
args/get-int |
Parsed to :int |
"float" |
args/get-int |
Parsed to :float |
"bool" |
args/get-bool |
"true"/"1" => true |
Flags are boolean switches -- present means true, absent means false:
(args/spec-flag spec "--verbose")
(args/spec-flag spec "--dry-run")
;; At runtime:
(args/has? result "--verbose") ; => true/false
Positional arguments are everything that is not a flag or option value. Access them as a cons list:
(let [pos (args/positional result)]
(println "first file:" (head pos))
(println "second file:" (head (tail pos))))
tur run copy.tur -- src.txt dst.txt
Register subcommands with their own specs. Each subcommand can have its own flags, options, and nested subcommands:
(defn main [] :int
(let [build-spec (-> (args/spec-new)
(args/spec-flag "--release")
(args/spec-option "--output" "string" (cstr "a.out")))
test-spec (-> (args/spec-new)
(args/spec-flag "--verbose")
(args/spec-option "--filter" "string" 0))
spec (-> (args/spec-new)
(args/spec-prog "myapp")
(args/spec-subcommand "build" build-spec)
(args/spec-subcommand "test" test-spec))
result (args/parse spec *args*)]
(if (args/error? result)
(do
(args/print-help spec)
1)
(let [sub (args/subcommand result)]
(cond
(cstr= sub "build")
(let [r (args/sub-result result)]
(println "building, release:" (args/has? r "--release"))
0)
(cstr= sub "test")
(let [r (args/sub-result result)]
(println "testing, filter:" (args/get-str r "--filter"))
0)
true
(do
(args/print-help spec)
1))))))
tur run myapp.tur -- build --release
tur run myapp.tur -- test --filter=core --verbose
Nested subcommands work the same way: call args/sub-result on the outer
result, then args/subcommand on the inner to walk the chain.
args/print-help writes usage text to stdout based on the spec:
(when (args/has? result "--help")
(args/print-help spec)
(exit 0))
Or just let the parser handle missing required options and check
args/error?:
(when (args/error? result)
(println (args/error-msg result))
(args/print-help spec)
(exit 1))
Both spec and result are heap-allocated. Free them when done:
(args/spec-free spec)
(args/result-free result)
| Function | Signature | Description |
|---|---|---|
args/spec-new |
-> :int |
Create an empty arg spec |
args/spec-prog |
spec :int name :cstr -> :int |
Set program name for help |
args/spec-flag |
spec :int name :cstr -> :int |
Register a boolean flag |
args/spec-option |
spec :int name :cstr type :cstr dflt :int -> :int |
Register a named option |
args/spec-subcommand |
spec :int name :cstr sub :int -> :int |
Register a subcommand |
args/parse |
spec :int argv :int -> :int |
Parse *args* against spec |
args/has? |
result :int key :cstr -> :bool |
True if flag/option was supplied |
args/get-str |
result :int key :cstr -> :cstr |
Get option value as string |
args/get-int |
result :int key :cstr -> :int |
Get option value as int |
args/get-bool |
result :int key :cstr -> :bool |
Get option value as bool |
args/positional |
result :int -> :int |
Cons list of positional args |
args/subcommand |
result :int -> :cstr |
Matched subcommand name |
args/sub-result |
result :int -> :int |
Parse result for subcommand |
args/error? |
result :int -> :bool |
True if parsing failed |
args/error-msg |
result :int -> :cstr |
Error description string |
args/print-help |
spec :int -> :void |
Print usage to stdout |
args/spec-free |
spec :int -> :void |
Free spec memory |
args/result-free |
result :int -> :void |
Free result memory |
The CLAUDE.md file for this project enforces a strict rule: CLI arguments
must only be read via *args* or stdlib/args.tur. Reading from
g_tur_args via raw inline C is forbidden. See CLAUDE.md for details.