Merge: Added contributing guidelines and link from readme
[nit.git] / contrib / nitiwiki / src / wiki_html.nit
index c984bc8..15ae4d7 100644 (file)
 # HTML wiki rendering
 module wiki_html
 
-import wiki_base
+import wiki_links
+import markdown::decorators
 
 redef class Nitiwiki
 
        # Render HTML output looking for changes in the markdown sources.
-       fun render do
+       redef fun render do
+               super
                if not root_section.is_dirty and not force_render then return
                var out_dir = expand_path(config.root_dir, config.out_dir)
                out_dir.mkdir
@@ -34,7 +36,7 @@ redef class Nitiwiki
                var src = expand_path(config.root_dir, config.assets_dir)
                var out = expand_path(config.root_dir, config.out_dir)
                if need_render(src, expand_path(out, config.assets_dir)) then
-                       if src.file_exists then sys.system "cp -R {src} {out}"
+                       if src.file_exists then sys.system "cp -R -- {src.escape_to_sh} {out.escape_to_sh}"
                end
        end
 
@@ -44,23 +46,32 @@ redef class Nitiwiki
                sitemap.is_dirty = true
                return sitemap
        end
-end
 
-redef class WikiEntry
+       # Markdown processor used for inline element such as titles in TOC.
+       private var inline_processor: MarkdownProcessor is lazy do
+               var proc = new MarkdownProcessor
+               proc.emitter.decorator = new InlineDecorator
+               return proc
+       end
 
-       # Url to `self` once generated.
-       fun url: String do return wiki.config.root_url.join_path(breadcrumbs.join("/"))
+       # Inline markdown (remove h1, p, ... elements).
+       private fun inline_md(md: Writable): Writable do
+               return inline_processor.process(md.write_to_string)
+       end
+end
 
+redef class WikiEntry
        # Get a `<a>` template link to `self`
-       fun tpl_link: Streamable do
-               return "<a href=\"{url}\">{title}</a>"
+       fun tpl_link(context: WikiEntry): Writable do
+               return "<a href=\"{href_from(context)}\">{title}</a>"
        end
 end
 
 redef class WikiSection
 
        # Output directory (where to ouput the HTML pages for this section).
-       redef fun out_path: String do
+       redef fun out_path do
+               var parent = self.parent
                if parent == null then
                        return wiki.config.out_dir
                else
@@ -73,24 +84,28 @@ redef class WikiSection
                if is_new then
                        out_full_path.mkdir
                else
-                       sys.system "touch {out_full_path}"
+                       sys.system "touch -- {out_full_path.escape_to_sh}"
                end
                if has_source then
-                       wiki.message("Render section {out_path}", 1)
+                       wiki.message("Render section {name} -> {out_path}", 1)
                        copy_files
                end
                var index = self.index
                if index isa WikiSectionIndex then
+                       wiki.message("Render auto-index for section {name} -> {out_path}", 1)
                        index.is_dirty = true
                        add_child index
                end
+               # Hack: Force the rendering of `index` first so that trails are collected
+               # TODO: Add first-pass analysis to collect global information before doing the rendering
+               index.render
                super
        end
 
        # Copy attached files from `src_path` to `out_path`.
        private fun copy_files do
                assert has_source
-               var dir = src_full_path.to_s
+               var dir = src_full_path.as(not null).to_s
                for name in dir.files do
                        if name == wiki.config_filename then continue
                        if name.has_suffix(".md") then continue
@@ -98,22 +113,11 @@ redef class WikiSection
                        var src = wiki.expand_path(dir, name)
                        var out = wiki.expand_path(out_full_path, name)
                        if not wiki.need_render(src, out) then continue
-                       sys.system "cp -R {src} {out_full_path}"
+                       sys.system "cp -R -- {src.escape_to_sh} {out_full_path.escape_to_sh}"
                end
        end
 
