`nitrpg` is broken since a long time. I think https:/api.github.com actually changed twice since it broke. I don't plan on killing it yet but I moved it to its own repository until I worked again on it (or never).
See https://github.com/Morriar/nitrpg.
Signed-off-by: Alexandre Terrasa <alexandre@moz-code.org>
Pull-Request: #2755
+++ /dev/null
-.github_data
-nitrpg_data
-listener
-web
+++ /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.
-
-NITC ?= nitc
-NITLS ?= nitls
-NITUNIT ?= nitunit
-NITDOC ?= nitdoc
-
-.PHONY: all
-all: bin/listener bin/web
-
-bin/listener: $(shell $(NITLS) -M src/listener.nit)
- mkdir -p bin/
- $(NITC) src/listener.nit -o bin/listener
-
-bin/web: $(shell $(NITLS) -M src/web.nit)
- mkdir -p bin/
- $(NITC) src/web.nit -o bin/web
-
-.PHONY: check
-check:
- $(NITUNIT) .
-
-.PHONY: doc
-doc:
- $(NITDOC) . -o doc/
-
-.PHONY: clean
-clean:
- rm -rf bin/
- rm -rf doc/
+++ /dev/null
-# Welcome to NitRPG!
-
-NitRPG is a Role Playing Game that takes place on [GitHub](https://github.com/).
-
-In NitRPG, GitHub users are represented by players that battle on repo for
-nitcoins and glory.
-
-## Features
-
-* Auto-update with GitHub hooks
-* Display repo statistics
-* Display players statsitics
-* Repo actions are rewarded by nitcoins
-* Players can unlock achievements
-
-## How to install
-
-From the `nit` root:
-
-~~~bash
-> cd contrib/nitrpg
-> make
-~~~
-
-### Configuring the GitHub hook
-
-NitRPG needs you to add a new GitHub hook on your repo to keep the game
-`listener` up-to-date automatically.
-
-Hook configuration:
-
-* **Payload URL**: URL and port to the listener (ex: `http://yourdomain.com:8080`)
-* **Content type**: `application/json`
-* **Wich events**: `Send me everything`
-
-Be sure to set the hook as `Active` in the GitHub admin panel.
-
-### Starting the listener
-
-The `listener` program is used to listen to GitHub hooks and update game data.
-It should alwaysd be up if you want your game to be kept up-to-date.
-
-To run the listener:
-
-~~~raw
- ./listener <host> <port>
-~~~
-
-The arguments `host` and `port` must correspond to what you entered in your
-GitHub hook settings.
-
-### Starting the web server
-
-The `web` program act as a [nitcorn](http://nitlanguage.org/doc/stdlib/module_nitcorn__nitcorn.html) webserver that display the game results live.
-
-To run the webserver:
-
-~~~raw
- ./web <host> <port> <root>
-~~~
-
-The arguments `host` and `port` must correspond to what you entered in your
-GitHub hook settings.
-The `root` argument is used to specify the path from the domain url to the
-NitRPG root.
-
-For example, if NitRPG is installed in `yourdomain.com/nitrpg`:
-
-~~~raw
- ./web localhost 3000 "/nitrpg"
-~~~
-
-Leave it empty if NitRPG is installed at the root of the domain:
-
-~~~raw
- ./web localhost 3000 ""
-~~~
-
-The webserver can then be accessed at `http://yourdomain.com:3000/nitrpg/`.
-
-## RoadMap
-
-NitRPG stills under heavy development.
-Incomming features contain (but are not limited to):
-
-* Periodized stats (weekly, monthly, yearly, overall)
-* Display graphs with stats
-* More achievements
-* Shop: exchange Nitcoins against glorifying items
-
-You can suggest new achievements or ideas in the
-[NitRPG RoadMap Issue](https://github.com/nitlang/nit/issues/1161).
+++ /dev/null
-// ==UserScript==
-// @name Github.com - Add RPG tab
-// @namespace nitlanguage/github/rpg
-// @description Adds a "Github RPG" Tab at the end of the tabs.
-// @include https://github.com/*
-// @downloadURL https://github.com/nitlang/nit/raw/master/contrib/nitrpg/nitrpg.user.js
-// @version 2
-// @grant none
-// ==/UserScript==
-
-// The nav bar with tabs
-var nav = document.getElementsByClassName('reponav');
-if (!nav || !nav[0]) return;
-
-// The current repo to link
-var repo = $("meta[name='octolytics-dimension-repository_nwo']").attr("content");
-//repo = "nitlang/nit";
-if (!repo) return;
-
-// The content of the new tab
-var html = '<a href="http://nitlanguage.org/rpg/games/' + repo + '" class="js-selected-navigation-item reponav-item" data-selected-links="nitrpg"><span class="octicon octicon-ruby"></span> Github RPG</a>';
-//html = '<p><span>x</span></p>';
-
-// Inject the new tab
-var div = document.createElement('div');
-div.innerHTML = html;
-nav[0].append(div.firstChild);
+++ /dev/null
-[package]
-name=nitrpg
-tags=devel,web,cli
-maintainer=Alexandre Terrasa <alexandre@moz-code.org>
-license=Apache-2.0
-desc=NitRPG, a Role Playing Game that takes place on GitHub
-[upstream]
-browse=https://github.com/nitlang/nit/tree/master/contrib/nitrpg/
-git=https://github.com/nitlang/nit.git
-git.directory=contrib/nitrpg/
-homepage=http://nitlanguage.org
-issues=https://github.com/nitlang/nit/issues
+++ /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
- stats.inc("achievements")
- achievement.owner = self
- achievement.save
- end
-
- # Is `a` unlocked for this `Player`?
- fun has_achievement(a: Achievement): Bool do return load_achievement(a.id) != null
-
- # 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 req = new JsonObject
- req["id"] = id
- req["game"] = game.key
- req["owner"] = key
- var obj = game.db.collection("achievements").find(req)
- if obj isa JsonObject then
- return new Achievement.from_json(game, obj)
- end
- return null
- 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 req = new JsonObject
- req["game"] = game.key
- req["owner"] = key
- var res = new HashMap[String, Achievement]
- for obj in game.db.collection("achievements").find_all(req) do
- var achievement = new Achievement.from_json(game, obj)
- res[achievement.id] = achievement
- 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 collection_name = "achievements"
-
- redef var game
-
- redef fun key do
- var owner = self.owner
- if owner == null then return id
- return "{owner.key}-{id}"
- end
-
- # 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
-
- # Game entity this achievement is about.
- var owner: nullable GameEntity = null
-
- # Init `self` from a `json` object.
- #
- # Used to load achievements from storage.
- init from_json(game: Game, json: JsonObject) do
- init(game,
- json["id"].as(String),
- json["name"].as(String),
- json["desc"].as(String),
- json["reward"].as(Int))
- end
-
- redef fun to_json_object do
- var json = super
- json["id"] = id
- json["name"] = name
- json["desc"] = desc
- json["reward"] = reward
- json["game"] = game.key
- var owner = self.owner
- if owner != null then json["owner"] = owner.key
- return json
- end
-end
-
-redef class Player
- # 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)
- save
- 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
- 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
-
-#####################
-### Issue Comments
-#####################
-
-# Unlock achievement after X issue comments.
-#
-# Used to factorize behavior.
-abstract class PlayerXComments
- super AchievementReactor
-
- # Number of comments required to unlock the achievement.
- var threshold: Int is noinit
-
- redef fun react_event(game, event) do
- if not event isa IssueCommentEvent then return
- if not event.action == "created" then return
- var player = event.comment.user.player(game)
- if player.stats["comments"] == threshold then
- var a = new_achievement(game)
- player.unlock_achievement(a, event)
- end
- end
-end
-
-# Player author his first comment in issues.
-class Player1Comment
- super PlayerXComments
-
- redef var id = "player_1_comment"
- redef var name = "From lurker to member"
- redef var desc = "Comment on an issue."
- redef var reward = 10
- redef var threshold = 1
-end
-
-# Player author 100 issue comments.
-class Player100Comments
- super PlayerXComments
-
- redef var id = "player_100_comments"
- redef var name = "Chatter"
- redef var desc = "Comment 100 times on issues."
- redef var reward = 100
- redef var threshold = 100
-end
-
-# Player author 1000 issue comments.
-class Player1KComments
- super PlayerXComments
-
- redef var id = "player_1000_comments"
- redef var name = "You sir, talk a lot!"
- redef var desc = "Comment 1000 times on issues."
- redef var reward = 1000
- redef var threshold = 1000
-end
-
-# Ping @privat in a comment.
-class PlayerPingGod
- super AchievementReactor
-
- redef var id = "player_ping_god"
- redef var name = "Ping god"
- redef var desc = "Ping the owner of the repo for the first time."
- redef var reward = 50
-
- redef fun react_event(game, event) do
- if not event isa IssueCommentEvent then return
- var owner = game.repo.owner.login
- if event.comment.body.has("@{owner}".to_re) then
- var player = event.comment.user.player(game)
- var a = new_achievement(game)
- player.unlock_achievement(a, event)
- end
- end
-end
-
-# Give your first +1
-class PlayerFirstReview
- super AchievementReactor
-
- redef var id = "player_first_review"
- redef var name = "First +1"
- redef var desc = "Give a +1 for the first time."
- redef var reward = 10
-
- redef fun react_event(game, event) do
- if not event isa IssueCommentEvent then return
- # FIXME use a more precise way to locate reviews
- if event.comment.is_ack then
- var player = event.comment.user.player(game)
- var a = new_achievement(game)
- player.unlock_achievement(a, event)
- end
- end
-end
-
-# Talk about nitcoin in issue comments.
-class PlayerSaysNitcoin
- super AchievementReactor
-
- redef var id = "player_says_nitcoin"
- redef var name = "Talking about money"
- redef var desc = "Say something about nitcoins in a comment."
- redef var reward = 10
-
- redef fun react_event(game, event) do
- if not event isa IssueCommentEvent then return
- if event.comment.body.has("(n|N)itcoin".to_re) then
- var player = event.comment.user.player(game)
- var a = new_achievement(game)
- player.unlock_achievement(a, event)
- end
- end
-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` game events.
-#
-# In this module we introduce the concept of `GameEvent`.
-# They can be attached to every GameEntities.
-module events
-
-import game
-
-redef class GameEntity
-
- # Register a new game event for this entity.
- fun add_event(event: GameEvent) do
- event.owner = self
- event.save
- 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_events: Array[GameEvent] do
- var req = new JsonObject
- req["game"] = game.key
- req["owner"] = key
- var res = new Array[GameEvent]
- for obj in game.db.collection("events").find_all(req) do
- res.add new GameEvent.from_json(game, obj)
- end
- (new EventTimeComparator).sort(res)
- return res
- 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_event(id: String): nullable GameEvent do
- var req = new JsonObject
- req["game"] = game.key
- req["owner"] = key
- req["internal_id"] = id
- var res = game.db.collection("events").find(req)
- if res != null then return new GameEvent.from_json(game, res)
- return null
- end
-end
-
-# An event that occurs in the `Game`.
-class GameEvent
- super GameEntity
-
- redef var collection_name = "events"
-
- redef var game
-
- # Entity this event belongs to.
- var owner: nullable GameEntity = null
-
- # String used to dissociate events in the display.
- var kind: String
-
- # GameEvents have raw data associated to them.
- #
- # These data are stored in a JsonObject.
- var data: JsonObject is writable
-
- # GameEvent uniq id used for storage.
- var internal_id: String is noinit
-
- redef var key = internal_id is lazy
-
- # Date and time of the event.
- var time: ISODate is noinit, writable
-
- # An event initialized at now `time`.
- init do
- internal_id = "{get_time}{object_id}{100.rand}"
- time = new ISODate
- end
-
- # Init `self` from a `json` object.
- #
- # Used to load events from json storage.
- init from_json(game: Game, json: JsonObject) do
- init(game, json["kind"].as(String), json["data"].as(JsonObject))
- internal_id = json["internal_id"].as(String)
- time = new ISODate.from_string(json["time"].as(String))
- end
-
- redef fun to_json_object do
- var json = new JsonObject
- json["internal_id"] = internal_id.to_s
- json["kind"] = kind
- json["time"] = time.to_s
- json["data"] = data
- json["game"] = game.key
- var owner = self.owner
- if owner != null then json["owner"] = owner.key
- return json
- end
-end
-
-# Compare `GameEvent` to sort them from the most recent to the older.
-class EventTimeComparator
- super Comparator
-
- redef type COMPARED: GameEvent
-
- redef fun compare(a, b) do return b.time <=> a.time
-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.
-
-# Generate Github events from repo data.
-#
-# Mainly used for testing and history importation.
-module events_generator
-
-import github::events
-
-# Github events generator
-#
-# Generates events from repo data.
-class EventsGenerator
-
- # 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(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(repo: Repo, issue: Issue): IssuesEvent do
- return issues_event(repo, "opened", issue)
- end
-
- # Generate a new IssuesEvent with an `closed` action.
- 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(repo: Repo, issue: Issue): IssuesEvent do
- return issues_event(repo, "reopened", issue)
- end
-
- # Generate a new IssuesEvent from a IssueEvent.
- 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(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(repo: Repo, pull: PullRequest): PullRequestEvent do
- return pull_event(repo, "opened", pull)
- end
-
- # Generate a new PullRequestEvent with an `closed` action.
- 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(repo: Repo, pull: PullRequest): PullRequestEvent do
- return pull_event(repo, "reopened", pull)
- end
-
- # 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
+++ /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` game structures.
-#
-# Here we define the main game entities:
-#
-# * `Game` holds all the entities for a game and provides high level services.
-# * `Player` represents a `Github::User` which plays the `Game`.
-#
-# Developpers who wants to extend the game capabilities should look at
-# the `GameReactor` abstraction.
-module game
-
-import mongodb
-import github::events
-
-# An entity within a `Game`.
-#
-# All game entities can be saved in a json format.
-interface GameEntity
- # The game instance containing `self`.
- fun game: Game is abstract
-
- # Collection `self` should be saved in.
- fun collection_name: String is abstract
-
- # Uniq key of this entity within the collection.
- fun key: String is abstract
-
- # Saves `self` in db.
- fun save do game.db.collection(collection_name).save(to_json_object)
-
- # Json representation of `self`.
- fun to_json_object: JsonObject do
- var json = new JsonObject
- json["_id"] = key
- return json
- end
-
- # Pretty print `self` to be displayed in a terminal.
- fun pretty: String is abstract
-end
-
-# Holder for game data and main services.
-#
-# Game is a `GameEntity` so it can be saved.
-class Game
- super GameEntity
-
- redef fun game do return self
-
- # We need a `GithubAPI` client to load Github data.
- var api: GithubAPI
-
- # A game takes place in a `github::Repo`.
- var repo: Repo
-
- # Game name
- var name: String = repo.full_name is lazy
-
- redef var key = name is lazy
-
- # Mongo server url where this game data are stored.
- var mongo_url = "mongodb://mongo:27017" is writable
-
- # Mongo db client.
- var client = new MongoClient(mongo_url) is lazy
-
- # Mongo db name where this game data are stored.
- var db_name = "nitrpg" is writable
-
- # Mongo db instance for this game.
- var db: MongoDb is lazy do return client.database(db_name)
-
- redef var collection_name = "games"
-
- # Init the Game and try to load saved data.
- init from_mongo(api: GithubAPI, repo: Repo) do
- init(api, repo)
- var req = new JsonObject
- req["name"] = repo.full_name
- var res = db.collection("games").find(req)
- if res != null then from_json(res)
- end
-
- # Init `self` from a JsonObject.
- #
- # Used to load entities from saved data.
- fun from_json(json: JsonObject) do end
-
- redef fun to_json_object do
- var json = super
- json["name"] = name
- return json
- end
-
- # Create a player from a Github `User`.
- #
- # Or return the existing one from game data.
- fun add_player(user: User): Player do
- # check if player already exists
- var player = load_player(user.login)
- if player != null then return player
- # create and store new player
- player = new Player(self, user.login)
- player.save
- return player
- end
-
- # Get a Player from his `name` or null if no player was found.
- #
- # Looks for the player save file in game data.
- #
- # Returns `null` if the player cannot be found.
- # In this case, the player can be created with `add_player`.
- fun load_player(name: String): nullable Player do
- var req = new JsonObject
- req["name"] = name
- req["game"] = game.key
- var res = db.collection("players").find(req)
- if res != null then return new Player.from_json(self, res)
- return null
- end
-
- # List known players.
- #
- # This list is reloaded from game data each time its called.
- #
- # To add players see `add_player`.
- fun load_players: MapRead[String, Player] do
- var req = new JsonObject
- req["game"] = game.key
- var res = new HashMap[String, Player]
- for obj in db.collection("players").find_all(req) do
- var player = new Player.from_json(self, obj)
- res[player.name] = player
- end
- return res
- end
-
- # Return a list of player name associated to their rank in the game.
- fun player_ranking: MapRead[String, Int] do
- var arr = load_players.values.to_a
- var res = new HashMap[String, Int]
- (new PlayerCoinComparator).sort(arr)
- var rank = 1
- for player in arr do
- res[player.name] = rank
- rank += 1
- end
- return res
- end
-
- # Erase all saved data for this game.
- fun clear do db.collection(collection_name).remove(to_json_object)
-
- # Verbosity level used fo stdout.
- #
- # * `-1` quiet
- # * `0` error and warnings
- # * `1` info
- # * `2` debug
- var verbose_lvl = 0 is writable
-
- # Display `msg` if `lvl` >= `verbose_lvl`
- fun message(lvl: Int, msg: String) do
- if lvl > verbose_lvl then return
- print msg
- end
-
- redef fun pretty do
- var res = new FlatBuffer
- res.append "-------------------------\n"
- res.append "{repo.full_name}\n"
- res.append "-------------------------\n"
- res.append "# {load_players.length} players \n"
- return res.write_to_string
- end
-end
-
-# Players can battle on nitrpg for nitcoins and glory.
-#
-# A `Player` is linked to a `Github::User`.
-class Player
- super GameEntity
-
- # Stored in collection `players`.
- redef var collection_name = "players"
-
- redef var game
-
- # FIXME contructor should be private
-
- # Player name.
- #
- # This is the unic key for this player.
- # Should be equal to the associated `Github::User::login`.
- #
- # The name is also used to load the user data lazilly from Github API.
- var name: String
-
- redef var key = name is lazy
-
- # Player amount of nitcoins.
- #
- # Nitcoins is the currency used in nitrpg.
- # They can be obtained by performing actions on the `Game::Repo`.
- var nitcoins: Int = 0 is public writable
-
- # `Github::User` linked to this player.
- var user: User is lazy do
- var user = game.api.load_user(name)
- assert user isa User
- return user
- end
-
- # Init `self` from a `json` object.
- #
- # Used to load players from saved data.
- init from_json(game: Game, json: JsonObject) do
- init(game, json["name"].as(String))
- nitcoins = json["nitcoins"].as(Int)
- end
-
- redef fun to_json_object do
- var json = super
- json["game"] = game.key
- json["name"] = name
- json["nitcoins"] = nitcoins
- return json
- end
-
- redef fun pretty do
- var res = new FlatBuffer
- res.append "-- {name} ({nitcoins} $)\n"
- return res.write_to_string
- end
-
- redef fun to_s do return name
-end
-
-redef class User
- # The player linked to `self`.
- 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`.
-#
-# Subclasses of `GameReactor` are implemented to handle all kind of
-# `GithubEvent`.
-# Depending on the received event, the reactor is used to update game data.
-#
-# Reactors are mostly used with a `Github::HookListener` that dispatchs received
-# events from the Github API.
-#
-# Example:
-#
-# ~~~
-# import github::hooks
-#
-# # Reactor that prints received events in console.
-# class PrintReactor
-# super GameReactor
-#
-# redef fun react_event(game, e) do print e
-# end
-#
-# # Hook listener that redirect events to reactors.
-# class RpgHookListener
-# super HookListener
-#
-# redef fun apply_event(event) do
-# var game = new Game(api, event.repo)
-# var reactor = new PrintReactor
-# reactor.react_event(game, event)
-# end
-# end
-# ~~~
-#
-# See module `reactors` and `listener` for more examples.
-interface GameReactor
-
- # Reacts to this `event` and update `game` accordingly.
- #
- # Concrete `GameReactor` implement this method to update game data
- # for each specific GithubEvent.
- fun react_event(game: Game, event: GithubEvent) is abstract
-end
-
-# utils
-
-# Sort games by descending number of players.
-#
-# The first in the list is the game with the more players.
-class GamePlayersComparator
- super Comparator
-
- redef type COMPARED: Game
-
- redef fun compare(a, b) do
- return b.load_players.length <=> a.load_players.length
- end
-end
-
-# Sort players by descending number of nitcoins.
-#
-# The first in the list is the player with the more of nitcoins.
-class PlayerCoinComparator
- super Comparator
-
- redef type COMPARED: Player
-
- redef fun compare(a, b) do return b.nitcoins <=> a.nitcoins
-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.
-
-# This tool is runned to listen to `Github::Event` and update the game.
-module listener
-
-import reactors
-import achievements
-import github::hooks
-
-# `HookListener` that redirects events to a `Game` instance.
-class RpgHookListener
- super HookListener
-
- # Registered reactors list.
- var reactors = new Array[GameReactor]
-
- # Dispatch event to registered `reactors`.
- redef fun apply_event(event) do
- var game = new Game(api, event.repo)
- # 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
- 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
- print "Error: missing argument"
- print ""
- print "Usage:"
- print "listener <host> <port>"
- exit 1
-end
-
-var host = args[0]
-var port = args[1].to_i
-
-var api = new GithubAPI(get_github_oauth)
-
-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)
-l.add_reactor(new Player1Comment, new Player100Comments, new Player1KComments)
-l.add_reactor(new PlayerPingGod, new PlayerFirstReview, new PlayerSaysNitcoin)
-
-print "Listening events on {host}:{port}"
-l.listen
+++ /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.
-
-# Various implementations of `GameReactor` can be found here.
-module reactors
-
-import events
-
-# Reacts to event that can affect players (like giving nitcoins).
-class PlayerReactor
- super GameReactor
-
- # Nitcoins rewarded when the player opens a new pull request.
- var nc_pull_open = 10
-
- # Nitcoins rewarded when the player reviews a pull request.
- var nc_pull_review = 2
-
- # Nitcoins rewarded when the player has a commit merged.
- var nc_commit_merged = 1
-
- redef fun react_event(game, e) do e.react_player_event(self, game)
-end
-
-redef class GithubEvent
- # Reacts to a player related event.
- #
- # Called by `PlayerReactor::react_event`.
- # No-op by default.
- private fun react_player_event(reactor: PlayerReactor, game: Game) do end
-
- # Generates a GameEvent preinitialized for a reward event.
- private fun player_reward_event(kind: String, player: Player, reward: Int): GameEvent do
- var obj = new JsonObject
- obj["player"] = player.name
- obj["reward"] = reward
- obj["github_event"] = self
- var event = new GameEvent(player.game, kind, obj)
- player.game.add_event(event)
- return event
- end
-end
-
-redef class PullRequestEvent
-
- # Rewards player for opened pull requests.
- redef fun react_player_event(r, game) do
- if action == "opened" or action == "reopened" then
- react_pull_open(r, game)
- else if action == "closed" then
- react_pull_close(r, game)
- end
- end
-
- private fun react_pull_open(r: PlayerReactor, game: Game) do
- var player = pull.user.player(game)
- player.nitcoins += r.nc_pull_open
- player.save
- var event = player_reward_event("pull_open", player, r.nc_pull_open)
- player.add_event(event)
- end
-
- private fun react_pull_close(r: PlayerReactor, game: Game) do
- var player = pull.user.player(game)
- var reward
- var event
- if pull.merged then
- reward = pull.commits * r.nc_commit_merged
- event = player_reward_event("pull_merged", player, reward)
- else
- reward = -r.nc_pull_open
- event = player_reward_event("pull_closed", player, reward)
- end
- player.nitcoins += reward
- player.save
- player.add_event(event)
- end
-end
-
-redef class IssueCommentEvent
-
- # Rewards player for review comments.
- #
- # TODO only give nitcoins if reviewers < 2
- # TODO give more points to first reviewer
- redef fun react_player_event(r, game) do
- if comment.is_ack then
- react_player_review(r, game)
- end
- end
-
- # TODO same player should not be authorized to review multiple times? How to handle rerols?
- private fun react_player_review(r: PlayerReactor, game: Game) do
- if issue.state == "closed" then return
- var player = comment.user.player(game)
- if issue.user == player.user then return
- player.nitcoins += r.nc_pull_review
- player.save
- var event = player_reward_event("pull_review", player, r.nc_pull_review)
- player.add_event(event)
- end
-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.
-
-# Statistics about the Game.
-#
-# This module uses `GameReactor` to extract statistics about the game from
-# triggered `Github::Event`.
-module statistics
-
-import game
-import github::hooks
-import counter
-
-redef class GameEntity
-
- # Statistics manager for this entity.
- fun stats: GameStatsManager is abstract
-end
-
-redef class Game
-
- redef var stats is lazy do return new GameStatsManager(game, self)
-
- redef fun pretty do
- var res = new FlatBuffer
- res.append super
- res.append "# stats:\n"
- res.append stats.pretty
- return res.write_to_string
- end
-
- redef fun save do
- super
- stats.save
- end
-end
-
-redef class Player
-
- redef var stats is lazy do return new GameStatsManager(game, self)
-
- redef fun nitcoins do return stats["nitcoins"]
- redef fun nitcoins=(nc) do stats["nitcoins"] = nc
-
- redef fun pretty do
- var res = new FlatBuffer
- res.append super
- res.append "# stats:\n"
- res.append stats.pretty
- return res.write_to_string
- end
-
- redef fun save do
- super
- stats.save
- end
-end
-
-# Store game stats for defined period.
-class GameStatsManager
- super GameEntity
- super Counter[String]
-
- redef var game
-
- # The GameEntity monitored by these statistics.
- var owner: GameEntity
-
- # Current date to extract stats
- private var date = new Tm.gmtime
-
- # Returns the `GameStats` instance for the overall statistics.
- var overall: GameStats = load_stats_for("all") is lazy
-
- # Returns the `GameStats` instance for the current year statistics.
- var yearly: GameStats = load_stats_for(date.strftime("%Y")) is lazy
-
- # Returns the `GameStats` instance for the current month statistics.
- var monthly: GameStats = load_stats_for(date.strftime("%Y-%m")) is lazy
-
- # Returns the `GameStats` instance for the current day statistics.
- var daily: GameStats = load_stats_for(date.strftime("%Y-%m-%d")) is lazy
-
- # Returns the `GameStats` instance for the current week statistics.
- var weekly: GameStats = load_stats_for(date.strftime("%Y-W%U")) is lazy
-
- # Load statistics for a `period` key.
- fun load_stats_for(period: String): GameStats do
- var req = new JsonObject
- req["period"] = period
- req["owner"] = owner.key
- var obj = game.db.collection("statistics").find(req)
- if obj isa JsonObject then
- return new GameStats.from_json(game, period, owner, obj)
- else
- return new GameStats(game, period, owner)
- end
- end
-
- redef fun [](key) do return overall[key]
-
- redef fun []=(key, value) do
- overall[key] = value
- yearly[key] = value
- monthly[key] = value
- daily[key] = value
- weekly[key] = value
- end
-
- redef fun inc(e) do
- overall.inc(e)
- yearly.inc(e)
- monthly.inc(e)
- daily.inc(e)
- weekly.inc(e)
- end
-
- redef fun dec(e) do
- overall.dec(e)
- yearly.dec(e)
- monthly.dec(e)
- daily.dec(e)
- weekly.dec(e)
- end
-
- redef fun save do
- overall.save
- yearly.save
- monthly.save
- daily.save
- weekly.save
- end
-
- redef fun pretty do return overall.pretty
-end
-
-# Game statistics structure that can be saved as a `GameEntity`.
-class GameStats
- super GameEntity
- super Counter[String]
-
- redef var game
-
- redef var collection_name = "statistics"
-
- # The period these stats are about.
- var period: String
-
- # The game entity these stats are about.
- var owner: GameEntity
-
- redef var key = "{owner.key}-{period}" is lazy
-
- # Load `self` from saved data.
- init from_json(game: Game, period: String, owner: GameEntity, json: JsonObject) do
- init(game, period, owner)
- var values = json.get_or_null("values")
- if not values isa JsonObject then return
- for k, v in values do self[k] = v.as(Int)
- end
-
- redef fun to_json_object do
- var obj = super
- obj["period"] = period
- obj["owner"] = owner.key
- var values = new JsonObject
- values.add_all(self)
- obj["values"] = values
- return obj
- end
-
- redef fun pretty do
- var res = new FlatBuffer
- for k, v in self do
- res.append "# {v} {k}\n"
- end
- return res.write_to_string
- end
-end
-
-# `GameReactor` that computes statistics about the game.
-class StatisticsReactor
- super GameReactor
-
- redef fun react_event(game, e) do e.react_stats_event(game)
-end
-
-redef class GithubEvent
- # Reacts to a statistics related event.
- #
- # Called by `StatisticsReactor::react_event`.
- # No-op by default.
- private fun react_stats_event(game: Game) do end
-end
-
-redef class IssuesEvent
-
- # Count opened and closed issues.
- redef fun react_stats_event(game) do
- var player = issue.user.player(game)
- if action == "opened" then
- game.stats.inc("issues")
- game.stats.inc("issues_open")
- game.save
- player.stats.inc("issues")
- player.stats.inc("issues_open")
- player.save
- else if action == "reopened" then
- game.stats.inc("issues_open")
- game.save
- player.stats.inc("issues_open")
- player.save
- else if action == "closed" then
- game.stats.dec("issues_open")
- game.save
- player.stats.dec("issues_open")
- player.save
- end
- end
-end
-
-redef class PullRequestEvent
-
- # Count opened and closed pull requests.
- redef fun react_stats_event(game) do
- var player = pull.user.player(game)
- if action == "opened" then
- game.stats.inc("pulls")
- game.stats.inc("pulls_open")
- game.save
- player.stats.inc("pulls")
- player.stats.inc("pulls_open")
- player.save
- else if action == "reopened" then
- game.stats.inc("pulls_open")
- game.save
- player.stats.inc("pulls_open")
- player.save
- else if action == "closed" then
- game.stats.dec("pulls_open")
- player.stats.dec("pulls_open")
- if pull.merged then
- game.stats["commits"] += pull.commits
- player.stats["commits"] += pull.commits
- end
- game.save
- player.save
- end
- end
-end
-
-redef class IssueCommentEvent
-
- # Count posted comments
- redef fun react_stats_event(game) do
- if action == "created" then
- var player = comment.user.player(game)
- game.stats.inc("comments")
- player.stats.inc("comments")
- # FIXME use a more precise way to locate reviews
- if comment.is_ack then
- game.stats.inc("reviews")
- player.stats.inc("reviews")
- end
- game.save
- player.save
- end
- end
-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.
-
-# Panels templates for `nitpg`.
-module panels
-
-import templates_events
-import markdown
-
-# A panel can be displayed in a html page.
-#
-# This display a Bootstrap panel.
-class Panel
- super Template
-
- redef fun rendering do
- add """<div class="panel panel-default">
- <div class="panel-heading">
- <h3 class="panel-title">"""
- render_title
- add """ </h3>
- </div>
- <div class="panel-body">"""
- render_body
- add """</div>
- </div>"""
- end
-
- # Render the panel title.
- # Betweem `<h4>` tags.
- fun render_title do end
-
- # Render the panel body.
- fun render_body do end
-end
-
-# A panel that contain only a table as body.
-class TablePanel
- super Panel
-
- redef fun rendering do
- add """<div class="panel panel-default">
- <div class="panel-heading">
- <h3 class="panel-title">"""
- render_title
- add """
- </h3>
- </div>"""
- render_body
- add """</div>"""
- end
-end
-
-# Display an error message within a panel.
-class ErrorPanel
- super Panel
-
- redef fun rendering do
- add """
-<div class="panel panel-danger">
- <div class="panel-heading">
- <h3 class="panel-title">"""
- render_title
- add """
- </h3>
- </div>
- <div class="panel-body">"""
- render_body
- add """
- </div>
-</div>
-"""
- end
-
- # The error message to display as panel body.
- var msg: String
-
- redef fun render_title do
- add "<span class=\"glyphicon glyphicon-warning-sign\"></span> "
- add "Error"
- end
-
- redef fun render_body do
- add msg.html_escape
- end
-
-end
-
-# A panel that display a markdown content rendered as HTML.
-class MDPanel
- super Panel
-
- # Markdown text to display.
- var text: String
-
- redef fun rendering do
- add """<div class="panel">
- <div class="panel-body">{{{text.md_to_html}}}</div>
- </div>"""
- end
-end
-
-# Display a list of active game.
-#
-# Used for NitRPG homepage.
-class GamesShortListPanel
- super Panel
-
- # Root url used for links.
- var root_url: String
-
- # List of NitRPG games to display.
- var games: Array[Game]
-
- redef fun render_title do
- add "<span class=\"glyphicon glyphicon-home\"></span> "
- add "<a href=\"{root_url}/games\">Active games</a>"
- end
-
- redef fun render_body do
- if games.is_empty then
- add "<em>No game yet...</em>"
- return
- end
- var sorted = games.to_a
- (new GamePlayersComparator).sort(sorted)
- for game in sorted do
- add "{game.link} ({game.load_players.length} players)<br>"
- end
- end
-end
-
-# A panel that display a list of player in a repo.
-class GamesListPanel
- super GamesShortListPanel
- super TablePanel
-
- redef fun render_title do
- add "<span class=\"glyphicon glyphicon-home\"></span> "
- add "<a href=\"{root_url}/games\">Active games</a>"
- end
-
- redef fun render_body do
- if games.is_empty then
- add "<div class=\"panel-body\">"
- add "<em>No player yet...</em>"
- add "</div>"
- return
- end
- var sorted = games.to_a
- (new GamePlayersComparator).sort(sorted)
- add """<table class="table table-striped table-hover">
- <tr>
- <th>Game</th>
- <th>Players</th>
- <th>Achievements</th>
- </tr>"""
- for game in sorted do
- add "<tr>"
- add " <td>{game.link}</td>"
- add " <td>{game.load_players.length}</td>"
- add " <td>{game.load_achievements.length}</td>"
- add "</tr>"
- end
- add "</table>"
- end
-end
-
-# A panel that display repo statistics.
-class GameStatusPanel
- super Panel
-
- # Repo to display.
- var game: Game
-
- redef fun render_title do
- add "<span class=\"glyphicon glyphicon-home\"></span> "
- add "{game.link}"
- end
-
- redef fun render_body do
- add "<strong class=\"text-success\">{game.load_players.length}</strong>"
- 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 " (<strong>{game.stats["issues_open"]}</strong> open)<br>"
- add "<strong class=\"text-success\">{game.stats["commits"]}</strong> commits"
- end
-end
-
-# Player status panel.
-class PlayerStatusPanel
- super Panel
-
- # Game instance.
- var game: Game
-
- # Target player.
- var player: Player
-
- redef fun render_title do
- add "<a href=\"{player.url}\">"
- add " <img class=\"img-circle\" style=\"width: 30px\""
- add " src=\"{player.user.avatar_url or else "#"}\" alt=\"{player.name}\">"
- add "</a> {player.link}"
- end
-
- redef fun render_body do
- var ranking = game.player_ranking
- # TODO player.rank
- 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"
- end
-end
-
-# A panel that display a list of player in a repo.
-class ShortListPlayersPanel
- super Panel
-
- # Game instance.
- var game: Game
-
- redef fun render_title do
- add "<span class=\"glyphicon glyphicon-user\"></span> "
- add "<a href=\"{game.url}/players\">Players</a>"
- end
-
- redef fun render_body do
- var players = game.load_players.values.to_a
- if players.is_empty then
- add "<em>No player yet...</em>"
- return
- end
- (new PlayerCoinComparator).sort(players)
- for player in players do
- add "{player.nitcoins} - {player.link}<br>"
- end
- end
-end
-
-# A panel that display a list of player in a repo.
-class ListPlayersPanel
- super TablePanel
-
- # Game instance.
- var game: Game
-
- redef fun render_title do
- add "<span class=\"glyphicon glyphicon-user\"></span> "
- add "<a href=\"{game.url}/players\">Players</a>"
- end
-
- redef fun render_body do
- var players = game.load_players.values.to_a
- (new PlayerCoinComparator).sort(players)
- if players.is_empty then
- add "<div class=\"panel-body\">"
- add "<em>No player yet...</em>"
- add "</div>"
- return
- end
- add """<table class="table table-striped table-hover">
- <tr>
- <th>#</th>
- <th>Player</th>
- <th>Nitcoins</th>
- </tr>"""
- var rank = 1
- for player in players do
- add "<tr>"
- add " <td>{rank}</td>"
- add " <td>{player.link}</td>"
- add " <td>{player.nitcoins}</td>"
- add "</tr>"
- rank += 1
- end
- add "</table>"
- end
-end
-
-# A panel that display the podium.
-class PodiumPanel
- super Panel
-
- # Game instance.
- var game: Game
-
- redef fun render_title do
- add "<span class=\"glyphicon glyphicon-stats\"></span> Hall of fame"
- end
-
- redef fun render_body do
- var players = game.load_players.values.to_a
- (new PlayerCoinComparator).sort(players)
- if players.is_empty then
- add "<em>No players yet...</em>"
- return
- end
- add """
- <div class="container-fluid">
- <div id="podium" class="row row-sm-height">"""
- var max = players.first.nitcoins
- var orders = [3, 1, 0, 2, 4]
- for order in orders do
- if order >= players.length then continue
- var player = players[order]
- var size = 0
- if max > 0 then size = player.nitcoins * 300 / max
- add """
- <div class="col-xs-2 col-xs-height col-xs-offset-{{{order}}} col-bottom"
- style="text-align: center;">
- <p>
- <a href="{{{player.url}}}">
- <img class="img-circle" style="width: 80px"
- src="{{{player.user.avatar_url or else "#"}}}" alt="{{{player.name}}}">
- </a>
- </p>
- <p>{{{player.link}}}</p>
- <p>{{{player.nitcoins}}}</p>
- <div class=" progress-bar-warning progress-bar-striped"
- style="height: {{{size}}}px;"></div>
- </div>"""
- end
- add """
- </div>
- </div>"""
- end
-end
-
-# A `Panel` that displays the list of PR to review for a `Player`.
-class PlayerReviewsPanel
- super Panel
-
- # Repo to display.
- var game: Game
-
- # Player to display customized list for.
- var player: Player
-
- redef fun render_title do
- add "<span class=\"glyphicon glyphicon-check\"></span> "
- add "Review pull requests and comment issues to gain nitcoins!"
- end
-
- redef fun render_body do
- var q = "is:open label:need_review sort:updated-asc " +
- "-involves:{player.name}"
-
- var q2 = "is:open label:request_for_comments sort:updated-asc " +
- "-involves:{player.name}"
-
- var issues = new ArraySet[Issue]
- 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
- end
- for issue in issues do
- var user = issue.user
- var uplay = user.player(game)
- add """<div class="media">
- <a class="media-left" href="{{{uplay.url}}}">
- <img class=\"img-circle\" style="width:50px"
- src="{{{user.avatar_url or else "#"}}}" alt="{{{uplay.name}}}">
- </a>
- <div class="media-body">
- <h4 class="media-heading">
- {{{issue.link}}} {{{issue.title}}}
- </h4>
- <span class="text-muted">opened by </span>
- {{{uplay.link}}}
- </div>
- </div>"""
- end
- end
-end
-
-# A `Panel` that displays the work assigned or tagged.
-class PlayerWorkPanel
- super Panel
-
- # Repo to display.
- var game: Game
-
- # Player to display customized list for.
- var player: Player
-
- redef fun render_title do
- add "<span class=\"glyphicon glyphicon-check\"></span> "
- add "Do your tasks to gain nitcoins!"
- end
-
- redef fun render_body do
- var q = "is:open label:need_work sort:updated-asc author:{player.name}"
- var q2 = "is:open sort:updated-asc assignee:{player.name}"
-
- var issues = new ArraySet[Issue]
- 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
- end
- for issue in issues do
- var user = issue.user
- var uplay = user.player(game)
- add """<div class="media">
- <a class="media-left" href="{{{uplay.url}}}">
- <img class=\"img-circle\" style="width:50px"
- src="{{{user.avatar_url or else "#"}}}" alt="{{{uplay.name}}}">
- </a>
- <div class="media-body">
- <h4 class="media-heading">
- {{{issue.link}}} {{{issue.title}}}
- </h4>
- <span class="text-muted">opened by </span>
- {{{uplay.link}}}
- </div>
- </div>"""
- end
- end
-end
-
-# A `Panel` that displays a pagined list of events stored in the `entity`.
-#
-# This way the panel can be used to view events stored under `Game`, `Player`...
-class EventListPanel
- super Panel
-
- # Entity to load the events from.
- var entity: GameEntity
-
- # Number of events to display.
- var limit: Int
-
- # From where to start?
- var from: Int
-
- redef fun render_title do
- add "<span class=\"glyphicon glyphicon-flash\"></span> "
- add "Last events"
- end
-
- redef fun render_body do
- var events = entity.load_events
- if events.is_empty then
- add "<em>No event yet...</em>"
- return
- end
- # check input
- if limit < 0 then limit = 10
- if from < 0 then from = 0
- # display events
- for i in [from .. from + limit] do
- if i >= events.length then break
- add events[i].tpl_event.media_item
- end
- # pagination
- if limit > events.length then return
- add "<hr>"
- add """<div class="btn-group" role="group">"""
- if from > 0 then
- add """<a class="btn btn-default" role="button"
- href="?pfrom={{{from - limit}}}&plimit={{{limit}}}">
- <span class=\"glyphicon glyphicon-chevron-left\"></span></a>"""
- end
- if from + limit < events.length then
- add """
- <a class="btn btn-default" role="button"
- href="?pfrom={{{from + limit}}}&plimit={{{limit}}}">
- <span class=\"glyphicon glyphicon-chevron-right\"></span></a>"""
- end
- 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 or else "#"}}}" 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
+++ /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.
-
-# Templates that compose the `nitrpg` site.
-module templates
-
-import panels
-
-# A page in the nitrp site.
-class NitRpgPage
- super Template
-
- # URL used as prefix for all the links generated in this page.
- var root_url: String
-
- # Breadcrumbs to this page if any.
- var breadcrumbs: nullable Breadcrumbs = null is public writable
-
- # Panels to display in the sidebar.
- var side_panels = new Array[Panel]
-
- # Panels to display in the page main container.
- var flow_panels = new Array[Panel]
-
- redef fun rendering do
- render_header
- render_footer
- end
-
- # Render the header shared by all pages.
- fun render_header do
- add """
-<!DOCTYPE html>
-<html>
- <head>
- <meta charset="UTF-8">
- <title>Github RPG</title>
- <link rel="stylesheet"
- href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
- <link rel="stylesheet" href="{{{root_url}}}/styles/main.css">
- </head>
- <body>
- <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
- <a class="navbar-brand" href="{{{root_url}}}/">Github RPG</a>"""
- if not breadcrumbs == null then
- add breadcrumbs.as(not null)
- end
- add """
- </nav>
- <div class="container-fluid">
- <div class="row">"""
- if not side_panels.is_empty then
- add """<div class="col-xs-3" id="side">"""
- for panel in side_panels do add panel
- add """</div>
- <div class="col-xs-9" id="flow">"""
- else
- add """<div class="col-xs-12" id="flow">"""
- end
- for panel in flow_panels do add panel
- add """ </div>
- </div>
- </div>
-"""
- end
-
- # Render the footer shared by all pages.
- fun render_footer do
- add """
- <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
- <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
- </body>
-</html>
-"""
- end
-end
-
-# A Bootstrap breadcrumbs component.
-class Breadcrumbs
- super Template
-
- # Items to display in this breadcrumb.
- var entries = new Array[String]
-
- redef fun rendering do
- add "<ol class=\"breadcrumb\">"
- for entry in entries do
- add "<li>{entry}</li>"
- end
- add "</ol>"
- end
-
- # Add a link to the breadcrumbs.
- fun add_link(href, name: String) do
- entries.add "<a href=\"{href}\">{name}</a>"
- end
-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.
-
-# Base HTML rendering templates for `nitpg`.
-module templates_base
-
-import achievements
-
-redef class GameEntity
-
- # Path to this entity from root.
- fun path: String do return collection_name / key
-
- # URL to this game entity page.
- fun url: String do return game.url / path
-end
-
-redef class Game
-
- # Root URL ise used as a prefix for `url`.
- #
- # This must be set before any access to `url`.
- var root_url: String is noinit, writable
-
- redef fun url do return "{root_url}/{path}"
-
- # Return a HTML link to this Game.
- fun link: String do return "<a href=\"{url}\">{name}</a>"
-end
-
-redef class Player
- # Return a HTML link to this Player.
- fun link: String do return "<a href=\"{url}\">{name}</a>"
-end
-
-redef class Issue
- # Return a HTML link to this Issue.
- 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">
- <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
+++ /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.
-
-# Templates to display `GameEvent` kinds.
-module templates_events
-
-import achievements
-import templates_base
-
-redef class GameEvent
- # See `TplEvent`
- fun tpl_event: TplEvent do
- if kind == "pull_open" then
- return new TplPullOpened(self)
- else if kind == "pull_merged" then
- 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
-end
-
-# A TplEvent factorizes HTML rendering methods for `GameEvent`.
-class TplEvent
-
- # Event to display.
- var event: GameEvent
-
- # Title to display.
- var title: String is lazy do return "raw event"
-
- # Load Player from event data.
- var player: nullable Player is lazy do
- return event.game.load_player(event.data["player"].to_s)
- end
-
- # Load reward from event data.
- var reward: Int is lazy do return event.data["reward"].as(Int)
-
- # Load `github_event` data key as a PullRequestEvent.
- var pull_event: PullRequestEvent is lazy do
- 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
- return event.game.api.deserialize(event.data["github_event"].as(JsonObject).to_json).as(IssueCommentEvent)
- 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">
- <a class="media-left" href="{{{player.url}}}">
- <span class="badge progress-bar-success"
- style=\"position: absolute\">+{{{reward}}}</span>
- <img class=\"img-circle\" style="width:50px"
- src="{{{player.user.avatar_url or else "#"}}}" alt="{{{player.name}}}">
- </a>
- <div class="media-body">
- <h4 class="media-heading">{{{title}}}</h4>
- <span class="text-muted">at {{{event.time}}}</span>
- </div>
- </div>"""
- end
-end
-
-# Event: pull_open
-class TplPullOpened
- super TplEvent
-
- redef var title is lazy do
- var pull = pull_event.pull
- return "{player.link} pushed {pull.link}"
- end
-end
-
-# Event: pull_merged
-class TplPullMerged
- super TplEvent
-
- redef var title is lazy do
- var pull = pull_event.pull
- return "{player.link} merged <strong>{pull.commits}</strong> commits with {pull.link}"
- end
-end
-
-# Event: pull_review
-class TplPullReview
- super TplEvent
-
- redef var title is lazy do
- var issue = issue_comment_event.issue
- 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
+++ /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.
-
-# Test module for `achievements.nit`
-module test_achievements is test
-
-import test_helper
-import achievements
-
-class TestGame
- super NitrpgTestHelper
- test
-
- fun test_add_achievement is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var a1 = new Achievement(game, "test_id1", "test_name", "test_desc", 15)
- var a2 = new Achievement(game, "test_id2", "test_name", "test_desc", 15)
- game.add_achievement(a1)
- game.add_achievement(a2)
- assert game.load_achievements.length == 2
- end
-
- fun test_load_achievement is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var a1 = new Achievement(game, "test_id1", "test_name", "test_desc", 15)
- var a2 = new Achievement(game, "test_id2", "test_name", "test_desc", 15)
- game.add_achievement(a1)
- assert game.load_achievement(a1.id).id == "test_id1"
- assert game.load_achievement(a2.id) == null
- end
-
- fun test_load_achievements is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var a1 = new Achievement(game, "test_id1", "test_name", "test_desc", 15)
- var a2 = new Achievement(game, "test_id2", "test_name", "test_desc", 15)
- var a3 = new Achievement(game, "test_id3", "test_name", "test_desc", 15)
- game.add_achievement(a1)
- game.add_achievement(a2)
- game.db.collection("achievements").insert(a3.to_json_object)
- var ok = [a1.id, a2.id]
- var res = game.load_achievements
- assert res.length == 2
- for a in res.values do assert ok.has(a.id)
- end
-end
-
-class TestPlayer
- super NitrpgTestHelper
- test
-
- fun test_add_achievement is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var player1 = new Player(game, "Morriar")
- var a1 = new Achievement(game, "test_id1", "test_name", "test_desc", 15)
- var a2 = new Achievement(game, "test_id2", "test_name", "test_desc", 15)
- player1.add_achievement(a1)
- player1.add_achievement(a2)
- assert player1.load_achievements.length == 2
- end
-
- fun test_load_achievement is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var player1 = new Player(game, "Morriar")
- var player2 = new Player(game, "xymus")
- var a1 = new Achievement(game, "test_id1", "test_name", "test_desc", 15)
- var a2 = new Achievement(game, "test_id2", "test_name", "test_desc", 15)
- player1.add_achievement(a1)
- player2.add_achievement(a2)
- assert player1.load_achievement(a1.id).id == "test_id1"
- assert player1.load_achievement(a2.id) == null
- assert player2.load_achievement(a2.id).id == "test_id2"
- assert player2.load_achievement(a1.id) == null
- end
-
- fun test_load_achievements is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var player1 = new Player(game, "Morriar")
- var player2 = new Player(game, "xymus")
- var a1 = new Achievement(game, "test_id1", "test_name", "test_desc", 15)
- var a2 = new Achievement(game, "test_id2", "test_name", "test_desc", 15)
- var a3 = new Achievement(game, "test_id3", "test_name", "test_desc", 15)
- player1.add_achievement(a1)
- player1.add_achievement(a2)
- player2.add_achievement(a3)
- var ok = [a1.id, a2.id]
- var res = player1.load_achievements
- assert res.length == 2
- for a in res.values do assert ok.has(a.id)
- end
-end
-
-class TestAchievement
- super NitrpgTestHelper
- test
-
- fun test_init is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var a = new Achievement(game, "test_id", "test_name", "test_desc", 15)
- assert a.id == "test_id"
- assert a.name == "test_name"
- assert a.desc == "test_desc"
- assert a.reward == 15
- end
-
- fun test_init_from_json is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var json = """{
- "id": "test_id",
- "name": "test_name",
- "desc": "test_desc",
- "reward": 15
- }""".parse_json.as(JsonObject)
- var a = new Achievement.from_json(game, json)
- assert a.id == "test_id"
- assert a.name == "test_name"
- assert a.desc == "test_desc"
- assert a.reward == 15
- end
-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.
-
-# Test module for `events.nit`
-module test_events is test
-
-import test_helper
-import events
-
-class TestGame
- super NitrpgTestHelper
- test
-
- fun test_add_event is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var event1 = new GameEvent(game, "test_kind", new JsonObject)
- var event2 = new GameEvent(game, "test_kind", new JsonObject)
- game.add_event(event1)
- game.add_event(event2)
- assert game.load_events.length == 2
- end
-
- fun test_load_event is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var event1 = new GameEvent(game, "test_kind", new JsonObject)
- var event2 = new GameEvent(game, "test_kind", new JsonObject)
- game.add_event(event1)
- assert game.load_event(event1.internal_id).kind == "test_kind"
- assert game.load_event(event2.internal_id) == null
- end
-
- fun test_load_events is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var event1 = new GameEvent(game, "test_kind", new JsonObject)
- var event2 = new GameEvent(game, "test_kind", new JsonObject)
- var event3 = new GameEvent(game, "test_kind", new JsonObject)
- game.add_event(event1)
- game.add_event(event2)
- game.db.collection("events").insert(event3.to_json_object)
- var ok = [event1.internal_id, event2.internal_id]
- var res = game.load_events
- assert res.length == 2
- for event in res do assert ok.has(event.internal_id)
- end
-end
-
-class TestPlayer
- super NitrpgTestHelper
- test
-
- fun test_add_event is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var player1 = new Player(game, "Morriar")
- var player2 = new Player(game, "xymus")
- var event1 = new GameEvent(game, "test_kind", new JsonObject)
- var event2 = new GameEvent(game, "test_kind", new JsonObject)
- player1.add_event(event1)
- player1.add_event(event2)
- assert player1.load_events.length == 2
- assert player2.load_events.length == 0
- end
-
- fun test_load_event is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var player1 = new Player(game, "Morriar")
- var player2 = new Player(game, "xymus")
- var event1 = new GameEvent(game, "test_kind", new JsonObject)
- var event2 = new GameEvent(game, "test_kind", new JsonObject)
- player1.add_event(event1)
- player2.add_event(event2)
- assert player1.load_event(event1.internal_id).kind == "test_kind"
- assert player1.load_event(event2.internal_id) == null
- assert player2.load_event(event2.internal_id).kind == "test_kind"
- assert player2.load_event(event1.internal_id) == null
- end
-
- fun test_load_events is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var player1 = new Player(game, "Morriar")
- var player2 = new Player(game, "xymus")
- var event1 = new GameEvent(game, "test_kind", new JsonObject)
- var event2 = new GameEvent(game, "test_kind", new JsonObject)
- var event3 = new GameEvent(game, "test_kind", new JsonObject)
- player1.add_event(event1)
- player1.add_event(event2)
- player2.add_event(event3)
- assert player1.load_events.length == 2
- assert player2.load_events.length == 1
- var ok = [event1.internal_id, event2.internal_id]
- for event in player1.load_events do assert ok.has(event.internal_id)
- end
-end
-
-class TestGameEvent
- super NitrpgTestHelper
- test
-
- fun test_init is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var event = new GameEvent(game, "test_kind", new JsonObject)
- assert event.to_json_object["kind"] == "test_kind"
- end
-
- fun test_init_from_json is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var json = """{
- "internal_id": "test_id",
- "kind": "test_kind",
- "time": "2015-02-05T00:00:00Z",
- "data": {"test_field": "test_value"}
- }""".parse_json.as(JsonObject)
- var event = new GameEvent.from_json(game, json)
- assert event.internal_id == "test_id"
- assert event.kind == "test_kind"
- assert event.data.to_json == """{"test_field":"test_value"}"""
- assert event.time.to_s == "2015-02-05T00:00:00Z"
- end
-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.
-
-# Test module for `game.nit`.
-module test_game is test
-
-import test_helper
-
-class TestGame
- super NitrpgTestHelper
- test
-
- fun test_add_player is test do
- var db = gen_test_db
- var game = load_game("privat/nit", db)
- var users = ["Morriar", "xymus"]
- for name in users do
- game.add_player(game.api.load_user(name).as(not null))
- end
- var res = game.load_players.values
- assert res.length == 2
- for player in res do
- assert users.has(player.name)
- end
- end
-
- fun test_load_player is test do
- var db = gen_test_db
- var game = load_game("privat/nit", db)
- var ogame = load_game("Morriar/nit", db)
-
- var player1 = new Player(game, "Morriar")
- var player2 = new Player(ogame, "privat")
- game.db.collection("players").insert(player1.to_json_object)
- ogame.db.collection("players").insert(player2.to_json_object)
-
- assert game.load_player("privat") == null
- assert game.load_player("Morriar").name == "Morriar"
- assert ogame.load_player("privat").name == "privat"
- assert ogame.load_player("Morriar") == null
- end
-
- fun test_load_players is test do
- var db = gen_test_db
- var game = load_game("privat/nit", db)
- var ogame = load_game("Morriar/nit", db)
-
- var player1 = new Player(game, "Morriar")
- var player2 = new Player(ogame, "privat")
- var player3 = new Player(game, "xymus")
- game.db.collection("players").insert(player1.to_json_object)
- ogame.db.collection("players").insert(player2.to_json_object)
- game.db.collection("players").insert(player3.to_json_object)
-
- var players = game.load_players
- var ok = ["Morriar", "xymus"]
- for player in players.values do assert ok.has(player.name)
- end
-end
-
-class TestPlayer
- super NitrpgTestHelper
- test
-
- fun test_init is test do
- var db = gen_test_db
- var game = load_game("privat/nit", db)
- var player = new Player(game, "Morriar")
- assert player.name == "Morriar"
- assert player.user.login == "Morriar"
- assert player.nitcoins == 0
- end
-
- fun test_init_from_json is test do
- var db = gen_test_db
- var game = load_game("privat/nit", db)
- var json = """{"name": "Morriar", "nitcoins": 10}""".parse_json
- var player = new Player.from_json(game, json.as(JsonObject))
- assert player.name == "Morriar"
- assert player.user.login == "Morriar"
- assert player.nitcoins == 10
- end
-
- fun test_save is test do
- var db = gen_test_db
- var game = load_game("privat/nit", db)
- var json = """{"name": "Morriar", "nitcoins": 10}""".parse_json.as(JsonObject)
- var player = new Player.from_json(game, json)
- player.save
- assert game.db.collection("players").find(json) != null
- end
-
- fun test_game_add_player is test do
- var db = gen_test_db
- var game = load_game("privat/nit", db)
- game.add_player(game.api.load_user("Morriar").as(not null))
- var json = """{"name": "Morriar"}""".parse_json.as(JsonObject)
- assert game.db.collection("players").find(json) != null
- end
-
- fun test_game_load_player is test do
- var db = gen_test_db
- var game = load_game("privat/nit", db)
- var json = """{"name": "Morriar", "nitcoins": 10}""".parse_json.as(JsonObject)
- var player = new Player.from_json(game, json)
- player.save
- var oplayer = game.load_player("Morriar")
- assert oplayer != null
- assert player.nitcoins == oplayer.nitcoins
- end
-end
-
-class TestUser
- super NitrpgTestHelper
- test
-
- fun test_player is test do
- var db = gen_test_db
- var api = new GithubAPI(get_github_oauth)
- var game = load_game("privat/nit", db)
- var user = api.load_user("Morriar")
- assert user != null
- var player = user.player(game)
- assert player.name == "Morriar"
- game.clear
- end
-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.
-
-# Test tools for NitRPG.
-module test_helper
-
-import game
-import github::cache
-
-# Used to factorize test treatments.
-abstract class NitrpgTestHelper
-
- # Github API client
- var api: GithubAPI do
- var api = new GithubAPI(get_github_oauth)
- api.enable_cache = true
- return api
- end
-
- # Mongo API client
- var mongo = new MongoClient("mongodb://mongo:27017/")
-
- # Load a new test database by with a name
- private fun load_db(name: String): MongoDb do return mongo.database(name)
-
- # Load a repo by its name.
- fun load_repo(name: String): Repo do
- var repo = api.load_repo(name)
- assert repo != null
- return repo
- end
-
- # Load a game by its name.
- fun load_game(name: String, db: MongoDb): Game do
- var game = new Game(api, load_repo(name))
- game.db_name = db.name
- return game
- end
-
- # Stack of db used for testing.
- var test_dbs = new Array[MongoDb]
-
- # Gen a test db with a random name (to avoid race conditions).
- fun gen_test_db: MongoDb do
- var testid = "NIT_TESTING_ID".environ.to_i
- var db_name = "test_nitrpg_{testid}"
- var db = load_db(db_name)
- test_dbs.add db
- return db
- end
-
- # Should be called after your test.
- fun drop_test_db do
- var db = test_dbs.pop
- db.drop
- end
-
- # Drop the databse after each test
- fun after_test is after do drop_test_db
-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.
-
-# Test module for `listener.nit`
-module test_listener is test
-
-import test_helper
-import reactors
-import achievements
-import events_generator
-
-private class DummyListener
- super NitrpgTestHelper
- test
-
- var reactors = new Array[GameReactor]
-
- fun apply_event(event: GithubEvent, db: MongoDb) do
- var game = load_game(event.repo.full_name, db)
- for reactor in reactors do
- reactor.react_event(game, event)
- end
- end
-
- fun add_reactor(reactors: GameReactor...) do self.reactors.add_all reactors
-end
-
-class TestListener
- super NitrpgTestHelper
- test
-
- var generator = new EventsGenerator(api)
-
- var repo: Repo is lazy do return load_repo("Morriar/nit")
-
- fun test_game_issue_stats is test do
- var db = gen_test_db
- var l = new DummyListener
- l.add_reactor(new StatisticsReactor)
-
- var issue = api.load_issue(repo, 322)
- assert issue != null
-
- 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(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(repo, issue), db)
- game = load_game("Morriar/nit", db)
- assert game.stats.overall["issues"] == 1
- assert game.stats.overall["issues_open"] == 1
- end
-
- fun test_player_issue_stats is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var l = new DummyListener
- l.add_reactor(new StatisticsReactor)
-
- var issue = api.load_issue(repo, 322)
- assert issue != null
-
- 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(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(repo, issue), db)
- player = new Player(game, "Morriar")
- assert player.stats.overall["issues"] == 1
- assert player.stats.overall["issues_open"] == 1
- end
-
- fun test_game_pr_stats is test do
- var db = gen_test_db
- var l = new DummyListener
- l.add_reactor(new StatisticsReactor)
-
- var pr = api.load_pull(repo, 275)
- assert pr != null
-
- 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(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(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(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"] == 2
- end
-
- fun test_game_issue_comment_stats is test do
- var db = gen_test_db
- var l = new DummyListener
- l.add_reactor(new StatisticsReactor)
-
- var issue = api.load_issue(repo, 322)
- assert issue != null
- var comment = api.load_issue_comment(repo, 76119442)
- assert comment != null
-
- comment.body = "foo bar"
- 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(repo, issue, comment), db)
- game = load_game("Morriar/nit", db)
- assert game.stats.overall["comments"] == 2
- assert game.stats.overall["reviews"] == 1
- end
-
- fun test_player_pull_reactor is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var l = new DummyListener
- l.add_reactor(new PlayerReactor)
-
- var pull = api.load_pull(repo, 275)
- assert pull != null
-
- 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(repo, pull), db)
- player = new Player(game, "itch76")
- assert player.stats.overall["nitcoins"] == 0
- 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(repo, pull), db)
- player = new Player(game, "itch76")
- assert player.stats.overall["nitcoins"] == 12
- end
-
- fun test_player_review_reactor is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var l = new DummyListener
- l.add_reactor(new PlayerReactor)
-
- var pull = api.load_pull(repo, 275)
- assert pull != null
- var comment = api.load_issue_comment(repo, 36961230)
- assert comment != null
-
- # TODO handle multiple review by the same user
-
- # no review in opened issue
- pull.state = "open"
- comment.body = "foo bar"
- 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(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(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(repo, pull, comment), db)
- player = new Player(game, "Morriar")
- assert player.stats.overall["nitcoins"] == 4
- end
-
- fun test_X_issues_achievements is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var l = new DummyListener
- l.add_reactor(new StatisticsReactor)
- l.add_reactor(new Player1Issue, new Player100Issues, new Player1KIssues)
-
- var issue = api.load_issue(repo, 322)
- assert issue != null
-
- for i in [0, 99, 999] do
- var id = "player_{i + 1}_issue"
- if i > 0 then id = "{id}s"
- var player = new Player(game, "Morriar")
- player.stats["issues"] = i
- player.save
- l.apply_event(generator.issue_open(repo, issue), db)
- assert player.load_achievements.has_key(id)
- end
- var player = new Player(game, "Morriar")
- assert player.stats.overall["nitcoins"] == 1110
- end
-
- fun test_X_pulls_achievements is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var l = new DummyListener
- l.add_reactor(new StatisticsReactor)
- l.add_reactor(new Player1Pull, new Player100Pulls, new Player1KPulls)
-
- var pull = api.load_pull(repo, 275)
- assert pull != null
-
- for i in [0, 99, 999] do
- var id = "player_{i + 1}_pull"
- if i > 0 then id = "{id}s"
- var player = new Player(game, "itch76")
- player.stats["pulls"] = i
- player.save
- l.apply_event(generator.pull_open(repo, pull), db)
- assert player.load_achievements.has_key(id)
- end
- var player = new Player(game, "itch76")
- assert player.stats.overall["nitcoins"] == 1110
- end
-
- fun test_X_commits_achievements is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var l = new DummyListener
- l.add_reactor(new StatisticsReactor)
- l.add_reactor(new Player1Commit, new Player100Commits)
- l.add_reactor(new Player1KCommits, new Player10KCommits)
-
- var pull = api.load_pull(repo, 275)
- assert pull != null
- pull.state = "closed"
- pull.merged = true
-
- for i in [0, 99, 999, 9999] do
- var id = "player_{i + 1}_commit"
- if i > 0 then id = "{id}s"
- var player = new Player(game, "itch76")
- player.stats["commits"] = i
- player.save
- l.apply_event(generator.pull_close(repo, pull), db)
- assert player.load_achievements.has_key(id)
- end
- var player = new Player(game, "itch76")
- assert player.stats.overall["nitcoins"] == 11110
- end
-
- fun test_X_comments_achievements is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var l = new DummyListener
- l.add_reactor(new StatisticsReactor)
- l.add_reactor(new Player1Comment, new Player100Comments, new Player1KComments)
-
- var pull = api.load_pull(repo, 275)
- assert pull != null
- var comment = api.load_issue_comment(repo, 36961230)
- assert comment != null
-
- for i in [0, 99, 999] do
- var id = "player_{i + 1}_comment"
- if i > 0 then id = "{id}s"
- var player = new Player(game, "Morriar")
- player.stats["comments"] = i
- player.save
- 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 player.stats.overall["nitcoins"] == 1110
- end
-
- fun test_issues_achievements is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var l = new DummyListener
- l.add_reactor(new IssueAboutNitdoc, new IssueAboutFFI)
-
- var issue = api.load_issue(repo, 322)
- assert issue != null
-
- issue.title = "nitdoc ffi"
- 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 player.stats.overall["nitcoins"] == 20
- end
-
- fun test_comments_reactor is test do
- var db = gen_test_db
- var game = load_game("Morriar/nit", db)
- var l = new DummyListener
- l.add_reactor(new PlayerPingGod, new PlayerFirstReview, new PlayerSaysNitcoin)
-
- var pull = api.load_pull(repo, 275)
- assert pull != null
- var comment = api.load_issue_comment(repo, 36961230)
- assert comment != null
-
- comment.body = "@{game.repo.owner.login}"
- 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(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(repo, pull, comment), db)
- player = new Player(game, "Morriar")
- assert player.load_achievements.has_key("player_says_nitcoin")
- assert player.stats.overall["nitcoins"] == 70
- end
-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.
-
-# Test module for `stats.nit`
-module test_statistics is test
-
-import test_helper
-import statistics
-
-class TestGame
- super NitrpgTestHelper
- test
-
- fun test_game_stats is test do
- var db = gen_test_db
- var game = load_game("privat/nit", db)
- var stats = game.stats
- assert stats.overall["test"] == 0
- stats.overall.inc("test")
- assert stats.overall["test"] == 1
- stats.save
- var ogame = load_game("privat/nit", db)
- var ostats = ogame.stats
- ostats.overall.inc("test")
- assert ostats.overall["test"] == 2
- end
-end
-
-class TestPlayer
- super NitrpgTestHelper
- test
-
- fun test_player_stats is test do
- var db = gen_test_db
- var game = load_game("privat/nit", db)
- var player = new Player(game, "Morriar")
- var stats = player.stats
- assert stats.overall["test"] == 0
- stats.overall.inc("test")
- assert stats.overall["test"] == 1
- stats.save
- var oplayer = new Player(game, "Morriar")
- var ostats = oplayer.stats
- ostats.overall.inc("test")
- assert ostats.overall["test"] == 2
- end
-end
-
-class TestGameStats
- super NitrpgTestHelper
- test
-
- fun test_init_from_json is test do
- var db = gen_test_db
- var game = load_game("privat/nit", db)
- var owner = new Player(game, "Morriar")
- var json = """{
- "period": "2015",
- "owner": "Morriar",
- "values": {
- "test1": 10,
- "test2": 20
- }
- }""".parse_json.as(JsonObject)
- var stats = new GameStats.from_json(game, "2015", owner, json)
- assert stats["test0"] == 0
- assert stats["test1"] == 10
- assert stats["test2"] == 20
- end
-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.
-
-# Display `nitrpg` data as a website.
-module web
-
-import nitcorn
-import templates
-
-# A custom action forn `nitrpg`.
-class RpgAction
- super Action
-
- # Root URL is used as a prefix for all URL generated by the actions.
- var root_url: String
-
- # Github oauth token used for GithubAPI.
- var auth: String is lazy do return get_github_oauth
-
- # API client used to import data from Github.
- var api: GithubAPI is lazy do
- var api = new GithubAPI(auth)
- return api
- end
-
- init do
- super
- if auth.is_empty then
- print "Error: Invalid Github oauth token!"
- exit 1
- end
- end
-
- # Return an Error reponse page.
- fun bad_request(msg: String): HttpResponse do
- var rsp = new HttpResponse(400)
- var page = new NitRpgPage(root_url)
- var error = new ErrorPanel(msg)
- page.flow_panels.add error
- rsp.body = page
- return rsp
- end
-
- # Returns the game with `name` or null if no game exists with this name.
- fun load_game(name: String): nullable Game do
- var repo = api.load_repo(name)
- if repo == null then return null
- var game = new Game.from_mongo(api, repo)
- game.root_url = root_url
- return game
- end
-
- # Returns the list of saved games from NitRPG data.
- fun load_games: Array[Game] do
- var res = new Array[Game]
- # TODO should be option
- var mongo = new MongoClient("mongodb://mongo:27017")
- var db = mongo.database("nitrpg")
- for obj in db.collection("games").find_all(new JsonObject) do
- var repo = api.load_repo(obj["name"].to_s)
- assert repo != null
- var game = new Game(api, repo)
- game.from_json(obj)
- game.root_url = root_url
- res.add game
- end
- return res
- end
-end
-
-# Repo overview page.
-class RpgHome
- super RpgAction
-
- # Response page stub.
- var page: NitRpgPage is noinit
-
- redef fun answer(request, url) do
- var readme = load_readme
- var games = load_games
- var response = new HttpResponse(200)
- page = new NitRpgPage(root_url)
- page.side_panels.add new GamesShortListPanel(root_url, games)
- page.flow_panels.add new MDPanel(readme)
- response.body = page
- return response
- end
-
- # Load the string content of the nitrpg readme file.
- private fun load_readme: String do
- var readme = "README.md"
- if not readme.file_exists then
- return "Unable to locate README file."
- end
- var file = new FileReader.open(readme)
- var text = file.read_all
- file.close
- return text
- end
-end
-
-# Display the list of active game.
-class ListGames
- super RpgAction
-
- # Response page stub.
- var page: NitRpgPage is noinit
-
- redef fun answer(request, url) do
- var games = load_games
- var response = new HttpResponse(200)
- page = new NitRpgPage(root_url)
- page.breadcrumbs = new Breadcrumbs
- page.breadcrumbs.add_link(root_url / "games", "games")
- page.flow_panels.add new GamesListPanel(root_url, games)
- response.body = page
- return response
- end
-end
-
-# An action that require a game.
-class GameAction
- super RpgAction
-
- # Response page stub.
- var page: NitRpgPage is noinit
-
- # Target game.
- var game: Game is noinit
-
- redef fun answer(request, url) is abstract
-
- # Check errors and prepare response.
- private fun prepare_response(request: HttpRequest, url: String): HttpResponse do
- var owner = request.param("owner")
- var repo_name = request.param("repo")
- if owner == null or repo_name == null then
- var msg = "Bad request: should look like /games/:owner/:repo."
- return bad_request(msg)
- end
- var game = load_game("{owner}/{repo_name}")
- if game == null then
- var msg = api.last_error.message
- return bad_request("Repo Error: {msg}")
- end
- self.game = game
- var response = new HttpResponse(200)
- page = new NitRpgPage(root_url)
- page.side_panels.add new GameStatusPanel(game)
- page.breadcrumbs = new Breadcrumbs
- page.breadcrumbs.add_link(game.url, game.name)
- prepare_pagination(request)
- return response
- end
-
- # Parse pagination related parameters.
- private fun prepare_pagination(request: HttpRequest) do
- var args = request.get_args
- list_from = args.get_or_default("pfrom", "0").to_i
- list_limit = args.get_or_default("plimit", "10").to_i
- end
-
- # Limit of events to display in lists.
- var list_limit = 10
-
- # From where to start the display of events related lists.
- var list_from = 0
-
- # TODO should also check 201, 203 ...
- private fun is_response_error(response: HttpResponse): Bool do
- return response.status_code != 200
- end
-end
-
-# Repo overview page.
-class RepoHome
- super GameAction
-
- redef fun answer(request, url) do
- var rsp = prepare_response(request, url)
- if is_response_error(rsp) then return rsp
- 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
- return rsp
- end
-end
-
-# Repo players list.
-class ListPlayers
- super GameAction
-
- redef fun answer(request, url) do
- var rsp = prepare_response(request, url)
- if is_response_error(rsp) then return rsp
- page.breadcrumbs.add_link(game.url / "players", "players")
- page.flow_panels.add new ListPlayersPanel(game)
- rsp.body = page
- return rsp
- end
-end
-
-# Player details page.
-class PlayerHome
- super GameAction
-
- redef fun answer(request, url) do
- var rsp = prepare_response(request, url)
- if is_response_error(rsp) then return rsp
- var name = request.param("player")
- if name == null then
- var msg = "Bad request: should look like /:owner/:repo/:players/:name."
- return bad_request(msg)
- end
- var player = game.load_player(name)
- if player == null then
- return bad_request("Request Error: unknown player {name}.")
- end
- page.breadcrumbs.add_link(game.url / "players", "players")
- page.breadcrumbs.add_link(player.url, name)
- 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 PlayerWorkPanel(game, player)
- page.flow_panels.add new AchievementsListPanel(player)
- page.flow_panels.add new EventListPanel(player, list_limit, list_from)
- rsp.body = page
- 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)
- if is_response_error(rsp) then return rsp
- page.breadcrumbs.add_link(game.url / "achievements", "achievements")
- page.flow_panels.add new AchievementsListPanel(game)
- rsp.body = page
- return rsp
- end
-end
-
-# Player details page.
-class AchievementHome
- super GameAction
-
- redef fun answer(request, url) do
- var rsp = prepare_response(request, url)
- if is_response_error(rsp) then return rsp
- 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
- return rsp
- end
-end
-
-if args.length != 3 then
- print "Error: missing argument"
- print ""
- print "Usage:"
- print "web <host> <port> <root_url>"
- exit 1
-end
-
-var host = args[0]
-var port = args[1]
-var root = args[2]
-
-var iface = "{host}:{port}"
-var vh = new VirtualHost(iface)
-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))
-vh.routes.add new Route("/games", new ListGames(root))
-vh.routes.add new Route("/", new RpgHome(root))
-
-var fac = new HttpFactory.and_libevent
-fac.config.virtual_hosts.add vh
-
-print "Launching server on http://{iface}/"
-fac.run
+++ /dev/null
-body { padding-top: 70px; }
-
-.navbar .breadcrumb {
- background-color: transparent;
- margin-bottom: 0;
- margin-top: 0.5em;
-}
-
-/* columns of same height styles */
-.container-xs-height {
- display:table;
- padding-left:0px;
- padding-right:0px;
-}
-.row-xs-height {
- display:table-row;
-}
-.col-xs-height {
- display:table-cell;
- float:none;
-}
-@media (min-width: 768px) {
- .container-sm-height {
- display:table;
- padding-left:0px;
- padding-right:0px;
- }
- .row-sm-height {
- display:table-row;
- }
- .col-sm-height {
- display:table-cell;
- float:none;
- }
-}
-@media (min-width: 992px) {
- .container-md-height {
- display:table;
- padding-left:0px;
- padding-right:0px;
- }
- .row-md-height {
- display:table-row;
- }
- .col-md-height {
- display:table-cell;
- float:none;
- }
-}
-@media (min-width: 1200px) {
- .container-lg-height {
- display:table;
- padding-left:0px;
- padding-right:0px;
- }
- .row-lg-height {
- display:table-row;
- }
- .col-lg-height {
- display:table-cell;
- float:none;
- }
-}
-
-/* vertical alignment styles */
-.col-top {
- vertical-align:top;
-}
-.col-middle {
- vertical-align:middle;
-}
-.col-bottom {
- vertical-align:bottom;
-}