b979b5d7ec8e6ea489f05b4768edf659ce424edf
[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 != null
44 # assert repo.name == "nit"
45 #
46 # var user = api.load_user("Morriar")
47 # assert user != null
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 return user.load_from_github
153 end
154
155 # Get the Github repo with `full_name`.
156 #
157 # Returns `null` if the repo cannot be found.
158 #
159 # var api = new GithubAPI(get_github_oauth)
160 # var repo = api.load_repo("privat/nit")
161 # assert repo.name == "nit"
162 # assert repo.owner.login == "privat"
163 # assert repo.default_branch.name == "master"
164 fun load_repo(full_name: String): nullable Repo do
165 var repo = new Repo(self, full_name)
166 return repo.load_from_github
167 end
168
169 # Get the Github branch with `name`.
170 #
171 # Returns `null` if the branch cannot be found.
172 #
173 # var api = new GithubAPI(get_github_oauth)
174 # var repo = api.load_repo("privat/nit")
175 # assert repo != null
176 # var branch = api.load_branch(repo, "master")
177 # assert branch.name == "master"
178 # assert branch.commit isa Commit
179 fun load_branch(repo: Repo, name: String): nullable Branch do
180 var branch = new Branch(self, repo, name)
181 return branch.load_from_github
182 end
183
184 # Get the Github commit with `sha`.
185 #
186 # Returns `null` if the commit cannot be found.
187 #
188 # var api = new GithubAPI(get_github_oauth)
189 # var repo = api.load_repo("privat/nit")
190 # assert repo != null
191 # var commit = api.load_commit(repo, "64ce1f")
192 # assert commit isa Commit
193 fun load_commit(repo: Repo, sha: String): nullable Commit do
194 var commit = new Commit(self, repo, sha)
195 return commit.load_from_github
196 end
197
198 # Get the Github issue #`number`.
199 #
200 # Returns `null` if the issue cannot be found.
201 #
202 # var api = new GithubAPI(get_github_oauth)
203 # var repo = api.load_repo("privat/nit")
204 # assert repo != null
205 # var issue = api.load_issue(repo, 1)
206 # assert issue.title == "Doc"
207 fun load_issue(repo: Repo, number: Int): nullable Issue do
208 var issue = new Issue(self, repo, number)
209 return issue.load_from_github
210 end
211
212 # Get the Github pull request #`number`.
213 #
214 # Returns `null` if the pull request cannot be found.
215 #
216 # var api = new GithubAPI(get_github_oauth)
217 # var repo = api.load_repo("privat/nit")
218 # assert repo != null
219 # var pull = api.load_pull(repo, 1)
220 # assert pull.title == "Doc"
221 # assert pull.user.login == "Morriar"
222 fun load_pull(repo: Repo, number: Int): nullable PullRequest do
223 var pull = new PullRequest(self, repo, number)
224 return pull.load_from_github
225 end
226
227 # Get the Github label with `name`.
228 #
229 # Returns `null` if the label cannot be found.
230 #
231 # var api = new GithubAPI(get_github_oauth)
232 # var repo = api.load_repo("privat/nit")
233 # assert repo != null
234 # var labl = api.load_label(repo, "ok_will_merge")
235 # assert labl != null
236 fun load_label(repo: Repo, name: String): nullable Label do
237 var labl = new Label(self, repo, name)
238 return labl.load_from_github
239 end
240
241 # Get the Github milestone with `id`.
242 #
243 # Returns `null` if the milestone cannot be found.
244 #
245 # var api = new GithubAPI(get_github_oauth)
246 # var repo = api.load_repo("privat/nit")
247 # assert repo != null
248 # var stone = api.load_milestone(repo, 4)
249 # assert stone.title == "v1.0prealpha"
250 fun load_milestone(repo: Repo, id: Int): nullable Milestone do
251 var milestone = new Milestone(self, repo, id)
252 return milestone.load_from_github
253 end
254
255 # Get the Github issue event with `id`.
256 #
257 # Returns `null` if the event cannot be found.
258 #
259 # var api = new GithubAPI(get_github_oauth)
260 # var repo = api.load_repo("privat/nit")
261 # assert repo isa Repo
262 # var event = api.load_issue_event(repo, 199674194)
263 # assert event.actor.login == "privat"
264 # assert event.event == "labeled"
265 # assert event.labl.name == "need_review"
266 # assert event.issue.number == 945
267 fun load_issue_event(repo: Repo, id: Int): nullable IssueEvent do
268 var event = new IssueEvent(self, repo, id)
269 event.load_from_github
270 if was_error then return null
271 return event
272 end
273
274 # Get the Github commit comment with `id`.
275 #
276 # Returns `null` if the comment cannot be found.
277 #
278 # var api = new GithubAPI(get_github_oauth)
279 # var repo = api.load_repo("privat/nit")
280 # assert repo != null
281 # var comment = api.load_commit_comment(repo, 8982707)
282 # assert comment.user.login == "Morriar"
283 # assert comment.body == "For testing purposes..."
284 # assert comment.commit.sha == "7eacb86d1e24b7e72bc9ac869bf7182c0300ceca"
285 fun load_commit_comment(repo: Repo, id: Int): nullable CommitComment do
286 var comment = new CommitComment(self, repo, id)
287 return comment.load_from_github
288 end
289
290 # Get the Github issue comment with `id`.
291 #
292 # Returns `null` if the comment cannot be found.
293 #
294 # var api = new GithubAPI(get_github_oauth)
295 # var repo = api.load_repo("privat/nit")
296 # assert repo != null
297 # var comment = api.load_issue_comment(repo, 6020149)
298 # assert comment.user.login == "privat"
299 # assert comment.created_at.to_s == "2012-05-30T20:16:54Z"
300 # assert comment.issue.number == 10
301 fun load_issue_comment(repo: Repo, id: Int): nullable IssueComment do
302 var comment = new IssueComment(self, repo, id)
303 return comment.load_from_github
304 end
305
306 # Get the Github diff comment with `id`.
307 #
308 # Returns `null` if the comment cannot be found.
309 #
310 # var api = new GithubAPI(get_github_oauth)
311 # var repo = api.load_repo("privat/nit")
312 # assert repo != null
313 # var comment = api.load_review_comment(repo, 21010363)
314 # assert comment.path == "src/modelize/modelize_property.nit"
315 # assert comment.original_position == 26
316 # assert comment.pull.number == 945
317 fun load_review_comment(repo: Repo, id: Int): nullable ReviewComment do
318 var comment = new ReviewComment(self, repo, id)
319 return comment.load_from_github
320 end
321 end
322
323 # Something returned by the Github API.
324 #
325 # Mainly a Nit wrapper around a JSON objet.
326 abstract class GithubEntity
327
328 # Github API instance.
329 var api: GithubAPI
330
331 # FIXME constructor should be private
332
333 # Key used to access this entity from Github api base.
334 fun key: String is abstract
335
336 # JSON representation of `self`.
337 #
338 # This is the same json structure than used by Github API.
339 var json: JsonObject is noinit, protected writable
340
341 # Load `json` from Github API.
342 private fun load_from_github: nullable SELF do
343 json = api.load_from_github(key)
344 if api.was_error then return null
345 return self
346 end
347
348 redef fun to_s do return json.to_json
349 end
350
351 # A Github user.
352 #
353 # Should be accessed from `GithubAPI::load_user`.
354 #
355 # See <https://developer.github.com/v3/users/>.
356 class User
357 super GithubEntity
358
359 redef var key is lazy do return "users/{login}"
360
361 # Github login.
362 var login: String
363
364 # Init `self` from a `json` object.
365 init from_json(api: GithubAPI, json: JsonObject) do
366 init(api, json["login"].to_s)
367 self.json = json
368 end
369
370 # Github User page url.
371 fun html_url: String do return json["html_url"].to_s
372
373 # Avatar image url for this user.
374 fun avatar_url: String do return json["avatar_url"].to_s
375 end
376
377 # A Github repository.
378 #
379 # Should be accessed from `GithubAPI::load_repo`.
380 #
381 # See <https://developer.github.com/v3/repos/>.
382 class Repo
383 super GithubEntity
384
385 redef var key is lazy do return "repos/{full_name}"
386
387 # Repo full name on Github.
388 var full_name: String
389
390 # Init `self` from a `json` object.
391 init from_json(api: GithubAPI, json: JsonObject) do
392 init(api, json["full_name"].to_s)
393 self.json = json
394 end
395
396 # Repo short name on Github.
397 fun name: String do return json["name"].to_s
398
399 # Github User page url.
400 fun html_url: String do return json["html_url"].to_s
401
402 # Get the repo owner.
403 fun owner: User do
404 return new User.from_json(api, json["owner"].as(JsonObject))
405 end
406
407 # List of branches associated with their names.
408 fun branches: Map[String, Branch] do
409 api.message(1, "Get branches for {full_name}")
410 var array = api.get("repos/{full_name}/branches")
411 var res = new HashMap[String, Branch]
412 if not array isa JsonArray then return res
413 for obj in array do
414 if not obj isa JsonObject then continue
415 var name = obj["name"].to_s
416 res[name] = new Branch.from_json(api, self, obj)
417 end
418 return res
419 end
420
421 # List of issues associated with their ids.
422 fun issues: Map[Int, Issue] do
423 api.message(1, "Get issues for {full_name}")
424 var res = new HashMap[Int, Issue]
425 var issue = last_issue
426 if issue == null then return res
427 res[issue.number] = issue
428 while issue.number > 1 do
429 issue = api.load_issue(self, issue.number - 1)
430 assert issue isa Issue
431 res[issue.number] = issue
432 end
433 return res
434 end
435
436 # Get the last published issue.
437 fun last_issue: nullable Issue do
438 var array = api.get("repos/{full_name}/issues")
439 if not array isa JsonArray then return null
440 if array.is_empty then return null
441 var obj = array.first
442 if not obj isa JsonObject then return null
443 return new Issue.from_json(api, self, obj)
444 end
445
446 # List of labels associated with their names.
447 fun labels: Map[String, Label] do
448 api.message(1, "Get labels for {full_name}")
449 var array = api.get("repos/{full_name}/labels")
450 var res = new HashMap[String, Label]
451 if not array isa JsonArray then return res
452 for obj in array do
453 if not obj isa JsonObject then continue
454 var name = obj["name"].to_s
455 res[name] = new Label.from_json(api, self, obj)
456 end
457 return res
458 end
459
460 # List of milestones associated with their ids.
461 fun milestones: Map[Int, Milestone] do
462 api.message(1, "Get milestones for {full_name}")
463 var array = api.get("repos/{full_name}/milestones")
464 var res = new HashMap[Int, Milestone]
465 if array isa JsonArray then
466 for obj in array do
467 if not obj isa JsonObject then continue
468 var number = obj["number"].as(Int)
469 res[number] = new Milestone.from_json(api, self, obj)
470 end
471 end
472 return res
473 end
474
475 # List of pull-requests associated with their ids.
476 #
477 # Implementation notes: because PR numbers are not consecutive,
478 # PR are loaded from pages.
479 # See: https://developer.github.com/v3/pulls/#list-pull-requests
480 fun pulls: Map[Int, PullRequest] do
481 api.message(1, "Get pulls for {full_name}")
482 var res = new HashMap[Int, PullRequest]
483 var page = 1
484 var array = api.get("{key}/pulls?page={page}").as(JsonArray)
485 while not array.is_empty do
486 for obj in array do
487 if not obj isa JsonObject then continue
488 var number = obj["number"].as(Int)
489 res[number] = new PullRequest.from_json(api, self, obj)
490 end
491 page += 1
492 array = api.get("{key}/pulls?page={page}").as(JsonArray)
493 end
494 return res
495 end
496
497 # List of contributor related statistics.
498 fun contrib_stats: Array[ContributorStats] do
499 api.message(1, "Get contributor stats for {full_name}")
500 var res = new Array[ContributorStats]
501 var array = api.get("{key}/stats/contributors")
502 if array isa JsonArray then
503 for obj in array do
504 res.add new ContributorStats.from_json(api, obj.as(JsonObject))
505 end
506 end
507 return res
508 end
509
510 # Repo default branch.
511 fun default_branch: Branch do
512 var name = json["default_branch"].to_s
513 var branch = api.load_branch(self, name)
514 assert branch isa Branch
515 return branch
516 end
517 end
518
519 # A `RepoEntity` is something contained in a `Repo`.
520 abstract class RepoEntity
521 super GithubEntity
522
523 # Repo that contains `self`.
524 var repo: Repo
525
526 # Init `self` from a `json` object.
527 init from_json(api: GithubAPI, repo: Repo, json: JsonObject) do
528 self.api = api
529 self.repo = repo
530 self.json = json
531 end
532 end
533
534 # A Github branch.
535 #
536 # Should be accessed from `GithubAPI::load_branch`.
537 #
538 # See <https://developer.github.com/v3/repos/#list-branches>.
539 class Branch
540 super RepoEntity
541
542 redef var key is lazy do return "{repo.key}/branches/{name}"
543
544 # Branch name.
545 var name: String
546
547 redef init from_json(api, repo, json) do
548 self.name = json["name"].to_s
549 super
550 end
551
552 # Get the last commit of `self`.
553 fun commit: Commit do
554 return new Commit.from_json(api, repo, json["commit"].as(JsonObject))
555 end
556
557 # List all commits in `self`.
558 #
559 # This can be long depending on the branch size.
560 # Commit are returned in an unspecified order.
561 fun commits: Array[Commit] do
562 var res = new Array[Commit]
563 var done = new HashSet[String]
564 var todos = new Array[Commit]
565 todos.add commit
566 while not todos.is_empty do
567 var commit = todos.pop
568 if done.has(commit.sha) then continue
569 done.add commit.sha
570 res.add commit
571 for parent in commit.parents do
572 todos.add parent
573 end
574 end
575 return res
576 end
577 end
578
579 # A Github commit.
580 #
581 # Should be accessed from `GithubAPI::load_commit`.
582 #
583 # See <https://developer.github.com/v3/commits/>.
584 class Commit
585 super RepoEntity
586
587 redef var key is lazy do return "{repo.key}/commits/{sha}"
588
589 # Commit SHA.
590 var sha: String
591
592 redef init from_json(api, repo, json) do
593 self.sha = json["sha"].to_s
594 super
595 end
596
597 # Parent commits of `self`.
598 fun parents: Array[Commit] do
599 var res = new Array[Commit]
600 var parents = json["parents"]
601 if not parents isa JsonArray then return res
602 for obj in parents do
603 if not obj isa JsonObject then continue
604 res.add(api.load_commit(repo, obj["sha"].to_s).as(not null))
605 end
606 return res
607 end
608
609 # Author of the commit.
610 fun author: nullable User do
611 if not json.has_key("author") then return null
612 var user = json["author"]
613 if not user isa JsonObject then return null
614 return new User.from_json(api, user)
615 end
616
617 # Committer of the commit.
618 fun committer: nullable User do
619 if not json.has_key("committer") then return null
620 var user = json["author"]
621 if not user isa JsonObject then return null
622 return new User.from_json(api, user)
623 end
624
625 # Authoring date as ISODate.
626 fun author_date: ISODate do
627 var commit = json["commit"].as(JsonObject)
628 var author = commit["author"].as(JsonObject)
629 return new ISODate.from_string(author["date"].to_s)
630 end
631
632 # Commit date as ISODate.
633 fun commit_date: ISODate do
634 var commit = json["commit"].as(JsonObject)
635 var author = commit["committer"].as(JsonObject)
636 return new ISODate.from_string(author["date"].to_s)
637 end
638
639 # List files staged in this commit.
640 fun files: Array[GithubFile] do
641 var res = new Array[GithubFile]
642 var files = json["files"]
643 if not files isa JsonArray then return res
644 for obj in files do
645 res.add(new GithubFile(obj.as(JsonObject)))
646 end
647 return res
648 end
649
650 # Commit message.
651 fun message: String do return json["commit"].as(JsonObject)["message"].to_s
652 end
653
654 # A Github issue.
655 #
656 # Should be accessed from `GithubAPI::load_issue`.
657 #
658 # See <https://developer.github.com/v3/issues/>.
659 class Issue
660 super RepoEntity
661
662 redef var key is lazy do return "{repo.key}/issues/{number}"
663
664 # Issue Github ID.
665 var number: Int
666
667 redef init from_json(api, repo, json) do
668 self.number = json["number"].as(Int)
669 super
670 end
671
672 # Issue title.
673 fun title: String do return json["title"].to_s
674
675 # User that created this issue.
676 fun user: User do
677 return new User.from_json(api, json["user"].as(JsonObject))
678 end
679
680 # List of labels on this issue associated to their names.
681 fun labels: Map[String, Label] do
682 var res = new HashMap[String, Label]
683 for obj in json["labels"].as(JsonArray) do
684 if not obj isa JsonObject then continue
685 var name = obj["name"].to_s
686 res[name] = new Label.from_json(api, repo, obj)
687 end
688 return res
689 end
690
691 # State of the issue on Github.
692 fun state: String do return json["state"].to_s
693
694 # Is the issue locked?
695 fun locked: Bool do return json["locked"].as(Bool)
696
697 # Assigned `User` (if any).
698 fun assignee: nullable User do
699 var assignee = json["assignee"]
700 if not assignee isa JsonObject then return null
701 return new User.from_json(api, assignee)
702 end
703
704 # `Milestone` (if any).
705 fun milestone: nullable Milestone do
706 var milestone = json["milestone"]
707 if not milestone isa JsonObject then return null
708 return new Milestone.from_json(api, repo, milestone)
709 end
710
711 # List of comments made on this issue.
712 fun comments: Array[IssueComment] do
713 var res = new Array[IssueComment]
714 var count = comments_count
715 var page = 1
716 var array = api.get("{key}/comments?page={page}")
717 if not array isa JsonArray then
718 return res
719 end
720 while not array.is_empty and res.length < count do
721 for obj in array do
722 if not obj isa JsonObject then continue
723 var id = obj["id"].as(Int)
724 res.add(api.load_issue_comment(repo, id).as(not null))
725 end
726 page += 1
727 array = api.get("{key}/comments?page={page}").as(JsonArray)
728 end
729 return res
730 end
731
732 # Number of comments on this issue.
733 fun comments_count: Int do return json["comments"].to_s.to_i
734
735 # Creation time in ISODate format.
736 fun created_at: ISODate do
737 return new ISODate.from_string(json["created_at"].to_s)
738 end
739
740 # Last update time in ISODate format (if any).
741 fun updated_at: nullable ISODate do
742 var res = json["updated_at"]
743 if res == null then return null
744 return new ISODate.from_string(res.to_s)
745 end
746
747 # Close time in ISODate format (if any).
748 fun closed_at: nullable ISODate do
749 var res = json["closed_at"]
750 if res == null then return null
751 return new ISODate.from_string(res.to_s)
752 end
753
754 # TODO link to pull request
755
756 # Full description of the issue.
757 fun body: String do return json["body"].to_s
758
759 # List of events on this issue.
760 fun events: Array[IssueEvent] do
761 var res = new Array[IssueEvent]
762 var page = 1
763 var array = api.get("{key}/events?page={page}").as(JsonArray)
764 while not array.is_empty do
765 for obj in array do
766 if not obj isa JsonObject then continue
767 res.add new IssueEvent.from_json(api, repo, obj)
768 end
769 page += 1
770 array = api.get("{key}/events?page={page}").as(JsonArray)
771 end
772 return res
773 end
774
775 # User that closed this issue (if any).
776 fun closed_by: nullable User do
777 var closer = json["closed_by"]
778 if not closer isa JsonObject then return null
779 return new User.from_json(api, closer)
780 end
781 end
782
783 # A Github pull request.
784 #
785 # Should be accessed from `GithubAPI::load_pull`.
786 #
787 # PullRequest are basically Issues with more data.
788 # See <https://developer.github.com/v3/pulls/>.
789 class PullRequest
790 super Issue
791
792 redef var key is lazy do return "{repo.key}/pulls/{number}"
793
794 # Merge time in ISODate format (if any).
795 fun merged_at: nullable ISODate do
796 var res = json["merged_at"]
797 if res == null then return null
798 return new ISODate.from_string(res.to_s)
799 end
800
801 # Merge commit SHA.
802 fun merge_commit_sha: String do return json["merge_commit_sha"].to_s
803
804 # Count of comments made on the pull request diff.
805 fun review_comments: Int do return json["review_comments"].to_s.to_i
806
807 # Pull request head (can be a commit SHA or a branch name).
808 fun head: PullRef do
809 var json = json["head"].as(JsonObject)
810 return new PullRef(api, json)
811 end
812
813 # Pull request base (can be a commit SHA or a branch name).
814 fun base: PullRef do
815 var json = json["base"].as(JsonObject)
816 return new PullRef(api, json)
817 end
818
819 # Is this pull request merged?
820 fun merged: Bool do return json["merged"].as(Bool)
821
822 # Is this pull request mergeable?
823 fun mergeable: Bool do return json["mergeable"].as(Bool)
824
825 # Mergeable state of this pull request.
826 #
827 # See <https://developer.github.com/v3/pulls/#list-pull-requests>.
828 fun mergeable_state: Int do return json["mergeable_state"].to_s.to_i
829
830 # User that merged this pull request (if any).
831 fun merged_by: nullable User do
832 var merger = json["merged_by"]
833 if not merger isa JsonObject then return null
834 return new User.from_json(api, merger)
835 end
836
837 # Count of commits in this pull request.
838 fun commits: Int do return json["commits"].to_s.to_i
839
840 # Added line count.
841 fun additions: Int do return json["additions"].to_s.to_i
842
843 # Deleted line count.
844 fun deletions: Int do return json["deletions"].to_s.to_i
845
846 # Changed files count.
847 fun changed_files: Int do return json["changed_files"].to_s.to_i
848 end
849
850 # A pull request reference (used for head and base).
851 class PullRef
852
853 # Api instance that maintains self.
854 var api: GithubAPI
855
856 # JSON representation.
857 var json: JsonObject
858
859 # Label pointed by `self`.
860 fun labl: String do return json["label"].to_s
861
862 # Reference pointed by `self`.
863 fun ref: String do return json["ref"].to_s
864
865 # Commit SHA pointed by `self`.
866 fun sha: String do return json["sha"].to_s
867
868 # User pointed by `self`.
869 fun user: User do
870 return new User.from_json(api, json["user"].as(JsonObject))
871 end
872
873 # Repo pointed by `self`.
874 fun repo: Repo do
875 return new Repo.from_json(api, json["repo"].as(JsonObject))
876 end
877 end
878
879 # A Github label.
880 #
881 # Should be accessed from `GithubAPI::load_label`.
882 #
883 # See <https://developer.github.com/v3/issues/labels/>.
884 class Label
885 super RepoEntity
886
887 redef var key is lazy do return "{repo.key}/labels/{name}"
888
889 # Label name.
890 var name: String
891
892 redef init from_json(api, repo, json) do
893 self.name = json["name"].to_s
894 super
895 end
896
897 # Label color code.
898 fun color: String do return json["color"].to_s
899 end
900
901 # A Github milestone.
902 #
903 # Should be accessed from `GithubAPI::load_milestone`.
904 #
905 # See <https://developer.github.com/v3/issues/milestones/>.
906 class Milestone
907 super RepoEntity
908
909 redef var key is lazy do return "{repo.key}/milestones/{number}"
910
911 # The milestone id on Github.
912 var number: Int
913
914 redef init from_json(api, repo, json) do
915 super
916 self.number = json["number"].as(Int)
917 end
918
919 # Milestone title.
920 fun title: String do return json["title"].to_s
921
922 # Milestone long description.
923 fun description: String do return json["description"].to_s
924
925 # Count of opened issues linked to this milestone.
926 fun open_issues: Int do return json["open_issues"].to_s.to_i
927
928 # Count of closed issues linked to this milestone.
929 fun closed_issues: Int do return json["closed_issues"].to_s.to_i
930
931 # Milestone state.
932 fun state: String do return json["state"].to_s
933
934 # Creation time in ISODate format.
935 fun created_at: ISODate do
936 return new ISODate.from_string(json["created_at"].to_s)
937 end
938
939 # User that created this milestone.
940 fun creator: User do
941 return new User.from_json(api, json["creator"].as(JsonObject))
942 end
943
944 # Due time in ISODate format (if any).
945 fun due_on: nullable ISODate do
946 var res = json["updated_at"]
947 if res == null then return null
948 return new ISODate.from_string(res.to_s)
949 end
950
951 # Update time in ISODate format (if any).
952 fun updated_at: nullable ISODate do
953 var res = json["updated_at"]
954 if res == null then return null
955 return new ISODate.from_string(res.to_s)
956 end
957
958 # Close time in ISODate format (if any).
959 fun closed_at: nullable ISODate do
960 var res = json["closed_at"]
961 if res == null then return null
962 return new ISODate.from_string(res.to_s)
963 end
964 end
965
966 # A Github comment
967 #
968 # There is two kinds of comments:
969 #
970 # * `CommitComment` are made on a commit page.
971 # * `IssueComment` are made on an issue or pull request page.
972 # * `ReviewComment` are made on the diff associated to a pull request.
973 abstract class Comment
974 super RepoEntity
975
976 # Identifier of this comment.
977 var id: Int
978
979 redef init from_json(api, repo, json) do
980 self.id = json["id"].as(Int)
981 super
982 end
983
984 # User that made this comment.
985 fun user: User do
986 return new User.from_json(api, json["user"].as(JsonObject))
987 end
988
989 # Creation time in ISODate format.
990 fun created_at: ISODate do
991 return new ISODate.from_string(json["created_at"].to_s)
992 end
993
994 # Last update time in ISODate format (if any).
995 fun updated_at: nullable ISODate do
996 if not json.has_key("updated_at") then return null
997 return new ISODate.from_string(json["updated_at"].to_s)
998 end
999
1000 # Comment body text.
1001 fun body: String do return json["body"].to_s
1002 end
1003
1004 # A comment made on a commit.
1005 class CommitComment
1006 super Comment
1007
1008 redef var key is lazy do return "{repo.key}/comments/{id}"
1009
1010 # Commented commit.
1011 fun commit: Commit do
1012 return api.load_commit(repo, json["commit_id"].to_s).as(not null)
1013 end
1014
1015 # Position of the comment on the line.
1016 fun position: nullable String do
1017 if not json.has_key("position") then return null
1018 var res = json["position"]
1019 if res == null then return null
1020 return res.to_s
1021 end
1022
1023 # Line of the comment.
1024 fun line: nullable String do
1025 if not json.has_key("line") then return null
1026 var res = json["line"]
1027 if res == null then return null
1028 return res.to_s
1029 end
1030
1031 # Path of the commented file.
1032 fun path: String do return json["path"].to_s
1033 end
1034
1035 # Comments made on Github issue and pull request pages.
1036 #
1037 # Should be accessed from `GithubAPI::load_issue_comment`.
1038 #
1039 # See <https://developer.github.com/v3/issues/comments/>.
1040 class IssueComment
1041 super Comment
1042
1043 redef var key is lazy do return "{repo.key}/issues/comments/{id}"
1044
1045 # Issue that contains `self`.
1046 fun issue: Issue do
1047 var number = issue_url.split("/").last.to_i
1048 return api.load_issue(repo, number).as(not null)
1049 end
1050
1051 # Link to the issue document on API.
1052 fun issue_url: String do return json["issue_url"].to_s
1053 end
1054
1055 # Comments made on Github pull request diffs.
1056 #
1057 # Should be accessed from `GithubAPI::load_diff_comment`.
1058 #
1059 # See <https://developer.github.com/v3/pulls/comments/>.
1060 class ReviewComment
1061 super Comment
1062
1063 redef var key is lazy do return "{repo.key}/pulls/comments/{id}"
1064
1065 # Pull request that contains `self`.
1066 fun pull: PullRequest do
1067 var number = pull_request_url.split("/").last.to_i
1068 return api.load_pull(repo, number).as(not null)
1069 end
1070
1071 # Link to the pull request on API.
1072 fun pull_request_url: String do return json["pull_request_url"].to_s
1073
1074 # Diff hunk.
1075 fun diff_hunk: String do return json["diff_hunk"].to_s
1076
1077 # Path of commented file.
1078 fun path: String do return json["path"].to_s
1079
1080 # Position of the comment on the file.
1081 fun position: Int do return json["position"].to_s.to_i
1082
1083 # Original position in the diff.
1084 fun original_position: Int do return json["original_position"].to_s.to_i
1085
1086 # Commit referenced by this comment.
1087 fun commit_id: String do return json["commit_id"].to_s
1088
1089 # Original commit id.
1090 fun original_commit_id: String do return json["original_commit_id"].to_s
1091 end
1092
1093 # An event that occurs on a Github `Issue`.
1094 #
1095 # Should be accessed from `GithubAPI::load_issue_event`.
1096 #
1097 # See <https://developer.github.com/v3/issues/events/>.
1098 class IssueEvent
1099 super RepoEntity
1100
1101 redef var key is lazy do return "{repo.key}/issues/events/{id}"
1102
1103 # Event id on Github.
1104 var id: Int
1105
1106 redef init from_json(api, repo, json) do
1107 self.id = json["id"].as(Int)
1108 super
1109 end
1110
1111 # Issue that contains `self`.
1112 fun issue: Issue do
1113 return new Issue.from_json(api, repo, json["issue"].as(JsonObject))
1114 end
1115
1116 # User that initiated the event.
1117 fun actor: User do
1118 return new User.from_json(api, json["actor"].as(JsonObject))
1119 end
1120
1121 # Creation time in ISODate format.
1122 fun created_at: ISODate do
1123 return new ISODate.from_string(json["created_at"].to_s)
1124 end
1125
1126 # Event descriptor.
1127 fun event: String do return json["event"].to_s
1128
1129 # Commit linked to this event (if any).
1130 fun commit_id: nullable String do
1131 var res = json["commit_id"]
1132 if res == null then return null
1133 return res.to_s
1134 end
1135
1136 # Label linked to this event (if any).
1137 fun labl: nullable Label do
1138 var res = json["label"]
1139 if not res isa JsonObject then return null
1140 return new Label.from_json(api, repo, res)
1141 end
1142
1143 # User linked to this event (if any).
1144 fun assignee: nullable User do
1145 var res = json["assignee"]
1146 if not res isa JsonObject then return null
1147 return new User.from_json(api, res)
1148 end
1149
1150 # Milestone linked to this event (if any).
1151 fun milestone: nullable Milestone do
1152 var res = json["milestone"]
1153 if not res isa JsonObject then return null
1154 return new Milestone.from_json(api, repo, res)
1155 end
1156
1157 # Rename linked to this event (if any).
1158 fun rename: nullable RenameAction do
1159 var res = json["rename"]
1160 if res == null then return null
1161 return new RenameAction(res.as(JsonObject))
1162 end
1163 end
1164
1165 # A rename action maintains the name before and after a renaming action.
1166 class RenameAction
1167
1168 # JSON content.
1169 var json: JsonObject
1170
1171 # Name before renaming.
1172 fun from: String do return json["from"].to_s
1173
1174 # Name after renaming.
1175 fun to: String do return json["to"].to_s
1176 end
1177
1178 # Contributors list with additions, deletions, and commit counts.
1179 #
1180 # Should be accessed from `Repo::contrib_stats`.
1181 #
1182 # See <https://developer.github.com/v3/repos/statistics/>.
1183 class ContributorStats
1184 super Comparable
1185
1186 redef type OTHER: ContributorStats
1187
1188 # Github API client.
1189 var api: GithubAPI
1190
1191 # Json content.
1192 var json: JsonObject
1193
1194 # Init `self` from a `json` object.
1195 init from_json(api: GithubAPI, json: JsonObject) do
1196 self.api = api
1197 self.json = json
1198 end
1199
1200 # User these statistics are about.
1201 fun author: User do
1202 return new User.from_json(api, json["author"].as(JsonObject))
1203 end
1204
1205 # Total number of commit.
1206 fun total: Int do return json["total"].to_s.to_i
1207
1208 # Are of weeks of activity with detailed statistics.
1209 fun weeks: JsonArray do return json["weeks"].as(JsonArray)
1210
1211 # ContributorStats can be compared on the total amount of commits.
1212 redef fun <(o) do return total < o.total
1213 end
1214
1215 # A Github file representation.
1216 #
1217 # Mostly a wrapper around a json object.
1218 class GithubFile
1219
1220 # Json content.
1221 var json: JsonObject
1222
1223 # File name.
1224 fun filename: String do return json["filename"].to_s
1225 end