-       # The index page for this section.
-       #
-       # If no file `index.md` exists for this section,
-       # a summary is generated using contained articles.
-       fun index: WikiArticle is cached do
-               for child in children.values do
-                       if child isa WikiArticle and child.is_index then return child
-               end
-               return new WikiSectionIndex(wiki, self)
-       end
-
-       redef fun tpl_link do return index.tpl_link
+       redef fun tpl_link(context) do return index.tpl_link(context)
 
        # Render the section hierarchy as a html tree.
        #
@@ -121,25 +125,27 @@ redef class WikiSection
        #
        # The generated tree will be something like this:
        #
-       #    <ul>
-       #     <li>section 1</li>
-       #     <li>section 2
-       #      <ul>
-       #       <li>section 2.1</li>
-       #       <li>section 2.2</li>
-       #      </ul>
-       #     </li>
-       #    </ul>
+       # ~~~html
+       # <ul>
+       #  <li>section 1</li>
+       #  <li>section 2
+       #   <ul>
+       #    <li>section 2.1</li>
+       #    <li>section 2.2</li>
+       #   </ul>
+       #  </li>
+       # </ul>
+       # ~~~
        fun tpl_tree(limit: Int): Template do
-               return tpl_tree_intern(limit, 1)
+               return tpl_tree_intern(limit, 1, self)
        end
 
        # Build the template tree for this section recursively.
-       protected fun tpl_tree_intern(limit, count: Int): Template do
+       protected fun tpl_tree_intern(limit, count: Int, context: WikiEntry): Template do
                var out = new Template
                var index = index
                out.add "<li>"
-               out.add tpl_link
+               out.add tpl_link(context)
                if (limit < 0 or count < limit) and
                   (children.length > 1 or (children.length == 1)) then
                        out.add " <ul>"
@@ -147,10 +153,10 @@ redef class WikiSection
                                if child == index then continue
                                if child isa WikiArticle then
                                        out.add "<li>"
-                                       out.add child.tpl_link
+                                       out.add child.tpl_link(context)
                                        out.add "</li>"
                                else if child isa WikiSection and not child.is_hidden then
-                                       out.add child.tpl_tree_intern(limit, count + 1)
+                                       out.add child.tpl_tree_intern(limit, count + 1, context)
                                end
                        end
                        out.add " </ul>"
@@ -162,7 +168,8 @@ end
 
 redef class WikiArticle
 
-       redef fun out_path: String do
+       redef fun out_path do
+               var parent = self.parent
                if parent == null then
                        return wiki.expand_path(wiki.config.out_dir, "{name}.html")
                else
@@ -170,32 +177,28 @@ redef class WikiArticle
                end
        end
 
-       redef fun url do
-               if parent == null then
-                       return wiki.config.root_url.join_path("{name}.html")
-               else
-                       return parent.url.join_path("{name}.html")
-               end
-       end
-
-       # Is `self` an index page?
-       #
-       # Checks if `self.name == "index"`.
-       fun is_index: Bool do return name == "index"
-
        redef fun render do
+               super
                if not is_dirty and not wiki.force_render then return
-               wiki.message("Render article {name}", 2)
                var file = out_full_path
+               wiki.message("Render article {name} -> {file}", 1)
                file.dirname.mkdir
                tpl_page.write_to_file file
-               super
        end
 
 
+       # Load a template and resolve page-related macros
+       fun load_template(template_file: String): TemplateString do
+               var tpl = wiki.load_template(template_file)
+               if tpl.has_macro("ROOT_URL") then
+                       tpl.replace("ROOT_URL", root_href)
+               end
+               return tpl
+       end
+
        # Replace macros in the template by wiki data.
        private fun tpl_page: TemplateString do
-               var tpl = wiki.load_template(template_file)
+               var tpl = load_template(template_file)
                if tpl.has_macro("TOP_MENU") then
                        tpl.replace("TOP_MENU", tpl_menu)
                end
@@ -208,33 +211,45 @@ redef class WikiArticle
                if tpl.has_macro("FOOTER") then
                        tpl.replace("FOOTER", tpl_footer)
                end
