defmodule Moar.Atom do # @related [test](/test/atom_test.exs) @moduledoc "Atom-related functions." @doc """ Given an atom, returns the atom. Given a string, converts it into an atom, converting non-alphanumeric characters into underscores (via `Moar.String.slug/2`). ## Examples ``` iex> Moar.Atom.atomize(:my_atom) :my_atom iex> Moar.Atom.atomize("my-atom") :my_atom ``` """ @spec atomize(atom() | binary()) :: atom() def atomize(atom) when is_atom(atom), do: atom def atomize(binary) when is_binary(binary), do: binary |> Moar.String.slug("_") |> from_string() @doc """ Given a list of strings, returns `:ok` if all values have a corresponding atom that already exists. Otherwise, it returns an error tuple with a list of strings that don't have corresponding atoms. Any atom included in the argument will be considered an existing atom. ## Examples ``` iex> :existing_atom iex> Moar.Atom.ensure_existing_atoms(["existing_atom", :another_existing_atom]) :ok iex> :existing_atom iex> Moar.Atom.ensure_existing_atoms(["existing_atom", :another_existing_atom, "some_nonexisting_atom"]) {:error, ["some_nonexisting_atom"]} ``` """ @spec ensure_existing_atoms([atom() | binary()]) :: :ok | {:error, [binary()]} def ensure_existing_atoms(values) do missing_atoms = Enum.reject(values, &existing_atom?/1) if Enum.empty?(missing_atoms), do: :ok, else: {:error, missing_atoms} end @doc """ Given a string, returns `true` if a corresponding atom has been previously defined. Otherwise, returns `false`. Given an atom, returns `true`. ## Examples ``` iex> :existing_atom iex> Moar.Atom.existing_atom?("existing_atom") true iex> Moar.Atom.existing_atom?(:another_existing_atom) true iex> Moar.Atom.existing_atom?("some_nonexisting_atom") false ``` """ @spec existing_atom?(atom() | binary()) :: boolean() def existing_atom?(value) when is_atom(value), do: true def existing_atom?(value) do String.to_existing_atom(value) && true rescue _ -> false end @doc """ Converts a string to an atom (via `String.to_atom/1`), and returns atoms unchanged. Useful when you aren't sure ahead of time whether you have a string or an atom. ## Examples ```elixir iex> Moar.Atom.from_string("foo") :foo iex> Moar.Atom.from_string(:bar) :bar iex> Moar.Atom.from_string(nil) ** (ArgumentError) Unable to convert nil into an atom ``` """ @spec from_string(atom() | binary()) :: atom() def from_string(nil), do: raise(ArgumentError, message: "Unable to convert nil into an atom") def from_string(s) when is_binary(s), do: String.to_atom(s) def from_string(a) when is_atom(a), do: a @doc """ Converts an atom to a string (via `Atom.to_string/1`), and returns strings unchanged. Useful when you aren't sure ahead of time whether you have a string or an atom. ## Examples ```elixir iex> Moar.Atom.to_string("foo") "foo" iex> Moar.Atom.to_string(:bar) "bar" iex> Moar.Atom.to_string(nil) ** (ArgumentError) Unable to convert nil into a string ``` """ @spec to_string(atom() | binary()) :: binary() def to_string(nil), do: raise(ArgumentError, message: "Unable to convert nil into a string") def to_string(a) when is_atom(a), do: Atom.to_string(a) def to_string(s) when is_binary(s), do: s @doc """ Converts a string to an existing atom (via `String.to_existing_atom/1`), and returns atoms unchanged. Useful when you aren't sure ahead of time whether you have a string or an atom. ## Examples ```elixir iex> Moar.Atom.to_existing_atom("foo") :foo iex> assert_raise ArgumentError, ~r/not an already existing atom/, fn -> ...> Moar.Atom.to_existing_atom("sadfasfsfasf") ...> end iex> Moar.Atom.to_existing_atom(:baz) :baz iex> Moar.Atom.to_existing_atom(nil) nil ``` """ @spec to_existing_atom(atom() | binary()) :: atom() def to_existing_atom(a) when is_atom(a), do: a def to_existing_atom(s) when is_binary(s), do: String.to_existing_atom(s) end