# Long-running server: argv as configuration

An escript and a Mix task both assume the process exits when the command
returns. A server does not: argv configures a supervision tree that keeps
running afterwards. The command tree is declared the same way; only the entry
point changes.

Full runnable project: [`examples/server/`](https://github.com/joshrotenberg/cheer/tree/main/examples/server).

## The entry point

`Application.start/2` reads argv, and the parse result decides what to
supervise:

```elixir
defmodule Server.Application do
  use Application

  @impl true
  def start(_type, _args) do
    case Cheer.parse(Server.CLI, Cheer.argv(), prog: "server") do
      {:ok, Server.CLI.Serve, args} ->
        children = [{Server.Listener, args}]
        Supervisor.start_link(children, strategy: :one_for_one, name: Server.Supervisor)

      :handled ->
        System.halt(0)

      {:error, :usage} ->
        System.halt(2)
    end
  end
end
```

`mod:` in `mix.exs` is what makes this the entry point:

```elixir
def application do
  [
    extra_applications: [:logger],
    mod: {Server.Application, []}
  ]
end
```

## Why `parse/3` and not `main/3`

`Cheer.main/3` halts the VM. That is the whole point of an escript entry point
and exactly wrong here: `start/2` has to return `{:ok, pid}` for the VM to keep
running.

`Cheer.parse/3` stops after resolution and validation and hands back the matched
command with its args, so the command tree describes the interface and the
supervision tree consumes the result. `run/2` is never called.

## The command has no handler

A command resolved this way has nothing to dispatch to, so `parse_only()` says
so and the compiler stops asking for a `run/2`:

```elixir
defmodule Server.CLI.Serve do
  use Cheer.Command

  command "serve" do
    parse_only()

    about "Start the server"

    option :port, type: :integer, short: :p, default: 4000, help: "Port to listen on"
    option :transport, type: :string, default: "http", choices: ["http", "stdio"],
      help: "Transport to serve"
  end
end
```

Without it, a leaf command with no handler warns, which fails a build using
`mix compile --warnings-as-errors` and forces exactly the stub this shape is
meant to avoid. `Cheer.run/3` on a `parse_only` command raises and names
`parse/3`, rather than dying on the missing callback.

It is per command, not inherited: declare it on the leaves that have no handler.
A tree with a mix of both is fine, and so is declaring it on a command that
implements `run/2` anyway.

## The `run/3` alternative

`Cheer.run/3` also returns rather than halting, so it works too. The cost is
that the leaf handler becomes a stub whose only job is to smuggle the parsed
options back out through its return value:

```elixir
case Cheer.run(Server.CLI, Cheer.argv(), prog: "server") do
  {:serve, args}   -> Supervisor.start_link(children(args), strategy: :one_for_one)
  :ok              -> System.halt(0)
  {:error, :usage} -> System.halt(2)
end
```

That shape has a second problem: `run/3` returns `:ok` for `--help` and
`--version`, which is indistinguishable from a handler that legitimately
returns `:ok`. `parse/3` reports that case as `:handled` instead, so the caller
does not have to constrain what its own handlers may return.

Use `run/3` when the handlers do real work and returning a config value is
incidental; use `parse/3` when argv is configuration.

## Halting, and exit codes

`start/2` has no "started nothing, exit cleanly" reply, so the cases with
nothing to supervise halt explicitly. `2` for a usage failure is the code
`Cheer.main/3` uses; `Cheer.exit_code/1` is the same mapping if you would rather
not restate it:

```elixir
result ->
  System.halt(Cheer.exit_code(result))
```

## Burrito

Under [Burrito](https://github.com/burrito-elixir/burrito), the wrapper boots
the BEAM and starts your applications. It never calls a main function, so
`main_module` in the release config is metadata: `Application.start/2` is the
real entry point, and that is where argv has to be read.

A Burrito binary also does not populate `System.argv/0`. Its arguments arrive
through `Burrito.Util.Args.argv/0`, which is what `Cheer.argv/0` returns when
`Burrito.Util.running_standalone?/0` is true.

The guard matters as much as the call. Keying off "is Burrito loaded" rather
than "is Burrito driving this run" hijacks argv under `mix test` and
`iex -S mix`, where the arguments belong to the test runner or the shell.

Getting the argv source wrong fails silently rather than loudly: `System.argv/0`
returns `[]`, every option falls through to its default, and the binary does
something plausible but wrong. A `--transport stdio` that quietly stays on the
`:http` default starts a web server while the client waits forever for a
handshake.

## `start/2` runs in dev too

Every way of starting the application runs `start/2`, including `iex -S mix` and
`mix test`. Those pass no argv of their own, so a bare command tree prints help
and halts, taking the shell or the test run with it.

When that is a problem, gate the dispatch:

```elixir
@impl true
def start(_type, _args) do
  if Application.get_env(:server, :dispatch_argv?, true) do
    dispatch(Cheer.argv())
  else
    Supervisor.start_link([], strategy: :one_for_one, name: Server.Supervisor)
  end
end
```

```elixir
# config/test.exs
config :server, dispatch_argv?: false
```

## Testing

`parse/3` makes the command tree testable without starting anything: it is a
pure question about argv, and the supervision tree never enters into it.

```elixir
test "serve defaults to http on 4000" do
  assert {:ok, Server.CLI.Serve, args} = Cheer.parse(Server.CLI, ["serve"])
  assert args.port == 4000
  assert args.transport == "http"
end

test "an unknown transport is a usage failure" do
  assert Cheer.parse(Server.CLI, ["serve", "--transport", "carrier-pigeon"]) ==
           {:error, :usage}
end
```

`Cheer.Test.run/3` still covers the handler path for trees where the handlers do
work. See [Testing](../guides/testing.md).

## Run it

```sh
cd examples/server
mix deps.get

mix run --no-halt -- serve
# [info] listening on port 4000 over http

mix run --no-halt -- serve --port 8080 --transport stdio
# [info] listening on port 8080 over stdio

SERVER_PORT=9000 mix run --no-halt -- serve
# [info] listening on port 9000 over http

mix run -- --help
# Usage: server <COMMAND>
# ... (exits 0)

mix run -- serve --transport carrier-pigeon
# error: --transport must be one of: http, stdio
# (exits 2)
```

## What it shows

- **`Cheer.parse/3`** -- resolution and validation without dispatch, so argv can
  configure a supervision tree.
- **`parse_only()`** -- the leaf declares that it has no handler, so no stub
  `run/2` and no compiler warning.
- **`Cheer.argv/0`** -- the right argv under `mix run`, an escript, and a
  Burrito binary.
- **`:handled`** -- help and version reported distinctly from a handler that
  returns `:ok`.
- **Explicit halts** -- `start/2` returns `{:ok, pid}` or the VM exits; there is
  no third option.
- **The dev-shell caveat** -- `start/2` also runs under `iex -S mix` and
  `mix test`.

## See also

- Cookbook: [Greeter](greeter.md) -- the same declarations as an escript that
  exits when the command returns.
- Cookbook: [Mix task](mix_task.md) -- the other entry point that must not halt
  the VM.
- Guides: [Testing](../guides/testing.md),
  [Help and output](../guides/help_and_output.md).
