lib/r_utils.ex

defmodule RUtils do
  @brank_regex ~r/\A[[:space:]]*\z/
  @default_undelegate_functions [:__info__, :module_info, :"MACRO-__using__"]
  @moduledoc """
  Utils for REnum.
  """
  @doc """
  Defines in the module that called all the functions of the argument module.
  ## Examples
      iex> defmodule A do
      ...>   defmacro __using__(_opts) do
      ...>     RUtils.define_all_functions!(__MODULE__)
      ...>   end
      ...>
      ...>   def test do
      ...>     :test
      ...>   end
      ...> end
      iex> defmodule B do
      ...>   use A
      ...> end
      iex> B.test
      :test
  """
  @spec define_all_functions!(module()) :: list
  def define_all_functions!(mod, undelegate_functions \\ []) do
    enum_funs =
      mod.module_info()[:exports]
      |> Enum.filter(fn {fun, _} ->
        fun not in (@default_undelegate_functions ++ undelegate_functions)
      end)

    for {fun, arity} <- enum_funs do
      quote do
        defdelegate unquote(fun)(unquote_splicing(make_args(arity))), to: unquote(mod)
      end
    end
  end

  @doc """
  Creates tuple for `unquote_splicing`.
  """
  @spec make_args(integer) :: list
  def make_args(0), do: []

  def make_args(n) do
    Enum.map(1..n, fn n -> {String.to_atom("arg#{n}"), [], Elixir} end)
  end

  @doc """
  Return true if object is blank, false, empty, or a whitespace string.
  For example, +nil+, '', '   ', [], {}, and +false+ are all blank.
  ## Examples
      iex>  RUtils.blank?(%{})
      true

      iex> RUtils.blank?([1])
      false

      iex> RUtils.blank?("  ")
      true
  """
  @spec blank?(any()) :: boolean()
  def blank?(map) when map == %{}, do: true
  def blank?([]), do: true
  def blank?(nil), do: true
  def blank?(false), do: true

  def blank?(str) when is_bitstring(str) do
    str
    |> String.match?(@brank_regex)
  end

  def blank?(_), do: false

  @doc """
  Returns true if not `RUtils.blank?`
  ## Examples
      iex>  RUtils.present?(%{})
      false

      iex> RUtils.present?([1])
      true

      iex> RUtils.present?("  ")
      false
  """
  @spec present?(any()) :: boolean()
  def present?(obj) do
    !blank?(obj)
  end

  @doc false
  def required_functions(functions, exclude_modules) do
    functions
    |> Enum.reject(fn method ->
      exclude_functions = exclude_modules |> Enum.flat_map(& &1.module_info()[:exports])

      already_defined_func =
        exclude_functions
        |> Keyword.keys()
        |> Enum.find(&(&1 == method))

      !!already_defined_func || to_string(method) =~ ~r/!/
    end)
    |> Enum.each(&IO.puts(&1))
  end
end