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