+               if tpl.has_macro("TRAIL") then
+                       tpl.replace("TRAIL", tpl_trail)
+               end
                return tpl
        end
 
        # Generate the HTML header for this article.
-       fun tpl_header: Streamable do
+       fun tpl_header: Writable do
                var file = header_file
                if not wiki.has_template(file) then return ""
-               return wiki.load_template(file)
+               return load_template(file)
        end
 
        # Generate the HTML page for this article.
        fun tpl_article: TplArticle do
                var article = new TplArticle
                article.body = content
-               article.breadcrumbs = new TplBreadcrumbs(self)
-               tpl_sidebar.blocks.add tpl_summary
+               if wiki.config.auto_breadcrumbs then
+                       article.breadcrumbs = new TplBreadcrumbs(self)
+               end
                article.sidebar = tpl_sidebar
+               article.sidebar_pos = wiki.config.sidebar
                return article
        end
 
        # Sidebar for this page.
-       var tpl_sidebar = new TplSidebar
+       var tpl_sidebar: TplSidebar is lazy do
+               var res = new TplSidebar
+               if wiki.config.auto_summary then
+                       res.blocks.add tpl_summary
+               end
+               res.blocks.add_all sidebar.blocks
+               return res
+       end
 
        # Generate the HTML summary for this article.
        #
        # Based on `headlines`
-       fun tpl_summary: Streamable do
+       fun tpl_summary: Writable do
                var headlines = self.headlines
                var tpl = new Template
                tpl.add "<ul class=\"summary list-unstyled\">"
@@ -242,8 +257,7 @@ redef class WikiArticle
                while iter.is_ok do
                        var hl = iter.item
                        # parse title as markdown
-                       var title = hl.title.md_to_html.to_s
-                       title = title.substring(3, title.length - 8)
+                       var title = wiki.inline_md(hl.title)
                        tpl.add "<li><a href=\"#{hl.id}\">{title}</a>"
                        iter.next
                        if iter.is_ok then
@@ -262,10 +276,10 @@ redef class WikiArticle
        end
 
        # Generate the HTML menu for this article.
-       fun tpl_menu: Streamable do
+       fun tpl_menu: Writable do
                var file = menu_file
                if not wiki.has_template(file) then return ""
-               var tpl = wiki.load_template(file)
+               var tpl = load_template(file)
                if tpl.has_macro("MENUS") then
                        var items = new Template
                        for child in wiki.root_section.children.values do
@@ -276,7 +290,7 @@ redef class WikiArticle
                                        items.add " class=\"active\""
                                end
                                items.add ">"
-                               items.add child.tpl_link
+                               items.add child.tpl_link(self)
                                items.add "</li>"
                        end
                        tpl.replace("MENUS", items)
@@ -284,11 +298,46 @@ redef class WikiArticle
                return tpl
        end
 
+       # Generate navigation links for the trail of this article, if any.
+       #
+       # A trail is generated if the article include or is included in a trail.
+       # See `wiki.trails` for details.
+       fun tpl_trail: Writable do
+               if not wiki.trails.has(self) then return ""
+
+               # Get the position of `self` in the trail
+               var flat = wiki.trails.to_a
+               var pos = flat.index_of(self)
+               assert pos >= 0
+
+               var res = new Template
+               res.add "<ul class=\"trail\">"
+               var parent = wiki.trails.parent(self)
+               # Up and prev are disabled on a root
+               if parent != null then
+                       if pos > 0 then
+                               var target = flat[pos-1]
+                               res.add "<li>{target.a_from(self, "prev")}</li>"
+                       end
+                       res.add "<li>{parent.a_from(self, "up")}</li>"
+               end
+               if pos < flat.length - 1 then
+                       var target = flat[pos+1]
+                       # Only print the next if it is not a root
+                       if target.parent != null then
+                               res.add "<li>{target.a_from(self, "next")}</li>"
+                       end
+               end
+               res.add "</ul>"
+
+               return res
+       end
+
        # Generate the HTML footer for this article.
