CLI dialect

CLI dialect: parse-args, help, and subcommands

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 Dict
  • parse-args\ctx - like parse-args, but returns a Context (dot-access)
  • generate-help - generate help text from a spec
  • generate-help\command - generate help for a specific subcommand path
  • format-parse-errors - pretty-print errors from failed parsing

The dialect lives in the spec block you pass to parse-args. It shares some similarities with other Rye dialects, like validation dialect. It supports:

  • short and long flags via flagwords: -v|verbose flag,
  • short and long flags with an argument -o|output string required
  • positional args via setwords: input: string required, files: string many
  • nested subcommands: subcommand { 'init { ... } 'remote { subcommand { 'add { ... } } } }

Looking more into details, the dialect shares mechanisms and vocabulary with the validation dialect:

  • defaults for optional fields: -n|count integer optional 10
  • custom validation with check blocks: num: check { > 0 } "must be positive"

Help is also generated from the dialect so it supports:

  • program metadata for help: program "mytool", description "..."
  • inline docs for help: doc "Enable verbose"

Basics

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"

Types and defaults

  • Types: string, integer, decimal, boolean, file, any
  • Optional with default: optional 42, optional 1.5, optional "stdout", etc.
  • Flags with values use the declared type; plain 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]
}  

Repeatable options and positional lists

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

Validation with check

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
}

Subcommands (git-like and nested)

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.

Putting it together: a tiny CLI

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"

Notes and tips

  • Long flag names become keys; if only a short flag exists, the short name is used.
  • list initializes to an empty list by default so you can append naturally.
  • Validation check blocks run with the value injected; return true/positive to accept.