Merge: share/man: Update man for `nitpretty`
authorJean Privat <jean@pryen.org>
Wed, 22 Apr 2015 04:18:41 +0000 (11:18 +0700)
committerJean Privat <jean@pryen.org>
Wed, 22 Apr 2015 04:18:41 +0000 (11:18 +0700)
Fixes #1130

Pull-Request: #1290
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>
Reviewed-by: Jean Privat <jean@pryen.org>

contrib/inkscape_tools/README.md
contrib/inkscape_tools/src/svg_to_png_and_nit.nit
contrib/nitrpg/README.md [new file with mode: 0644]
contrib/nitrpg/src/achievements.nit
contrib/nitrpg/src/game.nit
contrib/nitrpg/src/listener.nit
contrib/nitrpg/src/statistics.nit
contrib/nitrpg/src/templates/panels.nit
contrib/nitrpg/src/web.nit
src/doc/doc_phases/doc_html.nit
src/doc/html_templates/html_model.nit

index 017ec86..c338656 100644 (file)
@@ -1,25 +1,28 @@
 # SVG to PNG and Nit
 
-This tool is used in combination with Inkscape to simplify assets creation for _mnit_ apps. It uses Inkscape to extract a PNG file from a SVG file. It will also create a Nit source file to create _mnit_ images for each objects with an id beginning by 0.
+This tool uses Inkscape to prepare assets for Nit applications from a single SVG file.
+It selects objects to extract from the SVG file when their id begins with `0`.
 
-# Features
+It will produce two files:
 
-* Creates a sinlge PNG file per SVG source file
-* Creates subimages for objects with an id beginning by 0.
-* If the id ends with 0 to 9, will instead create an array of subimages.
+* A Nit source file that declares a single class with an attribute for each selected object.
+  The attribute usually holds a single texture, except if the the id ends with a digit, then it will be an array.
 
-# Usage
+* A single PNG image file that contains all the selected objects.
+  With the option `--pow2`, the image size is rounded to the next of 2.
+
+## Usage
 
 1. Create a new Inkscape document.
-2. Create objects and set their ids to begin with 0
-3. Save the document (ex: to `drawing.svg`) the name of the file is important
+2. Create objects and set their ids to begin with `0`.
+3. Save the document to `drawing.svg` (for this example), the name of the file is used to name the Nit class.
 4. Execute `bin/svg_to_png_and_nit drawing.svg`
