# See the License for the specific language governing permissions and
# limitations under the License.
-# Nit object oriented interface to Github api.
+# Nit object oriented interface to [Github api](https://developer.github.com/v3/).
#
# This modules reifies Github API elements as Nit classes.
#
module api
import github_curl
+intrude import json::serialization_read
-# Interface to Github REST API.
+# Client to Github API
#
-# Used by all `GithubEntity` to perform requests.
-#
-# Usage:
+# To access the API you need an instance of a `GithubAPI` client.
#
# ~~~
# # Get Github authentification token.
# var api = new GithubAPI(token)
# ~~~
#
-# The API client allows to get Github API entities:
+# The API client allows you to get Github API entities.
#
# ~~~
-# var repo = api.load_repo("privat/nit")
+# var repo = api.load_repo("nitlang/nit")
# assert repo != null
# assert repo.name == "nit"
#
# ~~~
class GithubAPI
- # Github API OAuth token.
+ # Github API OAuth token
+ #
+ # To access your private ressources, you must
+ # [authenticate](https://developer.github.com/guides/basics-of-authentication/).
+ #
+ # For client applications, Github recommands to use the
+ # [OAuth tokens](https://developer.github.com/v3/oauth/) authentification method.
+ #
+ #
#
- # This token is used to authenticate the application on Github API.
# Be aware that there is [rate limits](https://developer.github.com/v3/rate_limit/)
# associated to the key.
var auth: String
ghcurl = new GithubCurl(auth, user_agent)
end
+ # Deserialize an object
+ fun deserialize(string: String): nullable Object do
+ var deserializer = new GithubDeserializer(string)
+ var res = deserializer.deserialize
+ # print deserializer.errors.join("\n") # DEBUG
+ return res
+ end
+
# Execute a GET request on Github API.
#
# This method returns raw json data.
# See other `load_*` methods to use more expressive types.
#
# var api = new GithubAPI(get_github_oauth)
- # var obj = api.get("repos/privat/nit")
+ # var obj = api.get("/repos/nitlang/nit")
# assert obj isa JsonObject
# assert obj["name"] == "nit"
#
# Returns `null` in case of `error`.
#
- # obj = api.get("foo/bar/baz")
+ # obj = api.get("/foo/bar/baz")
# assert obj == null
# assert api.was_error
# var err = api.last_error
# assert err isa GithubError
# assert err.name == "GithubAPIError"
# assert err.message == "Not Found"
- fun get(path: String): nullable Jsonable do
+ fun get(path: String): nullable Serializable do
path = sanitize_uri(path)
- var res = ghcurl.get_and_parse("{api_url}/{path}")
+ var res = ghcurl.get_and_parse("{api_url}{path}")
if res isa Error then
last_error = res
was_error = true
# Load the json object from Github.
# See `GithubEntity::load_from_github`.
- private fun load_from_github(key: String): JsonObject do
+ protected fun load_from_github(key: String): nullable GithubEntity do
message(1, "Get {key} (github)")
var res = get(key)
- if was_error then return new JsonObject
- return res.as(JsonObject)
+ if was_error then return null
+ return deserialize(res.as(JsonObject).to_json).as(nullable GithubEntity)
+ end
+
+ # Get the Github logged user from `auth` token.
+ #
+ # Loads the `User` from the API or returns `null` if the user cannot be found.
+ #
+ # ~~~nitish
+ # var api = new GithubAPI(get_github_oauth)
+ # var user = api.load_auth_user
+ # assert user.login == "Morriar"
+ # ~~~
+ fun load_auth_user: nullable User do
+ var user = load_from_github("/user")
+ if was_error then return null
+ return user.as(nullable User)
end
- # Get the Github user with `login`.
+ # Get the Github user with `login`
#
- # Returns `null` if the user cannot be found.
+ # Loads the `User` from the API or returns `null` if the user cannot be found.
#
# var api = new GithubAPI(get_github_oauth)
# var user = api.load_user("Morriar")
+ # print user or else "null"
# assert user.login == "Morriar"
fun load_user(login: String): nullable User do
- var user = new User(self, login)
- return user.load_from_github
+ return load_from_github("/users/{login}").as(nullable User)
end
# Get the Github repo with `full_name`.
#
- # Returns `null` if the repo cannot be found.
+ # Loads the `Repo` from the API or returns `null` if the repo cannot be found.
#
# var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("privat/nit")
+ # var repo = api.load_repo("nitlang/nit")
# assert repo.name == "nit"
- # assert repo.owner.login == "privat"
- # assert repo.default_branch.name == "master"
+ # assert repo.owner.login == "nitlang"
+ # assert repo.default_branch == "master"
fun load_repo(full_name: String): nullable Repo do
- var repo = new Repo(self, full_name)
- return repo.load_from_github
+ return load_from_github("/repos/{full_name}").as(nullable Repo)
+ end
+
+ # List of branches associated with their names.
+ fun load_repo_branches(repo: Repo): Array[Branch] do
+ message(1, "Get branches for {repo.full_name}")
+ var array = get("/repos/{repo.full_name}/branches")
+ var res = new Array[Branch]
+ if not array isa JsonArray then return res
+ var deser = deserialize(array.to_json)
+ if deser isa Array[Object] then return res # empty array
+ return deser.as(Array[Branch])
+ end
+
+ # List of issues associated with their ids.
+ fun load_repo_issues(repo: Repo): Array[Issue] do
+ message(1, "Get issues for {repo.full_name}")
+ var res = new Array[Issue]
+ var issue = load_repo_last_issue(repo)
+ if issue == null then return res
+ res.add issue
+ while issue != null and issue.number > 1 do
+ issue = load_issue(repo, issue.number - 1)
+ if issue == null then continue
+ res.add issue
+ end
+ return res
+ end
+
+ # Search issues in this repo form an advanced query.
+ #
+ # Example:
+ #
+ # ~~~nitish
+ # var issues = repo.search_issues("is:open label:need_review")
+ # ~~~
+ #
+ # See <https://developer.github.com/v3/search/#search-issues>.
+ fun search_repo_issues(repo: Repo, query: String): Array[Issue] do
+ query = "/search/issues?q={query} repo:{repo.full_name}"
+ var res = new Array[Issue]
+ var response = get(query)
+ if was_error then return res
+ var arr = response.as(JsonObject)["items"].as(JsonArray)
+ return deserialize(arr.to_json).as(Array[Issue])
+ end
+
+ # Get the last published issue.
+ fun load_repo_last_issue(repo: Repo): nullable Issue do
+ var array = get("/repos/{repo.full_name}/issues")
+ if not array isa JsonArray then return null
+ if array.is_empty then return null
+ var obj = array.first
+ if not obj isa JsonObject then return null
+ return deserialize(obj.to_json).as(nullable Issue)
+ end
+
+ # List of labels associated with their names.
+ fun load_repo_labels(repo: Repo): Array[Label] do
+ message(1, "Get labels for {repo.full_name}")
+ var array = get("repos/{repo.full_name}/labels")
+ if not array isa JsonArray then return new Array[Label]
+ return deserialize(array.to_json).as(Array[Label])
+ end
+
+ # List of milestones associated with their ids.
+ fun load_repo_milestones(repo: Repo): Array[Milestone] do
+ message(1, "Get milestones for {repo.full_name}")
+ var array = get("/repos/{repo.full_name}/milestones")
+ if not array isa JsonArray then return new Array[Milestone]
+ return deserialize(array.to_json).as(Array[Milestone])
+ end
+
+ # List of pull-requests associated with their ids.
+ #
+ # Implementation notes: because PR numbers are not consecutive,
+ # PR are loaded from pages.
+ # See: https://developer.github.com/v3/pulls/#list-pull-requests
+ fun load_repo_pulls(repo: Repo): Array[PullRequest] do
+ message(1, "Get pulls for {repo.full_name}")
+ var key = "/repos/{repo.full_name}"
+ var res = new Array[PullRequest]
+ var page = 1
+ loop
+ var array = get("{key}/pulls?page={page}").as(JsonArray)
+ if array.is_empty then break
+ for obj in array do
+ if not obj isa JsonObject then continue
+ var pr = deserialize(array.to_json).as(nullable PullRequest)
+ if pr == null then continue
+ res.add pr
+ end
+ page += 1
+ end
+ return res
+ end
+
+ # List of contributor related statistics.
+ fun load_repo_contrib_stats(repo: Repo): Array[ContributorStats] do
+ message(1, "Get contributor stats for {repo.full_name}")
+ var res = new Array[ContributorStats]
+ var array = get("/repos/{repo.full_name}/stats/contributors")
+ if not array isa JsonArray then return res
+ return deserialize(array.to_json).as(Array[ContributorStats])
end
# Get the Github branch with `name`.
# Returns `null` if the branch cannot be found.
#
# var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("privat/nit")
+ # var repo = api.load_repo("nitlang/nit")
# assert repo != null
# var branch = api.load_branch(repo, "master")
# assert branch.name == "master"
# assert branch.commit isa Commit
fun load_branch(repo: Repo, name: String): nullable Branch do
- var branch = new Branch(self, repo, name)
- return branch.load_from_github
+ return load_from_github("/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 load_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
end
# Get the Github commit with `sha`.
# Returns `null` if the commit cannot be found.
#
# var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("privat/nit")
+ # var repo = api.load_repo("nitlang/nit")
# assert repo != null
# var commit = api.load_commit(repo, "64ce1f")
# assert commit isa Commit
fun load_commit(repo: Repo, sha: String): nullable Commit do
- var commit = new Commit(self, repo, sha)
- return commit.load_from_github
+ return load_from_github("/repos/{repo.full_name}/commits/{sha}").as(nullable Commit)
end
# Get the Github issue #`number`.
# Returns `null` if the issue cannot be found.
#
# var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("privat/nit")
+ # var repo = api.load_repo("nitlang/nit")
# assert repo != null
# var issue = api.load_issue(repo, 1)
# assert issue.title == "Doc"
fun load_issue(repo: Repo, number: Int): nullable Issue do
- var issue = new Issue(self, repo, number)
- return issue.load_from_github
+ return load_from_github("/repos/{repo.full_name}/issues/{number}").as(nullable Issue)
+ end
+
+ # List of event on this issue.
+ fun load_issue_comments(repo: Repo, issue: Issue): Array[IssueComment] do
+ var res = new Array[IssueComment]
+ var count = issue.comments or else 0
+ var page = 1
+ loop
+ var array = get("/repos/{repo.full_name}/issues/{issue.number}/comments?page={page}")
+ if not array isa JsonArray then break
+ if array.is_empty then break
+ for obj in array do
+ if not obj isa JsonObject then continue
+ var id = obj["id"].as(Int)
+ var comment = load_issue_comment(repo, id)
+ if comment == null then continue
+ res.add(comment)
+ end
+ if res.length >= count then break
+ page += 1
+ end
+ return res
+ end
+
+ # List of events on this issue.
+ fun load_issue_events(repo: Repo, issue: Issue): Array[IssueEvent] do
+ var res = new Array[IssueEvent]
+ var key = "/repos/{repo.full_name}/issues/{issue.number}"
+ var page = 1
+ loop
+ var array = get("{key}/events?page={page}")
+ if not array isa JsonArray or array.is_empty then break
+ for obj in array do
+ if not obj isa JsonObject then continue
+ var event = deserialize(obj.to_json).as(nullable IssueEvent)
+ if event == null then continue
+ res.add event
+ end
+ page += 1
+ end
+ return res
end
# Get the Github pull request #`number`.
# Returns `null` if the pull request cannot be found.
#
# var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("privat/nit")
+ # var repo = api.load_repo("nitlang/nit")
# assert repo != null
# var pull = api.load_pull(repo, 1)
# assert pull.title == "Doc"
# assert pull.user.login == "Morriar"
fun load_pull(repo: Repo, number: Int): nullable PullRequest do
- var pull = new PullRequest(self, repo, number)
- return pull.load_from_github
+ return load_from_github("/repos/{repo.full_name}/pulls/{number}").as(nullable PullRequest)
end
# Get the Github label with `name`.
# Returns `null` if the label cannot be found.
#
# var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("privat/nit")
+ # var repo = api.load_repo("nitlang/nit")
# assert repo != null
# var labl = api.load_label(repo, "ok_will_merge")
# assert labl != null
fun load_label(repo: Repo, name: String): nullable Label do
- var labl = new Label(self, repo, name)
- return labl.load_from_github
+ return load_from_github("/repos/{repo.full_name}/labels/{name}").as(nullable Label)
end
# Get the Github milestone with `id`.
# Returns `null` if the milestone cannot be found.
#
# var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("privat/nit")
+ # var repo = api.load_repo("nitlang/nit")
# assert repo != null
# var stone = api.load_milestone(repo, 4)
# assert stone.title == "v1.0prealpha"
fun load_milestone(repo: Repo, id: Int): nullable Milestone do
- var milestone = new Milestone(self, repo, id)
- return milestone.load_from_github
+ return load_from_github("/repos/{repo.full_name}/milestones/{id}").as(nullable Milestone)
end
# Get the Github issue event with `id`.
# Returns `null` if the event cannot be found.
#
# var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("privat/nit")
+ # var repo = api.load_repo("nitlang/nit")
# assert repo isa Repo
# var event = api.load_issue_event(repo, 199674194)
+ # assert event isa IssueEvent
# assert event.actor.login == "privat"
# assert event.event == "labeled"
+ # assert event.labl isa Label
# assert event.labl.name == "need_review"
- # assert event.issue.number == 945
fun load_issue_event(repo: Repo, id: Int): nullable IssueEvent do
- var event = new IssueEvent(self, repo, id)
- event.load_from_github
- if was_error then return null
- return event
+ return load_from_github("/repos/{repo.full_name}/issues/events/{id}").as(nullable IssueEvent)
end
# Get the Github commit comment with `id`.
# Returns `null` if the comment cannot be found.
#
# var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("privat/nit")
+ # var repo = api.load_repo("nitlang/nit")
# assert repo != null
# var comment = api.load_commit_comment(repo, 8982707)
# assert comment.user.login == "Morriar"
- # assert comment.body == "For testing purposes..."
- # assert comment.commit.sha == "7eacb86d1e24b7e72bc9ac869bf7182c0300ceca"
+ # assert comment.body == "For testing purposes...\n"
+ # assert comment.commit_id == "7eacb86d1e24b7e72bc9ac869bf7182c0300ceca"
fun load_commit_comment(repo: Repo, id: Int): nullable CommitComment do
- var comment = new CommitComment(self, repo, id)
- return comment.load_from_github
+ return load_from_github("/repos/{repo.full_name}/comments/{id}").as(nullable CommitComment)
end
# Get the Github issue comment with `id`.
# Returns `null` if the comment cannot be found.
#
# var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("privat/nit")
+ # var repo = api.load_repo("nitlang/nit")
# assert repo != null
# var comment = api.load_issue_comment(repo, 6020149)
# assert comment.user.login == "privat"
# assert comment.created_at.to_s == "2012-05-30T20:16:54Z"
- # assert comment.issue.number == 10
+ # assert comment.issue_number == 10
fun load_issue_comment(repo: Repo, id: Int): nullable IssueComment do
- var comment = new IssueComment(self, repo, id)
- return comment.load_from_github
+ return load_from_github("/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.
#
# var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("privat/nit")
+ # var repo = api.load_repo("nitlang/nit")
# assert repo != null
# var comment = api.load_review_comment(repo, 21010363)
# assert comment.path == "src/modelize/modelize_property.nit"
# assert comment.original_position == 26
- # assert comment.pull.number == 945
+ # assert comment.pull_number == 945
fun load_review_comment(repo: Repo, id: Int): nullable ReviewComment do
- var comment = new ReviewComment(self, repo, id)
- return comment.load_from_github
+ return load_from_github("/repos/{repo.full_name}/pulls/comments/{id}").as(nullable ReviewComment)
end
end
#
# Mainly a Nit wrapper around a JSON objet.
abstract class GithubEntity
+ serialize
- # Github API instance.
- var api: GithubAPI
-
- # FIXME constructor should be private
-
- # Key used to access this entity from Github api base.
- fun key: String is abstract
-
- # JSON representation of `self`.
- #
- # This is the same json structure than used by Github API.
- var json: JsonObject is noinit, protected writable
-
- # Load `json` from Github API.
- private fun load_from_github: nullable SELF do
- json = api.load_from_github(key)
- if api.was_error then return null
- return self
- end
-
- redef fun to_s do return json.to_json
+ # Github page url.
+ var html_url: nullable String is writable
end
-# A Github user.
+# A Github user
#
+# Provides access to [Github user data](https://developer.github.com/v3/users/).
# Should be accessed from `GithubAPI::load_user`.
-#
-# See <https://developer.github.com/v3/users/>.
class User
- super GithubEntity
-
- redef var key is lazy do return "users/{login}"
+ super GitUser
+ serialize
# Github login.
- var login: String
+ var login: String is writable
- # Init `self` from a `json` object.
- init from_json(api: GithubAPI, json: JsonObject) do
- init(api, json["login"].to_s)
- self.json = json
- end
+ # Avatar image url for this user.
+ var avatar_url: nullable String is writable
- # Github User page url.
- fun html_url: String do return json["html_url"].to_s
+ # User public name if any.
+ var name: nullable String is writable
- # Avatar image url for this user.
- fun avatar_url: String do return json["avatar_url"].to_s
+ # User public email if any.
+ var email: nullable String is writable
+
+ # User public blog if any.
+ var blog: nullable String is writable
end
# A Github repository.
#
+# Provides access to [Github repo data](https://developer.github.com/v3/repos/).
# Should be accessed from `GithubAPI::load_repo`.
-#
-# See <https://developer.github.com/v3/repos/>.
class Repo
super GithubEntity
-
- redef var key is lazy do return "repos/{full_name}"
+ serialize
# Repo full name on Github.
- var full_name: String
-
- # Init `self` from a `json` object.
- init from_json(api: GithubAPI, json: JsonObject) do
- init(api, json["full_name"].to_s)
- self.json = json
- end
+ var full_name: String is writable
# Repo short name on Github.
- fun name: String do return json["name"].to_s
-
- # Github User page url.
- fun html_url: String do return json["html_url"].to_s
+ var name: String is writable
# Get the repo owner.
- fun owner: User do
- return new User.from_json(api, json["owner"].as(JsonObject))
- end
-
- # List of branches associated with their names.
- fun branches: Map[String, Branch] do
- api.message(1, "Get branches for {full_name}")
- var array = api.get("repos/{full_name}/branches")
- var res = new HashMap[String, Branch]
- if not array isa JsonArray then return res
- for obj in array do
- if not obj isa JsonObject then continue
- var name = obj["name"].to_s
- res[name] = new Branch.from_json(api, self, obj)
- end
- return res
- end
-
- # List of issues associated with their ids.
- fun issues: Map[Int, Issue] do
- api.message(1, "Get issues for {full_name}")
- var res = new HashMap[Int, Issue]
- var issue = last_issue
- if issue == null then return res
- res[issue.number] = issue
- while issue.number > 1 do
- issue = api.load_issue(self, issue.number - 1)
- assert issue isa Issue
- res[issue.number] = issue
- end
- return res
- end
-
- # Get the last published issue.
- fun last_issue: nullable Issue do
- var array = api.get("repos/{full_name}/issues")
- if not array isa JsonArray then return null
- if array.is_empty then return null
- var obj = array.first
- if not obj isa JsonObject then return null
- return new Issue.from_json(api, self, obj)
- end
-
- # List of labels associated with their names.
- fun labels: Map[String, Label] do
- api.message(1, "Get labels for {full_name}")
- var array = api.get("repos/{full_name}/labels")
- var res = new HashMap[String, Label]
- if not array isa JsonArray then return res
- for obj in array do
- if not obj isa JsonObject then continue
- var name = obj["name"].to_s
- res[name] = new Label.from_json(api, self, obj)
- end
- return res
- end
-
- # List of milestones associated with their ids.
- fun milestones: Map[Int, Milestone] do
- api.message(1, "Get milestones for {full_name}")
- var array = api.get("repos/{full_name}/milestones")
- var res = new HashMap[Int, Milestone]
- if array isa JsonArray then
- for obj in array do
- if not obj isa JsonObject then continue
- var number = obj["number"].as(Int)
- res[number] = new Milestone.from_json(api, self, obj)
- end
- end
- return res
- end
+ var owner: User is writable
- # List of pull-requests associated with their ids.
- #
- # Implementation notes: because PR numbers are not consecutive,
- # PR are loaded from pages.
- # See: https://developer.github.com/v3/pulls/#list-pull-requests
- fun pulls: Map[Int, PullRequest] do
- api.message(1, "Get pulls for {full_name}")
- var res = new HashMap[Int, PullRequest]
- var page = 1
- var array = api.get("{key}/pulls?page={page}").as(JsonArray)
- while not array.is_empty do
- for obj in array do
- if not obj isa JsonObject then continue
- var number = obj["number"].as(Int)
- res[number] = new PullRequest.from_json(api, self, obj)
- end
- page += 1
- array = api.get("{key}/pulls?page={page}").as(JsonArray)
- end
- return res
- end
-
- # Repo default branch.
- fun default_branch: Branch do
- var name = json["default_branch"].to_s
- var branch = api.load_branch(self, name)
- assert branch isa Branch
- return branch
- end
-end
-
-# A `RepoEntity` is something contained in a `Repo`.
-abstract class RepoEntity
- super GithubEntity
-
- # Repo that contains `self`.
- var repo: Repo
-
- # Init `self` from a `json` object.
- init from_json(api: GithubAPI, repo: Repo, json: JsonObject) do
- self.api = api
- self.repo = repo
- self.json = json
- end
+ # Repo default branch name.
+ var default_branch: String is writable
end
# A Github branch.
#
# See <https://developer.github.com/v3/repos/#list-branches>.
class Branch
- super RepoEntity
-
- redef var key is lazy do return "{repo.key}/branches/{name}"
+ super GithubEntity
+ serialize
# Branch name.
- var name: String
-
- redef init from_json(api, repo, json) do
- self.name = json["name"].to_s
- super
- end
+ var name: String is writable
# Get the last commit of `self`.
- fun commit: Commit do
- return new Commit.from_json(api, repo, json["commit"].as(JsonObject))
- end
-
- # List all commits in `self`.
- #
- # This can be long depending on the branch size.
- # Commit are returned in an unspecified order.
- fun commits: Array[Commit] do
- var res = new Array[Commit]
- var done = new HashSet[String]
- var todos = new Array[Commit]
- todos.add commit
- while not todos.is_empty do
- var commit = todos.pop
- if done.has(commit.sha) then continue
- done.add commit.sha
- res.add commit
- for parent in commit.parents do
- todos.add parent
- end
- end
- return res
- end
+ var commit: Commit is writable
end
# A Github commit.
#
# Should be accessed from `GithubAPI::load_commit`.
#
-# See <https://developer.github.com/v3/commits/>.
+# See <https://developer.github.com/v3/repos/commits/>.
class Commit
- super RepoEntity
-
- redef var key is lazy do return "{repo.key}/commits/{sha}"
+ super GithubEntity
+ serialize
# Commit SHA.
- var sha: String
-
- redef init from_json(api, repo, json) do
- self.sha = json["sha"].to_s
- super
- end
+ var sha: String is writable
# Parent commits of `self`.
- fun parents: Array[Commit] do
- var res = new Array[Commit]
- var parents = json["parents"]
- if not parents isa JsonArray then return res
- for obj in parents do
- if not obj isa JsonObject then continue
- res.add(api.load_commit(repo, obj["sha"].to_s).as(not null))
- end
- return res
- end
+ var parents: nullable Array[Commit] = null is writable
# Author of the commit.
- fun author: nullable User do
- if not json.has_key("author") then return null
- var user = json["author"]
- if not user isa JsonObject then return null
- return new User.from_json(api, user)
- end
+ var author: nullable GitUser is writable
# Committer of the commit.
- fun committer: nullable User do
- if not json.has_key("committer") then return null
- var user = json["author"]
- if not user isa JsonObject then return null
- return new User.from_json(api, user)
- end
+ var committer: nullable GitUser is writable
+
+ # Authoring date as String.
+ var author_date: nullable String is writable
# Authoring date as ISODate.
- fun author_date: ISODate do
- var commit = json["commit"].as(JsonObject)
- var author = commit["author"].as(JsonObject)
- return new ISODate.from_string(author["date"].to_s)
+ 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 commit_date: ISODate do
- var commit = json["commit"].as(JsonObject)
- var author = commit["committer"].as(JsonObject)
- return new ISODate.from_string(author["date"].to_s)
+ 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
+
+ # Commit message.
+ var message: nullable String is writable
+
+ # Git commit representation linked to this commit.
+ var commit: nullable GitCommit
+end
+
+# A Git Commit representation
+class GitCommit
+ super GithubEntity
+ serialize
+
+ # Commit SHA.
+ var sha: nullable String is writable
+
+ # Parent commits of `self`.
+ var parents: nullable Array[GitCommit] = null is writable
+
+ # Author of the commit.
+ var author: nullable GitUser is writable
+
+ # Committer of the commit.
+ var committer: nullable GitUser is writable
+
# Commit message.
- fun message: String do return json["commit"].as(JsonObject)["message"].to_s
+ var message: nullable String is writable
+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.
#
# See <https://developer.github.com/v3/issues/>.
class Issue
- super RepoEntity
-
- redef var key is lazy do return "{repo.key}/issues/{number}"
+ super GithubEntity
+ serialize
# Issue Github ID.
- var number: Int
+ var number: Int is writable
- redef init from_json(api, repo, json) do
- self.number = json["number"].as(Int)
- super
- end
+ # Issue id.
+ var id: nullable Int is writable
# Issue title.
- fun title: String do return json["title"].to_s
+ var title: String is writable
# User that created this issue.
- fun user: User do
- return new User.from_json(api, json["user"].as(JsonObject))
- end
+ var user: nullable User is writable
# List of labels on this issue associated to their names.
- fun labels: Map[String, Label] do
- var res = new HashMap[String, Label]
- for obj in json["labels"].as(JsonArray) do
- if not obj isa JsonObject then continue
- var name = obj["name"].to_s
- res[name] = new Label.from_json(api, repo, obj)
- end
- return res
- end
+ var labels: nullable Array[Label] is writable
# State of the issue on Github.
- fun state: String do return json["state"].to_s
+ var state: String is writable
# Is the issue locked?
- fun locked: Bool do return json["locked"].as(Bool)
+ var locked: nullable Bool is writable
# Assigned `User` (if any).
- fun assignee: nullable User do
- var assignee = json["assignee"]
- if not assignee isa JsonObject then return null
- return new User.from_json(api, assignee)
- end
+ var assignee: nullable User is writable
# `Milestone` (if any).
- fun milestone: nullable Milestone do
- var milestone = json["milestone"]
- if not milestone isa JsonObject then return null
- return new Milestone.from_json(api, repo, milestone)
- end
-
- # List of comments made on this issue.
- fun comments: Array[IssueComment] do
- var res = new Array[IssueComment]
- var count = comments_count
- var page = 1
- var array = api.get("{key}/comments?page={page}")
- if not array isa JsonArray then
- return res
- end
- while not array.is_empty and res.length < count do
- for obj in array do
- if not obj isa JsonObject then continue
- var id = obj["id"].as(Int)
- res.add(api.load_issue_comment(repo, id).as(not null))
- end
- page += 1
- array = api.get("{key}/comments?page={page}").as(JsonArray)
- end
- return res
- end
+ var milestone: nullable Milestone is writable
# Number of comments on this issue.
- fun comments_count: Int do return json["comments"].to_s.to_i
+ var comments: nullable Int is writable
- # Creation time in ISODate format.
- fun created_at: ISODate do
- return new ISODate.from_string(json["created_at"].to_s)
- end
+ # Creation time as String.
+ var created_at: String is writable
- # Last update time in ISODate format (if any).
- fun updated_at: nullable ISODate do
- var res = json["updated_at"]
- if res == null then return null
- return new ISODate.from_string(res.to_s)
+ # Creation time as ISODate.
+ fun iso_created_at: ISODate do
+ return new ISODate.from_string(created_at)
end
- # Close time in ISODate format (if any).
- fun closed_at: nullable ISODate do
- var res = json["closed_at"]
- if res == null then return null
- return new ISODate.from_string(res.to_s)
- end
+ # Last update time as String (if any).
+ var updated_at: nullable String is writable
- # TODO link to pull request
+ # 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
- # Full description of the issue.
- fun body: String do return json["body"].to_s
+ # Close time as String (if any).
+ var closed_at: nullable String is writable
- # List of events on this issue.
- fun events: Array[IssueEvent] do
- var res = new Array[IssueEvent]
- var page = 1
- var array = api.get("{key}/events?page={page}").as(JsonArray)
- while not array.is_empty do
- for obj in array do
- if not obj isa JsonObject then continue
- res.add new IssueEvent.from_json(api, repo, obj)
- end
- page += 1
- array = api.get("{key}/events?page={page}").as(JsonArray)
- end
- return res
+ # 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
+
# User that closed this issue (if any).
- fun closed_by: nullable User do
- var closer = json["closed_by"]
- if not closer isa JsonObject then return null
- return new User.from_json(api, closer)
- end
+ var closed_by: nullable User is writable
+
+ # Is this issue linked to a pull request?
+ var is_pull_request: Bool = false is writable
end
# A Github pull request.
# See <https://developer.github.com/v3/pulls/>.
class PullRequest
super Issue
+ serialize
- redef var key is lazy do return "{repo.key}/pulls/{number}"
+ # Merge time as String (if any).
+ var merged_at: nullable String is writable
- # Merge time in ISODate format (if any).
- fun merged_at: nullable ISODate do
- var res = json["merged_at"]
- if res == null then return null
- return new ISODate.from_string(res.to_s)
+ # 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.
- fun merge_commit_sha: String do return json["merge_commit_sha"].to_s
+ var merge_commit_sha: nullable String is writable
# Count of comments made on the pull request diff.
- fun review_comments: Int do return json["review_comments"].to_s.to_i
+ var review_comments: Int is writable
# Pull request head (can be a commit SHA or a branch name).
- fun head: PullRef do
- var json = json["head"].as(JsonObject)
- return new PullRef(api, json)
- end
+ var head: PullRef is writable
# Pull request base (can be a commit SHA or a branch name).
- fun base: PullRef do
- var json = json["base"].as(JsonObject)
- return new PullRef(api, json)
- end
+ var base: PullRef is writable
# Is this pull request merged?
- fun merged: Bool do return json["merged"].as(Bool)
+ var merged: Bool is writable
# Is this pull request mergeable?
- fun mergeable: Bool do return json["mergeable"].as(Bool)
+ var mergeable: nullable Bool is writable
# Mergeable state of this pull request.
#
# See <https://developer.github.com/v3/pulls/#list-pull-requests>.
- fun mergeable_state: Int do return json["mergeable_state"].to_s.to_i
+ var mergeable_state: String is writable
# User that merged this pull request (if any).
- fun merged_by: nullable User do
- var merger = json["merged_by"]
- if not merger isa JsonObject then return null
- return new User.from_json(api, merger)
- end
+ var merged_by: nullable User is writable
# Count of commits in this pull request.
- fun commits: Int do return json["commits"].to_s.to_i
+ var commits: Int is writable
# Added line count.
- fun additions: Int do return json["additions"].to_s.to_i
+ var additions: Int is writable
# Deleted line count.
- fun deletions: Int do return json["deletions"].to_s.to_i
+ var deletions: Int is writable
# Changed files count.
- fun changed_files: Int do return json["changed_files"].to_s.to_i
+ var changed_files: Int is writable
+
+ # URL to patch file
+ var patch_url: nullable String is writable
end
# A pull request reference (used for head and base).
class PullRef
-
- # Api instance that maintains self.
- var api: GithubAPI
-
- # JSON representation.
- var json: JsonObject
+ serialize
# Label pointed by `self`.
- fun labl: String do return json["label"].to_s
+ var labl: String is writable, serialize_as("label")
# Reference pointed by `self`.
- fun ref: String do return json["ref"].to_s
+ var ref: String is writable
# Commit SHA pointed by `self`.
- fun sha: String do return json["sha"].to_s
+ var sha: String is writable
# User pointed by `self`.
- fun user: User do
- return new User.from_json(api, json["user"].as(JsonObject))
- end
+ var user: User is writable
- # Repo pointed by `self`.
- fun repo: Repo do
- return new Repo.from_json(api, json["repo"].as(JsonObject))
- end
+ # Repo pointed by `self` (if any).
+ #
+ # A `null` value means the `repo` was deleted.
+ var repo: nullable Repo is writable
end
# A Github label.
#
# See <https://developer.github.com/v3/issues/labels/>.
class Label
- super RepoEntity
-
- redef var key is lazy do return "{repo.key}/labels/{name}"
+ super GithubEntity
+ serialize
# Label name.
- var name: String
-
- redef init from_json(api, repo, json) do
- self.name = json["name"].to_s
- super
- end
+ var name: String is writable
# Label color code.
- fun color: String do return json["color"].to_s
+ var color: String is writable
end
# A Github milestone.
#
# See <https://developer.github.com/v3/issues/milestones/>.
class Milestone
- super RepoEntity
-
- redef var key is lazy do return "{repo.key}/milestones/{number}"
+ super GithubEntity
+ serialize
# The milestone id on Github.
- var number: Int
-
- redef init from_json(api, repo, json) do
- super
- self.number = json["number"].as(Int)
- end
+ var number: nullable Int = null is writable
# Milestone title.
- fun title: String do return json["title"].to_s
+ var title: String is writable
# Milestone long description.
- fun description: String do return json["description"].to_s
+ var description: nullable String is writable
# Count of opened issues linked to this milestone.
- fun open_issues: Int do return json["open_issues"].to_s.to_i
+ var open_issues: nullable Int = null is writable
# Count of closed issues linked to this milestone.
- fun closed_issues: Int do return json["closed_issues"].to_s.to_i
+ var closed_issues: nullable Int = null is writable
# Milestone state.
- fun state: String do return json["state"].to_s
+ var state: nullable String is writable
- # Creation time in ISODate format.
- fun created_at: ISODate do
- return new ISODate.from_string(json["created_at"].to_s)
+ # 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.
- fun creator: User do
- return new User.from_json(api, json["creator"].as(JsonObject))
- end
+ 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 due_on: nullable ISODate do
- var res = json["updated_at"]
- if res == null then return null
- return new ISODate.from_string(res.to_s)
+ 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
- # Update time in ISODate format (if any).
- fun updated_at: nullable ISODate do
- var res = json["updated_at"]
- if res == null then return null
- return new ISODate.from_string(res.to_s)
+ # 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 in ISODate format (if any).
- fun closed_at: nullable ISODate do
- var res = json["closed_at"]
- if res == null then return null
- return new ISODate.from_string(res.to_s)
+ # 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
# * `IssueComment` are made on an issue or pull request page.
# * `ReviewComment` are made on the diff associated to a pull request.
abstract class Comment
- super RepoEntity
+ super GithubEntity
+ serialize
# Identifier of this comment.
- var id: Int
-
- redef init from_json(api, repo, json) do
- self.id = json["id"].as(Int)
- super
- end
+ var id: Int is writable
# User that made this comment.
- fun user: User do
- return new User.from_json(api, json["user"].as(JsonObject))
- end
+ var user: User is writable
+
+ # Creation time as String.
+ var created_at: String is writable
- # Creation time in ISODate format.
- fun created_at: ISODate do
- return new ISODate.from_string(json["created_at"].to_s)
+ # Creation time as ISODate.
+ fun iso_created_at: nullable ISODate do
+ return new ISODate.from_string(created_at)
end
- # Last update time in ISODate format (if any).
- fun updated_at: nullable ISODate do
- if not json.has_key("updated_at") then return null
- return new ISODate.from_string(json["updated_at"].to_s)
+ # 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.
- fun body: String do return json["body"].to_s
+ var body: String is writable
+
+ # Does the comment contain an acknowledgement (+1)
+ fun is_ack: Bool do
+ return body.has("\\+1\\b".to_re) or body.has(":+1:") or body.has(":shipit:")
+ end
end
# A comment made on a commit.
class CommitComment
super Comment
-
- redef var key is lazy do return "{repo.key}/comments/{id}"
+ serialize
# Commented commit.
- fun commit: Commit do
- return api.load_commit(repo, json["commit_id"].to_s).as(not null)
- end
+ var commit_id: String is writable
# Position of the comment on the line.
- fun position: nullable String do
- if not json.has_key("position") then return null
- var res = json["position"]
- if res == null then return null
- return res.to_s
- end
+ var position: nullable Int is writable
# Line of the comment.
- fun line: nullable String do
- if not json.has_key("line") then return null
- var res = json["line"]
- if res == null then return null
- return res.to_s
- end
+ var line: nullable Int is writable
# Path of the commented file.
- fun path: String do return json["path"].to_s
+ var path: nullable String is writable
end
# Comments made on Github issue and pull request pages.
# See <https://developer.github.com/v3/issues/comments/>.
class IssueComment
super Comment
-
- redef var key is lazy do return "{repo.key}/issues/comments/{id}"
+ serialize
# Issue that contains `self`.
- fun issue: Issue do
- var number = issue_url.split("/").last.to_i
- return api.load_issue(repo, number).as(not null)
- end
+ fun issue_number: Int do return issue_url.split("/").last.to_i
# Link to the issue document on API.
- fun issue_url: String do return json["issue_url"].to_s
+ var issue_url: String is writable
end
# Comments made on Github pull request diffs.
# See <https://developer.github.com/v3/pulls/comments/>.
class ReviewComment
super Comment
-
- redef var key is lazy do return "{repo.key}/pulls/comments/{id}"
+ serialize
# Pull request that contains `self`.
- fun pull: PullRequest do
- var number = pull_request_url.split("/").last.to_i
- return api.load_pull(repo, number).as(not null)
- end
+ fun pull_number: Int do return pull_request_url.split("/").last.to_i
# Link to the pull request on API.
- fun pull_request_url: String do return json["pull_request_url"].to_s
+ var pull_request_url: String is writable
# Diff hunk.
- fun diff_hunk: String do return json["diff_hunk"].to_s
+ var diff_hunk: String is writable
# Path of commented file.
- fun path: String do return json["path"].to_s
+ var path: String is writable
# Position of the comment on the file.
- fun position: Int do return json["position"].to_s.to_i
+ var position: nullable Int is writable
# Original position in the diff.
- fun original_position: Int do return json["original_position"].to_s.to_i
+ var original_position: Int is writable
# Commit referenced by this comment.
- fun commit_id: String do return json["commit_id"].to_s
+ var commit_id: String is writable
# Original commit id.
- fun original_commit_id: String do return json["original_commit_id"].to_s
+ var original_commit_id: String is writable
end
# An event that occurs on a Github `Issue`.
#
# See <https://developer.github.com/v3/issues/events/>.
class IssueEvent
- super RepoEntity
-
- redef var key is lazy do return "{repo.key}/issues/events/{id}"
+ super GithubEntity
+ serialize
# Event id on Github.
- var id: Int
-
- redef init from_json(api, repo, json) do
- self.id = json["id"].as(Int)
- super
- end
-
- # Issue that contains `self`.
- fun issue: Issue do
- return new Issue.from_json(api, repo, json["issue"].as(JsonObject))
- end
+ var id: Int is writable
# User that initiated the event.
- fun actor: User do
- return new User.from_json(api, json["actor"].as(JsonObject))
- end
+ var actor: User is writable
- # Creation time in ISODate format.
- fun created_at: ISODate do
- return new ISODate.from_string(json["created_at"].to_s)
+ # 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.
- fun event: String do return json["event"].to_s
+ var event: String is writable
# Commit linked to this event (if any).
- fun commit_id: nullable String do
- var res = json["commit_id"]
- if res == null then return null
- return res.to_s
- end
+ var commit_id: nullable String is writable
# Label linked to this event (if any).
- fun labl: nullable Label do
- var res = json["label"]
- if not res isa JsonObject then return null
- return new Label.from_json(api, repo, res)
- end
+ var labl: nullable Label is writable, serialize_as("label")
# User linked to this event (if any).
- fun assignee: nullable User do
- var res = json["assignee"]
- if not res isa JsonObject then return null
- return new User.from_json(api, res)
- end
+ var assignee: nullable User is writable
# Milestone linked to this event (if any).
- fun milestone: nullable Milestone do
- var res = json["milestone"]
- if not res isa JsonObject then return null
- return new Milestone.from_json(api, repo, res)
- end
+ var milestone: nullable Milestone is writable
# Rename linked to this event (if any).
- fun rename: nullable RenameAction do
- var res = json["rename"]
- if res == null then return null
- return new RenameAction(res.as(JsonObject))
- end
+ var rename: nullable RenameAction is writable
end
# A rename action maintains the name before and after a renaming action.
class RenameAction
-
- # JSON content.
- var json: JsonObject
+ serialize
# Name before renaming.
- fun from: String do return json["from"].to_s
+ var from: String is writable
# Name after renaming.
- fun to: String do return json["to"].to_s
+ var to: String is writable
+end
+
+#
+# Should be accessed from `Repo::contrib_stats`.
+#
+# See <https://developer.github.com/v3/repos/statistics/>.
+class ContributorStats
+ super Comparable
+ serialize
+
+ redef type OTHER: ContributorStats
+
+ # Github API client.
+ var api: GithubAPI is writable
+
+ # User these statistics are about.
+ var author: User is writable
+
+ # Total number of commit.
+ var total: Int is writable
+
+ # Are of weeks of activity with detailed statistics.
+ var weeks: JsonArray is writable
+
+ # ContributorStats can be compared on the total amount of commits.
+ redef fun <(o) do return total < o.total
+end
+
+# A Github file representation.
+#
+# Mostly a wrapper around a json object.
+class GithubFile
+ serialize
+
+ # File name.
+ var filename: String is writable
+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
+ 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"
+ end
+ return null
+ end
+
+ redef fun deserialize_class(name) do
+ if name == "Issue" then
+ var issue = super.as(Issue)
+ if path.last.has_key("pull_request") then
+ issue.is_pull_request = true
+ end
+ return issue
+ else if name == "Commit" then
+ var commit = super.as(Commit)
+ var git_commit = commit.commit
+ if git_commit != null then commit.message = git_commit.message
+ return commit
+ end
+ return super
+ end
end