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