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