From: Ulf Wiger <ulf@feuerlabs.com>
Date: Fri, 10 Nov 2017 18:59:07 +0100
Subject: [PATCH] Support expansion of setup modes for find_hooks()
diff --git a/doc/setup.md b/doc/setup.md
index 571bde1..4069f34 100644
--- a/doc/setup.md
+++ b/doc/setup.md
@@ -242,6 +242,21 @@ A suggested convention is:
- Create tables (or configure schema) at 200
- Populate the database at 300
+Using the `setup` environment variable `modes`, it is possible to
+define a mode that includes all hooks from different modes.
+The format is `[{M1, [M2,...]}]`. The expansion is done recursively,
+so a mode entry in the right-hand side of a pair can expand into other
+modes. In order to be included in the final list of modes, an expanding
+mode needs to include itself in the right-hand side. For example:
+
+- Applying `a` to `[{a, [b]}]` returns `[b]`
+- Applying `a` to `[{a, [a,b]}]` returns `[a,b]`
+- Applying `a` to `[{a, [a,b]},{b,[c,d]}]` returns `[a,c,d]`
+
+A typical application of this would be `[{test, [normal, test]}]`, where
+starting in the `test` mode would cause all `normal` and all `test` hooks
+to be executed.
+
<a name="find_hooks-1"></a>
### find_hooks/1 ###
diff --git a/src/setup.erl b/src/setup.erl
index 43a1d0f..329c049 100644
--- a/src/setup.erl
+++ b/src/setup.erl
@@ -980,6 +980,21 @@ main(Args) ->
%% - Create the database at phase 100
%% - Create tables (or configure schema) at 200
%% - Populate the database at 300
+%%
+%% Using the `setup' environment variable `modes', it is possible to
+%% define a mode that includes all hooks from different modes.
+%% The format is `[{M1, [M2,...]}]'. The expansion is done recursively,
+%% so a mode entry in the right-hand side of a pair can expand into other
+%% modes. In order to be included in the final list of modes, an expanding
+%% mode needs to include itself in the right-hand side. For example:
+%%
+%% - Applying `a' to `[{a, [b]}]' returns `[b]'
+%% - Applying `a' to `[{a, [a,b]}]' returns `[a,b]'
+%% - Applying `a' to `[{a, [a,b]},{b,[c,d]}]' returns `[a,c,d]'
+%%
+%% A typical application of this would be `[{test, [normal, test]}]', where
+%% starting in the `test' mode would cause all `normal' and all `test' hooks
+%% to be executed.
%% @end
%%
find_hooks() ->
@@ -996,38 +1011,70 @@ find_hooks(Mode) when is_atom(Mode) ->
%% @doc Find all setup hooks for `Mode' in `Applications'.
%% @end
find_hooks(Mode, Applications) ->
+ find_hooks_(Mode, maybe_expand_mode(Mode), Applications).
+
+maybe_expand_mode(Mode) ->
+ maybe_expand_mode(Mode, app_get_env(setup, modes, [])).
+
+maybe_expand_mode(Mode, Modes) ->
+ maybe_expand_mode(Mode, Modes, ordsets:new()).
+
+maybe_expand_mode(Mode, Modes, Acc) ->
+ case lists:keyfind(Mode, 1, Modes) of
+ {_, Ms} ->
+ Modes1 = lists:keydelete(Mode, 1, Modes),
+ lists:foldl(
+ fun(M, Acc1) ->
+ maybe_expand_mode(M, Modes1, Acc1)
+ end, Acc, Ms);
+ false ->
+ ordsets:add_element(Mode, Acc)
+ end.
+
+find_hooks_(Mode, Modes, Applications) ->
lists:foldl(
fun(A, Acc) ->
case app_get_env(A, '$setup_hooks') of
{ok, Hooks} ->
lists:foldl(
- fun({Mode1, [{_, {_,_,_}}|_] = L}, Acc1)
- when Mode1 =:= Mode ->
- find_hooks_(Mode, A, L, Acc1);
- ({Mode1, [{_, [{_, _, _}|_]}|_] = L}, Acc1)
- when Mode1 =:= Mode ->
- find_hooks_(Mode, A, L, Acc1);
- ({N, {_, _, _} = MFA}, Acc1) when Mode=:=setup ->
- orddict:append(N, MFA, Acc1);
- ({N, [{_, _, _}|_] = L}, Acc1)
- when Mode=:=setup ->
- lists:foldl(
- fun(MFA, Acc2) ->
- orddict:append(N, MFA, Acc2)
- end, Acc1, L);
- (_, Acc1) ->
- Acc1
+ fun(H, Acc1) ->
+ f_find_hooks_(H, A, Mode, Modes, Acc1)
end, Acc, Hooks);
_ ->
Acc
end
end, orddict:new(), Applications).
-find_hooks_(Mode, A, L, Acc1) ->
+f_find_hooks_(Hook, A, Mode, Modes, Acc) ->
+ IsSetup = lists:member(setup, Modes),
+ case Hook of
+ {Mode1, [{_, {_,_,_}}|_] = L} ->
+ case lists:member(Mode1, Modes) of
+ true -> find_hooks_1(Mode1, A, L, Acc);
+ false -> Acc
+ end;
+ {Mode1, [{_, [{_, _, _}|_]}|_] = L} ->
+ case lists:member(Mode1, Modes) of
+ true -> find_hooks_1(Mode1, A, L, Acc);
+ false -> Acc
+ end;
+ {N, {_, _, _} = MFA} when IsSetup ->
+ orddict:append(N, MFA, Acc);
+ {N, [{_, _, _}|_] = L} when IsSetup ->
+ lists:foldl(
+ fun(MFA, Acc1) ->
+ orddict:append(N, MFA, Acc1)
+ end, Acc, L);
+ _ ->
+ Acc
+ end.
+
+
+find_hooks_1(Mode, A, L, Acc1) ->
lists:foldl(
fun({N, {_,_,_} = MFA}, Acc2) ->
orddict:append(N, MFA, Acc2);
- ({N, [{_,_,_}|_] = MFAs}, Acc2) when is_list(MFAs) ->
+ ({N, [{_,_,_}|_] = MFAs}, Acc2) ->
lists:foldl(
fun({_,_,_} = MFA1, Acc3) ->
orddict:append(
@@ -1653,11 +1700,26 @@ setup_test_() ->
application:unload(setup)
end,
[
+ ?_test(t_expand_modes()),
?_test(t_find_hooks()),
+ ?_test(t_find_hooks_1()),
?_test(t_expand_vars()),
?_test(t_nested_includes())
]}.
+t_expand_modes() ->
+ [a] = maybe_expand_mode(a, []),
+ [a] = maybe_expand_mode(a, [{a, [a]}]),
+ [a,b,c] = maybe_expand_mode(a, [{a, [a,b]},
+ {b, [b,c]}]),
+ [b] = maybe_expand_mode(a, [{a, [b]}]),
+ [a,b,c] = maybe_expand_mode(a, [{a, [a,b]},
+ {b, [b,c]},
+ {c, [c,a]}]),
+ [c,d] = maybe_expand_mode(a, [{a, [b]},
+ {b, [c,d]}]),
+ ok.
+
t_find_hooks() ->
application:set_env(setup, '$setup_hooks',
[{100, [{a, hook, [100,1]},
@@ -1678,6 +1740,36 @@ t_find_hooks() ->
{200, [{a,hook,[200,1]}]}] = SetupHooks,
ok.
+t_find_hooks_1() ->
+ application:set_env(setup, modes, [{test, [setup, normal, test]}]),
+ application:set_env(setup, '$setup_hooks',
+ [{100, [{a, hook, [100,1]},
+ {a, hook, [100,2]}]},
+ {200, [{a, hook, [200,1]}]},
+ {upgrade, [{100, [{a, upgrade_hook, [100,1]}]}]},
+ {setup, [{100, [{a, hook, [100,3]}]}]},
+ {normal, [{300, {a, normal_hook, [300,1]}}]},
+ {test, [{400, {a, test_hook, [400,1]}}]}
+ ]),
+ NormalHooks = find_hooks(normal),
+ [{300, [{a, normal_hook, [300,1]}]}] = NormalHooks,
+ UpgradeHooks = find_hooks(upgrade),
+ [{100, [{a, upgrade_hook, [100,1]}]}] = UpgradeHooks,
+ SetupHooks = find_hooks(setup),
+ [{100, [{a,hook,[100,1]},
+ {a,hook,[100,2]},
+ {a,hook,[100,3]}]},
+ {200, [{a,hook,[200,1]}]}] = SetupHooks,
+ TestHooks = find_hooks(test),
+ [{100, [{a,hook,[100,1]},
+ {a,hook,[100,2]},
+ {a,hook,[100,3]}]},
+ {200, [{a,hook,[200,1]}]},
+ {300, [{a,normal_hook, [300,1]}]},
+ {400, [{a,test_hook, [400,1]}]}] = TestHooks,
+ ok.
+
+
t_expand_vars() ->
%% global env
application:set_env(setup, vars, [{"PLUS", {apply,erlang,'+',[1,2]}},