interpreter&vm: handle multi-iterator
[nit.git] / lib / github / api.nit
index 815172a..dc4e586 100644 (file)
@@ -12,7 +12,7 @@
 # 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.
 #
@@ -21,11 +21,9 @@ module api
 
 import github_curl
 
-# 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.
@@ -36,10 +34,10 @@ import github_curl
 # 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"
 #
@@ -49,9 +47,16 @@ import github_curl
 # ~~~
 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
@@ -89,7 +94,7 @@ class GithubAPI
        # 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"
        #
@@ -133,16 +138,16 @@ class GithubAPI
 
        # 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): JsonObject do
                message(1, "Get {key} (github)")
                var res = get(key)
                if was_error then return new JsonObject
                return res.as(JsonObject)
        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")
@@ -154,12 +159,12 @@ class GithubAPI
 
        # 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.owner.login == "nitlang"
        #     assert repo.default_branch.name == "master"
        fun load_repo(full_name: String): nullable Repo do
                var repo = new Repo(self, full_name)
@@ -171,7 +176,7 @@ class GithubAPI
        # 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"
@@ -186,7 +191,7 @@ class GithubAPI
        # 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
@@ -200,7 +205,7 @@ class GithubAPI
        # 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"
@@ -214,7 +219,7 @@ class GithubAPI
        # 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"
@@ -229,7 +234,7 @@ class GithubAPI
        # 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
@@ -243,7 +248,7 @@ class GithubAPI
        # 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"
@@ -257,7 +262,7 @@ class GithubAPI
        # 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.actor.login == "privat"
@@ -266,9 +271,7 @@ class GithubAPI
        #     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 event.load_from_github
        end
 
        # Get the Github commit comment with `id`.
@@ -276,7 +279,7 @@ class GithubAPI
        # 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"
@@ -292,7 +295,7 @@ class GithubAPI
        # 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"
@@ -308,7 +311,7 @@ class GithubAPI
        # 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"
@@ -346,13 +349,15 @@ abstract class GithubEntity
        end
 
        redef fun to_s do return json.to_json
+
+       # Github page url.
+       fun html_url: String do return json["html_url"].to_s
 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
 
@@ -367,18 +372,14 @@ class User
                self.json = json
        end
 
-       # Github User page url.
-       fun html_url: String do return json["html_url"].to_s
-
        # Avatar image url for this user.
        fun avatar_url: String do return json["avatar_url"].to_s
 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
 
@@ -396,9 +397,6 @@ class Repo
        # 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
-
        # Get the repo owner.
        fun owner: User do
                return new User.from_json(api, json["owner"].as(JsonObject))
@@ -433,6 +431,27 @@ class Repo
                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_issues(query: String): nullable Array[Issue] do
+               query = "search/issues?q={query} repo:{full_name}"
+               var response = api.get(query)
+               if api.was_error then return null
+               var arr = response.as(JsonObject)["items"].as(JsonArray)
+               var res = new Array[Issue]
+               for obj in arr do
+                       res.add new Issue.from_json(api, self, obj.as(JsonObject))
+               end
+               return res
+       end
+
        # Get the last published issue.
        fun last_issue: nullable Issue do
                var array = api.get("repos/{full_name}/issues")
@@ -494,6 +513,19 @@ class Repo
                return res
        end
 
+       # List of contributor related statistics.
+       fun contrib_stats: Array[ContributorStats] do
+               api.message(1, "Get contributor stats for {full_name}")
+               var res = new Array[ContributorStats]
+               var array = api.get("{key}/stats/contributors")
+               if array isa JsonArray then
+                       for obj in array do
+                               res.add new ContributorStats.from_json(api, obj.as(JsonObject))
+                       end
+               end
+               return res
+       end
+
        # Repo default branch.
        fun default_branch: Branch do
                var name = json["default_branch"].to_s
@@ -567,7 +599,7 @@ end
 #
 # 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
 
@@ -623,6 +655,17 @@ class Commit
                return new ISODate.from_string(author["date"].to_s)
        end
 
+       # List files staged in this commit.
+       fun files: Array[GithubFile] do
+               var res = new Array[GithubFile]
+               var files = json["files"]
+               if not files isa JsonArray then return res
+               for obj in files do
+                       res.add(new GithubFile(obj.as(JsonObject)))
+               end
+               return res
+       end
+
        # Commit message.
        fun message: String do return json["commit"].as(JsonObject)["message"].to_s
 end
@@ -656,6 +699,7 @@ class Issue
        # List of labels on this issue associated to their names.
        fun labels: Map[String, Label] do
                var res = new HashMap[String, Label]
+               if not json.has_key("labels") then return res
                for obj in json["labels"].as(JsonArray) do
                        if not obj isa JsonObject then continue
                        var name = obj["name"].to_s
@@ -975,6 +1019,12 @@ abstract class Comment
 
        # Comment body text.
        fun body: String do return json["body"].to_s
+
+       # 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.
@@ -1150,3 +1200,52 @@ class RenameAction
        # Name after renaming.
        fun to: String do return json["to"].to_s
 end
+
+# Contributors list with additions, deletions, and commit counts.
+#
+# Should be accessed from `Repo::contrib_stats`.
+#
+# See <https://developer.github.com/v3/repos/statistics/>.
+class ContributorStats
+       super Comparable
+
+       redef type OTHER: ContributorStats
+
+       # Github API client.
+       var api: GithubAPI
+
+       # Json content.
+       var json: JsonObject
+
+       # Init `self` from a `json` object.
+       init from_json(api: GithubAPI, json: JsonObject) do
+               self.api = api
+               self.json = json
+       end
+
+       # User these statistics are about.
+       fun author: User do
+               return new User.from_json(api, json["author"].as(JsonObject))
+       end
+
+       # Total number of commit.
+       fun total: Int do return json["total"].to_s.to_i
+
+       # Are of weeks of activity with detailed statistics.
+       fun weeks: JsonArray do return json["weeks"].as(JsonArray)
+
+       # 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
+
+       # Json content.
+       var json: JsonObject
+
+       # File name.
+       fun filename: String do return json["filename"].to_s
+end