Cheer is not only for escripts. The same command definition can power a Mix
task, so mix greet world --loud parses, validates, and renders help exactly
like a standalone CLI. No framework changes are needed.
Full runnable project: examples/mix_task/.
The task
use Cheer.MixTask combines use Mix.Task and use Cheer.Command and generates
the Mix run/1 entry point. Declare the command and implement the leaf run/2:
defmodule Mix.Tasks.Greet do
use Cheer.MixTask
command "greet" do
about "Greet someone with style"
argument :name, type: :string, required: true, help: "Who to greet"
option :greeting, type: :string, default: "Hello", env: "GREET_GREETING",
help: "Greeting word"
option :loud, type: :boolean, short: :l, help: "SHOUT the greeting"
option :times, type: :integer, short: :n, default: 1,
validate: fn n -> if n in 1..10, do: :ok, else: {:error, "times must be 1-10"} end,
help: "Repeat the greeting"
end
@impl Cheer.Command
def run(%{name: name} = args, _raw) do
greeting = "#{args[:greeting]}, #{name}!"
greeting = if args[:loud], do: String.upcase(greeting), else: greeting
for _ <- 1..args[:times] do
Mix.shell().info(greeting)
end
:ok
end
endThen mix greet world --loud parses, validates, and renders help exactly like a
standalone CLI.
What the helper generates
use Cheer.MixTask gives you:
- a
run/1Mix entry point that dispatches argv through the command withCheer.run/3, usingmix greetas the program name in help and usage output; - a
{:error, :usage}toexit({:shutdown, 2})translation, the Mix idiom for a nonzero exit; @shortdocdefaulted to the command'sabouttext, somix helplists the task.
If you prefer to wire it by hand (or need to override run/1 for setup), the
manual equivalent is a plain module that does use Mix.Task and use Cheer.Command
(they coexist because run/1 and run/2 have different arities) with a run/1
that delegates:
@impl Mix.Task
def run(argv) do
case Cheer.run(__MODULE__, argv, prog: "mix greet") do
{:error, :usage} -> exit({:shutdown, 2})
other -> other
end
endRun it
cd examples/mix_task
mix deps.get
mix greet world
# Hello, world!
mix greet world --loud --times 3
# HELLO, WORLD!
# HELLO, WORLD!
# HELLO, WORLD!
GREET_GREETING=Hey mix greet Ada
# Hey, Ada!
mix greet --help
# Usage: mix greet <name> [OPTIONS]
# ...
mix greet
# error: missing required argument(s): <name>
# ... (exits 2)
mix help and mix help greet render from @shortdoc and @moduledoc, the
standard Mix mechanism, so the task lists and documents itself like any other.
Signaling failure
The helper translates a {:error, :usage} result into exit({:shutdown, 2}),
the Mix idiom: Mix catches this exit and halts with the given code without a
crash report.
Do not use Cheer.main/3 inside a Mix task. main/3 calls System.halt,
which hard-kills the VM immediately: it skips Mix's own cleanup and is wrong
inside mix, CI, and umbrella projects. The helper uses Cheer.run/3 for
exactly this reason. main/3 is for escript entry points, where halting the VM
is the whole point.
Starting the application
Mix does not start your application before running a task. If the task needs the
app running (a repo, a supervision tree, config), start it inside run/2:
@impl Cheer.Command
def run(args, _raw) do
Mix.Task.run("app.start")
# ... now the app is running
endApplication.ensure_all_started/1 works too when you only need a specific
application. If you would rather start the app before argv is even parsed,
override the generated run/1 and call Cheer.run/3 yourself.
What it shows
use Cheer.MixTask-- one line that makes a Cheer command a Mix task, generating therun/1entry point.mix greetprogram name -- the usage line reads as the Mix invocation, not a bare command name.- Exit codes -- a usage failure becomes
exit({:shutdown, 2}), a conventional nonzero exit. - Mix help integration --
@shortdocdefaults to the command'sabout. - The
main/3caveat -- neverSystem.haltfrom inside a Mix task.
See also
- Cookbook: Greeter -- the same command as a standalone escript.
- Guides: Options, Arguments, Validation.