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