Packages

Another decorator library for Elixir.

Current section

Files

Jump to
precuter lib precuter.ex
Raw

lib/precuter.ex

defmodule Precuter do
@moduledoc """
Precuter helps to implement decorators on your functions
What is a decorator? basicaly a function that you can run before, or after the call of a function, in order to
add stats, or validations or just to log something before the call
The point of this library it's not break your mind learning complex concepts about how to create decorators
All are just simple functions, simple modules, nothing to implement, just a module, a function with arguments and just
a simple use of the module `Precuter`
## Basic usage
```elixir
defmodule ModuleTest do
use Precuter
# just use the module attribute `@precuter` and set the mfa (module, function, arguments) or a keyword with the all the mfa with every step
# you can use the atom `:args` to send to your functions the arguments of the function call in a list of arguments
@precuter {SomeModule, :some_function, [:some_static_arg, 1, :args]}
def example_func(some_number) do
some_number * 2
end
end
```
## Options
You can set 3 callbacks, before run, after run, and a middle step that you can use to define if run or not your function
```elixir
@precuter [
pre: {SomeModule, :some_func_pre, [:an_arg, :args]},
cond: {SomeModule, :some_func_cond_fails, [:args]},
post: {SomeModule, :some_func_post, [:an_post_arg]}
]
```
## Considerations
Exist some limitations with the decorators, like use pattern matching or different arity functions, this it's a work
in progress so in future updates you will be able to implement decorators on functions with different arity
#### DON'T DO THIS
```elixir
defmodule ModuleTest do
use Precuter
@precuter {SomeModule, :some_function, [:some_static_arg, 1, :args]}
def example_func(some_number) when is_number(some_number) do
some_number * 2
end
@precuter {SomeModule, :some_function, [:some_static_arg, 1, :args]}
def example_func(other_values) do
other_values
end
end
```
"""
alias Precuter.Args
alias Precuter.Function
defmacro __using__(_opts) do
quote do
defp mfa_exec({m, f, a}, original_args) do
args =
Enum.map(a, fn
:args ->
original_args
arg ->
arg
end)
apply(m, f, args)
end
defp mfa_exec(_, _), do: true
@on_definition Precuter
end
end
def __on_definition__(%Macro.Env{line: line} = env, :def, name, args, guards, _body)
when line > 1 do
response =
env.module
|> Module.get_last_attribute(:precuter)
|> generate_decorator_functions()
case response do
nil ->
:pass
{pre, cond, post} ->
body = Function.get_body(env, name, args)
original_func = Function.generate_reimplemented_func(name, args, guards, body)
replaced_func =
Function.generate_replacement_func(name, Args.purify_args(args), pre, cond, post)
Module.delete_attribute(env.module, :precuter)
true = Module.delete_definition(env.module, {name, length(args)})
Module.eval_quoted(env.module, original_func)
Module.eval_quoted(env.module, replaced_func)
end
end
def __on_definition__(_env, _kind, _name, _args, _guards, _body), do: :pass
defp generate_decorator_functions(args) when is_list(args) do
pre = Keyword.get(args, :pre, nil)
cond = Keyword.get(args, :cond, nil)
post = Keyword.get(args, :post, nil)
{pre, cond, post}
end
defp generate_decorator_functions(func_ref) when is_tuple(func_ref),
do: {func_ref, nil, nil}
defp generate_decorator_functions(_), do: nil
def empty, do: true
end