1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
19 import markdown
::decorators
23 # Render HTML output looking for changes in the markdown sources.
26 if not root_section
.is_dirty
and not force_render
then return
27 var out_dir
= expand_path
(config
.root_dir
, config
.out_dir
)
30 root_section
.add_child make_sitemap
34 # Copy the asset directory to the (public) output directory.
35 private fun copy_assets
do
36 var src
= expand_path
(config
.root_dir
, config
.assets_dir
)
37 var out
= expand_path
(config
.root_dir
, config
.out_dir
)
38 if need_render
(src
, expand_path
(out
, config
.assets_dir
)) then
39 if src
.file_exists
then sys
.system
"cp -R -- {src.escape_to_sh} {out.escape_to_sh}"
43 # Build the wiki sitemap page.
44 private fun make_sitemap
: WikiSitemap do
45 var sitemap
= new WikiSitemap(self, "sitemap")
46 sitemap
.is_dirty
= true
50 # Markdown processor used for inline element such as titles in TOC.
51 private var inline_processor
: MarkdownProcessor is lazy
do
52 var proc
= new MarkdownProcessor
53 proc
.emitter
.decorator
= new InlineDecorator
57 # Inline markdown (remove h1, p, ... elements).
58 private fun inline_md
(md
: Writable): Writable do
59 return inline_processor
.process
(md
.write_to_string
)
64 # Get a `<a>` template link to `self`
65 fun tpl_link
(context
: WikiEntry): Writable do
66 return "<a href=\"{href_from(context)}\
">{title}</a>"
70 redef class WikiSection
72 # Output directory (where to ouput the HTML pages for this section).
73 redef fun out_path
: String do
74 if parent
== null then
75 return wiki
.config
.out_dir
77 return wiki
.expand_path
(parent
.out_path
, name
)
82 if not is_dirty
and not wiki
.force_render
then return
86 sys
.system
"touch -- {out_full_path.escape_to_sh}"
89 wiki
.message
("Render section {name} -> {out_path}", 1)
92 var index
= self.index
93 if index
isa WikiSectionIndex then
94 wiki
.message
("Render auto-index for section {name} -> {out_path}", 1)
98 # Hack: Force the rendering of `index` first so that trails are collected
99 # TODO: Add first-pass analysis to collect global information before doing the rendering
104 # Copy attached files from `src_path` to `out_path`.
105 private fun copy_files
do
107 var dir
= src_full_path
.to_s
108 for name
in dir
.files
do
109 if name
== wiki
.config_filename
then continue
110 if name
.has_suffix
(".md") then continue
111 if has_child
(name
) then continue
112 var src
= wiki
.expand_path
(dir
, name
)
113 var out
= wiki
.expand_path
(out_full_path
, name
)
114 if not wiki
.need_render
(src
, out
) then continue
115 sys
.system
"cp -R -- {src.escape_to_sh} {out_full_path.escape_to_sh}"
119 redef fun tpl_link
(context
) do return index
.tpl_link
(context
)
121 # Render the section hierarchy as a html tree.
123 # `limit` is used to specify the max-depth of the tree.
125 # The generated tree will be something like this:
132 # <li>section 2.1</li>
133 # <li>section 2.2</li>
138 fun tpl_tree
(limit
: Int): Template do
139 return tpl_tree_intern
(limit
, 1, self)
142 # Build the template tree for this section recursively.
143 protected fun tpl_tree_intern
(limit
, count
: Int, context
: WikiEntry): Template do
144 var out
= new Template
147 out
.add tpl_link
(context
)
148 if (limit
< 0 or count
< limit
) and
149 (children
.length
> 1 or (children
.length
== 1)) then
151 for child
in children
.values
do
152 if child
== index
then continue
153 if child
isa WikiArticle then
155 out
.add child
.tpl_link
(context
)
157 else if child
isa WikiSection and not child
.is_hidden
then
158 out
.add child
.tpl_tree_intern
(limit
, count
+ 1, context
)
168 redef class WikiArticle
170 redef fun out_path
: String do
171 if parent
== null then
172 return wiki
.expand_path
(wiki
.config
.out_dir
, "{name}.html")
174 return wiki
.expand_path
(parent
.out_path
, "{name}.html")
180 if not is_dirty
and not wiki
.force_render
then return
181 var file
= out_full_path
182 wiki
.message
("Render article {name} -> {file}", 1)
184 tpl_page
.write_to_file file
188 # Load a template and resolve page-related macros
189 fun load_template
(template_file
: String): TemplateString do
190 var tpl
= wiki
.load_template
(template_file
)
191 if tpl
.has_macro
("ROOT_URL") then
192 var root_dir
= href
.dirname
.relpath
("")
193 # Avoid issues if the macro is just followed by a `/` (as with url prefix)
194 if root_dir
== "" then root_dir
= "."
195 tpl
.replace
("ROOT_URL", root_dir
)
200 # Replace macros in the template by wiki data.
201 private fun tpl_page
: TemplateString do
202 var tpl
= load_template
(template_file
)
203 if tpl
.has_macro
("TOP_MENU") then
204 tpl
.replace
("TOP_MENU", tpl_menu
)
206 if tpl
.has_macro
("HEADER") then
207 tpl
.replace
("HEADER", tpl_header
)
209 if tpl
.has_macro
("BODY") then
210 tpl
.replace
("BODY", tpl_article
)
212 if tpl
.has_macro
("FOOTER") then
213 tpl
.replace
("FOOTER", tpl_footer
)
215 if tpl
.has_macro
("TRAIL") then
216 tpl
.replace
("TRAIL", tpl_trail
)
221 # Generate the HTML header for this article.
222 fun tpl_header
: Writable do
223 var file
= header_file
224 if not wiki
.has_template
(file
) then return ""
225 return load_template
(file
)
228 # Generate the HTML page for this article.
229 fun tpl_article
: TplArticle do
230 var article
= new TplArticle
231 article
.body
= content
232 if wiki
.config
.auto_breadcrumbs
then
233 article
.breadcrumbs
= new TplBreadcrumbs(self)
235 article
.sidebar
= tpl_sidebar
236 article
.sidebar_pos
= wiki
.config
.sidebar
240 # Sidebar for this page.
241 var tpl_sidebar
: TplSidebar is lazy
do
242 var res
= new TplSidebar
243 if wiki
.config
.auto_summary
then
244 res
.blocks
.add tpl_summary
246 res
.blocks
.add_all sidebar
.blocks
250 # Generate the HTML summary for this article.
252 # Based on `headlines`
253 fun tpl_summary
: Writable do
254 var headlines
= self.headlines
255 var tpl
= new Template
256 tpl
.add
"<ul class=\"summary list-unstyled\
">"
257 var iter
= headlines
.iterator
260 # parse title as markdown
261 var title
= wiki
.inline_md
(hl
.title
)
262 tpl
.add
"<li><a href=\"#{hl.id}\">{title}</a>"
265 if iter
.item
.level
> hl
.level
then
266 tpl
.add
"<ul class=\"list-unstyled\
">"
267 else if iter
.item
.level
< hl
.level
then
279 # Generate the HTML menu for this article.
280 fun tpl_menu
: Writable do
282 if not wiki
.has_template
(file
) then return ""
283 var tpl
= load_template
(file
)
284 if tpl
.has_macro
("MENUS") then
285 var items
= new Template
286 for child
in wiki
.root_section
.children
.values
do
287 if child
isa WikiArticle and child
.is_index
then continue
288 if child
isa WikiSection and child
.is_hidden
then continue
290 if self == child
or self.breadcrumbs
.has
(child
) then
291 items
.add
" class=\"active\
""
294 items
.add child
.tpl_link
(self)
297 tpl
.replace
("MENUS", items
)
302 # Generate navigation links for the trail of this article, if any.
304 # A trail is generated if the article include or is included in a trail.
305 # See `wiki.trails` for details.
306 fun tpl_trail
: Writable do
307 if not wiki
.trails
.has
(self) then return ""
309 # Get the position of `self` in the trail
310 var flat
= wiki
.trails
.to_a
311 var pos
= flat
.index_of
(self)
314 var res
= new Template
315 res
.add
"<ul class=\"trail\
">"
317 var target
= flat
[pos-1
]
318 res
.add
"<li>{target.a_from(self, "prev")}</li>"
320 var parent
= wiki
.trails
.parent
(self)
321 if parent
!= null then
322 res
.add
"<li>{parent.a_from(self, "up")}</li>"
324 if pos
< flat
.length
- 1 then
325 var target
= flat
[pos
+1]
326 res
.add
"<li>{target.a_from(self, "next")}</li>"
333 # Generate the HTML footer for this article.
334 fun tpl_footer
: Writable do
335 var file
= footer_file
336 if not wiki
.has_template
(file
) then return ""
337 var tpl
= load_template
(file
)
338 var time
= new Tm.gmtime
339 if tpl
.has_macro
("YEAR") then
340 tpl
.replace
("YEAR", (time
.year
+ 1900).to_s
)
342 if tpl
.has_macro
("GEN_TIME") then
343 tpl
.replace
("GEN_TIME", time
.to_s
)
345 if tpl
.has_macro
("LAST_CHANGES") then
346 var url
= "{wiki.config.last_changes}{src_path or else ""}"
347 tpl
.replace
("LAST_CHANGES", url
)
349 if tpl
.has_macro
("EDIT") then
350 var url
= "{wiki.config.edit}{src_path or else ""}"
351 tpl
.replace
("EDIT", url
)
357 # A `WikiArticle` that contains the sitemap tree.
361 redef fun tpl_article
do
362 var article
= new TplArticle.with_title
("Sitemap")
363 article
.body
= new TplPageTree(wiki
.root_section
, -1)
367 redef var is_dirty
= false
370 # A `WikiArticle` that contains the section index tree.
371 redef class WikiSectionIndex
373 redef var is_dirty
= false
375 redef fun tpl_article
do
376 var article
= new TplArticle.with_title
(section
.title
)
377 article
.body
= new TplPageTree(section
, -1)
378 article
.breadcrumbs
= new TplBreadcrumbs(self)
383 # Article HTML output.
388 var title
: nullable Writable = null
391 var body
: nullable Writable = null
393 # Sidebar of this article (if any).
394 var sidebar
: nullable TplSidebar = null
396 # Position of the sidebar.
398 # See `WikiConfig::sidebar`.
399 var sidebar_pos
: String = "left"
401 # Breadcrumbs from wiki root to this article.
402 var breadcrumbs
: nullable TplBreadcrumbs = null
404 # Init `self` with a `title`.
405 init with_title
(title
: Writable) do
409 redef fun rendering
do
410 if sidebar_pos
== "left" then render_sidebar
411 if sidebar
== null then
412 add
"<div class=\"col-sm-12 content\
">"
414 add
"<div class=\"col-sm-9 content\
">"
418 if breadcrumbs
!= null then
419 add breadcrumbs
.as(not null)
421 if title
!= null then
423 add title
.as(not null)
426 add body
.as(not null)
430 if sidebar_pos
== "right" then render_sidebar
433 private fun render_sidebar
do
434 if sidebar
== null then return
435 add
"<div class=\"col-sm-3 sidebar\
">"
436 add sidebar
.as(not null)
441 # A collection of HTML blocks displayed on the side of a page.
445 # Blocks are `Stremable` pieces that will be rendered in the sidebar.
446 var blocks
= new Array[Writable]
448 redef fun rendering
do
449 for block
in blocks
do
450 add
"<nav class=\"sideblock\
">"
457 # An HTML breadcrumbs that show the path from a `WikiArticle` to the `Nitiwiki` root.
461 # Bread crumb article.
462 var article
: WikiArticle
464 redef fun rendering
do
465 var path
= article
.breadcrumbs
466 if path
.is_empty
or path
.length
<= 2 and article
.is_index
then return
467 add
"<ol class=\"breadcrumb\
">"
469 if entry
== path
.last
then
470 add
"<li class=\"active\
">"
474 if article
.parent
== entry
and article
.is_index
then continue
476 add entry
.tpl_link
(article
)
484 # An HTML tree that show the section pages structure.
488 # Builds the page tree from `root`.
489 var root
: WikiSection
491 # Limits the tree depth to `max_depth` levels.
494 redef fun rendering
do
496 add root
.tpl_tree
(-1)