nitdoc: use templates for html output
authorAlexandre Terrasa <alexandre@moz-code.org>
Wed, 7 May 2014 17:09:51 +0000 (13:09 -0400)
committerAlexandre Terrasa <alexandre@moz-code.org>
Wed, 7 May 2014 17:09:51 +0000 (13:09 -0400)
Full rewriting of nitdoc to extract html templates

Signed-off-by: Alexandre Terrasa <alexandre@moz-code.org>
Signed-off-by: Maxime Leroy <maxime.leroy76@gmail.com>

src/doc_template.nit [new file with mode: 0644]
src/nitdoc.nit

diff --git a/src/doc_template.nit b/src/doc_template.nit
new file mode 100644 (file)
index 0000000..3424bef
--- /dev/null
@@ -0,0 +1,705 @@
+# 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.
+
+# HTML templates used by Nitdoc to generate API documentation
+# Pages are assembled using `Template`
+module doc_template
+
+import template
+
+# Full Nitdoc page template
+class TplNitdocPage
+       super Template
+
+       var head: TplHead writable
+       var body_attrs = new Array[TagAttribute] # attributes for body tag element
+       var topmenu: TplTopMenu writable
+       var sidebar: nullable TplSidebar writable
+       var content: Streamable writable
+       var footer: nullable TplFooter writable
+       var scripts = new Array[TplScript] # js scripts appended to body
+
+       redef fun rendering do
+               add "<!DOCTYPE html>"
+               add "<head>"
+               add head
+               add "</head>"
+               add "<body"
+               for attr in body_attrs do add attr
+               add ">"
+               add topmenu
+               if footer != null then
+                       add "<div class='page footed'>"
+               else
+                       add "<div class='page'>"
+               end
+               if sidebar != null then
+                       add sidebar.as(not null)
+               end
+               add content
+               add "</div>"
+               if footer != null then
+                       add footer.as(not null)
+               end
+               for script in scripts do
+                       add script
+               end
+               add "</body>"
+               add "</html>"
+       end
+end
+
+# general layout elements
+
+# <head> tag
+class TplHead
+       super Template
+
+       var title: String
+       var shareurl: String
+
+       init(title, shareurl: String) do
+               self.title = title
+               self.shareurl = shareurl
+       end
+
+       redef fun rendering do
+               add """
+<meta charset="utf-8"/>
+<link rel="stylesheet" href="{{{shareurl}}}/css/main.css" type="text/css"/>
+<link rel="stylesheet" href="{{{shareurl}}}/css/Nitdoc.UI.css" type="text/css"/>
+<link rel="stylesheet" href="{{{shareurl}}}/css/Nitdoc.QuickSearch.css" type="text/css"/>
+<link rel="stylesheet" href="{{{shareurl}}}/css/Nitdoc.GitHub.css" type="text/css"/>
+<link rel="stylesheet" href="{{{shareurl}}}/css/Nitdoc.ModalBox.css" type="text/css"/>
+<title>{{{title}}}</title>"""
+       end
+end
+
+# Top bar menu
+class TplTopMenu
+       super Template
+
+       private var elts = new Array[Streamable]
+
+       redef fun rendering do
+               add "<header>"
+               add "<nav class='main'>"
+               if not elts.is_empty then
+                       add "<ul>"
+                       for elt in elts do add(elt)
+                       add "</ul>"
+               end
+               add "</nav>"
+               add "</header>"
+       end
+
+       fun add_elt(href, name: String, is_active: Bool) do
+               elts.add(new TplTopMenuElt(href, name, is_active))
+       end
+
+       fun add_raw(content: Streamable) do
+               elts.add(content)
+       end
+end
+
+# A topmenu element
+private class TplTopMenuElt
+       super Template
+
+       var href: String
+       var name: String
+       var is_active: Bool
+
+       init(href, name: String, is_active: Bool) do
+               self.href = href
+               self.name = name
+               self.is_active = is_active
+       end
+
+       redef fun rendering do
+               if is_active then
+                       add """<li class="current">{{{name}}}</li>"""
+               else
+                       add """<li><a href="{{{href}}}">{{{name}}}</a></li>"""
+               end
+       end
+end
+
+# <footer> element
+class TplFooter
+       super Template
+
+       var content: Streamable writable
+
+       init(content: Streamable) do self.content = content
+
+       redef fun rendering do
+               add "<footer>"
+               add content
+               add "</footer>"
+       end
+end
+
+# sidebar layout
+
+# Sidebar <div>
+class TplSidebar
+       super Template
+
+       var boxes = new Array[TplSidebarBox]
+
+       redef fun rendering do
+               add """"<div class="sidebar">"""
+               for box in boxes do add box
+               add "</div>"
+       end
+end
+
+# A box that can be added to sidebar
+class TplSidebarBox
+       super Template
+
+       var name: String
+       var elts = new Array[TplSidebarGroup]
+
+       init(name: String) do self.name = name
+
+       redef fun rendering do
+               add """<nav class"properties filterable">"""
+               add """    <h3>{{{name}}}</h3>"""
+               for elt in elts do add elt
+               add "</nav>"
+       end
+end
+
+# A sidebar box group
+class TplSidebarGroup
+       super Template
+
+       var name: String
+       private var elts = new Array[Template]
+
+       init(name: String) do self.name = name
+
+       redef fun rendering do
+               if elts.is_empty then return
+               add "<h4>{name}</h4>"
+               add "<ul>"
+               for elt in elts do add elt
+               add "</ul>"
+       end
+
+       fun add_elt(content: Streamable, classes: Array[Streamable]) do
+               var tpl = new Template
+               tpl.add "<li {classes.join(" ")}>"
+               tpl.add content
+               tpl.add "</li>"
+               elts.add(tpl)
+       end
+
+       fun add_bullet(text, title, content: Streamable, classes: Array[Streamable]) do
+               var tpl = new Template
+               tpl.add "<span title='{title}'>{text}</span>"
+               tpl.add content
+               add_elt(tpl, classes)
+       end
+end
+
+# page layouts
+
+# Layout for Overview page
+class TplOverviewPage
+       super Template
+
+       var title: nullable Streamable writable
+       var text: nullable Streamable writable
+       var graph: nullable TplGraph writable
+       var modules = new Array[Streamable]
+
+       redef fun rendering do
+               add "<div class='content fullpage'>"
+               if title != null then add "<h1>{title}</h1>"
+               if text != null then add "<article class='overview'>{text}</article>"
+
+               if not modules.is_empty then
+                       add "<article class='overview'>"
+                       add "<h2>Modules</h2>"
+                       add "<ul>"
+                       for m in modules do
+                               add "<li>"
+                               add m
+                               add "</li>"
+                       end
+                       add "</ul>"
+               end
+               if not graph == null then add graph.as(not null)
+               add "</article>"
+               add "</div>"
+       end
+end
+
+# Layout for Search page
+class TplSearchPage
+       super Template
+
+       var title: nullable Streamable writable
+       var modules = new Array[Streamable]
+       var classes = new Array[Streamable]
+       var props = new Array[Streamable]
+
+       redef fun rendering do
+               add "<div class='content fullpage'>"
+               if title != null then add "<h1>{title}</h1>"
+               if not modules.is_empty then
+                       add "<article class='modules filterable'>"
+                       add "<h2>Modules</h2>"
+                       add "<ul>"
+                       for m in modules do
+                               add "<li>"
+                               add m
+                               add "</li>"
+                       end
+                       add "</ul>"
+                       add "</article>"
+               end
+               if not classes.is_empty then
+                       add "<article class='classes filterable'>"
+                       add "<h2>Classes</h2>"
+                       add "<ul>"
+                       for c in classes do
+                               add "<li>"
+                               add c
+                               add "</li>"
+                       end
+                       add "</ul>"
+                       add "</article>"
+               end
+               if not props.is_empty then
+                       add "<article class='properties filterable'>"
+                       add "<h2>Properties</h2>"
+                       add "<ul>"
+                       for p in props do
+                               add "<li>"
+                               add p
+                               add "</li>"
+                       end
+                       add "</ul>"
+                       add "</article>"
+               end
+               add "</div>"
+       end
+end
+
+# Layout for Module page
+class TplModulePage
+       super Template
+
+       var title: nullable Streamable writable
+       var subtitle: nullable Streamable writable
+       var definition: nullable TplDefinition writable
+       var graph: nullable TplGraph writable
+       var intros = new Array[TplArticle]
+       var redefs = new Array[TplArticle]
+
+       redef fun rendering do
+               add "<div class='content'>"
+               if title != null then
+                       add "<h1>"
+                       add title.as(not null)
+                       add "</h1>"
+               end
+               if subtitle != null then
+                       add "<div class='subtitle info'>"
+                       add subtitle.as(not null)
+                       add "</div>"
+               end
+               if definition != null then add definition.as(not null)
+               if graph != null then add graph.as(not null)
+               if not intros.is_empty then
+                       add "<section class='classes'>"
+                       add "<h2 class='section-header'>Introduced classes</h2>"
+                       for intro in intros do add intro
+                       add "</section>"
+               end
+               if not redefs.is_empty then
+                       add "<section class='classes'>"
+                       add "<h2 class='section-header'>Refined classes</h2>"
+                       for rdef in redefs do add rdef
+                       add "</section>"
+               end
+               add "</div>"
+       end
+end
+
+# Layout for Class page
+class TplClassPage
+       super Template
+
+       var title: nullable Streamable writable
+       var subtitle: nullable Streamable writable
+       var definition: nullable TplDefinition writable
+       var graph: nullable TplGraph writable
+       var concerns: nullable TplConcernList writable
+       var types = new Array[TplArticle]
+       var inits = new Array[TplArticle]
+       var methods = new Array[Streamable]
+
+       redef fun rendering do
+               add "<div class='content'>"
+               if title != null then
+                       add "<h1>"
+                       add title.as(not null)
+                       add "</h1>"
+               end
+               if subtitle != null then
+                       add "<div class='subtitle info'>"
+                       add subtitle.as(not null)
+                       add "</div>"
+               end
+               if definition != null then add definition.as(not null)
+               if graph != null then add graph.as(not null)
+
+               if concerns != null then
+                       add "<section class='concerns'>"
+                       add "<h2 class='section-header'>Concerns</h2>"
+                       add concerns.as(not null)
+                       add "</section>"
+               end
+               if not types.is_empty then
+                       add "<section class='types'>"
+                       add"<h2>Virtual Types</h2>"
+                       for t in types do add t
+                       add "</section>"
+               end
+               if not inits.is_empty then
+                       add "<section class='constructors'>"
+                       add"<h2>Constructors</h2>"
+                       for i in inits do add i
+                       add "</section>"
+               end
+               if not methods.is_empty then
+                       add "<section class='methods'>"
+                       add"<h2>Methods</h2>"
+                       for m in methods do add m
+                       add "</section>"
+               end
+               add "</div>"
+       end
+end
+
+# layout parts
+
+# A HTML tag attribute
+#  `<tag attr="value">`
+class TagAttribute
+       super Template
+
+       var name: String
+       var value: nullable String
+
+       init(name: String, value: nullable String) do
+               self.name = name
+               self.value = value
+       end
+
+       redef fun rendering do
+               if value == null then
+                       add(" {name}")
+               else
+                       add(" {name}=\"{value}\"")
+               end
+       end
+end
+
+# JS Script template
+class TplScript
+       super Template
+
+       var attrs = new Array[TagAttribute]
+       var content: nullable Streamable writable
+
+       init do
+               attrs.add(new TagAttribute("type", "text/javascript"))
+       end
+
+       redef fun rendering do
+               add "<script"
+               for attr in attrs do add attr
+               add ">"
+               if content != null then add content.as(not null)
+               add "</script>"
+       end
+end
+
+# JS script for Piwik Tracker
+class TplPiwikScript
+       super TplScript
+
+       var tracker_url: String
+       var site_id: String
+
+       init(tracker_url, site_id: String) do
+               super
+               self.tracker_url = tracker_url
+               self.site_id = site_id
+       end
+
+       redef fun rendering do
+               var tpl = new Template
+               tpl.add "<!-- Piwik -->"
+               tpl.add "var _paq = _paq || [];"
+               tpl.add " _paq.push([\"trackPageView\"]);"
+               tpl.add " _paq.push([\"enableLinkTracking\"]);"
+               tpl.add "(function() \{"
+               tpl.add " var u=((\"https:\" == document.location.protocol) ? \"https\" : \"http\") + \"://{tracker_url}\";"
+               tpl.add " _paq.push([\"setTrackerUrl\", u+\"piwik.php\"]);"
+               tpl.add " _paq.push([\"setSiteId\", \"{site_id}\"]);"
+               tpl.add " var d=document, g=d.createElement(\"script\"), s=d.getElementsByTagName(\"script\")[0]; g.type=\"text/javascript\";"
+               tpl.add " g.defer=true; g.async=true; g.src=u+\"piwik.js\"; s.parentNode.insertBefore(g,s);"
+               tpl.add "\})();"
+               content = tpl
+               super
+       end
+end
+
+# Graph image with clicable map
+class TplGraph
+       super Template
+
+       var name: String
+       var alt: String
+       var map: String
+
+       init(name, alt, map: String) do
+               self.name = name
+               self.alt = alt
+               self.map = map
+       end
+
+       redef fun rendering do
+               add "<article class='graph'>"
+               add "<img src='{name}.png' usemap='#{name}' style='margin:auto' alt='{alt}'/>"
+               add "</article>"
+               add map
+       end
+end
+
+# A page article (used for module, class, prop description)
+class TplArticle
+       super Template
+
+       var id: String writable
+       var classes = new HashSet[String]
+       var title: Template writable
+       var subtitle: Template writable
+       var content: nullable Template writable
+
+       redef fun rendering do
+               add "<article class='{classes.join(" ")}' id='{id}'>"
+               add "<h3 class='signature'>"
+               add title
+               add "</h3>"
+               add "<div class='info'>"
+               add subtitle
+               add "</div>"
+               if content != null then
+                       add content.as(not null)
+               end
+               add "</article>"
+       end
+end
+
+# A module / class / prop definition
+# Contains:
+# * namespace of the definition
+# * comment
+# * link to location
+class TplDefinition
+       super Template
+
+       var comment: nullable TplComment writable
+       var namespace: Streamable writable
+       var location: nullable Streamable writable
+       var github_area: nullable TplGithubArea writable
+
+       redef fun rendering do
+               add "<div class='description'>"
+               if github_area != null then
+                       add github_area.as(not null)
+               end
+               if comment == null then
+                       add "<p class='info inheritance'>"
+                       add "<span class=\"noComment\">no comment for </span>"
+               else
+                       add comment.as(not null)
+                       add "<p class='info inheritance'>"
+               end
+               add "definition in "
+               add namespace
+               if location != null then
+                       add " "
+                       add location.as(not null)
+               end
+               add "</p>"
+               add "</div>"
+       end
+end
+
+# Textarea used by Github comment edition plugin to store comments
+class TplGithubArea
+       super Template
+
+       var raw_comment: String writable
+       var raw_namespace: String writable
+       var location: String writable
+
+       init(raw_comment, raw_namespace, location: String) do
+               self.raw_comment = raw_comment
+               self.raw_namespace = raw_namespace
+               self.location = location
+       end
+
+       redef fun rendering do
+               add "<textarea"
+               add " class='baseComment'"
+               add " data-comment-namespace='{raw_namespace}'"
+               add " data-comment-location='{location}'>"
+               add raw_comment
+               add "</textarea>"
+       end
+end
+
+# Comment box
+class TplComment
+       super Template
+
+       var comment: Streamable writable
+
+       init(comment: Streamable) do self.comment = comment
+
+       redef fun rendering do
+               add "<div class='comment'>"
+               add comment
+               add "</div>"
+       end
+end
+
+# Comment box (for synopsys)
+class TplShortComment
+       super TplComment
+
+       redef fun rendering do
+               add "<div class='comment'>"
+               add "<div class='nitdoc'>"
+               add comment
+               add "</div>"
+               add "</div>"
+       end
+end
+
+# A html link (with optional title)
+class TplLink
+       super Template
+
+       var href: String writable
+       var text: String writable
+       var title: nullable String writable
+
+       redef fun rendering do
+               add "<a href=\""
+               add href
+               add "\""
+               if title != null then
+                       add " title=\""
+                       add title.as(not null)
+                       add "\""
+               end
+               add ">"
+               add text
+               add "</a>"
+       end
+end
+
+# Element to display in concerns list
+class TplConcernElt
+       super Template
+end
+
+# List of concerns
+class TplConcernList
+       super TplConcernElt
+
+       var elts = new Array[TplConcernElt]
+
+       redef fun rendering do
+               add "<ul>"
+               for elt in elts do
+                       add elt
+               end
+               add "</ul>"
+       end
+end
+
+# Element of a list of concerns
+class TplConcernListElt
+       super TplConcernElt
+
+       var anchor: String writable
+       var name: String writable
+       var comment: nullable String writable
+
+       redef fun rendering do
+               add "<li>"
+               add "<a href=\"{anchor}\">{name}</a>"
+               if comment != null then
+                       add ": {comment.as(not null)}"
+               end
+               add "</li>"
+       end
+end
+
+# Section for topconcern
+class TplTopConcern
+       super Template
+
+       var anchor: String writable
+       var concern: TplLink writable
+
+       redef fun rendering do
+               add "<a id=\"{anchor}\"></a>"
+               add "<h3 class=\"concern-toplevel\">Methods refined in "
+               add concern
+               add "</h3>"
+       end
+end
+
+# Section for subconcern
+class TplConcern
+       super Template
+
+       var anchor: String writable
+       var concern: TplLink writable
+       var comment: nullable String writable
+
+       redef fun rendering do
+               add "<a id=\"{anchor}\"></a>"
+               add "<p class=\"concern-doc\">"
+               add concern
+               if comment != null then
+                       add ": "
+                       add comment.as(not null)
+               end
+               add "</p>"
+       end
+end
index 91f00b8..56c313f 100644 (file)
@@ -19,99 +19,54 @@ module nitdoc
 import model_utils
 import modelize_property
 import markdown
