Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added propper documentation.

  • Loading branch information...
commit 941c3eef049e89366e49194e13d696f11d9f503e 1 parent e46d372
@Licenser authored
Showing with 462 additions and 49 deletions.
  1. +1 −0  .gitignore
  2. +3 −0  README.md
  3. +458 −49 src/eplugin.erl
View
1  .gitignore
@@ -1,3 +1,4 @@
+doc
*.beam
.eunit
ebin
View
3  README.md
@@ -70,6 +70,9 @@ is_enabled/1
------------
Returns true if a plugin is enabled (aka has any callbacks registered).
+register/4 /5
+-------------
+Registers a callback.
Writing plugins
View
507 src/eplugin.erl
@@ -15,109 +15,306 @@
is_enabled/1,
disable/1]).
+-export_type([plugin_desc/0]).
+
-define(TABLE, plugins).
-define(CONFTABLE, plugin_config).
+
+%%--------------------------------------------------------------------
+%% @type plugin_desc() = {Plugin,
+%% Modules,
+%% Config}
+%% Plugin = atom()
+%% Module = [module_desc()]
+%% Config = [plugin_config()].
+%%
+%% plugin_desc is the description of how plugin.config files are read.
+%% Plugin is the name, it should be unique.
+%% @end
+%%--------------------------------------------------------------------
+
+-type plugin_desc() :: {Plugin::atom(),
+ [module_desc()],
+ [plugin_config()]}.
+
+%%--------------------------------------------------------------------
+%% @type module_desc() = {Module,
+%% Callbacks}
+%% Module = atom()
+%% Callback = [callback_desc()].
+%%
+%% Describes one of the modules of a callback. A module corresponds
+%% with a Module.erl file in the plugin directory. A module does not
+%% have to register any callbacks, but this directive is needed to
+%% trigger compilation. (in other words if you have a utility Module
+%% in your plugin register it here just don't list any callbacks.)
+%% @end
+%%--------------------------------------------------------------------
+
+-type module_desc() :: {Module::atom(), [callback_desc()]}.
+
+%%--------------------------------------------------------------------
+%% @type callback_desc() = {Callback,
+%% Function,
+%% Config} |
+%% {Callback,
+%% Function}
+%% Callback = atom()
+%% Function = atom()
+%% Config = [callback_config()].
+%%
+%% Registers a function as a callback, the Function must be exported
+%% for this to work, optional configuration options can be passed.
+%% If the Config is ommitted a empty list is taken.
+%% @end
+%%--------------------------------------------------------------------
+
+-type callback_desc() :: {Callback::atom(),
+ Function::atom(),
+ Config::[callback_config()]} |
+ {Callback::atom(),
+ Function::atom()}.
+
+%%--------------------------------------------------------------------
+%% @type callback_config() = Option
+%% Option = {priority, integer()}.
+%%
+%% Options for a callback. The default for priority is 0, a higher
+%% priority means the callback gets executed earlery.
+%% @end
+%%--------------------------------------------------------------------
+
+-type callback_config() :: {priority, integer()}.
+
+
+%%--------------------------------------------------------------------
+%% @type plugin_config() = Option
+%% Option = {dependencies, [atom()]} |
+%% {provides, [atom()]} |
+%% disabled |
+%% atom() |
+%% {Key::atom(), Value::any()}.
+%%
+%% The plugin configuraiton the following values are used by eplugin:
+%% <dl>
+%% <dt><b>dependencies</b></dt>
+%% <dd>this dependecies must be provided, either by another plugin
+%% or by the application itself before the plugin gets enabled
+%% automatically.</dd>
+%% <dt><b>provided</b></dt>
+%% <dd>A list of provided capabilities for other plugins.</dd>
+%% <dt><b>disabled</b></dt>
+%% <dd>This plugin is not enabled automatically.</dd>
+%% </dl>
+%% @end
+%%--------------------------------------------------------------------
+
+-type plugin_config() :: {dependencies, [atom()]} |
+ {provides, [atom()]} |
+ disabled |
+ atom() |
+ {Key::atom(), Value::any()}.
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts eplugin.
+%%
+%% @spec start() -> ok
+%% @end
+%%--------------------------------------------------------------------
+
+-spec start() -> ok.
start() ->
application:start(sasl),
application:start(lager),
application:start(eplugin).
+%%--------------------------------------------------------------------
+%% @doc
+%% Lists all registered and enabled callbacks for a given Name. Sorted
+%% by the <b>priority</b> they were given.
+%%
+%% @spec callbacks(Name::atom()) -> {Module::atom(), Function::atom()}
+%% @end
+%%--------------------------------------------------------------------
+
+-spec callbacks(Name::atom()) -> {Module::atom(), Function::atom()}.
+
callbacks(Name) ->
C0 = [{P, M, F} || {_, _, M, F, P} <- ets:lookup(?TABLE, Name)],
[{M, F} || {_, M, F} <- lists:sort(C0)].
+%%--------------------------------------------------------------------
+%% @doc
+%% An equivalten to erlang:apply/2 for callbacks. All enabled callbacks
+%% as listed by callbacks/1. Results are retunred in an list.
+%%
+%% @spec apply(Name::atom(), Args::[any()]) -> [any()]
+%% @end
+%%--------------------------------------------------------------------
+
+-spec apply(Name::atom(), Args::[any()]) -> [any()].
+
apply(Name, Args) when is_list(Args) ->
[erlang:apply(M, F, Args) || {M, F} <- callbacks(Name)].
+%%--------------------------------------------------------------------
+%% @doc
+%% Equivalent to eplugin:apply/2 but optimized for calls without
+%% arguments by working around erlang:apply and calling the function
+%% directly.
+%%
+%% @spec call(Name::atom()) -> [any()]
+%% @end
+%%--------------------------------------------------------------------
+
+-spec call(Name::atom()) -> [any()].
call(Name) ->
[M:F() || {M, F} <- callbacks(Name)].
+%%--------------------------------------------------------------------
+%% @doc
+%% See call/1.
+%%
+%% @spec call(Name::atom(), Arg::any()) -> [any()]
+%% @end
+%%--------------------------------------------------------------------
+
+-spec call(Name::atom(), Arg::any()) -> [any()].
+
call(Name, Arg) ->
[M:F(Arg) || {M, F} <- callbacks(Name)].
+%%--------------------------------------------------------------------
+%% @doc
+%% See call/1.
+%%
+%% @spec call(Name::atom(), Arg1::any(), Arg2::any()) -> [any()]
+%% @end
+%%--------------------------------------------------------------------
+
+-spec call(Name::atom(), Arg1::any(), Arg2::any()) -> [any()].
+
call(Name, Arg1, Arg2) ->
[M:F(Arg1, Arg2) || {M, F} <- callbacks(Name)].
+%%--------------------------------------------------------------------
+%% @doc
+%% See call/1.
+%%
+%% @spec call(Name::atom(), Arg1::any(), Arg2::any(), Arg3::any()) -> [any()]
+%% @end
+%%--------------------------------------------------------------------
+
+-spec call(Name::atom(), Arg1::any(), Arg2::any(), Arg3::any()) -> [any()].
+
call(Name, Arg1, Arg2, Arg3) ->
[M:F(Arg1, Arg2, Arg3) || {M, F} <- callbacks(Name)].
+%%--------------------------------------------------------------------
+%% @doc
+%% Executes apply/2 on the results of callbacks/1 as long as true is
+%% returned, once another value is returned the execution is stopped
+%% and this value returned.
+%%
+%% @spec apply_test(Name::atom(), Args::[any()]) -> true|any()
+%% @end
+%%--------------------------------------------------------------------
+
+-spec apply_test(Name::atom(), Args::[any()]) -> true|any().
+
apply_test(Name, Args) ->
apply_test_(callbacks(Name), Args).
-apply_test_([], _) ->
- true;
+%%--------------------------------------------------------------------
+%% @doc
+%% Equivalent to eplugin:apply_test/2 but optimized for calls without
+%% arguments by working around erlang:apply and calling the function
+%% directly.
+%%
+%% @spec test(Name::atom()) -> true|any()
+%% @end
+%%--------------------------------------------------------------------
-apply_test_([{M, F} | Cs], Args) ->
- case erlang:apply(M, F, Args) of
- true ->
- apply_test_(Cs, Args);
- R ->
- R
- end.
+-spec test(Name::atom()) -> true|any().
test(Name) ->
test_(callbacks(Name)).
-test_([]) ->
- true;
+%%--------------------------------------------------------------------
+%% @doc
+%% See test/1.
+%%
+%% @spec test(Name::atom(), Arg::any()) -> true|any()
+%% @end
+%%--------------------------------------------------------------------
-test_([{M, F} | Cs]) ->
- case M:F() of
- true ->
- test_(Cs);
- R ->
- R
- end.
+-spec test(Name::atom(), Arg::any()) -> true|any().
test(Name, Arg) ->
test_(callbacks(Name), Arg).
-test_([], _) ->
- true;
+%%--------------------------------------------------------------------
+%% @doc
+%% See test/1.
+%%
+%% @spec test(Name::atom(), Arg1::any(), Arg2::any()) -> true|any()
+%% @end
+%%--------------------------------------------------------------------
-test_([{M, F} | Cs], Arg) ->
- case M:F(Arg) of
- true ->
- test_(Cs, Arg);
- R ->
- R
- end.
+-spec test(Name::atom(), Arg1::any(), Arg2::any()) -> true|any().
test(Name, Arg1, Arg2) ->
test_(callbacks(Name), Arg1, Arg2).
-test_([], _, _) ->
- true;
+%%--------------------------------------------------------------------
+%% @doc
+%% See test/1.
+%%
+%% @spec test(Name::atom(), Arg1::any(),
+%% Arg2::any(), Arg3::any()) -> true|any()
+%% @end
+%%--------------------------------------------------------------------
-test_([{M, F} | Cs], Arg1, Arg2) ->
- case M:F(Arg1, Arg2) of
- true ->
- test_(Cs, Arg1, Arg2);
- R ->
- R
- end.
+-spec test(Name::atom(), Arg1::any(), Arg2::any(), Arg3::any()) -> true|any().
test(Name, Arg1, Arg2, Arg3) ->
test_(callbacks(Name), Arg1, Arg2, Arg3).
-test_([], _, _, _) ->
- true;
+%%--------------------------------------------------------------------
+%% @doc
+%% This function folds <b>Acc0</b> through all callbacks for
+%% <b>Name</b> as returned by callbacks/1. The result of function N is
+%% passed as argument to function N+1, N0 is passed Acc0.
+%%
+%% @spec fold(Name::atom(), Acc0::any()) -> any()
+%% @end
+%%--------------------------------------------------------------------
-test_([{M, F} | Cs], Arg1, Arg2, Arg3) ->
- case M:F(Arg1, Arg2, Arg3) of
- true ->
- test_(Cs, Arg1, Arg2, Arg3);
- R ->
- R
- end.
+-spec fold(Name::atom(), Acc0::any()) -> any().
fold(Name, Acc0) ->
lists:foldl(fun({M, F}, AccIn) ->
M:F(AccIn)
end, Acc0, callbacks(Name)).
+%%--------------------------------------------------------------------
+%% @doc
+%% Returns the configuration of a plugin or undefined when the plugin
+%% is not known.
+%%
+%% @spec config(Plugin::atom()) -> undefined|any()
+%% @end
+%%--------------------------------------------------------------------
+
+-spec config(Plugin::atom()) -> undefined|plugin_config().
+
config(Plugin) ->
case ets:lookup(?CONFTABLE, Plugin) of
[] ->
@@ -126,6 +323,20 @@ config(Plugin) ->
{ok, Conf}
end.
+%%--------------------------------------------------------------------
+%% @doc
+%% Disables a plugin by removing all callbacks it has registered. The
+%% callback eplugin:disable(Config) will be called on the Plugin after
+%% it was disabled to allow cleanup. And eplugin:disable_plugin(Plugin)
+%% will be called after the plugin was disabled to inform any other
+%% interested plugins.
+%%
+%% @spec disable(Plugin::atom()) -> {error, not_found}|ok
+%% @end
+%%--------------------------------------------------------------------
+
+-spec disable(Plugin::atom()) -> {error, not_found}|ok.
+
disable(Plugin) ->
case is_enabled(Plugin) of
true ->
@@ -137,13 +348,29 @@ disable(Plugin) ->
eplugin:call('eplugin:disable_plugin', Plugin),
ok;
false ->
- lager:warning("[eplugin::~p] already disabled.", [Plugin])
+ lager:warning("[eplugin::~p] already disabled.", [Plugin]),
+ {error, not_found}
end.
+%%--------------------------------------------------------------------
+%% @doc
+%% Enables a plugin by adding all callbacks it has registered. The
+%% callback eplugin:enable(Config) will be called on the Plugin
+%% <b>befire</b> it is enabled to allow initialisation. And
+%% eplugin:disable_plugin(Plugin) will be called after the plugin is
+%% enabled to inform any other interested plugins.
+%%
+%% @spec enable(Plugin::atom()) -> {error, not_found}|ok
+%% @end
+%%--------------------------------------------------------------------
+
+-spec enable(Plugin::atom()) -> {error, not_found}|ok.
+
enable(Plugin) ->
case is_enabled(Plugin) of
true ->
- lager:warning("[eplugin::~p] already enabled.", [Plugin]);
+ lager:warning("[eplugin::~p] already enabled.", [Plugin]),
+ {error, not_found};
false ->
case ets:lookup(?CONFTABLE, Plugin) of
[] ->
@@ -164,12 +391,51 @@ enable(Plugin) ->
end
end.
-register(Name, Callback, Module, Function) ->
- register(Name, Callback, Module, Function, []).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Registers Module:Function of the plugin Plugin for Callback. This
+%% can be used to programatically hook into callbacks from outside of
+%% a plugin.
+%%
+%% @spec register(Plugin::atom(), Callback::atom(),
+%% Module::atom(), Function::atom(),
+%% Options::callback_config()) -> ok
+%% @end
+%%--------------------------------------------------------------------
+
+-spec register(Plugin::atom(), Callback::atom(),
+ Module::atom(), Function::atom(),
+ Options::callback_config()) -> ok.
register(Name, Callback, Module, Function, Options) ->
eplugin_srv:register_callback(Name, Callback, Module, Function, Options).
+%%--------------------------------------------------------------------
+%% @doc
+%% Calls register/5 with an default empty option set.
+%%
+%% @spec register(Plugin::atom(), Callback::atom(),
+%% Module::atom(), Function::atom()) -> ok
+%% @end
+%%--------------------------------------------------------------------
+
+-spec register(Plugin::atom(), Callback::atom(),
+ Module::atom(), Function::atom()) -> ok.
+
+register(Plugin, Callback, Module, Function) ->
+ register(Plugin, Callback, Module, Function, []).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Checks if a certain Plugin is enabled.
+%%
+%% @spec is_enabled(Plugin::atom()) -> true | false
+%% @end
+%%--------------------------------------------------------------------
+
+-spec is_enabled(Plugin::atom()) -> true | false.
+
is_enabled(Plugin) ->
case ets:match(?TABLE, {'_', Plugin, '_', '_'}) of
[] ->
@@ -178,8 +444,151 @@ is_enabled(Plugin) ->
true
end.
+%%--------------------------------------------------------------------
+%% @doc
+%% Lists all installed plugins.
+%%
+%% @spec plugins() -> [Plugin::atom()]
+%% @end
+%%--------------------------------------------------------------------
+
+-spec plugins() -> [Plugin::atom()].
+
plugins() ->
[P || [P] <- ets:match(?CONFTABLE, {'$1', '_', '_'})].
+%%--------------------------------------------------------------------
+%% @doc
+%% Registers a certain dependency as provided. This is used for the
+%% loading process of plugins.
+%%
+%% @spec provide(What::atom()) -> ok
+%% @end
+%%--------------------------------------------------------------------
+
+-spec provide(What::atom()) -> ok.
+
provide(What) ->
eplugin_srv:provide(What).
+
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% apply_test/2 recursion function.
+%%
+%% @spec apply_test_([{Module::atom(), Function::atom()}],
+%% Args::[any()]) -> true|any()
+%% @end
+%%--------------------------------------------------------------------
+
+-spec apply_test_([{Module::atom(), Function::atom()}], Args::[any()]) -> true|any().
+
+apply_test_([], _) ->
+ true;
+
+apply_test_([{M, F} | Cs], Args) ->
+ case erlang:apply(M, F, Args) of
+ true ->
+ apply_test_(Cs, Args);
+ R ->
+ R
+ end.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% test/1 recursion function.
+%%
+%% @spec test_([{Module::atom(), Function::atom()}]) -> true|any()
+%% @end
+%%--------------------------------------------------------------------
+
+-spec test_([{Module::atom(), Function::atom()}]) -> true|any().
+
+test_([]) ->
+ true;
+
+test_([{M, F} | Cs]) ->
+ case M:F() of
+ true ->
+ test_(Cs);
+ R ->
+ R
+ end.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% test/2 recursion function.
+%%
+%% @spec test_([{Module::atom(), Function::atom()}],
+%% Arg1::any()) -> true|any()
+%% @end
+%%--------------------------------------------------------------------
+
+-spec test_([{Module::atom(), Function::atom()}],
+ Arg1::any()) -> true|any().
+
+test_([], _) ->
+ true;
+
+test_([{M, F} | Cs], Arg) ->
+ case M:F(Arg) of
+ true ->
+ test_(Cs, Arg);
+ R ->
+ R
+ end.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% test/3 recursion function.
+%%
+%% @spec test_([{Module::atom(), Function::atom()}],
+%% Arg1::any(), Arg2::any()) -> true|any()
+%% @end
+%%--------------------------------------------------------------------
+
+-spec test_([{Module::atom(), Function::atom()}],
+ Arg1::any(), Arg2::any()) -> true|any().
+
+test_([], _, _) ->
+ true;
+
+test_([{M, F} | Cs], Arg1, Arg2) ->
+ case M:F(Arg1, Arg2) of
+ true ->
+ test_(Cs, Arg1, Arg2);
+ R ->
+ R
+ end.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% test/3 recursion function.
+%%
+%% @spec test_([{Module::atom(), Function::atom()}],
+%% Arg1::any(), Arg2::any(), Arg3::any()) -> true|any()
+%% @end
+%%--------------------------------------------------------------------
+
+-spec test_([{Module::atom(), Function::atom()}],
+ Arg1::any(), Arg2::any(), Arg3::any()) -> true|any().
+
+test_([], _, _, _) ->
+ true;
+
+test_([{M, F} | Cs], Arg1, Arg2, Arg3) ->
+ case M:F(Arg1, Arg2, Arg3) of
+ true ->
+ test_(Cs, Arg1, Arg2, Arg3);
+ R ->
+ R
+ end.
Please sign in to comment.
Something went wrong with that request. Please try again.