Merge: Kill `model_utils`
[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 projects 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/myproject/)", "--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 return mentity.nitdoc_url
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 MGroupPage
250 redef fun init_topmenu(v, doc) do
251 super
252 var mproject = mentity.mproject
253 if not mentity.is_root then
254 topmenu.add_li new ListItem(new Link(mproject.nitdoc_url, mproject.html_name))
255 end
256 topmenu.add_li new ListItem(new Link(html_url, mproject.html_name))
257 topmenu.active_item = topmenu.items.last
258 end
259
260 redef fun init_sidebar(v, doc) do
261 super
262 var mclasses = new HashSet[MClass]
263 mclasses.add_all intros
264 mclasses.add_all redefs
265 if mclasses.is_empty then return
266 var list = new UnorderedList
267 list.css_classes.add "list-unstyled list-labeled"
268 var sorted = mclasses.to_a
269 v.name_sorter.sort(sorted)
270 for mclass in sorted do
271 list.add_li tpl_sidebar_item(mclass)
272 end
273 sidebar.boxes.add new DocSideBox("All classes", list)
274 sidebar.boxes.last.is_open = false
275 end
276
277 private fun tpl_sidebar_item(def: MClass): ListItem do
278 var classes = def.intro.css_classes
279 if intros.has(def) then
280 classes.add "intro"
281 else
282 classes.add "redef"
283 end
284 var lnk = new Template
285 lnk.add new DocHTMLLabel.with_classes(classes)
286 lnk.add def.html_link
287 return new ListItem(lnk)
288 end
289 end
290
291 redef class MModulePage
292 redef fun init_topmenu(v, doc) do
293 super
294 var mproject = mentity.mproject
295 topmenu.add_li new ListItem(new Link(mproject.nitdoc_url, mproject.html_name))
296 topmenu.add_li new ListItem(new Link(mentity.nitdoc_url, mentity.html_name))
297 topmenu.active_item = topmenu.items.last
298 end
299
300 # Class list to display in sidebar
301 redef fun init_sidebar(v, doc) do
302 # TODO filter here?
303 super
304 var mclasses = new HashSet[MClass]
305 mclasses.add_all mentity.collect_intro_mclasses(v.ctx.min_visibility)
306 mclasses.add_all mentity.collect_redef_mclasses(v.ctx.min_visibility)
307 if mclasses.is_empty then return
308 var list = new UnorderedList
309 list.css_classes.add "list-unstyled list-labeled"
310
311 var sorted = mclasses.to_a
312 v.name_sorter.sort(sorted)
313 for mclass in sorted do
314 list.add_li tpl_sidebar_item(mclass)
315 end
316 sidebar.boxes.add new DocSideBox("All classes", list)
317 sidebar.boxes.last.is_open = false
318 end
319
320 private fun tpl_sidebar_item(def: MClass): ListItem do
321 var classes = def.intro.css_classes
322 if def.intro_mmodule == self.mentity then
323 classes.add "intro"
324 else
325 classes.add "redef"
326 end
327 var lnk = new Template
328 lnk.add new DocHTMLLabel.with_classes(classes)
329 lnk.add def.html_link
330 return new ListItem(lnk)
331 end
332 end
333
334 redef class MClassPage
335
336 redef fun init_topmenu(v, doc) do
337 super
338 var mproject = mentity.intro_mmodule.mgroup.mproject
339 topmenu.add_li new ListItem(new Link(mproject.nitdoc_url, mproject.html_name))
340 topmenu.add_li new ListItem(new Link(html_url, mentity.html_name))
341 topmenu.active_item = topmenu.items.last
342 end
343
344 redef fun init_sidebar(v, doc) do
345 super
346 var by_kind = new PropertiesByKind.with_elements(mclass_inherited_mprops(v, doc))
347 var summary = new UnorderedList
348 summary.css_classes.add "list-unstyled"
349
350 by_kind.sort_groups(v.name_sorter)
351 for g in by_kind.groups do tpl_sidebar_list(g, summary)
352 sidebar.boxes.add new DocSideBox("All properties", summary)
353 sidebar.boxes.last.is_open = false
354 end
355
356 private fun tpl_sidebar_list(mprops: PropertyGroup[MProperty], summary: UnorderedList) do
357 if mprops.is_empty then return
358 var list = new UnorderedList
359 list.css_classes.add "list-unstyled list-labeled"
360 for mprop in mprops do
361 list.add_li tpl_sidebar_item(mprop)
362 end
363 var content = new Template
364 content.add mprops.title
365 content.add list
366 var li = new ListItem(content)
367 summary.add_li li
368 end
369
370 private fun tpl_sidebar_item(mprop: MProperty): ListItem do
371 var classes = mprop.intro.css_classes
372 if not mprop_is_local(mprop) then
373 classes.add "inherit"
374 var cls_url = mprop.intro.mclassdef.mclass.nitdoc_url
375 var def_url = "{cls_url}#{mprop.nitdoc_id}.definition"
376 var lnk = new Link(def_url, mprop.html_name)
377 var mdoc = mprop.intro.mdoc_or_fallback
378 if mdoc != null then lnk.title = mdoc.short_comment
379 var item = new Template
380 item.add new DocHTMLLabel.with_classes(classes)
381 item.add lnk
382 return new ListItem(item)
383 end
384 if mpropdefs.has(mprop.intro) then
385 classes.add "intro"
386 else
387 classes.add "redef"
388 end
389 var def = select_mpropdef(mprop)
390 var anc = def.html_link_to_anchor
391 anc.href = "#{def.nitdoc_id}.definition"
392 var lnk = new Template
393 lnk.add new DocHTMLLabel.with_classes(classes)
394 lnk.add anc
395 return new ListItem(lnk)
396 end
397
398 # Get the mpropdef contained in `self` page for a mprop.
399 #
400 # FIXME this method is used to translate a mprop into a mpropdefs for
401 # section linking. A better page structure should avoid this...
402 private fun select_mpropdef(mprop: MProperty): MPropDef do
403 for mclassdef in mentity.mclassdefs do
404 for mpropdef in mclassdef.mpropdefs do
405 if mpropdef.mproperty == mprop then return mpropdef
406 end
407 end
408 abort # FIXME is there a case where the prop is not found?
409 end
410
411 private fun mclass_inherited_mprops(v: RenderHTMLPhase, doc: DocModel): Set[MProperty] do
412 var res = new HashSet[MProperty]
413 var local = mentity.collect_local_mproperties(v.ctx.min_visibility)
414 for mprop in mentity.collect_inherited_mproperties(v.ctx.min_visibility) do
415 if local.has(mprop) then continue
416 #if mprop isa MMethod and mprop.is_init then continue
417 if mprop.intro.mclassdef.mclass.name == "Object" and
418 (mprop.visibility == protected_visibility or
419 mprop.intro.mclassdef.mmodule.name != "kernel") then continue
420 res.add mprop
421 end
422 res.add_all local
423 return res
424 end
425
426 private fun mprop_is_local(mprop: MProperty): Bool do
427 for mpropdef in mprop.mpropdefs do
428 if self.mpropdefs.has(mpropdef) then return true
429 end
430 return false
431 end
432 end
433
434 redef class MPropertyPage
435 redef fun init_title(v, doc) do
436 title = "{mentity.html_name}{mentity.html_short_signature.write_to_string}"
437 end
438
439 redef fun init_topmenu(v, doc) do
440 super
441 var mmodule = mentity.intro_mclassdef.mmodule
442 var mproject = mmodule.mgroup.mproject
443 var mclass = mentity.intro_mclassdef.mclass
444 topmenu.add_li new ListItem(new Link(mproject.nitdoc_url, mproject.html_name))
445 topmenu.add_li new ListItem(new Link(mclass.nitdoc_url, mclass.html_name))
446 topmenu.add_li new ListItem(new Link(html_url, mentity.html_name))
447 topmenu.active_item = topmenu.items.last
448 end
449 end
450
451 redef class DocComposite
452 # Prepares the HTML rendering for this element.
453 #
454 # This visit is mainly used to set template attributes before rendering.
455 fun init_html_render(v: RenderHTMLPhase, doc: DocModel, page: DocPage) do
456 for child in children do child.init_html_render(v, doc, page)
457 end
458 end
459
460 # FIXME hideous hacks to avoid diff
461 redef class MEntitySection
462 redef fun init_html_render(v, doc, page) do
463 if not page isa MEntityPage then return
464 var mentity = self.mentity
465 if mentity isa MGroup and mentity.is_root then
466 html_title = mentity.mproject.html_name
467 html_subtitle = mentity.mproject.html_declaration
468 else if mentity isa MProperty then
469 var title = new Template
470 title.add mentity.html_name
471 title.add mentity.html_signature
472 html_title = title
473 html_subtitle = mentity.html_namespace
474 html_toc_title = mentity.html_name
475 end
476 super
477 end
478 end
479
480 # FIXME hideous hacks to avoid diff
481 redef class ConcernSection
482 redef fun init_html_render(v, doc, page) do
483 if not page isa MEntityPage then return
484 var mentity = self.mentity
485 if page isa MGroupPage then
486 html_title = null
487 html_toc_title = mentity.html_name
488 is_toc_hidden = false
489 else if page.mentity isa MModule and mentity isa MModule then
490 var title = new Template
491 if mentity == page.mentity then
492 title.add "in "
493 html_toc_title = "in {mentity.html_name}"
494 else
495 title.add "from "
496 html_toc_title = "from {mentity.html_name}"
497 end
498 title.add mentity.html_namespace
499 html_title = title
500 else if (page.mentity isa MClass and mentity isa MModule) or
501 (page.mentity isa MProperty and mentity isa MModule) then
502 var title = new Template
503 title.add "in "
504 title.add mentity.html_namespace
505 html_title = title
506 html_toc_title = "in {mentity.html_name}"
507 end
508 super
509 end
510 end
511
512 # TODO redo showlink
513 redef class IntroArticle
514 redef fun init_html_render(v, doc, page) do
515 var mentity = self.mentity
516 if mentity isa MModule then
517 html_source_link = v.html_source_link(mentity.location)
518 else if mentity isa MClassDef then
519 html_source_link = v.html_source_link(mentity.location)
520 else if mentity isa MPropDef then
521 html_source_link = v.html_source_link(mentity.location)
522 end
523 end
524 end
525
526 # FIXME less hideous hacks...
527 redef class DefinitionArticle
528 redef fun init_html_render(v, doc, page) do
529 var mentity = self.mentity
530 if mentity isa MProject or mentity isa MModule then
531 var title = new Template
532 title.add mentity.html_icon
533 title.add mentity.html_namespace
534 html_title = title
535 html_toc_title = mentity.html_name
536 if mentity isa MModule then
537 html_source_link = v.html_source_link(mentity.location)
538 end
539 else if mentity isa MClassDef then
540 var title = new Template
541 title.add "in "
542 title.add mentity.mmodule.html_namespace
543 html_title = mentity.html_declaration
544 html_subtitle = title
545 html_toc_title = "in {mentity.html_name}"
546 html_source_link = v.html_source_link(mentity.location)
547 if page isa MEntityPage and mentity.is_intro and mentity.mmodule != page.mentity then
548 is_short_comment = true
549 end
550 if page isa MModulePage then is_toc_hidden = true
551 else if mentity isa MPropDef then
552 if page isa MClassPage then
553 var title = new Template
554 title.add mentity.html_icon
555 title.add mentity.html_declaration
556 html_title = title
557 html_subtitle = mentity.html_namespace
558 html_toc_title = mentity.html_name
559 else
560 var title = new Template
561 title.add "in "
562 title.add mentity.mclassdef.html_link
563 html_title = title
564 html_toc_title = "in {mentity.mclassdef.html_name}"
565 end
566 html_source_link = v.html_source_link(mentity.location)
567 end
568 if page isa MGroupPage and mentity isa MModule then
569 is_toc_hidden = true
570 end
571 super
572 end
573 end
574
575 redef class HomeArticle
576 redef fun init_html_render(v, doc, page) do
577 if v.ctx.opt_custom_title.value != null then
578 self.html_title = v.ctx.opt_custom_title.value.to_s
579 self.html_toc_title = v.ctx.opt_custom_title.value.to_s
580 end
581 self.content = v.ctx.opt_custom_intro.value
582 super
583 end
584 end
585
586 redef class GraphArticle
587 redef fun init_html_render(v, doc, page) do
588 var output_dir = v.ctx.output_dir
589 var path = output_dir / graph_id
590 var path_sh = path.escape_to_sh
591 var file = new FileWriter.open("{path}.dot")
592 file.write(dot)
593 file.close
594 sys.system("\{ test -f {path_sh}.png && test -f {path_sh}.s.dot && diff -- {path_sh}.dot {path_sh}.s.dot >/dev/null 2>&1 ; \} || \{ cp -- {path_sh}.dot {path_sh}.s.dot && dot -Tpng -o{path_sh}.png -Tcmapx -o{path_sh}.map {path_sh}.s.dot ; \}")
595 var fmap = new FileReader.open("{path}.map")
596 self.map = fmap.read_all
597 fmap.close
598 end
599 end