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