lib/nosedrum/storage.ex

defmodule Nosedrum.Storage do
  @moduledoc """
  Storages contain commands and are used by command invokers to look up commands.

  How you start a storage is up to the module itself - what is
  expected is that storage modules implement the behaviours
  documented in this module.

  The public-facing API of storage modules takes an optional argument,
  the storage process or other information used to identify the storage
  such as an ETS table name.
  """

  @typedoc """
  A single command module or mapping of subcommand names to command groups.

  In addition to subcommand names, the key `:default` can be specified by
  the module. `:default` should be invoked when none of the subcommands in the
  map match.
  """
  @type command_group ::
          module
          | %{optional(:default) => command_group, required(String.t()) => command_group}

  @typedoc """
  The "invocation path" of the command.

  The public-facing API of storage modules should use this in order
  to allow users to identify the command they want to operate on.

  ## Usage
  To identify a single command, use a single element list, such as `["echo"]`.
  To identify a subcommand, use a pair, such as `["infraction", "search"]`.
  To identify the default subcommand invoked when no matching subcommand is
  found, specify the group name first, then `:default`, such as
  `["tags", :default]`.
  """
  @type command_path :: [String.t() | :default, ...]

  @doc """
  Look up a command group under the specified `name`.

  If the command was not found, `nil` should be returned.
  """
  @callback lookup_command(name :: String.t(), storage :: reference) :: command_group | nil

  @doc """
  Add a new command under the given `path`.

  If a command has the c:Nosedrum.Command.aliases/0 callback defined,
  they will also be added under `path`. If the command already exists,
  no error should be returned.
  """
  @callback add_command(path :: command_path, command :: module, storage :: reference) ::
              :ok | {:error, String.t()}

  @doc """
  Remove the command under the given `path`.

  If a command has the c:Nosedrum.Command.aliases/0 callback defined,
  they will also be removed under `path`. If the command does not exist,
  no error should be returned.
  """
  @callback remove_command(path :: command_path, storage :: reference) ::
              :ok | {:error, String.t()}

  @doc """
  Return a mapping of command names to `t:command_group/0`s.

  For top-level commands, the value should be a string, otherwise,
  a mapping of subcommand names to subcommand modules as described
  on `t:command_group/0`s documentation should be returned.
  """
  @callback all_commands(storage :: reference) :: %{String.t() => command_group}
end