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