+import doc_template
 
 # The NitdocContext contains all the knowledge used for doc generation
 class NitdocContext
 
        private var toolcontext = new ToolContext
-       private var model: Model
        private var mbuilder: ModelBuilder
        private var mainmodule: MModule
-       private var class_hierarchy: POSet[MClass]
-       private var arguments: Array[String]
-       private var output_dir: nullable String
-       private var dot_dir: nullable String
-       private var share_dir: nullable String
-       private var source: nullable String
+       private var output_dir: String
        private var min_visibility: MVisibility
 
-       private var github_upstream: nullable String
-       private var github_basesha1: nullable String
-       private var github_gitdir: nullable String
+       private var opt_dir = new OptionString("output directory", "-d", "--dir")
+       private var opt_source = new OptionString("link for source (%f for filename, %l for first line, %L for last line)", "--source")
+       private var opt_sharedir = new OptionString("directory containing nitdoc assets", "--sharedir")
+       private var opt_shareurl = new OptionString("use shareurl instead of copy shared files", "--shareurl")
+       private var opt_nodot = new OptionBool("do not generate graphes with graphviz", "--no-dot")
+       private var opt_private = new OptionBool("also generate private API", "--private")
 
-       private var opt_dir = new OptionString("Directory where doc is generated", "-d", "--dir")
-       private var opt_source = new OptionString("What link for source (%f for filename, %l for first line, %L for last line)", "--source")
-       private var opt_sharedir = new OptionString("Directory containing the nitdoc files", "--sharedir")
-       private var opt_shareurl = new OptionString("Do not copy shared files, link JS and CSS file to share url instead", "--shareurl")
-       private var opt_nodot = new OptionBool("Do not generate graphes with graphviz", "--no-dot")
-       private var opt_private: OptionBool = new OptionBool("Generate the private API", "--private")
+       private var opt_custom_title = new OptionString("custom title for homepage", "--custom-title")
+       private var opt_custom_menu = new OptionString("custom items added in top menu (each item must be enclosed in 'li' tags)", "--custom-menu-items")
+       private var opt_custom_intro = new OptionString("custom intro text for homepage", "--custom-overview-text")
+       private var opt_custom_footer = new OptionString("custom footer text", "--custom-footer-text")
 
-       private var opt_custom_title: OptionString = new OptionString("Title displayed in the top of the Overview page and as suffix of all page names", "--custom-title")
-       private var opt_custom_menu_items: OptionString = new OptionString("Items displayed in menu before the 'Overview' item (Each item must be enclosed in 'li' tags)", "--custom-menu-items")
-       private var opt_custom_overview_text: OptionString = new OptionString("Text displayed as introduction of Overview page before the modules list", "--custom-overview-text")
-       private var opt_custom_footer_text: OptionString = new OptionString("Text displayed as footer of all pages", "--custom-footer-text")
+       private var opt_github_upstream = new OptionString("Git branch where edited commits will be pulled into (ex: user:repo:branch)", "--github-upstream")
+       private var opt_github_base_sha1 = new OptionString("Git sha1 of base commit used to create pull request", "--github-base-sha1")
+       private var opt_github_gitdir = new OptionString("Git working directory used to resolve path name (ex: /home/me/myproject/)", "--github-gitdir")
 
-       private var opt_github_upstream: OptionString = new OptionString("The branch where edited commits will be pulled into (ex: user:repo:branch)", "--github-upstream")
-       private var opt_github_base_sha1: OptionString = new OptionString("The sha1 of the base commit used to create pull request", "--github-base-sha1")
-       private var opt_github_gitdir: OptionString = new OptionString("The git working directory used to resolve path name (ex: /home/me/myproject/)", "--github-gitdir")
-
-       private var opt_piwik_tracker: OptionString = new OptionString("The URL of the Piwik tracker (ex: nitlanguage.org/piwik/)", "--piwik-tracker")
-       private var opt_piwik_site_id: OptionString = new OptionString("The site ID in Piwik tracker", "--piwik-site-id")
+       private var opt_piwik_tracker = new OptionString("Piwik tracker URL (ex: nitlanguage.org/piwik/)", "--piwik-tracker")
+       private var opt_piwik_site_id = new OptionString("Piwik site ID", "--piwik-site-id")
 
        init do
-               toolcontext.option_context.add_option(opt_dir)
-               toolcontext.option_context.add_option(opt_source)
-               toolcontext.option_context.add_option(opt_sharedir, opt_shareurl)
-               toolcontext.option_context.add_option(opt_nodot)
-               toolcontext.option_context.add_option(opt_private)
-               toolcontext.option_context.add_option(opt_custom_title)
-               toolcontext.option_context.add_option(opt_custom_footer_text)
-               toolcontext.option_context.add_option(opt_custom_overview_text)
-               toolcontext.option_context.add_option(opt_custom_menu_items)
-               toolcontext.option_context.add_option(opt_github_upstream)
-               toolcontext.option_context.add_option(opt_github_base_sha1)
-               toolcontext.option_context.add_option(opt_github_gitdir)
-               toolcontext.option_context.add_option(opt_piwik_tracker)
-               toolcontext.option_context.add_option(opt_piwik_site_id)
-               toolcontext.tooldescription = "Usage: nitdoc [OPTION]... <file.nit>...\nGenerates HTML pages of API documentation from Nit source files."
+               var opts = toolcontext.option_context
+               opts.add_option(opt_dir, opt_source, opt_sharedir, opt_shareurl, opt_nodot, opt_private)
+               opts.add_option(opt_custom_title, opt_custom_footer, opt_custom_intro, opt_custom_menu)
+               opts.add_option(opt_github_upstream, opt_github_base_sha1, opt_github_gitdir)
+               opts.add_option(opt_piwik_tracker, opt_piwik_site_id)
+
+               var tpl = new Template
+               tpl.add "Usage: nitdoc [OPTION]... <file.nit>...\n"
+               tpl.add "Generates HTML pages of API documentation from Nit source files."
+               toolcontext.tooldescription = tpl.write_to_string
                toolcontext.process_options(args)
-               self.arguments = toolcontext.option_context.rest
 
                self.process_options
-
-               model = new Model
-               mbuilder = new ModelBuilder(model, toolcontext)
-               # Here we load and process all modules passed on the command line
-               var mmodules = mbuilder.parse(arguments)
-               if mmodules.is_empty then return
-               mbuilder.run_phases
-
-               if mmodules.length == 1 then
-                       mainmodule = mmodules.first
-               else
-                       # We need a main module, so we build it by importing all modules
-                       mainmodule = new MModule(model, null, "<main>", new Location(null, 0, 0, 0, 0))
-                       mainmodule.set_imported_mmodules(mmodules)
-               end
-               self.class_hierarchy = mainmodule.flatten_mclass_hierarchy
+               self.parse(toolcontext.option_context.rest)
        end
 
        private fun process_options do
-               if opt_dir.value != null then
-                       output_dir = opt_dir.value
-               else
-                       output_dir = "doc"
-               end
-               if opt_sharedir.value != null then
-                       share_dir = opt_sharedir.value
-               else
-                       var dir = toolcontext.nit_dir
-                       share_dir = "{dir}/share/nitdoc"
-                       if dir == null or not share_dir.file_exists then
-                               print "Error: Cannot locate nitdoc share files. Uses --sharedir or envvar NIT_DIR"
-                               abort
-                       end
-               end
                if opt_private.value then
                        min_visibility = none_visibility
                else
@@ -124,25 +79,26 @@ class NitdocContext
                        if gh_upstream == null or gh_base_sha == null or gh_gitdir == null then
                                print "Error: Options {opt_github_upstream.names.first}, {opt_github_base_sha1.names.first} and {opt_github_gitdir.names.first} are required to enable the GitHub plugin"
                                abort
-                       else
-                               self.github_upstream = gh_upstream
-                               self.github_basesha1 = gh_base_sha
-                               self.github_gitdir = gh_gitdir
                        end
                end
-               source = opt_source.value
        end
 
-       fun generate_nitdoc do
-               # Create destination dir if it's necessary
-               if not output_dir.file_exists then output_dir.mkdir
-               if opt_shareurl.value == null then
-                       sys.system("cp -r {share_dir.to_s}/* {output_dir.to_s}/")
+       private fun parse(arguments: Array[String]) do
+               var model = new Model
+               mbuilder = new ModelBuilder(model, toolcontext)
+               var mmodules = mbuilder.parse(arguments)
+               if mmodules.is_empty then return
+               mbuilder.run_phases
+               if mmodules.length == 1 then
+                       mainmodule = mmodules.first
                else
-                       sys.system("cp -r {share_dir.to_s}/resources/ {output_dir.to_s}/resources/")
+                       mainmodule = new MModule(model, null, "<main>", new Location(null, 0, 0, 0, 0))
+                       mainmodule.set_imported_mmodules(mmodules)
                end
-               self.dot_dir = null
-               if not opt_nodot.value then self.dot_dir = output_dir.to_s
+       end
+
+       private fun generate_nitdoc do
+               init_output_dir
                overview
                search
                modules
@@ -150,220 +106,313 @@ class NitdocContext
                quicksearch_list
        end
 
+       private fun init_output_dir do
+               # location output dir
+               var output_dir = opt_dir.value
+               if output_dir == null then
+                       output_dir = "doc"
+               end
+               self.output_dir = output_dir
+               # create destination dir if it's necessary
+               if not output_dir.file_exists then output_dir.mkdir
+               # locate share dir
+               var sharedir = opt_sharedir.value
+               if sharedir == null then
+                       var dir = toolcontext.nit_dir
+                       if dir == null then
+                               print "Error: Cannot locate nitdoc share files. Uses --sharedir or envvar NIT_DIR"
+                               abort
+                       end
+                       sharedir = "{dir}/share/nitdoc"
+                       if not sharedir.file_exists then
+                               print "Error: Cannot locate nitdoc share files. Uses --sharedir or envvar NIT_DIR"
+                               abort
+                       end
+               end
+               # copy shared files
+               if opt_shareurl.value == null then
+                       sys.system("cp -r {sharedir.to_s}/* {output_dir.to_s}/")
+               else
+                       sys.system("cp -r {sharedir.to_s}/resources/ {output_dir.to_s}/resources/")
+               end
+
+       end
+
        private fun overview do
                var overviewpage = new NitdocOverview(self)
-               overviewpage.save("{output_dir.to_s}/index.html")
+               overviewpage.render.write_to_file("{output_dir.to_s}/index.html")
        end
 
        private fun search do
                var searchpage = new NitdocSearch(self)
-               searchpage.save("{output_dir.to_s}/search.html")
+               searchpage.render.write_to_file("{output_dir.to_s}/search.html")
        end
 
        private fun modules do
-               for mmodule in model.mmodules do
+               for mmodule in mbuilder.model.mmodules do
                        if mmodule.name == "<main>" then continue
                        var modulepage = new NitdocModule(mmodule, self)
-                       modulepage.save("{output_dir.to_s}/{mmodule.url}")
+                       modulepage.render.write_to_file("{output_dir.to_s}/{mmodule.nitdoc_url}")
                end
        end
 
        private fun classes do
                for mclass in mbuilder.model.mclasses do
                        var classpage = new NitdocClass(mclass, self)
-                       classpage.save("{output_dir.to_s}/{mclass.url}")
+                       classpage.render.write_to_file("{output_dir.to_s}/{mclass.nitdoc_url}")
                end
        end
 
        private fun quicksearch_list do
-               var file = new OFStream.open("{output_dir.to_s}/quicksearch-list.js")
-               file.write("var nitdocQuickSearchRawList = \{ ")
-               for mmodule in model.mmodules do
+               var quicksearch = new QuickSearch(self)
+               quicksearch.render.write_to_file("{output_dir.to_s}/quicksearch-list.js")
+       end
+end
+
+# Nitdoc QuickSearch list generator
+#
+# Create a JSON object containing links to:
+#  * modules
+#  * mclasses
+#  * mpropdefs
+# All entities are grouped by name to make the research easier.
+class QuickSearch
+
+       private var mmodules = new HashSet[MModule]
+       private var mclasses = new HashSet[MClass]
+       private var mpropdefs = new HashMap[String, Set[MPropDef]]
+
+       init(ctx: NitdocContext) do
+               for mmodule in ctx.mbuilder.model.mmodules do
                        if mmodule.name == "<main>" then continue
-                       file.write("\"{mmodule.name}\": [")
-                       file.write("\{txt: \"{mmodule.full_name}\", url:\"{mmodule.url}\" \},")
-                       file.write("],")
-               end
-               for mclass in model.mclasses do
-                       if mclass.visibility < min_visibility then continue
-                       file.write("\"{mclass.name}\": [")
-                       file.write("\{txt: \"{mclass.full_name}\", url:\"{mclass.url}\" \},")
-                       file.write("],")
-               end
-               var name2mprops = new HashMap[String, Set[MPropDef]]
-               for mproperty in model.mproperties do
-                       if mproperty.visibility < min_visibility then continue
-                       if mproperty isa MAttribute then continue
-                       if not name2mprops.has_key(mproperty.name) then name2mprops[mproperty.name] = new HashSet[MPropDef]
-                       name2mprops[mproperty.name].add_all(mproperty.mpropdefs)
+                       mmodules.add mmodule
+               end
+               for mclass in ctx.mbuilder.model.mclasses do
+                       if mclass.visibility < ctx.min_visibility then continue
+                       mclasses.add mclass
                end
-               for mproperty, mpropdefs in name2mprops do
-                       file.write("\"{mproperty}\": [")
-                       for mpropdef in mpropdefs do
-                               file.write("\{txt: \"{mpropdef.full_name}\", url:\"{mpropdef.url}\" \},")
+               for mproperty in ctx.mbuilder.model.mproperties do
+                       if mproperty.visibility < ctx.min_visibility then continue
+                       if mproperty isa MAttribute then continue
+                       if not mpropdefs.has_key(mproperty.name) then
+                               mpropdefs[mproperty.name] = new HashSet[MPropDef]
                        end
