lib/github: handles github contributor stats
[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 # Commit message.
640 fun message: String do return json["commit"].as(JsonObject)["message"].to_s
641 end
642
643 # A Github issue.
644 #
645 # Should be accessed from `GithubAPI::load_issue`.
646 #
647 # See <https://developer.github.com/v3/issues/>.
648 class Issue
649 super RepoEntity
650
651 redef var key is lazy do return "{repo.key}/issues/{number}"
652
653 # Issue Github ID.
654 var number: Int
655
656 redef init from_json(api, repo, json) do
657 self.number = json["number"].as(Int)
658 super
659 end
660
661 # Issue title.
662 fun title: String do return json["title"].to_s
663
664 # User that created this issue.
665 fun user: User do
666 return new User.from_json(api, json["user"].as(JsonObject))
667 end
668
669 # List of labels on this issue associated to their names.
670 fun labels: Map[String, Label] do
671 var res = new HashMap[String, Label]
672 for obj in json["labels"].as(JsonArray) do
673 if not obj isa JsonObject then continue
674 var name = obj["name"].to_s
675 res[name] = new Label.from_json(api, repo, obj)
676 end
677 return res
678 end
679
680 # State of the issue on Github.
681 fun state: String do return json["state"].to_s
682
683 # Is the issue locked?
684 fun locked: Bool do return json["locked"].as(Bool)
685
686 # Assigned `User` (if any).
687 fun assignee: nullable User do
688 var assignee = json["assignee"]
689 if not assignee isa JsonObject then return null
690 return new User.from_json(api, assignee)
691 end
692
693 # `Milestone` (if any).
694 fun milestone: nullable Milestone do
695 var milestone = json["milestone"]
696 if not milestone isa JsonObject then return null
697 return new Milestone.from_json(api, repo, milestone)
698 end
699
700 # List of comments made on this issue.
701 fun comments: Array[IssueComment] do
702 var res = new Array[IssueComment]
703 var count = comments_count
704 var page = 1
705 var array = api.get("{key}/comments?page={page}")
706 if not array isa JsonArray then
707 return res
708 end
709 while not array.is_empty and res.length < count do
710 for obj in array do
711 if not obj isa JsonObject then continue
712 var id = obj["id"].as(Int)
713 res.add(api.load_issue_comment(repo, id).as(not null))
714 end
715 page += 1
716 array = api.get("{key}/comments?page={page}").as(JsonArray)
717 end
718 return res
719 end
720
721 # Number of comments on this issue.
722 fun comments_count: Int do return json["comments"].to_s.to_i
723
724 # Creation time in ISODate format.
725 fun created_at: ISODate do
726 return new ISODate.from_string(json["created_at"].to_s)
727 end
728
729 # Last update time in ISODate format (if any).
730 fun updated_at: nullable ISODate do
731 var res = json["updated_at"]
732 if res == null then return null
733 return new ISODate.from_string(res.to_s)
734 end
735
736 # Close time in ISODate format (if any).
737 fun closed_at: nullable ISODate do
738 var res = json["closed_at"]
739 if res == null then return null
740 return new ISODate.from_string(res.to_s)
741 end
742
743 # TODO link to pull request
744
745 # Full description of the issue.
746 fun body: String do return json["body"].to_s
747
748 # List of events on this issue.
749 fun events: Array[IssueEvent] do
750 var res = new Array[IssueEvent]
751 var page = 1
752 var array = api.get("{key}/events?page={page}").as(JsonArray)
753 while not array.is_empty do
754 for obj in array do
755 if not obj isa JsonObject then continue
756 res.add new IssueEvent.from_json(api, repo, obj)
757 end
758 page += 1
759 array = api.get("{key}/events?page={page}").as(JsonArray)
760 end
761 return res
762 end
763
764 # User that closed this issue (if any).
765 fun closed_by: nullable User do
766 var closer = json["closed_by"]
767 if not closer isa JsonObject then return null
768 return new User.from_json(api, closer)
769 end
770 end
771
772 # A Github pull request.
773 #
774 # Should be accessed from `GithubAPI::load_pull`.
775 #
776 # PullRequest are basically Issues with more data.
777 # See <https://developer.github.com/v3/pulls/>.
778 class PullRequest
779 super Issue
780
781 redef var key is lazy do return "{repo.key}/pulls/{number}"
782
783 # Merge time in ISODate format (if any).
784 fun merged_at: nullable ISODate do
785 var res = json["merged_at"]
786 if res == null then return null
787 return new ISODate.from_string(res.to_s)
788 end
789
790 # Merge commit SHA.
791 fun merge_commit_sha: String do return json["merge_commit_sha"].to_s
792
793 # Count of comments made on the pull request diff.
794 fun review_comments: Int do return json["review_comments"].to_s.to_i
795
796 # Pull request head (can be a commit SHA or a branch name).
797 fun head: PullRef do
798 var json = json["head"].as(JsonObject)
799 return new PullRef(api, json)
800 end
801
802 # Pull request base (can be a commit SHA or a branch name).
803 fun base: PullRef do
804 var json = json["base"].as(JsonObject)
805 return new PullRef(api, json)
806 end
807
808 # Is this pull request merged?
809 fun merged: Bool do return json["merged"].as(Bool)
810
811 # Is this pull request mergeable?
812 fun mergeable: Bool do return json["mergeable"].as(Bool)
813
814 # Mergeable state of this pull request.
815 #
816 # See <https://developer.github.com/v3/pulls/#list-pull-requests>.
817 fun mergeable_state: Int do return json["mergeable_state"].to_s.to_i
818
819 # User that merged this pull request (if any).
820 fun merged_by: nullable User do
821 var merger = json["merged_by"]
822 if not merger isa JsonObject then return null
823 return new User.from_json(api, merger)
824 end
825
826 # Count of commits in this pull request.
827 fun commits: Int do return json["commits"].to_s.to_i
828
829 # Added line count.
830 fun additions: Int do return json["additions"].to_s.to_i
831
832 # Deleted line count.
833 fun deletions: Int do return json["deletions"].to_s.to_i
834
835 # Changed files count.
836 fun changed_files: Int do return json["changed_files"].to_s.to_i
837 end
838
839 # A pull request reference (used for head and base).
840 class PullRef
841
842 # Api instance that maintains self.
843 var api: GithubAPI
844
845 # JSON representation.
846 var json: JsonObject
847
848 # Label pointed by `self`.
849 fun labl: String do return json["label"].to_s
850
851 # Reference pointed by `self`.
852 fun ref: String do return json["ref"].to_s
853
854 # Commit SHA pointed by `self`.
855 fun sha: String do return json["sha"].to_s
856
857 # User pointed by `self`.
858 fun user: User do
859 return new User.from_json(api, json["user"].as(JsonObject))
860 end
861
862 # Repo pointed by `self`.
863 fun repo: Repo do
864 return new Repo.from_json(api, json["repo"].as(JsonObject))
865 end
866 end
867
868 # A Github label.
869 #
870 # Should be accessed from `GithubAPI::load_label`.
871 #
872 # See <https://developer.github.com/v3/issues/labels/>.
873 class Label
874 super RepoEntity
875
876 redef var key is lazy do return "{repo.key}/labels/{name}"
877
878 # Label name.
879 var name: String
880
881 redef init from_json(api, repo, json) do
882 self.name = json["name"].to_s
883 super
884 end
885
886 # Label color code.
887 fun color: String do return json["color"].to_s
888 end
889
890 # A Github milestone.
891 #
892 # Should be accessed from `GithubAPI::load_milestone`.
893 #
894 # See <https://developer.github.com/v3/issues/milestones/>.
895 class Milestone
896 super RepoEntity
897
898 redef var key is lazy do return "{repo.key}/milestones/{number}"
899
900 # The milestone id on Github.
901 var number: Int
902
903 redef init from_json(api, repo, json) do
904 super
905 self.number = json["number"].as(Int)
906 end
907
908 # Milestone title.
909 fun title: String do return json["title"].to_s
910
911 # Milestone long description.
912 fun description: String do return json["description"].to_s
913
914 # Count of opened issues linked to this milestone.
915 fun open_issues: Int do return json["open_issues"].to_s.to_i
916
917 # Count of closed issues linked to this milestone.
918 fun closed_issues: Int do return json["closed_issues"].to_s.to_i
919
920 # Milestone state.
921 fun state: String do return json["state"].to_s
922
923 # Creation time in ISODate format.
924 fun created_at: ISODate do
925 return new ISODate.from_string(json["created_at"].to_s)
926 end
927
928 # User that created this milestone.
929 fun creator: User do
930 return new User.from_json(api, json["creator"].as(JsonObject))
931 end
932
933 # Due time in ISODate format (if any).
934 fun due_on: nullable ISODate do
935 var res = json["updated_at"]
936 if res == null then return null
937 return new ISODate.from_string(res.to_s)
938 end
939
940 # Update time in ISODate format (if any).
941 fun updated_at: nullable ISODate do
942 var res = json["updated_at"]
943 if res == null then return null
944 return new ISODate.from_string(res.to_s)
945 end
946
947 # Close time in ISODate format (if any).
948 fun closed_at: nullable ISODate do
949 var res = json["closed_at"]
950 if res == null then return null
951 return new ISODate.from_string(res.to_s)
952 end
953 end
954
955 # A Github comment
956 #
957 # There is two kinds of comments:
958 #
959 # * `CommitComment` are made on a commit page.
960 # * `IssueComment` are made on an issue or pull request page.
961 # * `ReviewComment` are made on the diff associated to a pull request.
962 abstract class Comment
963 super RepoEntity
964
965 # Identifier of this comment.
966 var id: Int
967
968 redef init from_json(api, repo, json) do
969 self.id = json["id"].as(Int)
970 super
971 end
972
973 # User that made this comment.
974 fun user: User do
975 return new User.from_json(api, json["user"].as(JsonObject))
976 end
977
978 # Creation time in ISODate format.
979 fun created_at: ISODate do
980 return new ISODate.from_string(json["created_at"].to_s)
981 end
982
983 # Last update time in ISODate format (if any).
984 fun updated_at: nullable ISODate do
985 if not json.has_key("updated_at") then return null
986 return new ISODate.from_string(json["updated_at"].to_s)
987 end
988
989 # Comment body text.
990 fun body: String do return json["body"].to_s
991 end
992
993 # A comment made on a commit.
994 class CommitComment
995 super Comment
996
997 redef var key is lazy do return "{repo.key}/comments/{id}"
998
999 # Commented commit.
1000 fun commit: Commit do
1001 return api.load_commit(repo, json["commit_id"].to_s).as(not null)
1002 end
1003
1004 # Position of the comment on the line.
1005 fun position: nullable String do
1006 if not json.has_key("position") then return null
1007 var res = json["position"]
1008 if res == null then return null
1009 return res.to_s
1010 end
1011
1012 # Line of the comment.
1013 fun line: nullable String do
1014 if not json.has_key("line") then return null
1015 var res = json["line"]
1016 if res == null then return null
1017 return res.to_s
1018 end
1019
1020 # Path of the commented file.
1021 fun path: String do return json["path"].to_s
1022 end
1023
1024 # Comments made on Github issue and pull request pages.
1025 #
1026 # Should be accessed from `GithubAPI::load_issue_comment`.
1027 #
1028 # See <https://developer.github.com/v3/issues/comments/>.
1029 class IssueComment
1030 super Comment
1031
1032 redef var key is lazy do return "{repo.key}/issues/comments/{id}"
1033
1034 # Issue that contains `self`.
1035 fun issue: Issue do
1036 var number = issue_url.split("/").last.to_i
1037 return api.load_issue(repo, number).as(not null)
1038 end
1039
1040 # Link to the issue document on API.
1041 fun issue_url: String do return json["issue_url"].to_s
1042 end
1043
1044 # Comments made on Github pull request diffs.
1045 #
1046 # Should be accessed from `GithubAPI::load_diff_comment`.
1047 #
1048 # See <https://developer.github.com/v3/pulls/comments/>.
1049 class ReviewComment
1050 super Comment
1051
1052 redef var key is lazy do return "{repo.key}/pulls/comments/{id}"
1053
1054 # Pull request that contains `self`.
1055 fun pull: PullRequest do
1056 var number = pull_request_url.split("/").last.to_i
1057 return api.load_pull(repo, number).as(not null)
1058 end
1059
1060 # Link to the pull request on API.
1061 fun pull_request_url: String do return json["pull_request_url"].to_s
1062
1063 # Diff hunk.
1064 fun diff_hunk: String do return json["diff_hunk"].to_s
1065
1066 # Path of commented file.
1067 fun path: String do return json["path"].to_s
1068
1069 # Position of the comment on the file.
1070 fun position: Int do return json["position"].to_s.to_i
1071
1072 # Original position in the diff.
1073 fun original_position: Int do return json["original_position"].to_s.to_i
1074
1075 # Commit referenced by this comment.
1076 fun commit_id: String do return json["commit_id"].to_s
1077
1078 # Original commit id.
1079 fun original_commit_id: String do return json["original_commit_id"].to_s
1080 end
1081
1082 # An event that occurs on a Github `Issue`.
1083 #
1084 # Should be accessed from `GithubAPI::load_issue_event`.
1085 #
1086 # See <https://developer.github.com/v3/issues/events/>.
1087 class IssueEvent
1088 super RepoEntity
1089
1090 redef var key is lazy do return "{repo.key}/issues/events/{id}"
1091
1092 # Event id on Github.
1093 var id: Int
1094
1095 redef init from_json(api, repo, json) do
1096 self.id = json["id"].as(Int)
1097 super
1098 end
1099
1100 # Issue that contains `self`.
1101 fun issue: Issue do
1102 return new Issue.from_json(api, repo, json["issue"].as(JsonObject))
1103 end
1104
1105 # User that initiated the event.
1106 fun actor: User do
1107 return new User.from_json(api, json["actor"].as(JsonObject))
1108 end
1109
1110 # Creation time in ISODate format.
1111 fun created_at: ISODate do
1112 return new ISODate.from_string(json["created_at"].to_s)
1113 end
1114
1115 # Event descriptor.
1116 fun event: String do return json["event"].to_s
1117
1118 # Commit linked to this event (if any).
1119 fun commit_id: nullable String do
1120 var res = json["commit_id"]
1121 if res == null then return null
1122 return res.to_s
1123 end
1124
1125 # Label linked to this event (if any).
1126 fun labl: nullable Label do
1127 var res = json["label"]
1128 if not res isa JsonObject then return null
1129 return new Label.from_json(api, repo, res)
1130 end
1131
1132 # User linked to this event (if any).
1133 fun assignee: nullable User do
1134 var res = json["assignee"]
1135 if not res isa JsonObject then return null
1136 return new User.from_json(api, res)
1137 end
1138
1139 # Milestone linked to this event (if any).
1140 fun milestone: nullable Milestone do
1141 var res = json["milestone"]
1142 if not res isa JsonObject then return null
1143 return new Milestone.from_json(api, repo, res)
1144 end
1145
1146 # Rename linked to this event (if any).
1147 fun rename: nullable RenameAction do
1148 var res = json["rename"]
1149 if res == null then return null
1150 return new RenameAction(res.as(JsonObject))
1151 end
1152 end
1153
1154 # A rename action maintains the name before and after a renaming action.
1155 class RenameAction
1156
1157 # JSON content.
1158 var json: JsonObject
1159
1160 # Name before renaming.
1161 fun from: String do return json["from"].to_s
1162
1163 # Name after renaming.
1164 fun to: String do return json["to"].to_s
1165 end
1166
1167 # Contributors list with additions, deletions, and commit counts.
1168 #
1169 # Should be accessed from `Repo::contrib_stats`.
1170 #
1171 # See <https://developer.github.com/v3/repos/statistics/>.
1172 class ContributorStats
1173 super Comparable
1174
1175 redef type OTHER: ContributorStats
1176
1177 # Github API client.
1178 var api: GithubAPI
1179
1180 # Json content.
1181 var json: JsonObject
1182
1183 # Init `self` from a `json` object.
1184 init from_json(api: GithubAPI, json: JsonObject) do
1185 self.api = api
1186 self.json = json
1187 end
1188
1189 # User these statistics are about.
1190 fun author: User do
1191 return new User.from_json(api, json["author"].as(JsonObject))
1192 end
1193
1194 # Total number of commit.
1195 fun total: Int do return json["total"].to_s.to_i
1196
1197 # Are of weeks of activity with detailed statistics.
1198 fun weeks: JsonArray do return json["weeks"].as(JsonArray)
1199
1200 # ContributorStats can be compared on the total amount of commits.
1201 redef fun <(o) do return total < o.total
1202 end