defmodule Mex do alias Mex.CLI defdelegate set_width(num_chars), to: Mex.CLI @name [ none: &Mex.no_expansion/2, once: &Macro.expand_once/2, expand: &Macro.expand/2, all: &Mex.expand_all/2 ] @pos [[:none, :once, :expand, :all], [:all], [:expand, :all], [:once, :expand, :all]] def by_name(atom), do: @name[atom] def by_column_count(i) do Enum.map(Enum.at(@pos, i) || List.first(@pos), &by_name/1) end @doc """ Call with a `do..end` block, preceded by an optional parameter: - a number 1-3, to limit number of expansions to show side by side - a single named expander, one of `:none, :once, :expand, :all` If you'd like to compare the output of your own expanders side by side, see `Mex.pp_compare`. """ defmacro mex(num \\ 0, rest) defmacro mex(num, do: node) when is_integer(num) do pp_compare(node, __CALLER__, by_column_count(num)) quote do: :ok end defmacro mex(atom, do: node) when is_atom(atom) do pp_compare(node, __CALLER__, [by_name(atom)]) quote do: :ok end @doc """ Display the expansion of a `node` side-by-side in `env`, expanded by `expanders`, a list of anonymous expansion functions. """ def pp_compare(node, env, expanders) do {texts, errors} = try_expansions(node, env, expanders) IO.puts(expanders |> Enum.map(&inspect/1) |> CLI.headers()) IO.puts(texts |> CLI.side_by_side()) IO.puts(errors |> Enum.reject(&is_nil/1) |> Enum.map_join("\n", &(" " <> &1))) end def expand_all(n, env) do Macro.prewalk(n, &Macro.expand(&1, env)) end def no_expansion(node, _env), do: node # error handling def try_expansions(node, env, fns) do fns |> Enum.map(&try_expand(&1, node, env)) |> Enum.reduce({[], []}, &accum_expansion/2) end defp accum_expansion({_status, txt, err}, {pages, errs}) do {pages ++ [txt], errs ++ [err]} end defp try_expand(f, node, env) do import IO.ANSI try do {:ok, f.(node, env) |> Macro.to_string(), nil} rescue e -> {:err, "ExpansionError", "#{bright()}#{inspect(f)}#{normal()} - #{e.message}"} end end end