-                       file.write("],")
+                       mpropdefs[mproperty.name].add_all(mproperty.mpropdefs)
                end
-               file.write(" \};")
-               file.close
        end
 
+       fun render: Template do
+               var tpl = new Template
+               tpl.add "var nitdocQuickSearchRawList=\{ "
+               for mmodule in mmodules do
+                       tpl.add "\"{mmodule.name}\":["
+                       tpl.add "\{txt:\"{mmodule.full_name}\",url:\"{mmodule.nitdoc_url}\"\},"
+                       tpl.add "],"
+               end
+               for mclass in mclasses do
+                       var full_name = mclass.intro.mmodule.full_name
+                       tpl.add "\"{mclass.name}\":["
+                       tpl.add "\{txt:\"{full_name}\",url:\"{mclass.nitdoc_url}\"\},"
+                       tpl.add "],"
+               end
+               for mproperty, mprops in mpropdefs do
+                       tpl.add "\"{mproperty}\":["
+                       for mpropdef in mprops do
+                               var full_name = mpropdef.mclassdef.mclass.full_name
+                               tpl.add "\{txt:\"{full_name}\",url:\"{mpropdef.nitdoc_url}\"\},"
+                       end
+                       tpl.add "],"
+               end
+               tpl.add " \};"
+               return tpl
+       end
 end
 
 # Nitdoc base page
+# Define page structure and properties
 abstract class NitdocPage
 
-       var ctx: NitdocContext
-       var shareurl = "."
+       private var ctx: NitdocContext
+       private var shareurl = "."
 
        init(ctx: NitdocContext) do
                self.ctx = ctx
                if ctx.opt_shareurl.value != null then shareurl = ctx.opt_shareurl.value.as(not null)
        end
 
-       protected fun head do
-               append("<meta charset='utf-8'/>")
-               append("<link rel='stylesheet' href='{shareurl}/css/main.css' type='text/css'/>")
-               append("<link rel='stylesheet' href='{shareurl}/css/Nitdoc.UI.css' type='text/css''/>")
-               append("<link rel='stylesheet' href='{shareurl}/css/Nitdoc.QuickSearch.css' type='text/css'/>")
-               append("<link rel='stylesheet' href='{shareurl}/css/Nitdoc.GitHub.css' type='text/css'/>")
-               append("<link rel='stylesheet' href='{shareurl}/css/Nitdoc.ModalBox.css' type='text/css'/>")
-               var title = ""
-               if ctx.opt_custom_title.value != null then
-                       title = " | {ctx.opt_custom_title.value.to_s}"
+       # Render the page as a html template
+       fun render: Template do
+               var tpl = new TplNitdocPage
+               tpl.head = tpl_head
+               tpl.topmenu = tpl_topmenu
+               tpl.sidebar = tpl_sidebar
+               tpl.content = tpl_content
+               tpl.footer = tpl_footer
+
+               tpl.body_attrs.add(new TagAttribute("data-bootstrap-share", shareurl))
+               if ctx.opt_github_upstream.value != null and ctx.opt_github_base_sha1.value != null then
+                       tpl.body_attrs.add(new TagAttribute("data-github-upstream", ctx.opt_github_upstream.value))
+                       tpl.body_attrs.add(new TagAttribute("data-github-base-sha1", ctx.opt_github_base_sha1.value))
                end
-               append("<title>{self.title}{title}</title>")
+               var requirejs = new TplScript
+               requirejs.attrs.add(new TagAttribute("data-main", "{shareurl}/js/nitdoc"))
+               requirejs.attrs.add(new TagAttribute("src", "{shareurl}/js/lib/require.js"))
+               tpl.scripts.add requirejs
+
+               # piwik tracking
+               var tracker_url = ctx.opt_piwik_tracker.value
+               var site_id = ctx.opt_piwik_site_id.value
+               if tracker_url != null and site_id != null then
+                       tpl.scripts.add new TplPiwikScript(tracker_url, site_id)
+               end
+               return tpl
        end
 
-       protected fun menu do
-               if ctx.opt_custom_menu_items.value != null then
-                       append(ctx.opt_custom_menu_items.value.to_s)
+       # Page title string
+       fun tpl_title: String do
+               if ctx.opt_custom_title.value != null then
+                       return ctx.opt_custom_title.value.to_s
+               else
+                       return "Nitdoc"
                end
        end
 
-       protected fun title: String is abstract
+       # Page <head> template
+       fun tpl_head: TplHead do return new TplHead(tpl_title, shareurl)
 
-       protected fun header do
-               append("<header>")
-               append("<nav class='main'>")
-               append("<ul>")
-               menu
-               append("</ul>")
-               append("</nav>")
-               append("</header>")
+       # Top menu template
+       fun tpl_topmenu: TplTopMenu do
+               var topmenu = new TplTopMenu
+               var custom_elt = ctx.opt_custom_menu.value
+               if custom_elt != null then topmenu.add_raw(custom_elt)
+               return topmenu
        end
 
-       protected fun content is abstract
+       # Page sidebar template
+       # return null if no sidebar for this page
+       fun tpl_sidebar: nullable TplSidebar do return null
 
-       protected fun footer do
-               if ctx.opt_custom_footer_text.value != null then
-                       append("<footer>{ctx.opt_custom_footer_text.value.to_s}</footer>")
+       # Page content template
+       fun tpl_content: Template is abstract
+
+       # Page footer template
+       # return null if no footer for this page
+       fun tpl_footer: nullable TplFooter do
+               if ctx.opt_custom_footer.value != null then
+                       return new TplFooter(ctx.opt_custom_footer.value.to_s)
                end
+               return null
        end
 
-       # Generate a clickable graphviz image using a dot content
-       protected fun generate_dot(dot: String, name: String, alt: String) do
-               var output_dir = ctx.dot_dir
-               if output_dir == null then return
+       # Clickable graphviz image using dot format
+       # return null if no graph for this page
+       fun tpl_graph(dot: FlatBuffer, name: String, alt: String): nullable TplGraph do
+               if ctx.opt_nodot.value then return null
+               var output_dir = ctx.output_dir
                var file = new OFStream.open("{output_dir}/{name}.dot")
                file.write(dot)
                file.close
                sys.system("\{ test -f {output_dir}/{name}.png && test -f {output_dir}/{name}.s.dot && diff {output_dir}/{name}.dot {output_dir}/{name}.s.dot >/dev/null 2>&1 ; \} || \{ cp {output_dir}/{name}.dot {output_dir}/{name}.s.dot && dot -Tpng -o{output_dir}/{name}.png -Tcmapx -o{output_dir}/{name}.map {output_dir}/{name}.s.dot ; \}")
-               append("<article class='graph'>")
-               append("<img src='{name}.png' usemap='#{name}' style='margin:auto' alt='{alt}'/>")
-               append("</article>")
                var fmap = new IFStream.open("{output_dir}/{name}.map")
-               append(fmap.read_all)
+               var map = fmap.read_all
                fmap.close
+               return new TplGraph(name, alt, map)
        end
 
-       # Add a (source) link for a given location
-       protected fun show_source(l: Location): String
+       # A (source) link template for a given location
+       fun tpl_showsource(location: nullable Location): nullable String
        do
-               var source = ctx.source
-               if source == null then
-                       return "({l.file.filename.simplify_path})"
+               if location == null then return null
+               var source = ctx.opt_source.value
+               if source == null then return "({location.file.filename.simplify_path})"
+               # THIS IS JUST UGLY ! (but there is no replace yet)
+               var x = source.split_with("%f")
+               source = x.join(location.file.filename.simplify_path)
+               x = source.split_with("%l")
+               source = x.join(location.line_start.to_s)
+               x = source.split_with("%L")
+               source = x.join(location.line_end.to_s)
+               source = source.simplify_path
+               return " (<a target='_blank' title='Show source' href=\"{source.to_s}\">source</a>)"
+       end
+
+       # MClassDef description template
+       fun tpl_mclassdef_article(mclassdef: MClassDef): TplArticle do
+               var article = mclassdef.tpl_article
+               article.content = new Template
+               if not mclassdef.is_intro then
+                       # add intro synopsys
+                       var intro = mclassdef.mclass.intro
+                       var location = intro.location
+                       var sourcelink = tpl_showsource(location)
+                       var intro_def = intro.tpl_short_definition
+                       intro_def.location = sourcelink
+                       intro_def.github_area = tpl_github(intro.full_namespace, intro.mdoc, location)
+                       article.content.add intro_def
+               end
+               # add mclassdef full description
+               var location = mclassdef.location
+               var sourcelink = tpl_showsource(location)
+               var prop_def = mclassdef.tpl_definition
+               prop_def.location = sourcelink
+               prop_def.github_area = tpl_github(mclassdef.full_namespace, mclassdef.mdoc, location)
+               article.content.add prop_def
+               return article
+       end
+
+       # MPropDef description template
+       fun tpl_mpropdef_article(mpropdef: MPropDef): TplArticle do
+               var article = mpropdef.tpl_article
+               article.content = new Template
+               if not mpropdef.is_intro then
+                       # add intro synopsys
+                       var intro = mpropdef.mproperty.intro
+                       var location = intro.location
+                       var sourcelink = tpl_showsource(location)
+                       var intro_def = intro.tpl_short_definition
+                       intro_def.location = sourcelink
+                       intro_def.github_area = tpl_github(intro.full_namespace, intro.mdoc, location)
+                       article.content.add intro_def
+               end
+               # add mpropdef description
+               var location = mpropdef.location
+               var sourcelink = tpl_showsource(location)
+               var prop_def = mpropdef.tpl_definition
+               prop_def.location = sourcelink
+               prop_def.github_area = tpl_github(mpropdef.full_namespace, mpropdef.mdoc, location)
+               article.content.add prop_def
+               return article
+       end
+
+       # Github area (for Github comment edition plugin)
+       # return null if no github plugin for this page
+       fun tpl_github(namespace: String, mdoc: nullable MDoc, loc: nullable Location): nullable TplGithubArea do
+               if loc == null then return null
+               if ctx.opt_github_gitdir.value == null then return null
+               var gitdir = ctx.opt_github_gitdir.value.as(not null)
+               var location = loc.github(gitdir)
+               var comment: String
+               if mdoc != null then
+                       comment = mdoc.full_comment
                else
-                       # THIS IS JUST UGLY ! (but there is no replace yet)
-                       var x = source.split_with("%f")
-                       source = x.join(l.file.filename.simplify_path)
-                       x = source.split_with("%l")
-                       source = x.join(l.line_start.to_s)
-                       x = source.split_with("%L")
-                       source = x.join(l.line_end.to_s)
-                       source = source.simplify_path
-                       return " (<a target='_blank' title='Show source' href=\"{source.to_s}\">source</a>)"
-               end
-       end
-
-       # Render the page as a html string
-       protected fun render do
-               append("<!DOCTYPE html>")
-               append("<head>")
-               head
-               append("</head>")
-               append("<body")
-               append(" data-bootstrap-share='{shareurl}'")
-               if ctx.opt_github_upstream.value != null and ctx.opt_github_base_sha1.value != null then
-                       append(" data-github-upstream='{ctx.opt_github_upstream.value.as(not null)}'")
-                       append(" data-github-base-sha1='{ctx.opt_github_base_sha1.value.as(not null)}'")
-               end
-               append(">")
-               header
-               var footed = ""
-               if ctx.opt_custom_footer_text.value != null then footed = "footed"
-               append("<div class='page {footed}'>")
-               content
-               append("</div>")
-               footer
-               append("<script data-main=\"{shareurl}/js/nitdoc\" src=\"{shareurl}/js/lib/require.js\"></script>")
-
-               # piwik tracking
-               var tracker_url = ctx.opt_piwik_tracker.value
-               var site_id = ctx.opt_piwik_site_id.value
-               if tracker_url != null and site_id != null then
-                       append("<!-- Piwik -->")
-                       append("<script type=\"text/javascript\">")
-                       append("  var _paq = _paq || [];")
-                       append("  _paq.push([\"trackPageView\"]);")
-                       append("  _paq.push([\"enableLinkTracking\"]);")
-                       append("  (function() \{")
-                       append("    var u=((\"https:\" == document.location.protocol) ? \"https\" : \"http\") + \"://{tracker_url}\";")
-                       append("    _paq.push([\"setTrackerUrl\", u+\"piwik.php\"]);")
-                       append("    _paq.push([\"setSiteId\", \"{site_id}\"]);")
-                       append("    var d=document, g=d.createElement(\"script\"), s=d.getElementsByTagName(\"script\")[0]; g.type=\"text/javascript\";")
-                       append("    g.defer=true; g.async=true; g.src=u+\"piwik.js\"; s.parentNode.insertBefore(g,s);")
-                       append("  \})();")
-                       append(" </script>")
-                       append("<!-- End Piwik Code -->")
-               end
-               append("</body>")
-       end
-
-       # Append a string to the page
-       fun append(s: String) do out.write(s)
-
-       # Save html page in the specified file
-       fun save(file: String) do
-               self.out = new OFStream.open(file)
-               render
-               self.out.close
-       end
-       private var out: nullable OFStream
+                       comment = ""
+               end
+               return new TplGithubArea(comment, namespace, location)
+       end
 end
 
 # The overview page
+# Display a list of modules contained in program
 class NitdocOverview
        super NitdocPage
-       private var mbuilder: ModelBuilder
+
        private var mmodules = new Array[MModule]
 
        init(ctx: NitdocContext) do
                super(ctx)
-               self.mbuilder = ctx.mbuilder
                # get modules
                var mmodules = new HashSet[MModule]
-               for mmodule in mbuilder.model.mmodule_importation_hierarchy do
+               for mmodule in ctx.mbuilder.model.mmodule_importation_hierarchy do
                        if mmodule.name == "<main>" then continue
                        var owner = mmodule.public_owner
                        if owner != null then
@@ -378,46 +427,44 @@ class NitdocOverview
                sorter.sort(self.mmodules)
        end
 
-       redef fun title do return "Overview"
+       redef fun tpl_title do return "Overview | {super}"
 
-       redef fun menu do
-               super
-               append("<li class='current'>Overview</li>")
-               append("<li><a href='search.html'>Search</a></li>")
+       redef fun tpl_topmenu do
+               var topmenu = super
+               topmenu.add_elt("#", "Overview", true)
+               topmenu.add_elt("search.html", "Search", false)
+               return topmenu
        end
 
-       redef fun content do
-               append("<div class='content fullpage'>")
-               var title = "Overview"
+       redef fun tpl_content do
+               var tpl = new TplOverviewPage
+               # title
                if ctx.opt_custom_title.value != null then
-                       title = ctx.opt_custom_title.value.to_s
+                       tpl.title = ctx.opt_custom_title.value.to_s
+               else
+                       tpl.title = "Overview"
                end
-               append("<h1>{title}</h1>")
-               var text = ""
-               if ctx.opt_custom_overview_text.value != null then
-                       text = ctx.opt_custom_overview_text.value.to_s
+               # intro text
+               if ctx.opt_custom_intro.value != null then
+                       tpl.text = ctx.opt_custom_intro.value.to_s
                end
-               append("<article class='overview'>{text}</article>")
-               append("<article class='overview'>")
                # module list
-               append("<h2>Modules</h2>")
-               append("<ul>")
                for mmodule in mmodules do
-                       if mbuilder.mmodule2nmodule.has_key(mmodule) then
-                               var amodule = mbuilder.mmodule2nmodule[mmodule]
-                               append("<li>")
-                               mmodule.html_link(self)
-                               append("&nbsp;{amodule.short_comment}</li>")
+                       if mmodule.mdoc != null then
+                               var mtpl = new Template
+                               mtpl.add mmodule.tpl_link
+                               mtpl.add "&nbsp;"
+                               mtpl.add mmodule.mdoc.short_comment
+                               tpl.modules.add mtpl
                        end
                end
-               append("</ul>")
                # module graph
-               process_generate_dot
-               append("</article>")
-               append("</div>")
+               tpl.graph = tpl_dot
+               return tpl
        end
 
