nitiwiki: makes auto-index appear in test files
[nit.git] / contrib / nitiwiki / src / wiki_html.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
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
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 # HTML wiki rendering
16 module wiki_html
17
18 import wiki_base
19
20 redef class Nitiwiki
21
22 # Render HTML output looking for changes in the markdown sources.
23 fun render do
24 if not root_section.is_dirty and not force_render then return
25 var out_dir = expand_path(config.root_dir, config.out_dir)
26 out_dir.mkdir
27 copy_assets
28 root_section.add_child make_sitemap
29 root_section.render
30 end
31
32 # Copy the asset directory to the (public) output directory.
33 private fun copy_assets do
34 var src = expand_path(config.root_dir, config.assets_dir)
35 var out = expand_path(config.root_dir, config.out_dir)
36 if need_render(src, expand_path(out, config.assets_dir)) then
37 if src.file_exists then sys.system "cp -R {src} {out}"
38 end
39 end
40
41 # Build the wiki sitemap page.
42 private fun make_sitemap: WikiSitemap do
43 var sitemap = new WikiSitemap(self, "sitemap")
44 sitemap.is_dirty = true
45 return sitemap
46 end
47 end
48
49 redef class WikiEntry
50
51 # Url to `self` once generated.
52 fun url: String do return wiki.config.root_url.join_path(breadcrumbs.join("/"))
53
54 # Get a `<a>` template link to `self`
55 fun tpl_link: Streamable do
56 return "<a href=\"{url}\">{title}</a>"
57 end
58 end
59
60 redef class WikiSection
61
62 # Output directory (where to ouput the HTML pages for this section).
63 redef fun out_path: String do
64 if parent == null then
65 return wiki.config.out_dir
66 else
67 return wiki.expand_path(parent.out_path, name)
68 end
69 end
70
71 redef fun render do
72 if not is_dirty and not wiki.force_render then return
73 if is_new then
74 out_full_path.mkdir
75 else
76 sys.system "touch {out_full_path}"
77 end
78 if has_source then
79 wiki.message("Render section {out_path}", 1)
80 copy_files
81 end
82 var index = self.index
83 if index isa WikiSectionIndex then
84 wiki.message("Render auto-index for section {out_path}", 1)
85 index.is_dirty = true
86 add_child index
87 end
88 super
89 end
90
91 # Copy attached files from `src_path` to `out_path`.
92 private fun copy_files do
93 assert has_source
94 var dir = src_full_path.to_s
95 for name in dir.files do
96 if name == wiki.config_filename then continue
97 if name.has_suffix(".md") then continue
98 if has_child(name) then continue
99 var src = wiki.expand_path(dir, name)
100 var out = wiki.expand_path(out_full_path, name)
101 if not wiki.need_render(src, out) then continue
102 sys.system "cp -R {src} {out_full_path}"
103 end
104 end
105
106 # The index page for this section.
107 #
108 # If no file `index.md` exists for this section,
109 # a summary is generated using contained articles.
110 fun index: WikiArticle is cached do
111 for child in children.values do
112 if child isa WikiArticle and child.is_index then return child
113 end
114 return new WikiSectionIndex(wiki, "index", self)
115 end
116
117 redef fun tpl_link do return index.tpl_link
118
119 # Render the section hierarchy as a html tree.
120 #
121 # `limit` is used to specify the max-depth of the tree.
122 #
123 # The generated tree will be something like this:
124 #
125 # ~~~html
126 # <ul>
127 # <li>section 1</li>
128 # <li>section 2
129 # <ul>
130 # <li>section 2.1</li>
131 # <li>section 2.2</li>
132 # </ul>
133 # </li>
134 # </ul>
135 # ~~~
136 fun tpl_tree(limit: Int): Template do
137 return tpl_tree_intern(limit, 1)
138 end
139
140 # Build the template tree for this section recursively.
141 protected fun tpl_tree_intern(limit, count: Int): Template do
142 var out = new Template
143 var index = index
144 out.add "<li>"
145 out.add tpl_link
146 if (limit < 0 or count < limit) and
147 (children.length > 1 or (children.length == 1)) then
148 out.add " <ul>"
149 for child in children.values do
150 if child == index then continue
151 if child isa WikiArticle then
152 out.add "<li>"
153 out.add child.tpl_link
154 out.add "</li>"
155 else if child isa WikiSection and not child.is_hidden then
156 out.add child.tpl_tree_intern(limit, count + 1)
157 end
158 end
159 out.add " </ul>"
160 end
161 out.add "</li>"
162 return out
163 end
164 end
165
166 redef class WikiArticle
167
168 redef fun out_path: String do
169 if parent == null then
170 return wiki.expand_path(wiki.config.out_dir, "{name}.html")
171 else
172 return wiki.expand_path(parent.out_path, "{name}.html")
173 end
174 end
175
176 redef fun url do
177 if parent == null then
178 return wiki.config.root_url.join_path("{name}.html")
179 else
180 return parent.url.join_path("{name}.html")
181 end
182 end
183
184 # Is `self` an index page?
185 #
186 # Checks if `self.name == "index"`.
187 fun is_index: Bool do return name == "index"
188
189 redef fun render do
190 if not is_dirty and not wiki.force_render then return
191 wiki.message("Render article {name}", 2)
192 var file = out_full_path
193 file.dirname.mkdir
194 tpl_page.write_to_file file
195 super
196 end
197
198
199 # Replace macros in the template by wiki data.
200 private fun tpl_page: TemplateString do
201 var tpl = wiki.load_template(template_file)
202 if tpl.has_macro("TOP_MENU") then
203 tpl.replace("TOP_MENU", tpl_menu)
204 end
205 if tpl.has_macro("HEADER") then
206 tpl.replace("HEADER", tpl_header)
207 end
208 if tpl.has_macro("BODY") then
209 tpl.replace("BODY", tpl_article)
210 end
211 if tpl.has_macro("FOOTER") then
212 tpl.replace("FOOTER", tpl_footer)
213 end
214 return tpl
215 end
216
217 # Generate the HTML header for this article.
218 fun tpl_header: Streamable do
219 var file = header_file
220 if not wiki.has_template(file) then return ""
221 return wiki.load_template(file)
222 end
223
224 # Generate the HTML page for this article.
225 fun tpl_article: TplArticle do
226 var article = new TplArticle
227 article.body = content
228 article.breadcrumbs = new TplBreadcrumbs(self)
229 tpl_sidebar.blocks.add tpl_summary
230 article.sidebar = tpl_sidebar
231 return article
232 end
233
234 # Sidebar for this page.
235 var tpl_sidebar = new TplSidebar
236
237 # Generate the HTML summary for this article.
238 #
239 # Based on `headlines`
240 fun tpl_summary: Streamable do
241 var headlines = self.headlines
242 var tpl = new Template
243 tpl.add "<ul class=\"summary list-unstyled\">"
244 var iter = headlines.iterator
245 while iter.is_ok do
246 var hl = iter.item
247 # parse title as markdown
248 var title = hl.title.md_to_html.to_s
249 title = title.substring(3, title.length - 8)
250 tpl.add "<li><a href=\"#{hl.id}\">{title}</a>"
251 iter.next
252 if iter.is_ok then
253 if iter.item.level > hl.level then
254 tpl.add "<ul class=\"list-unstyled\">"
255 else if iter.item.level < hl.level then
256 tpl.add "</li>"
257 tpl.add "</ul>"
258 end
259 else
260 tpl.add "</li>"
261 end
262 end
263 tpl.add "</ul>"
264 return tpl
265 end
266
267 # Generate the HTML menu for this article.
268 fun tpl_menu: Streamable do
269 var file = menu_file
270 if not wiki.has_template(file) then return ""
271 var tpl = wiki.load_template(file)
272 if tpl.has_macro("MENUS") then
273 var items = new Template
274 for child in wiki.root_section.children.values do
275 if child isa WikiArticle and child.is_index then continue
276 if child isa WikiSection and child.is_hidden then continue
277 items.add "<li"
278 if self == child or self.breadcrumbs.has(child) then
279 items.add " class=\"active\""
280 end
281 items.add ">"
282 items.add child.tpl_link
283 items.add "</li>"
284 end
285 tpl.replace("MENUS", items)
286 end
287 return tpl
288 end
289
290 # Generate the HTML footer for this article.
291 fun tpl_footer: Streamable do
292 var file = footer_file
293 if not wiki.has_template(file) then return ""
294 var tpl = wiki.load_template(file)
295 var time = new Tm.gmtime
296 if tpl.has_macro("YEAR") then
297 tpl.replace("YEAR", (time.year + 1900).to_s)
298 end
299 if tpl.has_macro("GEN_TIME") then
300 tpl.replace("GEN_TIME", time.to_s)
301 end
302 return tpl
303 end
304 end
305
306 # A `WikiArticle` that contains the sitemap tree.
307 class WikiSitemap
308 super WikiArticle
309
310 redef fun tpl_article do
311 var article = new TplArticle.with_title("Sitemap")
312 article.body = new TplPageTree(wiki.root_section, -1)
313 return article
314 end
315
316 redef var is_dirty = false
317 end
318
319 # A `WikiArticle` that contains the section index tree.
320 class WikiSectionIndex
321 super WikiArticle
322
323 # The section described by `self`.
324 var section: WikiSection
325
326 redef fun title do return section.title
327
328 redef fun url do return section.url
329
330 redef var is_dirty = false
331
332 redef fun tpl_article do
333 var article = new TplArticle.with_title(section.title)
334 article.body = new TplPageTree(section, -1)
335 article.breadcrumbs = new TplBreadcrumbs(self)
336 return article
337 end
338 end
339
340 # Article HTML output.
341 class TplArticle
342 super Template
343
344 # Article title.
345 var title: nullable Streamable = null
346
347 # Article HTML body.
348 var body: nullable Streamable = null
349
350 # Sidebar of this article (if any).
351 var sidebar: nullable TplSidebar = null
352
353 # Breadcrumbs from wiki root to this article.
354 var breadcrumbs: nullable TplBreadcrumbs = null
355
356 # Init `self` with a `title`.
357 init with_title(title: Streamable) do
358 self.title = title
359 end
360
361 redef fun rendering do
362 if sidebar != null then
363 add "<div class=\"col-sm-3 sidebar\">"
364 add sidebar.as(not null)
365 add "</div>"
366 add "<div class=\"col-sm-9 content\">"
367 else
368 add "<div class=\"col-sm-12 content\">"
369 end
370 if body != null then
371 add "<article>"
372 if breadcrumbs != null then
373 add breadcrumbs.as(not null)
374 end
375 if title != null then
376 add "<h1>"
377 add title.as(not null)
378 add "</h1>"
379 end
380 add body.as(not null)
381 add " </article>"
382 end
383 add "</div>"
384 end
385 end
386
387 # A collection of HTML blocks displayed on the side of a page.
388 class TplSidebar
389 super Template
390
391 # Blocks are `Stremable` pieces that will be rendered in the sidebar.
392 var blocks = new Array[Streamable]
393
394 redef fun rendering do
395 for block in blocks do
396 add "<div class=\"sideblock\">"
397 add block
398 add "</div>"
399 end
400 end
401 end
402
403 # An HTML breadcrumbs that show the path from a `WikiArticle` to the `Nitiwiki` root.
404 class TplBreadcrumbs
405 super Template
406
407 # Bread crumb article.
408 var article: WikiArticle
409
410 redef fun rendering do
411 var path = article.breadcrumbs
412 if path.is_empty or path.length <= 2 and article.is_index then return
413 add "<ol class=\"breadcrumb\">"
414 for entry in path do
415 if entry == path.last then
416 add "<li class=\"active\">"
417 add entry.title
418 add "</li>"
419 else
420 if article.parent == entry and article.is_index then continue
421 add "<li>"
422 add entry.tpl_link
423 add "</li>"
424 end
425 end
426 add "</ol>"
427 end
428 end
429
430 # An HTML tree that show the section pages structure.
431 class TplPageTree
432 super Template
433
434 # Builds the page tree from `root`.
435 var root: WikiSection
436
437 # Limits the tree depth to `max_depth` levels.
438 var max_depth: Int
439
440 redef fun rendering do
441 add "<ul>"
442 add root.tpl_tree(-1)
443 add "</ul>"
444 end
445 end