lib/github: handles github issue events
[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 # Repo default branch.
498 fun default_branch: Branch do
499 var name = json["default_branch"].to_s
500 var branch = api.load_branch(self, name)
501 assert branch isa Branch
502 return branch
503 end
504 end
505
506 # A `RepoEntity` is something contained in a `Repo`.
507 abstract class RepoEntity
508 super GithubEntity
509
510 # Repo that contains `self`.
511 var repo: Repo
512
513 # Init `self` from a `json` object.
514 init from_json(api: GithubAPI, repo: Repo, json: JsonObject) do
515 self.api = api
516 self.repo = repo
517 self.json = json
518 end
519 end
520
521 # A Github branch.
522 #
523 # Should be accessed from `GithubAPI::load_branch`.
524 #
525 # See <https://developer.github.com/v3/repos/#list-branches>.
526 class Branch
527 super RepoEntity
528
529 redef var key is lazy do return "{repo.key}/branches/{name}"
530
531 # Branch name.
532 var name: String
533
534 redef init from_json(api, repo, json) do
535 self.name = json["name"].to_s
536 super
537 end
538
539 # Get the last commit of `self`.
540 fun commit: Commit do
541 return new Commit.from_json(api, repo, json["commit"].as(JsonObject))
542 end
543
544 # List all commits in `self`.
545 #
546 # This can be long depending on the branch size.
547 # Commit are returned in an unspecified order.
548 fun commits: Array[Commit] do
549 var res = new Array[Commit]
550 var done = new HashSet[String]
551 var todos = new Array[Commit]
552 todos.add commit
553 while not todos.is_empty do
554 var commit = todos.pop
555 if done.has(commit.sha) then continue
556 done.add commit.sha
557 res.add commit
558 for parent in commit.parents do
559 todos.add parent
560 end
561 end
562 return res
563 end
564 end
565
566 # A Github commit.
567 #
568 # Should be accessed from `GithubAPI::load_commit`.
569 #
570 # See <https://developer.github.com/v3/commits/>.
571 class Commit
572 super RepoEntity
573
574 redef var key is lazy do return "{repo.key}/commits/{sha}"
575
576 # Commit SHA.
577 var sha: String
578
579 redef init from_json(api, repo, json) do
580 self.sha = json["sha"].to_s
581 super
582 end
583
584 # Parent commits of `self`.
585 fun parents: Array[Commit] do
586 var res = new Array[Commit]
587 var parents = json["parents"]
588 if not parents isa JsonArray then return res
589 for obj in parents do
590 if not obj isa JsonObject then continue
591 res.add(api.load_commit(repo, obj["sha"].to_s).as(not null))
592 end
593 return res
594 end
595
596 # Author of the commit.
597 fun author: nullable User do
598 if not json.has_key("author") then return null
599 var user = json["author"]
600 if not user isa JsonObject then return null
601 return new User.from_json(api, user)
602 end
603
604 # Committer of the commit.
605 fun committer: nullable User do
606 if not json.has_key("committer") then return null
607 var user = json["author"]
608 if not user isa JsonObject then return null
609 return new User.from_json(api, user)
610 end
611
612 # Authoring date as ISODate.
613 fun author_date: ISODate do
614 var commit = json["commit"].as(JsonObject)
615 var author = commit["author"].as(JsonObject)
616 return new ISODate.from_string(author["date"].to_s)
617 end
618
619 # Commit date as ISODate.
620 fun commit_date: ISODate do
621 var commit = json["commit"].as(JsonObject)
622 var author = commit["committer"].as(JsonObject)
623 return new ISODate.from_string(author["date"].to_s)
624 end
625
626 # Commit message.
627 fun message: String do return json["commit"].as(JsonObject)["message"].to_s
628 end
629
630 # A Github issue.
631 #
632 # Should be accessed from `GithubAPI::load_issue`.
633 #
634 # See <https://developer.github.com/v3/issues/>.
635 class Issue
636 super RepoEntity
637
638 redef var key is lazy do return "{repo.key}/issues/{number}"
639
640 # Issue Github ID.
641 var number: Int
642
643 redef init from_json(api, repo, json) do
644 self.number = json["number"].as(Int)
645 super
646 end
647
648 # Issue title.
649 fun title: String do return json["title"].to_s
650
651 # User that created this issue.
652 fun user: User do
653 return new User.from_json(api, json["user"].as(JsonObject))
654 end
655
656 # List of labels on this issue associated to their names.
657 fun labels: Map[String, Label] do
658 var res = new HashMap[String, Label]
659 for obj in json["labels"].as(JsonArray) do
660 if not obj isa JsonObject then continue
661 var name = obj["name"].to_s
662 res[name] = new Label.from_json(api, repo, obj)
663 end
664 return res
665 end
666
667 # State of the issue on Github.
668 fun state: String do return json["state"].to_s
669
670 # Is the issue locked?
671 fun locked: Bool do return json["locked"].as(Bool)
672
673 # Assigned `User` (if any).
674 fun assignee: nullable User do
675 var assignee = json["assignee"]
676 if not assignee isa JsonObject then return null
677 return new User.from_json(api, assignee)
678 end
679
680 # `Milestone` (if any).
681 fun milestone: nullable Milestone do
682 var milestone = json["milestone"]
683 if not milestone isa JsonObject then return null
684 return new Milestone.from_json(api, repo, milestone)
685 end
686
687 # List of comments made on this issue.
688 fun comments: Array[IssueComment] do
689 var res = new Array[IssueComment]
690 var count = comments_count
691 var page = 1
692 var array = api.get("{key}/comments?page={page}")
693 if not array isa JsonArray then
694 return res
695 end
696 while not array.is_empty and res.length < count do
697 for obj in array do
698 if not obj isa JsonObject then continue
699 var id = obj["id"].as(Int)
700 res.add(api.load_issue_comment(repo, id).as(not null))
701 end
702 page += 1
703 array = api.get("{key}/comments?page={page}").as(JsonArray)
704 end
705 return res
706 end
707
708 # Number of comments on this issue.
709 fun comments_count: Int do return json["comments"].to_s.to_i
710
711 # Creation time in ISODate format.
712 fun created_at: ISODate do
713 return new ISODate.from_string(json["created_at"].to_s)
714 end
715
716 # Last update time in ISODate format (if any).
717 fun updated_at: nullable ISODate do
718 var res = json["updated_at"]
719 if res == null then return null
720 return new ISODate.from_string(res.to_s)
721 end
722
723 # Close time in ISODate format (if any).
724 fun closed_at: nullable ISODate do
725 var res = json["closed_at"]
726 if res == null then return null
727 return new ISODate.from_string(res.to_s)
728 end
729
730 # TODO link to pull request
731
732 # Full description of the issue.
733 fun body: String do return json["body"].to_s
734
735 # List of events on this issue.
736 fun events: Array[IssueEvent] do
737 var res = new Array[IssueEvent]
738 var page = 1
739 var array = api.get("{key}/events?page={page}").as(JsonArray)
740 while not array.is_empty do
741 for obj in array do
742 if not obj isa JsonObject then continue
743 res.add new IssueEvent.from_json(api, repo, obj)
744 end
745 page += 1
746 array = api.get("{key}/events?page={page}").as(JsonArray)
747 end
748 return res
749 end
750
751 # User that closed this issue (if any).
752 fun closed_by: nullable User do
753 var closer = json["closed_by"]
754 if not closer isa JsonObject then return null
755 return new User.from_json(api, closer)
756 end
757 end
758
759 # A Github pull request.
760 #
761 # Should be accessed from `GithubAPI::load_pull`.
762 #
763 # PullRequest are basically Issues with more data.
764 # See <https://developer.github.com/v3/pulls/>.
765 class PullRequest
766 super Issue
767
768 redef var key is lazy do return "{repo.key}/pulls/{number}"
769
770 # Merge time in ISODate format (if any).
771 fun merged_at: nullable ISODate do
772 var res = json["merged_at"]
773 if res == null then return null
774 return new ISODate.from_string(res.to_s)
775 end
776
777 # Merge commit SHA.
778 fun merge_commit_sha: String do return json["merge_commit_sha"].to_s
779
780 # Count of comments made on the pull request diff.
781 fun review_comments: Int do return json["review_comments"].to_s.to_i
782
783 # Pull request head (can be a commit SHA or a branch name).
784 fun head: PullRef do
785 var json = json["head"].as(JsonObject)
786 return new PullRef(api, json)
787 end
788
789 # Pull request base (can be a commit SHA or a branch name).
790 fun base: PullRef do
791 var json = json["base"].as(JsonObject)
792 return new PullRef(api, json)
793 end
794
795 # Is this pull request merged?
796 fun merged: Bool do return json["merged"].as(Bool)
797
798 # Is this pull request mergeable?
799 fun mergeable: Bool do return json["mergeable"].as(Bool)
800
801 # Mergeable state of this pull request.
802 #
803 # See <https://developer.github.com/v3/pulls/#list-pull-requests>.
804 fun mergeable_state: Int do return json["mergeable_state"].to_s.to_i
805
806 # User that merged this pull request (if any).
807 fun merged_by: nullable User do
808 var merger = json["merged_by"]
809 if not merger isa JsonObject then return null
810 return new User.from_json(api, merger)
811 end
812
813 # Count of commits in this pull request.
814 fun commits: Int do return json["commits"].to_s.to_i
815
816 # Added line count.
817 fun additions: Int do return json["additions"].to_s.to_i
818
819 # Deleted line count.
820 fun deletions: Int do return json["deletions"].to_s.to_i
821
822 # Changed files count.
823 fun changed_files: Int do return json["changed_files"].to_s.to_i
824 end
825
826 # A pull request reference (used for head and base).
827 class PullRef
828
829 # Api instance that maintains self.
830 var api: GithubAPI
831
832 # JSON representation.
833 var json: JsonObject
834
835 # Label pointed by `self`.
836 fun labl: String do return json["label"].to_s
837
838 # Reference pointed by `self`.
839 fun ref: String do return json["ref"].to_s
840
841 # Commit SHA pointed by `self`.
842 fun sha: String do return json["sha"].to_s
843
844 # User pointed by `self`.
845 fun user: User do
846 return new User.from_json(api, json["user"].as(JsonObject))
847 end
848
849 # Repo pointed by `self`.
850 fun repo: Repo do
851 return new Repo.from_json(api, json["repo"].as(JsonObject))
852 end
853 end
854
855 # A Github label.
856 #
857 # Should be accessed from `GithubAPI::load_label`.
858 #
859 # See <https://developer.github.com/v3/issues/labels/>.
860 class Label
861 super RepoEntity
862
863 redef var key is lazy do return "{repo.key}/labels/{name}"
864
865 # Label name.
866 var name: String
867
868 redef init from_json(api, repo, json) do
869 self.name = json["name"].to_s
870 super
871 end
872
873 # Label color code.
874 fun color: String do return json["color"].to_s
875 end
876
877 # A Github milestone.
878 #
879 # Should be accessed from `GithubAPI::load_milestone`.
880 #
881 # See <https://developer.github.com/v3/issues/milestones/>.
882 class Milestone
883 super RepoEntity
884
885 redef var key is lazy do return "{repo.key}/milestones/{number}"
886
887 # The milestone id on Github.
888 var number: Int
889
890 redef init from_json(api, repo, json) do
891 super
892 self.number = json["number"].as(Int)
893 end
894
895 # Milestone title.
896 fun title: String do return json["title"].to_s
897
898 # Milestone long description.
899 fun description: String do return json["description"].to_s
900
901 # Count of opened issues linked to this milestone.
902 fun open_issues: Int do return json["open_issues"].to_s.to_i
903
904 # Count of closed issues linked to this milestone.
905 fun closed_issues: Int do return json["closed_issues"].to_s.to_i
906
907 # Milestone state.
908 fun state: String do return json["state"].to_s
909
910 # Creation time in ISODate format.
911 fun created_at: ISODate do
912 return new ISODate.from_string(json["created_at"].to_s)
913 end
914
915 # User that created this milestone.
916 fun creator: User do
917 return new User.from_json(api, json["creator"].as(JsonObject))
918 end
919
920 # Due time in ISODate format (if any).
921 fun due_on: nullable ISODate do
922 var res = json["updated_at"]
923 if res == null then return null
924 return new ISODate.from_string(res.to_s)
925 end
926
927 # Update time in ISODate format (if any).
928 fun updated_at: nullable ISODate do
929 var res = json["updated_at"]
930 if res == null then return null
931 return new ISODate.from_string(res.to_s)
932 end
933
934 # Close time in ISODate format (if any).
935 fun closed_at: nullable ISODate do
936 var res = json["closed_at"]
937 if res == null then return null
938 return new ISODate.from_string(res.to_s)
939 end
940 end
941
942 # A Github comment
943 #
944 # There is two kinds of comments:
945 #
946 # * `CommitComment` are made on a commit page.
947 # * `IssueComment` are made on an issue or pull request page.
948 # * `ReviewComment` are made on the diff associated to a pull request.
949 abstract class Comment
950 super RepoEntity
951
952 # Identifier of this comment.
953 var id: Int
954
955 redef init from_json(api, repo, json) do
956 self.id = json["id"].as(Int)
957 super
958 end
959
960 # User that made this comment.
961 fun user: User do
962 return new User.from_json(api, json["user"].as(JsonObject))
963 end
964
965 # Creation time in ISODate format.
966 fun created_at: ISODate do
967 return new ISODate.from_string(json["created_at"].to_s)
968 end
969
970 # Last update time in ISODate format (if any).
971 fun updated_at: nullable ISODate do
972 if not json.has_key("updated_at") then return null
973 return new ISODate.from_string(json["updated_at"].to_s)
974 end
975
976 # Comment body text.
977 fun body: String do return json["body"].to_s
978 end
979
980 # A comment made on a commit.
981 class CommitComment
982 super Comment
983
984 redef var key is lazy do return "{repo.key}/comments/{id}"
985
986 # Commented commit.
987 fun commit: Commit do
988 return api.load_commit(repo, json["commit_id"].to_s).as(not null)
989 end
990
991 # Position of the comment on the line.
992 fun position: nullable String do
993 if not json.has_key("position") then return null
994 var res = json["position"]
995 if res == null then return null
996 return res.to_s
997 end
998
999 # Line of the comment.
1000 fun line: nullable String do
1001 if not json.has_key("line") then return null
1002 var res = json["line"]
1003 if res == null then return null
1004 return res.to_s
1005 end
1006
1007 # Path of the commented file.
1008 fun path: String do return json["path"].to_s
1009 end
1010
1011 # Comments made on Github issue and pull request pages.
1012 #
1013 # Should be accessed from `GithubAPI::load_issue_comment`.
1014 #
1015 # See <https://developer.github.com/v3/issues/comments/>.
1016 class IssueComment
1017 super Comment
1018
1019 redef var key is lazy do return "{repo.key}/issues/comments/{id}"
1020
1021 # Issue that contains `self`.
1022 fun issue: Issue do
1023 var number = issue_url.split("/").last.to_i
1024 return api.load_issue(repo, number).as(not null)
1025 end
1026
1027 # Link to the issue document on API.
1028 fun issue_url: String do return json["issue_url"].to_s
1029 end
1030
1031 # Comments made on Github pull request diffs.
1032 #
1033 # Should be accessed from `GithubAPI::load_diff_comment`.
1034 #
1035 # See <https://developer.github.com/v3/pulls/comments/>.
1036 class ReviewComment
1037 super Comment
1038
1039 redef var key is lazy do return "{repo.key}/pulls/comments/{id}"
1040
1041 # Pull request that contains `self`.
1042 fun pull: PullRequest do
1043 var number = pull_request_url.split("/").last.to_i
1044 return api.load_pull(repo, number).as(not null)
1045 end
1046
1047 # Link to the pull request on API.
1048 fun pull_request_url: String do return json["pull_request_url"].to_s
1049
1050 # Diff hunk.
1051 fun diff_hunk: String do return json["diff_hunk"].to_s
1052
1053 # Path of commented file.
1054 fun path: String do return json["path"].to_s
1055
1056 # Position of the comment on the file.
1057 fun position: Int do return json["position"].to_s.to_i
1058
1059 # Original position in the diff.
1060 fun original_position: Int do return json["original_position"].to_s.to_i
1061
1062 # Commit referenced by this comment.
1063 fun commit_id: String do return json["commit_id"].to_s
1064
1065 # Original commit id.
1066 fun original_commit_id: String do return json["original_commit_id"].to_s
1067 end
1068
1069 # An event that occurs on a Github `Issue`.
1070 #
1071 # Should be accessed from `GithubAPI::load_issue_event`.
1072 #
1073 # See <https://developer.github.com/v3/issues/events/>.
1074 class IssueEvent
1075 super RepoEntity
1076
1077 redef var key is lazy do return "{repo.key}/issues/events/{id}"
1078
1079 # Event id on Github.
1080 var id: Int
1081
1082 redef init from_json(api, repo, json) do
1083 self.id = json["id"].as(Int)
1084 super
1085 end
1086
1087 # Issue that contains `self`.
1088 fun issue: Issue do
1089 return new Issue.from_json(api, repo, json["issue"].as(JsonObject))
1090 end
1091
1092 # User that initiated the event.
1093 fun actor: User do
1094 return new User.from_json(api, json["actor"].as(JsonObject))
1095 end
1096
1097 # Creation time in ISODate format.
1098 fun created_at: ISODate do
1099 return new ISODate.from_string(json["created_at"].to_s)
1100 end
1101
1102 # Event descriptor.
1103 fun event: String do return json["event"].to_s
1104
1105 # Commit linked to this event (if any).
1106 fun commit_id: nullable String do
1107 var res = json["commit_id"]
1108 if res == null then return null
1109 return res.to_s
1110 end
1111
1112 # Label linked to this event (if any).
1113 fun labl: nullable Label do
1114 var res = json["label"]
1115 if not res isa JsonObject then return null
1116 return new Label.from_json(api, repo, res)
1117 end
1118
1119 # User linked to this event (if any).
1120 fun assignee: nullable User do
1121 var res = json["assignee"]
1122 if not res isa JsonObject then return null
1123 return new User.from_json(api, res)
1124 end
1125
1126 # Milestone linked to this event (if any).
1127 fun milestone: nullable Milestone do
1128 var res = json["milestone"]
1129 if not res isa JsonObject then return null
1130 return new Milestone.from_json(api, repo, res)
1131 end
1132
1133 # Rename linked to this event (if any).
1134 fun rename: nullable RenameAction do
1135 var res = json["rename"]
1136 if res == null then return null
1137 return new RenameAction(res.as(JsonObject))
1138 end
1139 end
1140
1141 # A rename action maintains the name before and after a renaming action.
1142 class RenameAction
1143
1144 # JSON content.
1145 var json: JsonObject
1146
1147 # Name before renaming.
1148 fun from: String do return json["from"].to_s
1149
1150 # Name after renaming.
1151 fun to: String do return json["to"].to_s
1152 end