-       private fun process_generate_dot do
+       # Genrate dot and template for module hierarchy
+       fun tpl_dot: nullable TplGraph do
                # build poset with public owners
                var poset = new POSet[MModule]
                for mmodule in mmodules do
@@ -434,187 +481,170 @@ class NitdocOverview
                var op = new FlatBuffer
                op.append("digraph dep \{ rankdir=BT; node[shape=none,margin=0,width=0,height=0,fontsize=10]; edge[dir=none,color=gray]; ranksep=0.2; nodesep=0.1;\n")
                for mmodule in poset do
-                       op.append("\"{mmodule.name}\"[URL=\"{mmodule.url}\"];\n")
+                       op.append("\"{mmodule.name}\"[URL=\"{mmodule.nitdoc_url}\"];\n")
                        for omodule in poset[mmodule].direct_greaters do
                                op.append("\"{mmodule.name}\"->\"{omodule.name}\";\n")
                        end
                end
                op.append("\}\n")
-               generate_dot(op.to_s, "dep", "Modules hierarchy")
+               return tpl_graph(op, "dep", "Modules hierarchy")
        end
 end
 
 # The search page
+# Display a list of modules, classes and properties
 class NitdocSearch
        super NitdocPage
 
-       init(ctx: NitdocContext) do
-               super(ctx)
-       end
+       init(ctx: NitdocContext) do super(ctx)
 
-       redef fun title do return "Search"
+       redef fun tpl_title do return "Search | {super}"
 
-       redef fun menu do
-               super
-               append("<li><a href='index.html'>Overview</a></li>")
-               append("<li class='current'>Search</li>")
+       redef fun tpl_topmenu do
+               var topmenu = super
+               topmenu.add_elt("index.html", "Overview", false)
+               topmenu.add_elt("#", "Search", true)
+               return topmenu
        end
 
-       redef fun content do
-               append("<div class='content fullpage'>")
-               append("<h1>{title}</h1>")
-               module_column
-               classes_column
-               properties_column
-               append("</div>")
+       redef fun tpl_content do
+               var tpl = new TplSearchPage
+               # title
+               tpl.title = "Search"
+               # modules list
+               for mmodule in modules_list do
+                       tpl.modules.add mmodule.tpl_link
+               end
+               # classes list
+               for mclass in classes_list do
+                       tpl.classes.add mclass.tpl_link
+               end
+               # properties list
+               for mproperty in mprops_list do
+                       var m = new Template
+                       m.add mproperty.intro.tpl_link
+                       m.add " ("
+                       m.add mproperty.intro.mclassdef.mclass.tpl_link
+                       m.add ")"
+                       tpl.props.add m
+               end
+               return tpl
        end
 
-       # Add to content modules column
-       private fun module_column do
-               var sorted = ctx.mbuilder.model.mmodule_importation_hierarchy.to_a
-               var sorter = new MModuleNameSorter
-               sorter.sort(sorted)
-               append("<article class='modules filterable'>")
-               append("<h2>Modules</h2>")
-               append("<ul>")
-               for mmodule in sorted do
+       # Extract mmodule list to display (sorted by name)
+       private fun modules_list: Array[MModule] do
+               var sorted = new Array[MModule]
+               for mmodule in ctx.mbuilder.model.mmodule_importation_hierarchy do
                        if mmodule.name == "<main>" then continue
-                       append("<li>")
-                       mmodule.html_link(self)
-                       append("</li>")
+                       sorted.add mmodule
                end
-               append("</ul>")
-               append("</article>")
+               var sorter = new MModuleNameSorter
+               sorter.sort(sorted)
+               return sorted
        end
 
-       # Add to content classes modules
-       private fun classes_column do
-               var sorted = ctx.mbuilder.model.mclasses
-               var sorter = new MClassNameSorter
-               sorter.sort(sorted)
-               append("<article class='modules filterable'>")
-               append("<h2>Classes</h2>")
-               append("<ul>")
-               for mclass in sorted do
+       # Extract mclass list to display (sorted by name)
+       private fun classes_list: Array[MClass] do
+               var sorted = new Array[MClass]
+               for mclass in ctx.mbuilder.model.mclasses do
                        if mclass.visibility < ctx.min_visibility then continue
-                       append("<li>")
-                       mclass.html_link(self)
-                       append("</li>")
+                       sorted.add mclass
                end
-               append("</ul>")
-               append("</article>")
+               var sorter = new MClassNameSorter
+               sorter.sort(sorted)
+               return sorted
        end
 
-       # Insert the properties column of fullindex page
-       private fun properties_column do
-               var sorted = ctx.mbuilder.model.mproperties
-               var sorter = new MPropertyNameSorter
-               sorter.sort(sorted)
-               append("<article class='modules filterable'>")
-               append("<h2>Properties</h2>")
-               append("<ul>")
-               for mproperty in sorted do
+       # Extract mproperty list to display (sorted by name)
+       private fun mprops_list: Array[MProperty] do
+               var sorted = new Array[MProperty]
+               for mproperty in ctx.mbuilder.model.mproperties do
                        if mproperty.visibility < ctx.min_visibility then continue
                        if mproperty isa MAttribute then continue
-                       append("<li>")
-                       mproperty.intro.html_link(self)
-                       append(" (")
-                       mproperty.intro.mclassdef.mclass.html_link(self)
-                       append(")</li>")
+                       sorted.add mproperty
                end
-               append("</ul>")
-               append("</article>")
+               var sorter = new MPropertyNameSorter
+               sorter.sort(sorted)
+               return sorted
        end
-
 end
 
 # A module page
+# Display the list of introduced and redefined classes in module
 class NitdocModule
        super NitdocPage
 
        private var mmodule: MModule
-       private var mbuilder: ModelBuilder
-       private var local_mclasses = new HashSet[MClass]
-       private var intro_mclasses = new HashSet[MClass]
-       private var redef_mclasses = new HashSet[MClass]
+       private var intro_mclassdefs: Set[MClassDef]
+       private var redef_mclassdefs: Set[MClassDef]
+       private var sorted_intro_mclassdefs: Array[MClassDef]
+       private var sorted_redef_mclassdefs: Array[MClassDef]
 
        init(mmodule: MModule, ctx: NitdocContext) do
-               super(ctx)
                self.mmodule = mmodule
-               self.mbuilder = ctx.mbuilder
-               # get local mclasses
-               for m in mmodule.in_nesting.greaters do
-                       for mclassdef in m.mclassdefs do
-                               if mclassdef.mclass.visibility < ctx.min_visibility then continue
-                               if mclassdef.is_intro then
-                                       intro_mclasses.add(mclassdef.mclass)
-                               else
-                                       if mclassdef.mclass.mpropdefs_in_module(self).is_empty then continue
-                                       redef_mclasses.add(mclassdef.mclass)
-                               end
-                               local_mclasses.add(mclassdef.mclass)
-                       end
-               end
+               var sorter = new MClassDefNameSorter
+               intro_mclassdefs = in_nesting_intro_mclassdefs(ctx.min_visibility)
+               sorted_intro_mclassdefs = intro_mclassdefs.to_a
+               sorter.sort sorted_intro_mclassdefs
+               redef_mclassdefs = in_nesting_redef_mclassdefs(ctx.min_visibility)
+               sorted_redef_mclassdefs = redef_mclassdefs.to_a
+               sorter.sort sorted_redef_mclassdefs
+               super(ctx)
        end
 
-       redef fun title do
-               if mbuilder.mmodule2nmodule.has_key(mmodule) and not mbuilder.mmodule2nmodule[mmodule].short_comment.is_empty then
-                       var nmodule = mbuilder.mmodule2nmodule[mmodule]
-                       return "{mmodule.html_name} module | {nmodule.short_comment}"
+       redef fun tpl_title do
+               if mmodule.mdoc != null then
+                       return "{mmodule.nitdoc_name} module | {mmodule.mdoc.short_comment} | {super}"
                else
-                       return "{mmodule.html_name} module"
+                       return "{mmodule.nitdoc_name} module | {super}"
                end
        end
 
-       redef fun menu do
-               super
-               append("<li><a href='index.html'>Overview</a></li>")
-               append("<li class='current'>{mmodule.html_name}</li>")
-               append("<li><a href='search.html'>Search</a></li>")
+       redef fun tpl_topmenu do
+               var topmenu = super
+               topmenu.add_elt("index.html", "Overview", false)
+               topmenu.add_elt("#", "{mmodule.nitdoc_name}", true)
+               topmenu.add_elt("search.html", "Search", false)
+               return topmenu
        end
 
-       redef fun content do
-               append("<div class='sidebar'>")
-               classes_column
-               importation_column
-               append("</div>")
-               append("<div class='content'>")
-               module_doc
-               append("</div>")
+       redef fun tpl_sidebar do
+               var sidebar = new TplSidebar
+               tpl_sidebar_classes(sidebar)
+               tpl_sidebar_inheritance(sidebar)
+               return sidebar
        end
 
-       private fun classes_column do
-               var sorter = new MClassNameSorter
-               var sorted = new Array[MClass]
-               sorted.add_all(intro_mclasses)
-               sorted.add_all(redef_mclasses)
-               sorter.sort(sorted)
-               if not sorted.is_empty then
-                       append("<nav class='properties filterable'>")
-                       append("<h3>Classes</h3>")
-                       append("<h4>Classes</h4>")
-                       append("<ul>")
-                       for mclass in sorted do mclass.html_sidebar_item(self)
-                       append("</ul>")
-                       append("</nav>")
+       # Classes to display on sidebar
+       fun tpl_sidebar_classes(sidebar: TplSidebar) do
+               var box = new TplSidebarBox("Class Definitions")
+               var group = new TplSidebarGroup("Introductions")
+               for mclassdef in sorted_intro_mclassdefs do
+                       tpl_sidebar_item(mclassdef, group)
+               end
+               box.elts.add group
+               group = new TplSidebarGroup("Refinements")
+               for mclassdef in sorted_redef_mclassdefs do
+                       if intro_mclassdefs.has(mclassdef.mclass.intro) then continue
+                       tpl_sidebar_item(mclassdef, group)
                end
+               box.elts.add group
+               sidebar.boxes.add box
        end
 
-       private fun importation_column do
-               append("<nav>")
-               append("<h3>Module Hierarchy</h3>")
+       # Module inheritance to display on sidebar
+       fun tpl_sidebar_inheritance(sidebar: TplSidebar) do
+               var box = new TplSidebarBox("Module Hierarchy")
+               box.elts.add tpl_sidebar_group("Nested Modules", mmodule.in_nesting.direct_greaters.to_a)
                var dependencies = new Array[MModule]
                for dep in mmodule.in_importation.greaters do
-                       if dep == mmodule or dep.direct_owner == mmodule or dep.public_owner == mmodule then continue
+                       if dep == mmodule or
+                               dep.direct_owner == mmodule or
+                               dep.public_owner == mmodule then continue
                        dependencies.add(dep)
                end
-               if mmodule.in_nesting.direct_greaters.length > 0 then
-                       append("<h4>Nested Modules</h4>")
-                       display_module_list(mmodule.in_nesting.direct_greaters.to_a)
-               end
                if dependencies.length > 0 then
-                       append("<h4>All dependencies</h4>")
-                       display_module_list(dependencies)
+                       box.elts.add tpl_sidebar_group("All dependencies", dependencies)
                end
                var clients = new Array[MModule]
                for dep in mmodule.in_importation.smallers do
@@ -623,58 +653,47 @@ class NitdocModule
                        clients.add(dep)
                end
                if clients.length > 0 then
-                       append("<h4>All clients</h4>")
-                       display_module_list(clients)
+                       box.elts.add tpl_sidebar_group("All clients", clients)
                end
-               append("</nav>")
+               sidebar.boxes.add box
        end
 
-       private fun display_module_list(list: Array[MModule]) do
-               append("<ul>")
-               var sorter = new MModuleNameSorter
-               sorter.sort(list)
-               for m in list do
-                       append("<li>")
-                       m.html_link(self)
-                       append("</li>")
+       private fun tpl_sidebar_item(mclassdef: MClassDef, group: TplSidebarGroup) do
+               if mclassdef.is_intro then
+                       group.add_bullet("I", "Introduced", mclassdef.tpl_link_anchor, ["intro"])
+               else
+                       group.add_bullet("R", "Redefined", mclassdef.tpl_link_anchor, ["redef"])
                end
-               append("</ul>")
        end
 
-       private fun module_doc do
-               # title
-               append("<h1>{mmodule.html_name}</h1>")
-               append("<div class='subtitle info'>")
-               mmodule.html_signature(self)
-               append("</div>")
-               # comment
-               mmodule.html_comment(self)
-               process_generate_dot
-               # classes
+       private fun tpl_sidebar_group(name: String, elts: Array[MModule]): TplSidebarGroup do
+               var group = new TplSidebarGroup(name)
+               for elt in elts do
+                       group.add_elt(elt.tpl_link, new Array[String])
+               end
+               return group
+       end
+
+       redef fun tpl_content do
                var class_sorter = new MClassNameSorter
-               # intro
-               if not intro_mclasses.is_empty then
-                       var sorted = new Array[MClass]
-                       sorted.add_all(intro_mclasses)
-                       class_sorter.sort(sorted)
-                       append("<section class='classes'>")
-                       append("<h2 class='section-header'>Introduced classes</h2>")
-                       for mclass in sorted do mclass.html_full_desc(self)
-                       append("</section>")
-               end
-               # redefs
-               var redefs = new Array[MClass]
-               for mclass in redef_mclasses do if not intro_mclasses.has(mclass) then redefs.add(mclass)
-               class_sorter.sort(redefs)
-               if not redefs.is_empty then
-                       append("<section class='classes'>")
-                       append("<h2 class='section-header'>Refined classes</h2>")
-                       for mclass in redefs do mclass.html_full_desc(self)
-                       append("</section>")
-               end
-       end
-
-       private fun process_generate_dot do
+               var tpl = new TplModulePage
+               tpl.title = mmodule.nitdoc_name
+               tpl.subtitle = mmodule.tpl_signature
+               tpl.definition = mmodule.tpl_definition
+               var location = mmodule.location
+               tpl.definition.location = tpl_showsource(location)
+               tpl.definition.github_area = tpl_github(mmodule.full_namespace, mmodule.mdoc, location)
+               tpl.graph = tpl_dot
+               for mclassdef in sorted_intro_mclassdefs do tpl.intros.add tpl_mclassdef_article(mclassdef)
+               for mclassdef in sorted_redef_mclassdefs do
+                       if intro_mclassdefs.has(mclassdef.mclass.intro) then continue
+                       tpl.redefs.add tpl_mclassdef_article(mclassdef)
+               end
+               return tpl
+       end
+
+       # Genrate dot hierarchy for class inheritance
+       fun tpl_dot: nullable TplGraph do
                # build poset with public owners
                var poset = new POSet[MModule]
                for mmodule in self.mmodule.in_importation.poset do
@@ -709,367 +728,333 @@ class NitdocModule
                        if mmodule == self.mmodule then
                                op.append("\"{mmodule.name}\"[shape=box,margin=0.03];\n")
                        else
-                               op.append("\"{mmodule.name}\"[URL=\"{mmodule.url}\"];\n")
+                               op.append("\"{mmodule.name}\"[URL=\"{mmodule.nitdoc_url}\"];\n")
                        end
                        for omodule in poset[mmodule].direct_greaters do
                                op.append("\"{mmodule.name}\"->\"{omodule.name}\";\n")
                        end
                end
                op.append("\}\n")
-               generate_dot(op.to_s, name, "Dependency graph for module {mmodule.name}")
+               return tpl_graph(op, name, "Dependency graph for module {mmodule.name}")
+       end
+
+       private fun in_nesting_intro_mclassdefs(min_visibility: MVisibility): Set[MClassDef] do
+               var res = new HashSet[MClassDef]
+               for mmodule in self.mmodule.in_nesting.greaters do
+                       res.add_all mmodule.intro_mclassdefs(min_visibility)
+               end
+               return res
+       end
+
+       private fun in_nesting_redef_mclassdefs(min_visibility: MVisibility): Set[MClassDef] do
+               var res = new HashSet[MClassDef]
+               for mmodule in self.mmodule.in_nesting.greaters do
+                       res.add_all mmodule.redef_mclassdefs(min_visibility)
+               end
+               return res
        end
 end
 
 # A class page