-5. From your code, import the generated source file (at  src/drawing.nit`)
+5. From your code, import the generated source file at `src/drawing.nit`.
 6. Use the class `DrawingImages` and its attributes.
 
-# Examples
+## Examples
 
-The minimal test in  tests/app/  shows the basic usage of this tool.
+The minimal test in `tests/app/` shows the basic usage of this tool.
 
 The Dino example `../../../../examples/mnit_dino` also uses this tool and is a more complete and practical example.
 
index b24cbf5..f9e690b 100644 (file)
@@ -1,6 +1,6 @@
 # This file is part of NIT ( http://www.nitlanguage.org ).
 #
-# Copyright 2012-2014 Alexis Laferrière <alexis.laf@xymus.net>
+# Copyright 2012-2015 Alexis Laferrière <alexis.laf@xymus.net>
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# This script extracts pngs from a single svg for all objects with ids
-# beginning by 0. Requires Inkscape.
+# Extract images of objects from an SVG file using Inkscape
 module svg_to_png_and_nit
 
 import opts
 import template
 
+# Image information extracted from the SVG file
 class Image
+       # Name extracted from the object ID minus the `0` prefix and Nit safe
        var name: String
+
+       # Left border
        var x: Int
+
+       # Top border
        var y: Int
+
+       # Image width
        var w: Int
+
+       # Image height
        var h: Int
+
+       # Right border
        fun right: Int do return x+w
+
+       # Bottom border
        fun bottom: Int do return y+h
 
        redef fun to_s do return name
 end
 
-# The Nit source file to retreive all images
-class ImageSetSrc
+# Document being processed, concerns both the source and the target
+class Document
+       # Name of the source file
+       var drawing_name: String
+
+       # Name of the class to generate
+       var nit_class_name: String = drawing_name.capitalized + "Images" is lazy
+
+       # Scaling to apply to the exported image
+       var scale: Float
+
+       # Source minimum X
+       var min_x: Int
+
+       # Source maximum X
+       var max_x: Int
+
+       # Source minimum Y
+       var min_y: Int
+
+       # Source maximum Y
+       var max_y: Int
+
+       # Get the coordinates for `image` as `"x, y, w, h"`
+       fun coordinates(image: Image): String
+       do
+               var x = image.x.adapt(min_x, scale)
+               var y = image.y.adapt(min_y, scale)
+               var w = (image.w.to_f*scale).to_i
+               var h = (image.h.to_f*scale).to_i
+
+               return "{x}, {y}, {w}, {h}"
+       end
+end
+
+# Nit module with a single class to retrieve to access the extracted images
+abstract class ImageSetSrc
        super Template
 
-       var name: String
-       init(name: String) do self.name = name
+       # Target document
+       var document: Document
 
-       var attributes = new Array[String]
-       var load_exprs = new Array[String]
+       # Images found in the source document
+       var images: Array[Image]
+end
+
+# Nit module targeting the MNit framework
+class MnitImageSetSrc
+       super ImageSetSrc
 
        redef fun rendering
        do
+               # Known array of images
+               var arrays_of_images = new Array[String]
+
+               # Attributes of the generated class
+               var attributes = new Array[String]
+
+               # Statements for the generated `load_all` method
+               var load_exprs = new Array[String]
+
+               # Add images to Nit source file
+               for image in images do
+                       # Adapt coordinates to new top left and scale
+                       var coordinates = document.coordinates(image)
+
+                       var nit_name = image.name
+                       var last_char = nit_name.chars.last
+                       if last_char.to_s.is_numeric then
+                               # Array of images
+                               # TODO support more than 10 images in an array
+
+                               nit_name = nit_name.substring(0, nit_name.length-1)
+                               if not arrays_of_images.has(nit_name) then
+                                       # Create class attribute to store Array
+                                       arrays_of_images.add(nit_name)
+                                       attributes.add "\tvar {nit_name} = new Array[Image]\n"
+                               end
+                               load_exprs.add "\t\t{nit_name}.add(main_image.subimage({coordinates}))\n"
+                       else
+                               # Single image
+                               attributes.add "\tvar {nit_name}: Image is noinit\n"
+                               load_exprs.add "\t\t{nit_name} = main_image.subimage({coordinates})\n"
+                       end
+               end
+
                add """
-# file generated by svg_to_png, do not modify, redef instead
+# File generated by svg_to_png_and_nit, do not modify, redef instead
 
 import mnit::image_set
 
-class {{{name}}}
+class {{{document.nit_class_name}}}
        super ImageSet
 
+       private var main_image: Image is noinit
 """
                add_all attributes
                add """
 
        redef fun load_all(app: App)
        do
+               main_image = app.load_image(\"images/{{{document.drawing_name}}}.png\")
 """
                add_all load_exprs
                add """
@@ -68,13 +157,94 @@ end
        end
 end
 
+# Nit module targeting the Gamnit framework
+#
+# Gamnit's `Texture` already manage the lazy loading, no need to do it here.
+class GamnitImageSetSrc
+       super ImageSetSrc
+
+       private fun attributes: Array[String]
+       do
+               # Separate the images from the arrays of images
+               var single_images = new Array[Image]
+               var arrays_of_images = new HashMap[String, Array[Image]]
+
+               for image in images do
+                       var nit_name = image.name
+                       var last_char = nit_name.chars.last
+                       if last_char.to_s.is_numeric then
+
+                               # Is an array
+                               nit_name = nit_name.substring(0, nit_name.length-1)
+                               if not arrays_of_images.keys.has(nit_name) then
+                                       # Create a new array
+                                       var array = new Array[Image]
+                                       arrays_of_images[nit_name] = array
+                               end
+
+                               arrays_of_images[nit_name].add image
+                       else
+                               # Is a single image
+                               single_images.add image
+                       end
+               end
+
+               # Attributes of the class
+               var attributes = new Array[String]
+               attributes.add "\tprivate var main_image = new Texture(\"images/{document.drawing_name}.png\")\n"
+
+               # Add single images to Nit source file
+               for image in single_images do
+                       # Adapt coordinates to new top left and scale
+                       var coordinates = document.coordinates(image)
+
+                       attributes.add "\tvar {image.name}: Texture = main_image.subtexture({coordinates})\n"
+               end
+
+               # Add array of images too
+               for name, images in arrays_of_images do
+
+                       var lines = new Array[String]
+                       for image in images do
+                               var coordinates = document.coordinates(image)
+                               lines.add "\t\tmain_image.subtexture({coordinates})"
+                       end
+
+                       attributes.add """
+       var {{{name}}} = new Array[Texture].with_items(
+{{{lines.join(",\n")}}})
+"""
+               end
+
+               return attributes
+       end
+
+       redef fun rendering
+       do
+               add """
+# File generated by svg_to_png_and_nit, do not modify, redef instead
+
+import gamnit::display
+
+class {{{document.nit_class_name}}}
+
+"""
+               add_all attributes
+               add """
+end
+"""
+       end
+end
+
 redef class Int
-       fun adapt(d: Int, scale: Float): Int
+       # Magic adaption of this coordinates to the given `margin` and `scale`
+       fun adapt(margin: Int, scale: Float): Int
        do
-               var corrected = self-d
+               var corrected = self-margin
                return (corrected.to_f*scale).to_i
        end
 
+       # The first power of to equal or greater than `self`
        fun next_pow2: Int
        do
                var p = 2
@@ -83,13 +253,15 @@ redef class Int
        end
 end
 
-var opt_out_src = new OptionString("Path to output source file", "--src", "-s")
+var opt_out_src = new OptionString("Path to output source file (folder or file)", "--src", "-s")
 var opt_assets = new OptionString("Path to assert dir where to put PNG files", "--assets", "-a")
-var opt_scale = new OptionFloat("Apply scaling to exported images (defaut at 1.0 of 90dpi)", 1.0, "--scale", "-x")
+var opt_scale = new OptionFloat("Apply scaling to exported images (default at 1.0 of 90dpi)", 1.0, "--scale", "-x")
+var opt_gamnit = new OptionBool("Target the Gamnit framework (by default it targets Mnit)", "--gamnit", "-g")
+var opt_pow2 = new OptionBool("Round the image size to the next power of 2", "--pow2")
 var opt_help = new OptionBool("Print this help message", "--help", "-h")
 
 var opt_context = new OptionContext
-opt_context.add_option(opt_out_src, opt_assets, opt_scale, opt_help)
+opt_context.add_option(opt_out_src, opt_assets, opt_scale, opt_gamnit, opt_pow2, opt_help)
 
 opt_context.parse(args)
 var rest = opt_context.rest
@@ -125,15 +297,13 @@ end
 
 var src_path = opt_out_src.value
 if src_path == null then src_path = "src"
-if not src_path.file_exists then
+if not src_path.file_exists and src_path.file_extension != "nit" then
        stderr.write "Source dir '{src_path}' does not exist (use --src)\n"
        exit 1
 end
 
 var scale = opt_scale.value
 
-var arrays_of_images = new Array[String]
-
 for drawing in drawings do
        var drawing_name = drawing.basename(".svg")
 
@@ -177,7 +347,7 @@ for drawing in drawings do
        while not proc.eof do
                var line = proc.read_line
                var words = line.split(",")
-               
+
                if words.length == 5 then
                        var id = words[0]
 
@@ -202,54 +372,37 @@ for drawing in drawings do
        end
        proc.close
 
-       # Nit class
-       var nit_class_name = drawing_name.chars.first.to_s.to_upper + drawing_name.substring_from(1) + "Images"
-       var nit_src = new ImageSetSrc(nit_class_name)
-       nit_src.attributes.add "\tprivate var main_image: Image is noinit\n"
-       nit_src.load_exprs.add "\t\tmain_image = app.load_image(\"images/{drawing_name}.png\")\n"
 
        # Sort images by name, it prevents Array errors and looks better
        alpha_comparator.sort(images)
 
-       # Add images to Nit source file
-       for image in images do
-               # Adapt coordinates to new top left and scale
-               var x = image.x.adapt(min_x, scale)
-               var y = image.y.adapt(min_y, scale)
-               var w = (image.w.to_f*scale).to_i
-               var h = (image.h.to_f*scale).to_i
+       var document = new Document(drawing_name, scale, min_x, max_x, min_y, max_y)
 
-               var nit_name = image.name
-               var last_char = nit_name.chars.last
-               if last_char.to_s.is_numeric then
-                       # Array of images
-                       # TODO support more than 10 images in an array
-
-                       nit_name = nit_name.substring(0, nit_name.length-1)
-                       if not arrays_of_images.has(nit_name) then
-                               # Create class attribute to store Array
-                               arrays_of_images.add(nit_name)
-                               nit_src.attributes.add "\tvar {nit_name} = new Array[Image]\n"
-                       end
-                       nit_src.load_exprs.add "\t\t{nit_name}.add(main_image.subimage({x}, {y}, {w}, {h}))\n"
-               else
-                       # Single image
-                       nit_src.attributes.add "\tvar {nit_name}: Image is noinit\n"
-                       nit_src.load_exprs.add "\t\t{nit_name} = main_image.subimage({x}, {y}, {w}, {h})\n"
-               end
+       # Nit class
+       var nit_src: ImageSetSrc
+       if opt_gamnit.value then
+               nit_src = new GamnitImageSetSrc(document, images)
+       else
+               nit_src = new MnitImageSetSrc(document, images)
+       end
+
+       if not src_path.file_extension == "nit" then
+               src_path = src_path/drawing_name+".nit"
        end
 
        # Output source file
-       var src_file = new FileWriter.open("{src_path}/{drawing_name}.nit")
+       var src_file = new FileWriter.open(src_path)
        nit_src.write_to(src_file)
        src_file.close
 
-       # Find closest power of 2
-       var dx = max_x - min_x
-       max_x = dx.next_pow2 + min_x
+       # Find next power of 2
+       if opt_pow2.value then
+               var dx = max_x - min_x
+               max_x = dx.next_pow2 + min_x
 
-       var dy = max_y - min_y
-       max_y = dy.next_pow2 + min_y
+               var dy = max_y - min_y
+               max_y = dy.next_pow2 + min_y
+       end
 
        # Inkscape's --export-area inverts the Y axis. It uses the lower left corner of
        # the drawing area where as queries return coordinates from the top left.
diff --git a/contrib/nitrpg/README.md b/contrib/nitrpg/README.md
new file mode 100644 (file)
index 0000000..c91c77e
--- /dev/null
@@ -0,0 +1,84 @@
+# 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:
+
+       ./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:
+
+       ./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`:
+
+       ./web localhost 3000 "/nitrpg"
+
+Leave it empty if NitRPG is installed at the root of the domain:
+
+       ./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/privat/nit/issues/1161).
index 625bfc2..eeae17e 100644 (file)
@@ -400,3 +400,119 @@ class Player10KCommits
        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.has_ok_review 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
