Blame erlang-setup-0002-Support-expansion-of-setup-modes-for-find_hooks.patch

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