e41cae264999eed90d68e0b178fcc292749f6c97
[nit.git] / src / doc / doc_pages.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 # Nitdoc page generation
16 module doc_pages
17
18 import doc_model
19
20 # The NitdocContext contains all the knowledge used for doc generation
21 class NitdocContext
22 private var opt_dir = new OptionString("output directory", "-d", "--dir")
23 private var opt_source = new OptionString("link for source (%f for filename, %l for first line, %L for last line)", "--source")
24 private var opt_sharedir = new OptionString("directory containing nitdoc assets", "--sharedir")
25 private var opt_shareurl = new OptionString("use shareurl instead of copy shared files", "--shareurl")
26 private var opt_nodot = new OptionBool("do not generate graphes with graphviz", "--no-dot")
27 private var opt_private = new OptionBool("also generate private API", "--private")
28
29 private var opt_custom_title = new OptionString("custom title for homepage", "--custom-title")
30 private var opt_custom_menu = new OptionString("custom items added in top menu (each item must be enclosed in 'li' tags)", "--custom-menu-items")
31 private var opt_custom_intro = new OptionString("custom intro text for homepage", "--custom-overview-text")
32 private var opt_custom_footer = new OptionString("custom footer text", "--custom-footer-text")
33
34 private var opt_github_upstream = new OptionString("Git branch where edited commits will be pulled into (ex: user:repo:branch)", "--github-upstream")
35 private var opt_github_base_sha1 = new OptionString("Git sha1 of base commit used to create pull request", "--github-base-sha1")
36 private var opt_github_gitdir = new OptionString("Git working directory used to resolve path name (ex: /home/me/myproject/)", "--github-gitdir")
37
38 private var opt_piwik_tracker = new OptionString("Piwik tracker URL (ex: nitlanguage.org/piwik/)", "--piwik-tracker")
39 private var opt_piwik_site_id = new OptionString("Piwik site ID", "--piwik-site-id")
40
41 private var toolcontext = new ToolContext
42 private var mbuilder: ModelBuilder
43 private var mainmodule: MModule
44 private var output_dir: String
45 private var min_visibility: MVisibility
46
47 init do
48 var opts = toolcontext.option_context
49 opts.add_option(opt_dir, opt_source, opt_sharedir, opt_shareurl, opt_nodot, opt_private)
50 opts.add_option(opt_custom_title, opt_custom_footer, opt_custom_intro, opt_custom_menu)
51 opts.add_option(opt_github_upstream, opt_github_base_sha1, opt_github_gitdir)
52 opts.add_option(opt_piwik_tracker, opt_piwik_site_id)
53
54 var tpl = new Template
55 tpl.add "Usage: nitdoc [OPTION]... <file.nit>...\n"
56 tpl.add "Generates HTML pages of API documentation from Nit source files."
57 toolcontext.tooldescription = tpl.write_to_string
58 toolcontext.process_options(args)
59
60 self.process_options
61 self.parse(toolcontext.option_context.rest)
62 end
63
64 private fun process_options do
65 if opt_private.value then
66 min_visibility = none_visibility
67 else
68 min_visibility = protected_visibility
69 end
70 var gh_upstream = opt_github_upstream.value
71 var gh_base_sha = opt_github_base_sha1.value
72 var gh_gitdir = opt_github_gitdir.value
73 if not gh_upstream == null or not gh_base_sha == null or not gh_gitdir == null then
74 if gh_upstream == null or gh_base_sha == null or gh_gitdir == null then
75 print "Error: Options {opt_github_upstream.names.first}, {opt_github_base_sha1.names.first} and {opt_github_gitdir.names.first} are required to enable the GitHub plugin"
76 abort
77 end
78 end
79 end
80
81 private fun parse(arguments: Array[String]) do
82 var model = new Model
83 mbuilder = new ModelBuilder(model, toolcontext)
84 var mmodules = mbuilder.parse(arguments)
85 if mmodules.is_empty then return
86 mbuilder.run_phases
87 if mmodules.length == 1 then
88 mainmodule = mmodules.first
89 else
90 mainmodule = new MModule(model, null, "<main>", new Location(null, 0, 0, 0, 0))
91 mainmodule.is_fictive = true
92 mainmodule.set_imported_mmodules(mmodules)
93 end
94 end
95
96 fun generate_nitdoc do
97 init_output_dir
98 overview
99 search
100 modules
101 classes
102 quicksearch_list
103 end
104
105 private fun init_output_dir do
106 # location output dir
107 var output_dir = opt_dir.value
108 if output_dir == null then
109 output_dir = "doc"
110 end
111 self.output_dir = output_dir
112 # create destination dir if it's necessary
113 if not output_dir.file_exists then output_dir.mkdir
114 # locate share dir
115 var sharedir = opt_sharedir.value
116 if sharedir == null then
117 var dir = toolcontext.nit_dir
118 if dir == null then
119 print "Error: Cannot locate nitdoc share files. Uses --sharedir or envvar NIT_DIR"
120 abort
121 end
122 sharedir = "{dir}/share/nitdoc"
123 if not sharedir.file_exists then
124 print "Error: Cannot locate nitdoc share files. Uses --sharedir or envvar NIT_DIR"
125 abort
126 end
127 end
128 # copy shared files
129 if opt_shareurl.value == null then
130 sys.system("cp -r {sharedir.to_s}/* {output_dir.to_s}/")
131 else
132 sys.system("cp -r {sharedir.to_s}/resources/ {output_dir.to_s}/resources/")
133 end
134
135 end
136
137 private fun overview do
138 var overviewpage = new NitdocOverview(self)
139 overviewpage.render.write_to_file("{output_dir.to_s}/index.html")
140 end
141
142 private fun search do
143 var searchpage = new NitdocSearch(self)
144 searchpage.render.write_to_file("{output_dir.to_s}/search.html")
145 end
146
147 private fun modules do
148 for mmodule in mbuilder.model.mmodules do
149 if mmodule.name == "<main>" then continue
150 var modulepage = new NitdocModule(mmodule, self)
151 modulepage.render.write_to_file("{output_dir.to_s}/{mmodule.nitdoc_url}")
152 end
153 end
154
155 private fun classes do
156 for mclass in mbuilder.model.mclasses do
157 var classpage = new NitdocClass(mclass, self)
158 classpage.render.write_to_file("{output_dir.to_s}/{mclass.nitdoc_url}")
159 end
160 end
161
162 private fun quicksearch_list do
163 var quicksearch = new QuickSearch(self)
164 quicksearch.render.write_to_file("{output_dir.to_s}/quicksearch-list.js")
165 end
166 end
167
168 # Nitdoc QuickSearch list generator
169 #
170 # Create a JSON object containing links to:
171 # * modules
172 # * mclasses
173 # * mpropdefs
174 # All entities are grouped by name to make the research easier.
175 class QuickSearch
176
177 private var mmodules = new HashSet[MModule]
178 private var mclasses = new HashSet[MClass]
179 private var mpropdefs = new HashMap[String, Set[MPropDef]]
180
181 init(ctx: NitdocContext) do
182 for mmodule in ctx.mbuilder.model.mmodules do
183 if mmodule.name == "<main>" then continue
184 mmodules.add mmodule
185 end
186 for mclass in ctx.mbuilder.model.mclasses do
187 if mclass.visibility < ctx.min_visibility then continue
188 mclasses.add mclass
189 end
190 for mproperty in ctx.mbuilder.model.mproperties do
191 if mproperty.visibility < ctx.min_visibility then continue
192 if mproperty isa MAttribute then continue
193 if not mpropdefs.has_key(mproperty.name) then
194 mpropdefs[mproperty.name] = new HashSet[MPropDef]
195 end
196 mpropdefs[mproperty.name].add_all(mproperty.mpropdefs)
197 end
198 end
199
200 fun render: Template do
201 var tpl = new Template
202 tpl.add "var nitdocQuickSearchRawList=\{ "
203 for mmodule in mmodules do
204 tpl.add "\"{mmodule.name}\":["
205 tpl.add "\{txt:\"{mmodule.full_name}\",url:\"{mmodule.nitdoc_url}\"\},"
206 tpl.add "],"
207 end
208 for mclass in mclasses do
209 var full_name = mclass.intro.mmodule.full_name
210 tpl.add "\"{mclass.name}\":["
211 tpl.add "\{txt:\"{full_name}\",url:\"{mclass.nitdoc_url}\"\},"
212 tpl.add "],"
213 end
214 for mproperty, mprops in mpropdefs do
215 tpl.add "\"{mproperty}\":["
216 for mpropdef in mprops do
217 var full_name = mpropdef.mclassdef.mclass.full_name
218 tpl.add "\{txt:\"{full_name}\",url:\"{mpropdef.nitdoc_url}\"\},"
219 end
220 tpl.add "],"
221 end
222 tpl.add " \};"
223 return tpl
224 end
225 end
226
227 # Nitdoc base page
228 # Define page structure and properties
229 abstract class NitdocPage
230
231 private var ctx: NitdocContext
232 private var model: Model
233
234 init(ctx: NitdocContext) do
235 self.ctx = ctx
236 self.model = ctx.mbuilder.model
237 end
238
239 # Render the page as a html template
240 fun render: Template do
241 var shareurl = "."
242 if ctx.opt_shareurl.value != null then
243 shareurl = ctx.opt_shareurl.value.as(not null)
244 end
245
246 # build page
247 var tpl = tpl_page
248 tpl.title = tpl_title
249 tpl.shareurl = shareurl
250 tpl.topmenu = tpl_topmenu
251 tpl_content
252 tpl.footer = ctx.opt_custom_footer.value
253 tpl.body_attrs.add(new TagAttribute("data-bootstrap-share", shareurl))
254 tpl.sidebar = tpl_sidebar
255
256 # piwik tracking
257 var tracker_url = ctx.opt_piwik_tracker.value
258 var site_id = ctx.opt_piwik_site_id.value
259 if tracker_url != null and site_id != null then
260 tpl.scripts.add new TplPiwikScript(tracker_url, site_id)
261 end
262 return tpl
263 end
264
265 # Build page template
266 fun tpl_page: TplPage is abstract
267
268 # Build page sidebar if any
269 fun tpl_sidebar: nullable TplSidebar do return null
270
271 # Build page title string
272 fun tpl_title: String do
273 if ctx.opt_custom_title.value != null then
274 return ctx.opt_custom_title.value.to_s
275 end
276 return "Nitdoc"
277 end
278
279 # Build top menu template
280 fun tpl_topmenu: TplTopMenu do
281 var topmenu = new TplTopMenu
282 var custom_elt = ctx.opt_custom_menu.value
283 if custom_elt != null then topmenu.add_raw(custom_elt)
284 return topmenu
285 end
286
287 # Build page content template
288 fun tpl_content is abstract
289
290 # Clickable graphviz image using dot format
291 # return null if no graph for this page
292 fun tpl_graph(dot: FlatBuffer, name: String, title: String): nullable TplArticle do
293 if ctx.opt_nodot.value then return null
294 var output_dir = ctx.output_dir
295 var file = new OFStream.open("{output_dir}/{name}.dot")
296 file.write(dot)
297 file.close
298 sys.system("\{ test -f {output_dir}/{name}.png && test -f {output_dir}/{name}.s.dot && diff {output_dir}/{name}.dot {output_dir}/{name}.s.dot >/dev/null 2>&1 ; \} || \{ cp {output_dir}/{name}.dot {output_dir}/{name}.s.dot && dot -Tpng -o{output_dir}/{name}.png -Tcmapx -o{output_dir}/{name}.map {output_dir}/{name}.s.dot ; \}")
299 var fmap = new IFStream.open("{output_dir}/{name}.map")
300 var map = fmap.read_all
301 fmap.close
302
303 var article = new TplArticle.with_title("graph", title)
304 var content = new Template
305 content.add "<img src='{name}.png' usemap='#{name}' style='margin:auto' alt='{title}'/>"
306 content.add map
307 article.content = content
308 return article
309 end
310
311 # A (source) link template for a given location
312 fun tpl_showsource(location: nullable Location): nullable String
313 do
314 if location == null then return null
315 var source = ctx.opt_source.value
316 if source == null then return "({location.file.filename.simplify_path})"
317 # THIS IS JUST UGLY ! (but there is no replace yet)
318 var x = source.split_with("%f")
319 source = x.join(location.file.filename.simplify_path)
320 x = source.split_with("%l")
321 source = x.join(location.line_start.to_s)
322 x = source.split_with("%L")
323 source = x.join(location.line_end.to_s)
324 source = source.simplify_path
325 return " (<a target='_blank' title='Show source' href=\"{source.to_s}\">source</a>)"
326 end
327
328 # MClassDef description template
329 fun tpl_mclass_article(mclass: MClass, mclassdefs: Array[MClassDef]): TplArticle do
330 var article = new TplArticle(mclass.nitdoc_anchor)
331 var title = new Template
332 var icon = new TplIcon.with_icon("tag")
333 icon.css_classes.add_all(mclass.intro.tpl_css_classes)
334 title.add icon
335 title.add mclass.tpl_link
336 title.add mclass.intro.tpl_signature
337 article.title = title
338 article.title_classes.add "signature"
339 article.subtitle = mclass.tpl_declaration
340 article.summary_title = "{mclass.nitdoc_name}{mclass.tpl_signature.write_to_string}"
341 #article.subtitle = new Template
342 #article.subtitle.add mprop.intro.tpl_modifiers
343 #article.subtitle.add mprop.intro.tpl_namespace
344 var content = new Template
345
346 if not mclassdefs.has(mclass.intro) then
347 # add intro synopsys
348 var intro = mclass.intro
349 var location = intro.location
350 var sourcelink = tpl_showsource(location)
351 var intro_def = intro.tpl_definition
352 intro_def.location = sourcelink
353 content.add intro_def
354 end
355 ctx.mainmodule.linearize_mclassdefs(mclassdefs)
356 for mclassdef in mclassdefs do
357 # add mclassdef full description
358 var location = mclassdef.location
359 var sourcelink = tpl_showsource(location)
360 var prop_def = mclassdef.tpl_definition.as(TplClassDefinition)
361 prop_def.location = sourcelink
362 for mpropdef in mclassdef.mpropdefs do
363 var intro = mpropdef.mproperty.intro
364 if mpropdef isa MAttributeDef then continue
365 if mpropdef.mproperty.visibility < ctx.min_visibility then continue
366
367 var lnk = new Template
368 lnk.add new TplLabel.with_classes(mpropdef.tpl_css_classes.to_a)
369 lnk.add mpropdef.tpl_link
370 if intro.mdoc != null then
371 lnk.add ": "
372 lnk.add intro.mdoc.short_comment
373 end
374 if mpropdef.is_intro then
375 prop_def.intros.add new TplListItem.with_content(lnk)
376 else
377 prop_def.redefs.add new TplListItem.with_content(lnk)
378 end
379 end
380 content.add prop_def
381 end
382 article.content = content
383 return article
384 end
385
386 # MProp description template
387 fun tpl_mprop_article(mprop: MProperty, mpropdefs: Array[MPropDef]): TplArticle do
388 var article = new TplArticle(mprop.intro.nitdoc_anchor)
389 var icon = new TplIcon.with_icon("tag")
390 icon.css_classes.add_all(mprop.intro.tpl_css_classes)
391 var title = new Template
392 title.add icon
393 title.add mprop.nitdoc_name
394 title.add mprop.intro.tpl_signature
395 article.title = title
396 article.title_classes.add "signature"
397 article.subtitle = mprop.tpl_declaration
398 article.summary_title = mprop.nitdoc_name
399 #article.subtitle = new Template
400 #article.subtitle.add mprop.intro.tpl_modifiers
401 #article.subtitle.add mprop.intro.tpl_namespace
402 var content = new Template
403
404 if not mpropdefs.has(mprop.intro) then
405 # add intro synopsys
406 var intro = mprop.intro
407 var location = intro.location
408 var sourcelink = tpl_showsource(location)
409 var intro_def = intro.tpl_definition
410 intro_def.location = sourcelink
411 content.add intro_def
412 end
413
414 ctx.mainmodule.linearize_mpropdefs(mpropdefs)
415 for mpropdef in mpropdefs do
416 # add mpropdef description
417 var location = mpropdef.location
418 var sourcelink = tpl_showsource(location)
419 var prop_def = mpropdef.tpl_definition
420 prop_def.location = sourcelink
421 content.add prop_def
422 end
423 article.content = content
424 return article
425 end
426 end
427
428 # The overview page
429 # Display a list of modules contained in program
430 class NitdocOverview
431 super NitdocPage
432
433 init(ctx: NitdocContext) do super(ctx)
434
435 private var page = new TplPage
436 redef fun tpl_page do return page
437
438 private var sidebar = new TplSidebar
439 redef fun tpl_sidebar do return sidebar
440
441 redef fun tpl_title do
442 if ctx.opt_custom_title.value != null then
443 return ctx.opt_custom_title.value.to_s
444 else
445 return "Overview"
446 end
447 end
448
449 redef fun tpl_topmenu do
450 var topmenu = super
451 topmenu.add_link("#", "Overview", true)
452 topmenu.add_link("search.html", "Index", false)
453 return topmenu
454 end
455
456 # intro text
457 private fun tpl_intro: TplSection do
458 var section = new TplSection.with_title("overview", tpl_title)
459 var article = new TplArticle("intro")
460 if ctx.opt_custom_intro.value != null then
461 article.content = ctx.opt_custom_intro.value.to_s
462 end
463 section.add_child article
464 return section
465 end
466
467 # projects list
468 private fun tpl_projects(section: TplSection) do
469 # Projects list
470 var ssection = new TplSection.with_title("projects", "Projects")
471 for mproject in model.mprojects do
472 ssection.add_child mproject.tpl_article
473 end
474 section.add_child ssection
475 end
476
477 redef fun tpl_content do
478 var top = tpl_intro
479 tpl_projects(top)
480 tpl_page.add_section top
481 end
482 end
483
484 # The search page
485 # Display a list of modules, classes and properties
486 class NitdocSearch
487 super NitdocPage
488
489 init(ctx: NitdocContext) do super(ctx)
490
491 private var page = new TplPage
492 redef fun tpl_page do return page
493
494 redef fun tpl_title do return "Index"
495
496 redef fun tpl_topmenu do
497 var topmenu = super
498 topmenu.add_link("index.html", "Overview", false)
499 topmenu.add_link("#", "Index", true)
500 return topmenu
501 end
502
503 redef fun tpl_content do
504 var tpl = new TplSearchPage("search_all")
505 var section = new TplSection("search")
506 # title
507 tpl.title = "Index"
508 # modules list
509 for mmodule in modules_list do
510 tpl.modules.add mmodule.tpl_link
511 end
512 # classes list
513 for mclass in classes_list do
514 tpl.classes.add mclass.tpl_link
515 end
516 # properties list
517 for mproperty in mprops_list do
518 var m = new Template
519 m.add mproperty.intro.tpl_link
520 m.add " ("
521 m.add mproperty.intro.mclassdef.mclass.tpl_link
522 m.add ")"
523 tpl.props.add m
524 end
525 section.add_child tpl
526 tpl_page.add_section section
527 end
528
529 # Extract mmodule list to display (sorted by name)
530 private fun modules_list: Array[MModule] do
531 var sorted = new Array[MModule]
532 for mmodule in ctx.mbuilder.model.mmodule_importation_hierarchy do
533 if mmodule.name == "<main>" then continue
534 sorted.add mmodule
535 end
536 var sorter = new MModuleNameSorter
537 sorter.sort(sorted)
538 return sorted
539 end
540
541 # Extract mclass list to display (sorted by name)
542 private fun classes_list: Array[MClass] do
543 var sorted = new Array[MClass]
544 for mclass in ctx.mbuilder.model.mclasses do
545 if mclass.visibility < ctx.min_visibility then continue
546 sorted.add mclass
547 end
548 var sorter = new MClassNameSorter
549 sorter.sort(sorted)
550 return sorted
551 end
552
553 # Extract mproperty list to display (sorted by name)
554 private fun mprops_list: Array[MProperty] do
555 var sorted = new Array[MProperty]
556 for mproperty in ctx.mbuilder.model.mproperties do
557 if mproperty.visibility < ctx.min_visibility then continue
558 if mproperty isa MAttribute then continue
559 sorted.add mproperty
560 end
561 var sorter = new MPropertyNameSorter
562 sorter.sort(sorted)
563 return sorted
564 end
565 end
566
567 # A module page
568 # Display the list of introduced and redefined classes in module
569 class NitdocModule
570 super NitdocPage
571
572 private var mmodule: MModule
573
574 init(mmodule: MModule, ctx: NitdocContext) do
575 self.mmodule = mmodule
576 super(ctx)
577 end
578
579 private var page = new TplPage
580 redef fun tpl_page do return page
581
582 private var sidebar = new TplSidebar
583 redef fun tpl_sidebar do return sidebar
584
585 redef fun tpl_title do return "{mmodule.nitdoc_name}"
586
587 redef fun tpl_topmenu do
588 var topmenu = super
589 topmenu.add_link("index.html", "Overview", false)
590 topmenu.add_link("#", "{mmodule.nitdoc_name}", true)
591 topmenu.add_link("search.html", "Index", false)
592 return topmenu
593 end
594
595 # intro text
596 private fun tpl_intro: TplSection do
597 var section = new TplSection.with_title(mmodule.nitdoc_anchor, tpl_title)
598 section.subtitle = mmodule.tpl_declaration
599
600 var article = new TplArticle("intro")
601 var def = mmodule.tpl_definition
602 var location = mmodule.location
603 def.location = tpl_showsource(location)
604 article.content = def
605 section.add_child article
606 return section
607 end
608
609 # inheritance section
610 private fun tpl_inheritance(parent: TplSection) do
611 # Extract relevent modules
612 var nested = mmodule.in_nesting.direct_greaters.to_a
613 var imports = mmodule.in_importation.greaters
614 if imports.length > 10 then imports = mmodule.in_importation.direct_greaters
615 var clients = mmodule.in_importation.smallers
616 if clients.length > 10 then clients = mmodule.in_importation.direct_smallers
617
618 # Display lists
619 var sorter = new MModuleNameSorter
620 var section = new TplSection.with_title("inheritance", "Inheritance")
621
622 # Graph
623 var mmodules = new HashSet[MModule]
624 mmodules.add_all nested
625 mmodules.add_all imports
626 if clients.length < 10 then mmodules.add_all clients
627 mmodules.add mmodule
628 var graph = tpl_dot(mmodules)
629 if graph != null then section.add_child graph
630
631 # nested modules
632 if not nested.is_empty then
633 var lst = nested.to_a
634 sorter.sort lst
635 section.add_child tpl_list("nesting", "Nested modules", lst)
636 end
637
638 # Imports
639 var lst = new Array[MModule]
640 for dep in imports do
641 if dep.is_fictive then continue
642 if dep == mmodule then continue
643 lst.add(dep)
644 end
645 if not lst.is_empty then
646 sorter.sort lst
647 section.add_child tpl_list("imports", "Imports", lst)
648 end
649
650 # Clients
651 lst = new Array[MModule]
652 for dep in clients do
653 if dep.is_fictive then continue
654 if dep == mmodule then continue
655 lst.add(dep)
656 end
657 if not lst.is_empty then
658 sorter.sort lst
659 section.add_child tpl_list("clients", "Clients", lst)
660 end
661
662 parent.add_child section
663 end
664
665 private fun tpl_list(id: String, title: String, mmodules: Array[MModule]): TplArticle do
666 var article = new TplArticle.with_title(id, title)
667 var list = new TplList.with_classes(["list-unstyled", "list-definition"])
668 for mmodule in mmodules do list.elts.add mmodule.tpl_list_item
669 article.content = list
670 return article
671 end
672
673 private fun tpl_mclasses(parent: TplSection) do
674 var sorter = new MClassNameSorter
675 var mclassdefs = new HashSet[MClassDef]
676 mclassdefs.add_all mmodule.in_nesting_intro_mclassdefs(ctx.min_visibility)
677 mclassdefs.add_all mmodule.in_nesting_redef_mclassdefs(ctx.min_visibility)
678 var mclasses2mdefs = sort_by_mclass(mclassdefs)
679 var sorted_mclasses = mclasses2mdefs.keys.to_a
680 sorter.sort sorted_mclasses
681
682 # intros
683 var section = new TplSection.with_title("intros", "Introductions")
684 var intros = mmodule.in_nesting_intro_mclasses(ctx.min_visibility)
685 var sorted_intros = intros.to_a
686 sorter.sort(sorted_intros)
687 for mclass in sorted_intros do
688 if not mclasses2mdefs.has_key(mclass) then continue
689 section.add_child tpl_mclass_article(mclass, mclasses2mdefs[mclass].to_a)
690 end
691 parent.add_child section
692
693 # redefs
694 section = new TplSection.with_title("redefs", "Refinements")
695 var redefs = mmodule.in_nesting_redef_mclasses(ctx.min_visibility).to_a
696 sorter.sort(redefs)
697 for mclass in redefs do
698 if intros.has(mclass) then continue
699 if not mclasses2mdefs.has_key(mclass) then continue
700 section.add_child tpl_mclass_article(mclass, mclasses2mdefs[mclass].to_a)
701 end
702 parent.add_child section
703 end
704
705 redef fun tpl_content do
706 var top = tpl_intro
707 tpl_inheritance(top)
708 tpl_mclasses(top)
709 tpl_page.add_section top
710 end
711
712 # Genrate dot hierarchy for class inheritance
713 fun tpl_dot(mmodules: Collection[MModule]): nullable TplArticle do
714 var poset = new POSet[MModule]
715 for mmodule in mmodules do
716 if mmodule.is_fictive then continue
717 poset.add_node mmodule
718 for omodule in mmodules do
719 if mmodule.is_fictive then continue
720 poset.add_node mmodule
721 if mmodule.in_importation < omodule then
722 poset.add_edge(mmodule, omodule)
723 end
724 end
725 end
726 # build graph
727 var op = new FlatBuffer
728 var name = "dep_{mmodule.name}"
729 op.append("digraph {name} \{ rankdir=BT; node[shape=none,margin=0,width=0,height=0,fontsize=10]; edge[dir=none,color=gray]; ranksep=0.2; nodesep=0.1;\n")
730 for mmodule in poset do
731 if mmodule == self.mmodule then
732 op.append("\"{mmodule.name}\"[shape=box,margin=0.03];\n")
733 else
734 op.append("\"{mmodule.name}\"[URL=\"{mmodule.nitdoc_url}\"];\n")
735 end
736 for omodule in poset[mmodule].direct_greaters do
737 op.append("\"{mmodule.name}\"->\"{omodule.name}\";\n")
738 end
739 end
740 op.append("\}\n")
741 return tpl_graph(op, name, "Dependency graph")
742 end
743
744 private fun sort_by_mclass(mclassdefs: Collection[MClassDef]): Map[MClass, Set[MClassDef]] do
745 var map = new HashMap[MClass, Set[MClassDef]]
746 for mclassdef in mclassdefs do
747 var mclass = mclassdef.mclass
748 if not map.has_key(mclass) then map[mclass] = new HashSet[MClassDef]
749 map[mclass].add mclassdef
750 end
751 return map
752 end
753 end
754
755 # A class page
756 # Display a list properties defined or redefined for this class
757 class NitdocClass
758 super NitdocPage
759
760 private var mclass: MClass
761 private var mprops2mdefs: Map[MProperty, Set[MPropDef]]
762
763 init(mclass: MClass, ctx: NitdocContext) do
764 self.mclass = mclass
765 super(ctx)
766 var mpropdefs = new HashSet[MPropDef]
767 mpropdefs.add_all mclass.intro_mpropdefs(ctx.min_visibility)
768 mpropdefs.add_all mclass.redef_mpropdefs(ctx.min_visibility)
769 mprops2mdefs = sort_by_mproperty(mpropdefs)
770 end
771
772 private var page = new TplPage
773 redef fun tpl_page do return page
774
775 private var sidebar = new TplSidebar
776 redef fun tpl_sidebar do return sidebar
777
778 redef fun tpl_title do return "{mclass.nitdoc_name}{mclass.tpl_signature.write_to_string}"
779
780 redef fun tpl_topmenu do
781 var topmenu = super
782 var mmodule: MModule
783 if mclass.public_owner == null then
784 mmodule = mclass.intro_mmodule
785 else
786 mmodule = mclass.public_owner.as(not null)
787 end
788 topmenu.add_link("index.html", "Overview", false)
789 topmenu.add_link("{mmodule.nitdoc_url}", "{mmodule.nitdoc_name}", false)
790 topmenu.add_link("#", "{mclass.nitdoc_name}", true)
791 topmenu.add_link("search.html", "Index", false)
792 return topmenu
793 end
794
795 # Property list to display in sidebar
796 fun tpl_sidebar_properties do
797 var kind_map = sort_by_kind(mclass_inherited_mprops)
798 var summary = new TplList.with_classes(["list-unstyled"])
799
800 tpl_sidebar_list("Virtual types", kind_map["type"].to_a, summary)
801 tpl_sidebar_list("Constructors", kind_map["init"].to_a, summary)
802 tpl_sidebar_list("Methods", kind_map["fun"].to_a, summary)
803 tpl_sidebar.boxes.add new TplSideBox.with_content("All properties", summary)
804 end
805
806 var mprop_sorter = new MPropertyNameSorter
807
808 private fun tpl_sidebar_list(name: String, mprops: Array[MProperty], summary: TplList) do
809 if mprops.is_empty then return
810 mprop_sorter.sort(mprops)
811 var entry = new TplListItem.with_content(name)
812 var list = new TplList.with_classes(["list-unstyled", "list-labeled"])
813 for mprop in mprops do
814 list.add_li tpl_sidebar_item(mprop)
815 end
816 entry.append list
817 summary.elts.add entry
818 end
819
820 private fun tpl_sidebar_item(mprop: MProperty): Template do
821 var classes = mprop.intro.tpl_css_classes.to_a
822 if not mprops2mdefs.has_key(mprop) then
823 classes.add "inherit"
824 var lnk = new Template
825 lnk.add new TplLabel.with_classes(classes)
826 lnk.add mprop.intro.tpl_link
827 return lnk
828 end
829 var defs = mprops2mdefs[mprop]
830 if defs.has(mprop.intro) then
831 classes.add "intro"
832 else
833 classes.add "redef"
834 end
835 var lnk = new Template
836 lnk.add new TplLabel.with_classes(classes)
837 lnk.add mprop.intro.tpl_anchor
838 return lnk
839 end
840
841 private fun tpl_intro: TplSection do
842 var section = new TplSection.with_title(mclass.nitdoc_anchor, tpl_title)
843 section.subtitle = mclass.tpl_declaration
844 var article = new TplArticle("intro")
845 var intro = mclass.intro
846 var def = intro.tpl_definition
847 var location = intro.location
848 def.location = tpl_showsource(location)
849 article.content = def
850 section.add_child article
851 return section
852 end
853
854 private fun tpl_concerns(section: TplSection) do
855 var mmodules = collect_mmodules(mprops2mdefs.keys)
856 var owner_map = sort_by_public_owner(mmodules)
857 var owners = owner_map.keys.to_a
858
859 if not owners.is_empty then
860 var article = new TplArticle.with_title("concerns", "Concerns")
861 var module_sorter = new MModuleNameSorter
862 module_sorter.sort owners
863 var list = new TplList.with_classes(["list-unstyled", "list-definition"])
864 for owner in owners do
865 var li = new Template
866 li.add owner.tpl_anchor
867 if owner.mdoc != null then
868 li.add ": "
869 li.add owner.mdoc.short_comment
870 end
871 var smmodules = owner_map[owner].to_a
872 #if not smmodules.length >= 1 then
873 var slist = new TplList.with_classes(["list-unstyled", "list-definition"])
874 module_sorter.sort smmodules
875 for mmodule in smmodules do
876 if mmodule == owner then continue
877 var sli = new Template
878 sli.add mmodule.tpl_anchor
879 if mmodule.mdoc != null then
880 sli.add ": "
881 sli.add mmodule.mdoc.short_comment
882 end
883 slist.add_li(sli)
884 end
885 li.add slist
886 list.add_li li
887 #end
888 end
889 article.content = list
890 section.add_child article
891 end
892 end
893
894 private fun tpl_inheritance(parent: TplSection) do
895 # parents
896 var hparents = new HashSet[MClass]
897 for c in mclass.in_hierarchy(ctx.mainmodule).direct_greaters do
898 if c.visibility < ctx.min_visibility then continue
899 hparents.add c
900 end
901
902 # ancestors
903 var hancestors = new HashSet[MClass]
904 for c in mclass.in_hierarchy(ctx.mainmodule).greaters do
905 if c == mclass then continue
906 if c.visibility < ctx.min_visibility then continue
907 if hparents.has(c) then continue
908 hancestors.add c
909 end
910
911 # children
912 var hchildren = new HashSet[MClass]
913 for c in mclass.in_hierarchy(ctx.mainmodule).direct_smallers do
914 if c.visibility < ctx.min_visibility then continue
915 hchildren.add c
916 end
917
918 # descendants
919 var hdescendants = new HashSet[MClass]
920 for c in mclass.in_hierarchy(ctx.mainmodule).smallers do
921 if c == mclass then continue
922 if c.visibility < ctx.min_visibility then continue
923 if hchildren.has(c) then continue
924 hdescendants.add c
925 end
926
927 # Display lists
928 var sorter = new MClassNameSorter
929 var section = new TplSection.with_title("inheritance", "Inheritance")
930
931 # Graph
932 var mclasses = new HashSet[MClass]
933 mclasses.add_all hancestors
934 mclasses.add_all hparents
935 if hchildren.length < 10 then mclasses.add_all hchildren
936 if hdescendants.length < 10 then mclasses.add_all hdescendants
937 mclasses.add mclass
938 var graph = tpl_dot(mclasses)
939 if graph != null then section.add_child graph
940
941 # parents
942 if not hparents.is_empty then
943 var lst = hparents.to_a
944 sorter.sort lst
945 section.add_child tpl_list("parents", "Parents", lst)
946 end
947
948 # ancestors
949 if not hancestors.is_empty then
950 var lst = hancestors.to_a
951 sorter.sort lst
952 section.add_child tpl_list("ancestors", "Ancestors", lst)
953 end
954
955 # children
956 if not hchildren.is_empty and hchildren.length < 15 then
957 var lst = hchildren.to_a
958 sorter.sort lst
959 section.add_child tpl_list("children", "Children", lst)
960 end
961
962 # descendants
963 if not hdescendants.is_empty and hchildren.length < 15 then
964 var lst = hdescendants.to_a
965 sorter.sort lst
966 section.add_child tpl_list("descendants", "Descendants", lst)
967 end
968
969 parent.add_child section
970 end
971
972 private fun tpl_list(id: String, title: String, elts: Array[MClass]): TplArticle do
973 var article = new TplArticle.with_title(id, title)
974 var list = new TplList.with_classes(["list-unstyled", "list-definition"])
975 for elt in elts do list.elts.add elt.tpl_list_item
976 article.content = list
977 return article
978 end
979
980 private fun tpl_properties(parent: TplSection) do
981 var mod_map = sort_by_mmodule(mprops2mdefs.keys)
982 var owner_map = sort_by_public_owner(mod_map.keys)
983 var owners = owner_map.keys.to_a
984
985 for owner in owners do
986 var section = new TplSection(owner.nitdoc_anchor)
987 var title = new Template
988 title.add "Introductions in "
989 title.add owner.tpl_link
990 section.title = title
991 section.summary_title = "In {owner.nitdoc_name}"
992 for mmodule in owner_map[owner] do
993 # properties
994 var mprops = mod_map[mmodule]
995 var sorter = new MPropertyNameSorter
996 var kind_map = sort_by_kind(mprops)
997
998 # virtual types
999 var elts = kind_map["type"].to_a
1000 sorter.sort(elts)
1001 for elt in elts do
1002 var defs = mprops2mdefs[elt].to_a
1003 section.add_child tpl_mprop_article(elt, defs)
1004 end
1005
1006 # constructors
1007 elts = kind_map["init"].to_a
1008 sorter.sort(elts)
1009 for elt in elts do
1010 var defs = mprops2mdefs[elt].to_a
1011 section.add_child tpl_mprop_article(elt, defs)
1012 end
1013
1014 # methods
1015 elts = kind_map["fun"].to_a
1016 sorter.sort(elts)
1017 for elt in elts do
1018 var defs = mprops2mdefs[elt].to_a
1019 section.add_child tpl_mprop_article(elt, defs)
1020 end
1021 end
1022 parent.add_child section
1023 end
1024 end
1025
1026 redef fun tpl_content do
1027 tpl_sidebar_properties
1028 var top = tpl_intro
1029 tpl_concerns(top)
1030 tpl_inheritance(top)
1031 tpl_properties(top)
1032 tpl_page.add_section top
1033 end
1034
1035 private fun sort_by_mproperty(mpropdefs: Collection[MPropDef]): Map[MProperty, Set[MPropDef]] do
1036 var map = new HashMap[MProperty, Set[MPropDef]]
1037 for mpropdef in mpropdefs do
1038 var mproperty = mpropdef.mproperty
1039 if not map.has_key(mproperty) then map[mproperty] = new HashSet[MPropDef]
1040 map[mproperty].add mpropdef
1041 end
1042 return map
1043 end
1044
1045 private fun sort_by_mmodule(mprops: Collection[MProperty]): Map[MModule, Set[MProperty]] do
1046 var map = new HashMap[MModule, Set[MProperty]]
1047 for mprop in mprops do
1048 var mpropdefs = mprops2mdefs[mprop].to_a
1049 ctx.mainmodule.linearize_mpropdefs(mpropdefs)
1050 var mmodule = mpropdefs.first.mclassdef.mmodule
1051 if not map.has_key(mmodule) then map[mmodule] = new HashSet[MProperty]
1052 map[mmodule].add mprop
1053 end
1054 return map
1055 end
1056
1057 private fun sort_by_kind(mprops: Collection[MProperty]): Map[String, Set[MProperty]] do
1058 var map = new HashMap[String, Set[MProperty]]
1059 map["type"] = new HashSet[MProperty]
1060 map["init"] = new HashSet[MProperty]
1061 map["fun"] = new HashSet[MProperty]
1062 for mprop in mprops do
1063 if mprop isa MVirtualTypeProp then
1064 map["type"].add mprop
1065 else if mprop isa MMethod then
1066 if mprop.is_init then
1067 map["init"].add mprop
1068 else
1069 map["fun"].add mprop
1070 end
1071 end
1072 end
1073 return map
1074 end
1075
1076 private fun mclass_inherited_mprops: Set[MProperty] do
1077 var res = new HashSet[MProperty]
1078 var local = mclass.local_mproperties(ctx.min_visibility)
1079 for mprop in mclass.inherited_mproperties(ctx.mainmodule, ctx.min_visibility) do
1080 if local.has(mprop) then continue
1081 #if mprop isa MMethod and mprop.is_init then continue
1082 if mprop.intro.mclassdef.mclass.name == "Object" and
1083 (mprop.visibility == protected_visibility or
1084 mprop.intro.mclassdef.mmodule.name != "kernel") then continue
1085 res.add mprop
1086 end
1087 res.add_all local
1088 return res
1089 end
1090
1091 private fun collect_mmodules(mprops: Collection[MProperty]): Set[MModule] do
1092 var res = new HashSet[MModule]
1093 for mprop in mprops do
1094 if mprops2mdefs.has_key(mprop) then
1095 for mpropdef in mprops2mdefs[mprop] do res.add mpropdef.mclassdef.mmodule
1096 end
1097 end
1098 return res
1099 end
1100
1101 private fun sort_by_public_owner(mmodules: Collection[MModule]): Map[MModule, Set[MModule]] do
1102 var map = new HashMap[MModule, Set[MModule]]
1103 for mmodule in mmodules do
1104 var owner = mmodule
1105 if mmodule.public_owner != null then owner = mmodule.public_owner.as(not null)
1106 if not map.has_key(owner) then map[owner] = new HashSet[MModule]
1107 map[owner].add mmodule
1108 end
1109 return map
1110 end
1111
1112 # Generate dot hierarchy for classes
1113 fun tpl_dot(mclasses: Collection[MClass]): nullable TplArticle do
1114 var poset = new POSet[MClass]
1115
1116 for mclass in mclasses do
1117 poset.add_node mclass
1118 for oclass in mclasses do
1119 poset.add_node oclass
1120 if mclass.in_hierarchy(ctx.mainmodule) < oclass then
1121 poset.add_edge(mclass, oclass)
1122 end
1123 end
1124 end
1125
1126 var op = new FlatBuffer
1127 var name = "dep_{mclass.name}"
1128 op.append("digraph {name} \{ rankdir=BT; node[shape=none,margin=0,width=0,height=0,fontsize=10]; edge[dir=none,color=gray]; ranksep=0.2; nodesep=0.1;\n")
1129 for c in poset do
1130 if c == mclass then
1131 op.append("\"{c.name}\"[shape=box,margin=0.03];\n")
1132 else
1133 op.append("\"{c.name}\"[URL=\"{c.nitdoc_url}\"];\n")
1134 end
1135 for c2 in poset[c].direct_greaters do
1136 op.append("\"{c.name}\"->\"{c2.name}\";\n")
1137 end
1138 end
1139 op.append("\}\n")
1140 return tpl_graph(op, name, "Inheritance graph")
1141 end
1142 end
1143