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