index 4d62283..d1dff3e 100644 (file)
@@ -292,6 +292,19 @@ 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.
index 0e4ba00..8bee99b 100644 (file)
@@ -63,6 +63,8 @@ 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
index dfdb3ba..7b493c6 100644 (file)
@@ -187,3 +187,27 @@ redef class PullRequestEvent
                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.has_ok_review then
+                               game.stats.inc("reviews")
+                               player.stats.inc("reviews")
+                       end
+                       game.save
+                       player.save
+               end
+       end
+end
+
+redef class IssueComment
+       # Does this comment contain a "+1"?
+       fun has_ok_review: Bool do return body.has("\\+1\\b".to_re)
+end
index 14538c5..e4e7f8f 100644 (file)
@@ -18,6 +18,7 @@
 module panels
 
 import templates_events
+import markdown
 
 # A panel can be displayed in a html page.
 #
@@ -98,6 +99,86 @@ class ErrorPanel
 
 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>&nbsp;&nbsp;"
+               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>&nbsp;&nbsp;"
+               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
index b27c457..5f4992e 100644 (file)
@@ -53,6 +53,79 @@ class RpgAction
                rsp.body = page.write_to_string
                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 api.was_error or repo == null then return null
+               var game = new Game(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]
+               var rpgdir = "nitrpg_data"
+               if not rpgdir.file_exists then return res
+               for user in rpgdir.files do
+                       for repo in "{rpgdir}/{user}".files do
+                               var game = load_game("{user}/{repo}")
+                               if game != null then res.add game
+                       end
+               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.write_to_string
+               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.write_to_string
+               return response
+       end
 end
 
 # An action that require a game.
@@ -72,16 +145,15 @@ class GameAction
                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 /repos/:owner/:repo."
+                       var msg = "Bad request: should look like /games/:owner/:repo."
                        return bad_request(msg)
                end
-               var repo = new Repo(api, "{owner}/{repo_name}")
-               game = new Game(api, repo)
-               game.root_url = root_url
-               if api.was_error then
+               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)
@@ -103,6 +175,11 @@ class GameAction
 
        # 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.
@@ -111,6 +188,7 @@ class RepoHome
 
        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)
@@ -126,6 +204,7 @@ class ListPlayers
 
        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.write_to_string
@@ -139,6 +218,7 @@ class PlayerHome
 
        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."
@@ -166,6 +246,7 @@ class ListAchievements
 
        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.write_to_string
@@ -179,6 +260,7 @@ class AchievementHome
 
        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."
@@ -217,6 +299,8 @@ 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
index 83b02f9..ef74992 100644 (file)
@@ -244,7 +244,7 @@ redef class OverviewPage
                var ssection = new TplSection.with_title("projects", "Projects")
                for mproject in mprojects do
                        var sarticle = mproject.tpl_article
-                       sarticle.subtitle = mproject.tpl_declaration
+                       sarticle.subtitle = mproject.html_declaration
                        sarticle.content = mproject.tpl_definition
                        var mdoc = mproject.mdoc_or_fallback
                        if mdoc != null then
@@ -278,18 +278,18 @@ redef class SearchPage
                tpl.title = "Index"
                # modules list
                for mmodule in modules_list(v, doc) do
-                       tpl.modules.add mmodule.tpl_link
+                       tpl.modules.add mmodule.html_link
                end
                # classes list
                for mclass in classes_list(v, doc) do
-                       tpl.classes.add mclass.tpl_link
+                       tpl.classes.add mclass.html_link
                end
                # properties list
                for mproperty in mprops_list(v, doc) do
                        var m = new Template
-                       m.add mproperty.intro.tpl_link
+                       m.add mproperty.intro.html_link
                        m.add " ("
-                       m.add mproperty.intro.mclassdef.mclass.tpl_link
+                       m.add mproperty.intro.mclassdef.mclass.html_link
                        m.add ")"
                        tpl.props.add m
                end
