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