Prompt.Router behaviour (prompt v0.12.0)

View Source

Router for Prompt

Simplifies defining commands, sub-commands and arguments.

Choose the module responsible for taking the command line arguments and use Prompt.Router, otp_app: :your_app at the top.

Then simply define your commands and arguments.

Exposes a main/1 function that is called with the command line args. It runs the matching command and halts the VM with the command's exit code.

Exposes a run/1 function that does the same but returns the exit code instead of halting, which is what you want in tests:

assert My.CLI.run(["checkout", "--branch", "dev"]) == 0

To have main/1 return the exit code too, for example when it is called from a release or a Mix task, use use Prompt.Router, otp_app: :my_app, halt: false.

Arguments

See arg/3

Help

help/0 prints, in order of priority, the help: option given to use Prompt.Router, this module's @moduledoc, or usage generated from the commands. command --help prints the command module's @moduledoc (when available) followed by generated usage. Override help/0 or command_help/1 to change this.

--help output goes to stdout and exits 0. When parsing fails, the reason and the generated usage of the command go to stderr and the exit code is 1.

Distributing as an escript

Escripts strip documentation chunks by default, so @moduledoc text is not available at runtime. Add strip_beams: [keep: ["Docs"]] to the escript: options in mix.exs if you want moduledoc text in help. It is needed only for that: generated usage and the help: option work without it.

Example

defmodule My.CLI do
  use Prompt.Router, otp_app: :my_app

  command :checkout, My.CheckoutCommand do
    arg :help, :boolean
    arg :branch, :string, short: :b, default: "main"
  end

  command "", My.DefaultCommand do
    arg :info, :boolean
  end
end

defmodule My.CheckoutCommand do
  use Prompt.Command

  @impl true
  def process(arguments) do
    # arguments will be a map of the defined arguments and their values
    # from the command line input
    # If someone used the command and passed `--branch  feature/test`, then
    # `argmuments would look like `%{help: false, branch: "feature/test"}`
    display("checking out " <> arguments.branch)
  end
end

defmodule My.DefaultCommand do
  use Prompt.Command

  @impl true
  def init(arguments) do
    # you can implement the `c:init/1` callback to transform
    # the arguments before `c:process/1` is called if you want
    arguments
  end
  
  @impl true
  def process(arguments) do
    # arguments will have a key of `:leftover` for anything
    # passed to the command that doesn't have a `arg` defined.
    # If someone called this with `--info --test something`, then
    # arguments will look like `%{info: true, leftover: ["something", "--test"]}`:
    # unused positional values first, then unknown flags as typed
    display("showing info")
  end
end

Summary

Callbacks

Prints the usage of one command to the screen when --help is passed after it. The default prints the command module's @moduledoc when docs are available, then the generated usage.

Called when the flag --completions string is passed on the command-line.

This function is called after a command is done.

Prints help to stdout when --help is passed as an argument.

Prints the error reason and the router usage to stderr, for example when no command matches and there is no fallback command.

The entry point for an escript or release. Calls run/1 with the command line args and then halts the VM with the returned exit code.

Filters and calls the correct command module based on command line input and returns the exit code (see handle_exit_value/1). It never halts the VM, which makes it the function to call from tests.

Prints the version from the projects mix.exs file

Functions

Defines the arguments of a command.

Declares a command.

Declares a positional argument of a command.

Callbacks

command_help(t)

@callback command_help(Prompt.Router.CommandSpec.t()) :: non_neg_integer()

Prints the usage of one command to the screen when --help is passed after it. The default prints the command module's @moduledoc when docs are available, then the generated usage.

Overridable

generate_completions(binary)

@callback generate_completions(binary()) :: non_neg_integer()

Called when the flag --completions string is passed on the command-line.

The default behaviour for this is to write the completion script to the screen.

zsh, bash and fish are supported out of the box. For other shells, this callback will need to be implemented.

see https://github.com/zsh-users/zsh-completions/blob/master/zsh-completions-howto.org

Overrideable

handle_exit_value(any)

@callback handle_exit_value(any()) :: non_neg_integer()

This function is called after a command is done.

It does it's best to handle any value returned from a command and turn it into an integer, 0 being a successful command and any non-zero being an error. It returns the code and does not halt, main/1 does that.

An {:error, reason} prints the reason to stderr and results in 1.

Overrideable

help()

@callback help() :: non_neg_integer()

Prints help to stdout when --help is passed as an argument.

Overridable

help(t)

@callback help(String.t()) :: non_neg_integer()

Prints the error reason and the router usage to stderr, for example when no command matches and there is no fallback command.

Overridable

main(list)

@callback main([binary()]) :: no_return() | non_neg_integer()

The entry point for an escript or release. Calls run/1 with the command line args and then halts the VM with the returned exit code.

If the router was defined with use Prompt.Router, halt: false, it returns the exit code instead of halting.

run(list)

@callback run([binary()]) :: non_neg_integer()

Filters and calls the correct command module based on command line input and returns the exit code (see handle_exit_value/1). It never halts the VM, which makes it the function to call from tests.

version()

@callback version() :: non_neg_integer()

Prints the version from the projects mix.exs file

Overridable

Functions

arg(arg_name, arg_type, opts \\ [])

(macro)

Defines the arguments of a command.

Argument Name

This indicates what the user will type as the option to the sub-command. For example,

arg :print, :boolean

would allow the user to type $ your_command --print

Options

Available options are:

  • default - a default value if the user doesn't use this option
  • short - an optional short argument option i.e short: :h would all the user to type -h
  • required - the flag must be given
  • in - a list of allowed values of the arg type, e.g. strings for :string
  • doc - a description used in help

An unknown option raises at compile time.

command(name, module)

(macro)

Declares a command.

Takes an atom or string as the command name and a Prompt.Command module. The do block may declare arg/3, positional/3, and nested command calls. A nested command is typed after its parent, for example app accounts add.

Without a module, command name do ... end declares a group: a command that only holds nested commands. Its block may contain only command calls.

command :categories do
  command :add, My.AddCategory do
    positional :name, :string, required: true
  end

  command :list, My.ListCategories
end

command(name, module, list)

(macro)

positional(arg_name, arg_type, opts \\ [])

(macro)

Declares a positional argument of a command.

Options

  • required - the argument must be given
  • multiple - collects all remaining values, only allowed on the last positional
  • in - a list of allowed values of the arg type, e.g. strings for :string
  • doc - a description used in help