@@ -369,7 +369,7 @@ redef class MGroupPage
                end
                var lnk = new Template
                lnk.add new TplLabel.with_classes(classes)
-               lnk.add def.tpl_link
+               lnk.add def.html_link
                return new TplListItem.with_content(lnk)
        end
 end
@@ -410,7 +410,7 @@ redef class MModulePage
                end
                var lnk = new Template
                lnk.add new TplLabel.with_classes(classes)
-               lnk.add def.tpl_link
+               lnk.add def.html_link
                return new TplListItem.with_content(lnk)
        end
 end
@@ -467,7 +467,7 @@ redef class MClassPage
                end
                var lnk = new Template
                lnk.add new TplLabel.with_classes(classes)
-               lnk.add mprop.tpl_anchor
+               lnk.add mprop.html_link_to_anchor
                return new TplListItem.with_content(lnk)
        end
 
@@ -496,7 +496,7 @@ end
 
 redef class MPropertyPage
        redef fun init_title(v, doc) do
-               title = "{mentity.html_name}{mentity.tpl_signature.write_to_string}"
+               title = "{mentity.html_name}{mentity.html_short_signature.write_to_string}"
        end
 
        redef fun init_topmenu(v, doc) do
@@ -527,14 +527,14 @@ redef class DocRoot
                var section = new TplSection("top")
                var mentity = page.mentity
                section.title = mentity.html_name
-               section.subtitle = mentity.tpl_declaration
+               section.subtitle = mentity.html_declaration
                # FIXME ugly hack to avoid diff
                if mentity isa MGroup and mentity.is_root then
                        section.title = mentity.mproject.html_name
-                       section.subtitle = mentity.mproject.tpl_declaration
+                       section.subtitle = mentity.mproject.html_declaration
                else if mentity isa MProperty then
-                       section.title = "{mentity.html_name}{mentity.intro.tpl_signature.write_to_string}"
-                       section.subtitle = mentity.tpl_namespace
+                       section.title = "{mentity.html_name}{mentity.intro.html_signature.write_to_string}"
+                       section.subtitle = mentity.html_namespace
                        section.summary_title = mentity.html_name
                end
                render(v, doc, page, section)
@@ -575,14 +575,14 @@ redef class ConcernSection
                        title.add "from "
                        section.summary_title = "from {mmodule.html_name}"
                end
-               title.add mmodule.tpl_namespace
+               title.add mmodule.html_namespace
                section.title = title
        end
 
        private fun render_concern_other(page: MEntityPage, section: TplSection, mmodule: MModule) do
                var title = new Template
                title.add "in "
-               title.add mmodule.tpl_namespace
+               title.add mmodule.html_namespace
                section.title = title
                section.summary_title = "in {mmodule.html_name}"
        end
@@ -605,11 +605,11 @@ redef class IntroArticle
                else if mentity isa MPropDef then
                        article.source_link = v.tpl_showsource(mentity.location)
                end
-               # article.subtitle = mentity.tpl_declaration
+               # article.subtitle = mentity.html_declaration
                # FIXME diff hack
                if mentity isa MProperty then
                        # intro title
-                       var ns = mentity.intro.mclassdef.mmodule.tpl_namespace
+                       var ns = mentity.intro.mclassdef.mmodule.html_namespace
                        var section = new TplSection("intro")
                        var title = new Template
                        title.add "Introduction in "
@@ -644,7 +644,7 @@ redef class DefinitionArticle
                # FIXME hideous hacks...
                if mentity isa MModule then
                        article = mentity.tpl_article
-                       article.subtitle = mentity.tpl_declaration
+                       article.subtitle = mentity.html_declaration
                        article.content = mentity.tpl_definition
                else if mentity isa MClass then
                        article = make_mclass_article(v, page)
@@ -655,7 +655,7 @@ redef class DefinitionArticle
                        article = make_mpropdef_article(v, doc, page)
                else
                        article = mentity.tpl_article
-                       article.subtitle = mentity.tpl_declaration
+                       article.subtitle = mentity.html_declaration
                        if mentity isa MPropDef then
                                article.source_link = v.tpl_showsource(mentity.location)
                        end
@@ -673,7 +673,7 @@ redef class DefinitionArticle
 
        private fun make_mclass_article(v: RenderHTMLPhase, page: MEntityPage): TplArticle do
                var article = mentity.tpl_article
-               article.subtitle = mentity.tpl_namespace
+               article.subtitle = mentity.html_namespace
                article.content = null
                return article
        end
@@ -686,7 +686,7 @@ redef class DefinitionArticle
                end
                var title = new Template
                title.add "in "
-               title.add mclassdef.mmodule.tpl_namespace
+               title.add mclassdef.mmodule.html_namespace
                article.subtitle = title
                return article
        end
@@ -700,8 +700,8 @@ redef class DefinitionArticle
                title.add mprop.tpl_icon
                title.add "<span id='{mpropdef.nitdoc_id}'></span>"
                if mpropdef.is_intro then
-                       title.add mprop.tpl_link
-                       title.add mprop.intro.tpl_signature
+                       title.add mprop.html_link
+                       title.add mprop.intro.html_signature
                else
                        var cls_url = mprop.intro.mclassdef.mclass.nitdoc_url
                        var def_url = "{cls_url}#{mprop.nitdoc_id}"
@@ -713,7 +713,7 @@ redef class DefinitionArticle
                article.title = title
                article.title_classes.add "signature"
                article.summary_title = "{mprop.html_name}"
-               article.subtitle = mpropdef.tpl_namespace
+               article.subtitle = mpropdef.html_namespace
                if mpropdef.mdoc_or_fallback != null then
                        article.content = mpropdef.mdoc_or_fallback.tpl_comment
                end
index 2908fad..a37dd2b 100644 (file)
@@ -18,6 +18,7 @@ module html_model
 import doc_base
 import doc_down
 import html_components
+import html::bootstrap
 import ordered_tree
 
 redef class Location
@@ -56,9 +57,11 @@ redef class MEntity
        # * MPropdef: `foo(e)`
        var html_name: String is lazy do return name.html_escape
 
-       # A template link to the mentity `nitdoc_id`
-       fun tpl_anchor: TplLink do
-               var tpl = new TplLink("#{nitdoc_id}", html_name)
+       # Returns a Link to the mentity `html_url`.
+       #
+       # Example: `<a href="html_url" title="mdoc.short_comment">html_short_name</a>
+       var html_link: Link is lazy do
+               var tpl = new Link(nitdoc_url, html_name)
                var mdoc = mdoc_or_fallback
                if mdoc != null then
                        tpl.title = mdoc.short_comment
