Current section

Files

Jump to
clik lib clik option.ex
Raw

lib/clik/option.ex

defmodule Clik.Option do
@moduledoc """
Configurable CLI flag.
"""
defstruct [:default, :help, :hidden, :long, :name, :required, :short, :type]
@typedoc "Valid option types"
@type option_type :: :float | :integer | :string | :boolean | :count
@typedoc "Valid option value types"
@type option_value :: float() | integer() | String.t() | boolean()
@typedoc "Individual options used to configure an instance of `Clik.Option`"
@type opt ::
{:default, option_value()}
| {:help, String.t()}
| {:hidden, boolean()}
| {:long, atom()}
| {:required, boolean()}
| {:short, atom()}
| {:type, option_type()}
@typedoc "Set of configuration options used to configure an instance of `Clik.Option`"
@type opts :: [] | [opt()]
@type error :: {:error, atom()}
@typedoc "Struct corresponding to a single CLI flag or switch"
@type t :: %__MODULE__{
default: option_type() | nil,
help: String.t(),
hidden: boolean(),
long: atom(),
required: boolean(),
short: atom(),
type: option_type()
}
@default_default nil
@default_help ""
@default_hidden false
@default_required false
@default_short nil
@default_type :string
@valid_types [:boolean, :count, :float, :integer, :string]
@doc """
Creates a new `Clik.Option` instance.
Returns `{:ok, t}` or `{:error, reason}`.
"""
@doc since: "0.1.0"
@spec new(atom(), opts()) :: {:ok, t()} | error()
def new(name, opts \\ []) do
validate(%__MODULE__{
name: name,
default: Keyword.get(opts, :default, @default_default),
help: Keyword.get(opts, :help, @default_help),
hidden: Keyword.get(opts, :hidden, @default_hidden),
long: Keyword.get(opts, :long, name),
required: Keyword.get(opts, :required, @default_required),
short: Keyword.get(opts, :short, @default_short),
type: Keyword.get(opts, :type, @default_type)
})
end
@doc """
Creates a new `Clik.Option` instance.
Raises `ArgumentError` on error.
"""
@spec new!(atom(), opts()) :: t() | no_return()
def new!(name, opts \\ []) do
case new(name, opts) do
{:ok, option} ->
option
{:error, :badarg} ->
raise ArgumentError
end
end
@doc false
@spec prepare(t()) :: {{atom(), option_type()}, nil | {atom(), atom()}}
def prepare(option) do
long_form = {option.long, option.type}
aliases =
if option.short != nil do
{option.short, option.long}
else
nil
end
{long_form, aliases}
end
defp validate(option) do
cond do
option.required and option.default != nil ->
{:error, :badarg}
option.type not in @valid_types ->
{:error, :badarg}
not is_bitstring(option.help) ->
{:error, :badarg}
not check_default_value_type(option.default, option.type) ->
{:error, :badarg}
not check_short_name(option.short) ->
{:error, :badarg}
option.short == option.long ->
{:error, :badarg}
true ->
{:ok, option}
end
end
defp check_short_name(nil), do: true
defp check_short_name(name) do
String.length(Atom.to_string(name)) == 1
end
defp check_default_value_type(nil, _), do: true
defp check_default_value_type(default_value, :boolean) when is_boolean(default_value), do: true
defp check_default_value_type(_default_value, :boolean), do: false
defp check_default_value_type(default_value, :count) when is_integer(default_value), do: true
defp check_default_value_type(_default_value, :count), do: false
defp check_default_value_type(default_value, :float) when is_float(default_value), do: true
defp check_default_value_type(_default_value, :float), do: false
defp check_default_value_type(default_value, :integer) when is_integer(default_value), do: true
defp check_default_value_type(_default_value, :integer), do: false
defp check_default_value_type(default_value, :string) when is_bitstring(default_value), do: true
defp check_default_value_type(_default_value, :string), do: false
end