+# Display a list properties defined or redefined for this class
 class NitdocClass
        super NitdocPage
 
        private var mclass: MClass
-       private var vtypes = new HashSet[MVirtualTypeDef]
-       private var consts = new HashSet[MMethodDef]
-       private var meths = new HashSet[MMethodDef]
-       private var inherited = new HashSet[MPropDef]
+       private var inherited_mpropdefs: Set[MPropDef]
+       private var intro_mpropdefs: Set[MPropDef]
+       private var redef_mpropdefs: Set[MPropDef]
+       private var all_mpropdefs: Set[MPropDef]
 
        init(mclass: MClass, ctx: NitdocContext) do
-               super(ctx)
                self.mclass = mclass
-               # load properties
-               var locals = new HashSet[MProperty]
+               super(ctx)
+               intro_mpropdefs = mclass_intro_mpropdefs
+               redef_mpropdefs = mclass_redef_mpropdefs
+               inherited_mpropdefs = in_nesting_inherited_mpropedefs
+               all_mpropdefs = new HashSet[MPropDef]
+               all_mpropdefs.add_all intro_mpropdefs
+               all_mpropdefs.add_all redef_mpropdefs
+               all_mpropdefs.add_all inherited_mpropdefs
+       end
+
+       private fun in_nesting_inherited_mpropedefs: Set[MPropDef] do
+               var res = new HashSet[MPropDef]
+               var local = mclass.local_mproperties(ctx.min_visibility)
+               for mprop in mclass.inherited_mproperties(ctx.mainmodule, ctx.min_visibility) do
+                       if local.has(mprop) then continue
+                       if mprop isa MMethod and mprop.is_init then continue
+                       res.add mprop.intro
+               end
+               return res
+       end
+
+       private fun mclass_intro_mpropdefs: Set[MPropDef] do
+               var res = new HashSet[MPropDef]
                for mclassdef in mclass.mclassdefs do
+                       if mclassdef != mclass.intro then continue
                        for mpropdef in mclassdef.mpropdefs do
-                               if mpropdef.mproperty.visibility < ctx.min_visibility then continue
-                               if mpropdef isa MVirtualTypeDef then vtypes.add(mpropdef)
-                               if mpropdef isa MMethodDef then
-                                       if mpropdef.mproperty.is_init then
-                                               consts.add(mpropdef)
-                                       else
-                                               meths.add(mpropdef)
-                                       end
-                               end
-                               locals.add(mpropdef.mproperty)
+                               var mprop = mpropdef.mproperty
+                               if mprop isa MMethod and mprop.is_init and mclass.is_abstract then continue
+                               if mprop.visibility < ctx.min_visibility then continue
+                               res.add mpropdef
                        end
                end
-               # get inherited properties
-               for pclass in mclass.in_hierarchy(ctx.mainmodule).greaters do
-                       if pclass == mclass then continue
-                       for pclassdef in pclass.mclassdefs do
-                               for mprop in pclassdef.intro_mproperties do
-                                       var mpropdef = mprop.intro
-                                       if mprop.visibility < ctx.min_visibility then continue # skip if not correct visibiility
-                                       if locals.has(mprop) then continue # skip if local
-                                       if mclass.name != "Object" and mprop.intro_mclassdef.mclass.name == "Object" and (mprop.visibility <= protected_visibility or mprop.intro_mclassdef.mmodule.public_owner == null or mprop.intro_mclassdef.mmodule.public_owner.name != "standard") then continue # skip toplevels
-                                       if mpropdef isa MVirtualTypeDef then vtypes.add(mpropdef)
-                                       if mpropdef isa MMethodDef then
-                                               if mpropdef.mproperty.is_init then
-                                                       consts.add(mpropdef)
-                                               else
-                                                       meths.add(mpropdef)
-                                               end
-                                       end
-                                       inherited.add(mpropdef)
-                               end
+               return res
+       end
+
+       private fun mclass_redef_mpropdefs: Set[MPropDef] do
+               var res = new HashSet[MPropDef]
+               for mclassdef in mclass.mclassdefs do
+                       if mclassdef == mclass.intro then continue
+                       for mpropdef in mclassdef.mpropdefs do
+                               if mclassdef.mmodule.public_owner == mclass.public_owner then continue
+                               if mpropdef.mproperty.visibility < ctx.min_visibility then continue
+                               res.add mpropdef
                        end
                end
+               return res
        end
 
-       redef fun title do
-               var nclass = ctx.mbuilder.mclassdef2nclassdef[mclass.intro]
-               if nclass isa AStdClassdef then
-                       return "{mclass.html_name} class | {nclass.short_comment}"
+       redef fun tpl_title do
+               if mclass.mdoc != null then
+                       return "{mclass.nitdoc_name} class | {mclass.mdoc.short_comment} | {super}"
                else
-                       return "{mclass.html_name} class"
+                       return "{mclass.nitdoc_name} class | {super}"
                end
        end
 
-       redef fun menu do
-               super
-               append("<li><a href='index.html'>Overview</a></li>")
-               var public_owner = mclass.public_owner
-               if public_owner == null then
-                       append("<li>")
-                       mclass.intro_mmodule.html_link(self)
-                       append("</li>")
+       redef fun tpl_topmenu do
+               var topmenu = super
+               var mmodule: MModule
+               if mclass.public_owner == null then
+                       mmodule = mclass.intro_mmodule
                else
-                       append("<li>")
-                       public_owner.html_link(self)
-                       append("</li>")
+                       mmodule = mclass.public_owner.as(not null)
                end
-               append("<li class='current'>{mclass.html_name}</li>")
-               append("<li><a href='search.html'>Search</a></li>")
+               topmenu.add_elt("index.html", "Overview", false)
+               topmenu.add_elt("{mmodule.nitdoc_url}", "{mmodule.nitdoc_name}", false)
+               topmenu.add_elt("#", "{mclass.nitdoc_name}", true)
+               topmenu.add_elt("search.html", "Search", false)
+               return topmenu
        end
 
-       redef fun content do
-               append("<div class='sidebar'>")
-               properties_column
-               inheritance_column
-               append("</div>")
-               append("<div class='content'>")
-               class_doc
-               append("</div>")
+       redef fun tpl_sidebar do
+               var sidebar = new TplSidebar
+               tpl_sidebar_properties(sidebar)
+               tpl_sidebar_inheritance(sidebar)
+               return sidebar
        end
 
-       private fun properties_column do
+       # Property list to display in sidebar
+       fun tpl_sidebar_properties(sidebar: TplSidebar) do
+               var kind_map = sort_by_kind(all_mpropdefs)
                var sorter = new MPropDefNameSorter
-               append("<nav class='properties filterable'>")
-               append("<h3>Properties</h3>")
+               var box = new TplSidebarBox("Properties")
                # virtual types
-               if vtypes.length > 0 then
-                       var vts = new Array[MVirtualTypeDef]
-                       vts.add_all(vtypes)
-                       sorter.sort(vts)
-                       append("<h4>Virtual Types</h4>")
-                       append("<ul>")
-                       for mprop in vts do
-                               mprop.html_sidebar_item(self)
-                       end
-                       append("</ul>")
+               var elts = kind_map["type"].to_a
+               sorter.sort(elts)
+               var group = new TplSidebarGroup("Virtual Types")
+               for mprop in elts do
+                       tpl_sidebar_item(mprop, group)
                end
+               box.elts.add group
                # constructors
-               if consts.length > 0 then
-                       var cts = new Array[MMethodDef]
-                       cts.add_all(consts)
-                       sorter.sort(cts)
-                       append("<h4>Constructors</h4>")
-                       append("<ul>")
-                       for mprop in cts do
-                               if mprop.mproperty.name == "init" and mprop.mclassdef.mclass != mclass then continue
-                               mprop.html_sidebar_item(self)
-                       end
-                       append("</ul>")
+               elts = kind_map["init"].to_a
+               sorter.sort(elts)
+               group = new TplSidebarGroup("Constructors")
+               for mprop in elts do
+                       tpl_sidebar_item(mprop, group)
                end
+               box.elts.add group
                # methods
-               if meths.length > 0 then
-                       var mts = new Array[MMethodDef]
-                       mts.add_all(meths)
-                       sorter.sort(mts)
-                       append("<h4>Methods</h4>")
-                       append("<ul>")
-                       for mprop in mts do
-                               mprop.html_sidebar_item(self)
-                       end
-                       append("</ul>")
+               elts = kind_map["fun"].to_a
+               sorter.sort(elts)
+               group = new TplSidebarGroup("Methods")
+               for mprop in elts do
+                       tpl_sidebar_item(mprop, group)
                end
-               append("</nav>")
+               box.elts.add group
+               sidebar.boxes.add box
        end
 
-       private fun inheritance_column do
+       # Class inheritance to display in sidebar
+       fun tpl_sidebar_inheritance(sidebar: TplSidebar) do
                var sorted = new Array[MClass]
                var sorterp = new MClassNameSorter
-               append("<nav>")
-               append("<h3>Inheritance</h3>")
+               var box = new TplSidebarBox("Inheritance")
                var greaters = mclass.in_hierarchy(ctx.mainmodule).greaters.to_a
                if greaters.length > 1 then
                        ctx.mainmodule.linearize_mclasses(greaters)
-                       append("<h4>Superclasses</h4>")
-                       append("<ul>")
-                       for sup in greaters do
-                               if sup == mclass then continue
-                               append("<li>")
-                               sup.html_link(self)
-                               append("</li>")
-                       end
-                       append("</ul>")
+                       box.elts.add tpl_sidebar_group("Superclasses", greaters)
                end
                var smallers = mclass.in_hierarchy(ctx.mainmodule).smallers.to_a
                var direct_smallers = mclass.in_hierarchy(ctx.mainmodule).direct_smallers.to_a
                if smallers.length <= 1 then
-                       append("<h4>No Known Subclasses</h4>")
+                       box.elts.add(new TplSidebarGroup("No Known Subclasses"))
                else if smallers.length <= 100 then
                        ctx.mainmodule.linearize_mclasses(smallers)
-                       append("<h4>Subclasses</h4>")
-                       append("<ul>")
-                       for sub in smallers do
-                               if sub == mclass then continue
-                               append("<li>")
-                               sub.html_link(self)
-                               append("</li>")
-                       end
-                       append("</ul>")
+                       box.elts.add tpl_sidebar_group("Subclasses", smallers)
                else if direct_smallers.length <= 100 then
                        ctx.mainmodule.linearize_mclasses(direct_smallers)
-                       append("<h4>Direct Subclasses Only</h4>")
-                       append("<ul>")
-                       for sub in direct_smallers do
-                               if sub == mclass then continue
-                               append("<li>")
-                               sub.html_link(self)
-                               append("</li>")
-                       end
-                       append("</ul>")
+                       box.elts.add tpl_sidebar_group("Direct Subclasses Only", direct_smallers)
                else
-                       append("<h4>Too much Subclasses to list</h4>")
+                       box.elts.add(new TplSidebarGroup("Too much Subclasses to list"))
                end
-               append("</nav>")
+               sidebar.boxes.add box
        end
 
-       private fun class_doc do
-               # title
-               append("<h1>{mclass.html_name}{mclass.html_short_signature}</h1>")
-               append("<div class='subtitle info'>")
-               if mclass.visibility < public_visibility then append("{mclass.visibility.to_s} ")
-               append("{mclass.kind.to_s} ")
-               mclass.html_namespace(self)
-               append("{mclass.html_short_signature}</div>")
-               # comment
-               mclass.html_comment(self)
-               process_generate_dot
-               # concerns
-               var concern2meths = new ArrayMap[MModule, Array[MMethodDef]]
-               var sorted_meths = new Array[MMethodDef]
-               var sorted = new Array[MModule]
-               sorted_meths.add_all(meths)
-               ctx.mainmodule.linearize_mpropdefs(sorted_meths)
-               for meth in meths do
-                       if inherited.has(meth) then continue
-                       var mmodule = meth.mclassdef.mmodule
-                       if not concern2meths.has_key(mmodule) then
-                               sorted.add(mmodule)
-                               concern2meths[mmodule] = new Array[MMethodDef]
-                       end
-                       concern2meths[mmodule].add(meth)
+       private fun tpl_sidebar_item(mprop: MPropDef, group: TplSidebarGroup) do
+               if mprop.is_intro and mprop.mclassdef.mclass == mclass then
+                       group.add_bullet("I", "Introduced", mprop.tpl_link, ["intro"])
+               else if mprop.is_intro and mprop.mclassdef.mclass != mclass then
+                       group.add_bullet("H", "Inherited", mprop.tpl_link, ["inherit"])
+               else
+                       group.add_bullet("R", "Redefined", mprop.tpl_link, ["redef"])
                end
-               var sections = new ArrayMap[MModule, Array[MModule]]
-               for mmodule in concern2meths.keys do
-                       var owner = mmodule.public_owner
-                       if owner == null then owner = mmodule
-                       if not sections.has_key(owner) then sections[owner] = new Array[MModule]
-                       if owner != mmodule then sections[owner].add(mmodule)
-               end
-               append("<section class='concerns'>")
-               append("<h2 class='section-header'>Concerns</h2>")
-               append("<ul>")
-               for owner, mmodules in sections do
-                       var nowner = ctx.mbuilder.mmodule2nmodule[owner]
-                       append("<li>")
-                       if nowner.short_comment.is_empty then
-                               append("<a href=\"#{owner.anchor}\">{owner.html_name}</a>")
-                       else
-                               append("<a href=\"#{owner.anchor}\">{owner.html_name}</a>: {nowner.short_comment}")
-                       end
-                       if not mmodules.is_empty then
-                               append("<ul>")
-                               for mmodule in mmodules do
-                                       var nmodule = ctx.mbuilder.mmodule2nmodule[mmodule]
-                                       if nmodule.short_comment.is_empty then
-                                               append("<li><a href=\"#{mmodule.anchor}\">{mmodule.html_name}</a></li>")
-                                       else
-                                               append("<li><a href=\"#{mmodule.anchor}\">{mmodule.html_name}</a>: {nmodule.short_comment}</li>")
-                                       end
-                               end
-                               append("</ul>")
-                       end
-                       append("</li>")
+       end
+
+       private fun tpl_sidebar_group(name: String, elts: Array[MClass]): TplSidebarGroup do
+               var group = new TplSidebarGroup(name)
+               for elt in elts do
+                       if elt == mclass then continue
+                       group.add_elt(elt.tpl_link, new Array[String])
                end
-               append("</ul>")
-               append("</section>")
+               return group
+       end
+
+       redef fun tpl_content do
+               var intro = mclass.intro
+               var tpl = new TplClassPage
+               tpl.title = "{mclass.nitdoc_name}{mclass.tpl_short_signature}"
+               tpl.subtitle = mclass.tpl_namespace_with_signature
+               tpl.definition = intro.tpl_definition
+               var location = intro.location
+               tpl.definition.location = tpl_showsource(location)
+               tpl.definition.github_area = tpl_github(intro.full_namespace, intro.mdoc, location)
+               tpl.graph = tpl_dot
+
                # properties
                var prop_sorter = new MPropDefNameSorter
