Current section
Files
Jump to
Current section
Files
lib/hooks.ex
defmodule Hooks do
@moduledoc """
Elixir wrapper for the Erlang hooks system.
## Overview
This module provides an Elixir-friendly interface to the hooks system,
delegating all calls to the underlying Erlang implementation while
maintaining Elixir naming conventions and documentation.
## Features
- Priority-based hook execution
- Multiple execution strategies
- Plugin system support
- Zero overhead - direct delegation to Erlang
## Examples
### Basic Usage
# Register a hook
:ok = Hooks.reg(:on_user_login, MyModule, :log_login, 2, 10)
# Run hooks
:ok = Hooks.run(:on_user_login, [user_id, timestamp])
# Unregister a hook
:ok = Hooks.unreg(:on_user_login, MyModule, :log_login, 2)
### Using Plugins
# Enable a plugin
:ok = Hooks.enable_plugin(:my_plugin)
# Disable a plugin
:ok = Hooks.disable_plugin(:my_plugin)
### Different Execution Strategies
# Run all hooks until one returns :stop
:ok = Hooks.run(:before_save, [data])
# Fold over hooks with accumulator
result = Hooks.run_fold(:transform_data, [input], initial_acc)
# Get all results
results = Hooks.all(:validate_data, [data])
# Run until one succeeds
case Hooks.all_till_ok(:authenticate, [user, pass]) do
:ok -> :authenticated
{:ok, user_info} -> {:authenticated, user_info}
{:error, reasons} -> {:failed, reasons}
end
## Since
1.0.0
"""
@doc """
Register a function as a hook using the function name as the hook name.
## Parameters
* `module` - The module containing the hook function
* `fun` - The function name (also used as the hook name)
* `arity` - The function arity
## Returns
* `:ok` - Registration successful
* `{:error, term}` - Registration failed
## Examples
:ok = Hooks.reg(MyModule, :on_startup, 0)
"""
defdelegate reg(module, fun, arity), to: :hooks
@doc """
Register a function for a specific hook name.
## Parameters
* `name` - The hook identifier
* `module` - The module containing the hook function
* `fun` - The function name
* `arity` - The function arity
## Returns
* `:ok` - Registration successful
* `{:error, term}` - Registration failed
## Examples
:ok = Hooks.reg(:before_save, Validator, :check_data, 1)
"""
defdelegate reg(name, module, fun, arity), to: :hooks
@doc """
Register a function for a hook with a specific priority.
## Parameters
* `name` - The hook identifier
* `module` - The module containing the hook function
* `fun` - The function name
* `arity` - The function arity
* `priority` - Execution priority (higher values execute first)
## Returns
* `:ok` - Registration successful
* `{:error, term}` - Registration failed
## Examples
# High priority (executes first)
:ok = Hooks.reg(:before_save, Security, :check_permissions, 1, 100)
# Normal priority
:ok = Hooks.reg(:before_save, Validator, :validate_data, 1, 0)
# Low priority (executes last)
:ok = Hooks.reg(:before_save, Logger, :log_save, 1, -10)
"""
defdelegate reg(name, module, fun, arity, priority), to: :hooks
@doc """
Unregister a function where the function name is the hook name.
## Parameters
* `module` - The module containing the hook function
* `fun` - The function name (also the hook name)
* `arity` - The function arity
## Returns
Always returns `:ok`.
## Examples
:ok = Hooks.unreg(MyModule, :on_startup, 0)
"""
defdelegate unreg(module, fun, arity), to: :hooks
@doc """
Unregister a function from a specific hook.
## Parameters
* `name` - The hook identifier
* `module` - The module containing the hook function
* `fun` - The function name
* `arity` - The function arity
## Returns
Always returns `:ok`.
## Examples
:ok = Hooks.unreg(:before_save, Validator, :check_data, 1)
"""
defdelegate unreg(name, module, fun, arity), to: :hooks
@doc """
Unregister a function from a hook with a specific priority.
## Parameters
* `name` - The hook identifier
* `module` - The module containing the hook function
* `fun` - The function name
* `arity` - The function arity
* `priority` - The priority used during registration
## Returns
Always returns `:ok`.
## Examples
:ok = Hooks.unreg(:before_save, Security, :check_permissions, 1, 100)
"""
defdelegate unreg(name, module, fun, arity, priority), to: :hooks
@doc """
Register multiple hooks at once.
## Parameters
* `hooks` - A list of `{hook_name, hook_functions}` tuples
## Returns
* `:ok` - All registrations successful
* `{:error, term}` - At least one registration failed
## Examples
hooks = [
{:on_connect, [{Logger, :log_connection, 1}, {Stats, :count_connection, 1}]},
{:on_disconnect, [{Logger, :log_disconnection, 1}]}
]
:ok = Hooks.mreg(hooks)
"""
defdelegate mreg(hooks), to: :hooks
@doc """
Unregister multiple hooks at once.
## Parameters
* `hooks` - A list of `{hook_name, hook_functions}` tuples
## Returns
Always returns `:ok`.
## Examples
hooks = [
{:on_connect, [{Logger, :log_connection, 1}]},
{:on_disconnect, [{Logger, :log_disconnection, 1}]}
]
:ok = Hooks.munreg(hooks)
"""
defdelegate munreg(hooks), to: :hooks
@doc """
Retrieve all functions registered for a hook.
## Parameters
* `atom` - The hook identifier
## Returns
* `{:ok, [{module, function}]}` - List of registered functions
* `:error` - If no hooks are registered
## Examples
case Hooks.find(:on_user_login) do
{:ok, hooks} ->
IO.inspect(hooks, label: "Registered hooks")
:error ->
IO.puts("No hooks registered")
end
"""
defdelegate find(atom), to: :hooks
@doc """
Execute all hooks and collect all results.
## Parameters
* `atom` - The hook identifier
* `list` - Arguments to pass to each hook
## Returns
A list containing the return value of each hook function.
## Examples
results = Hooks.all(:validate_user, [user_data])
case Enum.all?(results, & &1 == :ok) do
true -> :proceed
false -> {:error, :validation_failed}
end
"""
defdelegate all(atom, list), to: :hooks
@doc """
Execute hooks until one returns success.
## Parameters
* `atom` - The hook identifier
* `list` - Arguments to pass to each hook
## Returns
* `:ok` - If any hook returns `:ok`
* `{:ok, value}` - If any hook returns `{:ok, value}`
* `{:error, [term]}` - List of all non-ok results
* `[]` - If no hooks are registered
## Examples
case Hooks.all_till_ok(:authenticate, [username, password]) do
:ok ->
login_user(username)
{:ok, user_info} ->
login_user_with_info(username, user_info)
{:error, reasons} ->
{:error, {:auth_failed, reasons}}
end
"""
defdelegate all_till_ok(atom, list), to: :hooks
@doc """
Disable a plugin and unregister all its hooks.
## Parameters
* `atom` - The plugin application name
## Returns
Always returns `:ok`.
## Examples
:ok = Hooks.disable_plugin(:my_plugin)
"""
defdelegate disable_plugin(atom), to: :hooks
@doc """
Enable a plugin application and register its hooks.
## Parameters
* `atom` - The plugin application name
## Returns
* `:ok` - Plugin enabled successfully
* `{:error, term}` - Failed to enable plugin
## Examples
:ok = Hooks.enable_plugin(:my_plugin)
"""
defdelegate enable_plugin(atom), to: :hooks
@doc """
Enable a plugin with additional code paths.
## Parameters
* `atom` - The plugin application name
* `list` - Additional code paths
## Returns
* `:ok` - Plugin enabled successfully
* `{:error, term}` - Failed to enable plugin
## Examples
:ok = Hooks.enable_plugin(:my_plugin, ["./plugins/my_plugin/ebin"])
"""
defdelegate enable_plugin(atom, list), to: :hooks
@doc """
Execute only the highest priority hook.
## Parameters
* `atom` - The hook identifier
* `list` - Arguments to pass to the hook
## Returns
* The return value of the highest priority hook
* `:no_hook` - If no hooks are registered
## Examples
case Hooks.only(:custom_handler, [request]) do
:no_hook ->
handle_default(request)
result ->
result
end
"""
defdelegate only(atom, list), to: :hooks
@doc """
Run all hooks registered for a hook name.
## Parameters
* `atom` - The hook identifier
* `list` - Arguments to pass to each hook
## Returns
Always returns `:ok`.
## Notes
Execution stops if any hook returns `:stop`.
## Examples
:ok = Hooks.run(:on_user_login, [user_id, timestamp])
"""
defdelegate run(atom, list), to: :hooks
@doc """
Fold over all hooks with an accumulator.
## Parameters
* `atom` - The hook identifier
* `list` - Arguments to pass to each hook
* `any` - Initial accumulator value
## Returns
The final accumulator value.
## Notes
- Each hook receives args ++ [accumulator]
- Return value becomes next accumulator
- Stops on `:stop` or `{:stop, new_acc}`
## Examples
processed_data = Hooks.run_fold(:transform_data, [options], initial_data)
"""
defdelegate run_fold(atom, list, any), to: :hooks
end