README.md

# HelpfulOptions

Processes command-line parameters.

Provides

* rich parameter declaration,
* formatted error messages,
* automatic logging setup,
* formatted parameter descriptions for `--help`.

It is intended for use with Mix tasks,
Bakeware and other systems for creating Elixir
command-line programs.

# Approach

This library assumes that the parameters that you want to process
are ordered as follows:

```sh
SUBCOMMAND --switch PARAMETER OTHER
```

With each part being repeatable and optional.

A Git example

```sh
git remote add -t feature -b main git@example.com:foo/bar
    __________ __________________ _______________________
        ^               ^                   ^
        |               |                   |
    subcommands      switches             other
```

Subcommands, like Git's `remote` can be one or more words.
Switches are `--key value` pairs.
Other parameters are positional arguments that follow the switches.

This library provides two main functions:

* `HelpfulOptions.parse/2` — for simple CLIs that only receive switches
  (and optional positional arguments).
* `HelpfulOptions.parse_commands/2` — for CLIs that accept commands
  and subcommands, each with their own switches and other parameters.

# Usage

## Simple CLI with `parse/2`

Use `parse/2` when your program does not have subcommands — it only
receives switches and, optionally, positional ("other") arguments.

```elixir
switches = [
  foo: %{type: :string, required: true},
  dry_run: %{type: :boolean},
  bar: %{type: :string, required: true}
]

case HelpfulOptions.parse(System.argv(), switches: switches, other: 1) do
  {:ok, parameters, [url]} ->
    MyApp.add_remote(parameters, url)
    0
  {:error, error} ->
    IO.puts(:stderr, error)
    1
end
```

## CLI with commands via `parse_commands/2`

Use `parse_commands/2` when your program handles multiple commands
(and, optionally, subcommands), each with its own set of switches
and other parameters — similar to tools like `git`, `mix`, or `docker`.

You provide a list of command definitions. Each definition specifies
the command words to match, the expected switches and other parameters:

```elixir
definitions = [
  %{commands: ["remote", "add"], switches: [name: %{type: :string, required: true}], other: 1},
  %{commands: ["remote"], switches: [verbose: %{type: :boolean}], other: nil},
  %{commands: ["status"], switches: [short: %{type: :boolean}], other: nil},
  %{commands: [], switches: [version: %{type: :boolean}], other: nil}
]

case HelpfulOptions.parse_commands(System.argv(), definitions) do
  {:ok, ["remote", "add"], switches, [url]} ->
    MyApp.add_remote(switches.name, url)
  {:ok, ["remote"], switches, _other} ->
    MyApp.list_remotes(switches)
  {:ok, ["status"], switches, _other} ->
    MyApp.show_status(switches)
  {:ok, [], switches, _other} ->
    if switches[:version], do: MyApp.print_version()
  {:error, {:unknown_command, commands}} ->
    IO.puts(:stderr, "Unknown command: #{Enum.join(commands, " ")}")
  {:error, error} ->
    IO.puts(:stderr, to_string(error))
end
```

Definitions are matched longest-first, so `["remote", "add"]` is
tried before `["remote"]`. An empty commands list (`[]`) matches
when no subcommand is given.

## `switches`

Underscores `_` in switches are replaced by hyphens `-`.

So, `:dry_run` is actually the command-line parameter `--dry-run`.

Three switches are added by the library:

```elixir
  [
    ...
    help: %{type: :boolean},
    verbose: %{type: :count},
    quiet: %{type: :boolean}
  ]
```

See the doc tests in `HelpfulOptions` for more examples.

## Installation

The package can be installed by adding `helpful_options`
to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:helpful_options, "~> 0.3"}
  ]
end
```