src/doc: introduce option --no-render in HTML phase.
[nit.git] / src / doc / doc_phases / doc_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 # Render the DocModel pages as HTML pages.
16 #
17 # FIXME this module is all f*cked up to maintain compatibility with
18 # the original `doc_templates` and `doc_model` modules.
19 # This will change in further refactorings.
20 module doc_html
21
22 import doc_structure
23 import doc_hierarchies
24 import doc_intros_redefs
25 import doc_graphs
26 import html_templates
27
28 redef class ToolContext
29
30 # File pattern used to link documentation to source code.
31 var opt_source = new OptionString("link for source (%f for filename, " +
32 "%l for first line, %L for last line)", "--source")
33
34 # Directory where the CSS and JS is stored.
35 var opt_sharedir = new OptionString("directory containing nitdoc assets", "--sharedir")
36
37 # Use a shareurl instead of copy shared files.
38 #
39 # This is usefull if you don't want to store the Nitdoc templates with your
40 # documentation.
41 var opt_shareurl = new OptionString("use shareurl instead of copy shared files", "--shareurl")
42
43 # Use a custom title for the homepage.
44 var opt_custom_title = new OptionString("custom title for homepage", "--custom-title")
45
46 # Display a custom brand or logo in the documentation top menu.
47 var opt_custom_brand = new OptionString("custom link to external site", "--custom-brand")
48
49 # Display a custom introduction text before the packages overview.
50 var opt_custom_intro = new OptionString("custom intro text for homepage", "--custom-overview-text")
51 # Display a custom footer on each documentation page.
52 #
53 # Generally used to display the documentation or product version.
54 var opt_custom_footer = new OptionString("custom footer text", "--custom-footer-text")
55
56 # Piwik tracker URL.
57 #
58 # If you want to monitor your visitors.
59 var opt_piwik_tracker = new OptionString("Piwik tracker URL (ex: nitlanguage.org/piwik/)", "--piwik-tracker")
60
61 # Piwik tracker site id.
62 var opt_piwik_site_id = new OptionString("Piwik site ID", "--piwik-site-id")
63
64 # These options are not currently used in Nitdoc.
65
66 # FIXME redo the plugin
67 var opt_github_upstream = new OptionString("Git branch where edited commits will be pulled into (ex: user:repo:branch)", "--github-upstream")
68 # FIXME redo the plugin
69 var opt_github_base_sha1 = new OptionString("Git sha1 of base commit used to create pull request", "--github-base-sha1")
70 # FIXME redo the plugin
71 var opt_github_gitdir = new OptionString("Git working directory used to resolve path name (ex: /home/me/mypackage/)", "--github-gitdir")
72
73 # Do not produce HTML files
74 var opt_no_render = new OptionBool("do not render HTML files", "--no-render")
75
76 redef init do
77 super
78
79 option_context.add_option(
80 opt_source, opt_sharedir, opt_shareurl, opt_custom_title,
81 opt_custom_footer, opt_custom_intro, opt_custom_brand,
82 opt_github_upstream, opt_github_base_sha1, opt_github_gitdir,
83 opt_piwik_tracker, opt_piwik_site_id,
84 opt_no_render)
85 end
86
87 redef fun process_options(args) do
88 super
89 var upstream = opt_github_upstream
90 var base_sha = opt_github_base_sha1
91 var git_dir = opt_github_gitdir
92 var opts = [upstream.value, base_sha.value, git_dir.value]
93 if not opts.has_only(null) and opts.has(null) then
94 print "Option Error: options {upstream.names.first}, " +
95 "{base_sha.names.first} and {git_dir.names.first} " +
96 "are required to enable the GitHub plugin"
97 exit 1
98 end
99 end
100 end
101
102 # Render the Nitdoc as a HTML website.
103 class RenderHTMLPhase
104 super DocPhase
105
106 # Used to sort sidebar elements by name.
107 var name_sorter = new MEntityNameSorter
108
109 redef fun apply do
110 if ctx.opt_no_render.value then return
111 init_output_dir
112 for page in doc.pages.values do
113 page.render(self, doc).write_to_file("{ctx.output_dir.to_s}/{page.html_url}")
114 end
115 end
116
117 # Creates the output directory and imports assets files form `resources/`.
118 fun init_output_dir do
119 # create destination dir if it's necessary
120 var output_dir = ctx.output_dir
121 if not output_dir.file_exists then output_dir.mkdir
122 # locate share dir
123 var sharedir = ctx.opt_sharedir.value
124 if sharedir == null then
125 var dir = ctx.nit_dir
126 sharedir = dir/"share/nitdoc"
127 if not sharedir.file_exists then
128 print "Error: cannot locate nitdoc share files. Uses --sharedir or envvar NIT_DIR"
129 abort
130 end
131 end
132 # copy shared files
133 if ctx.opt_shareurl.value == null then
134 sys.system("cp -r -- {sharedir.to_s.escape_to_sh}/* {output_dir.to_s.escape_to_sh}/")
135 else
136 sys.system("cp -r -- {sharedir.to_s.escape_to_sh}/resources/ {output_dir.to_s.escape_to_sh}/resources/")
137 end
138
139 end
140
141 # Returns a HTML link for a given `location`.
142 fun html_source_link(location: nullable Location): nullable String
143 do
144 if location == null then return null
145 var source = ctx.opt_source.value
146 if source == null then
147 var url = location.file.filename.simplify_path
148 return "<a target='_blank' title='Show source' href=\"{url.html_escape}\">View Source</a>"
149 end
150 # THIS IS JUST UGLY ! (but there is no replace yet)
151 var x = source.split_with("%f")
152 source = x.join(location.file.filename.simplify_path)
153 x = source.split_with("%l")
154 source = x.join(location.line_start.to_s)
155 x = source.split_with("%L")
156 source = x.join(location.line_end.to_s)
157 source = source.simplify_path
158 return "<a target='_blank' title='Show source' href=\"{source.to_s.html_escape}\">View Source</a>"
159 end
160 end
161
162 redef class DocPage
163
164 # Render the page as a html template.
165 private fun render(v: RenderHTMLPhase, doc: DocModel): Writable do
166 var shareurl = "."
167 if v.ctx.opt_shareurl.value != null then
168 shareurl = v.ctx.opt_shareurl.value.as(not null)
169 end
170
171 # init page options
172 self.shareurl = shareurl
173 self.footer = v.ctx.opt_custom_footer.value
174 self.body_attrs.add(new TagAttribute("data-bootstrap-share", shareurl))
175
176 # build page
177 init_title(v, doc)
178 init_topmenu(v, doc)
179 init_content(v, doc)
180 init_sidebar(v, doc)
181
182 # piwik tracking
183 var tracker_url = v.ctx.opt_piwik_tracker.value
184 var site_id = v.ctx.opt_piwik_site_id.value
185 if tracker_url != null and site_id != null then
186 self.scripts.add new TplPiwikScript(tracker_url, site_id)
187 end
188 return self
189 end
190
191 # FIXME diff hack
192 # all properties below are roughly copied from `doc_pages`
193
194 # Build page title string
195 fun init_title(v: RenderHTMLPhase, doc: DocModel) do end
196
197 # Build top menu template if any.
198 fun init_topmenu(v: RenderHTMLPhase, doc: DocModel) do
199 topmenu = new DocTopMenu
200 topmenu.brand = v.ctx.opt_custom_brand.value
201 var title = "Overview"
202 if v.ctx.opt_custom_title.value != null then
203 title = v.ctx.opt_custom_title.value.to_s
204 end
205 topmenu.add_li new ListItem(new Link("index.html", title))
206 topmenu.add_li new ListItem(new Link("search.html", "Index"))
207 topmenu.active_item = topmenu.items.first
208 end
209
210 # Build page sidebar if any.
211 fun init_sidebar(v: RenderHTMLPhase, doc: DocModel) do
212 sidebar = new DocSideBar
213 sidebar.boxes.add new DocSideBox("Summary", html_toc)
214 end
215
216 # Build page content template.
217 fun init_content(v: RenderHTMLPhase, doc: DocModel) do
218 root.init_html_render(v, doc, self)
219 end
220 end
221
222 redef class OverviewPage
223 redef var html_url = "index.html"
224
225 redef fun init_title(v, doc) do
226 title = "Overview"
227 if v.ctx.opt_custom_title.value != null then
228 title = v.ctx.opt_custom_title.value.to_s
229 end
230 end
231 end
232
233 redef class SearchPage
234 redef var html_url = "search.html"
235 redef fun init_title(v, doc) do title = "Index"
236
237 redef fun init_topmenu(v, doc) do
238 super
239 topmenu.active_item = topmenu.items.last
240 end
241
242 redef fun init_sidebar(v, doc) do end
243 end
244
245 redef class MEntityPage
246 redef var html_url is lazy do
247 if mentity isa MGroup and mentity.mdoc != null then
248 return "api_{mentity.nitdoc_url}"
249 end
250 return mentity.nitdoc_url
251 end
252
253 redef fun init_title(v, doc) do title = mentity.html_name
254 end
255
256 # FIXME all clases below are roughly copied from `doc_pages` and adapted to new
257 # doc phases. This is to preserve the compatibility with the current
258 # `doc_templates` module.
259
260 redef class ReadmePage
261 redef var html_url is lazy do return mentity.nitdoc_url
262
263 redef fun init_topmenu(v, doc) do
264 super
265 var mpackage = mentity.mpackage
266 if not mentity.is_root then
267 topmenu.add_li new ListItem(new Link(mpackage.nitdoc_url, mpackage.html_name))
268 end
269 topmenu.add_li new ListItem(new Link(html_url, mpackage.html_name))
270 topmenu.active_item = topmenu.items.last
271 end
272
273 redef fun init_sidebar(v, doc) do
274 super
275 var api_lnk = """<a href="api_{{{mentity.nitdoc_url}}}">Go to API</a>"""
276 sidebar.boxes.unshift new DocSideBox(api_lnk, "")
277 end
278 end
279
280 redef class MGroupPage
281 redef fun init_topmenu(v, doc) do
282 super
283 var mpackage = mentity.mpackage
284 if not mentity.is_root then
285 topmenu.add_li new ListItem(new Link(mpackage.nitdoc_url, mpackage.html_name))
286 end
287 topmenu.add_li new ListItem(new Link(html_url, mpackage.html_name))
288 topmenu.active_item = topmenu.items.last
289 end
290
291 redef fun init_sidebar(v, doc) do
292 super
293 # README link
294 if mentity.mdoc != null then
295 var doc_lnk = """<a href="{{{mentity.nitdoc_url}}}">Go to README</a>"""
296 sidebar.boxes.unshift new DocSideBox(doc_lnk, "")
297 end
298 # MClasses list
299 var mclasses = new HashSet[MClass]
300 mclasses.add_all intros
301 mclasses.add_all redefs
302 if mclasses.is_empty then return
303 var list = new UnorderedList
304 list.css_classes.add "list-unstyled list-labeled"
305 var sorted = mclasses.to_a
306 v.name_sorter.sort(sorted)
307 for mclass in sorted do
308 list.add_li tpl_sidebar_item(mclass)
309 end
310 sidebar.boxes.add new DocSideBox("All classes", list)
311 sidebar.boxes.last.is_open = false
312 end
313
314 private fun tpl_sidebar_item(def: MClass): ListItem do
315 var classes = def.intro.css_classes
316 if intros.has(def) then
317 classes.add "intro"
318 else
319 classes.add "redef"
320 end
321 var lnk = new Template
322 lnk.add new DocHTMLLabel.with_classes(classes)
323 lnk.add def.html_link
324 return new ListItem(lnk)
325 end
326 end
327
328 redef class MModulePage
329 redef fun init_topmenu(v, doc) do
330 super
331 var mpackage = mentity.mpackage
332 topmenu.add_li new ListItem(new Link(mpackage.nitdoc_url, mpackage.html_name))
333 topmenu.add_li new ListItem(new Link(mentity.nitdoc_url, mentity.html_name))
334 topmenu.active_item = topmenu.items.last
335 end
336
337 # Class list to display in sidebar
338 redef fun init_sidebar(v, doc) do
339 # TODO filter here?
340 super
341 var mclasses = new HashSet[MClass]
342 mclasses.add_all mentity.collect_intro_mclasses(v.ctx.min_visibility)
343 mclasses.add_all mentity.collect_redef_mclasses(v.ctx.min_visibility)
344 if mclasses.is_empty then return
345 var list = new UnorderedList
346 list.css_classes.add "list-unstyled list-labeled"
347
348 var sorted = mclasses.to_a
349 v.name_sorter.sort(sorted)
350 for mclass in sorted do
351 list.add_li tpl_sidebar_item(mclass)
352 end
353 sidebar.boxes.add new DocSideBox("All classes", list)
354 sidebar.boxes.last.is_open = false
355 end
356
357 private fun tpl_sidebar_item(def: MClass): ListItem do
358 var classes = def.intro.css_classes
359 if def.intro_mmodule == self.mentity then
360 classes.add "intro"
361 else
362 classes.add "redef"
363 end
364 var lnk = new Template
365 lnk.add new DocHTMLLabel.with_classes(classes)
366 lnk.add def.html_link
367 return new ListItem(lnk)
368 end
369 end
370
371 redef class MClassPage
372
373 redef fun init_topmenu(v, doc) do
374 super
375 var mpackage = mentity.intro_mmodule.mgroup.mpackage
376 topmenu.add_li new ListItem(new Link(mpackage.nitdoc_url, mpackage.html_name))
377 topmenu.add_li new ListItem(new Link(html_url, mentity.html_name))
378 topmenu.active_item = topmenu.items.last
379 end
380
381 redef fun init_sidebar(v, doc) do
382 super
383 var by_kind = new PropertiesByKind.with_elements(mclass_inherited_mprops(v, doc))
384 var summary = new UnorderedList
385 summary.css_classes.add "list-unstyled"
386
387 by_kind.sort_groups(v.name_sorter)
388 for g in by_kind.groups do tpl_sidebar_list(g, summary)
389 sidebar.boxes.add new DocSideBox("All properties", summary)
390 sidebar.boxes.last.is_open = false
391 end
392
393 private fun tpl_sidebar_list(mprops: PropertyGroup[MProperty], summary: UnorderedList) do
394 if mprops.is_empty then return
395 var list = new UnorderedList
396 list.css_classes.add "list-unstyled list-labeled"
397 for mprop in mprops do
398 list.add_li tpl_sidebar_item(mprop)
399 end
400 var content = new Template
401 content.add mprops.title
402 content.add list
403 var li = new ListItem(content)
404 summary.add_li li
405 end
406
407 private fun tpl_sidebar_item(mprop: MProperty): ListItem do
408 var classes = mprop.intro.css_classes
409 if not mprop_is_local(mprop) then
410 classes.add "inherit"
411 var cls_url = mprop.intro.mclassdef.mclass.nitdoc_url
412 var def_url = "{cls_url}#{mprop.nitdoc_id}.definition"
413 var lnk = new Link(def_url, mprop.html_name)
414 var mdoc = mprop.intro.mdoc_or_fallback
415 if mdoc != null then lnk.title = mdoc.synopsis
416 var item = new Template
417 item.add new DocHTMLLabel.with_classes(classes)
418 item.add lnk
419 return new ListItem(item)
420 end
421 if mpropdefs.has(mprop.intro) then
422 classes.add "intro"
423 else
424 classes.add "redef"
425 end
426 var def = select_mpropdef(mprop)
427 var anc = def.html_link_to_anchor
428 anc.href = "#{def.nitdoc_id}.definition"
429 var lnk = new Template
430 lnk.add new DocHTMLLabel.with_classes(classes)
431 lnk.add anc
432 return new ListItem(lnk)
433 end
434
435 # Get the mpropdef contained in `self` page for a mprop.
436 #
437 # FIXME this method is used to translate a mprop into a mpropdefs for
438 # section linking. A better page structure should avoid this...
439 private fun select_mpropdef(mprop: MProperty): MPropDef do
440 for mclassdef in mentity.mclassdefs do
441 for mpropdef in mclassdef.mpropdefs do
442 if mpropdef.mproperty == mprop then return mpropdef
443 end
444 end
445 abort # FIXME is there a case where the prop is not found?
446 end
447
448 private fun mclass_inherited_mprops(v: RenderHTMLPhase, doc: DocModel): Set[MProperty] do
449 var res = new HashSet[MProperty]
450 var local = mentity.collect_local_mproperties(v.ctx.min_visibility)
451 for mprop in mentity.collect_inherited_mproperties(v.ctx.min_visibility) do
452 if local.has(mprop) then continue
453 #if mprop isa MMethod and mprop.is_init then continue
454 if mprop.intro.mclassdef.mclass.name == "Object" and
455 (mprop.visibility == protected_visibility or
456 mprop.intro.mclassdef.mmodule.name != "kernel") then continue
457 res.add mprop
458 end
459 res.add_all local
460 return res
461 end
462
463 private fun mprop_is_local(mprop: MProperty): Bool do
464 for mpropdef in mprop.mpropdefs do
465 if self.mpropdefs.has(mpropdef) then return true
466 end
467 return false
468 end
469 end
470
471 redef class MPropertyPage
472 redef fun init_title(v, doc) do
473 title = "{mentity.html_name}{mentity.html_short_signature.write_to_string}"
474 end
475
476 redef fun init_topmenu(v, doc) do
477 super
478 var mmodule = mentity.intro_mclassdef.mmodule
479 var mpackage = mmodule.mgroup.mpackage
480 var mclass = mentity.intro_mclassdef.mclass
481 topmenu.add_li new ListItem(new Link(mpackage.nitdoc_url, mpackage.html_name))
482 topmenu.add_li new ListItem(new Link(mclass.nitdoc_url, mclass.html_name))
483 topmenu.add_li new ListItem(new Link(html_url, mentity.html_name))
484 topmenu.active_item = topmenu.items.last
485 end
486 end
487
488 redef class DocComposite
489 # Prepares the HTML rendering for this element.
490 #
491 # This visit is mainly used to set template attributes before rendering.
492 fun init_html_render(v: RenderHTMLPhase, doc: DocModel, page: DocPage) do
493 for child in children do child.init_html_render(v, doc, page)
494 end
495 end
496
497 # FIXME hideous hacks to avoid diff
498 redef class MEntitySection
499 redef fun init_html_render(v, doc, page) do
500 if not page isa MEntityPage then return
501 var mentity = self.mentity
502 if mentity isa MGroup and mentity.is_root then
503 html_title = mentity.mpackage.html_name
504 html_subtitle = mentity.mpackage.html_declaration
505 else if mentity isa MProperty then
506 var title = new Template
507 title.add mentity.html_name
508 title.add mentity.html_signature
509 html_title = title
510 html_subtitle = mentity.html_namespace
511 html_toc_title = mentity.html_name
512 end
513 super
514 end
515 end
516
517 # FIXME hideous hacks to avoid diff
518 redef class ConcernSection
519 redef fun init_html_render(v, doc, page) do
520 if not page isa MEntityPage then return
521 var mentity = self.mentity
522 if page isa MGroupPage then
523 html_title = null
524 html_toc_title = mentity.html_name
525 is_toc_hidden = false
526 else if page.mentity isa MModule and mentity isa MModule then
527 var title = new Template
528 if mentity == page.mentity then
529 title.add "in "
530 html_toc_title = "in {mentity.html_name}"
531 else
532 title.add "from "
533 html_toc_title = "from {mentity.html_name}"
534 end
535 title.add mentity.html_namespace
536 html_title = title
537 else if (page.mentity isa MClass and mentity isa MModule) or
538 (page.mentity isa MProperty and mentity isa MModule) then
539 var title = new Template
540 title.add "in "
541 title.add mentity.html_namespace
542 html_title = title
543 html_toc_title = "in {mentity.html_name}"
544 end
545 super
546 end
547 end
548
549 # TODO redo showlink
550 redef class IntroArticle
551 redef fun init_html_render(v, doc, page) do
552 var mentity = self.mentity
553 if mentity isa MModule then
554 html_source_link = v.html_source_link(mentity.location)
555 else if mentity isa MClassDef then
556 html_source_link = v.html_source_link(mentity.location)
557 else if mentity isa MPropDef then
558 html_source_link = v.html_source_link(mentity.location)
559 end
560 end
561 end
562
563 # FIXME less hideous hacks...
564 redef class DefinitionArticle
565 redef fun init_html_render(v, doc, page) do
566 var mentity = self.mentity
567 if mentity isa MPackage or mentity isa MModule then
568 var title = new Template
569 title.add mentity.html_icon
570 title.add mentity.html_namespace
571 html_title = title
572 html_toc_title = mentity.html_name
573 if mentity isa MModule then
574 html_source_link = v.html_source_link(mentity.location)
575 end
576 else if mentity isa MClassDef then
577 var title = new Template
578 title.add "in "
579 title.add mentity.mmodule.html_namespace
580 html_title = mentity.html_declaration
581 html_subtitle = title
582 html_toc_title = "in {mentity.html_name}"
583 html_source_link = v.html_source_link(mentity.location)
584 if page isa MEntityPage and mentity.is_intro and mentity.mmodule != page.mentity then
585 is_short_comment = true
586 end
587 if page isa MModulePage then is_toc_hidden = true
588 else if mentity isa MPropDef then
589 if page isa MClassPage then
590 var title = new Template
591 title.add mentity.html_icon
592 title.add mentity.html_declaration
593 html_title = title
594 html_subtitle = mentity.html_namespace
595 html_toc_title = mentity.html_name
596 else
597 var title = new Template
598 title.add "in "
599 title.add mentity.mclassdef.html_link
600 html_title = title
601 html_toc_title = "in {mentity.mclassdef.html_name}"
602 end
603 html_source_link = v.html_source_link(mentity.location)
604 end
605 if page isa MGroupPage and mentity isa MModule then
606 is_toc_hidden = true
607 end
608 super
609 end
610 end
611
612 redef class HomeArticle
613 redef fun init_html_render(v, doc, page) do
614 if v.ctx.opt_custom_title.value != null then
615 self.html_title = v.ctx.opt_custom_title.value.to_s
616 self.html_toc_title = v.ctx.opt_custom_title.value.to_s
617 end
618 self.content = v.ctx.opt_custom_intro.value
619 super
620 end
621 end
622
623 redef class GraphArticle
624 redef fun init_html_render(v, doc, page) do
625 var path = v.ctx.output_dir / graph_id
626 var file = new FileWriter.open("{path}.dot")
627 file.write(dot)
628 file.close
629 var proc = new ProcessReader("dot", "-Tsvg", "-Tcmapx", "{path}.dot")
630 var svg = new Buffer
631 var i = 0
632 while not proc.eof do
633 i += 1
634 if i < 6 then continue # skip dot default header
635 svg.append proc.read_line
636 end
637 proc.close
638 self.svg = svg.write_to_string
639 end
640 end