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