lib/map_sorter/compare.ex

defmodule MapSorter.Compare do
  use PersistConfig

  @compare_function get_env(:compare_function)

  @moduledoc """
  Generates a #{@compare_function} from a list of `sort specs`.
  """

  alias MapSorter.{Cond, Log, SortSpecs}

  # Access.container() :: keyword() | struct() | map()
  @type fun :: (Access.container(), Access.container() -> boolean)

  @doc """
  Generates a #{@compare_function} from a list of `sort specs`.

  ## Examples

      iex> alias MapSorter.Compare
      iex> sort_specs = [:dob, desc: :likes]
      iex> fun = Compare.fun(sort_specs)
      iex> is_function(fun, 2)
      true
  """
  @spec fun(SortSpecs.t()) :: fun
  def fun(sort_specs) when is_list(sort_specs) do
    Log.debug(:generating_runtime_comp_fun, {sort_specs, __ENV__})
    {fun, []} = heredoc(sort_specs) |> Code.eval_string()
    fun
  end

  def fun(sort_specs) do
    Log.error(:generating_no_op_sort, {sort_specs, __ENV__})
    fun([])
  end

  @doc """
  Generates a #{@compare_function} as a heredoc from a list of `sort specs`.

  ## Examples

      iex> alias MapSorter.Compare
      iex> Compare.heredoc([])
      \"""
      & cond do
      true -> true or &1 * &2
      end
      \"""

      iex> alias MapSorter.Compare
      iex> sort_specs = [:name, {:desc, :dob}]
      iex> Compare.heredoc(sort_specs)
      \"""
      & cond do
      &1[:name] < &2[:name] -> true
      &1[:name] > &2[:name] -> false
      &1[:dob] > &2[:dob] -> true
      &1[:dob] < &2[:dob] -> false
      true -> true or &1 * &2
      end
      \"""

      iex> alias MapSorter.Compare
      iex> sort_specs = [:name, {:desc, {:dob, Date}}]
      iex> Compare.heredoc(sort_specs)
      \"""
      & cond do
      &1[:name] < &2[:name] -> true
      &1[:name] > &2[:name] -> false
      &1[:dob] != nil and Date.compare(&1[:dob], &2[:dob]) == :gt -> true
      &1[:dob] != nil and Date.compare(&1[:dob], &2[:dob]) == :lt -> false
      true -> true or &1 * &2
      end
      \"""
  """
  @spec heredoc(SortSpecs.t()) :: String.t()
  def heredoc(sort_specs) when is_list(sort_specs) do
    heredoc = """
    & cond do
    #{Cond.clauses(sort_specs)}true -> true or &1 * &2
    end
    """

    Log.debug(:comp_fun_heredoc, {sort_specs, heredoc, __ENV__})
    heredoc
  end
end