Proof of concept of how a nitcc lexer can be monkey-patched.
maybe not enough for @ppepos, but it's something.
Pull-Request: #2258
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
redef class Deserializer
redef fun deserialize_class(name)
do
- if name == "Array[Beer]" then return new Array[Beer].from_deserializer(self)
- if name == "Array[User]" then return new Array[User].from_deserializer(self)
- if name == "Array[BeerBadge]" then return new Array[BeerBadge].from_deserializer(self)
- if name == "Array[BeerAndRatings]" then return new Array[BeerAndRatings].from_deserializer(self)
- if name == "Array[String]" then return new Array[String].from_deserializer(self)
if name == "Array[UserAndFollowing]" then return new Array[UserAndFollowing].from_deserializer(self)
return super
end
obj["player"] = name
obj["reward"] = achievement.reward
obj["achievement"] = achievement.id
- obj["github_event"] = event.json
+ obj["github_event"] = event
var ge = new GameEvent(game, "achievement_unlocked", obj)
add_event(ge)
game.add_event(ge)
# API client used to get github data.
var api: GithubAPI
+ # Gen a fake id for events
+ fun gen_event_id: String do return get_time.to_s
+
# Issues
# Generate a new IssuesEvent from an issue.
- fun issues_event(action: String, issue: Issue): IssuesEvent do
- var e = new IssuesEvent(api)
- e.action = action
- e.repo = issue.repo
- e.issue = issue
- return e
+ fun issues_event(repo: Repo, action: String, issue: Issue): IssuesEvent do
+ return new IssuesEvent(gen_event_id, action, repo, issue)
end
# Generate a new IssuesEvent with an `opened` action.
- fun issue_open(issue: Issue): IssuesEvent do return issues_event("opened", issue)
+ fun issue_open(repo: Repo, issue: Issue): IssuesEvent do
+ return issues_event(repo, "opened", issue)
+ end
# Generate a new IssuesEvent with an `closed` action.
- fun issue_close(issue: Issue): IssuesEvent do return issues_event("closed", issue)
+ fun issue_close(repo: Repo, issue: Issue): IssuesEvent do
+ return issues_event(repo, "closed", issue)
+ end
# Generate a new IssuesEvent with an `reopened` action.
- fun issue_reopen(issue: Issue): IssuesEvent do return issues_event("reopened", issue)
+ fun issue_reopen(repo: Repo, issue: Issue): IssuesEvent do
+ return issues_event(repo, "reopened", issue)
+ end
# Generate a new IssuesEvent from a IssueEvent.
- fun issue_raw_event(issue: Issue, event: IssueEvent): IssuesEvent do
- var e = issues_event(event.event, issue)
- e.lbl = event.labl
- e.assignee = event.assignee
- return e
+ fun issue_raw_event(repo: Repo, issue: Issue, event: IssueEvent): IssuesEvent do
+ return new IssuesEvent(event.id.to_s, event.event, repo, issue, event.labl, event.assignee)
+ end
+
+ # Generate a new IssueCommentEvent from a IssueComment.
+ fun issue_comment_event(repo: Repo, issue: Issue, comment: IssueComment): IssueCommentEvent do
+ return new IssueCommentEvent(gen_event_id, "created", repo, issue, comment)
end
# Pull requests
# Generate a new PullRequestEvent from a `pull` request.
- fun pull_event(action: String, pull: PullRequest): PullRequestEvent do
- var e = new PullRequestEvent(api)
- e.action = action
- e.repo = pull.repo
- e.pull = pull
- return e
+ fun pull_event(repo: Repo, action: String, pull: PullRequest): PullRequestEvent do
+ return new PullRequestEvent(gen_event_id, action, repo, pull.number, pull)
end
# Generate a new PullRequestEvent with an `opened` action.
- fun pull_open(pull: PullRequest): PullRequestEvent do return pull_event("opened", pull)
+ fun pull_open(repo: Repo, pull: PullRequest): PullRequestEvent do
+ return pull_event(repo, "opened", pull)
+ end
# Generate a new PullRequestEvent with an `closed` action.
- fun pull_close(pull: PullRequest): PullRequestEvent do return pull_event("closed", pull)
+ fun pull_close(repo: Repo, pull: PullRequest): PullRequestEvent do
+ return pull_event(repo, "closed", pull)
+ end
# Generate a new PullRequestEvent with an `reopened` action.
- fun pull_reopen(pull: PullRequest): PullRequestEvent do return pull_event("reopened", pull)
-
- # Generate a new PullRequestEvent from a IssueEvent.
- fun pull_raw_event(pull: PullRequest, event: IssueEvent): PullRequestEvent do
- return pull_event(event.event, pull)
+ fun pull_reopen(repo: Repo, pull: PullRequest): PullRequestEvent do
+ return pull_event(repo, "reopened", pull)
end
- # Generate a new IssueCommentEvent from a IssueComment.
- fun issue_comment_event(issue: Issue, comment: IssueComment): IssueCommentEvent do
- var e = new IssueCommentEvent(api)
- e.action = "created"
- e.repo = issue.repo
- e.issue = issue
- e.comment = comment
- return e
+ # Generate a new PullRequestEvent from a IssueEvent.
+ fun pull_raw_event(repo: Repo, pull: PullRequest, event: IssueEvent): PullRequestEvent do
+ return new PullRequestEvent(event.id.to_s, event.event, repo, pull.number, pull)
end
end
var obj = new JsonObject
obj["player"] = player.name
obj["reward"] = reward
- obj["github_event"] = json
+ obj["github_event"] = self
var event = new GameEvent(player.game, kind, obj)
player.game.add_event(event)
return event
redef fun render_title do
add "<a href=\"{player.url}\">"
add " <img class=\"img-circle\" style=\"width: 30px\""
- add " src=\"{player.user.avatar_url}\" alt=\"{player.name}\">"
+ add " src=\"{player.user.avatar_url or else "#"}\" alt=\"{player.name}\">"
add "</a> {player.link}"
end
<p>
<a href="{{{player.url}}}">
<img class="img-circle" style="width: 80px"
- src="{{{player.user.avatar_url}}}" alt="{{{player.name}}}">
+ src="{{{player.user.avatar_url or else "#"}}}" alt="{{{player.name}}}">
</a>
</p>
<p>{{{player.link}}}</p>
"-involves:{player.name}"
var issues = new ArraySet[Issue]
- var rq = game.repo.search_issues(q)
- if rq != null then issues.add_all rq
- var rq2 = game.repo.search_issues(q2)
- if rq2 != null then issues.add_all rq2
+ issues.add_all game.api.search_repo_issues(game.repo, q)
+ issues.add_all game.api.search_repo_issues(game.repo, q2)
if issues.is_empty then
add "<em>No pull request or issue to review yet...</em>"
return
add """<div class="media">
<a class="media-left" href="{{{uplay.url}}}">
<img class=\"img-circle\" style="width:50px"
- src="{{{user.avatar_url}}}" alt="{{{uplay.name}}}">
+ src="{{{user.avatar_url or else "#"}}}" alt="{{{uplay.name}}}">
</a>
<div class="media-body">
<h4 class="media-heading">
var q2 = "is:open sort:updated-asc assignee:{player.name}"
var issues = new ArraySet[Issue]
- var rq = game.repo.search_issues(q)
- if rq != null then issues.add_all rq
- var rq2 = game.repo.search_issues(q2)
- if rq2 != null then issues.add_all rq2
+ issues.add_all game.api.search_repo_issues(game.repo, q)
+ issues.add_all game.api.search_repo_issues(game.repo, q2)
if issues.is_empty then
add "<em>No work to do yet...</em>"
return
add """<div class="media">
<a class="media-left" href="{{{uplay.url}}}">
<img class=\"img-circle\" style="width:50px"
- src="{{{user.avatar_url}}}" alt="{{{uplay.name}}}">
+ src="{{{user.avatar_url or else "#"}}}" alt="{{{uplay.name}}}">
</a>
<div class="media-body">
<h4 class="media-heading">
<a class="media-left" href="{{{player.url}}}">
<span class="badge progress-bar-warning" style="position: absolute">#1</span>
<img class=\"img-circle\" style="width:50px"
- src="{{{player.user.avatar_url}}}" alt="{{{player.name}}}">
+ src="{{{player.user.avatar_url or else "#"}}}" alt="{{{player.name}}}">
</a>
<div class="media-body">
<h4 class="media-heading">Unlocked first by {{{player.link}}}</h4>
redef class Issue
# Return a HTML link to this Issue.
- fun link: String do return "<a href=\"{html_url}\">#{number}</a>"
+ fun link: String do return "<a href=\"{html_url or else "#"}\">#{number}</a>"
end
redef class Achievement
# Return a HTML link to this Issue.
fun link: String do return "<a href=\"{url}\">{name}</a>"
+ # Render self as a media item.
fun list_item: String do
return """<div class="media">
<div class="media-left" style="width: 50px">
# Load `github_event` data key as a PullRequestEvent.
var pull_event: PullRequestEvent is lazy do
- var obj = event.data["github_event"].as(JsonObject)
- return new PullRequestEvent.from_json(event.game.api, obj)
+ return event.game.api.deserialize(event.data["github_event"].as(JsonObject).to_json).as(PullRequestEvent)
end
# Load `github_event` data key as a IssueCommentEvent.
var issue_comment_event: IssueCommentEvent is lazy do
- var obj = event.data["github_event"].as(JsonObject)
- return new IssueCommentEvent.from_json(event.game.api, obj)
+ return event.game.api.deserialize(event.data["github_event"].as(JsonObject).to_json).as(IssueCommentEvent)
end
# Load `achievement` data key as an Achievement.
<span class="badge progress-bar-success"
style=\"position: absolute\">+{{{reward}}}</span>
<img class=\"img-circle\" style="width:50px"
- src="{{{player.user.avatar_url}}}" alt="{{{player.name}}}">
+ src="{{{player.user.avatar_url or else "#"}}}" alt="{{{player.name}}}">
</a>
<div class="media-body">
<h4 class="media-heading">{{{title}}}</h4>
var issue = api.load_issue(repo, 322)
assert issue != null
- l.apply_event(generator.issue_open(issue), db)
+ l.apply_event(generator.issue_open(repo, issue), db)
var game = load_game("Morriar/nit", db)
assert game.stats.overall["issues"] == 1
assert game.stats.overall["issues_open"] == 1
- l.apply_event(generator.issue_close(issue), db)
+ l.apply_event(generator.issue_close(repo, issue), db)
game = load_game("Morriar/nit", db)
assert game.stats.overall["issues"] == 1
assert game.stats.overall["issues_open"] == 0
- l.apply_event(generator.issue_reopen(issue), db)
+ l.apply_event(generator.issue_reopen(repo, issue), db)
game = load_game("Morriar/nit", db)
assert game.stats.overall["issues"] == 1
assert game.stats.overall["issues_open"] == 1
var issue = api.load_issue(repo, 322)
assert issue != null
- l.apply_event(generator.issue_open(issue), db)
+ l.apply_event(generator.issue_open(repo, issue), db)
var player = new Player(game, "Morriar")
assert player.stats.overall["issues"] == 1
assert player.stats.overall["issues_open"] == 1
- l.apply_event(generator.issue_close(issue), db)
+ l.apply_event(generator.issue_close(repo, issue), db)
player = new Player(game, "Morriar")
assert player.stats.overall["issues"] == 1
assert player.stats.overall["issues_open"] == 0
- l.apply_event(generator.issue_reopen(issue), db)
+ l.apply_event(generator.issue_reopen(repo, issue), db)
player = new Player(game, "Morriar")
assert player.stats.overall["issues"] == 1
assert player.stats.overall["issues_open"] == 1
var pr = api.load_pull(repo, 275)
assert pr != null
- l.apply_event(generator.pull_open(pr), db)
+ l.apply_event(generator.pull_open(repo, pr), db)
var game = load_game("Morriar/nit", db)
assert game.stats.overall["pulls"] == 1
assert game.stats.overall["pulls_open"] == 1
assert game.stats.overall["commits"] == 0
pr.merged = false
- l.apply_event(generator.pull_close(pr), db)
+ l.apply_event(generator.pull_close(repo, pr), db)
game = load_game("Morriar/nit", db)
assert game.stats.overall["pulls"] == 1
assert game.stats.overall["pulls_open"] == 0
assert game.stats.overall["commits"] == 0
- l.apply_event(generator.pull_reopen(pr), db)
+ l.apply_event(generator.pull_reopen(repo, pr), db)
game = load_game("Morriar/nit", db)
assert game.stats.overall["pulls"] == 1
assert game.stats.overall["pulls_open"] == 1
assert game.stats.overall["commits"] == 0
pr.merged = true
- l.apply_event(generator.pull_close(pr), db)
+ l.apply_event(generator.pull_close(repo, pr), db)
game = load_game("Morriar/nit", db)
assert game.stats.overall["pulls"] == 1
assert game.stats.overall["pulls_open"] == 0
assert comment != null
comment.body = "foo bar"
- l.apply_event(generator.issue_comment_event(issue, comment), db)
+ l.apply_event(generator.issue_comment_event(repo, issue, comment), db)
var game = load_game("Morriar/nit", db)
assert game.stats.overall["comments"] == 1
assert game.stats.overall["reviews"] == 0
comment.body = "foo +1 bar"
- l.apply_event(generator.issue_comment_event(issue, comment), db)
+ l.apply_event(generator.issue_comment_event(repo, issue, comment), db)
game = load_game("Morriar/nit", db)
assert game.stats.overall["comments"] == 2
assert game.stats.overall["reviews"] == 1
var pull = api.load_pull(repo, 275)
assert pull != null
- l.apply_event(generator.pull_open(pull), db)
+ l.apply_event(generator.pull_open(repo, pull), db)
var player = new Player(game, "itch76")
assert player.stats.overall["nitcoins"] == 10
pull.merged = false
- l.apply_event(generator.pull_close(pull), db)
+ l.apply_event(generator.pull_close(repo, pull), db)
player = new Player(game, "itch76")
assert player.stats.overall["nitcoins"] == 0
- l.apply_event(generator.pull_reopen(pull), db)
+ l.apply_event(generator.pull_reopen(repo, pull), db)
player = new Player(game, "itch76")
assert player.stats.overall["nitcoins"] == 10
pull.merged = true
- l.apply_event(generator.pull_close(pull), db)
+ l.apply_event(generator.pull_close(repo, pull), db)
player = new Player(game, "itch76")
assert player.stats.overall["nitcoins"] == 12
end
# no review in opened issue
pull.state = "open"
comment.body = "foo bar"
- l.apply_event(generator.issue_comment_event(pull, comment), db)
+ l.apply_event(generator.issue_comment_event(repo, pull, comment), db)
var player = new Player(game, "Morriar")
assert player.stats.overall["nitcoins"] == 0
# review in opened issue
pull.state = "open"
comment.body = "foo +1 bar"
- l.apply_event(generator.issue_comment_event(pull, comment), db)
+ l.apply_event(generator.issue_comment_event(repo, pull, comment), db)
player = new Player(game, "Morriar")
print player.stats.overall["nitcoins"]
assert player.stats.overall["nitcoins"] == 2
# review in closed issue
pull.state = "closed"
comment.body = "foo +1 bar"
- l.apply_event(generator.issue_comment_event(pull, comment), db)
+ l.apply_event(generator.issue_comment_event(repo, pull, comment), db)
player = new Player(game, "Morriar")
assert player.stats.overall["nitcoins"] == 2
# review in reopened issue
pull.state = "open"
comment.body = "foo +1 bar"
- l.apply_event(generator.issue_comment_event(pull, comment), db)
+ l.apply_event(generator.issue_comment_event(repo, pull, comment), db)
player = new Player(game, "Morriar")
assert player.stats.overall["nitcoins"] == 4
end
var player = new Player(game, "Morriar")
player.stats["issues"] = i
player.save
- l.apply_event(generator.issue_open(issue), db)
+ l.apply_event(generator.issue_open(repo, issue), db)
assert player.load_achievements.has_key(id)
end
var player = new Player(game, "Morriar")
var player = new Player(game, "itch76")
player.stats["pulls"] = i
player.save
- l.apply_event(generator.pull_open(pull), db)
+ l.apply_event(generator.pull_open(repo, pull), db)
assert player.load_achievements.has_key(id)
end
var player = new Player(game, "itch76")
var player = new Player(game, "itch76")
player.stats["commits"] = i
player.save
- l.apply_event(generator.pull_close(pull), db)
+ l.apply_event(generator.pull_close(repo, pull), db)
assert player.load_achievements.has_key(id)
end
var player = new Player(game, "itch76")
var player = new Player(game, "Morriar")
player.stats["comments"] = i
player.save
- l.apply_event(generator.issue_comment_event(pull, comment), db)
+ l.apply_event(generator.issue_comment_event(repo, pull, comment), db)
assert player.load_achievements.has_key(id)
end
var player = new Player(game, "Morriar")
assert issue != null
issue.title = "nitdoc ffi"
- l.apply_event(generator.issue_open(issue), db)
+ l.apply_event(generator.issue_open(repo, issue), db)
var player = new Player(game, "Morriar")
assert player.load_achievements.has_key("issue_about_nitdoc")
assert player.load_achievements.has_key("issue_about_ffi")
assert comment != null
comment.body = "@{game.repo.owner.login}"
- l.apply_event(generator.issue_comment_event(pull, comment), db)
+ l.apply_event(generator.issue_comment_event(repo, pull, comment), db)
var player = new Player(game, "Morriar")
assert player.load_achievements.has_key("player_ping_god")
assert player.stats.overall["nitcoins"] == 50
comment.body = "+1"
- l.apply_event(generator.issue_comment_event(pull, comment), db)
+ l.apply_event(generator.issue_comment_event(repo, pull, comment), db)
player = new Player(game, "Morriar")
assert player.load_achievements.has_key("player_first_review")
assert player.stats.overall["nitcoins"] == 60
comment.body = "Nitcoins"
- l.apply_event(generator.issue_comment_event(pull, comment), db)
+ l.apply_event(generator.issue_comment_event(repo, pull, comment), db)
player = new Player(game, "Morriar")
assert player.load_achievements.has_key("player_says_nitcoin")
assert player.stats.overall["nitcoins"] == 70
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Example that uses `shibuqam` to authenticate users and count the number of time they reload.
+module reloadgame
+
+import popcorn
+import counter
+import shibuqam
+
+redef class User
+ # How many reload?
+ var seen = 0
+end
+
+# Ugly global class to track the knowledge.
+class DB
+ # All known users
+ var users = new HashMap[String, User]
+end
+# Ugly global instance to track the knowledge.
+fun db: DB do return once new DB
+
+redef class HttpRequest
+ # Like `user` but reuse an user if already seen
+ var reuser: nullable User is lazy do
+ var user = self.user
+ if user == null then return null
+
+ var saved = db.users.get_or_null(user.id)
+ if saved != null then return saved
+
+ db.users[user.id] = user
+ return user
+ end
+end
+
+# The only handler of the example.
+class ReloadGame
+ super Handler
+
+ redef fun get(http_request, response)
+ do
+ var body = """
+ <!DOCTYPE html>
+ <head>
+ <meta charset="utf-8">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
+ <title>Nitcorn on Shibboleth/UQAM</title>
+ </head>
+ <body>
+ <div class="container">
+ <h1>Nitcorn on Shibboleth/UQAM</h1>
+ """
+
+ var user = http_request.user
+
+ if user != null then
+ user.seen += 1
+
+ body += """
+ <p>Welcome {{{user.given_name}}}</p>
+ <ul>
+ <li>Full Name: {{{user.display_name}}}</li>
+ <li>E-Mail: {{{user.email}}}</li>
+ <li>Id: {{{user.id}}}</li>
+ <li>Score: {{{user.seen}}}</li>
+ </ul>
+ """
+
+ #for k, v in http_request.header do body += "<li>{k}: {v}</li>"
+ else
+ # The login page, at the location the reverse proxy is expected to be configured
+ # to force an authentication.
+ var login = "/securep/login"
+ body += """
+ <p>Welcome annonymous, please <a href="{{{login}}}">log in</a>.</p>
+ """
+ end
+
+ var score = new Counter[User]
+ for u in db.users.values do
+ score[u] = u.seen
+ end
+
+ body += "<h2>Scoreboard</h2><ul>"
+ for u in score.sort.reversed do
+ body += "<li><img src='{u.avatar}'> {u.display_name}: {u.seen}</li>"
+ end
+
+
+ body += """</ul>
+ </div>
+ </body>
+ </html>
+ """
+
+ response.html body
+ end
+end
+
+var app = new App
+app.use("/*", new ReloadGame)
+app.listen("localhost", 3000)
--- /dev/null
+[package]
+name=shibuqam
+tags=web
+maintainer=Jean Privat <jean@pryen.org>
+license=Apache-2.0
+[upstream]
+browse=https://github.com/nitlang/nit/tree/master/contrib/shibuqam/
+git=https://github.com/nitlang/nit.git
+git.directory=contrib/shibuqam/
+homepage=http://nitlanguage.org
+issues=https://github.com/nitlang/nit/issues
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Gather the authenticated users on UQAM websites.
+#
+# The main method to use is `HttpRequest::user` and it extracts the information
+# of the authenticated user from the request header.
+# The real authentication must be done by a mandatory reverse proxy server.
+module shibuqam
+
+import nitcorn
+private import md5
+
+# Information on a user from Shibboleth/UQAM
+class User
+ # The *code permanent* (or the uid for non student)
+ var id: String
+
+ # Usually the first name
+ var given_name: String
+
+ # Usually "FamilyName, FirstName"
+ var display_name: String
+
+ # The email @courrier.uqam.ca (or @uqam.ca for non student)
+ var email: String
+
+ # The Gravatar URL (based on `email`)
+ var avatar: String is lazy do
+ var md5 = email.md5
+ return "https://www.gravatar.com/avatar/{md5}?d=retro"
+ end
+end
+
+redef class HttpRequest
+ # Extract the Shibboleth/UQAM information from the header, if any.
+ #
+ # We assume that a reverse proxy does the authentication and fill the request header.
+ # If the server is accessible directly, these headers can be easily forged.
+ # Thus, we assume that the reverse proxy is not by-passable.
+ #
+ # The reverse proxy might choose to force an authentication or not.
+ # If there is no authentication, there is no information in the request header (or with the `(null)` value).
+ # In this case, `null` is returned by this function.
+ fun user: nullable User do
+ var user = header.get_or_null("Remote-User")
+ if user == null or user == "(null)" then return null
+
+ var display_name = header.get_or_null("User-Display-Name")
+ var given_name = header.get_or_null("User-Given-Name")
+ var email = header.get_or_null("User-Mail")
+
+ if display_name == null or given_name == null or email == null then return null
+
+ var res = new User(user, given_name, display_name, email)
+ return res
+ end
+end
# ---
# Android
+#
+# There are 4 versions, combining 2 variations:
+# * scientific vs non-scientific
+# * android API 21+ vs under 21
-android: bin/calculator.apk bin/scientific.apk
+android: bin/calculator14.apk bin/scientific14.apk bin/calculator21.apk bin/scientific21.apk
-bin/calculator.apk: $(shell ${NITLS} -M src/android_calculator.nit) ${NITC} android/res/drawable-hdpi/icon.png
+bin/calculator14.apk: $(shell ${NITLS} -M src/android14.nit) ${NITC} android/res/drawable-hdpi/icon.png
mkdir -p bin
- ${NITC} -o $@ src/android_calculator.nit -D debug
+ ${NITC} -o $@ src/android14.nit -D debug
-bin/scientific.apk: $(shell ${NITLS} -M src/scientific src/android_calculator.nit) ${NITC} src/scientific/android/res/drawable-hdpi/icon.png
+bin/calculator21.apk: $(shell ${NITLS} -M src/android21) ${NITC} android/res/drawable-hdpi/icon.png
mkdir -p bin
- ${NITC} -o $@ src/scientific -m src/android_calculator.nit -D debug
+ ${NITC} -o $@ src/android21 -D debug
-android-release: $(shell ${NITLS} -M src/scientific src/android_calculator.nit) ${NITC} android/res/drawable-hdpi/icon.png
+bin/scientific14.apk: $(shell ${NITLS} -M src/scientific src/android14.nit) ${NITC} src/scientific/android/res/drawable-hdpi/icon.png
mkdir -p bin
- ${NITC} -o bin/calculator.apk src/scientific -m src/android_calculator.nit --release
+ ${NITC} -o $@ src/scientific -m src/android14.nit -D debug
+
+bin/scientific21.apk: $(shell ${NITLS} -M src/scientific src/android21) ${NITC} src/scientific/android/res/drawable-hdpi/icon.png
+ mkdir -p bin
+ ${NITC} -o $@ src/scientific -m src/android21 -D debug
+
+android-release: $(shell ${NITLS} -M src/scientific src/android14.nit) ${NITC} android/res/drawable-hdpi/icon.png
+ mkdir -p bin
+ ${NITC} -o bin/calculator14.apk src/android14.nit --release
+ ${NITC} -o bin/calculator21.apk src/android21 --release
+ ${NITC} -o bin/scientific14.apk src/scientific -m src/android14.nit --release
+ ${NITC} -o bin/scientific21.apk src/scientific -m src/android21 --release
android/res/drawable-hdpi/icon.png: art/icon.svg ../../contrib/inkscape_tools/bin/svg_to_icons
mkdir -p android/res
../../contrib/inkscape_tools/bin/svg_to_icons art/icon.svg --android --out android/res/
-src/scientific/android/res/drawable-hdpi/icon.png: art/icon_sci.svg ../../contrib/inkscape_tools/bin/svg_to_icons
+src/scientific/android/res/drawable-hdpi/icon.png: art/icon-sci.svg ../../contrib/inkscape_tools/bin/svg_to_icons
mkdir -p src/scientific/android/res
../../contrib/inkscape_tools/bin/svg_to_icons art/icon-sci.svg --android --out src/scientific/android/res/
../../contrib/inkscape_tools/bin/svg_to_icons:
make -C ../../contrib/inkscape_tools/
-android-install: bin/calculator.apk
- adb install -r bin/calculator.apk
+android-install: bin/calculator14.apk
+ adb install -r bin/calculator14.apk
# ---
# iOS
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+
+ <!-- Default background color for the status bar. -->
+ <color name="calculator_accent_color">#00BCD4</color>
+
+ <!-- Color to indicate an error has occured. -->
+ <color name="calculator_error_color">#F40056</color>
+
+ <!-- Background color of the calculator display. -->
+ <color name="display_background_color">#FFF</color>
+
+ <!-- Text color for the formula in the calculator display. -->
+ <color name="display_formula_text_color">#8A000000</color>
+
+ <!-- Text color for the result in the calculator display. -->
+ <color name="display_result_text_color">#6C000000</color>
+
+ <!-- Background color for the numeric pad. -->
+ <color name="pad_numeric_background_color">#434343</color>
+
+ <!-- Background color for the operator pad. -->
+ <color name="pad_operator_background_color">#636363</color>
+
+ <!-- Background color for the advanced pad. -->
+ <color name="pad_advanced_background_color">#1DE9B6</color>
+
+ <!-- Text color for a button in a pad. -->
+ <color name="pad_button_text_color">#FFF</color>
+
+ <!-- Text color for a button in the advanced pad. -->
+ <color name="pad_button_advanced_text_color">#91000000</color>
+
+ <!-- Ripple color when a button is pressed in a pad. -->
+ <color name="pad_button_ripple_color">#33FFFFFF</color>
+
+ <!-- Ripple color when a button is pressed in a pad. -->
+ <color name="pad_button_advanced_ripple_color">#1A000000</color>
+
+</resources>
--- /dev/null
+Categories:Nit
+License:Apache2
+Web Site:http://nitlanguage.org
+Source Code:http://nitlanguage.org/nit.git/tree/HEAD:/examples/calculator
+Issue Tracker:https://github.com/nitlang/nit/issues
+
+Summary:A Scientific Calculator
+Description:
+10 digits, 16 operations, hours of fun.
+.
git.directory=examples/calculator/
homepage=http://nitlanguage.org
issues=https://github.com/nitlang/nit/issues
-apk=http://nitlanguage.org/fdroid/apk/calculator.apk
+apk=http://nitlanguage.org/fdroid/apk/calculator21.apk
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Aesthetic adaptations for Android for API 14+
+module android14
+
+import calculator
+import android
+
+redef class Button
+ init do set_android_style(native, app.native_activity,
+ (text or else "?").is_int,
+ ["+","-","×","C","÷","=","."].has(text))
+
+ # Set color and text style
+ private fun set_android_style(java_button: NativeButton, activity: NativeActivity,
+ is_number: Bool, is_basic_op: Bool)
+ in "Java" `{
+ // Set color
+ int back_color_id = 0;
+ if (is_number)
+ back_color_id = R.color.pad_numeric_background_color;
+ else if (is_basic_op)
+ back_color_id = R.color.pad_operator_background_color;
+ else {
+ back_color_id = R.color.pad_advanced_background_color;
+
+ int text_color = activity.getResources().getColor(R.color.pad_button_advanced_text_color);
+ java_button.setTextColor(text_color);
+ }
+ java_button.setBackgroundResource(back_color_id);
+
+ // Center label, use lowercase and make text bigger
+ java_button.setGravity(android.view.Gravity.CENTER);
+ java_button.setAllCaps(false);
+ java_button.setTextSize(android.util.TypedValue.COMPLEX_UNIT_FRACTION, 100.0f);
+ `}
+end
+
+redef class TextInput
+ init do set_android_style(native, app.native_activity)
+
+ # Set text style and hide cursor
+ private fun set_android_style(java_edit_text: NativeEditText, activity: NativeActivity)
+ in "Java" `{
+ java_edit_text.setBackgroundResource(R.color.display_background_color);
+ java_edit_text.setTextColor(
+ activity.getResources().getColor(R.color.display_formula_text_color));
+ java_edit_text.setTextSize(android.util.TypedValue.COMPLEX_UNIT_FRACTION, 120.0f);
+ java_edit_text.setCursorVisible(false);
+ java_edit_text.setGravity(android.view.Gravity.CENTER_VERTICAL | android.view.Gravity.END);
+ `}
+end
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <style name="CalculatorTheme" parent="@android:style/Theme.Material.Light.NoActionBar">
+ <item name="android:colorPrimary">@color/calculator_accent_color</item>
+ <item name="android:navigationBarColor">@color/calculator_accent_color</item>
+ <item name="android:statusBarColor">@color/calculator_accent_color</item>
+ <item name="android:windowContentOverlay">@null</item>
+ </style>
+
+ <style name="DisplayEditTextStyle" parent="@android:style/Widget.Material.Light.EditText">
+ <item name="android:background">@android:color/transparent</item>
+ <item name="android:cursorVisible">false</item>
+ <item name="android:fontFamily">sans-serif-light</item>
+ <item name="android:includeFontPadding">false</item>
+ <item name="android:gravity">bottom|end</item>
+ </style>
+
+ <style name="PadButtonStyle" parent="@android:style/Widget.Material.Light.Button.Borderless">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:fontFamily">sans-serif-light</item>
+ <item name="android:gravity">center</item>
+ <item name="android:includeFontPadding">false</item>
+ <item name="android:minWidth">0dip</item>
+ <item name="android:minHeight">0dip</item>
+ <item name="android:textAllCaps">false</item>
+ <item name="android:textColor">@color/pad_button_text_color</item>
+ </style>
+
+ <style name="PadLayoutStyle">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">match_parent</item>
+ </style>
+</resources>
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Aesthetic adaptations for Android Lollypop (API 21)
+module android21 is
+ android_api_min 21
+ android_api_target 21
+ android_manifest_activity """android:theme="@style/CalculatorTheme" """
+ app_files
+end
+
+import android14
+
+redef class TextInput
+ init do
+ set_android_style(native, app.native_activity)
+ super
+ end
+
+ # Deactivate the virtual keyboard and set the text style from XML resources
+ private fun set_android_style(java_edit_text: NativeEditText, activity: NativeActivity)
+ in "Java" `{
+ java_edit_text.setShowSoftInputOnFocus(false);
+ java_edit_text.setTextAppearance(activity, R.style.DisplayEditTextStyle);
+ `}
+end
+
+redef class Button
+ init do
+ set_text_style(native, app.native_activity)
+ super
+ end
+
+ # Set the text style from XML resources
+ private fun set_text_style(java_button: NativeButton, activity: NativeActivity)
+ in "Java" `{
+ java_button.setTextAppearance(activity, R.style.PadButtonStyle);
+ `}
+end
+++ /dev/null
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Aesthetic adaptations for Android
-module android_calculator
-
-import calculator
-import android
-
-redef class Button
- init do set_android_style(native, (text or else "?").is_int)
-
- private fun set_android_style(java_button: NativeButton, is_number: Bool)
- in "Java" `{
- // Flatten the background and use a different color for digit buttons
- int color = is_number? android.graphics.Color.DKGRAY: android.graphics.Color.TRANSPARENT;
- java_button.setBackgroundColor(color);
-
- // Center the label on both horizontal and vertical axes
- java_button.setGravity(android.view.Gravity.CENTER);
-
- // Set lowercase text to correctly display constants like e and π
- java_button.setAllCaps(false);
- `}
-end
return new Couple[String, nullable Object](next_attribute_name, next_object)
end
- redef fun deserialize_attribute(name)
+ redef fun deserialize_attribute(name, static_type)
do
if unclaimed_attributes.last.keys.has(name) then
# Pick in already deserialized attributes
return res
end
+ # Correctly join `self` with `subpath` using the directory separator.
+ #
+ # Using a standard "{self}/{path}" does not work in the following cases:
+ #
+ # * `self` is empty.
+ # * `path` starts with `'/'`.
+ #
+ # This method ensures that the join is valid.
+ #
+ # var hello = "hello".to_path
+ # assert (hello/"world").to_s == "hello/world"
+ # assert ("hel/lo".to_path / "wor/ld").to_s == "hel/lo/wor/ld"
+ # assert ("".to_path / "world").to_s == "world"
+ # assert (hello / "/world").to_s == "/world"
+ # assert ("hello/".to_path / "world").to_s == "hello/world"
+ fun /(subpath: String): Path do return new Path(path / subpath)
# Lists the files contained within the directory at `path`.
#
end
var name = de.to_s_with_copy
if name == "." or name == ".." then continue
- res.add new Path(path / name)
+ res.add self / name
end
d.closedir
module api
import github_curl
+intrude import json::serialization
# Client to Github API
#
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/nitlang/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.message == "Not Found"
fun get(path: String): nullable Jsonable 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`.
- protected 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.
# assert user.login == "Morriar"
# ~~~
fun load_auth_user: nullable User do
- var json = load_from_github("user")
+ var user = load_from_github("/user")
if was_error then return null
- return new User.from_json(self, json)
+ return user.as(nullable User)
end
# Get the Github user with `login`
#
# 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`.
# var repo = api.load_repo("nitlang/nit")
# assert repo.name == "nit"
# assert repo.owner.login == "nitlang"
- # assert repo.default_branch.name == "master"
+ # 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
+ return deserialize(array.to_json).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`.
# 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`.
# 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`.
# 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}/comments?page={page}")
+ if not array isa JsonArray then break
+ if array.is_empty or res.length < count 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
+ 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`.
# 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`.
# 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`.
# 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`.
# 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)
- return event.load_from_github
+ return load_from_github("/repos/{repo.full_name}/issues/events/{id}").as(nullable IssueEvent)
end
# Get the Github commit comment with `id`.
# 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.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`.
# 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`.
# 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
-
- # 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
+ super Jsonable
+ serialize
# Github page url.
- fun html_url: String do return json["html_url"].as(String)
+ var html_url: nullable String is writable
- # Set page url.
- fun html_url=(url: String) do json["html_url"] = url
+ redef fun to_json do return serialize_to_json
end
# A Github user
# Should be accessed from `GithubAPI::load_user`.
class User
super GithubEntity
-
- redef var key is lazy do return "users/{login}"
+ serialize
# Github login.
- var login: String
-
- # Init `self` from a `json` object.
- init from_json(api: GithubAPI, json: JsonObject) do
- init(api, json["login"].as(String))
- self.json = json
- end
+ var login: String is writable
# Avatar image url for this user.
- fun avatar_url: String do return json["avatar_url"].as(String)
-
- # Set avatar url.
- fun avatar_url=(url: String) do json["avatar_url"] = url
+ var avatar_url: nullable String is writable
end
# A Github repository.
# Should be accessed from `GithubAPI::load_repo`.
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"].as(String))
- self.json = json
- end
+ var full_name: String is writable
# Repo short name on Github.
- fun name: String do return json["name"].as(String)
-
- # Set repo full name
- fun name=(name: String) do json["name"] = name
+ var name: String is writable
# Get the repo owner.
- fun owner: User do return new User.from_json(api, json["owner"].as(JsonObject))
-
- # Set repo owner
- fun owner=(owner: User) do json["owner"] = owner.json
-
- # 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"].as(String)
- 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
-
- # 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")
- 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"].as(String)
- 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
-
- # 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"].as(String)
- var branch = api.load_branch(self, name)
- assert branch isa Branch
- return branch
- end
-
- # Set the default branch
- fun default_branch=(branch: Branch) do json["default_branch"] = branch.json
-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
- init(api, 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"].as(String)
- 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))
-
- # Set the last commit
- fun commit=(commit: Commit) do json["commit"] = commit.json
-
- # 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.
#
# 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"].as(String)
- super
- end
+ var sha: String is writable
# Parent commits of `self`.
- fun parents: Array[Commit] do
- var res = new Array[Commit]
- var parents = json.get_or_null("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"].as(String)).as(not null))
- end
- return res
- end
-
- # Set parent commits.
- fun parents=(parents: Array[Commit]) do
- var res = new JsonArray
- for parent in parents do res.add parent.json
- json["parents"] = res
- end
+ var parents: nullable Array[Commit] = null is writable
# Author of the commit.
- fun author: nullable User do
- var user = json.get_or_null("author")
- if user isa JsonObject then return new User.from_json(api, user)
- return null
- end
-
- # Set commit author.
- fun author=(user: nullable User) do
- if user == null then
- json["author"] = null
- else
- json["author"] = user.json
- end
- end
+ var author: nullable User is writable
# Committer of the commit.
- fun committer: nullable User do
- var user = json.get_or_null("author")
- if user isa JsonObject then return new User.from_json(api, user)
- return null
- end
+ var committer: nullable User is writable
- # Set commit committer.
- fun committer=(user: nullable User) do
- if user == null then
- json["committer"] = null
- else
- json["committer"] = user.json
- end
- end
+ # 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"].as(String))
+ 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"].as(String))
+ 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.
- fun files: Array[GithubFile] do
- var res = new Array[GithubFile]
- var files = json.get_or_null("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
-
- # Set commit files.
- fun files=(files: Array[GithubFile]) do
- var res = new JsonArray
- for file in files do res.add file.json
- json["files"] = res
- end
+ var files: nullable Array[GithubFile] = null is optional, writable
# Commit message.
- fun message: String do return json["commit"].as(JsonObject)["message"].as(String)
+ var message: nullable String is writable
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
-
- redef init from_json(api, repo, json) do
- self.number = json["number"].as(Int)
- super
- end
+ var number: Int is writable
# Issue id.
- fun id: Int do return json["id"].as(Int)
-
- # Set issue id.
- fun id=(id: Int) do json["id"] = id
+ var id: nullable Int is writable
# Issue title.
- fun title: String do return json["title"].as(String)
-
- # Set issue title
- fun title=(title: String) do json["title"] = title
+ var title: String is writable
# User that created this issue.
- fun user: User do return new User.from_json(api, json["user"].as(JsonObject))
-
- # Set issue creator.
- fun user=(user: User) do json["user"] = user.json
+ 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]
- var lbls = json.get_or_null("labels")
- if not lbls isa JsonArray then return res
- for obj in lbls do
- if not obj isa JsonObject then continue
- var name = obj["name"].as(String)
- 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"].as(String)
-
- # Set the state of this issue.
- fun state=(state: String) do json["state"] = state
+ var state: String is writable
# Is the issue locked?
- fun locked: Bool do return json["locked"].as(Bool)
-
- # Set issue locked state.
- fun locked=(locked: Bool) do json["locked"] = locked
+ var locked: nullable Bool is writable
# Assigned `User` (if any).
- fun assignee: nullable User do
- var assignee = json.get_or_null("assignee")
- if assignee isa JsonObject then return new User.from_json(api, assignee)
- return null
- end
-
- # Set issue assignee.
- fun assignee=(user: nullable User) do
- if user == null then
- json["assignee"] = null
- else
- json["assignee"] = user.json
- end
- end
+ var assignee: nullable User is writable
# `Milestone` (if any).
- fun milestone: nullable Milestone do
- var milestone = json.get_or_null("milestone")
- if milestone isa JsonObject then return new Milestone.from_json(api, repo, milestone)
- return null
- end
-
- # Set issue milestone.
- fun milestone=(milestone: nullable Milestone) do
- if milestone == null then
- json["milestone"] = null
- else
- json["milestone"] = milestone.json
- end
- 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)
- var comment = api.load_issue_comment(repo, id)
- if comment == null then continue
- res.add(comment)
- end
- page += 1
- var json = api.get("{key}/comments?page={page}")
- if not json isa JsonArray then
- return res
- end
- array = json
- end
- return res
- end
+ var milestone: nullable Milestone is writable
# Number of comments on this issue.
- fun comments_count: Int do return json["comments"].as(Int)
+ var comments: nullable Int is writable
- # Creation time in ISODate format.
- fun created_at: ISODate do return new ISODate.from_string(json["created_at"].as(String))
+ # Creation time as String.
+ var created_at: String is writable
- # Set issue creation time.
- fun created_at=(created_at: nullable ISODate) do
- if created_at == null then
- json["created_at"] = null
- else
- json["created_at"] = created_at.to_s
- end
+ # Creation time as ISODate.
+ fun iso_created_at: ISODate do
+ return new ISODate.from_string(created_at)
end
- # Last update time in ISODate format (if any).
- fun updated_at: nullable ISODate do
- var res = json.get_or_null("updated_at")
- if res isa String then return new ISODate.from_string(res)
- return null
- end
+ # Last update time as String (if any).
+ var updated_at: nullable String is writable
- # Set issue last update time.
- fun updated_at=(updated_at: nullable ISODate) do
- if updated_at == null then
- json["updated_at"] = null
- else
- json["updated_at"] = updated_at.to_s
- end
+ # 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.get_or_null("closed_at")
- if res isa String then return new ISODate.from_string(res)
- return null
- end
+ # Close time as String (if any).
+ var closed_at: nullable String is writable
- # Set issue close time.
- fun closed_at=(closed_at: nullable ISODate) do
- if closed_at == null then
- json["closed_at"] = null
- else
- json["closed_at"] = closed_at.to_s
- end
+ # 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
- # TODO link to pull request
-
# Full description of the issue.
- fun body: String do return json["body"].as(String)
-
- # Set description body
- fun body=(body: String) do json["body"] = body
-
- # 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}")
- if not array isa JsonArray then return res
- 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
- end
+ var body: nullable String is writable
# User that closed this issue (if any).
- fun closed_by: nullable User do
- var closer = json.get_or_null("closed_by")
- if closer isa JsonObject then return new User.from_json(api, closer)
- return null
- end
-
- # Set user that closed the issue.
- fun closed_by=(user: nullable User) do
- if user == null then
- json["closed_by"] = null
- else
- json["closed_by"] = user.json
- end
- end
+ var closed_by: nullable User is writable
# Is this issue linked to a pull request?
- fun is_pull_request: Bool do return json.has_key("pull_request")
+ var is_pull_request: Bool is noserialize, 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.get_or_null("merged_at")
- if res isa String then return new ISODate.from_string(res)
- return null
- end
-
- # Set pull request merge time.
- fun merged_at=(merged_at: nullable ISODate) do
- if merged_at == null then
- json["merged_at"] = null
- else
- json["merged_at"] = merged_at.to_s
- end
+ # 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"].as(String)
-
- # Set merge_commit_sha
- fun merge_commit_sha=(sha: String) do json["merge_commit_sha"] = sha
+ 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"].as(Int)
-
- # Set review_comments
- fun review_comments=(count: Int) do json["review_comments"] = count
+ 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
-
- # Set head
- fun head=(head: PullRef) do json["head"] = head.json
+ 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
-
- # Set base
- fun base=(base: PullRef) do json["base"] = base.json
+ var base: PullRef is writable
# Is this pull request merged?
- fun merged: Bool do return json["merged"].as(Bool)
-
- # Set merged
- fun merged=(merged: Bool) do json["merged"] = merged
+ var merged: Bool is writable
# Is this pull request mergeable?
- fun mergeable: Bool do return json["mergeable"].as(Bool)
-
- # Set mergeable
- fun mergeable=(mergeable: Bool) do json["mergeable"] = mergeable
+ 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"].as(Int)
-
- # Set mergeable_state
- fun mergeable_state=(mergeable_state: Int) do json["mergeable_state"] = mergeable_state
+ var mergeable_state: String is writable
# User that merged this pull request (if any).
- fun merged_by: nullable User do
- var merger = json.get_or_null("merged_by")
- if merger isa JsonObject then return new User.from_json(api, merger)
- return null
- end
-
- # Set merged_by.
- fun merged_by=(merged_by: nullable User) do
- if merged_by == null then
- json["merged_by"] = null
- else
- json["merged_by"] = merged_by.json
- end
- end
+ var merged_by: nullable User is writable
# Count of commits in this pull request.
- fun commits: Int do return json["commits"].as(Int)
-
- # Set commits
- fun commits=(commits: Int) do json["commits"] = commits
+ var commits: Int is writable
# Added line count.
- fun additions: Int do return json["additions"].as(Int)
-
- # Set additions
- fun additions=(additions: Int) do json["additions"] = additions
+ var additions: Int is writable
# Deleted line count.
- fun deletions: Int do return json["deletions"].as(Int)
-
- # Set deletions
- fun deletions=(deletions: Int) do json["deletions"] = deletions
+ var deletions: Int is writable
# Changed files count.
- fun changed_files: Int do return json["changed_files"].as(Int)
-
- # Set changed_files
- fun changed_files=(changed_files: Int) do json["changed_files"] = changed_files
+ var changed_files: Int 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"].as(String)
-
- # Set labl
- fun labl=(labl: String) do json["label"] = labl
+ var labl: String is writable, serialize_as("label")
# Reference pointed by `self`.
- fun ref: String do return json["ref"].as(String)
-
- # Set ref
- fun ref=(ref: String) do json["ref"] = ref
+ var ref: String is writable
# Commit SHA pointed by `self`.
- fun sha: String do return json["sha"].as(String)
-
- # Set sha
- fun sha=(sha: String) do json["sha"] = sha
+ var sha: String is writable
# User pointed by `self`.
- fun user: User do
- return new User.from_json(api, json["user"].as(JsonObject))
- end
-
- # Set user
- fun user=(user: User) do json["user"] = user.json
+ var user: User is writable
# Repo pointed by `self`.
- fun repo: Repo do
- return new Repo.from_json(api, json["repo"].as(JsonObject))
- end
-
- # Set repo
- fun repo=(repo: Repo) do json["repo"] = repo.json
+ var repo: 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"].as(String)
- super
- end
+ var name: String is writable
# Label color code.
- fun color: String do return json["color"].as(String)
-
- # Set color
- fun color=(color: String) do json["color"] = color
+ 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: Int is writable
# Milestone title.
- fun title: String do return json["title"].as(String)
-
- # Set title
- fun title=(title: String) do json["title"] = title
+ var title: String is writable
# Milestone long description.
- fun description: String do return json["description"].as(String)
-
- # Set description
- fun description=(description: String) do json["description"] = description
+ var description: String is writable
# Count of opened issues linked to this milestone.
- fun open_issues: Int do return json["open_issues"].as(Int)
-
- # Set open_issues
- fun open_issues=(open_issues: Int) do json["open_issues"] = open_issues
+ var open_issues: Int is writable
# Count of closed issues linked to this milestone.
- fun closed_issues: Int do return json["closed_issues"].as(Int)
-
- # Set closed_issues
- fun closed_issues=(closed_issues: Int) do json["closed_issues"] = closed_issues
+ var closed_issues: Int is writable
# Milestone state.
- fun state: String do return json["state"].as(String)
-
- # Set state
- fun state=(state: String) do json["state"] = state
+ var state: String is writable
- # Creation time in ISODate format.
- fun created_at: ISODate do
- return new ISODate.from_string(json["created_at"].as(String))
- end
+ # Creation time as String.
+ var created_at: String is writable
- # Set created_at
- fun created_at=(created_at: ISODate) do json["created_at"] = created_at.to_s
+ # Creation time as ISODate.
+ fun iso_created_at: nullable ISODate do return new ISODate.from_string(created_at)
# User that created this milestone.
- fun creator: User do
- return new User.from_json(api, json["creator"].as(JsonObject))
- end
+ var creator: User is writable
- # Set creator
- fun creator=(creator: User) do json["creator"] = creator.json
+ # 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.get_or_null("updated_at")
- if res isa String then return new ISODate.from_string(res)
- return null
- end
-
- # Set due_on.
- fun due_on=(due_on: nullable ISODate) do
- if due_on == null then
- json["due_on"] = null
- else
- json["due_on"] = due_on.to_s
- end
+ 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.get_or_null("updated_at")
- if res isa String then return new ISODate.from_string(res)
- return null
- end
+ # Last update time as String (if any).
+ var updated_at: nullable String is writable
- # Set updated_at.
- fun updated_at=(updated_at: nullable ISODate) do
- if updated_at == null then
- json["updated_at"] = null
- else
- json["updated_at"] = updated_at.to_s
- end
+ # 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.get_or_null("closed_at")
- if res isa String then return new ISODate.from_string(res)
- return null
- end
+ # Close time as String (if any).
+ var closed_at: nullable String is writable
- # Set closed_at.
- fun closed_at=(closed_at: nullable ISODate) do
- if closed_at == null then
- json["closed_at"] = null
- else
- json["closed_at"] = closed_at.to_s
- end
+ # 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
- # Set user
- fun user=(user: User) do json["user"] = user.json
+ # 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"].as(String))
+ # Creation time as ISODate.
+ fun iso_created_at: nullable ISODate do
+ return new ISODate.from_string(created_at)
end
- # Set created_at
- fun created_at=(created_at: ISODate) do json["created_at"] = created_at.to_s
-
- # Last update time in ISODate format (if any).
- fun updated_at: nullable ISODate do
- var res = json.get_or_null("updated_at")
- if res isa String then return new ISODate.from_string(res)
- return null
- end
+ # Last update time as String (if any).
+ var updated_at: nullable String is writable
- # Set updated_at.
- fun updated_at=(updated_at: nullable ISODate) do
- if updated_at == null then
- json["updated_at"] = null
- else
- json["updated_at"] = updated_at.to_s
- end
+ # 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"].as(String)
-
- # Set body
- fun body=(body: String) do json["body"] = body
+ var body: String is writable
# Does the comment contain an acknowledgement (+1)
- fun is_ack: Bool
- do
+ 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"].as(String)).as(not null)
- end
-
- # Set commit
- fun commit=(commit: Commit) do json["commit_id"] = commit.json
+ var commit_id: String is writable
# Position of the comment on the line.
- fun position: nullable String do
- var res = json.get_or_null("position")
- if res isa String then return res
- return null
- end
-
- # Set position.
- fun position=(position: nullable String) do json["position"] = position
+ var position: nullable Int is writable
# Line of the comment.
- fun line: nullable String do
- var res = json.get_or_null("line")
- if res isa String then return res
- return null
- end
-
- # Set line.
- fun line=(line: nullable String) do json["line"] = line
+ var line: nullable Int is writable
# Path of the commented file.
- fun path: String do return json["path"].as(String)
-
- # Set path.
- fun path=(path: String) do json["path"] = path
+ 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"].as(String)
-
- # Set issue_url.
- fun issue_url=(issue_url: String) do json["issue_url"] = issue_url
+ 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"].as(String)
-
- # Set pull_request_url.
- fun pull_request_url=(pull_request_url: String) do json["pull_request_url"] = pull_request_url
+ var pull_request_url: String is writable
# Diff hunk.
- fun diff_hunk: String do return json["diff_hunk"].as(String)
-
- # Set diff_hunk.
- fun diff_hunk=(diff_hunk: String) do json["diff_hunk"] = diff_hunk
+ var diff_hunk: String is writable
# Path of commented file.
- fun path: String do return json["path"].as(String)
-
- # Set path.
- fun path=(path: String) do json["path"] = path
+ var path: String is writable
# Position of the comment on the file.
- fun position: Int do return json["position"].as(Int)
-
- # Set position.
- fun position=(position: Int) do json["position"] = position
+ var position: nullable Int is writable
# Original position in the diff.
- fun original_position: Int do return json["original_position"].as(Int)
-
- # Set original_position.
- fun original_position=(original_position: Int) do json["original_position"] = original_position
+ var original_position: Int is writable
# Commit referenced by this comment.
- fun commit_id: String do return json["commit_id"].as(String)
-
- # Set commit_id.
- fun commit_id=(commit_id: String) do json["commit_id"] = commit_id
+ var commit_id: String is writable
# Original commit id.
- fun original_commit_id: String do return json["original_commit_id"].as(String)
-
- # Set original_commit_id.
- fun original_commit_id=(commit_id: String) do json["original_commit_id"] = commit_id
+ 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
-
- # Set issue.
- fun issue=(issue: Issue) do json["issue"] = issue.json
+ 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
- # Set actor.
- fun actor=(actor: User) do json["actor"] = actor.json
+ # 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"].as(String))
+ # Creation time as ISODate.
+ fun iso_created_at: nullable ISODate do
+ return new ISODate.from_string(created_at)
end
- # Set created_at.
- fun created_at=(created_at: ISODate) do json["created_at"] = created_at.to_s
-
# Event descriptor.
- fun event: String do return json["event"].as(String)
-
- # Set event.
- fun event=(event: String) do json["event"] = event
+ var event: String is writable
# Commit linked to this event (if any).
- fun commit_id: nullable String do
- var res = json.get_or_null("commit_id")
- if res isa String then return res
- return null
- end
-
- # Set commit_id.
- fun commit_id=(commit_id: nullable String) do json["commit_id"] = commit_id
+ var commit_id: nullable String is writable
# Label linked to this event (if any).
- fun labl: nullable Label do
- var res = json.get_or_null("label")
- if res isa JsonObject then return new Label.from_json(api, repo, res)
- return null
- end
-
- # Set labl.
- fun labl=(labl: nullable Label) do
- if labl == null then
- json["labl"] = null
- else
- json["labl"] = labl.json
- end
- 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.get_or_null("assignee")
- if res isa JsonObject then return new User.from_json(api, res)
- return null
- end
-
- # Set assignee.
- fun assignee=(assignee: nullable User) do
- if assignee == null then
- json["assignee"] = null
- else
- json["assignee"] = assignee.json
- end
- end
+ var assignee: nullable User is writable
# Milestone linked to this event (if any).
- fun milestone: nullable Milestone do
- var res = json.get_or_null("milestone")
- if res isa JsonObject then return new Milestone.from_json(api, repo, res)
- return null
- end
-
- # Set milestone.
- fun milestone=(milestone: nullable User) do
- if milestone == null then
- json["milestone"] = null
- else
- json["milestone"] = milestone.json
- end
- end
+ var milestone: nullable Milestone is writable
# Rename linked to this event (if any).
- fun rename: nullable RenameAction do
- var res = json.get_or_null("rename")
- if res isa JsonObject then return new RenameAction(res)
- return null
- end
-
- # Set rename.
- fun rename=(rename: nullable User) do
- if rename == null then
- json["rename"] = null
- else
- json["rename"] = rename.json
- end
- 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"].as(String)
-
- # Set from.
- fun from=(from: String) do json["from"] = from
+ var from: String is writable
# Name after renaming.
- fun to: String do return json["to"].as(String)
-
- # Set to.
- fun to=(to: String) do json["to"] = to
+ var to: String is writable
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
+ serialize
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
- init(api, json)
- end
+ var api: GithubAPI is writable
# User these statistics are about.
- fun author: User do
- return new User.from_json(api, json["author"].as(JsonObject))
- end
-
- # Set author.
- fun author=(author: User) do json["author"] = author.json
+ var author: User is writable
# Total number of commit.
- fun total: Int do return json["total"].as(Int)
-
- # Set total.
- fun total=(total: Int) do json["total"] = total
+ var total: Int is writable
# Are of weeks of activity with detailed statistics.
- fun weeks: JsonArray do return json["weeks"].as(JsonArray)
-
- # Set weeks.
- fun weeks=(weeks: JsonArray) do json["weeks"] = weeks
+ var weeks: JsonArray is writable
# ContributorStats can be compared on the total amount of commits.
redef fun <(o) do return total < o.total
#
# Mostly a wrapper around a json object.
class GithubFile
-
- # Json content.
- var json: JsonObject
+ serialize
# File name.
- fun filename: String do return json["filename"].as(String)
+ var filename: String is writable
+end
- # Set filename.
- fun filename=(filename: String) do json["filename"] = filename
+# Make ISO Datew serilizable
+redef class ISODate
+ super Jsonable
+ serialize
+
+ redef fun to_json do return serialize_to_json
+end
+
+# JsonDeserializer specific for Github objects.
+class GithubDeserializer
+ super JsonDeserializer
+
+ redef fun class_name_heuristic(json_object) do
+ if json_object.has_key("login") or json_object.has_key("email") 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") or (json_object.has_key("id") and json_object.has_key("tree_id")) then
+ return "Commit"
+ 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
end
if store.has_key(key) then
message(1, "Get {key} (cache)")
was_error = false
- return store.load_object(key)
+ return deserialize(store.load_object(key).to_json).as(nullable GithubEntity)
end
var obj = super
- if not was_error then cache(key, obj)
+ if not was_error then
+ cache(key, obj.as(not null))
+ end
return obj
end
# Save `json` data in cache under `key`.
- private fun cache(key: String, json: JsonObject) do
+ private fun cache(key: String, obj: GithubEntity) do
message(2, "Cache key {key}")
- store.store_object(key, json)
+ store.store_object(key, obj.to_json.parse_json.as(JsonObject))
end
# Check if a cache file exists for `key`.
module events
import api
+intrude import json::serialization
# Github event stub.
class GithubEvent
-
- # Github API client.
- var api: GithubAPI
-
- # Json representation of `self`.
- var json: JsonObject is noinit
-
- init do
- json = new JsonObject
- end
-
- # Init `self` from a `json` object.
- init from_json(api: GithubAPI, json: JsonObject) do
- init(api)
- self.json = json
- end
+ super Jsonable
+ serialize
# Event ID from Github.
- fun id: String do return json["id"].as(String)
-
- # Set id.
- fun id=(id: String) do json["id"] = id
+ var id: nullable String is writable
# Action performed by the event.
- fun action: String do return json["action"].as(String)
-
- # Set action.
- fun action=(action: String) do json["action"] = action
+ var action: nullable String is writable
# Repo where this event occured.
- fun repo: Repo do
- return new Repo.from_json(api, json["repository"].as(JsonObject))
- end
+ var repo: Repo is writable
- # Set repo.
- fun repo=(repo: Repo) do json["repository"] = repo.json
+ redef fun to_json do return serialize_to_json
end
# Triggered when a commit comment is created.
class CommitCommentEvent
super GithubEvent
+ serialize
# The `Comment` itself.
- fun comment: CommitComment do
- return new CommitComment.from_json(api, repo, json["comment"].as(JsonObject))
- end
-
- # Set comment.
- fun comment=(comment: CommitComment) do json["comment"] = comment.json
+ var comment: CommitComment is writable
end
# Triggered when a repository, branch, or tag is created.
class CreateEvent
super GithubEvent
+ serialize
# Oject type that was created.
#
# Can be one of `repository`, `branch`, or `tag`.
- fun ref_type: String do return json["ref_type"].as(String)
-
- # Set ref_type.
- fun ref_type=(ref_type: String) do json["ref_type"] = ref_type
+ var ref_type: String is writable
# Git ref (or null if only a repository was created).
- fun ref: String do return json["ref"].as(String)
-
- # Set ref.
- fun ref=(ref: String) do json["ref"] = ref
+ var ref: String is writable
# Name of the repo's default branch (usually master).
- fun master_branch: String do return json["master_branch"].as(String)
-
- # Set master_branch.
- fun master_branch=(master_branch: String) do json["master_branch"] = master_branch
+ var master_branch: String is writable
# Repo's current description.
- fun description: String do return json["description"].as(String)
-
- # Set description.
- fun description=(description: String) do json["description"] = description
+ var description: nullable String is writable
end
# Triggered when a branch or a tag is deleted.
class DeleteEvent
super GithubEvent
+ serialize
# Object type that was deleted.
#
# Can be one of `repository`, `branch`, or `tag`.
- fun ref_type: String do return json["ref_type"].as(String)
-
- # Set ref_type.
- fun ref_type=(ref_type: String) do json["ref_type"] = ref_type
+ var ref_type: String is writable
# Git ref (or null if only a repository was deleted).
- fun ref: String do return json["ref"].as(String)
-
- # Set ref.
- fun ref=(ref: String) do json["ref"] = ref
+ var ref: String is writable
end
# Triggered when a new snapshot is deployed.
# Deployement are mainly used with integration testing servers.
class DeploymentEvent
super GithubEvent
+ serialize
# Commit SHA for which this deployment was created.
- fun sha: String do return json["sha"].as(String)
-
- # Set sha.
- fun sha=(sha: String) do json["sha"] = sha
+ var sha: String is writable
# Name of repository for this deployment, formatted as :owner/:repo.
- fun name: String do return json["name"].as(String)
-
- # Set name.
- fun name=(name: String) do json["name"] = name
+ var name: String is writable
# Optional extra information for this deployment.
- fun payload: nullable String do
- var res = json.get_or_null("payload")
- if res isa String then return res else return null
- end
-
- # Set payload.
- fun payload=(payload: nullable String) do json["payload"] = payload
+ var payload: nullable String is writable
# Optional environment to deploy to.
# Default: "production"
- fun environment: nullable String do
- var res = json.get_or_null("environment")
- if res isa String then return res else return null
- end
-
- # Set environment.
- fun environment=(environment: nullable String) do json["environment"] = environment
+ var environment: nullable String is writable
# Optional human-readable description added to the deployment.
- fun description: nullable String do
- var res = json.get_or_null("description")
- if res isa String then return res else return null
- end
-
- # Set description.
- fun description=(description: nullable String) do json["description"] = description
+ var description: nullable String is writable
end
# Triggered when a deployement's status changes.
class DeploymentStatusEvent
super GithubEvent
+ serialize
# New deployment state.
#
# Can be `pending`, `success`, `failure`, or `error`.
- fun state: String do return json["state"].as(String)
+ var state: String is writable
# Optional link added to the status.
- fun target_url: nullable String do
- var res = json.get_or_null("target_url")
- if res isa String then return res else return null
- end
-
- # Set target_url.
- fun target_url=(target_url: nullable String) do json["target_url"] = target_url
+ var target_url: nullable String is writable
# Deployment hash that this status is associated with.
- fun deployment: String do return json["deployment"].as(String)
-
- # Set deployment.
- fun deployment=(deployment: String) do json["deployment"] = deployment
+ var deployment: String is writable
# Optional human-readable description added to the status.
- fun description: nullable String do
- var res = json.get_or_null("description")
- if res isa String then return res else return null
- end
-
- # Set description.
- fun description=(description: nullable String) do json["description"] = description
+ var description: nullable String is writable
end
# Triggered when a user forks a repository.
class ForkEvent
super GithubEvent
+ serialize
# Created repository.
- fun forkee: Repo do return new Repo.from_json(api, json["forkee"].as(JsonObject))
-
- # Set forkee.
- fun forkee=(forkee: Repo) do json["forkee"] = forkee.json
+ var forkee: Repo is writable
end
# Triggered when an issue comment is created.
class IssueCommentEvent
super GithubEvent
+ serialize
# `Issue` the comment belongs to.
- fun issue: Issue do
- return new Issue.from_json(api, repo, json["issue"].as(JsonObject))
- end
-
- # Set issue.
- fun issue=(issue: Issue) do json["issue"] = issue.json
+ var issue: Issue is writable
# The `Comment` itself.
- fun comment: IssueComment do
- return new IssueComment.from_json(api, repo, json["comment"].as(JsonObject))
- end
-
- # Set comment.
- fun comment=(comment: IssueComment) do json["comment"] = comment.json
+ var comment: IssueComment is writable
end
# Triggered when an event occurs on an issue.
# opened, closed or reopened.
class IssuesEvent
super GithubEvent
+ serialize
# The `Issue` itself.
- fun issue: Issue do return new Issue.from_json(api, repo, json["issue"].as(JsonObject))
-
- # Set issue.
- fun issue=(issue: Issue) do json["issue"] = issue.json
+ var issue: Issue is writable
# Optional `Label` that was added or removed from the issue.
- fun lbl: nullable Label do
- var res = json.get_or_null("label")
- if res isa JsonObject then return new Label.from_json(api, repo, res) else return null
- end
-
- # Set lbl.
- fun lbl=(lbl: nullable Label) do
- if lbl == null then
- json["lbl"] = null
- else
- json["lbl"] = lbl.json
- end
- end
+ var lbl: nullable Label is writable, serialize_as("label")
# Optional `User` that was assigned or unassigned from the issue.
- fun assignee: nullable User do
- var res = json.get_or_null("assignee")
- if res isa JsonObject then return new User.from_json(api, res) else return null
- end
-
- # Set assignee.
- fun assignee=(assignee: nullable User) do
- if assignee == null then
- json["assignee"] = null
- else
- json["assignee"] = assignee.json
- end
- end
+ var assignee: nullable User is writable
end
# Triggered when a user is added as a collaborator to a repository.
class MemberEvent
super GithubEvent
+ serialize
# `User` that was added.
- fun member: User do return new User.from_json(api, json["member"].as(JsonObject))
-
- # Set member.
- fun member=(member: User) do json["member"] = member.json
+ var member: User is writable
end
# Triggered when an event occurs on a pull request.
# labeled, unlabeled, opened, closed, reopened, or synchronized.
class PullRequestEvent
super GithubEvent
+ serialize
# The pull request number.
- fun number: Int do return json["number"].as(Int)
-
- # Set number.
- fun number=(number: Int) do json["number"] = number
+ var number: Int is writable
# The `PullRequest` itself.
- fun pull: PullRequest do
- return new PullRequest.from_json(api, repo, json["pull_request"].as(JsonObject))
- end
-
- # Set pull.
- fun pull=(pull: PullRequest) do json["pull_request"] = pull.json
+ var pull: PullRequest is writable
end
# Triggered when a comment is created on a pull request diff.
class PullRequestReviewCommentEvent
super GithubEvent
+ serialize
# The `Comment` itself.
- fun comment: ReviewComment do
- return new ReviewComment.from_json(api, repo, json["comment"].as(JsonObject))
- end
-
- # Set comment.
- fun comment=(comment: ReviewComment) do json["comment"] = comment.json
+ var comment: ReviewComment is writable
# `PullRequest` the `comment` belongs to.
- fun pull: PullRequest do
- return new PullRequest.from_json(api, repo, json["pull_request"].as(JsonObject))
- end
-
- # Set pull.
- fun pull=(pull: PullRequest) do json["pull_request"] = pull.json
+ var pull: PullRequest is writable
end
# Triggered when a repository branch is pushed to.
class PushEvent
super GithubEvent
+ serialize
# SHA of the HEAD commit on the repository.
- fun head: String do return json["head"].as(String)
-
- # Set head.
- fun head=(head: String) do json["head"] = head
+ var head_commit: Commit is writable
# Full Git ref that was pushed.
#
# Example: “refs/heads/master”
- fun ref: String do return json["ref"].as(String)
-
- # Set ref.
- fun ref=(ref: String) do json["ref"] = ref
+ var ref: String is writable
# Number of commits in the push.
- fun size: Int do return json["size"].as(Int)
-
- # Set size.
- fun size=(size: Int) do json["size"] = size
+ var size: nullable Int is writable
# Array of pushed commits.
- fun commits: Array[Commit] do
- var res = new Array[Commit]
- var arr = json["commits"].as(JsonArray)
- for obj in arr do
- if not obj isa JsonObject then continue
- res.add api.load_commit(repo, obj["sha"].as(String)).as(not null)
- end
- return res
- end
-
- # Set commits.
- fun commits=(commits: Array[Commit]) do
- var arr = new JsonArray
- for commit in commits do arr.add commit.json
- json["commits"] = arr
- end
+ var commits = new Array[Commit] is writable, optional
end
# Triggered when the status of a Git commit changes.
class StatusEvent
super GithubEvent
+ serialize
# The `Commit` itself.
- fun commit: Commit do
- return api.load_commit(repo, json["sha"].as(String)).as(not null)
- end
-
- # Set commit.
- fun commit=(commit: Commit) do json["sha"] = commit.sha
+ var sha: String is writable
# New state.
#
# Can be `pending`, `success`, `failure`, or `error`.
- fun state: String do return json["state"].as(String)
-
- # Set state.
- fun state=(state: String) do json["state"] = state
+ var state: String is writable
# Optional human-readable description added to the status.
- fun description: nullable String do
- var res = json.get_or_null("description")
- if res isa String then return res else return null
- end
-
- # Set description.
- fun description=(description: nullable String) do json["description"] = description
+ var description: nullable String is writable
# Optional link added to the status.
- fun target_url: nullable String do
- var res = json.get_or_null("target_url")
- if res isa String then return res else return null
- end
-
- # Set target_url.
- fun target_url=(target_url: nullable String) do json["target_url"] = target_url
+ var target_url: nullable String is writable
# Array of branches containing the status' SHA.
#
# but the SHA may or may not be the head of the branch.
#
# The array includes a maximum of 10 branches.
- fun branches: Array[Branch] do
- var res = new Array[Branch]
- var arr = json["branches"].as(JsonArray)
- for obj in arr do
- if not obj isa JsonObject then continue
- res.add api.load_branch(repo, obj["name"].as(String)).as(not null)
- end
- return res
- end
+ var branches = new Array[Branch] is writable, optional
+end
- # Set branches.
- fun branches=(branches: Array[Commit]) do
- var arr = new JsonArray
- for branch in branches do arr.add branch.json
- json["branches"] = arr
+redef class GithubDeserializer
+
+ redef fun class_name_heuristic(json_object) do
+ if json_object.has_key("action") and json_object.has_key("commit") and json_object.has_key("comment") then
+ return "CommitCommentEvent"
+ else if json_object.has_key("ref") and json_object.has_key("master_branch") then
+ return "CreateEvent"
+ else if json_object.has_key("ref") and json_object.has_key("ref_type") then
+ return "DeleteEvent"
+ else if json_object.has_key("action") and json_object.has_key("sha") then
+ return "DeploymentEvent"
+ else if json_object.has_key("action") and json_object.has_key("state") then
+ return "DeploymentStatusEvent"
+ else if json_object.has_key("action") and json_object.has_key("forkee") then
+ return "ForkEvent"
+ else if json_object.has_key("action") and json_object.has_key("issue") and json_object.has_key("comment") then
+ return "IssueCommentEvent"
+ else if json_object.has_key("action") and json_object.has_key("issue") then
+ return "IssuesEvent"
+ else if json_object.has_key("action") and json_object.has_key("member") then
+ return "MemberEvent"
+ else if json_object.has_key("action") and json_object.has_key("number") then
+ return "PullRequestEvent"
+ else if json_object.has_key("action") and json_object.has_key("pull") and json_object.has_key("comment") then
+ return "PullRequestReviewCommentEvent"
+ else if json_object.has_key("head_commit") and json_object.has_key("commits") then
+ return "PushEvent"
+ else if json_object.has_key("action") and json_object.has_key("branches") then
+ return "StatusEvent"
+ else if json_object.has_key("action") and json_object.has_key("issue") then
+ return "GithubEvent"
+ end
+ return super
end
end
# redef class CommitCommentEvent
#
# redef fun log_event(l) do
-# print "new comment on commit {comment.commit.sha}"
+# print "new comment on commit {comment.commit_id}"
# end
# end
#
# How to build events from received json objects.
fun event_factory(kind: String, json: JsonObject): GithubEvent do
if kind == "commit_comment" then
- return new CommitCommentEvent.from_json(api, json)
+ return api.deserialize(json.to_json).as(CommitCommentEvent)
else if kind == "create" then
- return new CreateEvent.from_json(api, json)
+ return api.deserialize(json.to_json).as(CreateEvent)
else if kind == "delete" then
- return new DeleteEvent.from_json(api, json)
+ return api.deserialize(json.to_json).as(DeleteEvent)
else if kind == "deployment" then
- return new DeploymentEvent.from_json(api, json)
+ return api.deserialize(json.to_json).as(DeploymentEvent)
else if kind == "deployment_status" then
- return new DeploymentStatusEvent.from_json(api, json)
+ return api.deserialize(json.to_json).as(DeploymentStatusEvent)
else if kind == "fork" then
- return new ForkEvent.from_json(api, json)
+ return api.deserialize(json.to_json).as(ForkEvent)
else if kind == "issues" then
- return new IssuesEvent.from_json(api, json)
+ return api.deserialize(json.to_json).as(IssuesEvent)
else if kind == "issue_comment" then
- return new IssueCommentEvent.from_json(api, json)
+ return api.deserialize(json.to_json).as(IssueCommentEvent)
else if kind == "member" then
- return new MemberEvent.from_json(api, json)
+ return api.deserialize(json.to_json).as(MemberEvent)
else if kind == "pull_request" then
- return new PullRequestEvent.from_json(api, json)
+ return api.deserialize(json.to_json).as(PullRequestEvent)
else if kind == "pull_request_review_comment" then
- return new PullRequestReviewCommentEvent.from_json(api, json)
+ return api.deserialize(json.to_json).as(PullRequestReviewCommentEvent)
else if kind == "push" then
- return new PushEvent.from_json(api, json)
+ return api.deserialize(json.to_json).as(PushEvent)
else if kind == "status" then
- return new StatusEvent.from_json(api, json)
+ return api.deserialize(json.to_json).as(StatusEvent)
else
- return new GithubEvent.from_json(api, json)
+ return api.deserialize(json.to_json).as(GithubEvent)
end
end
#}"""
# ~~~
#
-# ## JSON to Nit objects
+# ## Read JSON to create Nit objects
#
-# The `JsonDeserializer` support reading JSON code with minimal metadata
-# to easily create Nit object from client-side code or configuration files.
-# Each JSON object must define the `__class` attribute with the corresponding
-# Nit class and the expected attributes with its name in Nit followed by its value.
+# The `JsonDeserializer` supports reading JSON code with or without metadata.
+# It can create Nit objects from a remote service returning JSON data
+# or to read local configuration files as Nit objects.
+# However, it needs to know which Nit class to recreate from each JSON object.
+# The class is either declared or inferred:
+#
+# 1. The JSON object defines a `__class` key with the name of the Nit class as value.
+# This attribute is generated by the `JsonSerializer` with other metadata,
+# it can also be specified by other external tools.
+# 2. A refinement of `JsonDeserializer::class_name_heuristic` identifies the Nit class.
+# 3. If all else fails, `JsonDeserializer` uses the static type of the attribute.
#
# ### Usage Example
#
# ~~~nitish
# import json::serialization
#
-# class MeetupConfig
+# class Triangle
# serialize
#
-# var description: String
-# var max_participants: nullable Int
-# var answers: Array[FlatString]
+# var corners = new Array[Point]
+# redef var to_s is serialize_as("name")
# end
#
-# var json_code = """
-# {"__class": "MeetupConfig", "description": "My Awesome Meetup", "max_participants": null, "answers": ["Pepperoni", "Chicken"]}"""
-# var deserializer = new JsonDeserializer(json_code)
+# class Point
+# serialize
+#
+# var x: Int
+# var y: Int
+# end
#
-# var meet = deserializer.deserialize
+# # Metadata on each JSON object tells the deserializer what is its Nit type,
+# # and it supports special types such as generic collections.
+# var json_with_metadata = """{
+# "__class": "Triangle",
+# "corners": {"__class": "Array[Point]",
+# "__items": [{"__class": "Point", "x": 0, "y": 0},
+# {"__class": "Point", "x": 3, "y": 0},
+# {"__class": "Point", "x": 2, "y": 2}]},
+# "name": "some triangle"
+# }"""
#
-# # Check for errors
+# var deserializer = new JsonDeserializer(json_with_metadata)
+# var object = deserializer.deserialize
# assert deserializer.errors.is_empty
+# assert object != null
+# print object
+#
+# # However most non-Nit services won't add the metadata and instead produce plain JSON.
+# # Without a "__class", the deserializer relies on `class_name_heuristic` and the static type.
+# # The type of the root object to deserialize can be specified by calling
+# # its deserialization constructor `from_deserializer`.
+# var plain_json = """{
+# "corners": [{"x": 0, "y": 0},
+# {"x": 3, "y": 0},
+# {"x": 2, "y": 2}],
+# "name": "the same triangle"
+# }"""
#
-# assert meet isa MeetupConfig
-# assert meet.description == "My Awesome Meetup"
-# assert meet.max_participants == null
-# assert meet.answers == ["Pepperoni", "Chicken"]
+# deserializer = new JsonDeserializer(plain_json)
+# object = new Triangle.from_deserializer(deserializer)
+# assert deserializer.errors.is_empty # If false, `obj` is invalid
+# print object
# ~~~
module serialization
# Depth-first path in the serialized object tree.
private var path = new Array[Map[String, nullable Object]]
+ # Names of the attributes from the root to the object currently being deserialized
+ var attributes_path = new Array[String]
+
# Last encountered object reference id.
#
# See `id_to_object`.
self.root = root
end
- redef fun deserialize_attribute(name)
+ redef fun deserialize_attribute(name, static_type)
do
assert not path.is_empty # This is an internal error, abort
var current = path.last
var value = current[name]
- return convert_object(value)
+ attributes_path.add name
+ var res = convert_object(value, static_type)
+ attributes_path.pop
+ return res
end
# This may be called multiple times by the same object from constructors
cache[id] = new_object
end
- # Convert from simple Json object to Nit object
- private fun convert_object(object: nullable Object): nullable Object
+ # Convert the simple JSON `object` to a Nit object
+ private fun convert_object(object: nullable Object, static_type: nullable String): nullable Object
do
if object isa JsonParseError then
errors.add object
if class_name == null then
# Fallback to custom heuristic
class_name = class_name_heuristic(object)
+
+ if class_name == null and static_type != null then
+ # Fallack to the static type, strip the `nullable` prefix
+ var prefix = "nullable "
+ if static_type.has(prefix) then
+ class_name = static_type.substring_from(prefix.length)
+ else class_name = static_type
+ end
end
if class_name == null then
# Simple JSON array without serialization metadata
if object isa Array[nullable Object] then
+ # Can we use the static type?
+ if static_type != null then
+ var prefix = "nullable "
+ var class_name = if static_type.has(prefix) then
+ static_type.substring_from(prefix.length)
+ else static_type
+
+ opened_array = object
+ var value = deserialize_class(class_name)
+ opened_array = null
+ return value
+ end
+
+ # This branch should rarely be used:
+ # when an array is the root object which is accepted but illegal in standard JSON,
+ # or in strange custom deserialization hacks.
+
var array = new Array[nullable Object]
var types = new HashSet[String]
var has_nullable = false
return typed_array
end
- # Uninferable type, return as `Array[nullable Object]`
+ # Uninferrable type, return as `Array[nullable Object]`
return array
end
return object
end
+ # Current array open for deserialization, used by `SimpleCollection::from_deserializer`
+ private var opened_array: nullable Array[nullable Object] = null
+
redef fun deserialize
do
errors.clear
return convert_object(root)
end
- # User customizable heuristic to get the name of the Nit class to deserialize `json_object`
+ # User customizable heuristic to infer the name of the Nit class to deserialize `json_object`
#
# This method is called only when deserializing an object without the metadata `__class`.
- # Return the class name as a `String` when it can be inferred.
- # Return `null` when the class name cannot be found.
+ # Use the content of `json_object` to identify what Nit class it should be deserialized into.
+ # Or use `self.attributes_path` indicating where the deserialized object will be stored,
+ # is is less reliable as some objects don't have an associated attribute:
+ # the root/first deserialized object and collection elements.
+ #
+ # Return the class name as a `String` when it can be inferred,
+ # or `null` when the class name cannot be found.
#
# If a valid class name is returned, `json_object` will then be deserialized normally.
# So it must contain the attributes of the corresponding class, as usual.
# serialize
#
# var error: String
+ # var related_data: MyData
# end
#
# class MyJsonDeserializer
#
# redef fun class_name_heuristic(json_object)
# do
+ # # Infer the Nit class from the content of the JSON object.
# if json_object.keys.has("error") then return "MyError"
# if json_object.keys.has("data") then return "MyData"
+ #
+ # # Infer the Nit class from the attribute where it will be stored.
+ # # This line duplicates a previous line, and would only apply when
+ # # `MyData` is within a `MyError`.
+ # if attributes_path.not_empty and attributes_path.last == "related_data" then return "MyData"
+ #
# return null
# end
# end
#
- # var json = """{"data": "some other data"}"""
+ # var json = """{"data": "some data"}"""
# var deserializer = new MyJsonDeserializer(json)
# var deserialized = deserializer.deserialize
# assert deserialized isa MyData
#
- # json = """{"error": "some error message"}"""
+ # json = """{"error": "some error message",
+ # "related_data": {"data": "some other data"}"""
# deserializer = new MyJsonDeserializer(json)
# deserialized = deserializer.deserialize
# assert deserialized isa MyError
v.notify_of_creation self
init
- var arr = v.path.last.get_or_null("__items")
- if not arr isa SequenceRead[nullable Object] then
- # If there is nothing, we consider that it is an empty collection.
- if arr != null then v.errors.add new Error("Deserialization Error: invalid format in {self.class_name}")
- return
+ var open_array: nullable SequenceRead[nullable Object] = v.opened_array
+ if open_array == null then
+ # With metadata
+ var arr = v.path.last.get_or_null("__items")
+ if not arr isa SequenceRead[nullable Object] then
+ # If there is nothing, we consider that it is an empty collection.
+ if arr != null then v.errors.add new Error("Deserialization Error: invalid format in {self.class_name}")
+ return
+ end
+ open_array = arr
+ end
+
+ # Try to get the name of the single parameter type assuming it is E.
+ # This does not work in non-generic subclasses,
+ # when the first parameter is not E, or
+ # when there is more than one parameter. (The last one could be fixed)
+ var class_name = class_name
+ var items_type = null
+ var bracket_index = class_name.index_of('[')
+ if bracket_index != -1 then
+ var start = bracket_index + 1
+ var ending = class_name.last_index_of(']')
+ items_type = class_name.substring(start, ending-start)
end
- for o in arr do
- var obj = v.convert_object(o)
+ # Fill array
+ for o in open_array do
+ var obj = v.convert_object(o, items_type)
if obj isa E then
add obj
else v.errors.add new AttributeTypeError(self, "items", obj, "E")
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2016 Alexandre Terrasa <alexandre@moz-code.org>.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Mongo queries framework
+#
+# The `queries` framework is used to build Mongo queries as JsonObject with
+# a fluent interface.
+#
+# Using the `queries` framework we can get from this:
+#
+# ~~~nitish
+# var exists = new JsonObject
+# exists["$exists"] = true
+#
+# var query = new JsonObject
+# query["login"] = "Morriar"
+# query["email"] = exists
+#
+# collection.find(query)
+# ~~~
+#
+# To this:
+#
+# ~~~nitish
+# collection.find((new MongoMatch).eq("login", "Morriar").exists("email", true))
+# ~~~
+#
+# The framework provides three classes used to map the MongoDB query API:
+# * `MongoMatch` the base query that can be used with most Mongo services
+# * `MongoPipeline` the array of queries that is expected by `MongoCollection::aggregate`
+# * `MongoGroup` the group query for a `MongoPipeline`
+#
+# More on this features can be found in the official MongoDB documentation:
+# https://docs.mongodb.com/manual/reference/operator/
+module queries
+
+import mongodb
+
+# A basic match query
+#
+# `MongoMatch` is used with most of the Mongo services like `find`, `find_all`,
+# `remove` etc.
+#
+# Building a query can be done with the fluent interface:
+#
+# ~~~
+# var query = (new MongoMatch).
+# eq("login", "Morriar").
+# gt("age", 18).
+# exists("email", true).
+# is_in("status", [1, 2, 3, 4])
+# ~~~
+#
+# Fore more help on how to use the query operators of MongoDB please
+# refer to the official MongoDB documentation:
+# https://docs.mongodb.com/manual/reference/operator/query/
+class MongoMatch
+ super JsonObject
+
+ private fun op(name: String, field: String, value: nullable Jsonable): MongoMatch do
+ var q = new JsonObject
+ q["${name}"] = value
+ self[field] = q
+ return self
+ end
+
+ # Match documents where `field` equals `value`
+ #
+ # https://docs.mongodb.com/manual/reference/operator/query/eq/#op._S_eq
+ #
+ # ~~~json
+ # {field: {$eq: value} }
+ # ~~~
+ fun eq(field: String, value: nullable Jsonable): MongoMatch do
+ self[field] = value
+ return self
+ end
+
+ # Match documents where `field` not equals `value`
+ #
+ # https://docs.mongodb.com/manual/reference/operator/query/ne/#op._S_ne
+ #
+ # ~~~json
+ # {field: {$ne: value} }
+ # ~~~
+ fun ne(field: String, value: nullable Jsonable): MongoMatch do
+ op("ne", field, value)
+ return self
+ end
+
+ # Match documents where `field` is greater than `value`
+ #
+ # https://docs.mongodb.com/manual/reference/operator/query/gt/#op._S_gt
+ #
+ # ~~~json
+ # {field: {$gt: value} }
+ # ~~~
+ fun gt(field: String, value: nullable Jsonable): MongoMatch do
+ op("gt", field, value)
+ return self
+ end
+
+ # Match documents where `field` is greater or equal to `value`
+ #
+ # https://docs.mongodb.com/manual/reference/operator/query/gte/#op._S_gte
+ #
+ # ~~~json
+ # {field: {$gte: value} }
+ # ~~~
+ fun gte(field: String, value: nullable Jsonable): MongoMatch do
+ op("gte", field, value)
+ return self
+ end
+
+ # Match documents where `field` is less than `value`
+ #
+ # https://docs.mongodb.com/manual/reference/operator/query/lt/#op._S_lt
+ #
+ # ~~~json
+ # {field: {$lt: value} }
+ # ~~~
+ fun lt(field: String, value: nullable Jsonable): MongoMatch do
+ op("lt", field, value)
+ return self
+ end
+
+ # Match documents where `field` is less or equal to `value`
+ #
+ # https://docs.mongodb.com/manual/reference/operator/query/lte/
+ #
+ # ~~~json
+ # {field: {$lte: value} }
+ # ~~~
+ fun lte(field: String, value: nullable Jsonable): MongoMatch do
+ op("lte", field, value)
+ return self
+ end
+
+ # Match documents where `field` exists or not
+ #
+ # https://docs.mongodb.com/manual/reference/operator/query/exists/#op._S_exists
+ #
+ # ~~~json
+ # {field: {$exists: boolean} }
+ # ~~~
+ #
+ # When `exists` is true, `$exists` matches the documents that contain the
+ # field, including documents where the field value is null.
+ # If <boolean> is false, the query returns only the documents that do not
+ # contain the field.
+ fun exists(field: String, exists: Bool): MongoMatch do
+ op("exists", field, exists)
+ return self
+ end
+
+ # Match documents where `field` is in `values`
+ #
+ # https://docs.mongodb.com/manual/reference/operator/query/in/
+ #
+ # ~~~json
+ # { field: { $in: [<value1>, <value2>, ... <valueN> ] } }
+ # ~~~
+ #
+ # `$in` selects the documents where the value of a field equals any value
+ # in the specified array.
+ fun is_in(field: String, values: Array[nullable Jsonable]): MongoMatch do
+ op("$in", field, new JsonArray.from(values))
+ return self
+ end
+
+ # Match documents where `field` is not in `values`
+ #
+ # https://docs.mongodb.com/manual/reference/operator/query/nin/
+ #
+ # ~~~json
+ # { field: { $nin: [<value1>, <value2>, ... <valueN> ] } }
+ # ~~~
+ #
+ # `$nin` selects the documents where:
+ # * the field value is not in the specified array or
+ # * the field does not exist.
+ fun is_nin(field: String, values: Array[nullable Jsonable]): MongoMatch do
+ op("$nin", field, new JsonArray.from(values))
+ return self
+ end
+end
+
+# Mongo pipelines are arrays of aggregation stages
+#
+# With the `MongoCollection::aggregate` method, pipeline stages appear in a array.
+# Documents pass through the stages in sequence.
+#
+# ~~~json
+# db.collection.aggregate( [ { <stage> }, ... ] )
+# ~~~
+#
+# The MongoPipeline fluent interface can be used to bluid a pipeline:
+# ~~~
+# var pipeline = (new MongoPipeline).
+# match((new MongoMatch).eq("game", "nit")).
+# group((new MongoGroup("$game._id")).sum("nitcoins", "$game.nitcoins")).
+# sort((new MongoMatch).eq("nitcoins", -1)).
+# limit(10)
+# ~~~
+#
+# The pipeline can then be used in an aggregation query:
+# ~~~nitish
+# collection.aggregate(pipeline)
+# ~~~
+#
+# For more information read about MongoDB pipeline operators from the MongoDB
+# official documentation: https://docs.mongodb.com/manual/reference/operator/aggregation/
+class MongoPipeline
+ super JsonArray
+
+ # Add a stage to the pipeline
+ #
+ # https://docs.mongodb.com/manual/reference/operator/aggregation/#stage-operators
+ #
+ # Each stage is registered as:
+ # ~~~json
+ # { $<stage>: <json> }
+ # ~~~
+ fun add_stage(stage: String, json: Jsonable): MongoPipeline do
+ var obj = new JsonObject
+ obj["${stage}"] = json
+ add obj
+ return self
+ end
+
+ # Apply projection
+ #
+ # https://docs.mongodb.com/manual/reference/operator/aggregation/project/#pipe._S_project
+ #
+ # Passes along the documents with only the specified fields to the next stage
+ # in the pipeline.
+ #
+ # ~~~json
+ # { $project: { <specifications> } }
+ # ~~~
+ #
+ # The specified fields can be existing fields from the input documents or
+ # newly computed fields.
+ fun project(projection: JsonObject): MongoPipeline do return add_stage("project", projection)
+
+ # Apply match
+ #
+ # https://docs.mongodb.com/manual/reference/operator/aggregation/match/
+ #
+ # Filters the documents to pass only the documents that match the specified
+ # condition(s) to the next pipeline stage.
+ #
+ # ~~~json
+ # { $match: { <query> } }
+ # ~~~
+ fun match(query: MongoMatch): MongoPipeline do return add_stage("match", query)
+
+ # Apply sort
+ #
+ # https://docs.mongodb.com/manual/reference/operator/aggregation/sort/
+ #
+ # Sorts all input documents and returns them to the pipeline in sorted order.
+ #
+ # ~~~json
+ # { $sort: { <projection> } }
+ # ~~~
+ fun sort(projection: JsonObject): MongoPipeline do return add_stage("sort", projection)
+
+ # Apply skip
+ #
+ # https://docs.mongodb.com/manual/reference/operator/aggregation/skip/
+ #
+ # Skips over the specified number of documents that pass into the stage and
+ # passes the remaining documents to the next stage in the pipeline.
+ #
+ # ~~~json
+ # { $skip: { <number> } }
+ # ~~~
+ fun skip(number: Int): MongoPipeline do return add_stage("skip", number)
+
+ # Apply limit
+ #
+ # https://docs.mongodb.com/manual/reference/operator/aggregation/limit/
+ #
+ # Limits the number of documents passed to the next stage in the pipeline.
+ #
+ # ~~~json
+ # { $limit: { <number> } }
+ # ~~~
+ fun limit(number: Int): MongoPipeline do return add_stage("limit", number)
+
+ # Apply group
+ #
+ # https://docs.mongodb.com/manual/reference/operator/aggregation/group/
+ #
+ # Groups documents by some specified expression and outputs to the next stage
+ # a document for each distinct grouping.
+ #
+ # The output documents contain an `_id` field which contains the distinct
+ # group by key.
+ #
+ # The output documents can also contain computed fields that hold the values
+ # of some accumulator expression grouped by the `$group`'s `_id` field.
+ # `$group` does not order its output documents.
+ #
+ # ~~~json
+ # { $group: { <group> } }
+ # ~~~
+ fun group(group: MongoGroup): MongoPipeline do return add_stage("group", group)
+end
+
+# Mongo pipeline group stage
+#
+# https://docs.mongodb.com/manual/reference/operator/aggregation/group/#pipe._S_group
+#
+# Groups documents by some specified expression and outputs to the next stage a
+# document for each distinct grouping.
+#
+# ~~~
+# var group = (new MongoGroup("$game._id")).sum("nitcoins", "$game.nitcoins")
+#
+# var pipeline = (new MongoPipeline).group(group)
+# ~~~
+#
+# The output documents contain an `_id` field which contains the distinct group by key.
+# The output documents can also contain computed fields that hold the values of
+# some accumulator expression grouped by the `$group`‘s `_id` field.
+# `$group` does not order its output documents.
+#
+# The `$group` stage has the following prototype form:
+#
+# ~~~json
+# { $group: { _id: <expression>, <field1>: { <accumulator1> : <expression1> }, ... } }
+# ~~~
+#
+# The `_id` field is mandatory; however, you can specify an `_id` value of null
+# to calculate accumulated values for all the input documents as a whole.
+#
+# The remaining computed fields are optional and computed using the `<accumulator>`
+# operators.
+class MongoGroup
+ super JsonObject
+
+ # Group `_id`
+ #
+ # See `MongoGroup::group`.
+ var id: String
+
+ init do self["_id"] = id
+
+ # Add an accumulator
+ #
+ # Each accumulator is registered as:
+ # ~~~json
+ # <field>: { <accumulator> : <expression> }
+ # ~~~
+ private fun acc(name: String, field: String, expression: nullable Jsonable): MongoGroup do
+ var q = new JsonObject
+ q["${name}"] = expression
+ self[field] = q
+ return self
+ end
+
+ # Calculates and returns the sum of numeric values
+ #
+ # https://docs.mongodb.com/manual/reference/operator/aggregation/sum/#grp._S_sum
+ #
+ # ~~~json
+ # { $sum: <expression> }
+ # ~~~
+ #
+ # `$sum` ignores non-numeric values.
+ fun sum(field: String, expression: Jsonable): MongoGroup do
+ return acc("sum", field, expression)
+ end
+
+ # Returns the average value of the numeric values
+ #
+ # https://docs.mongodb.com/manual/reference/operator/aggregation/avg/
+ #
+ # ~~~json
+ # { $avg: <expression> }
+ # ~~~
+ #
+ # `$avg` ignores non-numeric values.
+ fun avg(field: String, expression: Jsonable): MongoGroup do
+ return acc("avg", field, expression)
+ end
+
+ # Returns the maximum value
+ #
+ # https://docs.mongodb.com/manual/reference/operator/aggregation/max/
+ #
+ # ~~~json
+ # { $max: <expression> }
+ # ~~~
+ #
+ # `$max` compares both value and type, using the specified BSON comparison
+ # order for values of different types.
+ fun max(field: String, expression: Jsonable): MongoGroup do
+ return acc("max", field, expression)
+ end
+
+ # Returns the minimum value
+ #
+ # https://docs.mongodb.com/manual/reference/operator/aggregation/min/
+ #
+ # ~~~json
+ # { $min: <expression> }
+ # ~~~
+ #
+ # `$min` compares both value and type, using the specified BSON comparison
+ # order for values of different types.
+ fun min(field: String, expression: Jsonable): MongoGroup do
+ return acc("min", field, expression)
+ end
+
+ # Return the first value
+ #
+ # https://docs.mongodb.com/manual/reference/operator/aggregation/first/
+ #
+ # ~~~json
+ # { $first: <expression> }
+ # ~~~
+ #
+ # Returns the value that results from applying an expression to the first
+ # document in a group of documents that share the same group by key.
+ #
+ # Only meaningful when documents are in a defined order.
+ fun first(field: String, expression: Jsonable): MongoGroup do
+ return acc("first", field, expression)
+ end
+
+ # Return the last value
+ #
+ # https://docs.mongodb.com/manual/reference/operator/aggregation/last/
+ #
+ # ~~~json
+ # { $last: <expression> }
+ # ~~~
+ #
+ # Returns the value that results from applying an expression to the last
+ # document in a group of documents that share the same group by key.
+ #
+ # Only meaningful when documents are in a defined order.
+ fun last(field: String, expression: Jsonable): MongoGroup do
+ return acc("last", field, expression)
+ end
+
+ # Push to an array
+ #
+ # https://docs.mongodb.com/manual/reference/operator/aggregation/push/
+ #
+ # ~~~json
+ # { $push: <expression> }
+ # ~~~
+ #
+ # Returns an array of all values that result from applying an expression to
+ # each document in a group of documents that share the same group by key.
+ fun push(field: String, expr: Jsonable): MongoGroup do
+ return acc("push", field, expr)
+ end
+
+ # Push to a unique array
+ #
+ # https://docs.mongodb.com/manual/reference/operator/aggregation/addToSet/
+ #
+ # ~~~json
+ # { $addToSet: <expression> }
+ # ~~~
+ #
+ # Returns an array of all unique values that results from applying an
+ # expression to each document in a group of documents that share the same
+ # group by key.
+ #
+ # Order of the elements in the output array is unspecified.
+ fun addToSet(field: String, expr: Jsonable): MongoGroup do
+ return acc("addToSet", field, expr)
+ end
+end
module http_request
import core
+import serialization
# A request received over HTTP, is build by `HttpRequestParser`
class HttpRequest
+ serialize
+
private init is old_style_init do end
# HTTP protocol version
# Method of this request (GET or POST)
var method: String
- # The host targetter by this request (usually the server)
- var host: String
-
# The full URL requested by the client (including the `query_string`)
var url: String
# Provides the `HttpResponse` class and `http_status_codes`
module http_response
+import serialization
+
# A response to send over HTTP
class HttpResponse
+ serialize
# HTTP protocol version
var http_version = "HTTP/1.0" is writable
# Basic usage example:
# ~~~~
# class MyAction
-# super Action
-#
-# redef fun answer(http_request, turi)
-# do
-# var response = new HttpResponse(200)
-# response.body = """
-# <!DOCTYPE html>
-# <head>
-# <meta charset="utf-8">
-# <title>Hello World</title>
-# </head>
-# <body>
-# <p>Hello World</p>
-# </body>
-# </html>"""
-# return response
-# end
+# super Action
+#
+# redef fun answer(http_request, turi)
+# do
+# var response = new HttpResponse(200)
+# response.body = """
+# <!DOCTYPE html>
+# <head>
+# <meta charset="utf-8">
+# <title>Hello World</title>
+# </head>
+# <body>
+# <p>Hello World</p>
+# </body>
+# </html>"""
+# return response
+# end
# end
#
-# var vh = new VirtualHost("localhost:80")
+# # Listen to port 8080 on all interfaces
+# var vh = new VirtualHost("0.0.0.0:8080")
#
# # Serve index.html with our custom handler
# vh.routes.add new Route("/index.html", new MyAction)
redef fun add(e)
do
super
- var config = vh.server_config
- if config != null then sys.listen_on(e, config.factory)
+ var config = virtual_host.server_config
+ if config != null then register_and_listen(e, config)
+ end
+
+ # Indirection to `listen_on` and check if this targets all addresses
+ private fun register_and_listen(e: Interface, config: ServerConfig)
+ do
+ listen_on(e, config.factory)
+ if e.name == "0.0.0.0" or e.name == "::0" then config.default_virtual_host = virtual_host
end
# TODO remove
redef fun add(e)
do
super
- for i in e.interfaces do sys.listen_on(i, config.factory)
+ for i in e.interfaces do e.interfaces.register_and_listen(i, config)
end
# TODO remove
var virtual_hosts = new VirtualHosts(self)
# Default `VirtualHost` to respond to requests not handled by any of the `virtual_hosts`
- var default_virtual_host: nullable VirtualHost = null
+ var default_virtual_host: nullable VirtualHost = null is writable
end
# A `VirtualHost` configuration
super Array[Interface]
# Back reference to the associtated `VirtualHost`
- var vh: VirtualHost
+ var virtual_host: VirtualHost
# Add an `Interface` described by `text` formatted as `interface.name.com:port`
fun add_from_string(text: String)
res.error 403
return
end
- res.json user.json
+ res.json user
end
end
import serialization
import json::serialization
-import mongodb
+import mongodb::queries
# A Repository is an object that can store serialized instances.
#
# Serialization from/to Json is used to translate from/to nit instances.
#
# See `MongoRepository` for a concrete implementation example.
-interface JsonRepository[E: Serializable]
+abstract class JsonRepository[E: Serializable]
super Repository[E]
redef fun serialize(item) do
if item == null then return null
- return item.serialize_to_json
+ var stream = new StringWriter
+ var serializer = new RepoSerializer(stream)
+ serializer.serialize item
+ stream.close
+ return stream.to_s
end
redef fun deserialize(string) do
end
end
+private class RepoSerializer
+ super JsonSerializer
+
+ # Remove caching when saving refs to db
+ redef fun serialize_reference(object) do serialize object
+end
+
# A Repository that uses MongoDB as backend.
#
# ~~~
end
redef fun clear do return collection.drop
+
+ # Perform an aggregation query over the repo.
+ fun aggregate(pipeline: JsonArray): Array[E] do
+ var res = new Array[E]
+ for obj in collection.aggregate(pipeline) do
+ var instance = deserialize(obj.to_json)
+ if instance == null then continue
+ res.add instance
+ end
+ return res
+ end
end
# JsonObject can be used as a `RepositoryQuery`.
The serialization has some limitations:
-* Not enough classes from the standard library are supported.
- This only requires someone to actually code the support.
- It should not be especially hard for most classes, some can
- simply declare the `serialize` annotation.
-
-* A limitation of the Json parser prevents deserializing from files
+* A limitation of the JSON parser prevents deserializing from files
with more than one object.
This could be improved in the future, but for now you should
- serialize a single object to each filesand use different instances of
+ serialize a single object to each files and use different instances of
serializer and deserializer each time.
-* The `serialize` annotation does not handle very well
- complex constructors. This could be improved in the compiler.
- For now, you may prefer to use `serialize` on simple classes,
- of by using custom `Serializable`.
-
* The serialization uses only the short name of a class, not its qualified name.
This will cause problem when different classes using the same name.
This could be solved partially in the compiler and the library.
the different programs sharing the serialized data.
* The serialization support in the compiler need some help to
- deal with generic types. The solution is to use `nitserial`,
+ deal with generic types. A solution is to use `nitserial`,
the next section explores this subject.
## Dealing with generic types
# Abstract deserialization service
#
-# After initialization of one of its sub-classes, call `deserialize`
+# The main service is `deserialize`.
abstract class Deserializer
- # Main method of this class, returns a Nit object
+ # Deserialize and return an object, storing errors in the attribute `errors`
+ #
+ # This method behavior varies according to the implementation engines.
fun deserialize: nullable Object is abstract
- # Internal method to be implemented by sub-classes
- fun deserialize_attribute(name: String): nullable Object is abstract
+ # Deserialize the attribute with `name` from the object open for deserialization
+ #
+ # The `static_type` can be used as last resort if the deserialized object
+ # desn't have any metadata declaring the dynamic type.
+ #
+ # Internal method to be implemented by the engines.
+ fun deserialize_attribute(name: String, static_type: nullable String): nullable Object is abstract
- # Internal method called by objects in creation,
- # to be implemented by sub-classes
+ # Register a newly allocated object (even if not completely built)
+ #
+ # Internal method called by objects in creation, to be implemented by the engines.
fun notify_of_creation(new_object: Object) is abstract
# Deserialize the next available object as an instance of `class_name`
#
- # Returns the deserialized object on success, aborts on error.
+ # Return the deserialized object on success and
+ # record in `errors` if `class_name` is unknown.
#
# This method should be redefined for each custom subclass of `Serializable`.
# All refinement should look for a precise `class_name` and call super
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# A trie (or prefix tree) is a datastructure used to perform prefix searches.
+#
+# The trie uses an arborescent datastructure to perform searches on a prefix.
+# With this version of the trie, you can link data to nodes so the trie can
+# be used as a kind of Map by prefix.
+#
+# ~~~
+# # Associate some integers to Map keys
+# var trie = new Trie[Int]
+# trie["foo"] = 1
+# trie["fooo"] = 2
+# trie["foooo"] = 3
+# trie["bar"] = 4
+# trie["baz"] = 5
+#
+# # Get stored values by key
+# print trie.has_key("foo")
+# print trie["foo"] == 1
+#
+# # Get stored values by prefix
+# assert trie.has_prefix("fo")
+# assert not trie.has_prefix("z")
+# assert trie.find_by_prefix("foo") == [1, 2, 3]
+# assert trie.find_by_prefix("bar") == [4]
+# assert trie.find_by_prefix("z").is_empty
+# ~~~
+module trie
+
+import trees
+
+# Trie data structure for prefix searches
+#
+# ~~~
+# # Associate some integers to Map keys
+# var trie = new Trie[Int]
+# trie["foo"] = 1
+# trie["fooo"] = 2
+# trie["bar"] = 3
+#
+# # Search by key
+# assert trie.has_key("foo")
+# assert trie["foo"] == 1
+#
+# # Search by prefix
+# assert trie.find_by_prefix("") == [1, 2, 3]
+# assert trie.find_by_prefix("foo") == [1, 2]
+# assert trie.find_by_prefix("baz").is_empty
+# ~~~
+class Trie[E]
+ super Map[String, E]
+
+ # Trie root
+ #
+ # Root children are used to store the first letters.
+ private var root = new TrieNode[E]
+
+ redef fun []=(key, value) do
+ var children = root.children
+
+ for i in [0..key.length[ do
+ var c = key.chars[i]
+
+ var node
+ if children.has_key(c) then
+ node = children[c]
+ else
+ node = new TrieNode[E](c)
+ children[c] = node
+ end
+ children = node.children
+
+ if i == key.length - 1 then
+ node.is_leaf = true
+ node.value = value
+ end
+ end
+ end
+
+ # Cache node used between `has_key` and `[]`
+ private var cache: nullable TrieNode[E] = null
+
+ # Cache key used between `has_key` and `[]`
+ private var cache_key: nullable String = null
+
+ redef fun [](key) do
+ if cache_key == key then return cache.as(not null).value.as(not null)
+ var node = search_node(key)
+ assert node != null
+ return node.value
+ end
+
+ redef fun has_key(key) do
+ var node = search_node(key)
+ if node == null then return false
+ var res = node.is_leaf
+ if res then
+ cache = node
+ cache_key = key.as(String)
+ end
+ return res
+ end
+
+ # Find values stored under `prefix`
+ #
+ # ~~~
+ # # Associate some integers to Map keys
+ # var trie = new Trie[Int]
+ # trie["foo"] = 1
+ # trie["fooo"] = 2
+ # trie["foooo"] = 3
+ # trie["bar"] = 4
+ #
+ # assert trie.find_by_prefix("") == [1, 2, 3, 4]
+ # assert trie.find_by_prefix("foo") == [1, 2, 3]
+ # assert trie.find_by_prefix("bar") == [4]
+ # assert trie.find_by_prefix("baz").is_empty
+ # ~~~
+ fun find_by_prefix(prefix: String): Array[E] do
+ var res = new Array[E]
+ var node
+ if prefix == "" then
+ node = root
+ else
+ node = search_node(prefix)
+ end
+ if node != null then
+ node.collect_values(res)
+ end
+ return res
+ end
+
+ # Find values stored under `prefix`
+ #
+ # ~~~
+ # # Associate some integers to Map keys
+ # var trie = new Trie[Int]
+ # trie["foo"] = 1
+ # trie["bar"] = 4
+ # trie["baz"] = 5
+ #
+ # assert trie.has_prefix("")
+ # assert trie.has_prefix("f")
+ # assert not trie.has_prefix("z")
+ # ~~~
+ fun has_prefix(prefix: String): Bool do
+ if prefix == "" then return true
+ return search_node(prefix) != null
+ end
+
+ # Search a node by a key or prefix
+ #
+ # Returns `null` if no node matches `string`.
+ private fun search_node(string: nullable Object): nullable TrieNode[E] do
+ if string == null then return null
+ string = string.to_s
+ var children = root.children
+ var node = null
+ for i in [0..string.length[ do
+ var c = string.chars[i]
+ if children.has_key(c) then
+ node = children[c]
+ children = node.children
+ else
+ node = null
+ break
+ end
+ end
+ return node
+ end
+end
+
+# TrieNode used to store the Character key of the value
+private class TrieNode[E]
+ var c: nullable Char
+ var value: nullable E
+ var children = new HashMap[Char, TrieNode[E]]
+ var is_leaf: Bool = false
+
+ fun collect_values(values: Array[E]) do
+ var value = self.value
+ if value != null then values.add value
+ for child in children.values do
+ child.collect_values(values)
+ end
+ end
+end
# NAME
-nitls - lists the packages, groups and paths of Nit sources files.
+nitls - search and lists the packages, groups and paths of Nit sources files.
# SYNOPSIS
# DESCRIPTION
-`nitls` is used to list Nit files in directories and extract the module-group-package relation.
+`nitls` is used to search and list Nit files in directories and extract the module-group-package relation.
-It is basically a `ls` or a simple `find` specialized on `.nit` source files.
+It is basically a `ls`, a `which` or a simple `find` specialized on `.nit` source files.
By default `nitls` works with the current directory (`.`).
Show the tree of modules from the current directory.
- $ nitls -t
+ $ nitls
+ test_prog: Test program for model tools. (.)
+ |--game: Gaming group (./game)
+ | `--game: A game abstraction for RPG. (./game/game.nit)
+ |--platform: Fictive Crappy Platform. (./platform)
+ | `--platform: Declares base types allowed on the platform. (./platform/platform.nit)
+ |--rpg: Role Playing Game group (./rpg)
+ | |--careers: Careers of the game. (./rpg/careers.nit)
+ | |--character: Characters are playable entity in the world. (./rpg/character.nit)
+ | |--combat: COmbat interactions between characters. (./rpg/combat.nit)
+ | |--races: Races of the game. (./rpg/races.nit)
+ | `--rpg: A worlg RPG abstraction. (./rpg/rpg.nit)
+ `--test_prog: A test program with a fake model to check model tools. (./test_prog.nit)
+
Show the list of packages imported by the modules of the current directory.
$ nitls -d -P *.nit
+ base64: Offers the base 64 encoding and decoding algorithms (/home/privat/prog/nit/lib/base64.nit)
+ core: # Nit common library of core classes and methods (/home/privat/prog/nit/lib/core)
+ curl: Data transfer with URL syntax (/home/privat/prog/nit/lib/curl)
+ json (/home/privat/prog/nit/lib/json)
+ libevent: Low-level wrapper around the libevent library to manage events on file descriptors (/home/privat/prog/nit/lib/libevent.nit)
+ md5: Native MD5 digest implementation as `Text::md5` (/home/privat/prog/nit/lib/md5.nit)
+ more_collections: Highly specific, but useful, collections-related classes. (/home/privat/prog/nit/lib/more_collections.nit)
+ nitcc_runtime: Runtime library required by parsers and lexers generated by nitcc (/home/privat/prog/nit/lib/nitcc_runtime.nit)
+ nitcorn: Lightweight framework for Web applications development (.)
+ parser_base: Simple base for hand-made parsers of all kinds (/home/privat/prog/nit/lib/parser_base.nit)
+ performance_analysis: Services to gather information on the performance of events by categories (/home/privat/prog/nit/lib/performance_analysis.nit)
+ realtime: Services to keep time of the wall clock time (/home/privat/prog/nit/lib/realtime.nit)
+ serialization: # Abstract serialization services (/home/privat/prog/nit/lib/serialization)
+ template: Basic template system (/home/privat/prog/nit/lib/template)
+
+Show the directory of the package `inkscape_tools`.
+
+ $ nitls -pP inkscape_tools
+ /home/privat/prog/nit/contrib/inkscape_tools
# OPTIONS
--- /dev/null
+<ul class='nav navbar-nav'>
+ <li ng-if='!user'>
+ <a href='/login'>
+ Login
+ <span class='octicon octicon-mark-github'></span>
+ </a>
+ </li>
+ <li class="dropdown" ng-if='user'>
+ <a class="btn btn-link dropdown-toggle" type="button" data-toggle="dropdown">
+ {{user.login}}
+ <img class='avatar avatar-icon' ng-src='{{user.avatar_url}}' />
+ </a>
+ <ul class="dropdown-menu dropdown-menu-right">
+ <li><a href="/user">Profile</a></li>
+ <li role="separator" class="divider"></li>
+ <li><a href="/logout">Logout</a></li>
+ </ul>
+ </li>
+</ul>
<link href='//cdnjs.cloudflare.com/ajax/libs/angular-loading-bar/0.9.0/loading-bar.min.css'
type='text/css' rel='stylesheet' media='all'>
+ <link href='https://cdnjs.cloudflare.com/ajax/libs/octicons/3.5.0/octicons.css'
+ type='text/css' rel='stylesheet'>
+
<link href='/stylesheets/nitweb_bootstrap.css' rel='stylesheet'>
<link href='/stylesheets/nitweb.css' rel='stylesheet'>
</div>
</form>
</div>
+ <div class='col-xs-2'>
+ <user-menu />
+ </div>
</div>
</nav>
<div ng-view></div>
<script src='/javascripts/index.js'></script>
<script src='/javascripts/docdown.js'></script>
<script src='/javascripts/metrics.js'></script>
+ <script src='/javascripts/users.js'></script>
</body>
</html>
}
}
}])
+
+ .factory('User', [ '$http', function($http) {
+ return {
+ loadUser: function(cb, cbErr) {
+ $http.get(apiUrl + '/user')
+ .success(cb)
+ .error(cbErr);
+ }
+ }
+ }])
})();
*/
(function() {
- angular.module('nitweb', ['ngRoute', 'ngSanitize', 'angular-loading-bar', 'entities', 'docdown', 'index', 'metrics'])
+ angular.module('nitweb', ['ngRoute', 'ngSanitize', 'angular-loading-bar', 'entities', 'docdown', 'index', 'metrics', 'users'])
.config(['cfpLoadingBarProvider', function(cfpLoadingBarProvider) {
cfpLoadingBarProvider.includeSpinner = false;
}])
controller: 'IndexCtrl',
controllerAs: 'indexCtrl'
})
+ .when('/user', {
+ templateUrl: 'views/user.html',
+ controller: 'UserCtrl',
+ controllerAs: 'userCtrl'
+ })
.when('/docdown', {
templateUrl: 'views/docdown.html',
controller: 'DocdownCtrl',
controllerAs: 'docdownCtrl'
})
+ .when('/login', {
+ controller : function(){
+ window.location.replace('/login');
+ },
+ template : "<div></div>"
+ })
+ .when('/logout', {
+ controller : function(){
+ window.location.replace('/logout');
+ },
+ template : "<div></div>"
+ })
.when('/doc/:id', {
templateUrl: 'views/doc.html',
controller: 'EntityCtrl',
--- /dev/null
+/*
+ * Copyright 2016 Alexandre Terrasa <alexandre@moz-code.org>.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+ angular
+ .module('users', ['ngSanitize', 'model'])
+
+ .controller('UserCtrl', ['User', '$routeParams', '$scope', function(User, $routeParams, $scope) {
+ this.loadUser = function() {
+ User.loadUser(
+ function(data) {
+ $scope.user = data;
+ }, function(err) {
+ $scope.error = err;
+ });
+ };
+
+ this.loadUser();
+ }])
+
+ .directive('userMenu', ['User', function(User) {
+ return {
+ restrict: 'E',
+ templateUrl: '/directives/user/user-menu.html',
+ link: function ($scope, element, attrs) {
+ $scope.loadUser = function() {
+ User.loadUser(
+ function(data) {
+ $scope.user = data;
+ }, function(err) {
+ //$scope.error = err;
+ });
+ }
+ $scope.loadUser();
+ }
+ };
+ }])
+})();
border-radius: 2px;
}
+.avatar-icon {
+ width: 18px;
+ height: 18px;
+}
+
/*
* Code Highlighting
*/
--- /dev/null
+<div class='container'>
+ <div class='col-xs-4'>
+ <img class='avatar' width='100%' src='{{user.avatar_url}}' />
+ <h1>{{user.login}}</h1>
+ <h3>{{user.name}}</h3>
+ <ul class='list-unstyled'>
+ <li>
+ <span class='glyphicon glyphicon-envelope' />
+ <a href='mailto:{{user.email}}'>{{user.email}}</a>
+ </li>
+ <li>
+ <span class='glyphicon glyphicon-link' />
+ <a href='{{user.blog}}'>{{user.blog}}</a>
+ </li>
+ </ul>
+ </div>
+ <div class='col-xs-8'>
+ Nothing to display yet.
+ </div>
+<div>
private import parser_util
import modelize
private import annotation
+intrude import literal
redef class ToolContext
# The second phase of the serialization
var serialization_phase_post_model: Phase = new SerializationPhasePostModel(self,
- [modelize_class_phase, serialization_phase_pre_model])
-
- private fun place_holder_type_name: String do return "PlaceHolderTypeWhichShouldNotExist"
+ [modelize_property_phase, serialization_phase_pre_model])
end
redef class ANode
# Is this node annotated to not be made serializable?
private fun is_noserialize: Bool do return false
-
- private fun accept_precise_type_visitor(v: PreciseTypeVisitor) do visit_all(v)
end
redef class ADefinition
if not node isa AModuledecl then
var up_serialize = false
var up: nullable ANode = node
- loop
+ while up != null do
up = up.parent
if up == null then
break
# Add services
var per_attribute = not serialize_by_default
generate_serialization_method(nclassdef, per_attribute)
- generate_deserialization_init(nclassdef, per_attribute)
+ generate_deserialization_init(nclassdef)
end
end
nmodule.inits_to_retype.clear
# collect all classes
- var auto_serializable_nclassdefs = new Array[AStdClassdef]
- for nclassdef in nmodule.n_classdefs do
- if nclassdef isa AStdClassdef and nclassdef.how_serialize != null then
- auto_serializable_nclassdefs.add nclassdef
- end
- end
-
+ var auto_serializable_nclassdefs = nmodule.auto_serializable_nclassdefs
if not auto_serializable_nclassdefs.is_empty then
generate_deserialization_method(nmodule, auto_serializable_nclassdefs)
end
end
+ # Implement `core_serialize_to` on `nclassdef`
+ #
+ # Are attributes serialized on demand `per_attribute` with `serialize`?
+ # Otherwise they are serialized by default, and we check instead for `noserialize`.
fun generate_serialization_method(nclassdef: AClassdef, per_attribute: Bool)
do
var npropdefs = nclassdef.n_propdefs
npropdefs.push(toolcontext.parse_propdef(code.join("\n")))
end
- # Add a constructor to the automated nclassdef
- fun generate_deserialization_init(nclassdef: AClassdef, per_attribute: Bool)
+ # Add an empty constructor to the automated nclassdef
+ #
+ # Will be filled by `SerializationPhasePostModel`.
+ fun generate_deserialization_init(nclassdef: AClassdef)
do
var npropdefs = nclassdef.n_propdefs
end
end
+ var code = """
+redef init from_deserializer(v: Deserializer) do abort"""
+
+ var npropdef = toolcontext.parse_propdef(code).as(AMethPropdef)
+ npropdefs.add npropdef
+ nclassdef.parent.as(AModule).inits_to_retype.add npropdef
+ end
+
+ # Add an empty `Deserializer::deserialize_class_intern`
+ #
+ # Will be filled by `SerializationPhasePostModel`.
+ fun generate_deserialization_method(nmodule: AModule, nclassdefs: Array[AStdClassdef])
+ do
+ var code = new Array[String]
+
+ var deserializer_nclassdef = nmodule.deserializer_nclassdef
+ var deserializer_npropdef
+ if deserializer_nclassdef == null then
+ # create the class
+ code.add "redef class Deserializer"
+ deserializer_npropdef = null
+ else
+ deserializer_npropdef = deserializer_nclassdef.deserializer_npropdef
+ end
+
+ if deserializer_npropdef == null then
+ # create the property
+ code.add " redef fun deserialize_class_intern(name) do abort"
+ else
+ toolcontext.error(deserializer_npropdef.location, "Error: `Deserializer::deserialize_class_intern` is generated and must not be defined, use `deserialize_class` instead.")
+ return
+ end
+
+ if deserializer_nclassdef == null then
+ code.add "end"
+ nmodule.n_classdefs.add toolcontext.parse_classdef(code.join("\n"))
+ else
+ deserializer_nclassdef.n_propdefs.add(toolcontext.parse_propdef(code.join("\n")))
+ end
+ end
+end
+
+private class SerializationPhasePostModel
+ super Phase
+
+ # Fill the deserialization init `from_deserializer` and `Deserializer.deserialize_class_intern`
+ redef fun process_nmodule(nmodule)
+ do
+ for npropdef in nmodule.inits_to_retype do
+ var nclassdef = npropdef.parent
+ assert nclassdef isa AStdClassdef
+
+ var serialize_by_default = nclassdef.how_serialize
+ assert serialize_by_default != null
+
+ var per_attribute = not serialize_by_default
+ fill_deserialization_init(nclassdef, npropdef, per_attribute)
+ end
+
+ # collect all classes
+ var auto_serializable_nclassdefs = nmodule.auto_serializable_nclassdefs
+ if not auto_serializable_nclassdefs.is_empty then
+ fill_deserialization_method(nmodule, auto_serializable_nclassdefs)
+ end
+ end
+
+ # Fill the constructor to the generated `init_npropdef` of `nclassdef`
+ fun fill_deserialization_init(nclassdef: AClassdef, init_npropdef: AMethPropdef, per_attribute: Bool)
+ do
var code = new Array[String]
code.add """
redef init from_deserializer(v: Deserializer)
v.notify_of_creation self
"""
- for attribute in npropdefs do if attribute isa AAttrPropdef then
+ for attribute in nclassdef.n_propdefs do
+ if not attribute isa AAttrPropdef then continue
# Is `attribute` to be skipped?
if (per_attribute and not attribute.is_serialize) or
attribute.is_noserialize then continue
- var n_type = attribute.n_type
- var type_name
- var type_name_pretty
- if n_type == null then
- # Use a place holder, we will replace it with the inferred type after the model phases
- type_name = toolcontext.place_holder_type_name
- type_name_pretty = "Unknown type"
- else
- type_name = n_type.type_name
- type_name_pretty = type_name
- end
+ var mtype = attribute.mtype
+ if mtype == null then continue
+ var type_name = mtype.to_s
var name = attribute.name
if type_name == "nullable Object" then
# Don't type check
code.add """
- var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}")
+ var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}", "{{{type_name}}}")
"""
else code.add """
- var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}")
+ var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}", "{{{type_name}}}")
if not {{{name}}} isa {{{type_name}}} then
# Check if it was a subjectent error
- v.errors.add new AttributeTypeError(self, "{{{attribute.serialize_name}}}", {{{name}}}, "{{{type_name_pretty}}}")
+ v.errors.add new AttributeTypeError(self, "{{{attribute.serialize_name}}}", {{{name}}}, "{{{type_name}}}")
# Clear subjacent error
if v.keep_going == false then return
code.add "end"
+ # Replace the body of the constructor
var npropdef = toolcontext.parse_propdef(code.join("\n")).as(AMethPropdef)
- npropdefs.add npropdef
- nclassdef.parent.as(AModule).inits_to_retype.add npropdef
+ init_npropdef.n_block = npropdef.n_block
+
+ # Run the literal phase on the generated code
+ var v = new LiteralVisitor(toolcontext)
+ v.enter_visit(npropdef.n_block)
end
- # Added to the abstract serialization service
- fun generate_deserialization_method(nmodule: AModule, nclassdefs: Array[AStdClassdef])
+ # Fill the abstract serialization service
+ fun fill_deserialization_method(nmodule: AModule, nclassdefs: Array[AStdClassdef])
do
- var code = new Array[String]
-
var deserializer_nclassdef = nmodule.deserializer_nclassdef
- var deserializer_npropdef
- if deserializer_nclassdef == null then
- # create the class
- code.add "redef class Deserializer"
- deserializer_npropdef = null
- else
- deserializer_npropdef = deserializer_nclassdef.deserializer_npropdef
- end
+ if deserializer_nclassdef == null then return
+ var deserializer_npropdef = deserializer_nclassdef.deserializer_npropdef
+ if deserializer_npropdef == null then return
- if deserializer_npropdef == null then
- # create the property
- code.add " redef fun deserialize_class_intern(name)"
- code.add " do"
- else
- toolcontext.error(deserializer_npropdef.location, "Error: `Deserializer::deserialize_class_intern` is generated and must not be defined, use `deserialize_class` instead.")
- return
- end
+ # Collect local types expected to be deserialized
+ var types_to_deserialize = new Set[String]
+ ## Local serializable standard class without parameters
for nclassdef in nclassdefs do
- var name = nclassdef.n_qid.n_id.text
- if nclassdef.n_formaldefs.is_empty and
- nclassdef.n_classkind isa AConcreteClasskind then
+ var mclass = nclassdef.mclass
+ if mclass == null then continue
- code.add " if name == \"{name}\" then return new {name}.from_deserializer(self)"
+ if mclass.arity == 0 and mclass.kind == concrete_kind then
+ types_to_deserialize.add mclass.name
end
end
- code.add " return super"
- code.add " end"
+ ## Static parametized types on serializable attributes
+ for nclassdef in nmodule.n_classdefs do
+ if not nclassdef isa AStdClassdef then continue
- if deserializer_nclassdef == null then
- code.add "end"
- nmodule.n_classdefs.add toolcontext.parse_classdef(code.join("\n"))
- else
- deserializer_nclassdef.n_propdefs.add(toolcontext.parse_propdef(code.join("\n")))
- end
- end
-end
+ for attribute in nclassdef.n_propdefs do
+ if not attribute isa AAttrPropdef then continue
-private class SerializationPhasePostModel
- super Phase
+ var serialize_by_default = nclassdef.how_serialize
+ if serialize_by_default == null then continue
+ var per_attribute = not serialize_by_default
- redef fun process_nmodule(nmodule)
- do
- for npropdef in nmodule.inits_to_retype do
- var mpropdef = npropdef.mpropdef
- if mpropdef == null then continue # skip error
- var v = new PreciseTypeVisitor(npropdef, mpropdef.mclassdef, toolcontext)
- npropdef.accept_precise_type_visitor v
- end
- end
-end
+ # Is `attribute` to be skipped?
+ if (per_attribute and not attribute.is_serialize) or
+ attribute.is_noserialize then continue
-# Visitor on generated constructors to replace the expected type of deserialized attributes
-private class PreciseTypeVisitor
- super Visitor
+ var mtype = attribute.mtype
+ if mtype == null then continue
+ if mtype isa MNullableType then mtype = mtype.mtype
- var npropdef: AMethPropdef
- var mclassdef: MClassDef
- var toolcontext: ToolContext
+ if mtype isa MClassType and mtype.mclass.arity > 0 and
+ mtype.mclass.kind == concrete_kind and not mtype.need_anchor then
- redef fun visit(n) do n.accept_precise_type_visitor(self)
-end
+ # Check is a `Serializable`
+ var mmodule = nmodule.mmodule
+ if mmodule == null then continue
-redef class AIsaExpr
- redef fun accept_precise_type_visitor(v)
- do
- if n_type.collect_text != v.toolcontext.place_holder_type_name then return
-
- var attr_name = "_" + n_expr.collect_text
- for mattrdef in v.mclassdef.mpropdefs do
- if mattrdef isa MAttributeDef and mattrdef.name == attr_name then
- var new_ntype = v.toolcontext.parse_something(mattrdef.static_mtype.to_s)
- n_type.replace_with new_ntype
- break
+ var greaters = mtype.mclass.in_hierarchy(mmodule).greaters
+ var is_serializable = false
+ for sup in greaters do if sup.name == "Serializable" then
+ is_serializable = true
+ break
+ end
+
+ if is_serializable then types_to_deserialize.add mtype.to_s
+ end
end
end
+
+ # Build implementation code
+ var code = new Array[String]
+ code.add "redef fun deserialize_class_intern(name)"
+ code.add "do"
+
+ for name in types_to_deserialize do
+ code.add " if name == \"{name}\" then return new {name}.from_deserializer(self)"
+ end
+
+ code.add " return super"
+ code.add "end"
+
+ # Replace the body of the constructor
+ var npropdef = toolcontext.parse_propdef(code.join("\n")).as(AMethPropdef)
+ deserializer_npropdef.n_block = npropdef.n_block
+
+ # Run the literal phase on the generated code
+ var v = new LiteralVisitor(toolcontext)
+ v.enter_visit(npropdef.n_block)
end
end
private fun deserializer_nclassdef: nullable AStdClassdef
do
for nclassdef in n_classdefs do
- if nclassdef isa AStdClassdef and nclassdef.n_qid.n_id.text == "Deserializer" then
- return nclassdef
- end
+ if not nclassdef isa AStdClassdef then continue
+ var n_qid = nclassdef.n_qid
+ if n_qid != null and n_qid.n_id.text == "Deserializer" then return nclassdef
end
return null
private var inits_to_retype = new Array[AMethPropdef]
- redef fun is_serialize do return n_moduledecl != null and n_moduledecl.is_serialize
+ redef fun is_serialize
+ do
+ var n_moduledecl = n_moduledecl
+ return n_moduledecl != null and n_moduledecl.is_serialize
+ end
+
+ # `AStdClassdef` marked as serializable, itself or one of theur attribute
+ private var auto_serializable_nclassdefs: Array[AStdClassdef] is lazy do
+ var array = new Array[AStdClassdef]
+ for nclassdef in n_classdefs do
+ if nclassdef isa AStdClassdef and nclassdef.how_serialize != null then
+ array.add nclassdef
+ end
+ end
+ return array
+ end
end
redef class AStdClassdef
# Runs a webserver based on nitcorn that render things from model.
module nitweb
+import popcorn::pop_config
+import popcorn::pop_auth
import frontend
import web
+redef class NitwebConfig
+
+ # Github client id used for Github OAuth login.
+ #
+ # * key: `github.client_id`
+ # * default: ``
+ var github_client_id: String is lazy do return value_or_default("github.client.id", "")
+
+ # Github client secret used for Github OAuth login.
+ #
+ # * key: `github.client_secret`
+ # * default: ``
+ var github_client_secret: String is lazy do
+ return value_or_default("github.client.secret", "")
+ end
+end
+
redef class ToolContext
- # Host name to bind on.
+ # Path to app config file.
+ var opt_config = new OptionString("Path to app config file", "--config")
+
+ # Host name to bind on (will overwrite the config one).
var opt_host = new OptionString("Host to bind the server on", "--host")
- # Port number to bind on.
- var opt_port = new OptionInt("Port number to use", 3000, "--port")
+ # Port number to bind on (will overwrite the config one).
+ var opt_port = new OptionInt("Port number to use", -1, "--port")
# Web rendering phase.
var webphase: Phase = new NitwebPhase(self, null)
init do
super
- option_context.add_option(opt_host, opt_port)
+ option_context.add_option(opt_config, opt_host, opt_port)
end
end
# Phase that builds the model and wait for http request to serve pages.
private class NitwebPhase
super Phase
- redef fun process_mainmodule(mainmodule, mmodules)
- do
- var model = mainmodule.model
- var modelbuilder = toolcontext.modelbuilder
- # Build catalog
+ # Build the nitweb config from `toolcontext` options.
+ fun build_config(toolcontext: ToolContext, mainmodule: MModule): NitwebConfig do
+ var config_file = toolcontext.opt_config.value
+ if config_file == null then config_file = "nitweb.ini"
+ var config = new NitwebConfig(
+ config_file,
+ toolcontext.modelbuilder.model,
+ mainmodule,
+ toolcontext.modelbuilder)
+ var opt_host = toolcontext.opt_host.value
+ if opt_host != null then config["app.host"] = opt_host
+ var opt_port = toolcontext.opt_port.value
+ if opt_port >= 0 then config["app.port"] = opt_port.to_s
+ return config
+ end
+
+ # Build the nit catalog used in homepage.
+ fun build_catalog(model: Model, modelbuilder: ModelBuilder): Catalog do
var catalog = new Catalog(modelbuilder)
for mpackage in model.mpackages do
catalog.deps.add_node(mpackage)
catalog.git_info(mpackage)
catalog.package_page(mpackage)
end
+ return catalog
+ end
- # Prepare mongo connection
- var mongo = new MongoClient("mongodb://localhost:27017/")
- var db = mongo.database("nitweb")
- var collection = db.collection("stars")
-
- # Run the server
- var host = toolcontext.opt_host.value or else "localhost"
- var port = toolcontext.opt_port.value
+ redef fun process_mainmodule(mainmodule, mmodules)
+ do
+ var model = mainmodule.model
+ var modelbuilder = toolcontext.modelbuilder
+ var config = build_config(toolcontext, mainmodule)
+ var catalog = build_catalog(model, modelbuilder)
var app = new App
+ app.use_before("/*", new SessionInit)
app.use_before("/*", new RequestClock)
- app.use("/api", new APIRouter(model, modelbuilder, mainmodule, catalog, collection))
+ app.use("/api", new NitwebAPIRouter(config, catalog))
+ app.use("/login", new GithubLogin(config.github_client_id))
+ app.use("/oauth", new GithubOAuthCallBack(config.github_client_id, config.github_client_secret))
+ app.use("/logout", new GithubLogout)
app.use("/*", new StaticHandler(toolcontext.share_dir / "nitweb", "index.html"))
app.use_after("/*", new ConsoleLog)
- app.listen(host, port.to_i)
+ app.listen(config.app_host, config.app_port)
end
end
# Group all api handlers in one router.
-class APIRouter
- super Router
-
- # Model to pass to handlers.
- var model: Model
-
- # ModelBuilder to pass to handlers.
- var modelbuilder: ModelBuilder
-
- # Mainmodule to pass to handlers.
- var mainmodule: MModule
+class NitwebAPIRouter
+ super APIRouter
# Catalog to pass to handlers.
var catalog: Catalog
- # Mongo collection used to store ratings.
- var collection: MongoCollection
-
init do
- use("/catalog", new APICatalogRouter(model, mainmodule, catalog))
- use("/list", new APIList(model, mainmodule))
- use("/search", new APISearch(model, mainmodule))
- use("/random", new APIRandom(model, mainmodule))
- use("/entity/:id", new APIEntity(model, mainmodule))
- use("/code/:id", new APIEntityCode(model, mainmodule, modelbuilder))
- use("/uml/:id", new APIEntityUML(model, mainmodule))
- use("/linearization/:id", new APIEntityLinearization(model, mainmodule))
- use("/defs/:id", new APIEntityDefs(model, mainmodule))
- use("/feedback/", new APIFeedbackRouter(model, mainmodule, collection))
- use("/inheritance/:id", new APIEntityInheritance(model, mainmodule))
- use("/graph/", new APIGraphRouter(model, mainmodule))
- use("/docdown/", new APIDocdown(model, mainmodule, modelbuilder))
- use("/metrics/", new APIMetricsRouter(model, mainmodule))
+ use("/catalog", new APICatalogRouter(config, catalog))
+ use("/list", new APIList(config))
+ use("/search", new APISearch(config))
+ use("/random", new APIRandom(config))
+ use("/entity/:id", new APIEntity(config))
+ use("/code/:id", new APIEntityCode(config))
+ use("/uml/:id", new APIEntityUML(config))
+ use("/linearization/:id", new APIEntityLinearization(config))
+ use("/defs/:id", new APIEntityDefs(config))
+ use("/feedback/", new APIFeedbackRouter(config))
+ use("/inheritance/:id", new APIEntityInheritance(config))
+ use("/graph/", new APIGraphRouter(config))
+ use("/docdown/", new APIDocdown(config))
+ use("/metrics/", new APIMetricsRouter(config))
+ use("/user", new GithubUser)
end
end
var toolcontext = new ToolContext
var tpl = new Template
tpl.add "Usage: nitweb [OPTION]... <file.nit>...\n"
-tpl.add "Run a webserver based on nitcorn that serve pages about model."
+tpl.add "Run a webserver based on nitcorn that serves pages about model."
toolcontext.tooldescription = tpl.write_to_string
# process options
# Group all api handlers in one router.
class APICatalogRouter
- super Router
-
- # Model to pass to handlers.
- var model: Model
-
- # Mainmodule to pass to handlers.
- var mainmodule: MModule
+ super APIRouter
# Catalog to pass to handlers.
var catalog: Catalog
init do
- use("/highlighted", new APICatalogHighLighted(model, mainmodule, catalog))
- use("/required", new APICatalogMostRequired(model, mainmodule, catalog))
- use("/bytags", new APICatalogByTags(model, mainmodule, catalog))
- use("/contributors", new APICatalogContributors(model, mainmodule, catalog))
- use("/stats", new APICatalogStats(model, mainmodule, catalog))
+ use("/highlighted", new APICatalogHighLighted(config, catalog))
+ use("/required", new APICatalogMostRequired(config, catalog))
+ use("/bytags", new APICatalogByTags(config, catalog))
+ use("/contributors", new APICatalogContributors(config, catalog))
+ use("/stats", new APICatalogStats(config, catalog))
end
end
redef fun get(req, res) do
var obj = new JsonObject
- obj["packages"] = model.mpackages.length
+ obj["packages"] = config.model.mpackages.length
obj["maintainers"] = catalog.maint2proj.length
obj["contributors"] = catalog.contrib2proj.length
obj["modules"] = catalog.mmodules.sum
redef fun get(req, res) do
if catalog.deps.not_empty then
var reqs = new Counter[MPackage]
- for p in model.mpackages do
+ for p in config.model.mpackages do
reqs[p] = catalog.deps[p].smallers.length - 1
end
res.json list_best(reqs)
class APIDocdown
super APIHandler
- # Modelbuilder used by the commands
- var modelbuilder: ModelBuilder
-
# Specific Markdown processor to use within Nitweb
var md_processor: MarkdownProcessor is lazy do
var proc = new MarkdownProcessor
- proc.emitter.decorator = new NitwebDecorator(view, modelbuilder)
+ proc.emitter.decorator = new NitwebDecorator(view, config.modelbuilder)
return proc
end
import web_base
import mongodb
-# Group all api handlers in one router
-class APIFeedbackRouter
- super Router
+redef class NitwebConfig
+
+ # MongoDB uri used for data persistence.
+ #
+ # * key: `mongo.uri`
+ # * default: `mongodb://localhost:27017/`
+ var mongo_uri: String is lazy do
+ return value_or_default("mongo.uri", "mongodb://localhost:27017/")
+ end
- # Model to pass to handlers
- var model: Model
+ # MongoDB DB used for data persistence.
+ #
+ # * key: `mongo.db`
+ # * default: `nitweb`
+ var mongo_db: String is lazy do return value_or_default("mongo.db", "nitweb")
- # Mainmodule to pass to handlers
- var mainmodule: MModule
+ # Mongo instance
+ var mongo: MongoClient is lazy do return new MongoClient(mongo_uri)
- # Mongo collection used to store ratings
- var collection: MongoCollection
+ # Database instance
+ var db: MongoDb is lazy do return mongo.database(mongo_db)
+
+ # MongoDB collection used to store stars.
+ var stars: MongoCollection is lazy do return db.collection("stars")
+end
+
+# Group all api handlers in one router
+class APIFeedbackRouter
+ super APIRouter
init do
- use("/stars/:id", new APIStars(model, mainmodule, collection))
+ use("/stars/:id", new APIStars(config))
end
end
class APIStars
super APIHandler
- # Collection used to store ratings
- var collection: MongoCollection
-
redef fun get(req, res) do
var mentity = mentity_from_uri(req, res)
if mentity == null then
end
var val = new MEntityRating(mentity.full_name, rating, get_time)
- collection.insert(val.json)
+ config.stars.insert(val.json)
res.json mentity_ratings(mentity)
end
var req = new JsonObject
req["mentity"] = mentity.full_name
- var rs = collection.find_all(req)
+ var rs = config.stars.find_all(req)
for r in rs do ratings.ratings.add new MEntityRating.from_json(r)
return ratings
end
# Group all api handlers in one router.
class APIGraphRouter
- super Router
-
- # Model to pass to handlers.
- var model: Model
-
- # Mainmodule to pass to handlers.
- var mainmodule: MModule
+ super APIRouter
init do
- use("/inheritance/:id", new APIInheritanceGraph(model, mainmodule))
+ use("/inheritance/:id", new APIInheritanceGraph(config))
end
end
# Group all api handlers in one router.
class APIMetricsRouter
- super Router
-
- # Model to pass to handlers.
- var model: Model
-
- # Mainmodule to pass to handlers.
- var mainmodule: MModule
+ super APIRouter
init do
- use("/structural/:id", new APIStructuralMetrics(model, mainmodule))
+ use("/structural/:id", new APIStructuralMetrics(config))
end
end
super APIHandler
private fun mclasses_metrics: MetricSet do
+ var mainmodule = config.mainmodule
var metrics = new MetricSet
metrics.register(new CNOA(mainmodule, view))
metrics.register(new CNOP(mainmodule, view))
end
private fun mmodules_metrics: MetricSet do
+ var mainmodule = config.mainmodule
var metrics = new MetricSet
metrics.register(new MNOA(mainmodule, view))
metrics.register(new MNOP(mainmodule, view))
res.error 404
return
end
- var lin = mentity.collect_linearization(mainmodule)
+ var lin = mentity.collect_linearization(config.mainmodule)
if lin == null then
res.error 404
return
var dot
if mentity isa MClassDef then mentity = mentity.mclass
if mentity isa MClass then
- var uml = new UMLModel(view, mainmodule)
+ var uml = new UMLModel(view, config.mainmodule)
dot = uml.generate_class_uml.write_to_string
else if mentity isa MModule then
var uml = new UMLModel(view, mentity)
class APIEntityCode
super APIHandler
- # Modelbuilder used to access sources.
- var modelbuilder: ModelBuilder
-
redef fun get(req, res) do
var mentity = mentity_from_uri(req, res)
if mentity == null then return
# Highlight `mentity` source code.
private fun render_source(mentity: MEntity): nullable HTMLTag do
- var node = modelbuilder.mentity2node(mentity)
+ var node = config.modelbuilder.mentity2node(mentity)
if node == null then return null
var hl = new HighlightVisitor
hl.enter_visit node
import model::model_json
import doc_down
import popcorn
+import popcorn::pop_config
-# Specific nitcorn Action that uses a Model
-class ModelHandler
- super Handler
+# Nitweb config file.
+class NitwebConfig
+ super AppConfig
# Model to use.
var model: Model
# MModule used to flatten model.
var mainmodule: MModule
+ # Modelbuilder used to access sources.
+ var modelbuilder: ModelBuilder
+end
+
+# Specific nitcorn Action that uses a Model
+class ModelHandler
+ super Handler
+
+ # App config.
+ var config: NitwebConfig
+
# Find the MEntity ` with `full_name`.
fun find_mentity(model: ModelView, full_name: nullable String): nullable MEntity do
if full_name == null then return null
# Init the model view from the `req` uri parameters.
fun init_model_view(req: HttpRequest): ModelView do
- var view = new ModelView(model)
+ var view = new ModelView(config.model)
var show_private = req.bool_arg("private") or else false
if not show_private then view.min_visibility = protected_visibility
#
# So we can cache the model view.
var view: ModelView is lazy do
- var view = new ModelView(model)
+ var view = new ModelView(config.model)
view.min_visibility = private_visibility
view.include_fictive = true
view.include_empty_doc = true
end
end
+# A Rooter dedicated to APIHandlers.
+class APIRouter
+ super Router
+
+ # App config.
+ var config: NitwebConfig
+end
+
redef class MEntity
# URL to `self` within the web interface.
<D: <B: <A: false b 123.123 2345 new line ->
<- false p4ssw0rd> 1111 f"\r\/> true>
-Deserialization Error: Doesn't know how to deserialize class "Array", Deserialization Error: Wrong type on `E::a` expected `Unknown type`, got `null`, Deserialization Error: Doesn't know how to deserialize class "Array", Deserialization Error: Wrong type on `E::b` expected `Unknown type`, got `null`
+Deserialization Error: Doesn't know how to deserialize class "Array", Deserialization Error: Wrong type on `E::a` expected `Array[Object]`, got `null`, Deserialization Error: Doesn't know how to deserialize class "Array", Deserialization Error: Wrong type on `E::b` expected `Array[nullable Serializable]`, got `null`
# Src:
<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
# Dst:
Deserialization Error: Doesn't know how to deserialize class "F"
Deserialization Error: Doesn't know how to deserialize class "F"
-Deserialization Error: Doesn't know how to deserialize class "HashSet", Deserialization Error: Wrong type on `G::hs` expected `Unknown type`, got `null`, Deserialization Error: Doesn't know how to deserialize class "ArraySet", Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`, Deserialization Error: Doesn't know how to deserialize class "HashMap", Deserialization Error: Wrong type on `G::hm` expected `Unknown type`, got `null`, Deserialization Error: Doesn't know how to deserialize class "ArrayMap", Deserialization Error: Wrong type on `G::am` expected `Unknown type`, got `null`
+Deserialization Error: Doesn't know how to deserialize class "HashSet", Deserialization Error: Wrong type on `G::hs` expected `HashSet[Int]`, got `null`, Deserialization Error: Doesn't know how to deserialize class "ArraySet", Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`, Deserialization Error: Doesn't know how to deserialize class "HashMap", Deserialization Error: Wrong type on `G::hm` expected `HashMap[String, Int]`, got `null`, Deserialization Error: Doesn't know how to deserialize class "ArrayMap", Deserialization Error: Wrong type on `G::am` expected `ArrayMap[String, String]`, got `null`
# Src:
<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
# Dst:
<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
Deserialization Error: Doesn't know how to deserialize class "Array"
-Deserialization Error: Wrong type on `E::a` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `E::a` expected `Array[Object]`, got `null`
Deserialization Error: Doesn't know how to deserialize class "Array"
-Deserialization Error: Wrong type on `E::b` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `E::b` expected `Array[nullable Serializable]`, got `null`
# Nit:
<E: 2222>
<G: hs: ; s: ; hm: ; am: >
Deserialization Error: Doesn't know how to deserialize class "HashSet"
-Deserialization Error: Wrong type on `G::hs` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `G::hs` expected `HashSet[Int]`, got `null`
Deserialization Error: Doesn't know how to deserialize class "ArraySet"
Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`
Deserialization Error: Doesn't know how to deserialize class "HashMap"
-Deserialization Error: Wrong type on `G::hm` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `G::hm` expected `HashMap[String, Int]`, got `null`
Deserialization Error: Doesn't know how to deserialize class "ArrayMap"
-Deserialization Error: Wrong type on `G::am` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `G::am` expected `ArrayMap[String, String]`, got `null`
<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
Deserialization Error: Doesn't know how to deserialize class "Array"
-Deserialization Error: Wrong type on `E::a` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `E::a` expected `Array[Object]`, got `null`
Deserialization Error: Doesn't know how to deserialize class "Array"
-Deserialization Error: Wrong type on `E::b` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `E::b` expected `Array[nullable Serializable]`, got `null`
# Nit:
<E: 2222>
<G: hs: ; s: ; hm: ; am: >
Deserialization Error: Doesn't know how to deserialize class "HashSet"
-Deserialization Error: Wrong type on `G::hs` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `G::hs` expected `HashSet[Int]`, got `null`
Deserialization Error: Doesn't know how to deserialize class "ArraySet"
Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`
Deserialization Error: Doesn't know how to deserialize class "HashMap"
-Deserialization Error: Wrong type on `G::hm` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `G::hm` expected `HashMap[String, Int]`, got `null`
Deserialization Error: Doesn't know how to deserialize class "ArrayMap"
-Deserialization Error: Wrong type on `G::am` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `G::am` expected `ArrayMap[String, String]`, got `null`
Usage: nitweb [OPTION]... <file.nit>...
-Run a webserver based on nitcorn that serve pages about model.
+Run a webserver based on nitcorn that serves pages about model.
Use --help for help