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