sema-cli

0.1.0

CLI argument parsing with flags, options, and subcommands

$ sema pkg install sema-cli
Readme Versions 1 Dependencies 0

sema-cli

CLI argument parsing with flags, options, and subcommands.

Install

sema pkg add sema-cli

Quick start

(import "sema-cli")

(define spec
  (list {:name "host"    :short "H" :type :string :default "localhost" :help "Hostname"}
        {:name "port"    :short "p" :type :int    :default 3000        :help "Port number"}
        {:name "verbose" :short "v" :type :bool                        :help "Verbose output"}))

(cli/parse spec (list "--port" "8080" "-v" "server.log"))
; => {:args ("server.log") :host "localhost" :port 8080 :verbose #t}

A small CLI with subcommands

A complete todo script — save as todo.sema and run sema todo.sema -- add "buy milk" --priority 2 (Sema passes script arguments after --; cli/args strips the interpreter noise):

(import "sema-cli")

(define commands
  (list {:name "add"
         :help "Add a todo item"
         :spec (list {:name "priority" :short "p" :type :int :default 1 :help "Priority (1-3)"})
         :run  (fn (opts)
                 (for-each
                   (fn (item)
                     (println (format "added [p~a] ~a" (get opts :priority) item)))
                   (get opts :args)))}
        {:name "list"
         :help "List todo items"
         :spec (list {:name "all" :short "a" :type :bool :help "Include completed items"})
         :run  (fn (opts)
                 (println (if (get opts :all) "listing all items" "listing open items")))}))

(cli/dispatch commands (cli/args)
              {:program "todo" :description "A tiny todo manager"})
$ sema todo.sema -- add "buy milk" --priority 2
added [p2] buy milk

$ sema todo.sema -- help
todo — A tiny todo manager

Usage: todo <command> [options]

Commands:
  add   Add a todo item
  list  List todo items

Run 'todo help <command>' for command options.

API

Function Description
(cli/args) The script's own arguments, without the interpreter/script/-- prefix
(cli/parse spec args) Parse args against a spec; returns a map, positionals under :args
(cli/help spec program-name description) Generate help text from a spec
(cli/parse! spec args program-name description) Parse with auto --help/-h and exit-on-error
(cli/dispatch commands args opts?) Route args to a subcommand and run its handler

cli/args

(cli/args)
; sema todo.sema -- add x   =>   ("add" "x")

Returns (sys/args) minus the interpreter name, the script path, and the leading -- separator — i.e. exactly the arguments your script was given. Use it as the args for cli/parse, cli/parse!, or cli/dispatch.

cli/parse

(cli/parse spec (list "--out" "a.txt" "-v" "--" "-x"))
; => {:args ("-x") :out "a.txt" :verbose #t}

Parses a list of argument strings against a spec (see Spec format). Returns a map keyed by each option's long :name (as a keyword), with positional arguments under :args. Raises an error for unknown options, missing values, bad :int/:float input, and missing {:required #t} options.

cli/help

(cli/help spec "myapp" "A demo application")

Returns the formatted help text: a header, a usage line, and one line per option showing short/long forms, the value type (<str>, <int>, <float>), the :help text, and any (required) / (default: ...) markers.

cli/parse!

(define opts (cli/parse! spec (cli/args) "myapp" "A demo application"))

Like cli/parse, but made for script entrypoints: prints help and exits 0 when args contain --help or -h, prints the parse error to stderr and exits 1 on invalid input. Note that -h is reserved — don't give an option the short name "h" when using cli/parse!.

cli/dispatch

(cli/dispatch commands args)
(cli/dispatch commands args {:program "todo" :description "A tiny todo manager"})

commands is a list of {:name "add" :help "..." :spec [...] :run (fn (opts) ...)}. The first arg selects the command; the rest are parsed against that command's :spec with cli/parse and passed to :run. Returns the handler's result.

  • No args, help, --help, or -h → prints an overview of all commands, returns nil.
  • help <command> (or --help after the command name) → prints that command's option help, returns nil.
  • Unknown command → raises an error.

Spec format

Each option is a map:

Key Required Description
:name yes Long option name (e.g. "port"); also the result key
:short no Single-letter short flag (e.g. "p")
:type no :string (default), :int, :float, or :bool
:default no Value used when the option is not given
:required no #t to error when the option is missing
:help no Description shown in help text

A non-bool option without :default and without :required is simply absent from the result map when not given. Bool options always appear, defaulting to #f.

Parsing rules

  • --name value or --name=value for long options
  • -x value for short options
  • --flag / -f for boolean flags (presence = #t)
  • -abc sets the boolean flags -a -b -c; it errors if any letter is not a boolean short flag
  • Negative numbers (-5, -2.5) that match no option are positional
  • -- stops option parsing; everything after it is positional
  • Unknown options, missing values, non-numeric :int/:float values, and missing required options raise errors (cli/parse: ...)

Testing

sema pkg add sema-test   # once
sema tests.sema

License

MIT

VersionSizePublished
0.1.0 7 KB 2026-07-07 21:27:55
No dependencies