Current section
Files
Jump to
Current section
Files
lib/maximum_jank.ex
defmodule MaximumJank do
@moduledoc """
Documentation for MaximumJank.
"""
@doc """
TODO: Come up w/ a non-lame way to document how awesome declaring `use MaximumJank` actually is; a good example is needed!
"""
defmacro __using__(_) do
# throws an error if used outside of a module
if __CALLER__.module == nil do
raise "cannot invoke 'use #{inspect(__MODULE__)}' outside module"
end
# concatenates caller's module w/ current module
# example: MyModule + __MODULE__ becomes MyModule.__MODULE__
module = Module.concat([__CALLER__.module, __MODULE__])
# checks to see if `use MaximumJank` has been called previously:
# - this check is done by calling `module.using?`
# - an `UndefinedFunctionError` means `use MaximumJank` has not been called
# - `true` means it has not been called, and the in the rescue block is run
try do
module.using?()
rescue
_ in UndefinedFunctionError ->
Kernel.defmodule(module) do
@moduledoc false
@doc false
@spec using?() :: true
def using?() do
true
end
end
error ->
raise "this sould not happen: #{inspect(error)}"
end
quote do
end
end
@doc """
Takes a `%Macro.Env{}` struct, and returns the same struct, if the struct's module has declared `use MaximumJank`. If the module is not using `MaximumJank`, a RuntimeError will be raised.
##Notes
- For some examples, see the tests.
- Refer to the docs for `&__using__/1` to understand why you'd use this function.
"""
def __using__!(%Macro.Env{module: nil}) do
inspected_func = inspected_func(__ENV__)
raise "cannot invoke #{inspected_func} outside module"
end
def __using__!(%Macro.Env{}=env) do
if __using__?(env) do
env
else
module = inspect(__ENV__.module)
inspected_func = inspected_func(__ENV__)
raise "you must declare 'use #{module}' to invoke #{inspected_func}"
end
end
@doc """
Takes a `%Macro.Env{}` struct, and returns `true` the struct's module has declared `use MaximumJank`. If the module is not using `MaximumJank`, it returns false.
## Notes
- For some examples, see the tests.
- Refer to the docs for `&__using__/1` to understand why you'd use this function.
"""
def __using__?(%Macro.Env{module: nil}) do
inspected_func = inspected_func(__ENV__)
raise "cannot invoke #{inspected_func} outside module"
end
def __using__?(%Macro.Env{}=env) do
module = Module.concat([env.module, MaximumJank])
module.using?()
rescue
_ -> false
end
@doc """
Returns `arg` if it's a module, and raises a `RuntimeError ` for anything else.
"""
@spec module!(any()) :: module()
def module!(arg) do
if module?(arg) do
arg
else
raise "not a module: #{inspect(arg)}"
end
end
@doc """
Returns `true` if argument is a module, and `false` for anything else.
"""
@spec module?(any()) :: boolean()
def module?(arg) when is_atom(arg) do
arg.module_info()[:module] == arg
rescue
_ -> false
end
def module?(_), do: false
###########
# Private #
###########
defp inspected_func(%Macro.Env{}=env) do
module = inspect(env.module)
{function,arity} = env.function
function2 = Atom.to_string(function)
arity2 = Integer.to_string(arity)
"&" <> module <> "." <> function2 <> "/" <> arity2
end
end