-               var lmmodule = new List[MModule]
-               var nclass = ctx.mbuilder.mclassdef2nclassdef[mclass.intro]
-               # virtual and formal types
-               var local_vtypes = new Array[MVirtualTypeDef]
-               for vt in vtypes do if not inherited.has(vt) then local_vtypes.add(vt)
-               if local_vtypes.length > 0 or mclass.arity > 0 then
-                       append("<section class='types'>")
-                       append("<h2>Formal and Virtual Types</h2>")
-                       # formal types
-                       if mclass.arity > 0 and nclass isa AStdClassdef then
-                               for ft, bound in mclass.parameter_types do
-                                       append("<article id='FT_{ft}'>")
-                                       append("<h3 class='signature' data-untyped-signature='{ft.to_s}'><span>{ft}: ")
-                                       bound.html_link(self)
-                                       append("</span></h3>")
-                                       append("<div class=\"info\">formal generic type</div>")
-                                       append("</article>")
-                               end
-                       end
-                       # virtual types
-                       prop_sorter.sort(local_vtypes)
-                       for prop in local_vtypes do prop.html_full_desc(self, self.mclass)
-                       append("</section>")
-               end
+               var kind_map = sort_by_kind(intro_mpropdefs)
+
+               # virtual types
+               var elts = kind_map["type"].to_a
+               prop_sorter.sort(elts)
+               for elt in elts do tpl.types.add tpl_mpropdef_article(elt)
+
                # constructors
-               var local_consts = new Array[MMethodDef]
-               for const in consts do if not inherited.has(const) then local_consts.add(const)
-               prop_sorter.sort(local_consts)
-               if local_consts.length > 0 then
-                       append("<section class='constructors'>")
-                       append("<h2 class='section-header'>Constructors</h2>")
-                       for prop in local_consts do prop.html_full_desc(self, self.mclass)
-                       append("</section>")
-               end
-               # methods
-               if not concern2meths.is_empty then
-                       append("<section class='methods'>")
-                       append("<h2 class='section-header'>Methods</h2>")
-                       for owner, mmodules in sections do
-                               append("<a id=\"{owner.anchor}\"></a>")
-                               if owner != mclass.intro_mmodule and owner != mclass.public_owner then
-                                       var nowner = ctx.mbuilder.mmodule2nmodule[owner]
-                                       append("<h3 class=\"concern-toplevel\">Methods refined in ")
-                                       owner.html_link(self)
-                                       append("</h3>")
-                                       append("<p class=\"concern-doc\">")
-                                       owner.html_link(self)
-                                       if not nowner.short_comment.is_empty then
-                                               append(": {nowner.short_comment}")
-                                       end
-                                       append("</p>")
-                               end
-                               if concern2meths.has_key(owner) then
-                                       var mmethods = concern2meths[owner]
-                                       prop_sorter.sort(mmethods)
-                                       for prop in mmethods do prop.html_full_desc(self, self.mclass)
+               elts = kind_map["init"].to_a
+               prop_sorter.sort(elts)
+               for elt in elts do tpl.inits.add tpl_mpropdef_article(elt)
+
+               # intro methods
+               elts = kind_map["fun"].to_a
+               prop_sorter.sort(elts)
+               for elt in elts do tpl.methods.add tpl_mpropdef_article(elt)
+
+               # redef methods
+               kind_map = sort_by_kind(redef_mpropdefs)
+               var module_sorter = new MModuleNameSorter
+               var module_map = sort_by_mmodule(kind_map["fun"])
+               var owner_map = sort_by_public_owner(module_map.keys)
+               var owners = owner_map.keys.to_a
+               module_sorter.sort owners
+
+               var ctpl = new TplConcernList
+               var mtpl = new Template
+               for owner in owners do
+                       # concerns list
+                       var octpl = new TplConcernListElt
+                       octpl.anchor = "#{owner.nitdoc_anchor}"
+                       octpl.name = owner.nitdoc_name
+                       if owner.mdoc != null then
+                               octpl.comment = owner.mdoc.short_comment
+                       end
+                       ctpl.elts.add octpl
+                       # concern section
+                       var otpl = new TplTopConcern
+                       otpl.anchor = owner.nitdoc_anchor
+                       otpl.concern = owner.tpl_link
+                       mtpl.add otpl
+
+                       var mmodules = owner_map[owner].to_a
+                       module_sorter.sort mmodules
+                       var stpl = new TplConcernList
+                       for mmodule in mmodules do
+                               # concerns list
+                               var mctpl = new TplConcernListElt
+                               mctpl.anchor = "#{mmodule.nitdoc_anchor}"
+                               mctpl.name = mmodule.nitdoc_name
+                               if mmodule.mdoc != null then
+                                       mctpl.comment = mmodule.mdoc.short_comment
                                end
-                               for mmodule in mmodules do
-                                       append("<a id=\"{mmodule.anchor}\"></a>")
-                                       var nmodule = ctx.mbuilder.mmodule2nmodule[mmodule]
-                                       if mmodule != mclass.intro_mmodule and mmodule != mclass.public_owner then
-                                               append("<p class=\"concern-doc\">")
-                                               mmodule.html_link(self)
-                                               if not nmodule.short_comment.is_empty then
-                                                       append(": {nmodule.short_comment}")
-                                               end
-                                               append("</p>")
-                                       end
-                                       var mmethods = concern2meths[mmodule]
-                                       prop_sorter.sort(mmethods)
-                                       for prop in mmethods do prop.html_full_desc(self, self.mclass)
+                               stpl.elts.add mctpl
+                               # concern sectionm
+                               var cctpl = new TplConcern
+                               cctpl.anchor = mmodule.nitdoc_anchor
+                               cctpl.concern = mmodule.tpl_link
+                               if mmodule.mdoc != null then
+                                       cctpl.comment = mmodule.mdoc.short_comment
                                end
+                               mtpl.add cctpl
+
+                               var mprops = module_map[mmodule].to_a
+                               prop_sorter.sort mprops
+                               for mprop in mprops do mtpl.add tpl_mpropdef_article(mprop)
                        end
