Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 243 lines (222 sloc) 10.559 kB
44a5bb4 @jlouis Hint Emacs that the Tab Stop of the files are 4 spaces.
jlouis authored
1 %% -*- Mode: Erlang; tab-width: 4 -*-
3d5cf18 @yrashk Made agner_github a parametrized module
yrashk authored
2 -module(agner_github, [Account]).
61c97b0 @yrashk Added modular indeces support
yrashk authored
3 -behaviour(agner_index).
4 -include_lib("agner.hrl").
5 -include_lib("agner_index.hrl").
1f567bb @yrashk Everybody shpuld be able to read cache files in /tmp
yrashk authored
6 -include_lib("kernel/include/file.hrl").
61c97b0 @yrashk Added modular indeces support
yrashk authored
7
8 -export([repositories/0,
9 repository/1,
10 tags/1,
11 branches/1,
7e7089d @yrashk Added -b/--browser option that will open browser to show the specific…
yrashk authored
12 spec/2,
13 spec_url/2
1aabca9 @yrashk Initial WIP, nothing to see yet
yrashk authored
14 ]).
15
61c97b0 @yrashk Added modular indeces support
yrashk authored
16 repositories() ->
38f928b @yrashk Apparently GitHub API or listing repositories paginates results, so w…
yrashk authored
17 repositories(1).
18
19 repositories(Page) ->
20 case request("https://github.com/api/v2/json/repos/show/" ++ Account ++ "?page=" ++ integer_to_list(Page)) of
61c97b0 @yrashk Added modular indeces support
yrashk authored
21 {error, _Reason} = Error ->
22 Error;
58abe94 @talentdeficit removes mochijson2 dependency, replaced with vastly superior jsx
talentdeficit authored
23 Object ->
38f928b @yrashk Apparently GitHub API or listing repositories paginates results, so w…
yrashk authored
24 case proplists:get_value(<<"repositories">>, Object) of
25 [] ->
26 [];
27 Repositories ->
94bf977 @yrashk Empty .agner repos shouldn't cause failure listing other packages
yrashk authored
28 Repos = lists:filter(fun
29 ({_,undefined}) ->
30 false;
31 ({invalid,_}) ->
32 false;
33 (_) ->
34 true
35 end,
58abe94 @talentdeficit removes mochijson2 dependency, replaced with vastly superior jsx
talentdeficit authored
36 lists:map(fun (RepObject) ->
94bf977 @yrashk Empty .agner repos shouldn't cause failure listing other packages
yrashk authored
37 {repo_name(proplists:get_value(<<"name">>, RepObject)),
38 proplists:get_value(<<"pushed_at">>, RepObject)}
39 end, Repositories)),
38f928b @yrashk Apparently GitHub API or listing repositories paginates results, so w…
yrashk authored
40 Repos ++ repositories(Page + 1)
41 end
61c97b0 @yrashk Added modular indeces support
yrashk authored
42 end.
43
44 repository(Name) ->
1539dde @yrashk Allow using github accounts other than 'agner'
yrashk authored
45 case request("https://github.com/api/v2/json/repos/show/" ++ proper_repo_name(Name)) of
61c97b0 @yrashk Added modular indeces support
yrashk authored
46 {error, _Reason} = Error ->
47 Error;
58abe94 @talentdeficit removes mochijson2 dependency, replaced with vastly superior jsx
talentdeficit authored
48 Object ->
49 proplists:get_value(<<"repository">>, Object)
61c97b0 @yrashk Added modular indeces support
yrashk authored
50 end.
51
1aabca9 @yrashk Initial WIP, nothing to see yet
yrashk authored
52
61c97b0 @yrashk Added modular indeces support
yrashk authored
53 tags(Name) ->
04d4cfb @yrashk Don't use GitHub API to retrieve package versions
yrashk authored
54 {ok, RepoServer} = agner_repo_server:create(Name, {flavour, "master"}),
55 ok = agner_repo_server:clone(RepoServer, fun (N) -> "git://github.com/" ++ proper_repo_name(N) ++ ".git" end),
56 Path = agner_repo_server:file(RepoServer, []),
57 Port = agner_download:git(["tag", "-l"], [{cd, Path}, use_stdio, stderr_to_stdout, {line, 255}]),
58 PortHandler = fun (F,Acc) ->
59 receive
60 {'EXIT', Port, _} ->
61 error;
62 {Port,{exit_status,0}} ->
63 {ok, Acc};
64 {Port,{exit_status,_}} ->
65 error;
66 {Port, {data, {_, D}}} when is_list(D) ->
67 Tag = string:strip(D, right, $\n),
68 F(F,[Tag|Acc]);
69 _ ->
70 F(F,Acc)
71 end
72 end,
73 Result = PortHandler(PortHandler,[]),
74 case Result of
75 error ->
76 [];
77 {ok, Tags} ->
78 lists:map(fun (Tag) ->
79 RevParsePort = agner_download:git(["rev-parse",Tag],
80 [{cd, Path}, use_stdio, stderr_to_stdout, {line, 255}]),
81 RevParsePortHandler = fun (F1,Acc1) ->
82 receive
83 {'EXIT', RevParsePort, _} ->
84 "";
85 {RevParsePort,{exit_status,0}} ->
86 Acc1;
87 {RevParsePort, {exit_status, _}} ->
88 "";
89 {RevParsePort, {data, {_, D1}}} when is_list (D1) ->
90 F1(F1,D1);
91 _ ->
92 F1(F1,Acc1)
93 end
94 end,
95 SHA1 = RevParsePortHandler(RevParsePortHandler,""),
96 {Tag, SHA1}
97 end, Tags)
98 end.
61c97b0 @yrashk Added modular indeces support
yrashk authored
99
100
101 branches(Name) ->
04d4cfb @yrashk Don't use GitHub API to retrieve package versions
yrashk authored
102 {ok, RepoServer} = agner_repo_server:create(Name, {flavour, "master"}),
103 ok = agner_repo_server:clone(RepoServer, fun (N) -> "git://github.com/" ++ proper_repo_name(N) ++ ".git" end),
104 Path = agner_repo_server:file(RepoServer, []),
105 Port = agner_download:git(["branch", "-r"], [{cd, Path}, use_stdio, stderr_to_stdout, {line, 255}]),
106 PortHandler = fun (F,Acc) ->
107 receive
108 {'EXIT', Port, _} ->
109 error;
110 {Port,{exit_status,0}} ->
111 {ok, Acc};
112 {Port,{exit_status,_}} ->
113 error;
114 {Port, {data, {_, " origin/HEAD" ++ _}}} -> %% ignore
115 F(F, Acc);
116 {Port, {data, {_, " origin/" ++ Branch}}} when is_list(Branch) ->
117 F(F,[Branch|Acc]);
118 {Port, {data, {_, _}}} -> %% ignore as well
119 F(F,Acc);
120 _ ->
121 F(F,Acc)
122 end
123 end,
124 Result = PortHandler(PortHandler,[]),
125 case Result of
126 error ->
127 [];
128 {ok, Branches} ->
129 lists:map(fun (Branch) ->
130 RevParsePort = agner_download:git(["rev-parse",Branch],
131 [{cd, Path}, use_stdio, stderr_to_stdout, {line, 255}]),
132 RevParsePortHandler = fun (F1,Acc1) ->
133 receive
134 {'EXIT', RevParsePort, _} ->
135 "";
136 {RevParsePort,{exit_status,0}} ->
137 Acc1;
138 {RevParsePort, {exit_status, _}} ->
139 "";
140 {RevParsePort, {data, {_, D1}}} when is_list (D1) ->
141 F1(F1,D1);
142 _ ->
143 F1(F1,Acc1)
144 end
145 end,
146 SHA1 = RevParsePortHandler(RevParsePortHandler,""),
147 {Branch, SHA1}
148 end, Branches)
149 end.
1aabca9 @yrashk Initial WIP, nothing to see yet
yrashk authored
150
7ee519f @yrashk Extracted agner_repo_server
yrashk authored
151 spec(Name, Version) ->
152 {ok, RepoServer} = agner_repo_server:create(Name, Version),
153 case agner_repo_server:pushed_at(RepoServer) of
77ad16b @yrashk Implemented some aggressive caching and got rid of another GitHub API…
yrashk authored
154 At when is_list(At) ->
350bc0f @yrashk Use /tmp/agner instead of /tmp/.agner for caching
yrashk authored
155 DotDir = filename:join("/tmp","agner"),
77ad16b @yrashk Implemented some aggressive caching and got rid of another GitHub API…
yrashk authored
156 filelib:ensure_dir(DotDir ++ "/"),
81c5ad3 @yrashk BUGFIX: /tmp/agner should be writeable to all users
yrashk authored
157 {ok, #file_info{mode = Mode}} = file:read_file_info(DotDir),
158 file:change_mode(DotDir, Mode bor 8#0222 bor 8#0444),
7ee519f @yrashk Extracted agner_repo_server
yrashk authored
159 AtFilename = filename:join([DotDir, "cache." ++ Name ++ integer_to_list(erlang:phash2(Version)) ++
160 lists:map(fun ($/) ->
161 $_;
162 ($\s) ->
163 $_;
164 (C) ->
165 C
166 end, At)]),
167 spec_1(RepoServer, AtFilename);
77ad16b @yrashk Implemented some aggressive caching and got rid of another GitHub API…
yrashk authored
168 undefined ->
169 {A,B,C} = now(),
170 N = node(),
171 TmpFile = lists:flatten(io_lib:format("/tmp/agner-~p-~p.~p.~p",[N,A,B,C])),
7ee519f @yrashk Extracted agner_repo_server
yrashk authored
172 Result = spec_1(RepoServer, TmpFile),
77ad16b @yrashk Implemented some aggressive caching and got rid of another GitHub API…
yrashk authored
173 file:delete(TmpFile),
174 Result
175 end.
176
7ee519f @yrashk Extracted agner_repo_server
yrashk authored
177 spec_1(RepoServer, AtFilename) ->
c091f2e @yrashk Extracted spec defaults
yrashk authored
178 Spec =
179 case file:read_file_info(AtFilename) of
180 {error, enoent} ->
181 case agner_repo_server:clone(RepoServer, fun (Name) -> "git://github.com/" ++ proper_repo_name(Name) ++ ".git" end) of
182 ok ->
183 Config = agner_repo_server:file(RepoServer, "agner.config"),
184 {ok, S} = file:consult(Config),
185 {ok, _} = file:copy(Config, AtFilename),
186 {ok, #file_info{mode = Mode}} = file:read_file_info(AtFilename),
187 file:change_mode(AtFilename, Mode bor 8#00444),
188 S;
189 _ ->
190 {error, not_found}
191 end;
192 {ok, _} ->
193 {ok, S} = file:consult(AtFilename),
194 S
195 end,
196 agner_spec:normalize(Spec).
77ad16b @yrashk Implemented some aggressive caching and got rid of another GitHub API…
yrashk authored
197
7e7089d @yrashk Added -b/--browser option that will open browser to show the specific…
yrashk authored
198 spec_url(Name, SHA1) ->
199 "https://github.com/" ++ proper_repo_name(Name) ++ "/blob/" ++ SHA1 ++ "/agner.config".
1aabca9 @yrashk Initial WIP, nothing to see yet
yrashk authored
200
201 %%%
202
1539dde @yrashk Allow using github accounts other than 'agner'
yrashk authored
203 proper_repo_name(Name) ->
204 case string:tokens(Name,"/") of
205 [_, _]=L ->
206 string:join(L,"/") ++ ".agner";
207 [Name] ->
208 string:join([Account, Name],"/") ++ ".agner"
209 end.
210
211 %%%
212
1aabca9 @yrashk Initial WIP, nothing to see yet
yrashk authored
213 request(URL) ->
214 parse_response(httpc_request(URL)).
215
216 httpc_request(URL) ->
217 httpc_request_1(URL,[]).
218
219 httpc_request_1(URL, Opts) ->
220 httpc:request(get,{URL,
221 []},
222 [{timeout, 60000}],
7acb9dc @yrashk Minor styling change
yrashk authored
223 [{body_format, binary}|Opts],
1aabca9 @yrashk Initial WIP, nothing to see yet
yrashk authored
224 agner).
61c97b0 @yrashk Added modular indeces support
yrashk authored
225
226 parse_response({ok, {{"HTTP/1.1",200,_},_Headers,Body}}) ->
58abe94 @talentdeficit removes mochijson2 dependency, replaced with vastly superior jsx
talentdeficit authored
227 jsx:json_to_term(Body);
61c97b0 @yrashk Added modular indeces support
yrashk authored
228 parse_response({ok, {{"HTTP/1.1",404,_},_Headers,_Body}}) ->
4877e63 @yrashk Added a clause for GitHub's 403 status code (normally used when API r…
yrashk authored
229 {error, not_found};
230 parse_response({ok, {{"HTTP/1.1",403,_}, _Headers, _Body}}) ->
231 {error, github_rate_limit_exceeded}.
61c97b0 @yrashk Added modular indeces support
yrashk authored
232 %%%
233
234 repo_name(B) when is_binary(B) ->
235 S = binary_to_list(B),
236 case string:tokens(S,".") of
237 [Name,"agner"] ->
238 Name;
239 _ ->
240 invalid
241 end.
242
Something went wrong with that request. Please try again.