X-Git-Url: http://nitlanguage.org diff --git a/lib/github/api.nit b/lib/github/api.nit index 8f439f1..597c05f 100644 --- a/lib/github/api.nit +++ b/lib/github/api.nit @@ -19,7 +19,6 @@ # For most use-cases you need to use the `GithubAPI` client. module api -# TODO to remove intrude import json::serialization_read import json::static @@ -225,8 +224,8 @@ class GithubAPI # assert repo.owner.login == "nitlang" # assert repo.default_branch == "master" # ~~~ - fun get_repo(full_name: String): nullable Repo do - return get("/repos/{full_name}").as(nullable Repo) + fun get_repo(repo_slug: String): nullable Repo do + return get("/repos/{repo_slug}").as(nullable Repo) end # List of repo branches. @@ -234,15 +233,15 @@ class GithubAPI # Pagination: # * `page`: page to fetch (default: 1) # * `per_page`: number of branches by page (default: 30) - fun get_repo_branches(repo: Repo, page, per_page: nullable Int): Array[Branch] do + fun get_repo_branches(repo_slug: String, page, per_page: nullable Int): Array[Branch] do return new GithubArray[Branch].from(get( - "/repos/{repo.full_name}/branches?{pagination(page, per_page)}")) + "/repos/{repo_slug}/branches?{pagination(page, per_page)}")) end # List of issues associated with their ids. - fun get_repo_issues(repo: Repo, page, per_page: nullable Int): Array[Issue] do + fun get_repo_issues(repo_slug: String, page, per_page: nullable Int): Array[Issue] do return new GithubArray[Issue].from(get( - "/repos/{repo.full_name}/issues?{pagination(page, per_page)}")) + "/repos/{repo_slug}/issues?{pagination(page, per_page)}")) end # Search issues in this repo form an advanced query. @@ -254,20 +253,20 @@ class GithubAPI # ~~~ # # See . - fun search_repo_issues(repo: Repo, query: String, page, per_page: nullable Int): nullable SearchResults do - return get("/search/issues?q={query} repo:{repo.full_name}&{pagination(page, per_page)}").as(nullable SearchResults) + fun search_repo_issues(repo_slug: String, query: String, page, per_page: nullable Int): nullable SearchResults do + return get("/search/issues?q={query} repo:{repo_slug}&{pagination(page, per_page)}").as(nullable SearchResults) end # List of labels associated with their names. - fun get_repo_labels(repo: Repo, page, per_page: nullable Int): Array[Label] do + fun get_repo_labels(repo_slug: String, page, per_page: nullable Int): Array[Label] do return new GithubArray[Label].from(get( - "/repos/{repo.full_name}/labels?{pagination(page, per_page)}")) + "/repos/{repo_slug}/labels?{pagination(page, per_page)}")) end # List of milestones associated with their ids. - fun get_repo_milestones(repo: Repo, page, per_page: nullable Int): Array[Milestone] do + fun get_repo_milestones(repo_slug: String, page, per_page: nullable Int): Array[Milestone] do return new GithubArray[Milestone].from(get( - "/repos/{repo.full_name}/milestones?{pagination(page, per_page)}")) + "/repos/{repo_slug}/milestones?{pagination(page, per_page)}")) end # List of pull-requests associated with their ids. @@ -275,16 +274,16 @@ class GithubAPI # Implementation notes: because PR numbers are not consecutive, # PR are loaded from pages. # See: https://developer.github.com/v3/pulls/#list-pull-requests - fun get_repo_pulls(repo: Repo, page, per_page: nullable Int): Array[PullRequest] do + fun get_repo_pulls(repo_slug: String, page, per_page: nullable Int): Array[PullRequest] do return new GithubArray[PullRequest].from(get( - "/repos/{repo.full_name}/pulls?{pagination(page, per_page)}")) + "/repos/{repo_slug}/pulls?{pagination(page, per_page)}")) end # List of contributor related statistics. - fun get_repo_contrib_stats(repo: Repo): Array[ContributorStats] do - message(1, "Get contributor stats for {repo.full_name}") + fun get_repo_contrib_stats(repo_slug: String): Array[ContributorStats] do + message(1, "Get contributor stats for {repo_slug}") var res = new Array[ContributorStats] - var array = get("/repos/{repo.full_name}/stats/contributors") + var array = get("/repos/{repo_slug}/stats/contributors") if not array isa JsonArray then return res return deserialize(array.to_json).as(Array[ContributorStats]) end @@ -301,32 +300,8 @@ class GithubAPI # assert branch.name == "master" # assert branch.commit isa Commit # ~~~ - fun get_branch(repo: Repo, name: String): nullable Branch do - return get("/repos/{repo.full_name}/branches/{name}").as(nullable Branch) - end - - # List all commits in `self`. - # - # This can be long depending on the branch size. - # Commit are returned in an unspecified order. - fun get_branch_commits(branch: Branch): Array[Commit] do - var res = new Array[Commit] - var done = new HashSet[String] - var todos = new Array[Commit] - todos.add branch.commit - loop - if todos.is_empty then break - var commit = todos.pop - if done.has(commit.sha) then continue - done.add commit.sha - res.add commit - var parents = commit.parents - if parents == null then continue - for parent in parents do - todos.add parent - end - end - return res + fun get_branch(repo_slug: String, name: String): nullable Branch do + return get("/repos/{repo_slug}/branches/{name}").as(nullable Branch) end # Get the Github commit with `sha`. @@ -340,8 +315,15 @@ class GithubAPI # var commit = api.get_commit(repo, "64ce1f") # assert commit isa Commit # ~~~ - fun get_commit(repo: Repo, sha: String): nullable Commit do - return get("/repos/{repo.full_name}/commits/{sha}").as(nullable Commit) + fun get_commit(repo_slug: String, sha: String): nullable Commit do + return get("/repos/{repo_slug}/commits/{sha}").as(nullable Commit) + end + + # Get the status of a commit + # + # The status holds the result of each check ran on a commit like CI, reviews etc. + fun get_commit_status(repo_slug: String, sha: String): nullable CommitStatus do + return get("/repos/{repo_slug}/commits/{sha}/status").as(nullable CommitStatus) end # Get the Github issue #`number`. @@ -355,20 +337,20 @@ class GithubAPI # var issue = api.get_issue(repo, 1) # assert issue.title == "Doc" # ~~~ - fun get_issue(repo: Repo, number: Int): nullable Issue do - return get("/repos/{repo.full_name}/issues/{number}").as(nullable Issue) + fun get_issue(repo_slug: String, number: Int): nullable Issue do + return get("/repos/{repo_slug}/issues/{number}").as(nullable Issue) end # List of event on this issue. - fun get_issue_comments(repo: Repo, issue: Issue, page, per_page: nullable Int): Array[IssueComment] do + fun get_issue_comments(repo_slug: String, issue_number: Int, page, per_page: nullable Int): Array[IssueComment] do return new GithubArray[IssueComment].from(get( - "/repos/{repo.full_name}/issues/{issue.number}/comments?{pagination(page, per_page)}")) + "/repos/{repo_slug}/issues/{issue_number}/comments?{pagination(page, per_page)}")) end # List of events on this issue. - fun get_issue_events(repo: Repo, issue: Issue, page, per_page: nullable Int): Array[IssueEvent] do + fun get_issue_events(repo_slug: String, issue_number: Int, page, per_page: nullable Int): Array[IssueEvent] do return new GithubArray[IssueEvent].from(get( - "/repos/{repo.full_name}/issues/{issue.number}/events?{pagination(page, per_page)}")) + "/repos/{repo_slug}/issues/{issue_number}/events?{pagination(page, per_page)}")) end # Get the Github pull request #`number`. @@ -383,8 +365,13 @@ class GithubAPI # assert pull.title == "Doc" # assert pull.user.login == "Morriar" # ~~~ - fun get_pull(repo: Repo, number: Int): nullable PullRequest do - return get("/repos/{repo.full_name}/pulls/{number}").as(nullable PullRequest) + fun get_pull(repo_slug: String, number: Int): nullable PullRequest do + return get("/repos/{repo_slug}/pulls/{number}").as(nullable PullRequest) + end + + # Get a specific pull request comment + fun get_pull_comment(repo_slug: String, id: Int): nullable PullComment do + return get("/repos/{repo_slug}/pulls/comments/{id}").as(nullable PullComment) end # Get the Github label with `name`. @@ -398,8 +385,8 @@ class GithubAPI # var labl = api.get_label(repo, "ok_will_merge") # assert labl != null # ~~~ - fun get_label(repo: Repo, name: String): nullable Label do - return get("/repos/{repo.full_name}/labels/{name}").as(nullable Label) + fun get_label(repo_slug: String, name: String): nullable Label do + return get("/repos/{repo_slug}/labels/{name}").as(nullable Label) end # Get the Github milestone with `id`. @@ -413,8 +400,8 @@ class GithubAPI # var stone = api.get_milestone(repo, 4) # assert stone.title == "v1.0prealpha" # ~~~ - fun get_milestone(repo: Repo, id: Int): nullable Milestone do - return get("/repos/{repo.full_name}/milestones/{id}").as(nullable Milestone) + fun get_milestone(repo_slug: String, id: Int): nullable Milestone do + return get("/repos/{repo_slug}/milestones/{id}").as(nullable Milestone) end # Get the Github issue event with `id`. @@ -432,8 +419,8 @@ class GithubAPI # assert event.labl isa Label # assert event.labl.name == "need_review" # ~~~ - fun get_issue_event(repo: Repo, id: Int): nullable IssueEvent do - return get("/repos/{repo.full_name}/issues/events/{id}").as(nullable IssueEvent) + fun get_issue_event(repo_slug: String, id: Int): nullable IssueEvent do + return get("/repos/{repo_slug}/issues/events/{id}").as(nullable IssueEvent) end # Get the Github commit comment with `id`. @@ -449,8 +436,8 @@ class GithubAPI # assert comment.body == "For testing purposes...\n" # assert comment.commit_id == "7eacb86d1e24b7e72bc9ac869bf7182c0300ceca" # ~~~ - fun get_commit_comment(repo: Repo, id: Int): nullable CommitComment do - return get("/repos/{repo.full_name}/comments/{id}").as(nullable CommitComment) + fun get_commit_comment(repo_slug: String, id: Int): nullable CommitComment do + return get("/repos/{repo_slug}/comments/{id}").as(nullable CommitComment) end # Get the Github issue comment with `id`. @@ -466,25 +453,8 @@ class GithubAPI # assert comment.created_at.to_s == "2012-05-30T20:16:54Z" # assert comment.issue_number == 10 # ~~~ - fun get_issue_comment(repo: Repo, id: Int): nullable IssueComment do - return get("/repos/{repo.full_name}/issues/comments/{id}").as(nullable IssueComment) - end - - # Get the Github diff comment with `id`. - # - # Returns `null` if the comment cannot be found. - # - # ~~~nitish - # var api = new GithubAPI(get_github_oauth) - # var repo = api.get_repo("nitlang/nit") - # assert repo != null - # var comment = api.get_review_comment(repo, 21010363) - # assert comment.path == "src/modelize/modelize_property.nit" - # assert comment.original_position == 26 - # assert comment.pull_number == 945 - # ~~~ - fun get_review_comment(repo: Repo, id: Int): nullable ReviewComment do - return get("/repos/{repo.full_name}/pulls/comments/{id}").as(nullable ReviewComment) + fun get_issue_comment(repo_slug: String, id: Int): nullable IssueComment do + return get("/repos/{repo_slug}/issues/comments/{id}").as(nullable IssueComment) end private fun pagination(page, per_page: nullable Int): String do @@ -531,7 +501,7 @@ class GithubAPIError var requested_uri: String end -# An Error returned while deserializing GithubEntity objects +# An Error returned while deserializing objects from the API class GithubDeserializerErrors super GithubError @@ -539,16 +509,6 @@ class GithubDeserializerErrors var deserizalization_errors: Array[Error] end -# Something returned by the Github API. -# -# Mainly a Nit wrapper around a JSON objet. -abstract class GithubEntity - serialize - - # Github page url. - var html_url: nullable String is writable -end - # A Github user # # Provides access to [Github user data](https://developer.github.com/v3/users/). @@ -578,7 +538,6 @@ end # Provides access to [Github repo data](https://developer.github.com/v3/repos/). # Should be accessed from `GithubAPI::get_repo`. class Repo - super GithubEntity serialize # Repo full name on Github. @@ -591,7 +550,7 @@ class Repo var owner: User is writable # Repo default branch name. - var default_branch: String is writable + var default_branch: nullable String = null is optional, writable end # A Github branch. @@ -600,7 +559,6 @@ end # # See . class Branch - super GithubEntity serialize # Branch name. @@ -616,7 +574,6 @@ end # # See . class Commit - super GithubEntity serialize # Commit SHA. @@ -634,23 +591,9 @@ class Commit # Authoring date as String. var author_date: nullable String is writable - # Authoring date as ISODate. - fun iso_author_date: nullable ISODate do - var author_date = self.author_date - if author_date == null then return null - return new ISODate.from_string(author_date) - end - # Commit date as String. var commit_date: nullable String is writable - # Commit date as ISODate. - fun iso_commit_date: nullable ISODate do - var commit_date = self.commit_date - if commit_date == null then return null - return new ISODate.from_string(commit_date) - end - # List files staged in this commit. var files: nullable Array[GithubFile] = null is optional, writable @@ -663,7 +606,6 @@ end # A Git Commit representation class GitCommit - super GithubEntity serialize # Commit SHA. @@ -684,18 +626,10 @@ end # Git user authoring data class GitUser - super GithubEntity serialize # Authoring date. var date: nullable String = null is writable - - # Authoring date as ISODate. - fun iso_date: nullable ISODate do - var date = self.date - if date == null then return null - return new ISODate.from_string(date) - end end # A Github issue. @@ -704,7 +638,6 @@ end # # See . class Issue - super GithubEntity serialize # Issue Github ID. @@ -740,31 +673,12 @@ class Issue # Creation time as String. var created_at: String is writable - # Creation time as ISODate. - fun iso_created_at: ISODate do - return new ISODate.from_string(created_at) - end - # Last update time as String (if any). var updated_at: nullable String is writable - # Last update date as ISODate. - fun iso_updated_at: nullable ISODate do - var updated_at = self.updated_at - if updated_at == null then return null - return new ISODate.from_string(updated_at) - end - # Close time as String (if any). var closed_at: nullable String is writable - # Close time as ISODate. - fun iso_closed_at: nullable ISODate do - var closed_at = self.closed_at - if closed_at == null then return null - return new ISODate.from_string(closed_at) - end - # Full description of the issue. var body: nullable String is writable @@ -788,13 +702,6 @@ class PullRequest # Merge time as String (if any). var merged_at: nullable String is writable - # Merge time as ISODate. - fun iso_merged_at: nullable ISODate do - var merged_at = self.merged_at - if merged_at == null then return null - return new ISODate.from_string(merged_at) - end - # Merge commit SHA. var merge_commit_sha: nullable String is writable @@ -865,7 +772,6 @@ end # # See . class Label - super GithubEntity serialize # Label name. @@ -881,7 +787,6 @@ end # # See . class Milestone - super GithubEntity serialize # The milestone id on Github. @@ -905,45 +810,17 @@ class Milestone # Creation time as String. var created_at: nullable String is writable - # Creation time as ISODate. - fun iso_created_at: nullable ISODate do - var created_at = self.created_at - if created_at == null then return null - return new ISODate.from_string(created_at) - end - # User that created this milestone. var creator: nullable User is writable # Due time as String (if any). var due_on: nullable String is writable - # Due time in ISODate format (if any). - fun iso_due_on: nullable ISODate do - var due_on = self.due_on - if due_on == null then return null - return new ISODate.from_string(due_on) - end - # Last update time as String (if any). var updated_at: nullable String is writable - # Last update date as ISODate. - fun iso_updated_at: nullable ISODate do - var updated_at = self.updated_at - if updated_at == null then return null - return new ISODate.from_string(updated_at) - end - # Close time as String (if any). var closed_at: nullable String is writable - - # Close time as ISODate. - fun iso_closed_at: nullable ISODate do - var closed_at = self.closed_at - if closed_at == null then return null - return new ISODate.from_string(closed_at) - end end # A Github comment @@ -952,9 +829,8 @@ end # # * `CommitComment` are made on a commit page. # * `IssueComment` are made on an issue or pull request page. -# * `ReviewComment` are made on the diff associated to a pull request. +# * `PullComment` are made on the diff associated to a pull request. abstract class Comment - super GithubEntity serialize # Identifier of this comment. @@ -966,21 +842,9 @@ abstract class Comment # Creation time as String. var created_at: String is writable - # Creation time as ISODate. - fun iso_created_at: nullable ISODate do - return new ISODate.from_string(created_at) - end - # Last update time as String (if any). var updated_at: nullable String is writable - # Last update date as ISODate. - fun iso_updated_at: nullable ISODate do - var updated_at = self.updated_at - if updated_at == null then return null - return new ISODate.from_string(updated_at) - end - # Comment body text. var body: String is writable @@ -1008,6 +872,55 @@ class CommitComment var path: nullable String is writable end +# Status of a commit +# +# Can contain sub-status for reviews, CI etc. +class CommitStatus + serialize + + # Global state of this commit + var state: nullable String = null is optional, writable + + # Sha of the commit this status is for + var sha: nullable String = null is optional, writable + + # Repository the commit belongs to + var repository: nullable Repo = null is optional, writable + + # All sub statuses (one for each check) + var statuses = new Array[RepoStatus] is optional, writable + + # Total count of sub statuses + var total_count: nullable Int = null is optional, writable +end + +# Sub status of a CommitStatus +# +# Represents a check applied to a commit (reviews, CI, ...). +class RepoStatus + serialize + + # State of this check + var state: nullable String = null is optional, writable + + # Description of this check + var description: nullable String = null is optional, writable + + # External URL + var target_url: nullable String = null is optional, writable + + # Context this status is related to + # + # Used to hold the name of the check applied. + var context: nullable String = null is optional, writable + + # Date when this status was created + var created_at: nullable String = null is optional, writable + + # Last date this status was updated + var updated_at: nullable String = null is optional, writable +end + # Comments made on Github issue and pull request pages. # # Should be accessed from `GithubAPI::get_issue_comment`. @@ -1029,7 +942,7 @@ end # Should be accessed from `GithubAPI::get_diff_comment`. # # See . -class ReviewComment +class PullComment super Comment serialize @@ -1064,7 +977,6 @@ end # # See . class IssueEvent - super GithubEntity serialize # Event id on Github. @@ -1076,11 +988,6 @@ class IssueEvent # Creation time as String. var created_at: String is writable - # Creation time as ISODate. - fun iso_created_at: nullable ISODate do - return new ISODate.from_string(created_at) - end - # Event descriptor. var event: String is writable @@ -1161,47 +1068,53 @@ class SearchResults var items: Array[Object] end -# Make ISO Datew serilizable -redef class ISODate - serialize -end - # JsonDeserializer specific for Github objects. class GithubDeserializer super JsonDeserializer - redef fun class_name_heuristic(json_object) do - if json_object.has_key("login") then - return "User" - else if json_object.has_key("full_name") then - return "Repo" - else if json_object.has_key("name") and json_object.has_key("commit") then + private var pattern_base = "https://api.github.com" + + # Url patterns to class names + var url_patterns: Map[Regex, String] is lazy do + var map = new HashMap[Regex, String] + map["{pattern_base}/users/[^/]*$".to_re] = "User" + map["{pattern_base}/repos/[^/]*/[^/]*$".to_re] = "Repo" + map["{pattern_base}/repos/[^/]*/[^/]*/labels/[^/]+$".to_re] = "Label" + map["{pattern_base}/repos/[^/]*/[^/]*/milestones/[0-9]+$".to_re] = "Milestone" + map["{pattern_base}/repos/[^/]*/[^/]*/issues/[0-9]+$".to_re] = "Issue" + map["{pattern_base}/repos/[^/]*/[^/]*/issues/comments/[0-9]+$".to_re] = "IssueComment" + map["{pattern_base}/repos/[^/]*/[^/]*/issues/events/[0-9]+$".to_re] = "IssueEvent" + map["{pattern_base}/repos/[^/]*/[^/]*/pulls/[0-9]+$".to_re] = "PullRequest" + map["{pattern_base}/repos/[^/]*/[^/]*/pulls/comments/[0-9]+$".to_re] = "PullComment" + map["{pattern_base}/repos/[^/]*/[^/]*/comments/[0-9]+$".to_re] = "CommitComment" + map["{pattern_base}/repos/[^/]*/[^/]*/commits/[a-f0-9]+$".to_re] = "Commit" + map["{pattern_base}/repos/[^/]*/[^/]*/commits/[a-f0-9]+/status$".to_re] = "CommitStatus" + map["{pattern_base}/repos/[^/]*/[^/]*/statuses/[a-f0-9]+$".to_re] = "RepoStatus" + return map + end + + # Match `url` property in object to a class name + fun url_heuristic(raw: Map[String, nullable Object]): nullable String do + if not raw.has_key("url") then return null + + var url = raw["url"].as(String) + for re, class_name in url_patterns do + if url.has(re) then return class_name + end + return null + end + + redef fun class_name_heuristic(raw) do + # Try with url + var class_name = url_heuristic(raw) + if class_name != null then return class_name + + # print raw.serialize_to_json(true, true) # debug + + # Use properties heuristics + if raw.has_key("name") and raw.has_key("commit") then return "Branch" - else if json_object.has_key("sha") and json_object.has_key("ref") then - return "PullRef" - else if (json_object.has_key("sha") and json_object.has_key("commit")) or (json_object.has_key("id") and json_object.has_key("tree_id")) then - return "Commit" - else if json_object.has_key("sha") and json_object.has_key("tree") then - return "GitCommit" - else if json_object.has_key("name") and json_object.has_key("date") then - return "GitUser" - else if json_object.has_key("number") and json_object.has_key("patch_url") then - return "PullRequest" - else if json_object.has_key("open_issues") and json_object.has_key("closed_issues") then - return "Milestone" - else if json_object.has_key("number") and json_object.has_key("title") then - return "Issue" - else if json_object.has_key("color") then - return "Label" - else if json_object.has_key("event") then - return "IssueEvent" - else if json_object.has_key("original_commit_id") then - return "ReviewComment" - else if json_object.has_key("commit_id") then - return "CommitComment" - else if json_object.has_key("issue_url") then - return "IssueComment" - else if json_object.has_key("total_count") then + else if raw.has_key("total_count") and raw.has_key("items") then return "SearchResults" end return null