nitdoc: use --share-dir option
[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 topmenu.add_li new ListItem(new Link(mpackage.nitdoc_url, mpackage.html_name))
322 topmenu.add_li new ListItem(new Link(mentity.nitdoc_url, mentity.html_name))
323 topmenu.active_item = topmenu.items.last
324 end
325
326 # Class list to display in sidebar
327 redef fun init_sidebar(v, doc) do
328 # TODO filter here?
329 super
330 var mclasses = new HashSet[MClass]
331 mclasses.add_all mentity.collect_intro_mclasses(v.doc)
332 mclasses.add_all mentity.collect_redef_mclasses(v.doc)
333 if mclasses.is_empty then return
334 var list = new UnorderedList
335 list.css_classes.add "list-unstyled list-labeled"
336
337 var sorted = mclasses.to_a
338 v.name_sorter.sort(sorted)
339 for mclass in sorted do
340 list.add_li tpl_sidebar_item(mclass)
341 end
342 sidebar.boxes.add new DocSideBox("All classes", list)
343 sidebar.boxes.last.is_open = false
344 end
345
346 private fun tpl_sidebar_item(def: MClass): ListItem do
347 var classes = def.intro.css_classes
348 if def.intro_mmodule == self.mentity then
349 classes.add "intro"
350 else
351 classes.add "redef"
352 end
353 var lnk = new Template
354 lnk.add new DocHTMLLabel.with_classes(classes)
355 lnk.add def.html_link
356 return new ListItem(lnk)
357 end
358 end
359
360 redef class MClassPage
361
362 redef fun init_topmenu(v, doc) do
363 super
364 var mpackage = mentity.intro_mmodule.mgroup.mpackage
365 topmenu.add_li new ListItem(new Link(mpackage.nitdoc_url, mpackage.html_name))
366 topmenu.add_li new ListItem(new Link(html_url, mentity.html_name))
367 topmenu.active_item = topmenu.items.last
368 end
369
370 redef fun init_sidebar(v, doc) do
371 super
372 var by_kind = new PropertiesByKind.with_elements(mclass_inherited_mprops(v, doc))
373 var summary = new UnorderedList
374 summary.css_classes.add "list-unstyled"
375
376 by_kind.sort_groups(v.name_sorter)
377 for g in by_kind.groups do tpl_sidebar_list(g, summary)
378 sidebar.boxes.add new DocSideBox("All properties", summary)
379 sidebar.boxes.last.is_open = false
380 end
381
382 private fun tpl_sidebar_list(mprops: PropertyGroup[MProperty], summary: UnorderedList) do
383 if mprops.is_empty then return
384 var list = new UnorderedList
385 list.css_classes.add "list-unstyled list-labeled"
386 for mprop in mprops do
387 list.add_li tpl_sidebar_item(mprop)
388 end
389 var content = new Template
390 content.add mprops.title
391 content.add list
392 var li = new ListItem(content)
393 summary.add_li li
394 end
395
396 private fun tpl_sidebar_item(mprop: MProperty): ListItem do
397 var classes = mprop.intro.css_classes
398 if not mprop_is_local(mprop) then
399 classes.add "inherit"
400 var cls_url = mprop.intro.mclassdef.mclass.nitdoc_url
401 var def_url = "{cls_url}#{mprop.nitdoc_id}.definition"
402 var lnk = new Link(def_url, mprop.html_name)
403 var mdoc = mprop.intro.mdoc_or_fallback
404 if mdoc != null then lnk.title = mdoc.synopsis
405 var item = new Template
406 item.add new DocHTMLLabel.with_classes(classes)
407 item.add lnk
408 return new ListItem(item)
409 end
410 if mpropdefs.has(mprop.intro) then
411 classes.add "intro"
412 else
413 classes.add "redef"
414 end
415 var def = select_mpropdef(mprop)
416 var anc = def.html_link_to_anchor
417 anc.href = "#{def.nitdoc_id}.definition"
418 var lnk = new Template
419 lnk.add new DocHTMLLabel.with_classes(classes)
420 lnk.add anc
421 return new ListItem(lnk)
422 end
423
424 # Get the mpropdef contained in `self` page for a mprop.
425 #
426 # FIXME this method is used to translate a mprop into a mpropdefs for
427 # section linking. A better page structure should avoid this...
428 private fun select_mpropdef(mprop: MProperty): MPropDef do
429 for mclassdef in mentity.mclassdefs do
430 for mpropdef in mclassdef.mpropdefs do
431 if mpropdef.mproperty == mprop then return mpropdef
432 end
433 end
434 abort # FIXME is there a case where the prop is not found?
435 end
436
437 private fun mclass_inherited_mprops(v: RenderHTMLPhase, doc: DocModel): Set[MProperty] do
438 var res = new HashSet[MProperty]
439 var local = mentity.collect_local_mproperties(v.doc)
440 for mprop in mentity.collect_inherited_mproperties(v.doc) do
441 if local.has(mprop) then continue
442 #if mprop isa MMethod and mprop.is_init then continue
443 if mprop.intro.mclassdef.mclass.name == "Object" and
444 (mprop.visibility == protected_visibility or
445 mprop.intro.mclassdef.mmodule.name != "kernel") then continue
446 res.add mprop
447 end
448 res.add_all local
449 return res
450 end
451
452 private fun mprop_is_local(mprop: MProperty): Bool do
453 for mpropdef in mprop.mpropdefs do
454 if self.mpropdefs.has(mpropdef) then return true
455 end
456 return false
457 end
458 end
459
460 redef class MPropertyPage
461 redef fun init_title(v, doc) do
462 title = "{mentity.html_name}{mentity.html_short_signature.write_to_string}"
463 end
464
465 redef fun init_topmenu(v, doc) do
466 super
467 var mmodule = mentity.intro_mclassdef.mmodule
468 var mpackage = mmodule.mgroup.mpackage
469 var mclass = mentity.intro_mclassdef.mclass
470 topmenu.add_li new ListItem(new Link(mpackage.nitdoc_url, mpackage.html_name))
471 topmenu.add_li new ListItem(new Link(mclass.nitdoc_url, mclass.html_name))
472 topmenu.add_li new ListItem(new Link(html_url, mentity.html_name))
473 topmenu.active_item = topmenu.items.last
474 end
475 end
476
477 redef class DocComposite
478 # Prepares the HTML rendering for this element.
479 #
480 # This visit is mainly used to set template attributes before rendering.
481 fun init_html_render(v: RenderHTMLPhase, doc: DocModel, page: DocPage) do
482 for child in children do child.init_html_render(v, doc, page)
483 end
484 end
485
486 # FIXME hideous hacks to avoid diff
487 redef class MEntitySection
488 redef fun init_html_render(v, doc, page) do
489 if not page isa MEntityPage then return
490 var mentity = self.mentity
491 if mentity isa MGroup and mentity.is_root then
492 html_title = mentity.mpackage.html_name
493 html_subtitle = mentity.mpackage.html_declaration
494 else if mentity isa MProperty then
495 var title = new Template
496 title.add mentity.html_name
497 title.add mentity.html_signature
498 html_title = title
499 html_subtitle = mentity.html_namespace
500 html_toc_title = mentity.html_name
501 end
502 super
503 end
504 end
505
506 # FIXME hideous hacks to avoid diff
507 redef class ConcernSection
508 redef fun init_html_render(v, doc, page) do
509 if not page isa MEntityPage then return
510 var mentity = self.mentity
511 if page isa MGroupPage then
512 html_title = null
513 html_toc_title = mentity.html_name
514 is_toc_hidden = false
515 else if page.mentity isa MModule and mentity isa MModule then
516 var title = new Template
517 if mentity == page.mentity then
518 title.add "in "
519 html_toc_title = "in {mentity.html_name}"
520 else
521 title.add "from "
522 html_toc_title = "from {mentity.html_name}"
523 end
524 title.add mentity.html_namespace
525 html_title = title
526 else if (page.mentity isa MClass and mentity isa MModule) or
527 (page.mentity isa MProperty and mentity isa MModule) then
528 var title = new Template
529 title.add "in "
530 title.add mentity.html_namespace
531 html_title = title
532 html_toc_title = "in {mentity.html_name}"
533 end
534 super
535 end
536 end
537
538 # TODO redo showlink
539 redef class IntroArticle
540 redef fun init_html_render(v, doc, page) do
541 var mentity = self.mentity
542 if mentity isa MModule then
543 html_source_link = v.html_source_link(mentity.location)
544 else if mentity isa MClassDef then
545 html_source_link = v.html_source_link(mentity.location)
546 else if mentity isa MPropDef then
547 html_source_link = v.html_source_link(mentity.location)
548 end
549 end
550 end
551
552 # FIXME less hideous hacks...
553 redef class DefinitionArticle
554 redef fun init_html_render(v, doc, page) do
555 var mentity = self.mentity
556 if mentity isa MPackage or mentity isa MModule then
557 var title = new Template
558 title.add mentity.html_icon
559 title.add mentity.html_namespace
560 html_title = title
561 html_toc_title = mentity.html_name
562 if mentity isa MModule then
563 html_source_link = v.html_source_link(mentity.location)
564 end
565 else if mentity isa MClassDef then
566 var title = new Template
567 title.add "in "
568 title.add mentity.mmodule.html_namespace
569 html_title = mentity.html_declaration
570 html_subtitle = title
571 html_toc_title = "in {mentity.html_name}"
572 html_source_link = v.html_source_link(mentity.location)
573 if page isa MEntityPage and mentity.is_intro and mentity.mmodule != page.mentity then
574 is_short_comment = true
575 end
576 if page isa MModulePage then is_toc_hidden = true
577 else if mentity isa MPropDef then
578 if page isa MClassPage then
579 var title = new Template
580 title.add mentity.html_icon
581 title.add mentity.html_declaration
582 html_title = title
583 html_subtitle = mentity.html_namespace
584 html_toc_title = mentity.html_name
585 else
586 var title = new Template
587 title.add "in "
588 title.add mentity.mclassdef.html_link
589 html_title = title
590 html_toc_title = "in {mentity.mclassdef.html_name}"
591 end
592 html_source_link = v.html_source_link(mentity.location)
593 end
594 if page isa MGroupPage and mentity isa MModule then
595 is_toc_hidden = true
596 end
597 super
598 end
599 end
600
601 redef class HomeArticle
602 redef fun init_html_render(v, doc, page) do
603 if v.ctx.opt_custom_title.value != null then
604 self.html_title = v.ctx.opt_custom_title.value.to_s
605 self.html_toc_title = v.ctx.opt_custom_title.value.to_s
606 end
607 self.content = v.ctx.opt_custom_intro.value
608 super
609 end
610 end
611
612 redef class GraphArticle
613 redef fun init_html_render(v, doc, page) do
614 var path = v.ctx.output_dir / graph_id
615 var file = new FileWriter.open("{path}.dot")
616 file.write(dot)
617 file.close
618 var proc = new ProcessReader("dot", "-Tsvg", "-Tcmapx", "{path}.dot")
619 var svg = new Buffer
620 var i = 0
621 while not proc.eof do
622 i += 1
623 if i < 6 then continue # skip dot default header
624 svg.append proc.read_line
625 end
626 proc.close
627 self.svg = svg.write_to_string
628 end
629 end