nitiwiki: implements html generation.
authorAlexandre Terrasa <alexandre@moz-code.org>
Tue, 23 Sep 2014 00:10:41 +0000 (20:10 -0400)
committerAlexandre Terrasa <alexandre@moz-code.org>
Mon, 24 Nov 2014 18:52:24 +0000 (13:52 -0500)
nitiwiki can now render wiki in html

Signed-off-by: Alexandre Terrasa <alexandre@moz-code.org>

contrib/nitiwiki/src/wiki_html.nit [new file with mode: 0644]

diff --git a/contrib/nitiwiki/src/wiki_html.nit b/contrib/nitiwiki/src/wiki_html.nit
new file mode 100644 (file)
index 0000000..c984bc8
--- /dev/null
@@ -0,0 +1,435 @@
+# 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 wiki rendering
+module wiki_html
+
+import wiki_base
+
+redef class Nitiwiki
+
+       # Render HTML output looking for changes in the markdown sources.
+       fun render do
+               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
+               copy_assets
+               root_section.add_child make_sitemap
+               root_section.render
+       end
+
+       # Copy the asset directory to the (public) output directory.
+       private fun copy_assets do
+               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}"
+               end
+       end
+
+       # Build the wiki sitemap page.
+       private fun make_sitemap: WikiSitemap do
+               var sitemap = new WikiSitemap(self, "sitemap")
+               sitemap.is_dirty = true
+               return sitemap
+       end
+end
+
+redef class WikiEntry
+
+       # Url to `self` once generated.
+       fun url: String do return wiki.config.root_url.join_path(breadcrumbs.join("/"))
+
+       # Get a `<a>` template link to `self`
+       fun tpl_link: Streamable do
+               return "<a href=\"{url}\">{title}</a>"
+       end
+end
+
+redef class WikiSection
+
+       # Output directory (where to ouput the HTML pages for this section).
+       redef fun out_path: String do
+               if parent == null then
+                       return wiki.config.out_dir
+               else
+                       return wiki.expand_path(parent.out_path, name)
+               end
+       end
+
+       redef fun render do
+               if not is_dirty and not wiki.force_render then return
+               if is_new then
+                       out_full_path.mkdir
+               else
+                       sys.system "touch {out_full_path}"
+               end
+               if has_source then
+                       wiki.message("Render section {out_path}", 1)
+                       copy_files
+               end
+               var index = self.index
+               if index isa WikiSectionIndex then
+                       index.is_dirty = true
+                       add_child index
+               end
+               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
+               for name in dir.files do
+                       if name == wiki.config_filename then continue
+                       if name.has_suffix(".md") then continue
+                       if has_child(name) then continue
+                       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}"
+               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
+
+       # Render the section hierarchy as a html tree.
+       #
+       # `limit` is used to specify the max-depth of the tree.
+       #
+       # 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>
+       fun tpl_tree(limit: Int): Template do
+               return tpl_tree_intern(limit, 1)
+       end
+
+       # Build the template tree for this section recursively.
+       protected fun tpl_tree_intern(limit, count: Int): Template do
+               var out = new Template
+               var index = index
+               out.add "<li>"
+               out.add tpl_link
+               if (limit < 0 or count < limit) and
+                  (children.length > 1 or (children.length == 1)) then
+                       out.add " <ul>"
+                       for child in children.values do
+                               if child == index then continue
+                               if child isa WikiArticle then
+                                       out.add "<li>"
+                                       out.add child.tpl_link
+                                       out.add "</li>"
+                               else if child isa WikiSection and not child.is_hidden then
+                                       out.add child.tpl_tree_intern(limit, count + 1)
+                               end
+                       end
+                       out.add " </ul>"
+               end
+               out.add "</li>"
+               return out
+       end
+end
+
+redef class WikiArticle
+
+       redef fun out_path: String do
+               if parent == null then
+                       return wiki.expand_path(wiki.config.out_dir, "{name}.html")
+               else
+                       return wiki.expand_path(parent.out_path, "{name}.html")
+               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
+               if not is_dirty and not wiki.force_render then return
+               wiki.message("Render article {name}", 2)
+               var file = out_full_path
+               file.dirname.mkdir
+               tpl_page.write_to_file file
+               super
+       end
+
+
+       # Replace macros in the template by wiki data.
+       private fun tpl_page: TemplateString do
+               var tpl = wiki.load_template(template_file)
+               if tpl.has_macro("TOP_MENU") then
+                       tpl.replace("TOP_MENU", tpl_menu)
+               end
+               if tpl.has_macro("HEADER") then
+                       tpl.replace("HEADER", tpl_header)
+               end
+               if tpl.has_macro("BODY") then
+                       tpl.replace("BODY", tpl_article)
+               end
+               if tpl.has_macro("FOOTER") then
+                       tpl.replace("FOOTER", tpl_footer)
+               end
+               return tpl
+       end
+
+       # Generate the HTML header for this article.
+       fun tpl_header: Streamable do
+               var file = header_file
+               if not wiki.has_template(file) then return ""
+               return wiki.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
+               article.sidebar = tpl_sidebar
+               return article
+       end
+
+       # Sidebar for this page.
+       var tpl_sidebar = new TplSidebar
+
+       # Generate the HTML summary for this article.
+       #
+       # Based on `headlines`
+       fun tpl_summary: Streamable do
+               var headlines = self.headlines
+               var tpl = new Template
+               tpl.add "<ul class=\"summary list-unstyled\">"
+               var iter = headlines.iterator
+               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)
+                       tpl.add "<li><a href=\"#{hl.id}\">{title}</a>"
+                       iter.next
+                       if iter.is_ok then
+                               if iter.item.level > hl.level then
+                                       tpl.add "<ul class=\"list-unstyled\">"
+                               else if iter.item.level < hl.level then
+                                       tpl.add "</li>"
+                                       tpl.add "</ul>"
+                               end
+                       else
+                               tpl.add "</li>"
+                       end
+               end
+               tpl.add "</ul>"
+               return tpl
+       end
+
+       # Generate the HTML menu for this article.
+       fun tpl_menu: Streamable do
+               var file = menu_file
+               if not wiki.has_template(file) then return ""
+               var tpl = wiki.load_template(file)
+               if tpl.has_macro("MENUS") then
+                       var items = new Template
+                       for child in wiki.root_section.children.values do
+                               if child isa WikiArticle and child.is_index then continue
+                               if child isa WikiSection and child.is_hidden then continue
+                               items.add "<li"
+                               if self == child or self.breadcrumbs.has(child) then
+                                       items.add " class=\"active\""
+                               end
+                               items.add ">"
+                               items.add child.tpl_link
+                               items.add "</li>"
+                       end
+                       tpl.replace("MENUS", items)
+               end
+               return tpl
+       end
+
+       # Generate the HTML footer for this article.
+       fun tpl_footer: Streamable do
+               var file = footer_file
+               if not wiki.has_template(file) then return ""
+               var tpl = wiki.load_template(file)
+               var time = new Tm.gmtime
+               if tpl.has_macro("YEAR") then
+                       tpl.replace("YEAR", (time.year + 1900).to_s)
+               end
+               if tpl.has_macro("GEN_TIME") then
+                       tpl.replace("GEN_TIME", time.to_s)
+               end
+               return tpl
+       end
+end
+
+# A `WikiArticle` that contains the sitemap tree.
+class WikiSitemap
+       super WikiArticle
+
+       redef fun tpl_article do
+               var article = new TplArticle.with_title("Sitemap")
+               article.body = new TplPageTree(wiki.root_section, -1)
+               return article
+       end
+
+       redef var is_dirty = false
+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 var is_dirty = false
+
+       redef fun tpl_article do
+               var article = new TplArticle.with_title(section.title)
+               article.body = new TplPageTree(section, -1)
+               article.breadcrumbs = new TplBreadcrumbs(self)
+               return article
+       end
+end
+
+# Article HTML output.
+class TplArticle
+       super Template
+
+       var title: nullable Streamable = null
+       var body: nullable Streamable = null
+       var sidebar: nullable TplSidebar = null
+       var breadcrumbs: nullable TplBreadcrumbs = null
+
+       init with_title(title: Streamable) 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
+                       add "<div class=\"col-sm-12 content\">"
+               end
+               if body != null then
+                       add "<article>"
+                       if breadcrumbs != null then
+                               add breadcrumbs.as(not null)
+                       end
+                       if title != null then
+                               add "<h1>"
+                               add title.as(not null)
+                               add "</h1>"
+                       end
+                       add     body.as(not null)
+                       add " </article>"
+               end
+               add "</div>"
+       end
+end
+
+# A collection of HTML blocks displayed on the side of a page.
+class TplSidebar
+       super Template
+
+       # Blocks are `Stremable` pieces that will be rendered in the sidebar.
+       var blocks = new Array[Streamable]
+
+       redef fun rendering do
+               for block in blocks do
+                       add "<div class=\"sideblock\">"
+                       add block
+                       add "</div>"
+               end
+       end
+end
+
+# An HTML breadcrumbs that show the path from a `WikiArticle` to the `Nitiwiki` root.
+class TplBreadcrumbs
+       super Template
+
+       # Bread crumb article.
+       var article: WikiArticle
+
+       redef fun rendering do
+               var path = article.breadcrumbs
+               if path.is_empty or path.length <= 2 and article.is_index then return
+               add "<ol class=\"breadcrumb\">"
+               for entry in path do
+                       if entry == path.last then
+                               add "<li class=\"active\">"
+                               add entry.title
+                               add "</li>"
+                       else
+                               if article.parent == entry and article.is_index then continue
+                               add "<li>"
+                               add entry.tpl_link
+                               add "</li>"
+                       end
+               end
+               add "</ol>"
+       end
+end
+
+# An HTML tree that show the section pages structure.
+class TplPageTree
+       super Template
+
+       # Builds the page tree from `root`.
+       var root: WikiSection
+
+       # Limits the tree depth to `max_depth` levels.
+       var max_depth: Int
+
+       redef fun rendering do
+               add "<ul>"
+               add root.tpl_tree(-1)
+               add "</ul>"
+       end
+end