lib/nexus/cli.ex

defmodule Nexus.CLI do
  @moduledoc """
  Define callback that a CLI module needs to follow to be able
  to be runned and also define helper functions to parse a single
  command againts a raw input.
  """
  alias Nexus.Parser

  @callback version :: String.t()
  @callback banner :: String.t()
  @callback handle_input(cmd) :: :ok
            when cmd: atom
  @callback handle_input(cmd, args) :: :ok
            when cmd: atom,
                 args: Nexus.Command.Input.t()

  @optional_callbacks banner: 0, handle_input: 2, handle_input: 1

  @type t :: map

  @spec build(list(binary), module) :: {:ok, Nexus.CLI.t()} | {:error, atom}
  def build(raw, module) do
    cmds = module.__commands__()
    acc = {%{}, raw}

    {cli, _raw} =
      Enum.reduce_while(cmds, acc, fn spec, {cli, raw} ->
        try do
          input = Parser.run!(raw, spec)
          {:halt, {Map.put(cli, spec.name, input), raw}}
        rescue
          _ -> {:cont, {cli, raw}}
        end
      end)

    {:ok, struct(module, cli)}
  end
end