Rye comes with a small, expressive CLI parsing dialect that lets you define flags, positional arguments, validation, and nested subcommands directly in Rye code. It also generates help text for you.
You’ll use these builtins:
parse-args - parse argv against a spec, returns a Dictparse-args\ctx - like parse-args, but returns a Context (dot-access)generate-help - generate help text from a specgenerate-help\command - generate help for a specific subcommand pathformat-parse-errors - pretty-print errors from failed parsingThe dialect lives in the spec block you pass to parse-args. It shares some similarities with other Rye dialects, like validation dialect. It supports:
-v|verbose flag,-o|output string requiredinput: string required, files: string manysubcommand { 'init { ... } 'remote { subcommand { 'add { ... } } } }Looking more into details, the dialect shares mechanisms and vocabulary with the validation dialect:
-n|count integer optional 10num: check { > 0 } "must be positive"-d|doit flag do { do-it }Help is also generated from the dialect so it supports:
program "mytool", description "..."doc "Enable verbose"Our basic cli has a flag –verbose, flag with argument –output and a required positional argument at the end.
; argv simulated as a block for examples
args: { "--verbose" "-o" "out.txt" "in.txt" }
spec: {
program "mytool"
description "Example tool using Rye's CLI dialect"
-v|verbose flag doc "Enable verbose output"
-o|output string required doc "Output file"
input: string required doc "Input file"
}
result: parse-args args spec |^fix {
print format-parse-errors result
}
print2 "verbose: " result."verbose"
print2 "output: " result."output"
print2 "input: " result."input"
string, integer, decimal, boolean, file, anyoptional 42, optional 1.5, optional "stdout", etc.flag is boolean.Like with validation dialect, values are converted according to the dialect.
res: parse-args\ctx { } {
-n|count integer optional 3 doc "How many"
-r|rate decimal optional 1.5
-q|quiet flag
}
|do\in {
probe count ; [Integer: 3]
probe rate ; [Decimal: 1.5]
probe quiet ; [Boolean: false]
}
Use list to collect repeated flags; use many for variadic positionals.
parse-args
{ -I "/usr/include" -I "./include" }
{ -I|include string list }
|-> "include" |length? |print ; 2
parse-args
{ "a.txt" "b.txt" "c.txt" }
{ files: string many }
|-> "files" |length? |print ; 3
Attach check { ... } "message" to a flag or positional. The block runs with the value injected; it succeeds on truthy boolean or positive integer.
spec: { -p|port integer check { >= 1024 } "port must be >= 1024" }
parse-args { --port 80 } spec
|fix {
.format-parse-errors |print
}
; => Error: --port port must be >= 1024
Another example for positionals:
spec: { file: string check { .ends-with? ".txt" } "must be .txt file" }
parse-args { "notes.pdf" } spec
|fix {
.format-parse-errors |print
}
Define subcommands with 'name { ... }. Nest with subcommand { ... } inside another.
spec: {
-v|verbose flag
subcommand {
'commit {
-m|message string required doc "Commit message"
-a|amend flag doc "Amend last commit"
}
'remote {
doc "Manage remotes"
subcommand {
'add {
name: string required
url: string required
}
}
}
}
}
; commit
r1: parse-args { commit -m "Init" } spec
print r1."command" ; "commit"
print r1."message" ; "Init"
; nested: remote add
r2: parse-args { remote add origin "https://example.com" } spec
print r2."command" ; "remote add"
print r2."command-path" ; ["remote" "add"]
print r2."name" ; "origin"
print r2."url" ; "https://example.com"
Global flags (declared at the top level) are available alongside subcommands.
args: rye .Args? ; read argv as a Rye block
spec: {
program "minitool"
description "Demo CLI"
-v|verbose flag doc "Verbose"
-o|output string required doc "Output file"
input: string required doc "Input file"
}
res: parse-args args spec
|fix {
print generate-help spec
print "" ,
.format-parse-errors .print ,
exit 1
}
if res."verbose" { print "Verbose mode ON" }
Write file res."output" Read file res."input"
When flags select actions, you don’t need to query the parsed flags afterward. Attach a do { ... } block to each flag or option and call your functions directly from the CLI spec.
do-it: does { print "Just do it" }
greet: fn { name } { print join { "Hello, " name "!" } }
spec: {
program "actions"
description "Run actions directly from the CLI spec"
-d|doit flag doc "Do it" do { do-it }
-g|greet string doc "Greet someone"
check { .length? > 0 } "name must not be empty"
do { ::name greet name }
-h|help flag doc "Show this help" do { print generate-help spec }
}
; Simulated argv; use rye .Args? for real command-line arguments.
parse-args { --doit --greet "Ada" } spec
; Just do it
; Hello, Ada!
parse-args { --help } spec
; Prints the help generated from spec.
There is no if result."doit" or separate dispatch step: parse-args runs the matching actions. It still returns the usual dictionary, but this example doesn’t need it.
Actions run after the entire command line passes parsing and validation, in argument order, once per supplied occurrence. Absent flags and default values don’t trigger actions. If any argument fails validation, none of the actions run-including a help action if another required argument is missing.
For options with values, the converted, validated value is injected into the action block. In the example, ::name captures that value before calling greet. Actions run in the caller’s context, so they can access your functions and spec. They also work with subcommand flags and parse-args\ctx.
list initializes to an empty list by default so you can append naturally.check blocks run with the value injected; return true/positive to accept.