-       fun tpl_footer: Streamable do
+       fun tpl_footer: Writable do
                var file = footer_file
                if not wiki.has_template(file) then return ""
-               var tpl = wiki.load_template(file)
+               var tpl = load_template(file)
                var time = new Tm.gmtime
                if tpl.has_macro("YEAR") then
                        tpl.replace("YEAR", (time.year + 1900).to_s)
@@ -296,6 +345,14 @@ redef class WikiArticle
                if tpl.has_macro("GEN_TIME") then
                        tpl.replace("GEN_TIME", time.to_s)
                end
+               if tpl.has_macro("LAST_CHANGES") then
+                       var url = "{wiki.config.last_changes}{src_path or else ""}"
+                       tpl.replace("LAST_CHANGES", url)
+               end
+               if tpl.has_macro("EDIT") then
+                       var url = "{wiki.config.edit}{src_path or else ""}"
+                       tpl.replace("EDIT", url)
+               end
                return tpl
        end
 end
@@ -314,16 +371,7 @@ class WikiSitemap
 end
 
 # A `WikiArticle` that contains the section index tree.
-class WikiSectionIndex
-       super WikiArticle
-
-       # The section described by `self`.
-       var section: WikiSection
-
-       init(wiki: Nitiwiki, section: WikiSection) do
-               super(wiki, "index")
-               self.section = section
-       end
+redef class WikiSectionIndex
 
        redef var is_dirty = false
 
@@ -339,23 +387,34 @@ end
 class TplArticle
        super Template
 
-       var title: nullable Streamable = null
-       var body: nullable Streamable = null
+       # Article title.
+       var title: nullable Writable = null
+
+       # Article HTML body.
+       var body: nullable Writable = null
+
+       # Sidebar of this article (if any).
        var sidebar: nullable TplSidebar = null
+
+       # Position of the sidebar.
+       #
+       # See `WikiConfig::sidebar`.
+       var sidebar_pos: String = "left"
+
+       # Breadcrumbs from wiki root to this article.
        var breadcrumbs: nullable TplBreadcrumbs = null
 
-       init with_title(title: Streamable) do
+       # Init `self` with a `title`.
+       init with_title(title: Writable) do
                self.title = title
        end
 
        redef fun rendering do
-               if sidebar != null then
-                       add "<div class=\"col-sm-3 sidebar\">"
-                       add sidebar.as(not null)
-                       add "</div>"
-                       add "<div class=\"col-sm-9 content\">"
-               else
+               if sidebar_pos == "left" then render_sidebar
+               if sidebar == null then
                        add "<div class=\"col-sm-12 content\">"
+               else
+                       add "<div class=\"col-sm-9 content\">"
                end
                if body != null then
                        add "<article>"
@@ -371,6 +430,14 @@ class TplArticle
                        add " </article>"
                end
                add "</div>"
+               if sidebar_pos == "right" then render_sidebar
+       end
+
+       private fun render_sidebar do
+               if sidebar == null then return
+               add "<div class=\"col-sm-3 sidebar\">"
+               add sidebar.as(not null)
+               add "</div>"
        end
 end
 
@@ -379,13 +446,13 @@ class TplSidebar
        super Template
 
        # Blocks are `Stremable` pieces that will be rendered in the sidebar.
-       var blocks = new Array[Streamable]
+       var blocks = new Array[Writable]
 
        redef fun rendering do
                for block in blocks do
-                       add "<div class=\"sideblock\">"
+                       add "<nav class=\"sideblock\">"
                        add block
-                       add "</div>"
+                       add "</nav>"
                end
        end
 end
@@ -409,7 +476,7 @@ class TplBreadcrumbs
                        else
                                if article.parent == entry and article.is_index then continue
                                add "<li>"
-                               add entry.tpl_link
+                               add entry.tpl_link(article)
                                add "</li>"
                        end
                end