You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

125 line
3.7 KiB

  1. %%% @doc Meta-provider that dynamically compiles providers
  2. %%% to run aliased commands.
  3. %%%
  4. %%% This is hackish and out-there, but this module has graduated
  5. %%% from a plugin at https://github.com/tsloughter/rebar_alias after
  6. %%% years of stability. Only some error checks were added
  7. -module(rebar_prv_alias).
  8. -export([init/1]).
  9. -include("rebar.hrl").
  10. %% ===================================================================
  11. %% Public API
  12. %% ===================================================================
  13. -spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
  14. init(State) ->
  15. Aliases = rebar_state:get(State, alias, []),
  16. lists:foldl(fun({Alias, Cmds}, {ok, StateAcc}) ->
  17. case validate_provider(Alias, Cmds, State) of
  18. true -> init_alias(Alias, Cmds, StateAcc);
  19. false -> {ok, State}
  20. end
  21. end, {ok, State}, Aliases).
  22. init_alias(Alias, Cmds, State) ->
  23. Module = list_to_atom("rebar_prv_alias_" ++ atom_to_list(Alias)),
  24. MF = module(Module),
  25. EF = exports(),
  26. FF = do_func(Cmds),
  27. {ok, _, Bin} = compile:forms([MF, EF, FF]),
  28. code:load_binary(Module, "none", Bin),
  29. Provider = providers:create([
  30. {name, Alias},
  31. {module, Module},
  32. {bare, true},
  33. {deps, []},
  34. {example, example(Alias)},
  35. {opts, []},
  36. {short_desc, desc(Cmds)},
  37. {desc, desc(Cmds)}
  38. ]),
  39. {ok, rebar_state:add_provider(State, Provider)}.
  40. validate_provider(Alias, Cmds, State) ->
  41. %% This would be caught and prevented anyway, but the warning
  42. %% is friendlier
  43. case providers:get_provider(Alias, rebar_state:providers(State)) of
  44. not_found ->
  45. %% check for circular deps in the alias.
  46. case not proplists:is_defined(Alias, Cmds) of
  47. true -> true;
  48. false ->
  49. ?WARN("Alias ~p contains itself and would never "
  50. "terminate. It will be ignored.",
  51. [Alias]),
  52. false
  53. end;
  54. _ ->
  55. ?WARN("Alias ~p is already the name of a command in "
  56. "the default namespace and will be ignored.",
  57. [Alias]),
  58. false
  59. end.
  60. example(Alias) ->
  61. "rebar3 " ++ atom_to_list(Alias).
  62. desc(Cmds) ->
  63. "Equivalent to running: rebar3 do "
  64. ++ rebar_string:join(lists:map(fun to_desc/1, Cmds), ",").
  65. to_desc({Cmd, Args}) ->
  66. atom_to_list(Cmd) ++ " " ++ Args;
  67. to_desc(Cmd) ->
  68. atom_to_list(Cmd).
  69. module(Name) ->
  70. {attribute, 1, module, Name}.
  71. exports() ->
  72. {attribute, 1, export, [{do, 1}]}.
  73. do_func(Cmds) ->
  74. {function, 1, do, 1,
  75. [{clause, 1,
  76. [{var, 1, 'State'}],
  77. [],
  78. [{call, 1,
  79. {remote, 1, {atom, 1, rebar_prv_do}, {atom, 1, do_tasks}},
  80. [make_args(Cmds), {var, 1, 'State'}]}]}]}.
  81. make_args(Cmds) ->
  82. make_list(
  83. lists:map(fun make_tuple/1,
  84. lists:map(fun make_arg/1, Cmds))).
  85. make_arg({Cmd, Args}) ->
  86. {make_string(Cmd), make_list([make_string(A) || A <- split_args(Args)])};
  87. make_arg(Cmd) ->
  88. {make_string(Cmd), make_list([])}.
  89. make_tuple(Tuple) ->
  90. {tuple, 1, tuple_to_list(Tuple)}.
  91. make_list(List) ->
  92. lists:foldr(
  93. fun(Elem, Acc) -> {cons, 1, Elem, Acc} end,
  94. {nil, 1},
  95. List).
  96. make_string(Atom) when is_atom(Atom) ->
  97. make_string(atom_to_list(Atom));
  98. make_string(String) when is_list(String) ->
  99. {string, 1, String}.
  100. %% In case someone used the long option format, the option needs to get
  101. %% separated from its value.
  102. split_args(Args) ->
  103. rebar_string:lexemes(
  104. lists:map(fun($=) -> 32; (C) -> C end, Args),
  105. " ").