1%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
2%% ex: ts=4 sw=4 et
3
4-module(rebar_prv_path).
5
6-behaviour(provider).
7
8-export([init/1,
9         do/1,
10         format_error/1]).
11
12-include("rebar.hrl").
13
14-define(PROVIDER, path).
15-define(DEPS, [app_discovery]).
16
17%% ===================================================================
18%% Public API
19%% ===================================================================
20
21-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
22init(State) ->
23    State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER},
24                                                               {module, ?MODULE},
25                                                               {bare, true},
26                                                               {deps, ?DEPS},
27                                                               {example, "rebar3 path"},
28                                                               {short_desc, "Print paths to build dirs in current profile."},
29                                                               {desc, "Print paths to build dirs in current profile."},
30                                                               {opts, path_opts(State)}])),
31
32    {ok, State1}.
33
34-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
35do(State) ->
36    {RawOpts, _} = rebar_state:command_parsed_args(State),
37    %% retrieve apps to filter by for other args
38    Apps = filter_apps(RawOpts, State),
39    %% remove apps and separator opts from options
40    Paths = lists:filter(fun({app, _}) -> false; ({separator, _}) -> false; (_) -> true end, RawOpts),
41    %% if no paths requested in opts print the base_dir instead
42    P = case Paths of [] -> [{ebin, true}]; _ -> Paths end,
43    paths(P, Apps, State, []),
44    {ok, State}.
45
46-spec format_error(any()) -> iolist().
47format_error(Reason) ->
48    io_lib:format("~p", [Reason]).
49
50filter_apps(RawOpts, State) ->
51    RawApps = proplists:get_all_values(app, RawOpts),
52    Apps = lists:foldl(fun(String, Acc) -> rebar_string:lexemes(String, ",") ++ Acc end, [], RawApps),
53    case Apps of
54        [] ->
55            ProjectDeps = project_deps(State),
56            ProjectApps = rebar_state:project_apps(State),
57            lists:map(fun(A) -> binary_to_list(rebar_app_info:name(A)) end, ProjectApps) ++ ProjectDeps;
58        _  -> Apps
59    end.
60
61
62paths([], _, State, Acc) -> print_paths_if_exist(lists:reverse(Acc), State);
63paths([{base, true}|Rest], Apps, State, Acc) ->
64    paths(Rest, Apps, State, [base_dir(State)|Acc]);
65paths([{bin, true}|Rest], Apps, State, Acc) ->
66    paths(Rest, Apps, State, [bin_dir(State)|Acc]);
67paths([{ebin, true}|Rest], Apps, State, Acc) ->
68    paths(Rest, Apps, State, ebin_dirs(Apps, State) ++ Acc);
69paths([{lib, true}|Rest], Apps, State, Acc) ->
70    paths(Rest, Apps, State, [lib_dir(State)|Acc]);
71paths([{priv, true}|Rest], Apps, State, Acc) ->
72    paths(Rest, Apps, State, priv_dirs(Apps, State) ++ Acc);
73paths([{src, true}|Rest], Apps, State, Acc) ->
74    paths(Rest, Apps, State, src_dirs(Apps, State) ++ Acc);
75paths([{rel, true}|Rest], Apps, State, Acc) ->
76    paths(Rest, Apps, State, [rel_dir(State)|Acc]).
77
78base_dir(State) -> io_lib:format("~ts", [rebar_dir:base_dir(State)]).
79bin_dir(State)  -> io_lib:format("~ts/bin", [rebar_dir:base_dir(State)]).
80lib_dir(State)  -> io_lib:format("~ts", [rebar_dir:deps_dir(State)]).
81rel_dir(State)  -> io_lib:format("~ts/rel", [rebar_dir:base_dir(State)]).
82
83ebin_dirs(Apps, State) ->
84    lists:flatmap(fun(App) -> [io_lib:format("~ts/~ts/ebin", [rebar_dir:checkouts_out_dir(State), App]),
85                               io_lib:format("~ts/~ts/ebin", [rebar_dir:deps_dir(State), App]) ] end, Apps).
86priv_dirs(Apps, State) ->
87    lists:flatmap(fun(App) -> [io_lib:format("~ts/~ts/priv", [rebar_dir:checkouts_out_dir(State), App]),
88                               io_lib:format("~ts/~ts/priv", [rebar_dir:deps_dir(State), App]) ] end, Apps).
89src_dirs(Apps, State) ->
90    lists:flatmap(fun(App) -> [io_lib:format("~ts/~ts/src", [rebar_dir:checkouts_out_dir(State), App]),
91                               io_lib:format("~ts/~ts/src", [rebar_dir:deps_dir(State), App]) ] end, Apps).
92
93print_paths_if_exist(Paths, State) ->
94    {RawOpts, _} = rebar_state:command_parsed_args(State),
95    Sep = proplists:get_value(separator, RawOpts, " "),
96    RealPaths = lists:filter(fun(P) -> ec_file:is_dir(P) end, Paths),
97    io:format("~ts", [rebar_string:join(RealPaths, Sep)]).
98
99project_deps(State) ->
100    Profiles = rebar_state:current_profiles(State),
101    DepList = lists:foldl(fun(Profile, Acc) -> rebar_state:get(State, {deps, Profile}, []) ++ Acc end, [], Profiles),
102    LockList = lists:foldl(fun(Profile, Acc) -> rebar_state:get(State, {locks, Profile}, []) ++ Acc end, [], Profiles),
103    Deps = [normalize(name(Dep)) || Dep <- DepList++LockList],
104    lists:usort(Deps).
105
106name(App) when is_tuple(App) -> element(1, App);
107name(Name) when is_binary(Name); is_list(Name); is_atom(Name) -> Name.
108
109normalize(AppName) when is_list(AppName) -> AppName;
110normalize(AppName) when is_atom(AppName) -> atom_to_list(AppName);
111normalize(AppName) when is_binary(AppName) -> binary_to_list(AppName).
112
113path_opts(_State) ->
114    [{app, undefined, "app", string, help(app)},
115     {base, undefined, "base", boolean, help(base)},
116     {bin, undefined, "bin", boolean, help(bin)},
117     {ebin, undefined, "ebin", boolean, help(ebin)},
118     {lib, undefined, "lib", boolean, help(lib)},
119     {priv, undefined, "priv", boolean, help(priv)},
120     {separator, $s, "separator", string, help(separator)},
121     {src, undefined, "src", boolean, help(src)},
122     {rel, undefined, "rel", boolean, help(rel)}].
123
124help(app)       -> "Comma separated list of applications to return paths for.";
125help(base)      -> "Return the `base' path of the current profile.";
126help(bin)       -> "Return the `bin' path of the current profile.";
127help(ebin)      -> "Return all `ebin' paths of the current profile's applications.";
128help(lib)       -> "Return the `lib' path of the current profile.";
129help(priv)      -> "Return the `priv' path of the current profile's applications.";
130help(separator) -> "In case of multiple return paths, the separator character to use to join them.";
131help(src)       -> "Return the `src' path of the current profile's applications.";
132help(rel)       -> "Return the `rel' path of the current profile.".
133