lib/type_class/utility/module.ex

defmodule TypeClass.Utility.Module do
  @moduledoc "Naming convention helpers to help follow TypeClass conventions"

  @type ast :: tuple()

  @doc ~S"""
  Generate the module name for the protocol portion of the class.
  Does not nest `Protocol`s.

  ## Examples

      iex> to_protocol MyClass.Awesome
      MyClass.Awesome.Protocol

      iex> to_protocol MyClass.Awesome.Protocol
      MyClass.Awesome.Protocol

  """
  @spec to_protocol(module()) :: module()
  def to_protocol(base_module), do: to_submodule(base_module, "Protocol")

  @doc ~S"""
  Generate the module name for the protocol portion of the class.
  Does not nest `Protocol`s.

  ## Examples

      iex> to_property MyClass.Awesome
      MyClass.Awesome.Property

      iex> to_property MyClass.Awesome.Property
      MyClass.Awesome.roperty

  """
  @spec to_property(module()) :: module()
  def to_property(base_module), do: to_submodule(base_module, "Property")

  @doc ~S"""
  Generate a submodule name. If the module already ends in the
  requested submodule, it will return the module name unchanged.

  ## Examples

      iex> MyModule.Awesome |> to_submodule(Submodule)
      MyModule.Awesome.Submodule

      iex> MyModule.Awesome |> to_submodule("Submodule")
      MyModule.Awesome.Submodule

      iex> MyModule.Awesome.Submodule |> to_submodule(Submodule)
      MyModule.Awesome.Submodule

  """
  @spec to_submodule(module(), String.t() | module()) :: module()
  def to_submodule(base_module, child_name) when is_bitstring(child_name) do
    base_module
    |> Module.split()
    |> List.last()
    |> case do
      ^child_name -> base_module
      _ -> Module.concat([base_module, child_name])
    end
  end

  def to_submodule(base_module, child_module) do
    to_submodule(base_module, Module.split(child_module))
  end

  def get_functions(module) do
    module.__info__(:functions)
    |> Enum.into(%{})
    |> Map.drop([
      :__protocol__,
      :impl_for,
      :impl_for!,
      :__builtin__,
      :__derive__,
      :__ensure_defimpl__,
      :__functions_spec__,
      :__impl__,
      :__spec__?,
      :assert_impl!,
      :assert_protocol!,
      :consolidate,
      :consolidated?,
      :extract_impls,
      :extract_protocols
    ])
  end
end