Use the option --log to log things.
It is not that hard to add logged things and it helped me, in #1177, to track easily the bug in rta.
Pull-Request: #1178
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>
return id
end
- fun collections_list: Array[String] is cached do return ["List", "ArrayList", "LinkedList", "Vector", "Set", "SortedSet", "HashSet", "TreeSet", "LinkedHashSet", "Map", "SortedMap", "HashMap", "TreeMap", "Hashtable", "LinkedHashMap"]
- fun iterable: Array[String] is cached do return ["ArrayList", "Set", "HashSet", "LinkedHashSet", "LinkedList", "Stack", "TreeSet", "Vector"]
- fun maps: Array[String] is cached do return ["Map", "SortedMap", "HashMap", "TreeMap", "Hashtable", "LinkedHashMap"]
+ var collections_list: Array[String] is lazy do return ["List", "ArrayList", "LinkedList", "Vector", "Set", "SortedSet", "HashSet", "TreeSet", "LinkedHashSet", "Map", "SortedMap", "HashMap", "TreeMap", "Hashtable", "LinkedHashMap"]
+ var iterable: Array[String] is lazy do return ["ArrayList", "Set", "HashSet", "LinkedHashSet", "LinkedList", "Stack", "TreeSet", "Vector"]
+ var maps: Array[String] is lazy do return ["Map", "SortedMap", "HashMap", "TreeMap", "Hashtable", "LinkedHashMap"]
end
class NitType
# Result is returned as an array containg ordered entries:
# `breadcrumbs.first` is the root entry and
# `breadcrumbs.last == self`
- fun breadcrumbs: Array[WikiEntry] is cached do
+ var breadcrumbs: Array[WikiEntry] is lazy do
var path = new Array[WikiEntry]
var entry: nullable WikiEntry = self
while entry != null and not entry.is_root do
# Extract the markdown text from `source_file`.
#
# REQUIRE: `has_source`.
- fun md: String is cached do
+ var md: String is lazy do
assert has_source
var file = new FileReader.open(src_full_path.to_s)
var md = file.read_all
#
# * key: `wiki.name`
# * default: `MyWiki`
- fun wiki_name: String is cached do return value_or_default("wiki.name", "MyWiki")
+ var wiki_name: String is lazy do return value_or_default("wiki.name", "MyWiki")
# Site description.
#
#
# * key: `wiki.desc`
# * default: ``
- fun wiki_desc: String is cached do return value_or_default("wiki.desc", "")
+ var wiki_desc: String is lazy do return value_or_default("wiki.desc", "")
# Site logo url.
#
#
# * key: `wiki.logo`
# * default: ``
- fun wiki_logo: String is cached do return value_or_default("wiki.logo", "")
+ var wiki_logo: String is lazy do return value_or_default("wiki.logo", "")
# Root url of the wiki.
#
# * key: `wiki.root_url`
# * default: `http://localhost/`
- fun root_url: String is cached do return value_or_default("wiki.root_url", "http://localhost/")
+ var root_url: String is lazy do return value_or_default("wiki.root_url", "http://localhost/")
# Root directory of the wiki.
#
# * key: `wiki.root_dir`
# * default: `./`
- fun root_dir: String is cached do return value_or_default("wiki.root_dir", "./").simplify_path
+ var root_dir: String is lazy do return value_or_default("wiki.root_dir", "./").simplify_path
# Pages directory.
#
#
# * key: `wiki.source_dir
# * default: `pages/`
- fun source_dir: String is cached do
+ var source_dir: String is lazy do
return value_or_default("wiki.source_dir", "pages/").simplify_path
end
#
# * key: `wiki.out_dir`
# * default: `out/`
- fun out_dir: String is cached do return value_or_default("wiki.out_dir", "out/").simplify_path
+ var out_dir: String is lazy do return value_or_default("wiki.out_dir", "out/").simplify_path
# Asset files directory.
#
#
# * key: `wiki.assets_dir`
# * default: `assets/`
- fun assets_dir: String is cached do
+ var assets_dir: String is lazy do
return value_or_default("wiki.assets_dir", "assets/").simplify_path
end
#
# * key: `wiki.templates_dir`
# * default: `templates/`
- fun templates_dir: String is cached do
+ var templates_dir: String is lazy do
return value_or_default("wiki.templates_dir", "templates/").simplify_path
end
#
# * key: `wiki.template`
# * default: `template.html`
- fun template_file: String is cached do
+ var template_file: String is lazy do
return value_or_default("wiki.template", "template.html")
end
#
# * key: `wiki.header`
# * default: `header.html`
- fun header_file: String is cached do
+ var header_file: String is lazy do
return value_or_default("wiki.header", "header.html")
end
#
# * key: `wiki.menu`
# * default: `menu.html`
- fun menu_file: String is cached do
+ var menu_file: String is lazy do
return value_or_default("wiki.menu", "menu.html")
end
#
# * key: `wiki.footer`
# * default: `footer.html`
- fun footer_file: String is cached do
+ var footer_file: String is lazy do
return value_or_default("wiki.footer", "footer.html")
end
#
# * key: `wiki.rsync_dir`
# * default: ``
- fun rsync_dir: String is cached do return value_or_default("wiki.rsync_dir", "")
+ var rsync_dir: String is lazy do return value_or_default("wiki.rsync_dir", "")
# Remote repository used to pull modifications on sources.
#
# * key: `wiki.git_origin`
# * default: `origin`
- fun git_origin: String is cached do return value_or_default("wiki.git_origin", "origin")
+ var git_origin: String is lazy do return value_or_default("wiki.git_origin", "origin")
# Remote branch used to pull modifications on sources.
#
# * key: `wiki.git_branch`
# * default: `master`
- fun git_branch: String is cached do return value_or_default("wiki.git_branch", "master")
+ var git_branch: String is lazy do return value_or_default("wiki.git_branch", "master")
end
# WikiSection custom configuration.
#
# If no file `index.md` exists for this section,
# a summary is generated using contained articles.
- fun index: WikiArticle is cached do
+ var index: WikiArticle is lazy do
for child in children.values do
if child isa WikiArticle and child.is_index then return child
end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014-2015 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.
+
+# `nitrpg` achievements.
+#
+# Players can unlock achievements by performing remarkable actions on the repo.
+# Achievements are rewarded by nitcoins.
+module achievements
+
+import events
+import statistics
+
+redef class GameEntity
+
+ # Register a new achievement for this game entity.
+ #
+ # Saves the achievement in game data.
+ # Do nothing is the achievement is already registered.
+ #
+ # TODO should update the achievement?
+ fun add_achievement(achievement: Achievement) do
+ var key = self.key / achievement.key
+ if game.store.has_key(key) then return
+ stats.inc("achievements")
+ achievement.save_in(self)
+ save
+ end
+
+ # Load the event from its `id`.
+ #
+ # Looks for the event save file in game data.
+ # Returns `null` if the event cannot be found.
+ fun load_achievement(id: String): nullable Achievement do
+ var key = self.key / "achievements" / id
+ if not game.store.has_key(key) then return null
+ var json = game.store.load_object(key)
+ return new Achievement.from_json(game, json)
+ end
+
+ # List all events registered in this entity.
+ #
+ # This list is reloaded from game data each time its called.
+ #
+ # To add events see `add_event`.
+ fun load_achievements: MapRead[String, Achievement] do
+ var res = new HashMap[String, Achievement]
+ var key = self.key / "achievements"
+ if not game.store.has_collection(key) then return res
+ var coll = game.store.list_collection(key)
+ for id in coll do
+ res[id.to_s] = load_achievement(id.to_s).as(not null)
+ end
+ return res
+ end
+end
+
+# Achievements are rewarded by `nitcoins`.
+#
+# An achievement represents a notable action performed by a `Player`.
+# Player that `unlock` achievements are rewarded by nitcoins.
+class Achievement
+ super GameEntity
+
+ redef var key is lazy do return "achievements" / id
+
+ redef var game
+
+ # Uniq ID for this achievement.
+ var id: String
+
+ # Name of this achievement.
+ var name: String
+
+ # Description of the achievement.
+ var desc: String
+
+ # Reward that this achievement give in nitcoins.
+ var reward: Int
+
+ # Is this achievement unlocked by somebody?
+ var is_unlocked: Bool is lazy do return not load_events.is_empty
+
+ # Init `self` from a `json` object.
+ #
+ # Used to load achievements from storage.
+ init from_json(game: Game, json: JsonObject) do
+ self.game = game
+ id = json["id"].to_s
+ name = json["name"].to_s
+ desc = json["desc"].to_s
+ reward = json["reward"].as(Int)
+ end
+
+ redef fun to_json do
+ var json = super
+ json["id"] = id
+ json["name"] = name
+ json["desc"] = desc
+ json["reward"] = reward
+ return json
+ end
+end
+
+redef class Player
+
+ # Is `a` unlocked for this `Player`?
+ fun has_achievement(a: Achievement): Bool do
+ return load_achievement(a.id) != null
+ end
+
+ # Unlocks an achievement for this Player based on a GithubEvent.
+ #
+ # Register the achievement and adds the achievement reward to the player
+ # nitcoins.
+ #
+ # Do nothing is this player has already unlocked the achievement.
+ #
+ # TODO: add abstraction so achievements do not depend on GithubEvent.
+ fun unlock_achievement(a: Achievement, event: GithubEvent) do
+ if has_achievement(a) then return
+ nitcoins += a.reward
+ add_achievement(a)
+ trigger_unlock_event(a, event)
+ end
+
+ # Create a new event that marks the achievement unlocking.
+ fun trigger_unlock_event(achievement: Achievement, event: GithubEvent) do
+ var obj = new JsonObject
+ obj["player"] = name
+ obj["reward"] = achievement.reward
+ obj["achievement"] = achievement.id
+ obj["github_event"] = event.json
+ var ge = new GameEvent(game, "achievement_unlocked", obj)
+ add_event(ge)
+ game.add_event(ge)
+ achievement.add_event(ge)
+ end
+end
+
+# `GameReactor` dedicated to achievements unlocking.
+interface AchievementReactor
+ super GameReactor
+
+ # Unic ID of the achievement this reactor unlocks.
+ fun id: String is abstract
+
+ # Name of the achievement this reactor unlocks.
+ fun name: String is abstract
+
+ # Description of the achievement this reactor unlocks.
+ fun desc: String is abstract
+
+ # Amount of nitcoins rewarded for unlocking the achievement.
+ fun reward: Int is abstract
+
+ # Return a new instance of the achievement to unlock.
+ fun new_achievement(game: Game): Achievement do
+ var achievement = new Achievement(game, id, name, desc, reward)
+ game.add_achievement(achievement)
+ return achievement
+ end
+end
+
+#####################
+### Issues
+#####################
+
+# Unlock achievement after X issues.
+#
+# Used to factorize behavior.
+abstract class PlayerXIssues
+ super AchievementReactor
+
+ # Number of PR required to unlock the achievement.
+ var threshold: Int is noinit
+
+ redef fun react_event(game, event) do
+ if not event isa IssuesEvent then return
+ if not event.action == "opened" then return
+ var player = event.issue.user.player(game)
+ if player.stats["issues"] == threshold then
+ var a = new_achievement(game)
+ player.unlock_achievement(a, event)
+ end
+ end
+end
+
+# Player open his first issue.
+class Player1Issue
+ super PlayerXIssues
+
+ redef var id = "player_1_issue"
+ redef var name = "First complaint"
+ redef var desc = "Open your first issue."
+ redef var reward = 10
+ redef var threshold = 1
+end
+
+# Player open 100 issues.
+class Player100Issues
+ super PlayerXIssues
+
+ redef var id = "player_100_issues"
+ redef var name = "Mature whiner"
+ redef var desc = "Open 100 issues in the game."
+ redef var reward = 100
+ redef var threshold = 100
+end
+
+# Player open 1 000 issues.
+class Player1KIssues
+ super PlayerXIssues
+
+ redef var id = "player_1000_issues"
+ redef var name = "You, sir, complain a lot"
+ redef var desc = "Open 1000 issues in the game."
+ redef var reward = 1000
+ redef var threshold = 1000
+end
+
+# Player open an issue about nitdoc.
+class IssueAboutNitdoc
+ super AchievementReactor
+
+ redef var id = "issue_about_nitdoc"
+ redef var name = "Say nitdoc again, I double dare you!"
+ redef var desc = "Open an issue with \"nitdoc\" in the title."
+ redef var reward = 10
+
+ redef fun react_event(game, event) do
+ if not event isa IssuesEvent then return
+ if not event.action == "opened" then return
+ var player = event.issue.user.player(game)
+ var re = "nitdoc".to_re
+ re.ignore_case = true
+ if event.issue.title.has(re) then
+ var a = new_achievement(game)
+ player.unlock_achievement(a, event)
+ end
+ end
+end
+
+# Player open an issue about FFI.
+class IssueAboutFFI
+ super PlayerXIssues
+
+ redef var id = "issue_about_ffi"
+ redef var name = "Polyglot what?"
+ redef var desc = "Open an issue with `ffi` in the title."
+ redef var reward = 10
+
+ redef fun react_event(game, event) do
+ if not event isa IssuesEvent then return
+ if not event.action == "opened" then return
+ var player = event.issue.user.player(game)
+ var re = "\\bffi\\b".to_re
+ re.ignore_case = true
+ if event.issue.title.has(re) then
+ var a = new_achievement(game)
+ player.unlock_achievement(a, event)
+ end
+ end
+end
+
+#####################
+### Pull requests
+#####################
+
+# Unlock achievement after X pull requests.
+#
+# Used to factorize behavior.
+abstract class PlayerXPulls
+ super AchievementReactor
+
+ # Number of PR required to unlock the achievement.
+ var threshold: Int is noinit
+
+ redef fun react_event(game, event) do
+ if not event isa PullRequestEvent then return
+ if not event.action == "opened" then return
+ var player = event.pull.user.player(game)
+ if player.stats["pulls"] == threshold then
+ var a = new_achievement(game)
+ player.unlock_achievement(a, event)
+ end
+ end
+end
+
+# Open your first pull request.
+class Player1Pull
+ super PlayerXPulls
+
+ redef var id = "player_1_pull"
+ redef var name = "First PR"
+ redef var desc = "Open your first pull request."
+ redef var reward = 10
+ redef var threshold = 1
+end
+
+# Author 100 pull requests.
+class Player100Pulls
+ super PlayerXPulls
+
+ redef var id = "player_100_pulls"
+ redef var name = "100 pull requests!!!"
+ redef var desc = "Open 100 pull requests in the game."
+ redef var reward = 100
+ redef var threshold = 100
+end
+
+# Author 1000 pull requests.
+class Player1KPulls
+ super PlayerXPulls
+
+ redef var id = "player_1000_pulls"
+ redef var name = "1000 PULL REQUESTS!!!"
+ redef var desc = "Open 1000 pull requests in the game."
+ redef var reward = 1000
+ redef var threshold = 1000
+end
+
+#####################
+### Commits
+#####################
+
+# Unlock achievement after X merged commits.
+#
+# Used to factorize behavior.
+abstract class PlayerXCommits
+ super AchievementReactor
+
+ # Number of PR required to unlock the achievement.
+ var threshold: Int is noinit
+
+ redef fun react_event(game, event) do
+ if not event isa PullRequestEvent then return
+ if not event.action == "closed" then return
+ if not event.pull.merged then return
+ var player = event.pull.user.player(game)
+ if player.stats["commits"] == threshold then
+ var a = new_achievement(game)
+ player.unlock_achievement(a, event)
+ end
+ end
+end
+
+# Author your first commit in the game.
+class Player1Commit
+ super PlayerXCommits
+
+ redef var id = "player_1_commit"
+ redef var name = "First blood"
+ redef var desc = "Author your first commit in the game."
+ redef var reward = 10
+ redef var threshold = 1
+end
+
+# Author 100 commits.
+class Player100Commits
+ super PlayerXCommits
+
+ redef var id = "player_100_commits"
+ redef var name = "100 commits"
+ redef var desc = "Author 100 commits in the game."
+ redef var reward = 100
+ redef var threshold = 100
+end
+
+# Author 1 000 commits.
+class Player1KCommits
+ super PlayerXCommits
+
+ redef var id = "player_1000_commits"
+ redef var name = "1000 commits!!!"
+ redef var desc = "Author 1000 commits in the game."
+ redef var reward = 1000
+ redef var threshold = 1000
+end
+
+# Author 10 000 commits.
+class Player10KCommits
+ super PlayerXCommits
+
+ redef var id = "player_10000_commits"
+ redef var name = "10000 COMMITS!!!"
+ redef var desc = "Author 10000 commits in the game."
+ redef var reward = 10000
+ redef var threshold = 10000
+end
redef class User
# The player linked to `self`.
- fun player(game: Game): Player is lazy do
- var player = game.load_player(login)
+ fun player(game: Game): Player do
+ var player = player_cache.get_or_null(game)
+ if player != null then return player
+ player = game.load_player(login)
if player == null then player = game.add_player(self)
+ player_cache[game] = player
return player
end
+
+ private var player_cache = new HashMap[Game, Player]
end
# A GameReactor reacts to event sent by a `Github::HookListener`.
# This tool is runned to listen to `Github::Event` and update the game.
module listener
-import statistics
import reactors
+import achievements
import github::hooks
# `HookListener` that redirects events to a `Game` instance.
# TODO handle verbosity with opts
game.verbose_lvl = 1
game.message(1, "Received event {event} for {game.repo.full_name}")
- for reactor in reactors do reactor.react_event(game, event)
+ for reactor in reactors do
+ game.message(2, "Apply reactor {reactor} on {event}")
+ reactor.react_event(game, event)
+ end
end
+
+ # Register a reactor for this listener.
+ fun add_reactor(reactors: GameReactor...) do self.reactors.add_all reactors
end
if args.length != 2 then
var api = new GithubAPI(get_github_oauth)
-var listener = new RpgHookListener(api, host, port)
-listener.reactors.add new StatisticsReactor
-listener.reactors.add new PlayerReactor
+var l = new RpgHookListener(api, host, port)
+l.add_reactor(new StatisticsReactor, new PlayerReactor)
+l.add_reactor(new Player1Issue, new Player100Issues, new Player1KIssues)
+l.add_reactor(new Player1Pull, new Player100Pulls, new Player1KPulls)
+l.add_reactor(new Player1Commit, new Player100Commits, new Player1KCommits)
+l.add_reactor(new IssueAboutNitdoc, new IssueAboutFFI)
print "Listening events on {host}:{port}"
-listener.listen
+l.listen
redef class GameEntity
+ # Statistics for this entity.
+ fun stats: GameStats is abstract
+
# Load statistics for this `MEntity` if any.
fun load_statistics: nullable GameStats do
var key = self.key / "statistics"
var json = game.store.load_object(key)
return new GameStats.from_json(game, json)
end
-
- # Save statistics under this `MEntity`.
- fun save_statistics(stats: GameStats) do
- game.store.store_object(key / stats.key, stats.to_json)
- end
end
redef class Game
- # Statistics for this game instance.
- var stats: GameStats is lazy do
+ redef var stats is lazy do
return load_statistics or else new GameStats(game)
end
redef fun save do
super
- save_statistics(stats)
+ stats.save_in(self)
end
redef fun pretty do
redef class Player
- # Statistics for this player.
- var stats: GameStats is lazy do
+ redef var stats is lazy do
return load_statistics or else new GameStats(game)
end
redef fun save do
super
- save_statistics(stats)
+ stats.save_in(self)
end
redef fun pretty do
class StatisticsReactor
super GameReactor
- redef fun react_event(game, e) do
- e.react_stats_event(game)
- end
+ redef fun react_event(game, e) do e.react_stats_event(game)
end
redef class GithubEvent
redef fun render_body do
add "<strong class=\"text-success\">{game.load_players.length}</strong>"
- add " <a href=\"{game.url}/players\">players</a><br><br>"
+ add " <a href=\"{game.url}/players\">players</a><br>"
+ add "<strong class=\"text-success\">{game.stats["achievements"]}</strong>"
+ add " <a href=\"{game.url}/achievements\">achievements</a><br><br>"
add "<strong class=\"text-success\">{game.stats["pulls"]}</strong> pull requests"
add " (<strong>{game.stats["pulls_open"]}</strong> open)<br>"
add "<strong class=\"text-success\">{game.stats["issues"]}</strong> issues"
add "<p class=\"lead\">ranked "
add " <span class=\"text-success\"># {ranking[player.name]}</span></p>"
add "<strong class=\"text-success\">{player.nitcoins}</strong> nitcoins<br><br>"
+ add "<strong class=\"text-success\">{player.stats["achievements"]}</strong> achievements<br><br>"
add "<strong>{player.stats["pulls"]}</strong> pull requests<br>"
add "<strong>{player.stats["issues"]}</strong> issues<br>"
add "<strong>{player.stats["commits"]}</strong> commits"
add "</div>"
end
end
+
+# Achievement unlocked list panel.
+class AchievementsListPanel
+ super Panel
+
+ # Entity to load the events from.
+ var entity: GameEntity
+
+ redef fun render_title do
+ add "<span class=\"glyphicon glyphicon-list\"></span> "
+ add "Achievements unlocked"
+ end
+
+ redef fun render_body do
+ var achs = entity.load_achievements.values.to_a
+ if achs.is_empty then
+ add "<em>No achievement yet...</em>"
+ return
+ end
+ for ach in achs do add ach.list_item
+ end
+end
+
+# Achievement detail panel.
+class AchievementPanel
+ super Panel
+
+ # Achievement to display.
+ var achievement: Achievement
+
+ redef fun render_title do
+ add "<span class=\"glyphicon glyphicon-check\"></span> "
+ add "Achievement details"
+ end
+
+ redef fun render_body do
+ add """<p class=\"lead\">
+ <span class="badge progress-bar-success"
+ style="vertical-align: middle">+{{{achievement.reward}}}</span>
+ {{{achievement.name}}}
+ </p>
+ <p><strong>{{{achievement.desc}}}</strong></p>"""
+
+ var events = achievement.load_events
+
+ if events.is_empty then
+ add "<em>Never unlocked...</em>"
+ return
+ end
+
+ var event = events.last
+ var tpl = event.tpl_event
+ var player = tpl.player
+ add "<hr>"
+ add """<div class="media">
+ <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}}}">
+ </a>
+ <div class="media-body">
+ <h4 class="media-heading">Unlocked first by {{{player.link}}}</h4>
+ <span class="text-muted">at {{{event.time}}} </span>
+ </div>
+ </div>"""
+
+ if events.length > 1 then
+ add """<p><br>Also unlocked by <strong class="text-success">
+ {{{events.length}}} players</strong>.</p>"""
+ end
+ end
+end
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
- <a class="navbar-brand" href="/">Github RPG</a>"""
+ <a class="navbar-brand" href="{{{root_url}}}/">Github RPG</a>"""
if not breadcrumbs == null then
add breadcrumbs.as(not null)
end
# Base HTML rendering templates for `nitpg`.
module templates_base
-import statistics
+import achievements
redef class GameEntity
# Return a HTML link to this Issue.
fun link: String do return "<a href=\"{html_url}\">#{number}</a>"
end
+
+redef class Achievement
+ # Return a HTML link to this Issue.
+ fun link: String do return "<a href=\"{url}\">{name}</a>"
+
+ fun list_item: String do
+ return """<div class="media">
+ <div class="media-left" style="width: 50px">
+ <span class="glyphicon glyphicon-check"></span>
+ <span class="badge progress-bar-success"
+ style="position: absolute; margin-top: 10px;
+ margin-left: -5px;">+{{{reward}}}</span>
+ </div>
+ <div class="media-body">
+ <h4 class="media-heading">{{{link}}}</h4>
+ <span class="text-muted">{{{desc}}}</span>
+ </div>
+ </div>"""
+
+ end
+end
# Templates to display `GameEvent` kinds.
module templates_events
-import events
+import achievements
import templates_base
redef class GameEvent
return new TplPullMerged(self)
else if kind == "pull_review" then
return new TplPullReview(self)
+ else if kind == "achievement_unlocked" then
+ return new TplAchievementUnlocked(self)
end
abort
end
return new IssueCommentEvent.from_json(event.game.api, obj)
end
+ # Load `achievement` data key as an Achievement.
+ var achievement: Achievement is lazy do
+ return player.load_achievement(event.data["achievement"].to_s).as(not null)
+ end
+
# Display a media item for a reward event.
fun media_item: String do
return """<div class="media">
return "{player.link} reviewed {issue.link}"
end
end
+
+# Event: achievement_unlocked
+class TplAchievementUnlocked
+ super TplEvent
+
+ redef var title is lazy do
+ return "{player.link} unlocked {achievement.link}"
+ end
+end
page.side_panels.add new ShortListPlayersPanel(game)
page.flow_panels.add new PodiumPanel(game)
page.flow_panels.add new EventListPanel(game, list_limit, list_from)
+ page.flow_panels.add new AchievementsListPanel(game)
rsp.body = page.write_to_string
return rsp
end
page.side_panels.clear
page.side_panels.add new PlayerStatusPanel(game, player)
page.flow_panels.add new PlayerReviewsPanel(game, player)
+ page.flow_panels.add new AchievementsListPanel(player)
page.flow_panels.add new EventListPanel(player, list_limit, list_from)
rsp.body = page.write_to_string
return rsp
end
end
+# Display the list of achievements unlocked for this game.
+class ListAchievements
+ super GameAction
+
+ redef fun answer(request, url) do
+ var rsp = prepare_response(request, url)
+ page.breadcrumbs.add_link(game.url / "achievements", "achievements")
+ page.flow_panels.add new AchievementsListPanel(game)
+ rsp.body = page.write_to_string
+ return rsp
+ end
+end
+
+# Player details page.
+class AchievementHome
+ super GameAction
+
+ redef fun answer(request, url) do
+ var rsp = prepare_response(request, url)
+ var name = request.param("achievement")
+ if name == null then
+ var msg = "Bad request: should look like /:owner/:repo/achievements/:achievement."
+ return bad_request(msg)
+ end
+ var achievement = game.load_achievement(name)
+ if achievement == null then
+ return bad_request("Request Error: unknown achievement {name}.")
+ end
+ page.breadcrumbs.add_link(game.url / "achievements", "achievements")
+ page.breadcrumbs.add_link(achievement.url, achievement.name)
+ page.flow_panels.add new AchievementPanel(achievement)
+ page.flow_panels.add new EventListPanel(achievement, list_limit, list_from)
+ rsp.body = page.write_to_string
+ return rsp
+ end
+end
+
if args.length != 3 then
print "Error: missing argument"
print ""
vh.routes.add new Route("/styles/", new FileServer("www/styles"))
vh.routes.add new Route("/games/:owner/:repo/players/:player", new PlayerHome(root))
vh.routes.add new Route("/games/:owner/:repo/players", new ListPlayers(root))
+vh.routes.add new Route("/games/:owner/:repo/achievements/:achievement", new AchievementHome(root))
+vh.routes.add new Route("/games/:owner/:repo/achievements", new ListAchievements(root))
vh.routes.add new Route("/games/:owner/:repo", new RepoHome(root))
var fac = new HttpFactory.and_libevent
redef class App
# Resource Manager used to manage resources placed in the `res` folder of the app
- fun resource_manager: ResourcesManager is cached do return new ResourcesManager(self.resources, self.package_name.to_s)
+ var resource_manager: ResourcesManager is lazy do return new ResourcesManager(self.resources, self.package_name.to_s)
# Assets Manager used to manage resources placed in the `assets` folder of the app
- fun asset_manager: AssetManager is cached do return new AssetManager(self)
+ var asset_manager: AssetManager is lazy do return new AssetManager(self)
# Get the native AssetsManager of the application, used to initialize the nit's AssetManager
private fun assets: NativeAssetManager import native_activity in "Java" `{ return App_native_activity(recv).getAssets(); `}
# Returns the default MediaPlayer of the application.
# When you load a music, it goes in this MediaPlayer.
# Use it for advanced sound management
- fun default_mediaplayer: MediaPlayer is cached do return new MediaPlayer
+ var default_mediaplayer: MediaPlayer is lazy do return new MediaPlayer
# Returns the default MediaPlayer of the application.
# When you load a short sound (not a music), it's added to this soundpool.
# Use it for advanced sound management.
- fun default_soundpool: SoundPool is cached do return new SoundPool
+ var default_soundpool: SoundPool is lazy do return new SoundPool
# Get the native audio manager
fun audio_manager: NativeAudioManager import native_activity in "Java" `{
end
redef class App
- fun shared_preferences: SharedPreferences is cached do
+ var shared_preferences: SharedPreferences is lazy do
return new SharedPreferences.privately(self, "")
end
end
redef class App
# Get the handle to this device vibrator as a global ref
- fun vibrator: Vibrator is cached do
+ var vibrator: Vibrator is lazy do
var v = vibrator_native(native_activity)
return v.new_global_ref
end
# GL capability: blend the computed fragment color values
#
# Foreign: GL_BLEND
- fun blend: GLCap is lazy do return new GLCap(0x0BE2)
+ var blend: GLCap is lazy do return new GLCap(0x0BE2)
# GL capability: cull polygons based of their winding in window coordinates
#
# Foreign: GL_CULL_FACE
- fun cull_face: GLCap is lazy do return new GLCap(0x0B44)
+ var cull_face: GLCap is lazy do return new GLCap(0x0B44)
# GL capability: do depth comparisons and update the depth buffer
#
# Foreign: GL_DEPTH_TEST
- fun depth_test: GLCap is lazy do return new GLCap(0x0B71)
+ var depth_test: GLCap is lazy do return new GLCap(0x0B71)
# GL capability: dither color components or indices before they are written to the color buffer
#
# Foreign: GL_DITHER
- fun dither: GLCap is lazy do return new GLCap(0x0BE2)
+ var dither: GLCap is lazy do return new GLCap(0x0BE2)
# GL capability: add an offset to depth values of a polygon fragment before depth test
#
# Foreign: GL_POLYGON_OFFSET_FILL
- fun polygon_offset_fill: GLCap is lazy do return new GLCap(0x8037)
+ var polygon_offset_fill: GLCap is lazy do return new GLCap(0x8037)
# GL capability: compute a temporary coverage value where each bit is determined by the alpha value at the corresponding location
#
# Foreign: GL_SAMPLE_ALPHA_TO_COVERAGE
- fun sample_alpha_to_coverage: GLCap is lazy do return new GLCap(0x809E)
+ var sample_alpha_to_coverage: GLCap is lazy do return new GLCap(0x809E)
# GL capability: AND the fragment coverage with the temporary coverage value
#
# Foreign: GL_SAMPLE_COVERAGE
- fun sample_coverage: GLCap is lazy do return new GLCap(0x80A0)
+ var sample_coverage: GLCap is lazy do return new GLCap(0x80A0)
# GL capability: discard fragments that are outside the scissor rectangle
#
# Foreign: GL_SCISSOR_TEST
- fun scissor_test: GLCap is lazy do return new GLCap(0x0C11)
+ var scissor_test: GLCap is lazy do return new GLCap(0x0C11)
# GL capability: do stencil testing and update the stencil buffer
#
# Foreign: GL_STENCIL_TEST
- fun stencil_test: GLCap is lazy do return new GLCap(0x0B90)
+ var stencil_test: GLCap is lazy do return new GLCap(0x0B90)
end
# Float related data types of OpenGL ES 2.0 shaders
# Name of the column
#
# require: `self.statement.is_open`
- fun name: String is cached do
+ var name: String is lazy do
assert statement_closed: statement.is_open
return statement.native_statement.column_name(index)
super Rope
super String
- redef fun chars is cached do return new RopeChars(self)
+ redef var chars is lazy do return new RopeChars(self)
end
# Node that represents a concatenation between two `String`
redef fun empty do return ""
- redef fun to_cstring is cached do
+ redef var to_cstring is lazy do
var len = length
var ns = new NativeString(len + 1)
ns[len] = '\0'
super Rope
super Buffer
- redef fun chars: Sequence[Char] is cached do return new RopeBufferChars(self)
+ redef var chars: Sequence[Char] is lazy do return new RopeBufferChars(self)
# The final string being built on the fly
private var str: String is noinit
+++ /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.
-
-# Implementation of the method-related annotation `cached`
-#
-# The cached annotation is deprecated, use the `lazy` annotation instead.
-module cached
-
-import modelize
-private import parser_util
-import simple_misc_analysis
-private import annotation
-intrude import modelize::modelize_property
-
-redef class ToolContext
- # Process the `cached` annotation on methods
- var cached_phase: Phase = new CachedPhase(self, [modelize_property_phase])
-end
-
-private class CachedPhase
- super Phase
-
- init
- do
- # FIXME The phase has to be executed just after `modelize_property_phase`
- # But there is no simple way to express this
- # So, for the moment, I just looked at the linearization and see what phase is after `modelize_property_phase`
- # And inserted before it
- toolcontext.phases.add_edge(toolcontext.simple_misc_analysis_phase, self)
- end
-
- redef fun process_annotated_node(npropdef, nat)
- do
- # Skip if we are not interested
- if nat.name != "cached" then return
-
- # Do some validity checks and print errors if the annotation is used incorrectly
- var modelbuilder = toolcontext.modelbuilder
-
- if not npropdef isa AMethPropdef then
- modelbuilder.error(npropdef, "Syntax error: only a function can be cached.")
- return
- end
-
- var mpropdef = npropdef.mpropdef.as(not null)
-
- var mtype = mpropdef.msignature.return_mtype
- if mtype == null then
- modelbuilder.error(npropdef, "Syntax error: only a function can be cached.")
- return
- end
-
- if not npropdef.n_signature.n_params.is_empty then
- modelbuilder.error(npropdef, "Syntax error: only a function without arguments can be cached.")
- return
- end
-
- # OK, let we do some meta-programming...
-
- var location = npropdef.location
- var name = mpropdef.mproperty.name
- var nclassdef = npropdef.parent.as(AClassdef)
- var mclassdef = nclassdef.mclassdef.as(not null)
-
- if not mclassdef.mclass.kind.need_init then
- modelbuilder.error(npropdef, "Error: only abstract and concrete classes can have cached functions.")
- return
- end
-
- # Create a new private attribute to store the cache
- var cache_mpropdef = new MAttributeDef(mclassdef, new MAttribute(mclassdef, "@{name}<cache>", private_visibility), location)
- cache_mpropdef.static_mtype = mtype.as_nullable
-
- # Create another new private attribute to store the boolean «is the function cached?»
- # The point is to manage the case where `null` is a genuine return value of the method
- var is_cached_mpropdef = new MAttributeDef(mclassdef, new MAttribute(mclassdef, "@{name}<is_cached>", private_visibility), location)
- is_cached_mpropdef.static_mtype = mclassdef.mmodule.get_primitive_class("Bool").mclass_type
- # FIXME? Because there is a default value ("false") a real propdef is required
- var is_cached_npropdef = toolcontext.parse_propdef("var is_cached = false").as(AAttrPropdef)
- associate_propdef(is_cached_mpropdef, is_cached_npropdef)
-
- # Create a new private method to do the real work
- var real_mpropdef = new MMethodDef(mclassdef, new MMethod(mclassdef, "{name}<real>", private_visibility), location)
- real_mpropdef.msignature = mpropdef.msignature
- # FIXME: Again, if the engine require a real propdef even if it is empty
- var real_npropdef = toolcontext.parse_propdef("fun real do end").as(AMethPropdef)
- associate_propdef(real_mpropdef, real_npropdef)
- # Note: the body is set at the last line of this function
-
- # Save the original body
- var real_body = npropdef.n_block.as(not null)
-
- # Replace the original body with a new body that do the proxy'n'cache work
- var proxy_body = toolcontext.parse_stmts("if self._is_cached then return self._cache.as(not null)\nvar res = call_real\nself._cache_write = res\nself._is_cached_write = true\nreturn res")
- real_body.replace_with(proxy_body)
-
- # Do some transformation on the identifiers used on the proxy body so that correct entities are designated
- # FIXME: we just trick the following phases into associating by name some tokens with some model-entities
- # But this is bad at at least two levels
- # - we already know the real model-entities, so why doing latter the association and not now?
- # - associating by names may cause a useless fragility (name-conflicts, etc.)
- proxy_body.collect_tokens_by_text("_is_cached").first.text = is_cached_mpropdef.mproperty.name
- proxy_body.collect_tokens_by_text("_is_cached_write").first.text = is_cached_mpropdef.mproperty.name
- proxy_body.collect_tokens_by_text("_cache").first.text = cache_mpropdef.mproperty.name
- proxy_body.collect_tokens_by_text("_cache_write").first.text = cache_mpropdef.mproperty.name
- proxy_body.collect_tokens_by_text("call_real").first.text = real_mpropdef.mproperty.name
-
- # FIXME a last transformation cannot be done yet. So, the call to `super` (`ASuperExpr`) is broken in cached methods.
-
- # Give the original body to the private real methoddef
- real_npropdef.n_block.replace_with(real_body)
- end
-
- # Detach `n` from its original AST and attach it to `m` (and its related AST)
- # `n` must not be already attached to an existing model entity
- # `m` must not be already attached to an existing AST node
- fun associate_propdef(m: MPropDef, n: APropdef)
- do
- # FIXME: the model-AST relations **must** be rationalized:
- # * 1- fragility: the risk of inconsistencies is too hight
- # * 2- complexity: there is too much paths to access the same things
-
- # Easy attach
- assert n.mpropdef == null
- n.mpropdef = m
-
- # Required to so that look-for implementation works
- assert not toolcontext.modelbuilder.mpropdef2npropdef.has_key(m)
- toolcontext.modelbuilder.mpropdef2npropdef[m] = n
-
- var mclassdef = m.mclassdef
- var nclassdef = toolcontext.modelbuilder.mclassdef2nclassdef[mclassdef]
- # Sanity checks
- assert nclassdef.mclassdef == mclassdef
-
- if n isa AAttrPropdef then
- n.has_value = n.n_expr != null or n.n_block != null
- end
-
- # Required so that propdef are visited in visitors
- if not nclassdef.n_propdefs.has(n) then nclassdef.n_propdefs.add(n)
- end
-end
writable
autoinit
noautoinit
-cached
nosuper
old_style_init
abstract
import modelize
import semantize
import div_by_zero
-import cached
import serialization_phase
import check_annotation
import glsl_validation
end
redef class MProject
- redef fun concern_rank is cached do
+ redef var concern_rank is lazy do
var max = 0
for mgroup in mgroups do
var mmax = mgroup.concern_rank
return res
end
- redef fun concern_rank is cached do
+ redef var concern_rank is lazy do
var max = 0
for mmodule in collect_mmodules do
var mmax = mmodule.concern_rank
return mclasses
end
- redef fun concern_rank is cached do
+ redef var concern_rank is lazy do
var max = 0
for p in in_importation.direct_greaters do
var pmax = p.concern_rank
mpropdef.is_abstract = self.get_single_annotation("abstract", modelbuilder) != null
mpropdef.is_intern = self.get_single_annotation("intern", modelbuilder) != null
mpropdef.is_extern = self.n_extern_code_block != null or self.get_single_annotation("extern", modelbuilder) != null
+
+ # Check annotations
+ var at = self.get_single_annotation("lazy", modelbuilder)
+ if at != null then modelbuilder.error(at, "Syntax error: `lazy` must be used on attributes.")
end
redef fun check_signature(modelbuilder)
var toolcontext = new ToolContext
-# Disable `cached` because it causes issues when printing transformed AST. FIXME
-toolcontext.cached_phase.disabled = true
-
# Try to colorize, even if programs are non valid
toolcontext.keep_going = true
redef class MModule
# Get the type of the class `Serializable`
- fun serializable_type: MClassType is cached do
+ var serializable_type: MClassType is lazy do
return self.get_primitive_class("Serializable").mclass_type
end
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.
-
-import kernel
-
-class Base
- var foo: Int = 10
- fun -: Int do return foo + 20
- fun bar: Int do return -self + 40
- #alt1#fun fail is cached do end
- #alt2#fun fail(i: Int): Int is cached do return i
-end
-
-class CMinus
- super Base
-
- redef fun - is cached do return foo + 1
-end
-
-class CBar
- super Base
-
- redef fun bar is cached do return -self + 2
-end
-
-#alt3#fun fail: Int is cached do return 0
-
-fun test(b: Base)
-do
- b.foo.output
- (-b).output
- b.bar.output
- b.foo = 110
- b.foo.output
- (-b).output
- b.bar.output
- '\n'.output
-end
-
-test(new Base)
-test(new CMinus)
-test(new CBar)
return 20
end
#alt1#var a3: Object is lazy
+ #alt2#fun a4: Object is lazy
end
var f = new Foo
+++ /dev/null
-10
-30
-70
-110
-130
-170
-
-10
-11
-51
-110
-11
-51
-
-10
-30
-32
-110
-130
-32
-
+++ /dev/null
-alt/base_at_cached_alt1.nit:21,6--9: Syntax error: only a function can be cached.
+++ /dev/null
-alt/base_at_cached_alt2.nit:22,6--9: Syntax error: only a function without arguments can be cached.
+++ /dev/null
-alt/base_at_cached_alt3.nit:37,5--8: Error: only abstract and concrete classes can have cached functions.
--- /dev/null
+alt/base_attr_lazy_alt2.nit:29,20--23: Syntax error: `lazy` must be used on attributes.