ci: do not error when nothing with nitunit_some
[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](https://developer.github.com/v3/).
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 intrude import json::serialization_read
24
25 # Client to Github API
26 #
27 # To access the API you need an instance of a `GithubAPI` client.
28 #
29 # ~~~nitish
30 # # Get Github authentification token.
31 # var token = get_github_oauth
32 # assert not token.is_empty
33 #
34 # # Init the client.
35 # var api = new GithubAPI(token)
36 # ~~~
37 #
38 # The API client allows you to get Github API entities.
39 #
40 # ~~~nitish
41 # var repo = api.load_repo("nitlang/nit")
42 # assert repo != null
43 # assert repo.name == "nit"
44 #
45 # var user = api.load_user("Morriar")
46 # assert user != null
47 # assert user.login == "Morriar"
48 # ~~~
49 class GithubAPI
50
51 # Github API OAuth token
52 #
53 # To access your private ressources, you must
54 # [authenticate](https://developer.github.com/guides/basics-of-authentication/).
55 #
56 # For client applications, Github recommands to use the
57 # [OAuth tokens](https://developer.github.com/v3/oauth/) authentification method.
58 #
59 #
60 #
61 # Be aware that there is [rate limits](https://developer.github.com/v3/rate_limit/)
62 # associated to the key.
63 var auth: String
64
65 # Github API base url.
66 #
67 # Default is `https://api.github.com` and should not be changed.
68 var api_url = "https://api.github.com"
69
70 # User agent used for HTTP requests.
71 #
72 # Default is `nit_github_api`.
73 #
74 # See <https://developer.github.com/v3/#user-agent-required>
75 var user_agent = "nit_github_api"
76
77 # Curl instance.
78 #
79 # Internal Curl instance used to perform API calls.
80 private var ghcurl: GithubCurl is noinit
81
82 # Verbosity level.
83 #
84 # * `0`: only errors (default)
85 # * `1`: verbose
86 var verbose_lvl = 0 is public writable
87
88 init do
89 ghcurl = new GithubCurl(auth, user_agent)
90 end
91
92 # Deserialize an object
93 fun deserialize(string: String): nullable Object do
94 var deserializer = new GithubDeserializer(string)
95 var res = deserializer.deserialize
96 # print deserializer.errors.join("\n") # DEBUG
97 return res
98 end
99
100 # Execute a GET request on Github API.
101 #
102 # This method returns raw json data.
103 # See other `load_*` methods to use more expressive types.
104 #
105 # ~~~nitish
106 # var api = new GithubAPI(get_github_oauth)
107 # var obj = api.get("/repos/nitlang/nit")
108 # assert obj isa JsonObject
109 # assert obj["name"] == "nit"
110 # ~~~
111 #
112 # Returns `null` in case of `error`.
113 #
114 # ~~~nitish
115 # obj = api.get("/foo/bar/baz")
116 # assert obj == null
117 # assert api.was_error
118 # var err = api.last_error
119 # assert err isa GithubError
120 # assert err.name == "GithubAPIError"
121 # assert err.message == "Not Found"
122 # ~~~
123 fun get(path: String): nullable Serializable do
124 path = sanitize_uri(path)
125 var res = ghcurl.get_and_parse("{api_url}{path}")
126 if res isa Error then
127 last_error = res
128 was_error = true
129 return null
130 end
131 was_error = false
132 return res
133 end
134
135 # Display a message depending on `verbose_lvl`.
136 fun message(lvl: Int, message: String) do
137 if lvl <= verbose_lvl then print message
138 end
139
140 # Escape `uri` in an acceptable format for Github.
141 private fun sanitize_uri(uri: String): String do
142 # TODO better URI escape.
143 return uri.replace(" ", "%20")
144 end
145
146 # Last error occured during Github API communications.
147 var last_error: nullable Error = null is public writable
148
149 # Does the last request provoqued an error?
150 var was_error = false is protected writable
151
152 # Load the json object from Github.
153 # See `GithubEntity::load_from_github`.
154 protected fun load_from_github(key: String): nullable GithubEntity do
155 message(1, "Get {key} (github)")
156 var res = get(key)
157 if was_error then return null
158 return deserialize(res.as(JsonObject).to_json).as(nullable GithubEntity)
159 end
160
161 # Get the Github logged user from `auth` token.
162 #
163 # Loads the `User` from the API or returns `null` if the user cannot be found.
164 #
165 # ~~~nitish
166 # var api = new GithubAPI(get_github_oauth)
167 # var user = api.load_auth_user
168 # assert user.login == "Morriar"
169 # ~~~
170 fun load_auth_user: nullable User do
171 var user = load_from_github("/user")
172 if was_error then return null
173 return user.as(nullable User)
174 end
175
176 # Get the Github user with `login`
177 #
178 # Loads the `User` from the API or returns `null` if the user cannot be found.
179 #
180 # ~~~nitish
181 # var api = new GithubAPI(get_github_oauth)
182 # var user = api.load_user("Morriar")
183 # print user or else "null"
184 # assert user.login == "Morriar"
185 # ~~~
186 fun load_user(login: String): nullable User do
187 return load_from_github("/users/{login}").as(nullable User)
188 end
189
190 # Get the Github repo with `full_name`.
191 #
192 # Loads the `Repo` from the API or returns `null` if the repo cannot be found.
193 #
194 # ~~~nitish
195 # var api = new GithubAPI(get_github_oauth)
196 # var repo = api.load_repo("nitlang/nit")
197 # assert repo.name == "nit"
198 # assert repo.owner.login == "nitlang"
199 # assert repo.default_branch == "master"
200 # ~~~
201 fun load_repo(full_name: String): nullable Repo do
202 return load_from_github("/repos/{full_name}").as(nullable Repo)
203 end
204
205 # List of branches associated with their names.
206 fun load_repo_branches(repo: Repo): Array[Branch] do
207 message(1, "Get branches for {repo.full_name}")
208 var array = get("/repos/{repo.full_name}/branches")
209 var res = new Array[Branch]
210 if not array isa JsonArray then return res
211 var deser = deserialize(array.to_json)
212 if not deser isa Array[Object] then return res # empty array
213 for branch in deser do
214 if not branch isa Branch then continue
215 res.add branch
216 end
217 return res
218 end
219
220 # List of issues associated with their ids.
221 fun load_repo_issues(repo: Repo): Array[Issue] do
222 message(1, "Get issues for {repo.full_name}")
223 var res = new Array[Issue]
224 var issue = load_repo_last_issue(repo)
225 if issue == null then return res
226 res.add issue
227 while issue != null and issue.number > 1 do
228 issue = load_issue(repo, issue.number - 1)
229 if issue == null then continue
230 res.add issue
231 end
232 return res
233 end
234
235 # Search issues in this repo form an advanced query.
236 #
237 # Example:
238 #
239 # ~~~nitish
240 # var issues = repo.search_issues("is:open label:need_review")
241 # ~~~
242 #
243 # See <https://developer.github.com/v3/search/#search-issues>.
244 fun search_repo_issues(repo: Repo, query: String): Array[Issue] do
245 query = "/search/issues?q={query} repo:{repo.full_name}"
246 var res = new Array[Issue]
247 var response = get(query)
248 if was_error then return res
249 var arr = response.as(JsonObject)["items"].as(JsonArray)
250 return deserialize(arr.to_json).as(Array[Issue])
251 end
252
253 # Get the last published issue.
254 fun load_repo_last_issue(repo: Repo): nullable Issue do
255 var array = get("/repos/{repo.full_name}/issues")
256 if not array isa JsonArray then return null
257 if array.is_empty then return null
258 var obj = array.first
259 if not obj isa JsonObject then return null
260 return deserialize(obj.to_json).as(nullable Issue)
261 end
262
263 # List of labels associated with their names.
264 fun load_repo_labels(repo: Repo): Array[Label] do
265 message(1, "Get labels for {repo.full_name}")
266 var array = get("repos/{repo.full_name}/labels")
267 if not array isa JsonArray then return new Array[Label]
268 return deserialize(array.to_json).as(Array[Label])
269 end
270
271 # List of milestones associated with their ids.
272 fun load_repo_milestones(repo: Repo): Array[Milestone] do
273 message(1, "Get milestones for {repo.full_name}")
274 var array = get("/repos/{repo.full_name}/milestones")
275 if not array isa JsonArray then return new Array[Milestone]
276 return deserialize(array.to_json).as(Array[Milestone])
277 end
278
279 # List of pull-requests associated with their ids.
280 #
281 # Implementation notes: because PR numbers are not consecutive,
282 # PR are loaded from pages.
283 # See: https://developer.github.com/v3/pulls/#list-pull-requests
284 fun load_repo_pulls(repo: Repo): Array[PullRequest] do
285 message(1, "Get pulls for {repo.full_name}")
286 var key = "/repos/{repo.full_name}"
287 var res = new Array[PullRequest]
288 var page = 1
289 loop
290 var array = get("{key}/pulls?page={page}").as(JsonArray)
291 if array.is_empty then break
292 for obj in array do
293 if not obj isa JsonObject then continue
294 var pr = deserialize(array.to_json).as(nullable PullRequest)
295 if pr == null then continue
296 res.add pr
297 end
298 page += 1
299 end
300 return res
301 end
302
303 # List of contributor related statistics.
304 fun load_repo_contrib_stats(repo: Repo): Array[ContributorStats] do
305 message(1, "Get contributor stats for {repo.full_name}")
306 var res = new Array[ContributorStats]
307 var array = get("/repos/{repo.full_name}/stats/contributors")
308 if not array isa JsonArray then return res
309 return deserialize(array.to_json).as(Array[ContributorStats])
310 end
311
312 # Get the Github branch with `name`.
313 #
314 # Returns `null` if the branch cannot be found.
315 #
316 # ~~~nitish
317 # var api = new GithubAPI(get_github_oauth)
318 # var repo = api.load_repo("nitlang/nit")
319 # assert repo != null
320 # var branch = api.load_branch(repo, "master")
321 # assert branch.name == "master"
322 # assert branch.commit isa Commit
323 # ~~~
324 fun load_branch(repo: Repo, name: String): nullable Branch do
325 return load_from_github("/repos/{repo.full_name}/branches/{name}").as(nullable Branch)
326 end
327
328 # List all commits in `self`.
329 #
330 # This can be long depending on the branch size.
331 # Commit are returned in an unspecified order.
332 fun load_branch_commits(branch: Branch): Array[Commit] do
333 var res = new Array[Commit]
334 var done = new HashSet[String]
335 var todos = new Array[Commit]
336 todos.add branch.commit
337 loop
338 if todos.is_empty then break
339 var commit = todos.pop
340 if done.has(commit.sha) then continue
341 done.add commit.sha
342 res.add commit
343 var parents = commit.parents
344 if parents == null then continue
345 for parent in parents do
346 todos.add parent
347 end
348 end
349 return res
350 end
351
352 # Get the Github commit with `sha`.
353 #
354 # Returns `null` if the commit cannot be found.
355 #
356 # ~~~nitish
357 # var api = new GithubAPI(get_github_oauth)
358 # var repo = api.load_repo("nitlang/nit")
359 # assert repo != null
360 # var commit = api.load_commit(repo, "64ce1f")
361 # assert commit isa Commit
362 # ~~~
363 fun load_commit(repo: Repo, sha: String): nullable Commit do
364 return load_from_github("/repos/{repo.full_name}/commits/{sha}").as(nullable Commit)
365 end
366
367 # Get the Github issue #`number`.
368 #
369 # Returns `null` if the issue cannot be found.
370 #
371 # ~~~nitish
372 # var api = new GithubAPI(get_github_oauth)
373 # var repo = api.load_repo("nitlang/nit")
374 # assert repo != null
375 # var issue = api.load_issue(repo, 1)
376 # assert issue.title == "Doc"
377 # ~~~
378 fun load_issue(repo: Repo, number: Int): nullable Issue do
379 return load_from_github("/repos/{repo.full_name}/issues/{number}").as(nullable Issue)
380 end
381
382 # List of event on this issue.
383 fun load_issue_comments(repo: Repo, issue: Issue): Array[IssueComment] do
384 var res = new Array[IssueComment]
385 var count = issue.comments or else 0
386 var page = 1
387 loop
388 var array = get("/repos/{repo.full_name}/issues/{issue.number}/comments?page={page}")
389 if not array isa JsonArray then break
390 if array.is_empty then break
391 for obj in array do
392 if not obj isa JsonObject then continue
393 var id = obj["id"].as(Int)
394 var comment = load_issue_comment(repo, id)
395 if comment == null then continue
396 res.add(comment)
397 end
398 if res.length >= count then break
399 page += 1
400 end
401 return res
402 end
403
404 # List of events on this issue.
405 fun load_issue_events(repo: Repo, issue: Issue): Array[IssueEvent] do
406 var res = new Array[IssueEvent]
407 var key = "/repos/{repo.full_name}/issues/{issue.number}"
408 var page = 1
409 loop
410 var array = get("{key}/events?page={page}")
411 if not array isa JsonArray or array.is_empty then break
412 for obj in array do
413 if not obj isa JsonObject then continue
414 var event = deserialize(obj.to_json).as(nullable IssueEvent)
415 if event == null then continue
416 res.add event
417 end
418 page += 1
419 end
420 return res
421 end
422
423 # Get the Github pull request #`number`.
424 #
425 # Returns `null` if the pull request cannot be found.
426 #
427 # ~~~nitish
428 # var api = new GithubAPI(get_github_oauth)
429 # var repo = api.load_repo("nitlang/nit")
430 # assert repo != null
431 # var pull = api.load_pull(repo, 1)
432 # assert pull.title == "Doc"
433 # assert pull.user.login == "Morriar"
434 # ~~~
435 fun load_pull(repo: Repo, number: Int): nullable PullRequest do
436 return load_from_github("/repos/{repo.full_name}/pulls/{number}").as(nullable PullRequest)
437 end
438
439 # Get the Github label with `name`.
440 #
441 # Returns `null` if the label cannot be found.
442 #
443 # ~~~nitish
444 # var api = new GithubAPI(get_github_oauth)
445 # var repo = api.load_repo("nitlang/nit")
446 # assert repo != null
447 # var labl = api.load_label(repo, "ok_will_merge")
448 # assert labl != null
449 # ~~~
450 fun load_label(repo: Repo, name: String): nullable Label do
451 return load_from_github("/repos/{repo.full_name}/labels/{name}").as(nullable Label)
452 end
453
454 # Get the Github milestone with `id`.
455 #
456 # Returns `null` if the milestone cannot be found.
457 #
458 # ~~~nitish
459 # var api = new GithubAPI(get_github_oauth)
460 # var repo = api.load_repo("nitlang/nit")
461 # assert repo != null
462 # var stone = api.load_milestone(repo, 4)
463 # assert stone.title == "v1.0prealpha"
464 # ~~~
465 fun load_milestone(repo: Repo, id: Int): nullable Milestone do
466 return load_from_github("/repos/{repo.full_name}/milestones/{id}").as(nullable Milestone)
467 end
468
469 # Get the Github issue event with `id`.
470 #
471 # Returns `null` if the event cannot be found.
472 #
473 # ~~~nitish
474 # var api = new GithubAPI(get_github_oauth)
475 # var repo = api.load_repo("nitlang/nit")
476 # assert repo isa Repo
477 # var event = api.load_issue_event(repo, 199674194)
478 # assert event isa IssueEvent
479 # assert event.actor.login == "privat"
480 # assert event.event == "labeled"
481 # assert event.labl isa Label
482 # assert event.labl.name == "need_review"
483 # ~~~
484 fun load_issue_event(repo: Repo, id: Int): nullable IssueEvent do
485 return load_from_github("/repos/{repo.full_name}/issues/events/{id}").as(nullable IssueEvent)
486 end
487
488 # Get the Github commit comment with `id`.
489 #
490 # Returns `null` if the comment cannot be found.
491 #
492 # ~~~nitish
493 # var api = new GithubAPI(get_github_oauth)
494 # var repo = api.load_repo("nitlang/nit")
495 # assert repo != null
496 # var comment = api.load_commit_comment(repo, 8982707)
497 # assert comment.user.login == "Morriar"
498 # assert comment.body == "For testing purposes...\n"
499 # assert comment.commit_id == "7eacb86d1e24b7e72bc9ac869bf7182c0300ceca"
500 # ~~~
501 fun load_commit_comment(repo: Repo, id: Int): nullable CommitComment do
502 return load_from_github("/repos/{repo.full_name}/comments/{id}").as(nullable CommitComment)
503 end
504
505 # Get the Github issue comment with `id`.
506 #
507 # Returns `null` if the comment cannot be found.
508 #
509 # ~~~nitish
510 # var api = new GithubAPI(get_github_oauth)
511 # var repo = api.load_repo("nitlang/nit")
512 # assert repo != null
513 # var comment = api.load_issue_comment(repo, 6020149)
514 # assert comment.user.login == "privat"
515 # assert comment.created_at.to_s == "2012-05-30T20:16:54Z"
516 # assert comment.issue_number == 10
517 # ~~~
518 fun load_issue_comment(repo: Repo, id: Int): nullable IssueComment do
519 return load_from_github("/repos/{repo.full_name}/issues/comments/{id}").as(nullable IssueComment)
520 end
521
522 # Get the Github diff comment with `id`.
523 #
524 # Returns `null` if the comment cannot be found.
525 #
526 # ~~~nitish
527 # var api = new GithubAPI(get_github_oauth)
528 # var repo = api.load_repo("nitlang/nit")
529 # assert repo != null
530 # var comment = api.load_review_comment(repo, 21010363)
531 # assert comment.path == "src/modelize/modelize_property.nit"
532 # assert comment.original_position == 26
533 # assert comment.pull_number == 945
534 # ~~~
535 fun load_review_comment(repo: Repo, id: Int): nullable ReviewComment do
536 return load_from_github("/repos/{repo.full_name}/pulls/comments/{id}").as(nullable ReviewComment)
537 end
538 end
539
540 # Something returned by the Github API.
541 #
542 # Mainly a Nit wrapper around a JSON objet.
543 abstract class GithubEntity
544 serialize
545
546 # Github page url.
547 var html_url: nullable String is writable
548 end
549
550 # A Github user
551 #
552 # Provides access to [Github user data](https://developer.github.com/v3/users/).
553 # Should be accessed from `GithubAPI::load_user`.
554 class User
555 super GitUser
556 serialize
557
558 # Github login.
559 var login: String is writable
560
561 # Avatar image url for this user.
562 var avatar_url: nullable String is writable
563
564 # User public name if any.
565 var name: nullable String is writable
566
567 # User public email if any.
568 var email: nullable String is writable
569
570 # User public blog if any.
571 var blog: nullable String is writable
572 end
573
574 # A Github repository.
575 #
576 # Provides access to [Github repo data](https://developer.github.com/v3/repos/).
577 # Should be accessed from `GithubAPI::load_repo`.
578 class Repo
579 super GithubEntity
580 serialize
581
582 # Repo full name on Github.
583 var full_name: String is writable
584
585 # Repo short name on Github.
586 var name: String is writable
587
588 # Get the repo owner.
589 var owner: User is writable
590
591 # Repo default branch name.
592 var default_branch: String is writable
593 end
594
595 # A Github branch.
596 #
597 # Should be accessed from `GithubAPI::load_branch`.
598 #
599 # See <https://developer.github.com/v3/repos/#list-branches>.
600 class Branch
601 super GithubEntity
602 serialize
603
604 # Branch name.
605 var name: String is writable
606
607 # Get the last commit of `self`.
608 var commit: Commit is writable
609 end
610
611 # A Github commit.
612 #
613 # Should be accessed from `GithubAPI::load_commit`.
614 #
615 # See <https://developer.github.com/v3/repos/commits/>.
616 class Commit
617 super GithubEntity
618 serialize
619
620 # Commit SHA.
621 var sha: String is writable
622
623 # Parent commits of `self`.
624 var parents: nullable Array[Commit] = null is writable
625
626 # Author of the commit.
627 var author: nullable GitUser is writable
628
629 # Committer of the commit.
630 var committer: nullable GitUser is writable
631
632 # Authoring date as String.
633 var author_date: nullable String is writable
634
635 # Authoring date as ISODate.
636 fun iso_author_date: nullable ISODate do
637 var author_date = self.author_date
638 if author_date == null then return null
639 return new ISODate.from_string(author_date)
640 end
641
642 # Commit date as String.
643 var commit_date: nullable String is writable
644
645 # Commit date as ISODate.
646 fun iso_commit_date: nullable ISODate do
647 var commit_date = self.commit_date
648 if commit_date == null then return null
649 return new ISODate.from_string(commit_date)
650 end
651
652 # List files staged in this commit.
653 var files: nullable Array[GithubFile] = null is optional, writable
654
655 # Commit message.
656 var message: nullable String is writable
657
658 # Git commit representation linked to this commit.
659 var commit: nullable GitCommit
660 end
661
662 # A Git Commit representation
663 class GitCommit
664 super GithubEntity
665 serialize
666
667 # Commit SHA.
668 var sha: nullable String is writable
669
670 # Parent commits of `self`.
671 var parents: nullable Array[GitCommit] = null is writable
672
673 # Author of the commit.
674 var author: nullable GitUser is writable
675
676 # Committer of the commit.
677 var committer: nullable GitUser is writable
678
679 # Commit message.
680 var message: nullable String is writable
681 end
682
683 # Git user authoring data
684 class GitUser
685 super GithubEntity
686 serialize
687
688 # Authoring date.
689 var date: nullable String = null is writable
690
691 # Authoring date as ISODate.
692 fun iso_date: nullable ISODate do
693 var date = self.date
694 if date == null then return null
695 return new ISODate.from_string(date)
696 end
697 end
698
699 # A Github issue.
700 #
701 # Should be accessed from `GithubAPI::load_issue`.
702 #
703 # See <https://developer.github.com/v3/issues/>.
704 class Issue
705 super GithubEntity
706 serialize
707
708 # Issue Github ID.
709 var number: Int is writable
710
711 # Issue id.
712 var id: nullable Int is writable
713
714 # Issue title.
715 var title: String is writable
716
717 # User that created this issue.
718 var user: nullable User is writable
719
720 # List of labels on this issue associated to their names.
721 var labels: nullable Array[Label] is writable
722
723 # State of the issue on Github.
724 var state: String is writable
725
726 # Is the issue locked?
727 var locked: nullable Bool is writable
728
729 # Assigned `User` (if any).
730 var assignee: nullable User is writable
731
732 # `Milestone` (if any).
733 var milestone: nullable Milestone is writable
734
735 # Number of comments on this issue.
736 var comments: nullable Int is writable
737
738 # Creation time as String.
739 var created_at: String is writable
740
741 # Creation time as ISODate.
742 fun iso_created_at: ISODate do
743 return new ISODate.from_string(created_at)
744 end
745
746 # Last update time as String (if any).
747 var updated_at: nullable String is writable
748
749 # Last update date as ISODate.
750 fun iso_updated_at: nullable ISODate do
751 var updated_at = self.updated_at
752 if updated_at == null then return null
753 return new ISODate.from_string(updated_at)
754 end
755
756 # Close time as String (if any).
757 var closed_at: nullable String is writable
758
759 # Close time as ISODate.
760 fun iso_closed_at: nullable ISODate do
761 var closed_at = self.closed_at
762 if closed_at == null then return null
763 return new ISODate.from_string(closed_at)
764 end
765
766 # Full description of the issue.
767 var body: nullable String is writable
768
769 # User that closed this issue (if any).
770 var closed_by: nullable User is writable
771
772 # Is this issue linked to a pull request?
773 var is_pull_request: Bool = false is writable
774 end
775
776 # A Github pull request.
777 #
778 # Should be accessed from `GithubAPI::load_pull`.
779 #
780 # PullRequest are basically Issues with more data.
781 # See <https://developer.github.com/v3/pulls/>.
782 class PullRequest
783 super Issue
784 serialize
785
786 # Merge time as String (if any).
787 var merged_at: nullable String is writable
788
789 # Merge time as ISODate.
790 fun iso_merged_at: nullable ISODate do
791 var merged_at = self.merged_at
792 if merged_at == null then return null
793 return new ISODate.from_string(merged_at)
794 end
795
796 # Merge commit SHA.
797 var merge_commit_sha: nullable String is writable
798
799 # Count of comments made on the pull request diff.
800 var review_comments: Int is writable
801
802 # Pull request head (can be a commit SHA or a branch name).
803 var head: PullRef is writable
804
805 # Pull request base (can be a commit SHA or a branch name).
806 var base: PullRef is writable
807
808 # Is this pull request merged?
809 var merged: Bool is writable
810
811 # Is this pull request mergeable?
812 var mergeable: nullable Bool is writable
813
814 # Mergeable state of this pull request.
815 #
816 # See <https://developer.github.com/v3/pulls/#list-pull-requests>.
817 var mergeable_state: String is writable
818
819 # User that merged this pull request (if any).
820 var merged_by: nullable User is writable
821
822 # Count of commits in this pull request.
823 var commits: Int is writable
824
825 # Added line count.
826 var additions: Int is writable
827
828 # Deleted line count.
829 var deletions: Int is writable
830
831 # Changed files count.
832 var changed_files: Int is writable
833
834 # URL to patch file
835 var patch_url: nullable String is writable
836 end
837
838 # A pull request reference (used for head and base).
839 class PullRef
840 serialize
841
842 # Label pointed by `self`.
843 var labl: String is writable, serialize_as("label")
844
845 # Reference pointed by `self`.
846 var ref: String is writable
847
848 # Commit SHA pointed by `self`.
849 var sha: String is writable
850
851 # User pointed by `self`.
852 var user: User is writable
853
854 # Repo pointed by `self` (if any).
855 #
856 # A `null` value means the `repo` was deleted.
857 var repo: nullable Repo is writable
858 end
859
860 # A Github label.
861 #
862 # Should be accessed from `GithubAPI::load_label`.
863 #
864 # See <https://developer.github.com/v3/issues/labels/>.
865 class Label
866 super GithubEntity
867 serialize
868
869 # Label name.
870 var name: String is writable
871
872 # Label color code.
873 var color: String is writable
874 end
875
876 # A Github milestone.
877 #
878 # Should be accessed from `GithubAPI::load_milestone`.
879 #
880 # See <https://developer.github.com/v3/issues/milestones/>.
881 class Milestone
882 super GithubEntity
883 serialize
884
885 # The milestone id on Github.
886 var number: nullable Int = null is writable
887
888 # Milestone title.
889 var title: String is writable
890
891 # Milestone long description.
892 var description: nullable String is writable
893
894 # Count of opened issues linked to this milestone.
895 var open_issues: nullable Int = null is writable
896
897 # Count of closed issues linked to this milestone.
898 var closed_issues: nullable Int = null is writable
899
900 # Milestone state.
901 var state: nullable String is writable
902
903 # Creation time as String.
904 var created_at: nullable String is writable
905
906 # Creation time as ISODate.
907 fun iso_created_at: nullable ISODate do
908 var created_at = self.created_at
909 if created_at == null then return null
910 return new ISODate.from_string(created_at)
911 end
912
913 # User that created this milestone.
914 var creator: nullable User is writable
915
916 # Due time as String (if any).
917 var due_on: nullable String is writable
918
919 # Due time in ISODate format (if any).
920 fun iso_due_on: nullable ISODate do
921 var due_on = self.due_on
922 if due_on == null then return null
923 return new ISODate.from_string(due_on)
924 end
925
926 # Last update time as String (if any).
927 var updated_at: nullable String is writable
928
929 # Last update date as ISODate.
930 fun iso_updated_at: nullable ISODate do
931 var updated_at = self.updated_at
932 if updated_at == null then return null
933 return new ISODate.from_string(updated_at)
934 end
935
936 # Close time as String (if any).
937 var closed_at: nullable String is writable
938
939 # Close time as ISODate.
940 fun iso_closed_at: nullable ISODate do
941 var closed_at = self.closed_at
942 if closed_at == null then return null
943 return new ISODate.from_string(closed_at)
944 end
945 end
946
947 # A Github comment
948 #
949 # There is two kinds of comments:
950 #
951 # * `CommitComment` are made on a commit page.
952 # * `IssueComment` are made on an issue or pull request page.
953 # * `ReviewComment` are made on the diff associated to a pull request.
954 abstract class Comment
955 super GithubEntity
956 serialize
957
958 # Identifier of this comment.
959 var id: Int is writable
960
961 # User that made this comment.
962 var user: User is writable
963
964 # Creation time as String.
965 var created_at: String is writable
966
967 # Creation time as ISODate.
968 fun iso_created_at: nullable ISODate do
969 return new ISODate.from_string(created_at)
970 end
971
972 # Last update time as String (if any).
973 var updated_at: nullable String is writable
974
975 # Last update date as ISODate.
976 fun iso_updated_at: nullable ISODate do
977 var updated_at = self.updated_at
978 if updated_at == null then return null
979 return new ISODate.from_string(updated_at)
980 end
981
982 # Comment body text.
983 var body: String is writable
984
985 # Does the comment contain an acknowledgement (+1)
986 fun is_ack: Bool do
987 return body.has("\\+1\\b".to_re) or body.has(":+1:") or body.has(":shipit:")
988 end
989 end
990
991 # A comment made on a commit.
992 class CommitComment
993 super Comment
994 serialize
995
996 # Commented commit.
997 var commit_id: String is writable
998
999 # Position of the comment on the line.
1000 var position: nullable Int is writable
1001
1002 # Line of the comment.
1003 var line: nullable Int is writable
1004
1005 # Path of the commented file.
1006 var path: nullable String is writable
1007 end
1008
1009 # Comments made on Github issue and pull request pages.
1010 #
1011 # Should be accessed from `GithubAPI::load_issue_comment`.
1012 #
1013 # See <https://developer.github.com/v3/issues/comments/>.
1014 class IssueComment
1015 super Comment
1016 serialize
1017
1018 # Issue that contains `self`.
1019 fun issue_number: Int do return issue_url.split("/").last.to_i
1020
1021 # Link to the issue document on API.
1022 var issue_url: String is writable
1023 end
1024
1025 # Comments made on Github pull request diffs.
1026 #
1027 # Should be accessed from `GithubAPI::load_diff_comment`.
1028 #
1029 # See <https://developer.github.com/v3/pulls/comments/>.
1030 class ReviewComment
1031 super Comment
1032 serialize
1033
1034 # Pull request that contains `self`.
1035 fun pull_number: Int do return pull_request_url.split("/").last.to_i
1036
1037 # Link to the pull request on API.
1038 var pull_request_url: String is writable
1039
1040 # Diff hunk.
1041 var diff_hunk: String is writable
1042
1043 # Path of commented file.
1044 var path: String is writable
1045
1046 # Position of the comment on the file.
1047 var position: nullable Int is writable
1048
1049 # Original position in the diff.
1050 var original_position: Int is writable
1051
1052 # Commit referenced by this comment.
1053 var commit_id: String is writable
1054
1055 # Original commit id.
1056 var original_commit_id: String is writable
1057 end
1058
1059 # An event that occurs on a Github `Issue`.
1060 #
1061 # Should be accessed from `GithubAPI::load_issue_event`.
1062 #
1063 # See <https://developer.github.com/v3/issues/events/>.
1064 class IssueEvent
1065 super GithubEntity
1066 serialize
1067
1068 # Event id on Github.
1069 var id: Int is writable
1070
1071 # User that initiated the event.
1072 var actor: User is writable
1073
1074 # Creation time as String.
1075 var created_at: String is writable
1076
1077 # Creation time as ISODate.
1078 fun iso_created_at: nullable ISODate do
1079 return new ISODate.from_string(created_at)
1080 end
1081
1082 # Event descriptor.
1083 var event: String is writable
1084
1085 # Commit linked to this event (if any).
1086 var commit_id: nullable String is writable
1087
1088 # Label linked to this event (if any).
1089 var labl: nullable Label is writable, serialize_as("label")
1090
1091 # User linked to this event (if any).
1092 var assignee: nullable User is writable
1093
1094 # Milestone linked to this event (if any).
1095 var milestone: nullable Milestone is writable
1096
1097 # Rename linked to this event (if any).
1098 var rename: nullable RenameAction is writable
1099 end
1100
1101 # A rename action maintains the name before and after a renaming action.
1102 class RenameAction
1103 serialize
1104
1105 # Name before renaming.
1106 var from: String is writable
1107
1108 # Name after renaming.
1109 var to: String is writable
1110 end
1111
1112 #
1113 # Should be accessed from `Repo::contrib_stats`.
1114 #
1115 # See <https://developer.github.com/v3/repos/statistics/>.
1116 class ContributorStats
1117 super Comparable
1118 serialize
1119
1120 redef type OTHER: ContributorStats
1121
1122 # Github API client.
1123 var api: GithubAPI is writable
1124
1125 # User these statistics are about.
1126 var author: User is writable
1127
1128 # Total number of commit.
1129 var total: Int is writable
1130
1131 # Are of weeks of activity with detailed statistics.
1132 var weeks: JsonArray is writable
1133
1134 # ContributorStats can be compared on the total amount of commits.
1135 redef fun <(o) do return total < o.total
1136 end
1137
1138 # A Github file representation.
1139 #
1140 # Mostly a wrapper around a json object.
1141 class GithubFile
1142 serialize
1143
1144 # File name.
1145 var filename: String is writable
1146 end
1147
1148 # Make ISO Datew serilizable
1149 redef class ISODate
1150 serialize
1151 end
1152
1153 # JsonDeserializer specific for Github objects.
1154 class GithubDeserializer
1155 super JsonDeserializer
1156
1157 redef fun class_name_heuristic(json_object) do
1158 if json_object.has_key("login") then
1159 return "User"
1160 else if json_object.has_key("full_name") then
1161 return "Repo"
1162 else if json_object.has_key("name") and json_object.has_key("commit") then
1163 return "Branch"
1164 else if json_object.has_key("sha") and json_object.has_key("ref") then
1165 return "PullRef"
1166 else if (json_object.has_key("sha") and json_object.has_key("commit")) or (json_object.has_key("id") and json_object.has_key("tree_id")) then
1167 return "Commit"
1168 else if json_object.has_key("sha") and json_object.has_key("tree") then
1169 return "GitCommit"
1170 else if json_object.has_key("name") and json_object.has_key("date") then
1171 return "GitUser"
1172 else if json_object.has_key("number") and json_object.has_key("patch_url") then
1173 return "PullRequest"
1174 else if json_object.has_key("open_issues") and json_object.has_key("closed_issues") then
1175 return "Milestone"
1176 else if json_object.has_key("number") and json_object.has_key("title") then
1177 return "Issue"
1178 else if json_object.has_key("color") then
1179 return "Label"
1180 else if json_object.has_key("event") then
1181 return "IssueEvent"
1182 else if json_object.has_key("original_commit_id") then
1183 return "ReviewComment"
1184 else if json_object.has_key("commit_id") then
1185 return "CommitComment"
1186 else if json_object.has_key("issue_url") then
1187 return "IssueComment"
1188 end
1189 return null
1190 end
1191
1192 redef fun deserialize_class(name) do
1193 if name == "Issue" then
1194 var issue = super.as(Issue)
1195 if path.last.has_key("pull_request") then
1196 issue.is_pull_request = true
1197 end
1198 return issue
1199 else if name == "Commit" then
1200 var commit = super.as(Commit)
1201 var git_commit = commit.commit
1202 if git_commit != null then commit.message = git_commit.message
1203 return commit
1204 end
1205 return super
1206 end
1207 end