1-module(rebar3_hex_info).
2
3-export([init/1
4        ,do/1
5        ,format_error/1]).
6
7-include("rebar3_hex.hrl").
8
9-define(PROVIDER, info).
10-define(DEPS, []).
11
12-define(ENDPOINT, "packages").
13-define(RELEASE, "releases").
14-define(REGISTRY_FILE, "registry").
15
16%% ===================================================================
17%% Public API
18%% ===================================================================
19
20-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
21init(State) ->
22    Provider = providers:create([
23                                {name, ?PROVIDER},
24                                {module, ?MODULE},
25                                {namespace, hex},
26                                {bare, true},
27                                {deps, ?DEPS},
28                                {example, "rebar3 hex user <command>"},
29                                {short_desc, "Prints hex package or system information"},
30                                {desc, ""},
31                                {opts, []}
32                                ]),
33    State1 = rebar_state:add_provider(State, Provider),
34    {ok, State1}.
35
36-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
37do(State) ->
38    case rebar_state:command_args(State) of
39        [] ->
40            general(State);
41        [Package] ->
42            package(Package);
43        [Package, Version] ->
44            release(Package, Version)
45    end,
46    {ok, State}.
47
48-spec format_error(any()) -> iolist().
49format_error(Reason) ->
50    io_lib:format("~p", [Reason]).
51
52general(State) ->
53    Deps = [
54	case X of
55	    {Name, Ver} -> {normalize(Name), Ver};
56	    Name -> {normalize(Name), get_ver(normalize(Name), State)}
57	end
58	|| X <-rebar_state:get(State, deps, [])],
59    display(dedup({Deps, []}), [], State).
60
61%%prints out all deps at level 0, and
62%%For those level 0 deps which are package dependencies (Found in Hex Database),
63%%it continue printing deps at level > 0
64display([], _, _State) ->
65    ok;
66
67display([{Name, Vsn} | Deps], Listed, State) ->
68    ec_talk:say("~s ~s", [Name, Vsn]),
69    %accumulates already listed deps
70    NewListed = [{Name, Vsn} | Listed],
71    case get_deps(Name, Vsn, State) of
72	error ->
73	    display(Deps, NewListed, State);
74	NewDeps ->
75	    display(dedup({Deps ++ NewDeps, NewListed}), NewListed, State)
76    end.
77
78%gets deps name(s) and version(s) for a package
79get_deps(Package, Version, State) ->
80    RegistryDir = rebar_packages:registry_dir(State),
81    case ets:file2tab(filename:join(RegistryDir, ?REGISTRY_FILE)) of
82	{ok, Registry} ->
83	    VerBin = ec_cnv:to_binary(Version),
84		case ets:lookup(Registry, {Package, VerBin}) of
85		    [{{Package, VerBin}, [DepList, _, _]}] ->
86			[{normalize(Name), Ver} || [Name, Ver, _ , _] <- DepList];
87		    _ -> error
88		end;
89	_ ->
90	    %registry table not found
91	    case rebar3_hex_http:get(filename:join([?ENDPOINT, Package, "releases", Version]), []) of
92		{ok, Map} ->
93		    [{normalize(X), binary_to_list(maps:get(<<"requirement">>, maps:get(X, maps:get(<<"requirements">>, Map))))}
94			|| X <- maps:keys(maps:get(<<"requirements">>, Map))];
95		{error, 404} -> error;
96		_ -> error
97	    end
98    end.
99
100%gets latest release version
101%returns " " if the Package is not found in Hex
102get_ver(Package, State) ->
103    RegistryDir = rebar_packages:registry_dir(State),
104    case ets:file2tab(filename:join(RegistryDir, ?REGISTRY_FILE)) of
105	{ok, Registry} ->
106	    case ets:lookup(Registry, Package) of
107		[{Package, [VerList]}] ->
108		    lists:last(VerList);
109		_ -> " "
110	    end;
111	_ ->
112	    %registry table not found
113	    case rebar3_hex_http:get(filename:join(?ENDPOINT, Package), []) of
114        	{ok, Map} ->
115            	    Release = hd(maps:get(<<"releases">>, Map, [])),
116            	    binary_to_list(maps:get(<<"version">>, Release, []));
117        	{error, 404} ->
118            		" ";
119        	_ ->
120            		" "
121    	    end
122    end.
123
124dedup({Deps, Listed}) ->
125    Current = Deps ++ Listed,
126    dedup(Deps, [name(Dep) || Dep <- Current]).
127
128dedup([], _) -> [];
129dedup([Dep|Deps], [Name|DepNames]) ->
130    case lists:member(Name, DepNames) of
131        true -> dedup(Deps, DepNames);
132        false -> [Dep | dedup(Deps, DepNames)]
133    end.
134
135name(T) when is_tuple(T) -> element(1, T).
136
137normalize(Name) when is_binary(Name) ->
138    Name;
139normalize(Name) when is_atom(Name) ->
140    ec_cnv:to_binary(Name).
141
142package(Package) ->
143    case rebar3_hex_http:get(filename:join(?ENDPOINT, Package), []) of
144        {ok, Map} ->
145            ec_talk:say("~s", [Package]),
146            Releases = maps:get(<<"releases">>, Map, []),
147            ec_talk:say("  Releases: ~s", [string:join([binary_to_list(maps:get(<<"version">>, X, [])) || X <- Releases], ", ")]),
148            Meta = maps:get(<<"meta">>, Map),
149
150            % Remove this when Hex no longer supports contributors
151            Contributors = maps:get(<<"contributors">>, Meta, []),
152            ec_talk:say("  Contributors: ~s", [join(Contributors)]),
153
154            Maintainers = maps:get(<<"maintainers">>, Meta, []),
155            ec_talk:say("  Maintainers: ~s", [join(Maintainers)]),
156
157            Licenses = maps:get(<<"licenses">>, Meta, []),
158            ec_talk:say("  Licenses: ~s", [join(Licenses)]),
159
160            Links = maps:to_list(maps:get(<<"links">>, Meta, [])),
161            ec_talk:say("  Links:\n    ~s", [tup_list_join(Links)]),
162
163            Description = maps:get(<<"description">>, Meta, []),
164            ec_talk:say(Description, []);
165        {error, 404} ->
166            rebar_api:error("No package with name ~s", [Package]);
167        _ ->
168            rebar_api:error("Failed to retrieve package information")
169    end.
170
171
172release(Package, Version) ->
173    case rebar3_hex_http:get(filename:join([?ENDPOINT, Package, "releases", Version]), []) of
174        {ok, Map} ->
175            ec_talk:say("~s ~s", [Package, Version]),
176            ec_talk:say("  Dependencies:\n    ~s", [req_join(Map)]);
177        {error, 404} ->
178            rebar_api:error("No package with name ~s", [Package]);
179        _ ->
180            rebar_api:error("Failed to retrieve package information")
181    end.
182
183join(List) ->
184    string:join([binary_to_list(X) || X <- List], ", ").
185
186tup_list_join(List) ->
187    string:join([binary_to_list(X)++": "++binary_to_list(Y) || {X, Y} <- List], "\n    ").
188
189req_join(ReleaseMap) ->
190    string:join([binary_to_list(X)++": "++binary_to_list(maps:get(<<"requirement">>, maps:get(X, maps:get(<<"requirements">>, ReleaseMap))))
191	|| X <- maps:keys(maps:get(<<"requirements">>, ReleaseMap))],"\n    ").
192