Terminalwire.CLI turns a module into a command router. Public functions become commands, their parameters become arguments, and @desc becomes the help text. It covers the common case (named commands with positional arguments) without a parser library. When you need flags or subcommands, drop down to a plain handler.
Commands
A public function is a command. Its name is what the user types.
defmodule MyApp.CLI do use Terminalwire.CLI, name: "my-app" @desc "Say hello" def hello do puts("Hello, World!") end end
my-app hello
Hello, World!
Running my-app with no command (or my-app help) prints the generated list of commands and their @desc text.
Arguments
Function parameters are positional arguments, parsed left to right.
@desc "Greet NAME with GREETING" def greet(greeting, name) do puts("#{greeting}, #{name}!") end
my-app greet "Good morning" Ada
Good morning, Ada!
A default value makes an argument optional.
@desc "Say hello to NAME, or the world" def hello(name \\ "World") do puts("Hello, #{name}!") end
my-app hello
Hello, World!
my-app hello Ada
Hello, Ada!
Flags and subcommands
Terminalwire.CLI deliberately stops at positional arguments. For --flags, -o options, or nested subcommands, write a handler (a run/1 function that takes a Terminalwire.Server.Context) and parse the args yourself. Optimus is a good fit.
defmodule MyApp.Deploy do alias Terminalwire.Server.Context @optimus Optimus.new!( name: "deploy", args: [env: [required: true]], flags: [verbose: [short: "-v", long: "--verbose"]] ) def run(ctx) do # Don't use Optimus.parse!/2 — it calls System.halt on error, which would # take your server down. Parse the result and return an exit code instead. case Optimus.parse(@optimus, Context.args(ctx)) do {:ok, parsed} -> Context.puts(ctx, "Deploying #{parsed.args.env}…") if parsed.flags.verbose, do: Context.puts(ctx, "(verbose)") 0 {:error, message} -> Context.warn(ctx, message) 1 end end end
Mount a handler the same way you mount a Terminalwire.CLI module: pass &MyApp.Deploy.run/1 as the :handler. Both forms receive the same Context, so you can mix them: a Terminalwire.CLI router for everyday commands, a handler for the one command that needs rich parsing.
Never call
System.halt(or anything that does, likeOptimus.parse!). Your command runs inside your server process; halting takes the whole server down. Return an integer exit code instead, or callContext.exit(ctx, status).
Next: Standard I/O covers reading and writing the terminal in more detail.