-                       append("</section>")
-               end
-               # inherited properties
-               if inherited.length > 0 then
-                       var sorted_inherited = new Array[MPropDef]
-                       sorted_inherited.add_all(inherited)
-                       ctx.mainmodule.linearize_mpropdefs(sorted_inherited)
-                       var classes = new ArrayMap[MClass, Array[MPropDef]]
-                       for mmethod in sorted_inherited.reversed do
-                               var mclass = mmethod.mclassdef.mclass
-                               if not classes.has_key(mclass) then classes[mclass] = new Array[MPropDef]
-                               classes[mclass].add(mmethod)
-                       end
-                       append("<section class='inherited'>")
-                       append("<h2 class='section-header'>Inherited Properties</h2>")
-                       for c, mmethods in classes do
-                               prop_sorter.sort(mmethods)
-                               append("<p>Defined in ")
-                               c.html_link(self)
-                               append(": ")
-                               for i in [0..mmethods.length[ do
-                                       var mmethod = mmethods[i]
-                                       mmethod.html_link(self)
-                                       if i <= mmethods.length - 1 then append(", ")
+                       ctpl.elts.add stpl
+               end
+               if not owners.is_empty then
+                       tpl.concerns = ctpl
+               end
+               tpl.methods.add mtpl
+               return tpl
+       end
+
+       private fun sort_by_kind(mpropdefs: Set[MPropDef]): Map[String, Set[MPropDef]] do
+               var map = new HashMap[String, Set[MPropDef]]
+               map["type"] = new HashSet[MPropDef]
+               map["init"] = new HashSet[MPropDef]
+               map["fun"] = new HashSet[MPropDef]
+               for mpropdef in mpropdefs do
+                       if mpropdef isa MVirtualTypeDef then
+                               map["type"].add mpropdef
+                       else if mpropdef isa MMethodDef then
+                               if mpropdef.mproperty.is_init then
+                                       map["init"].add mpropdef
+                               else
+                                       map["fun"].add mpropdef
                                end
-                               append("</p>")
                        end
-                       append("</section>")
                end
+               return map
        end
 
-       private fun process_generate_dot do
-               var pe = ctx.class_hierarchy[mclass]
+       private fun sort_by_mmodule(mpropdefs: Collection[MPropDef]): Map[MModule, Set[MPropDef]] do
+               var map = new HashMap[MModule, Set[MPropDef]]
+               for mpropdef in mpropdefs do
+                       var mmodule = mpropdef.mclassdef.mmodule
+                       if not map.has_key(mmodule) then map[mmodule] = new HashSet[MPropDef]
+                       map[mmodule].add mpropdef
+               end
+               return map
+       end
+
+       private fun sort_by_public_owner(mmodules: Collection[MModule]): Map[MModule, Set[MModule]] do
+               var map = new HashMap[MModule, Set[MModule]]
+               for mmodule in mmodules do
+                       var owner = mmodule
+                       if mmodule.public_owner != null then owner = mmodule.public_owner.as(not null)
+                       if not map.has_key(owner) then map[owner] = new HashSet[MModule]
+                       map[owner].add mmodule
+               end
+               return map
+       end
+
+       # Generate dot hierarchy for classes
+       fun tpl_dot: nullable TplGraph do
+               var pe = mclass.in_hierarchy(ctx.mainmodule)
                var cla = new HashSet[MClass]
                var sm = new HashSet[MClass]
                var sm2 = new HashSet[MClass]
@@ -1093,7 +1078,7 @@ class NitdocClass
                        if c == mclass then
                                op.append("\"{c.name}\"[shape=box,margin=0.03];\n")
                        else
-                               op.append("\"{c.name}\"[URL=\"{c.url}\"];\n")
+                               op.append("\"{c.name}\"[URL=\"{c.nitdoc_url}\"];\n")
                        end
                        for c2 in pe.poset[c].direct_greaters do
                                if not cla.has(c2) then continue
@@ -1111,7 +1096,7 @@ class NitdocClass
                        end
                end
                op.append("\}\n")
-               generate_dot(op.to_s, name, "Dependency graph for class {mclass.name}")
+               return tpl_graph(op, name, "Dependency graph for class {mclass.name}")
        end
 end
 
@@ -1121,228 +1106,176 @@ end
 
 redef class MModule
        # Return the HTML escaped name of the module
-       private fun html_name: String do return name.html_escape
+       private fun nitdoc_name: String do return name.html_escape
+
+       private fun full_namespace: String do
+               if public_owner != null then
+                       return "{public_owner.nitdoc_name}::{nitdoc_name}"
+               end
+               return nitdoc_name
+       end
 
        # URL to nitdoc page
        #       module_owner_name.html
-       private fun url: String do
-               if url_cache == null then
-                       var res = new FlatBuffer
-                       res.append("module_")
-                       var mowner = public_owner
-                       if mowner != null then
-                               res.append("{public_owner.name}_")
-                       end
-                       res.append("{self.name}.html")
-                       url_cache = res.to_s
+       private fun nitdoc_url: String do
+               var res = new FlatBuffer
+               res.append("module_")
+               var mowner = public_owner
+               if mowner != null then
+                       res.append("{public_owner.name}_")
                end
-               return url_cache.as(not null)
+               res.append("{self.name}.html")
+               return res.to_s
        end
-       private var url_cache: nullable String
 
-       # html anchor id for the module in a nitdoc page
+       # html nitdoc_anchor id for the module in a nitdoc page
        #       MOD_owner_name
-       private fun anchor: String do
-               if anchor_cache == null then
-                       var res = new FlatBuffer
-                       res.append("MOD_")
-                       var mowner = public_owner
-                       if mowner != null then
-                               res.append("{public_owner.name}_")
-                       end
-                       res.append(self.name)
-                       anchor_cache = res.to_s
+       private fun nitdoc_anchor: String do
+               var res = new FlatBuffer
+               res.append("MOD_")
+               var mowner = public_owner
+               if mowner != null then
+                       res.append("{public_owner.name}_")
                end
-               return anchor_cache.as(not null)
+               res.append(self.name)
+               return res.to_s
        end
-       private var anchor_cache: nullable String
 
        # Return a link (html a tag) to the nitdoc module page
-       #       <a href="url" title="short_comment">html_name</a>
-       private fun html_link(page: NitdocPage) do
-               if html_link_cache == null then
-                       var res = new FlatBuffer
-                       if page.ctx.mbuilder.mmodule2nmodule.has_key(self) then
-                               res.append("<a href='{url}' title='{page.ctx.mbuilder.mmodule2nmodule[self].short_comment}'>{html_name}</a>")
-                       else
-                               res.append("<a href='{url}'>{html_name}</a>")
-                       end
-                       html_link_cache = res.to_s
+       private fun tpl_link: TplLink do
+               var tpl = new TplLink
+               tpl.href = nitdoc_url
+               tpl.text = nitdoc_name
+               if mdoc != null then
+                       tpl.title = mdoc.short_comment
                end
-               page.append(html_link_cache.as(not null))
+               return tpl
        end
-       private var html_link_cache: nullable String
 
        # Return the module signature decorated with html
-       #       <span>module html_full_namespace</span>
-       private fun html_signature(page: NitdocPage) do
-               page.append("<span>module ")
-               html_full_namespace(page)
-               page.append("</span>")
+       private fun tpl_signature: Template do
+               var tpl = new Template
+               tpl.add "<span>module "
+               tpl.add tpl_full_namespace
+               tpl.add "</span>"
+               return tpl
        end
 
        # Return the module full namespace decorated with html
-       #       <span>public_owner.html_namespace::html_link</span>
-       private fun html_full_namespace(page: NitdocPage) do
-               page.append("<span>")
+       private fun tpl_full_namespace: Template do
+               var tpl = new Template
+               tpl.add ("<span>")
                var mowner = public_owner
                if mowner != null then
-                       public_owner.html_namespace(page)
-                       page.append("::")
+                       tpl.add public_owner.tpl_namespace
+                       tpl.add "::"
                end
-               html_link(page)
-               page.append("</span>")
+               tpl.add tpl_link
+               tpl.add "</span>"
+               return tpl
        end
 
        # Return the module full namespace decorated with html
-       #       <span>public_owner.html_namespace</span>
-       private fun html_namespace(page: NitdocPage) do
-               page.append("<span>")
+       private fun tpl_namespace: Template do
+               var tpl = new Template
+               tpl.add "<span>"
                var mowner = public_owner
                if mowner != null then
-                       public_owner.html_namespace(page)
+                       tpl.add public_owner.tpl_namespace
                else
-                       html_link(page)
+                       tpl.add tpl_link
                end
-               page.append("</span>")
+               tpl.add "</span>"
+               return tpl
        end
 
-       # Return the full comment of the module decorated with html
-       private fun html_comment(page: NitdocPage) do
-               page.append("<div class='description'>")
-               if page.ctx.mbuilder.mmodule2nmodule.has_key(self) then
-                       var nmodule = page.ctx.mbuilder.mmodule2nmodule[self]
-                       if page.ctx.github_gitdir != null then
-                               var loc = nmodule.doc_location.github(page.ctx.github_gitdir.as(not null))
-                               page.append("<textarea class='baseComment' data-comment-namespace='{full_name}' data-comment-location='{loc}'>{nmodule.full_comment}</textarea>")
-                       end
-                       if nmodule.full_comment == "" then
-                               page.append("<p class='info inheritance'>")
-                               page.append("<span class=\"noComment\">no comment for </span>")
-                       else
-                               page.append("<div class='comment'>{nmodule.full_markdown}</div>")
-                               page.append("<p class='info inheritance'>")
-                       end
-                       page.append("definition in ")
-                       self.html_full_namespace(page)
-                       page.append(" {page.show_source(nmodule.location)}</p>")
+       # Description with short comment
+       private fun tpl_short_definition: TplDefinition do
+               var tpl = new TplDefinition
+               tpl.namespace = tpl_namespace
+               if mdoc != null then
+                       tpl.comment = mdoc.tpl_short_comment
                end
-               page.append("</div>")
+               return tpl
        end
 
-       private fun has_mclassdef_for(mclass: MClass): Bool do
-               for mmodule in self.in_nesting.greaters do
-                       for mclassdef in mmodule.mclassdefs do
-                               if mclassdef.mclass == mclass then return true
-                       end
+       # Description with full comment
+       private fun tpl_definition: TplDefinition do
+               var tpl = new TplDefinition
+               tpl.namespace = tpl_namespace
+               if mdoc != null then
+                       tpl.comment = mdoc.tpl_comment
                end
-               return false
-       end
-
-       private fun has_mclassdef(mclassdef: MClassDef): Bool do
-               for mmodule in self.in_nesting.greaters do
-                       for oclassdef in mmodule.mclassdefs do
-                               if mclassdef == oclassdef then return true
-                       end
-               end
-               return false
+               return tpl
        end
 end
 
 redef class MClass
        # Return the HTML escaped name of the module
-       private fun html_name: String do return name.html_escape
+       private fun nitdoc_name: String do return name.html_escape
 
        # URL to nitdoc page
        #       class_owner_name.html
-       private fun url: String do
+       private fun nitdoc_url: String do
                return "class_{public_owner}_{name}.html"
        end
 
-       # html anchor id for the class in a nitdoc page
+       # html nitdoc_anchor id for the class in a nitdoc page
        #       MOD_owner_name
-       private fun anchor: String do
-               if anchor_cache == null then
-                       anchor_cache = "CLASS_{public_owner.name}_{name}"
-               end
-               return anchor_cache.as(not null)
+       private fun nitdoc_anchor: String do
+               return "CLASS_{public_owner.name}_{name}"
        end
-       private var anchor_cache: nullable String
 
        # Return a link (with signature) to the nitdoc class page
-       #       <a href="url" title="short_comment">html_name(signature)</a>
-       private fun html_link(page: NitdocPage) do
-               if html_link_cache == null then
-                       var res = new FlatBuffer
-                       res.append("<a href='{url}'")
-                       if page.ctx.mbuilder.mclassdef2nclassdef.has_key(intro) then
-                               var nclass = page.ctx.mbuilder.mclassdef2nclassdef[intro]
-                               if nclass isa AStdClassdef then
-                                       res.append(" title=\"{nclass.short_comment}\"")
-                               end
-                       end
-                       res.append(">{html_name}{html_short_signature}</a>")
-                       html_link_cache = res.to_s
+       private fun tpl_link: TplLink do
+               var tpl = new TplLink
+               tpl.href = nitdoc_url
+               tpl.text = "{nitdoc_name}{tpl_short_signature.write_to_string}"
+               if intro.mdoc != null then
+                       tpl.title = intro.mdoc.short_comment
                end
-               page.append(html_link_cache.as(not null))
+               return tpl
        end
-       private var html_link_cache: nullable String
 
        # Return a short link (without signature) to the nitdoc class page
-       #       <a href="url" title="short_comment">html_name</a>
-       private fun html_short_link(page: NitdocPage) do
-               if html_short_link_cache == null then
-                       var res = new FlatBuffer
-                       res.append("<a href='{url}'")
-                       if page.ctx.mbuilder.mclassdef2nclassdef.has_key(intro) then
-                               var nclass = page.ctx.mbuilder.mclassdef2nclassdef[intro]
-                               if nclass isa AStdClassdef then
-                                       res.append(" title=\"{nclass.short_comment}\"")
-                               end
-                       end
-                       res.append(">{html_name}</a>")
-                       html_short_link_cache = res.to_s
-               end
-               page.append(html_short_link_cache.as(not null))
-       end
-       private var html_short_link_cache: nullable String
-
-       # Return a link (with signature) to the class anchor
-       #       <a href="url" title="short_comment">html_name</a>
-       private fun html_link_anchor(page: NitdocPage) do
-               if html_link_anchor_cache == null then
-                       var res = new FlatBuffer
-                       res.append("<a href='#{anchor}'")
-                       if page.ctx.mbuilder.mclassdef2nclassdef.has_key(intro) then
-                               var nclass = page.ctx.mbuilder.mclassdef2nclassdef[intro]
-                               if nclass isa AStdClassdef then
-                                       res.append(" title=\"{nclass.short_comment}\"")
-                               end
-                       end
-                       res.append(">{html_name}{html_short_signature}</a>")
-                       html_link_anchor_cache = res.to_s
+       private fun tpl_short_link: TplLink do
+               var tpl = new TplLink
+               tpl.href = nitdoc_url
+               tpl.text = nitdoc_name
+               if intro.mdoc != null then
+                       tpl.title = intro.mdoc.short_comment
+               end
+               return tpl
+       end
+
+       # Return a link (with signature) to the class nitdoc_anchor
+       private fun tpl_link_anchor: TplLink do
+               var tpl = new TplLink
+               tpl.href = "#{nitdoc_anchor}"
+               tpl.text = "{nitdoc_name}{tpl_short_signature.write_to_string}"
+               if intro.mdoc != null then
+                       tpl.title = intro.mdoc.short_comment
                end
-               page.append(html_link_anchor_cache.as(not null))
+               return tpl
        end
-       private var html_link_anchor_cache: nullable String
 
        # Return the generic signature of the class with bounds
-       #       [E: <a>MType</a>, F: <a>MType</a>]
-       private fun html_signature(page: NitdocPage) do
+       private fun tpl_signature: Template do
+               var tpl = new Template
                if arity > 0 then
-                       page.append("[")
+                       tpl.add "["
                        for i in [0..intro.parameter_names.length[ do
-                               page.append("{intro.parameter_names[i]}: ")
-                               intro.bound_mtype.arguments[i].html_link(page)
-                               if i < intro.parameter_names.length - 1 then page.append(", ")
+                               tpl.add "{intro.parameter_names[i]}: "
+                               tpl.add intro.bound_mtype.arguments[i].tpl_link
+                               if i < intro.parameter_names.length - 1 then tpl.add ", "
                        end
-                       page.append("]")
+                       tpl.add "]"
                end
+               return tpl
        end
 
        # Return the generic signature of the class without bounds
-       #       [E, F]
-       private fun html_short_signature: String do
+       private fun tpl_short_signature: String do
                if arity > 0 then
                        return "[{intro.parameter_names.join(", ")}]"
                else
@@ -1351,416 +1284,289 @@ redef class MClass
        end
 
        # Return the class namespace decorated with html
-       #       <span>intro_module::html_short_link</span>
-       private fun html_namespace(page: NitdocPage) do
-               intro_mmodule.html_namespace(page)
-               page.append("::<span>")
-               html_short_link(page)
-               page.append("</span>")
-       end
-
-       # Return a list item for the mclass
-       #       <li>html_link</li>
-       private fun html_sidebar_item(page: NitdocModule) do
-               if page.mmodule.in_nesting.greaters.has(intro.mmodule) then
-                       page.append("<li class='intro'>")
-                       page.append("<span title='Introduced'>I</span>")
-                       html_link_anchor(page)
-               else if page.mmodule.has_mclassdef_for(self) then
-                       page.append("<li class='redef'>")
-                       page.append("<span title='Redefined'>R</span>")
-                       html_link_anchor(page)
-               else
-                       page.append("<li class='inherit'>")
-                       page.append("<span title='Inherited'>H</span>")
-                       html_link(page)
-               end
-               page.append("</li>")
-       end
-
-       private fun html_full_desc(page: NitdocModule) do
-               var is_redef = not page.mmodule.in_nesting.greaters.has(intro.mmodule)
-               var redefs = mpropdefs_in_module(page)
-               if not is_redef or not redefs.is_empty then
-                       var classes = new Array[String]
-                       classes.add(kind.to_s)
-                       if is_redef then classes.add("redef")
-                       classes.add(visibility.to_s)
-                       page.append("<article class='{classes.join(" ")}' id='{anchor}'>")
-                       page.append("<h3 class='signature' data-untyped-signature='{html_name}{html_short_signature}'>")
-                       page.append("<span>")
-                       html_short_link(page)
-                       html_signature(page)
-                       page.append("</span></h3>")
-                       html_info(page)
-                       html_comment(page)
-                       page.append("</article>")
-               end
-       end
-
-       private fun html_info(page: NitdocModule) do
-               page.append("<div class='info'>")
-               if visibility < public_visibility then page.append("{visibility.to_s} ")
-               if not page.mmodule.in_nesting.greaters.has(intro.mmodule) then page.append("redef ")
-               page.append("{kind} ")
-               html_namespace(page)
-               page.append("{html_short_signature}</div>")
-       end
-
-       private fun html_comment(page: NitdocPage) do
-               page.append("<div class='description'>")
-               if page isa NitdocModule then
-                       page.mmodule.linearize_mclassdefs(mclassdefs)
-                       # comments for each mclassdef contained in current mmodule
-                       for mclassdef in mclassdefs do
-                               if not mclassdef.is_intro and not page.mmodule.mclassdefs.has(mclassdef) then continue
-                               if page.ctx.mbuilder.mclassdef2nclassdef.has_key(mclassdef) then
-                                       var nclass = page.ctx.mbuilder.mclassdef2nclassdef[mclassdef]
-                                       if nclass isa AStdClassdef then
-                                               if page.ctx.github_gitdir != null then
-                                                       var loc = nclass.doc_location.github(page.ctx.github_gitdir.as(not null))
-                                                       page.append("<textarea class='baseComment' data-comment-namespace='{mclassdef.mmodule.full_name}::{name}' data-comment-location='{loc}'>{nclass.full_comment}</textarea>")
-                                               end
-                                               if nclass.full_comment == "" then
-                                                       page.append("<p class='info inheritance'>")
-                                                       page.append("<span class=\"noComment\">no comment for </span>")
-                                               else
-                                                       page.append("<div class='comment'>{nclass.full_markdown}</div>")
-                                                       page.append("<p class='info inheritance'>")
-                                               end
-                                               if mclassdef.is_intro then
-                                                       page.append("introduction in ")
-                                               else
-                                                       page.append("refinement in ")
-                                               end
-                                               mclassdef.mmodule.html_full_namespace(page)
-                                               page.append(" {page.show_source(nclass.location)}</p>")
-                                       end
-                               end
-                       end
-               else
-                       # comments for intro
-                       if page.ctx.mbuilder.mclassdef2nclassdef.has_key(intro) then
-                               var nclass = page.ctx.mbuilder.mclassdef2nclassdef[intro]
-                               if nclass isa AStdClassdef then
-                                       if page.ctx.github_gitdir != null then
-                                               var loc = nclass.doc_location.github(page.ctx.github_gitdir.as(not null))
-                                               page.append("<textarea class='baseComment' data-comment-namespace='{intro.mmodule.full_name}::{name}' data-comment-location='{loc}'>{nclass.full_comment}</textarea>")
-                                       end
-                                       if nclass.full_comment == "" then
-                                               page.append("<p class='info inheritance'>")
-                                               page.append("<span class=\"noComment\">no comment for </span>")
-                                       else
-                                               page.append("<div class='comment'>{nclass.full_markdown}</div>")
-                                               page.append("<p class='info inheritance'>")
-                                       end
-                                       page.append("introduction in ")
-                                       intro.mmodule.html_full_namespace(page)
-                                       page.append(" {page.show_source(nclass.location)}</p>")
-                               end
-                       end
-               end
-               page.append("</div>")
-       end
-
-       private fun mpropdefs_in_module(page: NitdocModule): Array[MPropDef] do
-               var res = new Array[MPropDef]
-               page.mmodule.linearize_mclassdefs(mclassdefs)
-               for mclassdef in mclassdefs do
-                       if not page.mmodule.mclassdefs.has(mclassdef) then continue
-                       if mclassdef.is_intro then continue
-                       for mpropdef in mclassdef.mpropdefs do
-                               if mpropdef.mproperty.visibility < page.ctx.min_visibility then continue
-                               if mpropdef isa MAttributeDef then continue
-                               res.add(mpropdef)
-                       end
-               end
-               return res
+       private fun tpl_namespace: Template do
+               var tpl = new Template
+               tpl.add intro_mmodule.tpl_namespace
+               tpl.add "::<span>"
+               tpl.add tpl_short_link
+               tpl.add "</span>"
+               return tpl
+       end
+
+       private fun tpl_namespace_with_signature: Template do
+               var tpl = new Template
+               tpl.add intro.tpl_modifiers
+               tpl.add intro.mmodule.tpl_namespace
+               tpl.add "::"
+               tpl.add nitdoc_name
+               tpl.add tpl_signature
+               return tpl
        end
 end
 
 redef class MProperty
        # Escape name for html output
-       private fun html_name: String do return name.html_escape
+       private fun nitdoc_name: String do return name.html_escape
 
        # Return the property namespace decorated with html
-       #       <span>intro_module::intro_class::html_link</span>
-       private fun html_namespace(page: NitdocPage) do
-               intro_mclassdef.mclass.html_namespace(page)
-               page.append(intro_mclassdef.mclass.html_short_signature)
-               page.append("::<span>")
-               intro.html_link(page)
-               page.append("</span>")
+       private fun tpl_namespace: Template do
+               var tpl = new Template
+               tpl.add intro_mclassdef.mclass.tpl_namespace
+               tpl.add intro_mclassdef.mclass.tpl_short_signature
+               tpl.add "::<span>"
+               tpl.add intro.tpl_link
+               tpl.add "</span>"
+               return tpl
+       end
+
+       private fun tpl_signature: Template is abstract
+end
+
+redef class MMethod
+       redef fun tpl_signature do return intro.msignature.tpl_signature
+end
+
+redef class MVirtualTypeProp
+       redef fun tpl_signature do
+               var tpl = new Template
+               tpl.add ": "
+               tpl.add  intro.bound.tpl_link
+               return tpl
        end
 end
 
 redef class MType
        # Link to the type definition in the nitdoc page
-       private fun html_link(page: NitdocPage) is abstract
+       private fun tpl_link: Template is abstract
 end
 
 redef class MClassType
-       redef fun html_link(page) do mclass.html_link(page)
+       redef fun tpl_link do return mclass.tpl_link
 end
 
 redef class MNullableType
-       redef fun html_link(page) do
-               page.append("nullable ")
-               mtype.html_link(page)
+       redef fun tpl_link do
+               var tpl = new Template
+               tpl.add "nullable "
+               tpl.add mtype.tpl_link
+               return tpl
        end
 end
 
 redef class MGenericType
-       redef fun html_link(page) do
-               page.append("<a href='{mclass.url}'>{mclass.html_name}</a>[")
+       redef fun tpl_link: Template do
+               var tpl = new Template
+               tpl.add mclass.tpl_short_link
+               tpl.add "["
                for i in [0..arguments.length[ do
-                       arguments[i].html_link(page)
-                       if i < arguments.length - 1 then page.append(", ")
+                       tpl.add arguments[i].tpl_link
+                       if i < arguments.length - 1 then tpl.add ", "
                end
-               page.append("]")
+               tpl.add "]"
+               return tpl
        end
 end
 
 redef class MParameterType
-       redef fun html_link(page) do
+       redef fun tpl_link do
                var name = mclass.intro.parameter_names[rank]
-               page.append("<a href='{mclass.url}#FT_{name}' title='formal type'>{name}</a>")
+               var tpl = new TplLink
+               tpl.href = "{mclass.nitdoc_url}#FT_{name}"
+               tpl.text = name
+               tpl.title = "formal type"
+               return tpl
        end
 end
 
 redef class MVirtualType
-       redef fun html_link(page) do mproperty.intro.html_link(page)
+       redef fun tpl_link do return mproperty.intro.tpl_link
 end
 
 redef class MClassDef
        # Return the classdef namespace decorated with html
-       private fun html_namespace(page: NitdocPage) do
-               mmodule.html_full_namespace(page)
-               page.append("::<span>")
-               mclass.html_link(page)
-               page.append("</span>")
+       private fun tpl_namespace: Template do
+               var tpl = new Template
+               tpl.add mmodule.tpl_namespace
+               tpl.add "::<span>"
+               tpl.add mclass.tpl_link
+               tpl.add "</span>"
+               return tpl
+       end
+
+       private fun full_namespace: String do
+               return "{mmodule.full_namespace}::{mclass.nitdoc_name}"
+       end
+
+       private fun tpl_link_anchor: TplLink do return mclass.tpl_link_anchor
+
+       private fun tpl_article: TplArticle do
+               var tpl = new TplArticle
+               tpl.id = mclass.nitdoc_anchor
+               tpl.classes.add_all(tpl_css_classes)
+               tpl.title = new Template
+               tpl.title.add mclass.tpl_short_link
+               tpl.title.add mclass.tpl_signature
+               tpl.subtitle = new Template
+               tpl.subtitle.add tpl_modifiers
+               tpl.subtitle.add tpl_namespace
+               tpl.content = new Template
+               tpl.content.add tpl_definition
+               return tpl
+       end
+
+       private fun tpl_css_classes: Set[String] do
+               var set = new HashSet[String]
+               set.add_all mclass.intro.modifiers
+               set.add_all modifiers
+               return set
+       end
+
+       private fun tpl_modifiers: Template do
+               var tpl = new Template
+               for modifier in modifiers do
+                       if modifier == "public" then continue
+                       tpl.add "{modifier} "
+               end
+               return tpl
+       end
+
+       private fun tpl_short_definition: TplDefinition do
+               var tpl = new TplDefinition
+               tpl.namespace = mmodule.tpl_full_namespace
+               if mdoc != null then
+                       tpl.comment = mdoc.tpl_short_comment
+               end
+               return tpl
+       end
+
+       private fun tpl_definition: TplDefinition do
+               var tpl = new TplDefinition
+               tpl.namespace = mmodule.tpl_full_namespace
+               if mdoc != null then
+                       tpl.comment = mdoc.tpl_comment
+               end
+               return tpl
        end
 end
 
 redef class MPropDef
        # Return the full qualified name of the mpropdef
        #       module::classdef::name
-       private fun full_name: String do
-               return "{mclassdef.mclass.public_owner.name}::{mclassdef.mclass.name}::{mproperty.name}"
+       private fun tpl_namespace: Template do
+               var tpl = new Template
+               tpl.add mclassdef.tpl_namespace
+               tpl.add "::"
+               tpl.add mproperty.name
+               return tpl
+       end
+
+       private fun full_namespace: String do
+               return "{mclassdef.full_namespace}::{mproperty.nitdoc_name}"
        end
 
        # URL into the nitdoc page
-       #       class_owner_name.html#anchor
-       private fun url: String do
-               if url_cache == null then
-                       url_cache = "{mclassdef.mclass.url}#{anchor}"
-               end
-               return url_cache.as(not null)
+       #       class_owner_name.html#nitdoc_anchor
+       private fun nitdoc_url: String do
+               return "{mclassdef.mclass.nitdoc_url}#{nitdoc_anchor}"
        end
-       private var url_cache: nullable String
 
-       # html anchor id for the property in a nitdoc class page
+       # html nitdoc_anchor id for the property in a nitdoc class page
        #       PROP_mclass_propertyname
-       private fun anchor: String do
-               if anchor_cache == null then
-                       anchor_cache = "PROP_{mclassdef.mclass.public_owner.name}_{mproperty.name.replace(" ", "_")}"
-               end
-               return anchor_cache.as(not null)
+       private fun nitdoc_anchor: String do
+               return "PROP_{mclassdef.mclass.public_owner.nitdoc_name}_{mproperty.name.replace(" ", "_")}"
        end
-       private var anchor_cache: nullable String
 
        # Return a link to property into the nitdoc class page
-       #       <a href="url" title="short_comment">html_name</a>
-       private fun html_link(page: NitdocPage) do
-               if html_link_cache == null then
-                       var res = new FlatBuffer
-                       if page.ctx.mbuilder.mpropdef2npropdef.has_key(self) then
-                               var nprop = page.ctx.mbuilder.mpropdef2npropdef[self]
-                               res.append("<a href=\"{url}\" title=\"{nprop.short_comment}\">{mproperty.html_name}</a>")
-                       else
-                               res.append("<a href=\"{url}\">{mproperty.html_name}</a>")
-                       end
-                       html_link_cache = res.to_s
+       #       <a href="nitdoc_url" title="short_comment">nitdoc_name</a>
+       private fun tpl_link: TplLink do
+               var tpl = new TplLink
+               tpl.href = nitdoc_url
+               tpl.text = mproperty.nitdoc_name
+               if mproperty.intro.mdoc != null then
+                       tpl.title = mproperty.intro.mdoc.short_comment
                end
-               page.append(html_link_cache.as(not null))
+               return tpl
        end
-       private var html_link_cache: nullable String
 
-       # Return a list item for the mpropdef
-       #       <li>html_link</li>
-       private fun html_sidebar_item(page: NitdocClass) do
-               if is_intro and mclassdef.mclass == page.mclass then
-                       page.append("<li class='intro'>")
-                       page.append("<span title='Introduced'>I</span>")
-               else if is_intro and mclassdef.mclass != page.mclass then
-                       page.append("<li class='inherit'>")
-                       page.append("<span title='Inherited'>H</span>")
-               else
-                       page.append("<li class='redef'>")
-                       page.append("<span title='Redefined'>R</span>")
-               end
-               html_link(page)
-               page.append("</li>")
+       private fun tpl_article: TplArticle do
+               var tpl = new TplArticle
+               tpl.id = nitdoc_anchor
+               tpl.classes.add_all(tpl_css_classes)
+               tpl.title = new Template
+               tpl.title.add mproperty.nitdoc_name
+               tpl.title.add mproperty.tpl_signature
+               tpl.subtitle = new Template
+               tpl.subtitle.add tpl_modifiers
+               tpl.subtitle.add tpl_namespace
+               tpl.content = new Template
+               tpl.content.add tpl_definition
+               return tpl
        end
 
-       private fun html_full_desc(page: NitdocPage, ctx: MClass) is abstract
-       private fun html_info(page: NitdocPage, ctx: MClass) is abstract
+       private fun tpl_css_classes: Set[String] do
+               var set = new HashSet[String]
+               set.add_all mproperty.intro.modifiers
+               set.add_all modifiers
+               return set
+       end
 
-       private fun html_comment(page: NitdocPage) do
-               page.append("<div class='description'>")
-               if not is_intro then
-                       if page.ctx.mbuilder.mpropdef2npropdef.has_key(mproperty.intro) then
-                               var intro_nprop = page.ctx.mbuilder.mpropdef2npropdef[mproperty.intro]
-                               if page.ctx.github_gitdir != null then
-                                       var loc = intro_nprop.doc_location.github(page.ctx.github_gitdir.as(not null))
-                                       page.append("<textarea class='baseComment' data-comment-namespace='{mproperty.intro.mclassdef.mmodule.full_name}::{mproperty.intro.mclassdef.mclass.name}::{mproperty.name}' data-comment-location='{loc}'>{intro_nprop.full_comment}</textarea>")
-                               end
-                               if intro_nprop.full_comment.is_empty then
-                                       page.append("<p class='info inheritance'>")
-                                       page.append("<span class=\"noComment\">no comment for </span>")
-                               else
-                                       page.append("<div class='comment'>{intro_nprop.full_markdown}</div>")
-                                       page.append("<p class='info inheritance'>")
-                               end
-                               page.append("introduction in ")
-                               mproperty.intro.mclassdef.html_namespace(page)
-                               page.append(" {page.show_source(intro_nprop.location)}</p>")
-                       end
+       private fun tpl_modifiers: Template do
+               var tpl = new Template
+               for modifier in modifiers do
+                       if modifier == "public" then continue
+                       tpl.add "{modifier} "
                end
-               if page.ctx.mbuilder.mpropdef2npropdef.has_key(self) then
-                       var nprop = page.ctx.mbuilder.mpropdef2npropdef[self]
-                       if page.ctx.github_gitdir != null then
-                               var loc = nprop.doc_location.github(page.ctx.github_gitdir.as(not null))
-                               page.append("<textarea class='baseComment' data-comment-namespace='{mclassdef.mmodule.full_name}::{mclassdef.mclass.name}::{mproperty.name}' data-comment-location='{loc}'>{nprop.full_comment}</textarea>")
-                       end
-                       if nprop.full_comment == "" then
-                               page.append("<p class='info inheritance'>")
-                               page.append("<span class=\"noComment\">no comment for </span>")
-                       else
-                               page.append("<div class='comment'>{nprop.full_markdown}</div>")
-                               page.append("<p class='info inheritance'>")
-                       end
-                       if is_intro then
-                               page.append("introduction in ")
-                       else
-                               page.append("redefinition in ")
-                       end
-                       mclassdef.html_namespace(page)
-                       page.append(" {page.show_source(nprop.location)}</p>")
-               end
-               page.append("</div>")
+               return tpl
        end
-end
 
-redef class MMethodDef
-       redef fun html_full_desc(page, ctx) do
-               var classes = new Array[String]
-               var is_redef = mproperty.intro_mclassdef.mclass != ctx
-               if mproperty.is_init then
-                       classes.add("init")
-               else
-                       classes.add("fun")
-               end
-               if is_redef then classes.add("redef")
-               classes.add(mproperty.visibility.to_s)
-               page.append("<article class='{classes.join(" ")}' id='{anchor}'>")
-               if page.ctx.mbuilder.mpropdef2npropdef.has_key(self) then
-                       page.append("<h3 class='signature' data-untyped-signature='{mproperty.name}{msignature.untyped_signature(page)}'>")
-                       page.append("<span>{mproperty.html_name}")
-                       msignature.html_signature(page)
-                       page.append("</span></h3>")
-               else
-                       page.append("<h3 class='signature' data-untyped-signature='init{msignature.untyped_signature(page)}'>")
-                       page.append("<span>init")
-                       msignature.html_signature(page)
-                       page.append("</span></h3>")
-               end
-               html_info(page, ctx)
-               html_comment(page)
-               page.append("</article>")
-       end
-
-       redef fun html_info(page, ctx) do
-               page.append("<div class='info'>")
-               if mproperty.visibility < public_visibility then page.append("{mproperty.visibility.to_s} ")
-               if mproperty.intro_mclassdef.mclass != ctx then page.append("redef ")
-               if mproperty.is_init then
-                       page.append("init ")
-               else
-                       page.append("fun ")
+       private fun tpl_short_definition: TplDefinition do
+               var tpl = new TplDefinition
+               tpl.namespace = mclassdef.tpl_namespace
+               if mdoc != null then
+                       tpl.comment = mdoc.tpl_short_comment
                end
-               mproperty.html_namespace(page)
-               page.append("</div>")
+               return tpl
        end
-end
 
-redef class MVirtualTypeDef
-       redef fun html_full_desc(page, ctx) do
-               var is_redef = mproperty.intro_mclassdef.mclass != ctx
-               var classes = new Array[String]
-               classes.add("type")
-               if is_redef then classes.add("redef")
-               classes.add(mproperty.visibility.to_s)
-               page.append("<article class='{classes.join(" ")}' id='{anchor}'>")
-               page.append("<h3 class='signature' data-untyped-signature='{mproperty.name}'><span>{mproperty.html_name}: ")
-               bound.html_link(page)
-               page.append("</span></h3>")
-               html_info(page, ctx)
-               html_comment(page)
-               page.append("</article>")
-       end
-
-       redef fun html_info(page, ctx) do
-               page.append("<div class='info'>")
-               if mproperty.intro_mclassdef.mclass != ctx then page.append("redef ")
-               page.append("type ")
-               mproperty.html_namespace(page)
-               page.append("</div>")
+       private fun tpl_definition: TplDefinition do
+               var tpl = new TplDefinition
+               tpl.namespace = mclassdef.tpl_namespace
+               if mdoc != null then
+                       tpl.comment = mdoc.tpl_comment
+               end
+               return tpl
        end
 end
 
 redef class MSignature
-       private fun html_signature(page: NitdocPage) do
+       private fun tpl_signature: Template do
+               var tpl = new Template
                if not mparameters.is_empty then
-                       page.append("(")
+                       tpl.add "("
                        for i in [0..mparameters.length[ do
-                               mparameters[i].html_link(page)
-                               if i < mparameters.length - 1 then page.append(", ")
+                               tpl.add mparameters[i].tpl_link
+                               if i < mparameters.length - 1 then tpl.add ", "
                        end
-                       page.append(")")
+                       tpl.add ")"
                end
                if return_mtype != null then
-                       page.append(": ")
-                       return_mtype.html_link(page)
-               end
-       end
-
-       private fun untyped_signature(page: NitdocPage): String do
-               var res = new FlatBuffer
-               if not mparameters.is_empty then
-                       res.append("(")
-                       for i in [0..mparameters.length[ do
-                               res.append(mparameters[i].name)
-                               if i < mparameters.length - 1 then res.append(", ")
-                       end
-                       res.append(")")
+                       tpl.add ": "
+                       tpl.add return_mtype.tpl_link
                end
-               return res.to_s
+               return tpl
        end
 end
 
 redef class MParameter
-       private fun html_link(page: NitdocPage) do
-               page.append("{name}: ")
-               mtype.html_link(page)
-               if is_vararg then page.append("...")
+       private fun tpl_link: Template do
+               var tpl = new Template
+               tpl.add "{name}: "
+               tpl.add mtype.tpl_link
+               if is_vararg then tpl.add "..."
+               return tpl
        end
 end
 
-#
-# Nodes redefs
-#
-
 redef class Location
        fun github(gitdir: String): String do
                var base_dir = getcwd.join_path(gitdir).simplify_path
@@ -1770,104 +1576,24 @@ redef class Location
        end
 end
 
-redef class ADoc
-       private fun short_comment: String do
-               return n_comment.first.text.substring_from(2).replace("\n", "").html_escape
-       end
-
-       private fun full_comment: String do
-               var res = new FlatBuffer
-               for t in n_comment do
-                       var text = t.text
-                       text = text.substring_from(1)
-                       if text.chars.first == ' ' then text = text.substring_from(1)
-                       res.append(text.html_escape)
-               end
-               var str = res.to_s
-               return str.substring(0, str.length - 1)
-       end
-end
-
-redef class AModule
-       private fun short_comment: String do
-               if n_moduledecl != null and n_moduledecl.n_doc != null then
-                       return n_moduledecl.n_doc.short_comment
-               end
-               return ""
-       end
-
-       private fun full_comment: String do
-               if n_moduledecl != null and n_moduledecl.n_doc != null then
-                       return n_moduledecl.n_doc.full_comment
-               end
-               return ""
-       end
-
-       private fun full_markdown: String do
-               if n_moduledecl != null and n_moduledecl.n_doc != null then
-                       return n_moduledecl.n_doc.to_mdoc.full_markdown.write_to_string
-               end
-               return ""
-       end
-
-       # The doc location or the first line of the block if doc node is null
-       private fun doc_location: Location do
-               if n_moduledecl != null and n_moduledecl.n_doc != null then
-                       return n_moduledecl.n_doc.location
-               end
-               var l = location
-               return new Location(l.file, l.line_start, l.line_start, l.column_start, l.column_start)
-       end
-end
-
-redef class AStdClassdef
+redef class MDoc
        private fun short_comment: String do
-               if n_doc != null then return n_doc.short_comment
-               return ""
+               return content.first.html_escape
        end
 
        private fun full_comment: String do
-               if n_doc != null then return n_doc.full_comment
-               return ""
+               return content.join("\n").html_escape
        end
 
-       private fun full_markdown: String do
-               if n_doc != null then return n_doc.to_mdoc.full_markdown.write_to_string
-               return ""
+       private fun tpl_short_comment: TplShortComment do
+               return new TplShortComment(short_markdown)
        end
 
-       # The doc location or the first line of the block if doc node is null
-       private fun doc_location: Location do
-               if n_doc != null then return n_doc.location
-               var l = location
-               return new Location(l.file, l.line_start, l.line_start, l.column_start, l.column_start)
+       private fun tpl_comment: TplComment do
+               return new TplComment(full_markdown)
        end
 end
 
-redef class APropdef
-       private fun short_comment: String do
-               if n_doc != null then return n_doc.short_comment
-               return ""
-       end
-
-       private fun full_comment: String do
-               if n_doc != null then return n_doc.full_comment
-               return ""
-       end
-
-       private fun full_markdown: String do
-               if n_doc != null then return n_doc.to_mdoc.full_markdown.write_to_string
-               return ""
-       end
-
-       private fun doc_location: Location do
-               if n_doc != null then return n_doc.location
-               var l = location
-               return new Location(l.file, l.line_start, l.line_start, l.column_start, l.column_start)
-
-       end
-end
-
-
 var nitdoc = new NitdocContext
 nitdoc.generate_nitdoc
+