@@ -66,9 +69,11 @@ redef class MEntity
                return tpl
        end
 
-       # A template link to the mentity `nitdoc_url`
-       fun tpl_link: TplLink do
-               var tpl = new TplLink(nitdoc_url, html_name)
+       # Returns a Link to the mentity `nitdoc_id`.
+       #
+       # Example: `<a href="#nitdoc_id" title="mdoc.short_comment">html_short_name</a>
+       fun html_link_to_anchor: Link do
+               var tpl = new Link("#{nitdoc_id}", html_name)
                var mdoc = mdoc_or_fallback
                if mdoc != null then
                        tpl.title = mdoc.short_comment
@@ -76,6 +81,39 @@ redef class MEntity
                return tpl
        end
 
+       # Returns the list of keyword used in `self` declaration.
+       fun html_modifiers: Array[String] is abstract
+
+       # Returns the complete MEntity declaration decorated with HTML.
+       #
+       # * MProject: `project foo`
+       # * MGroup: `group foo`
+       # * MModule: `module foo`
+       # * MClass: `private abstract class Foo[E: Object]`
+       # * MClassDef: `redef class Foo[E]`
+       # * MProperty: `private fun foo(e: Object): Int`
+       # * MPropdef: `redef fun foo(e)`
+       fun html_declaration: Template do
+               var tpl = new Template
+               tpl.add "<span>"
+               tpl.add html_modifiers.join(" ")
+               tpl.add " "
+               tpl.add html_link
+               tpl.add "</span>"
+               return tpl
+       end
+
+       # Returns `self` namespace decorated with HTML links.
+       #
+       # * MProject: `mproject`
+       # * MGroup: `mproject(::group)`
+       # * MModule: `mgroup::mmodule`
+       # * MClass: `mproject::mclass`
+       # * MClassDef: `mmodule::mclassdef`
+       # * MProperty: `mclass::mprop`
+       # * MPropdef: `mclassdef:mpropdef`
+       fun html_namespace: Template is abstract
+
        # A template article that briefly describe the entity
        fun tpl_short_article: TplArticle do
                var tpl = tpl_article
@@ -90,17 +128,11 @@ redef class MEntity
        fun tpl_article: TplArticle do
                var tpl = new TplArticle.with_title(nitdoc_id, tpl_title)
                tpl.title_classes.add "signature"
-               tpl.subtitle = tpl_namespace
+               tpl.subtitle = html_namespace
                tpl.summary_title = html_name
                return tpl
        end
 
-       # A template signature that contains modifiers and parameters
-       fun tpl_declaration: Template is abstract
-
-       # A template namespace
-       fun tpl_namespace: Template is abstract
-
        # A template definition of the mentity
        # include name, sysnopsys, comment and namespace
        fun tpl_definition: TplDefinition is abstract
@@ -109,7 +141,7 @@ redef class MEntity
        fun tpl_list_item: TplListItem do
                var lnk = new Template
                lnk.add new TplLabel.with_classes(tpl_css_classes)
-               lnk.add tpl_link
+               lnk.add html_link
                var mdoc = mdoc_or_fallback
                if mdoc != null then
                        lnk.add ": "
@@ -124,7 +156,7 @@ redef class MEntity
        fun tpl_title: Template do
                var title = new Template
                title.add tpl_icon
-               title.add tpl_namespace
+               title.add html_namespace
                return title
        end
 
@@ -140,7 +172,7 @@ redef class MConcern
        # Return a li element for `self` that can be displayed in a concern list
        private fun tpl_concern_item: TplListItem do
                var lnk = new Template
-               lnk.add tpl_anchor
+               lnk.add html_link_to_anchor
                var mdoc = mdoc_or_fallback
                if mdoc != null then
                        lnk.add ": "
@@ -153,16 +185,8 @@ end
 redef class MProject
        redef var nitdoc_id = name.to_cmangle is lazy
        redef fun nitdoc_url do return root.nitdoc_url
-
-       redef fun tpl_declaration do
-               var tpl = new Template
-               tpl.add "<span>project "
-               tpl.add tpl_link
-               tpl.add "</span>"
-               return tpl
-       end
-
-       redef fun tpl_namespace do return tpl_link
+       redef var html_modifiers = ["project"]
+       redef fun html_namespace do return html_link
 
        redef fun tpl_definition do
                var tpl = new TplDefinition
@@ -185,25 +209,22 @@ redef class MGroup
        end
 
        redef fun nitdoc_url do return "group_{nitdoc_id}.html"
+       redef var html_modifiers = ["group"]
 
-       redef fun tpl_namespace do
+       # Depends if `self` is root or not.
+       #
+       # * If root `mproject`.
+       # * Else `mproject::self`.
+       redef fun html_namespace do
                var tpl = new Template
-               tpl.add mproject.tpl_namespace
+               tpl.add mproject.html_namespace
                if mproject.root != self then
                        tpl.add "::"
-                       tpl.add tpl_link
+                       tpl.add html_link
                end
                return tpl
        end
 
-       redef fun tpl_declaration do
-               var tpl = new Template
-               tpl.add "<span>group "
-               tpl.add tpl_link
-               tpl.add "</span>"
-               return tpl
-       end
-
        redef fun tpl_definition do
                var tpl = new TplDefinition
                var mdoc = mdoc_or_fallback
@@ -227,22 +248,19 @@ redef class MModule
        end
 
        redef fun nitdoc_url do return "module_{nitdoc_id}.html"
+       redef var html_modifiers = ["module"]
 
-       redef fun tpl_declaration do
-               var tpl = new Template
-               tpl.add "<span>module "
-               tpl.add tpl_namespace
-               tpl.add "</span>"
-               return tpl
-       end
-
-       redef fun tpl_namespace do
+       # Depends if `self` belongs to a MGroup.
+       #
+       # * If mgroup `mgroup::self`.
+       # * Else `self`.
+       redef fun html_namespace do
                var tpl = new Template
                if mgroup != null then
-                       tpl.add mgroup.tpl_namespace
+                       tpl.add mgroup.html_namespace
                        tpl.add "::"
                end
-               tpl.add tpl_link
+               tpl.add html_link
                return tpl
        end
 
@@ -279,67 +297,125 @@ redef class MClass
                return tpl.write_to_string
        end
 
