Permalink
Browse files

Added execution order, test, fold. Sepperated call and apply.

  • Loading branch information...
Licenser committed Mar 17, 2013
1 parent a360c12 commit a2cd0ef199197b590978427a7d833325c1e7a83f
Showing with 159 additions and 20 deletions.
  1. +28 −3 README.md
  2. +2 −1 plugins/example0/plugin.conf
  3. +10 −1 plugins/example1/example1.erl
  4. +5 −2 plugins/example1/plugin.conf
  5. +104 −10 src/eplugin.erl
  6. +10 −3 src/eplugin_srv.erl
View
@@ -9,14 +9,34 @@ Simply start the applicaiton, there is only one configuration variable at the mo
apply/2
-------
-And respectively `apply/1` which is the same as `apply/2` with empty args.
-
This function can be used to call all the functions that were registered to a certain callback, with the args given as seconds argument.
```erlang
eplugin:apply(my_fancy_callback, [1, 2, 3]).
```
+call/1+
+-------
+This function is a simplification of apply with a fixed number of arguments, this way it gets around using `erlang:apply` internally.
+
+```erlang
+eplugin:apply(my_fancy_callback, [1, 2, 3]) =:= eplugin:call(my_fancy_callback, 1, 2, 3).
+```
+
+fold/2
+------
+This function folds an argument through all callbacks. The first argument is the name the second the Acc0 for the fold.
+
+test_apply/1+
+------
+Threads through the callbacks as long as true is returned, if anything else is returned execution is terminated and this value returned.
+
+test_apply/2 test/1+
+---------------------
+Threads through the callbacks as long as true is returned, if anything else is returned execution is terminated and this value returned.
+
+Naming is according to apply and call.
+
callbacks/1
-----------
The basis for apply, this function returns all modules and functions registered for a certain callback. It returns: `[{Module, Function}]`
@@ -50,6 +70,8 @@ is_enabled/1
------------
Returns true if a plugin is enabled (aka has any callbacks registered).
+
+
Writing plugins
===============
A plugin is a directory with a `plugin.conf` file and one or more .erl files so the a plugin directory could look like:
@@ -70,10 +92,13 @@ The plugin.conf is a simple file with the following syntax:
```erlang
{PluginName,
- [{Module, [{Callback, Function}]}],
+ [{Module, [{Callback, Function}|{Callback, Function, CallbackOptions}]}],
OptionPlist}.
```
+CallbackOptions has the following possbile values
+* priority - the priority for execution order, highest priority first, default is 0
+
OptionsPlist has the following reserved options:
* disabled - this plugin will not load.
@@ -1,5 +1,6 @@
{example0,
- [{example0, [{hello, hello},
+ [{example0, [{hello1, hello, [{priority, 100}]},
+ {hello2, hello},
{'eplugin:enable', enable},
{'eplugin:disable', disable}]}],
[{dependencies, [example1]}]}.
@@ -2,7 +2,9 @@
-export([hello/0,
enable/1,
- disable/1]).
+ disable/1,
+ test1/0,
+ test2/0]).
hello() ->
io:format("[example1] hello~n").
@@ -12,3 +14,10 @@ enable(Conf) ->
disable(Conf) ->
io:format("[example1] we have been disabled!~n~p~n", [Conf]).
+
+
+test1() ->
+ true.
+
+test2() ->
+ {error, "we always fail!"}.
@@ -1,5 +1,8 @@
{example1,
- [{example1, [{hello, hello},
+ [{example1, [{hello1, hello},
+ {hello2, hello, [{priority, 100}]},
{'eplugin:enable', enable},
- {'eplugin:disable', disable}]}],
+ {'eplugin:disable', disable},
+ {test1, test1},
+ {test2, test2}]}],
[{provides, [example1]}]}.
View
@@ -3,10 +3,14 @@
-export([start/0,
callbacks/1,
apply/2,
- apply/1,
+ call/1, call/2, call/3, call/4,
+ apply_test/2,
+ test/1, test/2, test/3, test/4,
+ fold/2,
config/1,
plugins/0,
enable/1,
+ provide/1,
is_enabled/1,
disable/1]).
@@ -19,14 +23,100 @@ start() ->
application:start(eplugin).
callbacks(Name) ->
- [{M, F} || {_, _, M, F} <- ets:lookup(?TABLE, Name)].
+ C0 = [{P, M, F} || {_, _, M, F, P} <- ets:lookup(?TABLE, Name)],
+ [{M, F} || {_, M, F} <- lists:sort(C0)].
-apply(Name) ->
- eplugin:apply(Name, []).
-
-apply(Name, Args) ->
+apply(Name, Args) when is_list(Args) ->
[erlang:apply(M, F, Args) || {M, F} <- callbacks(Name)].
+
+call(Name) ->
+ [M:F() || {M, F} <- callbacks(Name)].
+
+call(Name, Arg) ->
+ [M:F(Arg) || {M, F} <- callbacks(Name)].
+
+call(Name, Arg1, Arg2) ->
+ [M:F(Arg1, Arg2) || {M, F} <- callbacks(Name)].
+
+call(Name, Arg1, Arg2, Arg3) ->
+ [M:F(Arg1, Arg2, Arg3) || {M, F} <- callbacks(Name)].
+
+apply_test(Name, Args) ->
+ apply_test_(callbacks(Name), Args).
+
+apply_test_([], _) ->
+ true;
+
+apply_test_([{M, F} | Cs], Args) ->
+ case erlang:apply(M, F, Args) of
+ true ->
+ apply_test_(Cs, Args);
+ R ->
+ R
+ end.
+
+test(Name) ->
+ test_(callbacks(Name)).
+
+test_([]) ->
+ true;
+
+test_([{M, F} | Cs]) ->
+ case M:F() of
+ true ->
+ test_(Cs);
+ R ->
+ R
+ end.
+
+test(Name, Arg) ->
+ test_(callbacks(Name), Arg).
+
+test_([], _) ->
+ true;
+
+test_([{M, F} | Cs], Arg) ->
+ case M:F(Arg) of
+ true ->
+ test_(Cs, Arg);
+ R ->
+ R
+ end.
+
+test(Name, Arg1, Arg2) ->
+ test_(callbacks(Name), Arg1, Arg2).
+
+test_([], _, _) ->
+ true;
+
+test_([{M, F} | Cs], Arg1, Arg2) ->
+ case M:F(Arg1, Arg2) of
+ true ->
+ test_(Cs, Arg1, Arg2);
+ R ->
+ R
+ end.
+
+test(Name, Arg1, Arg2, Arg3) ->
+ test_(callbacks(Name), Arg1, Arg2, Arg3).
+
+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.
+
+fold(Name, Acc0) ->
+ lists:foldl(fun({M, F}, AccIn) ->
+ M:F(AccIn)
+ end, Acc0, callbacks(Name)).
+
config(Plugin) ->
case ets:lookup(?CONFTABLE, Plugin) of
[] ->
@@ -42,8 +132,8 @@ disable(Plugin) ->
Callback = ets:match(?TABLE, {'eplugin:disable', Plugin, '$1', '$2'}),
ets:match_delete(?TABLE, {'_', Plugin, '_', '_'}),
{ok, Config} = config(Plugin),
- [erlang:apply(M, F, [Config]) || [M, F] <- Callback],
- eplugin:apply('eplugin:disable_plugin', [Plugin]),
+ [M:F(Config) || [M, F] <- Callback],
+ eplugin:call('eplugin:disable_plugin', Plugin),
ok;
false ->
lager:warning("[eplugin::~p] already disabled.", [Plugin])
@@ -61,18 +151,19 @@ enable(Plugin) ->
{ok, Config} = config(Plugin),
lists:foreach(fun({M, Callbacks}) ->
lists:foreach(fun({'eplugin:enable', Fun}) ->
- erlang:apply(M, Fun, [Config]);
+ M:Fun(Config);
(_) ->
ok
end, Callbacks)
end, Modules),
[eplugin_srv:register_callbacks(Plugin, M) || M <- Modules],
- eplugin:apply('eplugin:enable_plugin', [Plugin]),
+ eplugin:call('eplugin:enable_plugin', Plugin),
[eplugin_srv:provide(What) || What <- proplists:get_value(provides, Config, [])],
ok
end
end.
+
is_enabled(Plugin) ->
case ets:match(?TABLE, {'_', Plugin, '_', '_'}) of
[] ->
@@ -83,3 +174,6 @@ is_enabled(Plugin) ->
plugins() ->
[P || [P] <- ets:match(?CONFTABLE, {'$1', '_', '_'})].
+
+provide(What) ->
+ eplugin_srv:provide(What).
View
@@ -229,11 +229,18 @@ compile_module(Name, Path, Module) ->
end.
register_callbacks(Name, {Module, Callbacks}) ->
- [register_callback(Name, Callback, Module, Function) || {Callback, Function} <- Callbacks ].
+ [case C of
+ {Callback, Function, Options} ->
+ register_callback(Name, Callback, Module, Function, Options);
+ {Callback, Function} ->
+ register_callback(Name, Callback, Module, Function, [])
+ end || C <- Callbacks ].
-register_callback(Name, Callback, Module, Function) ->
+register_callback(Name, Callback, Module, Function, Options) ->
+ Priority = proplists:get_value(priority, Options, 0),
lager:info("[eplugin::~p] Registering callback ~p with ~p:~p.", [Name, Callback, Module, Function]),
- ets:insert(?TABLE, {Callback, Name, Module, Function}).
+ %% To get a propper sorting order we take the negative priority value
+ ets:insert(?TABLE, {Callback, Name, Module, Function, Priority*-1}).
load_config(ConfigFile) ->
Path = filename:dirname(ConfigFile),

0 comments on commit a2cd0ef

Please sign in to comment.