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