Packages

Enable or disable code using feature toggles

Current section

Files

Jump to
features lib features.ex
Raw

lib/features.ex

defmodule Features do
@moduledoc """
Features enables or disables code sections with annotation.
## Example
defmodule MyModule do
use Features
@doc "A doc for do_something/0 function."
# this will enable the next function with his doc if :a_feature is in config
@feature :a_feature
def do_something do
:a_feature_is_enabled
# this will enable the next statement if :another_feature is in config
@feature :another_feature
:another_feature_is_enabled
# this will enable the next statement if :another_feature is not in config
@feature_off :another_feature
:another_feature_is_disabled
end
end
Code is automatically removed during compilation if the feature condition is not met.
A config example is the following:
config :features, features: [:a_feature]
"""
alias Features.Ast.Compile
alias Features.Ast.Runtime
defmacro __using__(_) do
quote do
import Kernel, except: [def: 1, def: 2]
Module.register_attribute(__MODULE__, :feature, persist: true)
Module.register_attribute(__MODULE__, :feature_off, persist: true)
@before_compile Features
Attributes.set(__MODULE__, [:methods], %{})
require Features
import Features
end
end
@doc false
defmacro def(call, expr \\ nil) do
{method, params} =
case call do
{:when, _, [{method, _, params} | _]} -> {method, params}
{method, _, params} -> {method, params}
end
param_len = if is_nil(params), do: 0, else: length(params)
quote do
feature = Module.get_attribute(__MODULE__, :feature)
feature_off = Module.get_attribute(__MODULE__, :feature_off)
doc = Module.get_attribute(__MODULE__, :doc)
if feature != nil && feature_off != nil do
raise "Cannot use feature and feature_off"
end
path = [:methods, {__MODULE__, unquote(method), unquote(param_len)}]
call = unquote(Macro.escape(call))
expr = unquote(Macro.escape(expr))
method = {feature, feature_off, doc, call, expr}
Attributes.update(__MODULE__, path, [], &(&1 ++ [method]))
Module.delete_attribute(__MODULE__, :feature)
Module.delete_attribute(__MODULE__, :feature_off)
Module.delete_attribute(__MODULE__, :doc)
end
end
defmacro __before_compile__(_) do
__CALLER__.module
|> Attributes.get([:methods])
|> Enum.map(&replace_all/1)
end
defp replace_all(methods) do
if test?(), do: Runtime.replace_methods(methods), else: Compile.replace_all(methods)
end
defp test?, do: Application.fetch_env!(:features, :test)
end