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