Current section
Files
Jump to
Current section
Files
lib/mex.ex
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