lib/github: handles branches
[nit.git] / lib / github / api.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Nit object oriented interface to Github api.
16 #
17 # This modules reifies Github API elements as Nit classes.
18 #
19 # For most use-cases you need to use the `GithubAPI` client.
20 module api
21
22 import github_curl
23
24 # Interface to Github REST API.
25 #
26 # Used by all `GithubEntity` to perform requests.
27 #
28 # Usage:
29 #
30 # ~~~
31 # # Get Github authentification token.
32 # var token = get_github_oauth
33 # assert not token.is_empty
34 #
35 # # Init the client.
36 # var api = new GithubAPI(token)
37 # ~~~
38 #
39 # The API client allows to get Github API entities:
40 #
41 # ~~~
42 # var repo = api.load_repo("privat/nit")
43 # assert repo isa Repo
44 # assert repo.name == "nit"
45 #
46 # var user = api.load_user("Morriar")
47 # assert user isa User
48 # assert user.login == "Morriar"
49 # ~~~
50 class GithubAPI
51
52 # Github API OAuth token.
53 #
54 # This token is used to authenticate the application on Github API.
55 # Be aware that there is [rate limits](https://developer.github.com/v3/rate_limit/)
56 # associated to the key.
57 var auth: String
58
59 # Github API base url.
60 #
61 # Default is `https://api.github.com` and should not be changed.
62 var api_url = "https://api.github.com"
63
64 # User agent used for HTTP requests.
65 #
66 # Default is `nit_github_api`.
67 #
68 # See <https://developer.github.com/v3/#user-agent-required>
69 var user_agent = "nit_github_api"
70
71 # Curl instance.
72 #
73 # Internal Curl instance used to perform API calls.
74 private var ghcurl: GithubCurl is noinit
75
76 # Verbosity level.
77 #
78 # * `0`: only errors (default)
79 # * `1`: verbose
80 var verbose_lvl = 0 is public writable
81
82 init do
83 ghcurl = new GithubCurl(auth, user_agent)
84 end
85
86 # Execute a GET request on Github API.
87 #
88 # This method returns raw json data.
89 # See other `load_*` methods to use more expressive types.
90 #
91 # var api = new GithubAPI(get_github_oauth)
92 # var obj = api.get("repos/privat/nit")
93 # assert obj isa JsonObject
94 # assert obj["name"] == "nit"
95 #
96 # Returns `null` in case of `error`.
97 #
98 # obj = api.get("foo/bar/baz")
99 # assert obj == null
100 # assert api.was_error
101 # var err = api.last_error
102 # assert err isa GithubError
103 # assert err.name == "GithubAPIError"
104 # assert err.message == "Not Found"
105 fun get(path: String): nullable Jsonable do
106 path = sanitize_uri(path)
107 var res = ghcurl.get_and_parse("{api_url}/{path}")
108 if res isa Error then
109 last_error = res
110 was_error = true
111 return null
112 end
113 was_error = false
114 return res
115 end
116
117 # Display a message depending on `verbose_lvl`.
118 fun message(lvl: Int, message: String) do
119 if lvl <= verbose_lvl then print message
120 end
121
122 # Escape `uri` in an acceptable format for Github.
123 private fun sanitize_uri(uri: String): String do
124 # TODO better URI escape.
125 return uri.replace(" ", "%20")
126 end
127
128 # Last error occured during Github API communications.
129 var last_error: nullable Error = null is public writable
130
131 # Does the last request provoqued an error?
132 var was_error = false is protected writable
133
134 # Load the json object from Github.
135 # See `GithubEntity::load_from_github`.
136 private fun load_from_github(key: String): JsonObject do
137 message(1, "Get {key} (github)")
138 var res = get(key)
139 if was_error then return new JsonObject
140 return res.as(JsonObject)
141 end
142
143 # Get the Github user with `login`.
144 #
145 # Returns `null` if the user cannot be found.
146 #
147 # var api = new GithubAPI(get_github_oauth)
148 # var user = api.load_user("Morriar")
149 # assert user.login == "Morriar"
150 fun load_user(login: String): nullable User do
151 var user = new User(self, login)
152 user.load_from_github
153 if was_error then return null
154 return user
155 end
156
157 # Get the Github repo with `full_name`.
158 #
159 # Returns `null` if the repo cannot be found.
160 #
161 # var api = new GithubAPI(get_github_oauth)
162 # var repo = api.load_repo("privat/nit")
163 # assert repo.name == "nit"
164 # assert repo.owner.login == "privat"
165 # assert repo.default_branch.name == "master"
166 fun load_repo(full_name: String): nullable Repo do
167 var repo = new Repo(self, full_name)
168 repo.load_from_github
169 if was_error then return null
170 return repo
171 end
172
173 # Get the Github branch with `name`.
174 #
175 # Returns `null` if the branch cannot be found.
176 #
177 # var api = new GithubAPI(get_github_oauth)
178 # var repo = api.load_repo("privat/nit")
179 # assert repo isa Repo
180 # var branch = api.load_branch(repo, "master")
181 # assert branch.name == "master"
182 fun load_branch(repo: Repo, name: String): nullable Branch do
183 var branch = new Branch(self, repo, name)
184 branch.load_from_github
185 if was_error then return null
186 return branch
187 end
188 end
189
190 # Something returned by the Github API.
191 #
192 # Mainly a Nit wrapper around a JSON objet.
193 abstract class GithubEntity
194
195 # Github API instance.
196 var api: GithubAPI
197
198 # FIXME constructor should be private
199
200 # Key used to access this entity from Github api base.
201 fun key: String is abstract
202
203 # JSON representation of `self`.
204 #
205 # This is the same json structure than used by Github API.
206 var json: JsonObject is noinit, protected writable
207
208 # Load `json` from Github API.
209 private fun load_from_github do
210 json = api.load_from_github(key)
211 end
212
213 redef fun to_s do return json.to_json
214 end
215
216 # A Github user.
217 #
218 # Should be accessed from `GithubAPI::load_user`.
219 #
220 # See <https://developer.github.com/v3/users/>.
221 class User
222 super GithubEntity
223
224 redef var key is lazy do return "users/{login}"
225
226 # Github login.
227 var login: String
228
229 # Init `self` from a `json` object.
230 init from_json(api: GithubAPI, json: JsonObject) do
231 init(api, json["login"].to_s)
232 self.json = json
233 end
234
235 # Github User page url.
236 fun html_url: String do return json["html_url"].to_s
237
238 # Avatar image url for this user.
239 fun avatar_url: String do return json["avatar_url"].to_s
240 end
241
242 # A Github repository.
243 #
244 # Should be accessed from `GithubAPI::load_repo`.
245 #
246 # See <https://developer.github.com/v3/repos/>.
247 class Repo
248 super GithubEntity
249
250 redef var key is lazy do return "repos/{full_name}"
251
252 # Repo full name on Github.
253 var full_name: String
254
255 # Init `self` from a `json` object.
256 init from_json(api: GithubAPI, json: JsonObject) do
257 init(api, json["full_name"].to_s)
258 self.json = json
259 end
260
261 # Repo short name on Github.
262 fun name: String do return json["name"].to_s
263
264 # Github User page url.
265 fun html_url: String do return json["html_url"].to_s
266
267 # Get the repo owner.
268 fun owner: User do
269 return new User.from_json(api, json["owner"].as(JsonObject))
270 end
271
272 # List of branches associated with their names.
273 fun branches: Map[String, Branch] do
274 api.message(1, "Get branches for {full_name}")
275 var array = api.get("repos/{full_name}/branches")
276 var res = new HashMap[String, Branch]
277 if not array isa JsonArray then return res
278 for obj in array do
279 if not obj isa JsonObject then continue
280 var name = obj["name"].to_s
281 res[name] = new Branch.from_json(api, self, obj)
282 end
283 return res
284 end
285
286 # Repo default branch.
287 fun default_branch: Branch do
288 var name = json["default_branch"].to_s
289 var branch = api.load_branch(self, name)
290 assert branch isa Branch
291 return branch
292 end
293 end
294
295 # A `RepoEntity` is something contained in a `Repo`.
296 abstract class RepoEntity
297 super GithubEntity
298
299 # Repo that contains `self`.
300 var repo: Repo
301
302 # Init `self` from a `json` object.
303 init from_json(api: GithubAPI, repo: Repo, json: JsonObject) do
304 self.api = api
305 self.repo = repo
306 self.json = json
307 end
308 end
309
310 # A Github branch.
311 #
312 # Should be accessed from `GithubAPI::load_branch`.
313 #
314 # See <https://developer.github.com/v3/repos/#list-branches>.
315 class Branch
316 super RepoEntity
317
318 redef var key is lazy do return "{repo.key}/branches/{name}"
319
320 # Branch name.
321 var name: String
322
323 redef init from_json(api, repo, json) do
324 self.name = json["name"].to_s
325 super
326 end
327 end