-       redef fun tpl_declaration do return intro.tpl_declaration
-       redef fun tpl_definition do return intro.tpl_definition
+       redef fun html_modifiers do return intro.html_modifiers
+       redef fun html_declaration do return intro.html_declaration
 
-       redef fun tpl_namespace do
+       # Returns `mproject::self`.
+       redef fun html_namespace do
                var tpl = new Template
-               tpl.add intro_mmodule.mgroup.mproject.tpl_namespace
+               tpl.add intro_mmodule.mgroup.mproject.html_namespace
                tpl.add "::<span>"
-               tpl.add tpl_link
+               tpl.add html_link
                tpl.add "</span>"
                return tpl
        end
 
+       # Returns `intro.html_short_signature`.
+       fun html_short_signature: Template do return intro.html_short_signature
+
+       # Returns `intro.html_signature`.
+       fun html_signature: Template do return intro.html_signature
+
+       redef fun tpl_definition do return intro.tpl_definition
+
        redef fun tpl_title do
                var title = new Template
                title.add tpl_icon
-               title.add tpl_link
+               title.add html_link
                return title
        end
 
        redef fun tpl_icon do return intro.tpl_icon
-
-       fun tpl_signature: Template do
-               var tpl = new Template
-               if arity > 0 then
-                       tpl.add "["
-                       var parameter_names = new Array[String]
-                       for p in mparameters do
-                               parameter_names.add(p.html_name)
-                       end
-                       tpl.add parameter_names.join(", ")
-                       tpl.add "]"
-               end
-               return tpl
-       end
-
        redef fun tpl_css_classes do return intro.tpl_css_classes
 end
 
 redef class MClassDef
        redef var nitdoc_id = "{mmodule.nitdoc_id}__{name.to_cmangle}" is lazy
        redef fun nitdoc_url do return "{mclass.nitdoc_url}#{nitdoc_id}"
-
        redef fun mdoc_or_fallback do return mdoc or else mclass.mdoc_or_fallback
 
-       redef fun tpl_namespace do
+       # Depends if `self` is an intro or not.
+       #
+       # * If intro contains the visibility and kind.
+       # * If redef contains the `redef` keyword and kind.
+       redef fun html_modifiers do
+               var res = new Array[String]
+               if not is_intro then
+                       res.add "redef"
+               else
+                       if mclass.visibility != public_visibility then
+                               res.add mclass.visibility.to_s
+                       end
+               end
+               res.add mclass.kind.to_s
+               return res
+       end
+
+       # Depends if `self` is an intro or not.
+       #
+       # For intro: `private abstract class Foo[E: Object]`
+       # For redef: `redef class Foo[E]`
+       # TODO change the implementation to correspond to the comment.
+       redef fun html_declaration do
                var tpl = new Template
-               tpl.add mmodule.tpl_namespace
+               tpl.add "<span>"
+               tpl.add html_modifiers.join(" ")
+               tpl.add " "
+               tpl.add html_link
+               tpl.add html_signature
+               tpl.add "</span>"
+               return tpl
+       end
+
+       # Returns `mmodule::self`
+       redef fun html_namespace do
+               var tpl = new Template
+               tpl.add mmodule.html_namespace
                tpl.add "::<span>"
-               tpl.add mclass.tpl_link
+               tpl.add mclass.html_link
                tpl.add "</span>"
                return tpl
        end
 
