Files
elixir-koans/lib/koans.ex

102 lines
2.3 KiB
Elixir

defmodule Koans do
@moduledoc false
defp valid_name(name) do
Regex.match?(~r/([A-Z]|\.\.\.).+/, name)
end
defmacro koan(name, do: body) do
if not valid_name(name) do
raise "Name does not start with a capital letter: #{name}"
end
compiled_name = String.to_atom(name)
number_of_args = Blanks.count(body)
compiled_body = Blanks.replace_line(body, &blank_line_replacement/1)
quote do
@koans unquote(compiled_name)
generate_test_method(unquote(compiled_name), unquote(number_of_args), unquote(body))
def unquote(compiled_name)() do
unquote(compiled_body)
:ok
rescue
e -> {:error, __STACKTRACE__, e}
end
end
end
defmacro generate_test_method(_name, 0, _body), do: false
defmacro generate_test_method(name, 1, body) do
single_var = Blanks.replace(body, Macro.var(:answer, __MODULE__))
quote do
def unquote(name)(answer) do
unquote(single_var)
:ok
rescue
e -> {:error, __STACKTRACE__, e}
end
end
end
defmacro generate_test_method(name, number_of_args, body) do
answer_vars =
for id <- 1..number_of_args, do: Macro.var(String.to_atom("answer#{id}"), __MODULE__)
multi_var = Blanks.replace(body, answer_vars)
quote do
def unquote(name)({:multiple, unquote(answer_vars)}) do
unquote(multi_var)
:ok
rescue
e -> {:error, __STACKTRACE__, e}
end
end
end
defp blank_line_replacement({:assert, _meta, [expr]}) do
code = Macro.escape(expr)
quote do: raise(ExUnit.AssertionError, expr: unquote(code))
end
defp blank_line_replacement(line) do
code = Macro.escape(line)
quote do: raise(ExUnit.AssertionError, expr: unquote(code))
end
defmacro __using__(_opts) do
quote do
@compile :nowarn_unused_vars
Module.register_attribute(__MODULE__, :koans, accumulate: true)
require ExUnit.Assertions
import ExUnit.Assertions
import Koans
@before_compile Koans
end
end
defmacro __before_compile__(env) do
koans = koans(env)
quote do
def all_koans do
unquote(koans)
end
def intro, do: @intro
end
end
defp koans(env) do
env.module
|> Module.get_attribute(:koans)
|> Enum.reverse()
end
end