A clap-inspired CLI argument parsing framework for Elixir.
Provides declarative command definitions with arbitrarily nested subcommands, typed options, automatic help generation, and shell completion.
Usage
defmodule MyApp.CLI.Greet do
use Cheer.Command
command "greet" do
about "Greet someone"
argument :name, type: :string, required: true, help: "Name to greet"
option :loud, type: :boolean, short: :l, help: "Shout the greeting"
end
@impl Cheer.Command
def run(%{name: name} = args, _opts) do
greeting = "Hello, #{name}!"
if args[:loud], do: String.upcase(greeting), else: greeting
end
endArchitecture
Commands are modules that use Cheer.Command. Each command declares its name,
about text, arguments, options, and subcommands via macros. At compile time,
Cheer builds a command tree that handles:
- Argv routing through nested subcommands
- Option parsing via OptionParser
- Type validation and coercion
- Help text generation
- Shell completion script generation (bash, zsh, fish)
Summary
Functions
Returns the command-line arguments, accounting for Burrito binaries.
Run as an entry point with argv from argv/0, halting the VM.
Run as an escript entry point, halting the VM with a conventional exit code.
Parse argv and return the matched command without invoking its handler.
Parse argv and dispatch to the appropriate command handler.
Returns the command tree as a nested data structure.
Functions
@spec argv() :: [String.t()]
Returns the command-line arguments, accounting for Burrito binaries.
A Burrito-wrapped binary does not
populate System.argv/0; its arguments arrive through
Burrito.Util.Args.argv/0. This returns whichever one is correct for the
current runtime, so a single entry point works under mix run, an escript,
and a Burrito binary.
Cheer.main(MyApp.CLI, Cheer.argv(), prog: "myapp")The check is Burrito.Util.running_standalone?/0, not merely whether Burrito
is loaded, so mix test and iex -S mix in a project that ships via Burrito
still see their own argv.
Burrito is resolved at runtime rather than referenced at compile time, so Cheer takes no dependency on it and this compiles clean in projects that do not use it.
@spec exit_code(term()) :: 0 | 2
Map a run/3 or parse/3 result to a conventional process exit code.
Returns 2 for {:error, :usage} and 0 for anything else, which is the
mapping main/3 applies. Call it directly when a command wants exit codes of
its own and so has to halt itself, rather than restating the convention:
case Cheer.run(MyApp.CLI, argv, prog: "myapp") do
{:error, :not_found} -> System.halt(4)
other -> System.halt(exit_code(other))
endPure, unlike main/3, which halts.
Run as an entry point with argv from argv/0, halting the VM.
Equivalent to main(root_command, Cheer.argv(), []). Use this where the
runtime hands you no argv of its own, such as an Application.start/2 in a
Burrito binary that exits when the command returns.
Run as an escript entry point, halting the VM with a conventional exit code.
Dispatches argv like run/3, then halts the VM: 0 on success (including
--help and --version) and 2 on a usage failure. The command's own
run/2 return value does not affect the exit code; a command that wants
custom codes should call run/3 and halt itself, mapping the results it does
not special-case with exit_code/1.
def main(argv), do: Cheer.main(MyApp.CLI, argv, prog: "myapp")An escript is handed its argv, so pass it straight through. A Burrito binary
is not: use main/1, or pass argv/0 explicitly to set :prog.
Cheer.main(MyApp.CLI, Cheer.argv(), prog: "myapp")
@spec parse(module(), [String.t()], keyword()) :: {:ok, module(), map()} | :handled | {:error, :usage}
Parse argv and return the matched command without invoking its handler.
Everything run/3 does up to the point of dispatch: subcommand resolution,
option parsing, defaults, env fallback, validation, and help output. The
matched command's run/2 is not called.
Options:
:prog- program name for usage lines (default: derived from root command name)
Return value
{:ok, command_module, args}on a successful parse.argsis the maprun/2would have received.:handledwhen Cheer printed help or a version and there is nothing left to run.{:error, :usage}on a parse failure, error already printed.
:handled is distinct from :ok on purpose: a caller can tell "Cheer printed
help" from a handler that legitimately returned :ok.
Use this where argv configures something rather than driving a unit of work,
such as an Application.start/2 that turns options into a supervision tree:
def start(_type, _args) do
case Cheer.parse(MyApp.CLI.Root, Cheer.argv(), prog: "myapp") do
{:ok, MyApp.CLI.Serve, args} ->
Supervisor.start_link(children(args), strategy: :one_for_one, name: MyApp.Supervisor)
:handled ->
System.halt(0)
{:error, :usage} ->
System.halt(2)
end
endbefore_run and persistent_before_run hooks still run, since they shape the
args. after_run hooks do not: nothing ran, so there is no result to pass
them.
A command resolved only this way has no run/2 to implement. Declare
Cheer.Command.DSL.parse_only/1 in its command block so the compiler stops
asking for one.
Parse argv and dispatch to the appropriate command handler.
Options:
:prog- program name for usage lines (default: derived from root command name)
Return value
- On success, returns whatever the matched command's
run/2returns. - On a usage failure (unknown option, missing required argument, bad choice,
unknown or ambiguous subcommand, missing required subcommand), prints the
error and returns
{:error, :usage}. - For
--help/--version(and a bare command that just prints help), returns:ok.
Use the {:error, :usage} result to set a nonzero exit code, or call
main/3 to have Cheer halt with a conventional code for you.
Pass Cheer.argv/0 rather than System.argv/0 if the app also ships as a
Burrito binary. Use parse/3 instead if argv configures a process that keeps
running rather than driving a unit of work.
Returns the command tree as a nested data structure.
Useful for documentation generation, introspection, and testing.