+       # Returns the MClassDef generic signature without static bounds.
+       fun html_short_signature: Template do
+               var tpl = new Template
+               var mparameters = mclass.mparameters
+               if not mparameters.is_empty then
+                       tpl.add "["
+                       for i in [0..mparameters.length[ do
+                               tpl.add mparameters[i].html_name
+                               if i < mparameters.length - 1 then tpl.add ", "
+                       end
+                       tpl.add "]"
+               end
+               return tpl
+       end
+
+       # Returns the MClassDef generic signature with static bounds.
+       fun html_signature: Template do
+               var tpl = new Template
+               var mparameters = mclass.mparameters
+               if not mparameters.is_empty then
+                       tpl.add "["
+                       for i in [0..mparameters.length[ do
+                               tpl.add "{mparameters[i].html_name}: "
+                               tpl.add bound_mtype.arguments[i].html_signature
+                               if i < mparameters.length - 1 then tpl.add ", "
+                       end
+                       tpl.add "]"
+               end
+               return tpl
+       end
+
        redef fun tpl_article do
                var tpl = new TplArticle(nitdoc_id)
                tpl.summary_title = "in {mmodule.html_name}"
-               tpl.title = tpl_declaration
+               tpl.title = html_declaration
                tpl.title_classes.add "signature"
                var title = new Template
                title.add "in "
-               title.add mmodule.tpl_namespace
+               title.add mmodule.html_namespace
                tpl.subtitle = title
                var mdoc = mdoc_or_fallback
                if mdoc != null then
@@ -351,33 +427,10 @@ redef class MClassDef
        redef fun tpl_title do
                var title = new Template
                title.add tpl_icon
-               title.add tpl_link
+               title.add html_link
                return title
        end
 
-       redef fun tpl_declaration do
-               var tpl = new Template
-               tpl.add tpl_modifiers
-               tpl.add tpl_link
-               tpl.add tpl_signature
-               return tpl
-       end
-
-       fun tpl_signature: Template do
-               var tpl = new Template
-               var mparameters = mclass.mparameters
-               if not mparameters.is_empty then
-                       tpl.add "["
-                       for i in [0..mparameters.length[ do
-                               tpl.add "{mparameters[i].html_name}: "
-                               tpl.add bound_mtype.arguments[i].tpl_signature
-                               if i < mparameters.length - 1 then tpl.add ", "
-                       end
-                       tpl.add "]"
-               end
-               return tpl
-       end
-
        redef fun tpl_definition do
                var tpl = new TplClassDefinition
                var mdoc = mdoc_or_fallback
@@ -408,21 +461,25 @@ end
 redef class MProperty
        redef var nitdoc_id = "{intro_mclassdef.mclass.nitdoc_id}__{name.to_cmangle}" is lazy
        redef fun nitdoc_url do return "property_{nitdoc_id}.html"
-
        redef fun mdoc_or_fallback do return intro.mdoc
+       redef fun html_modifiers do return intro.html_modifiers
+       redef fun html_declaration do return intro.html_declaration
 
-       redef fun tpl_namespace do
+       # Returns `mclass::self`.
+       redef fun html_namespace do
                var tpl = new Template
-               tpl.add intro_mclassdef.mclass.tpl_namespace
+               tpl.add intro_mclassdef.mclass.html_namespace
                tpl.add "::<span>"
-               tpl.add intro.tpl_link
+               tpl.add intro.html_link
                tpl.add "</span>"
                return tpl
        end
 
-       redef fun tpl_declaration do return intro.tpl_declaration
+       # Returns `intro.html_short_signature`.
+       fun html_short_signature: Template do return intro.html_short_signature
 
-       fun tpl_signature: Template do return new Template
+       # Returns `intro.html_signature`.
+       fun html_signature: Template do return intro.html_signature
 
        redef fun tpl_title do return intro.tpl_title
 
@@ -434,25 +491,63 @@ end
 redef class MPropDef
        redef var nitdoc_id = "{mclassdef.nitdoc_id}__{name.to_cmangle}" is lazy
        redef fun nitdoc_url do return "{mproperty.nitdoc_url}#{nitdoc_id}"
-
        redef fun mdoc_or_fallback do return mdoc or else mproperty.mdoc_or_fallback
 
-       redef fun tpl_namespace do
+       # Depends if `self` is an intro or not.
+       #
+       # * If intro contains the visibility and kind.
+       # * If redef contains the `redef` keyword and kind.
+       redef fun html_modifiers do
+               var res = new Array[String]
+               if not is_intro then
+                       res.add "redef"
+               else
+                       if mproperty.visibility != public_visibility then
+                               res.add mproperty.visibility.to_s
+                       end
+               end
+               return res
+       end
+
+       # Depends if `self` is an intro or not.
+       #
+       # For intro: `private fun foo(e: Object): Bar is abstract`
+       # For redef: `redef fun foo(e) is cached`
+       # TODO change the implementation to correspond to the comment.
+       redef fun html_declaration do
                var tpl = new Template
-               tpl.add mclassdef.tpl_namespace
+               tpl.add "<span>"
+               tpl.add html_modifiers.join(" ")
+               tpl.add " "
+               tpl.add html_link
+               tpl.add html_signature
+               tpl.add "</span>"
+               return tpl
+       end
+
+       # Returns `mclassdef::self`
+       redef fun html_namespace do
+               var tpl = new Template
+               tpl.add mclassdef.html_namespace
                tpl.add "::"
-               tpl.add tpl_link
+               tpl.add html_link
                return tpl
        end
 
+       # Returns the MPropdDef signature without static types.
+       fun html_short_signature: Template is abstract
+
+       # Returns the MPropDef signature with static types.
+       fun html_signature: Template is abstract
+
        redef fun tpl_article do
                var tpl = new TplArticle(nitdoc_id)
                tpl.summary_title = "in {mclassdef.html_name}"
                var title = new Template
                title.add "in "
-               title.add mclassdef.tpl_link
+               title.add mclassdef.html_link
                tpl.title = title
-               tpl.subtitle = tpl_declaration
+               tpl.subtitle = html_declaration
                var mdoc = mdoc_or_fallback
                if mdoc != null then
                        tpl.content = mdoc.tpl_comment
@@ -469,14 +564,6 @@ redef class MPropDef
                return tpl
        end
 
-       redef fun tpl_declaration do
-               var tpl = new Template
-               tpl.add tpl_modifiers
-               tpl.add tpl_link
-               tpl.add tpl_signature
-               return tpl
-       end
-
        redef fun tpl_css_classes do
                var set = new HashSet[String]
                if is_intro then set.add "intro"
@@ -494,13 +581,13 @@ redef class MPropDef
                return tpl
        end
 
-       fun tpl_signature: Template do return new Template
-
        redef fun tpl_list_item do
                var lnk = new Template
                lnk.add new TplLabel.with_classes(tpl_css_classes.to_a)
-               var anchor = tpl_link
-               anchor.href = "{mclassdef.mclass.nitdoc_url}#{mproperty.nitdoc_id}"
+               var atext = html_link.text
+               var ahref = "{mclassdef.mclass.nitdoc_url}#{mproperty.nitdoc_id}"
+               var atitle = html_link.title
+               var anchor = new Link.with_title(ahref, atext, atitle)
                lnk.add anchor
                var mdoc = mdoc_or_fallback
                if mdoc != null then
@@ -513,10 +600,12 @@ redef class MPropDef
        fun tpl_inheritance_item: TplListItem do
                var lnk = new Template
                lnk.add new TplLabel.with_classes(tpl_css_classes.to_a)
-               lnk.add mclassdef.mmodule.tpl_namespace
+               lnk.add mclassdef.mmodule.html_namespace
                lnk.add "::"
-               var anchor = mclassdef.tpl_link
-               anchor.href = "{mclassdef.mclass.nitdoc_url}#{mproperty.nitdoc_id}"
+               var atext = mclassdef.html_link.text
+               var ahref = "{mclassdef.mclass.nitdoc_url}#{mproperty.nitdoc_id}"
+               var atitle = mclassdef.html_link.title
+               var anchor = new Link.with_title(ahref, atext, atitle)
                lnk.add anchor
                var mdoc = mdoc_or_fallback
                if mdoc != null then
@@ -530,78 +619,122 @@ redef class MPropDef
 end
 
 redef class MAttributeDef
-       redef fun tpl_signature do
+
+       redef fun html_modifiers do
+               var res = super
+               res.add "var"
+               return res
+       end
+
+       redef fun html_short_signature do return new Template
+
+       redef fun html_signature do
                var tpl = new Template
                if static_mtype != null then
                        tpl.add ": "
-                       tpl.add static_mtype.tpl_signature
+                       tpl.add static_mtype.html_signature
                end
                return tpl
        end
 end
 
-redef class MMethod
-       redef fun tpl_signature do
-               var tpl = new Template
-               var params = new Array[String]
-               for param in intro.msignature.mparameters do
-                       params.add param.name.html_escape
+redef class MMethodDef
+
+       # FIXME annotation should be handled in their own way
+       redef fun html_modifiers do
+               var res = super
+               if is_abstract then
+                       res.add "abstract"
+               else if is_intern then
+                       res.add "intern"
                end
-               if not params.is_empty then
-                       tpl.add "("
-                       tpl.add params.join(", ")
-                       tpl.add ")"
+               if mproperty.is_init then
+                       res.add "init"
+               else
+                       res.add "fun"
                end
-               return tpl
+               return res
        end
-end
 
-redef class MMethodDef
-       redef fun tpl_signature do return msignature.tpl_signature
+       redef fun html_short_signature do return msignature.html_short_signature
+       redef fun html_signature do return msignature.html_signature
 end
 
 redef class MVirtualTypeProp
-       redef fun tpl_link do return mvirtualtype.tpl_link
-       redef fun tpl_signature do return tpl_link
+       redef fun html_link do return mvirtualtype.html_link
 end
 
 redef class MVirtualTypeDef
-       redef fun tpl_signature do
+
+       redef fun html_modifiers do
+               var res = super
+               res.add "type"
+               return res
+       end
+
+       redef fun html_short_signature do return new Template
+
+       redef fun html_signature do
                var tpl = new Template
                if bound == null then return tpl
                tpl.add ": "
-               tpl.add bound.tpl_signature
+               tpl.add bound.html_signature
                return tpl
        end
 end
 
 redef class MType
-       fun tpl_signature: Template is abstract
+       # Returns the signature of this type whithout bounds.
+       fun html_short_signature: Template is abstract
+
+       # Returns the signature of this type.
+       fun html_signature: Template is abstract
 end
 
 redef class MClassType
-       redef fun tpl_link do return mclass.tpl_link
-       redef fun tpl_signature do return tpl_link
+       redef fun html_link do return mclass.html_link
+       redef fun html_short_signature do return html_link
+       redef fun html_signature do return html_link
 end
 
 redef class MNullableType
-       redef fun tpl_signature do
+
+       redef fun html_short_signature do
+               var tpl = new Template
+               tpl.add "nullable "
+               tpl.add mtype.html_short_signature
+               return tpl
+       end
+
+       redef fun html_signature do
                var tpl = new Template
                tpl.add "nullable "
-               tpl.add mtype.tpl_signature
+               tpl.add mtype.html_signature
                return tpl
        end
 end
 
 redef class MGenericType
-       redef fun tpl_signature do
+       redef fun html_short_signature do
+               var lnk = html_link
                var tpl = new Template
-               var lnk = tpl_link
-               lnk.text = mclass.name.html_escape
-               tpl.add lnk
+               tpl.add new Link.with_title(lnk.href, mclass.name.html_escape, lnk.title)
                tpl.add "["
                for i in [0..arguments.length[ do
-                       tpl.add arguments[i].tpl_signature
+                       tpl.add arguments[i].html_short_signature
+                       if i < arguments.length - 1 then tpl.add ", "
+               end
+               tpl.add "]"
+               return tpl
+       end
+
+       redef fun html_signature do
+               var lnk = html_link
+               var tpl = new Template
+               tpl.add new Link.with_title(lnk.href, mclass.name.html_escape, lnk.title)
+               tpl.add "["
+               for i in [0..arguments.length[ do
+                       tpl.add arguments[i].html_signature
                        if i < arguments.length - 1 then tpl.add ", "
                end
                tpl.add "]"
@@ -610,41 +743,67 @@ redef class MGenericType
 end
 
 redef class MParameterType
-       redef fun tpl_link do
-               return new TplLink.with_title("{mclass.nitdoc_url}#FT_{name.to_cmangle}", name, "formal type")
+       redef fun html_link do
+               return new Link.with_title("{mclass.nitdoc_url}#FT_{name.to_cmangle}", name, "formal type")
        end
-       redef fun tpl_signature do return tpl_link
+
+       redef fun html_short_signature do return html_link
+       redef fun html_signature do return html_link
 end
 
 redef class MVirtualType
-       redef fun tpl_link do return mproperty.intro.tpl_link
-       redef fun tpl_signature do return tpl_link
+       redef fun html_link do return mproperty.intro.html_link
+       redef fun html_signature do return html_link
 end
 
 redef class MSignature
-       redef fun tpl_signature do
+
+       redef fun html_short_signature do
                var tpl = new Template
                if not mparameters.is_empty then
                        tpl.add "("
                        for i in [0..mparameters.length[ do
-                               tpl.add mparameters[i].tpl_signature
+                               tpl.add mparameters[i].html_short_signature
+                               if i < mparameters.length - 1 then tpl.add ", "
+                       end
+                       tpl.add ")"
+               end
+               return tpl
+       end
+
+       redef fun html_signature do
+               var tpl = new Template
+               if not mparameters.is_empty then
+                       tpl.add "("
+                       for i in [0..mparameters.length[ do
+                               tpl.add mparameters[i].html_signature
                                if i < mparameters.length - 1 then tpl.add ", "
                        end
                        tpl.add ")"
                end
                if return_mtype != null then
                        tpl.add ": "
-                       tpl.add return_mtype.tpl_signature
+                       tpl.add return_mtype.html_signature
                end
                return tpl
        end
 end
 
 redef class MParameter
-       fun tpl_signature: Template do
+
+       # Returns `self` name and ellipsys if any.
+       fun html_short_signature: Template do
+               var tpl = new Template
+               tpl.add name
+               if is_vararg then tpl.add "..."
+               return tpl
+       end
+
+       # Returns `self` name with it's static type and ellipsys if any.
+       fun html_signature: Template do
                var tpl = new Template
                tpl.add "{name}: "
-               tpl.add mtype.tpl_signature
+               tpl.add mtype.html_signature
                if is_vararg then tpl.add "..."
                return tpl
        end
@@ -692,12 +851,12 @@ end
 # Additions to `model_ext`.
 
 redef class MRawType
-       redef fun tpl_signature do
+       redef fun html_signature do
                var tpl = new Template
 
                for part in parts do
                        if part.target != null then
-                               tpl.add part.target.as(not null).tpl_link
+                               tpl.add part.target.as(not null).html_link
                        else
                                tpl.add part.text.html_escape
                        end
@@ -708,15 +867,15 @@ end
 
 redef class MInnerClass
        redef fun nitdoc_url do return inner.nitdoc_url
-       redef fun tpl_signature do return inner.tpl_signature
+       redef fun html_signature do return inner.html_signature
 end
 
 redef class MInnerClassDef
        redef fun nitdoc_url do return inner.nitdoc_url
 
-       redef fun tpl_anchor do return inner.tpl_anchor
-       redef fun tpl_link do return inner.tpl_link
-       redef fun tpl_signature do return inner.tpl_signature
+       redef fun html_link_to_anchor do return inner.html_link_to_anchor
+       redef fun html_link do return inner.html_link
+       redef fun html_signature do return inner.html_signature
 
        redef fun tpl_definition do
                var tpl = new TplClassDefinition