model_utils: replace many sorters by MEntityNameSorter
[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 private var name_sorter = new MEntityNameSorter
234
235 init(ctx: NitdocContext) do
236 self.ctx = ctx
237 self.model = ctx.mbuilder.model
238 end
239
240 # Render the page as a html template
241 fun render: Template do
242 var shareurl = "."
243 if ctx.opt_shareurl.value != null then
244 shareurl = ctx.opt_shareurl.value.as(not null)
245 end
246
247 # build page
248 var tpl = tpl_page
249 tpl.title = tpl_title
250 tpl.shareurl = shareurl
251 tpl.topmenu = tpl_topmenu
252 tpl_content
253 tpl.footer = ctx.opt_custom_footer.value
254 tpl.body_attrs.add(new TagAttribute("data-bootstrap-share", shareurl))
255 tpl.sidebar = tpl_sidebar
256
257 # piwik tracking
258 var tracker_url = ctx.opt_piwik_tracker.value
259 var site_id = ctx.opt_piwik_site_id.value
260 if tracker_url != null and site_id != null then
261 tpl.scripts.add new TplPiwikScript(tracker_url, site_id)
262 end
263 return tpl
264 end
265
266 # Build page template
267 fun tpl_page: TplPage is abstract
268
269 # Build page sidebar if any
270 fun tpl_sidebar: nullable TplSidebar do return null
271
272 # Build page title string
273 fun tpl_title: String do
274 if ctx.opt_custom_title.value != null then
275 return ctx.opt_custom_title.value.to_s
276 end
277 return "Nitdoc"
278 end
279
280 # Build top menu template
281 fun tpl_topmenu: TplTopMenu do
282 var topmenu = new TplTopMenu
283 var custom_elt = ctx.opt_custom_menu.value
284 if custom_elt != null then topmenu.add_raw(custom_elt)
285 return topmenu
286 end
287
288 # Build page content template
289 fun tpl_content is abstract
290
291 # Clickable graphviz image using dot format
292 # return null if no graph for this page
293 fun tpl_graph(dot: FlatBuffer, name: String, title: String): nullable TplArticle do
294 if ctx.opt_nodot.value then return null
295 var output_dir = ctx.output_dir
296 var file = new OFStream.open("{output_dir}/{name}.dot")
297 file.write(dot)
298 file.close
299 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 ; \}")
300 var fmap = new IFStream.open("{output_dir}/{name}.map")
301 var map = fmap.read_all
302 fmap.close
303
304 var article = new TplArticle.with_title("graph", title)
305 var content = new Template
306 content.add "<img src='{name}.png' usemap='#{name}' style='margin:auto' alt='{title}'/>"
307 content.add map
308 article.content = content
309 return article
310 end
311
312 # A (source) link template for a given location
313 fun tpl_showsource(location: nullable Location): nullable String
314 do
315 if location == null then return null
316 var source = ctx.opt_source.value
317 if source == null then return "({location.file.filename.simplify_path})"
318 # THIS IS JUST UGLY ! (but there is no replace yet)
319 var x = source.split_with("%f")
320 source = x.join(location.file.filename.simplify_path)
321 x = source.split_with("%l")
322 source = x.join(location.line_start.to_s)
323 x = source.split_with("%L")
324 source = x.join(location.line_end.to_s)
325 source = source.simplify_path
326 return " (<a target='_blank' title='Show source' href=\"{source.to_s}\">source</a>)"
327 end
328
329 # MClassDef description template
330 fun tpl_mclass_article(mclass: MClass, mclassdefs: Array[MClassDef]): TplArticle do
331 var article = new TplArticle(mclass.nitdoc_anchor)
332 var title = new Template
333 var icon = new TplIcon.with_icon("tag")
334 icon.css_classes.add_all(mclass.intro.tpl_css_classes)
335 title.add icon
336 title.add mclass.tpl_link
337 title.add mclass.intro.tpl_signature
338 article.title = title
339 article.title_classes.add "signature"
340 article.subtitle = mclass.tpl_declaration
341 article.summary_title = "{mclass.nitdoc_name}{mclass.tpl_signature.write_to_string}"
342 #article.subtitle = new Template
343 #article.subtitle.add mprop.intro.tpl_modifiers
344 #article.subtitle.add mprop.intro.tpl_namespace
345 var content = new Template
346
347 if not mclassdefs.has(mclass.intro) then
348 # add intro synopsys
349 var intro = mclass.intro
350 var location = intro.location
351 var sourcelink = tpl_showsource(location)
352 var intro_def = intro.tpl_definition
353 intro_def.location = sourcelink
354 content.add intro_def
355 end
356 ctx.mainmodule.linearize_mclassdefs(mclassdefs)
357 for mclassdef in mclassdefs do
358 # add mclassdef full description
359 var location = mclassdef.location
360 var sourcelink = tpl_showsource(location)
361 var prop_def = mclassdef.tpl_definition.as(TplClassDefinition)
362 prop_def.location = sourcelink
363 for mpropdef in mclassdef.mpropdefs do
364 var intro = mpropdef.mproperty.intro
365 if mpropdef isa MAttributeDef then continue
366 if mpropdef.mproperty.visibility < ctx.min_visibility then continue
367
368 var lnk = new Template
369 lnk.add new TplLabel.with_classes(mpropdef.tpl_css_classes.to_a)
370 lnk.add mpropdef.tpl_link
371 if intro.mdoc != null then
372 lnk.add ": "
373 lnk.add intro.mdoc.short_comment
374 end
375 if mpropdef.is_intro then
376 prop_def.intros.add new TplListItem.with_content(lnk)
377 else
378 prop_def.redefs.add new TplListItem.with_content(lnk)
379 end
380 end
381 content.add prop_def
382 end
383 article.content = content
384 return article
385 end
386
387 # MProp description template
388 fun tpl_mprop_article(mprop: MProperty, mpropdefs: Array[MPropDef]): TplArticle do
389 var article = new TplArticle(mprop.intro.nitdoc_anchor)
390 var icon = new TplIcon.with_icon("tag")
391 icon.css_classes.add_all(mprop.intro.tpl_css_classes)
392 var title = new Template
393 title.add icon
394 title.add mprop.nitdoc_name
395 title.add mprop.intro.tpl_signature
396 article.title = title
397 article.title_classes.add "signature"
398 article.subtitle = mprop.tpl_declaration
399 article.summary_title = mprop.nitdoc_name
400 #article.subtitle = new Template
401 #article.subtitle.add mprop.intro.tpl_modifiers
402 #article.subtitle.add mprop.intro.tpl_namespace
403 var content = new Template
404
405 if not mpropdefs.has(mprop.intro) then
406 # add intro synopsys
407 var intro = mprop.intro
408 var location = intro.location
409 var sourcelink = tpl_showsource(location)
410 var intro_def = intro.tpl_definition
411 intro_def.location = sourcelink
412 content.add intro_def
413 end
414
415 ctx.mainmodule.linearize_mpropdefs(mpropdefs)
416 for mpropdef in mpropdefs do
417 # add mpropdef description
418 var location = mpropdef.location
419 var sourcelink = tpl_showsource(location)
420 var prop_def = mpropdef.tpl_definition
421 prop_def.location = sourcelink
422 content.add prop_def
423 end
424 article.content = content
425 return article
426 end
427 end
428
429 # The overview page
430 # Display a list of modules contained in program
431 class NitdocOverview
432 super NitdocPage
433
434 init(ctx: NitdocContext) do super(ctx)
435
436 private var page = new TplPage
437 redef fun tpl_page do return page
438
439 private var sidebar = new TplSidebar
440 redef fun tpl_sidebar do return sidebar
441
442 redef fun tpl_title do
443 if ctx.opt_custom_title.value != null then
444 return ctx.opt_custom_title.value.to_s
445 else
446 return "Overview"
447 end
448 end
449
450 redef fun tpl_topmenu do
451 var topmenu = super
452 topmenu.add_link("#", "Overview", true)
453 topmenu.add_link("search.html", "Index", false)
454 return topmenu
455 end
456
457 # intro text
458 private fun tpl_intro: TplSection do
459 var section = new TplSection.with_title("overview", tpl_title)
460 var article = new TplArticle("intro")
461 if ctx.opt_custom_intro.value != null then
462 article.content = ctx.opt_custom_intro.value.to_s
463 end
464 section.add_child article
465 return section
466 end
467
468 # projects list
469 private fun tpl_projects(section: TplSection) do
470 # Projects list
471 var ssection = new TplSection.with_title("projects", "Projects")
472 for mproject in model.mprojects do
473 ssection.add_child mproject.tpl_article
474 end
475 section.add_child ssection
476 end
477
478 redef fun tpl_content do
479 var top = tpl_intro
480 tpl_projects(top)
481 tpl_page.add_section top
482 end
483 end
484
485 # The search page
486 # Display a list of modules, classes and properties
487 class NitdocSearch
488 super NitdocPage
489
490 init(ctx: NitdocContext) do super(ctx)
491
492 private var page = new TplPage
493 redef fun tpl_page do return page
494
495 redef fun tpl_title do return "Index"
496
497 redef fun tpl_topmenu do
498 var topmenu = super
499 topmenu.add_link("index.html", "Overview", false)
500 topmenu.add_link("#", "Index", true)
501 return topmenu
502 end
503
504 redef fun tpl_content do
505 var tpl = new TplSearchPage("search_all")
506 var section = new TplSection("search")
507 # title
508 tpl.title = "Index"
509 # modules list
510 for mmodule in modules_list do
511 tpl.modules.add mmodule.tpl_link
512 end
513 # classes list
514 for mclass in classes_list do
515 tpl.classes.add mclass.tpl_link
516 end
517 # properties list
518 for mproperty in mprops_list do
519 var m = new Template
520 m.add mproperty.intro.tpl_link
521 m.add " ("
522 m.add mproperty.intro.mclassdef.mclass.tpl_link
523 m.add ")"
524 tpl.props.add m
525 end
526 section.add_child tpl
527 tpl_page.add_section section
528 end
529
530 # Extract mmodule list to display (sorted by name)
531 private fun modules_list: Array[MModule] do
532 var sorted = new Array[MModule]
533 for mmodule in ctx.mbuilder.model.mmodule_importation_hierarchy do
534 if mmodule.name == "<main>" then continue
535 sorted.add mmodule
536 end
537 name_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 name_sorter.sort(sorted)
549 return sorted
550 end
551
552 # Extract mproperty list to display (sorted by name)
553 private fun mprops_list: Array[MProperty] do
554 var sorted = new Array[MProperty]
555 for mproperty in ctx.mbuilder.model.mproperties do
556 if mproperty.visibility < ctx.min_visibility then continue
557 if mproperty isa MAttribute then continue
558 sorted.add mproperty
559 end
560 name_sorter.sort(sorted)
561 return sorted
562 end
563 end
564
565 # A module page
566 # Display the list of introduced and redefined classes in module
567 class NitdocModule
568 super NitdocPage
569
570 private var mmodule: MModule
571
572 init(mmodule: MModule, ctx: NitdocContext) do
573 self.mmodule = mmodule
574 super(ctx)
575 end
576
577 private var page = new TplPage
578 redef fun tpl_page do return page
579
580 private var sidebar = new TplSidebar
581 redef fun tpl_sidebar do return sidebar
582
583 redef fun tpl_title do return "{mmodule.nitdoc_name}"
584
585 redef fun tpl_topmenu do
586 var topmenu = super
587 topmenu.add_link("index.html", "Overview", false)
588 topmenu.add_link("#", "{mmodule.nitdoc_name}", true)
589 topmenu.add_link("search.html", "Index", false)
590 return topmenu
591 end
592
593 # intro text
594 private fun tpl_intro: TplSection do
595 var section = new TplSection.with_title(mmodule.nitdoc_anchor, tpl_title)
596 section.subtitle = mmodule.tpl_declaration
597
598 var article = new TplArticle("intro")
599 var def = mmodule.tpl_definition
600 var location = mmodule.location
601 def.location = tpl_showsource(location)
602 article.content = def
603 section.add_child article
604 return section
605 end
606
607 # inheritance section
608 private fun tpl_inheritance(parent: TplSection) do
609 # Extract relevent modules
610 var nested = mmodule.in_nesting.direct_greaters.to_a
611 var imports = mmodule.in_importation.greaters
612 if imports.length > 10 then imports = mmodule.in_importation.direct_greaters
613 var clients = mmodule.in_importation.smallers
614 if clients.length > 10 then clients = mmodule.in_importation.direct_smallers
615
616 # Display lists
617 var section = new TplSection.with_title("inheritance", "Inheritance")
618
619 # Graph
620 var mmodules = new HashSet[MModule]
621 mmodules.add_all nested
622 mmodules.add_all imports
623 if clients.length < 10 then mmodules.add_all clients
624 mmodules.add mmodule
625 var graph = tpl_dot(mmodules)
626 if graph != null then section.add_child graph
627
628 # nested modules
629 if not nested.is_empty then
630 var lst = nested.to_a
631 name_sorter.sort lst
632 section.add_child tpl_list("nesting", "Nested modules", lst)
633 end
634
635 # Imports
636 var lst = new Array[MModule]
637 for dep in imports do
638 if dep.is_fictive then continue
639 if dep == mmodule then continue
640 lst.add(dep)
641 end
642 if not lst.is_empty then
643 name_sorter.sort lst
644 section.add_child tpl_list("imports", "Imports", lst)
645 end
646
647 # Clients
648 lst = new Array[MModule]
649 for dep in clients do
650 if dep.is_fictive then continue
651 if dep == mmodule then continue
652 lst.add(dep)
653 end
654 if not lst.is_empty then
655 name_sorter.sort lst
656 section.add_child tpl_list("clients", "Clients", lst)
657 end
658
659 parent.add_child section
660 end
661
662 private fun tpl_list(id: String, title: String, mmodules: Array[MModule]): TplArticle do
663 var article = new TplArticle.with_title(id, title)
664 var list = new TplList.with_classes(["list-unstyled", "list-definition"])
665 for mmodule in mmodules do list.elts.add mmodule.tpl_list_item
666 article.content = list
667 return article
668 end
669
670 private fun tpl_mclasses(parent: TplSection) do
671 var mclassdefs = new HashSet[MClassDef]
672 mclassdefs.add_all mmodule.in_nesting_intro_mclassdefs(ctx.min_visibility)
673 mclassdefs.add_all mmodule.in_nesting_redef_mclassdefs(ctx.min_visibility)
674 var mclasses2mdefs = sort_by_mclass(mclassdefs)
675 var sorted_mclasses = mclasses2mdefs.keys.to_a
676 name_sorter.sort sorted_mclasses
677
678 # intros
679 var section = new TplSection.with_title("intros", "Introductions")
680 var intros = mmodule.in_nesting_intro_mclasses(ctx.min_visibility)
681 var sorted_intros = intros.to_a
682 name_sorter.sort(sorted_intros)
683 for mclass in sorted_intros do
684 if not mclasses2mdefs.has_key(mclass) then continue
685 section.add_child tpl_mclass_article(mclass, mclasses2mdefs[mclass].to_a)
686 end
687 parent.add_child section
688
689 # redefs
690 section = new TplSection.with_title("redefs", "Refinements")
691 var redefs = mmodule.in_nesting_redef_mclasses(ctx.min_visibility).to_a
692 name_sorter.sort(redefs)
693 for mclass in redefs do
694 if intros.has(mclass) then continue
695 if not mclasses2mdefs.has_key(mclass) then continue
696 section.add_child tpl_mclass_article(mclass, mclasses2mdefs[mclass].to_a)
697 end
698 parent.add_child section
699 end
700
701 redef fun tpl_content do
702 var top = tpl_intro
703 tpl_inheritance(top)
704 tpl_mclasses(top)
705 tpl_page.add_section top
706 end
707
708 # Genrate dot hierarchy for class inheritance
709 fun tpl_dot(mmodules: Collection[MModule]): nullable TplArticle do
710 var poset = new POSet[MModule]
711 for mmodule in mmodules do
712 if mmodule.is_fictive then continue
713 poset.add_node mmodule
714 for omodule in mmodules do
715 if mmodule.is_fictive then continue
716 poset.add_node mmodule
717 if mmodule.in_importation < omodule then
718 poset.add_edge(mmodule, omodule)
719 end
720 end
721 end
722 # build graph
723 var op = new FlatBuffer
724 var name = "dep_{mmodule.name}"
725 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")
726 for mmodule in poset do
727 if mmodule == self.mmodule then
728 op.append("\"{mmodule.name}\"[shape=box,margin=0.03];\n")
729 else
730 op.append("\"{mmodule.name}\"[URL=\"{mmodule.nitdoc_url}\"];\n")
731 end
732 for omodule in poset[mmodule].direct_greaters do
733 op.append("\"{mmodule.name}\"->\"{omodule.name}\";\n")
734 end
735 end
736 op.append("\}\n")
737 return tpl_graph(op, name, "Dependency graph")
738 end
739
740 private fun sort_by_mclass(mclassdefs: Collection[MClassDef]): Map[MClass, Set[MClassDef]] do
741 var map = new HashMap[MClass, Set[MClassDef]]
742 for mclassdef in mclassdefs do
743 var mclass = mclassdef.mclass
744 if not map.has_key(mclass) then map[mclass] = new HashSet[MClassDef]
745 map[mclass].add mclassdef
746 end
747 return map
748 end
749 end
750
751 # A class page
752 # Display a list properties defined or redefined for this class
753 class NitdocClass
754 super NitdocPage
755
756 private var mclass: MClass
757 private var mprops2mdefs: Map[MProperty, Set[MPropDef]]
758
759 init(mclass: MClass, ctx: NitdocContext) do
760 self.mclass = mclass
761 super(ctx)
762 var mpropdefs = new HashSet[MPropDef]
763 mpropdefs.add_all mclass.intro_mpropdefs(ctx.min_visibility)
764 mpropdefs.add_all mclass.redef_mpropdefs(ctx.min_visibility)
765 mprops2mdefs = sort_by_mproperty(mpropdefs)
766 end
767
768 private var page = new TplPage
769 redef fun tpl_page do return page
770
771 private var sidebar = new TplSidebar
772 redef fun tpl_sidebar do return sidebar
773
774 redef fun tpl_title do return "{mclass.nitdoc_name}{mclass.tpl_signature.write_to_string}"
775
776 redef fun tpl_topmenu do
777 var topmenu = super
778 var mmodule: MModule
779 if mclass.public_owner == null then
780 mmodule = mclass.intro_mmodule
781 else
782 mmodule = mclass.public_owner.as(not null)
783 end
784 topmenu.add_link("index.html", "Overview", false)
785 topmenu.add_link("{mmodule.nitdoc_url}", "{mmodule.nitdoc_name}", false)
786 topmenu.add_link("#", "{mclass.nitdoc_name}", true)
787 topmenu.add_link("search.html", "Index", false)
788 return topmenu
789 end
790
791 # Property list to display in sidebar
792 fun tpl_sidebar_properties do
793 var kind_map = sort_by_kind(mclass_inherited_mprops)
794 var summary = new TplList.with_classes(["list-unstyled"])
795
796 tpl_sidebar_list("Virtual types", kind_map["type"].to_a, summary)
797 tpl_sidebar_list("Constructors", kind_map["init"].to_a, summary)
798 tpl_sidebar_list("Methods", kind_map["fun"].to_a, summary)
799 tpl_sidebar.boxes.add new TplSideBox.with_content("All properties", summary)
800 end
801
802 private fun tpl_sidebar_list(name: String, mprops: Array[MProperty], summary: TplList) do
803 if mprops.is_empty then return
804 name_sorter.sort(mprops)
805 var entry = new TplListItem.with_content(name)
806 var list = new TplList.with_classes(["list-unstyled", "list-labeled"])
807 for mprop in mprops do
808 list.add_li tpl_sidebar_item(mprop)
809 end
810 entry.append list
811 summary.elts.add entry
812 end
813
814 private fun tpl_sidebar_item(mprop: MProperty): Template do
815 var classes = mprop.intro.tpl_css_classes.to_a
816 if not mprops2mdefs.has_key(mprop) then
817 classes.add "inherit"
818 var lnk = new Template
819 lnk.add new TplLabel.with_classes(classes)
820 lnk.add mprop.intro.tpl_link
821 return lnk
822 end
823 var defs = mprops2mdefs[mprop]
824 if defs.has(mprop.intro) then
825 classes.add "intro"
826 else
827 classes.add "redef"
828 end
829 var lnk = new Template
830 lnk.add new TplLabel.with_classes(classes)
831 lnk.add mprop.intro.tpl_anchor
832 return lnk
833 end
834
835 private fun tpl_intro: TplSection do
836 var section = new TplSection.with_title(mclass.nitdoc_anchor, tpl_title)
837 section.subtitle = mclass.tpl_declaration
838 var article = new TplArticle("intro")
839 var intro = mclass.intro
840 var def = intro.tpl_definition
841 var location = intro.location
842 def.location = tpl_showsource(location)
843 article.content = def
844 section.add_child article
845 return section
846 end
847
848 private fun tpl_concerns(section: TplSection) do
849 var mmodules = collect_mmodules(mprops2mdefs.keys)
850 var owner_map = sort_by_public_owner(mmodules)
851 var owners = owner_map.keys.to_a
852
853 if not owners.is_empty then
854 var article = new TplArticle.with_title("concerns", "Concerns")
855 name_sorter.sort owners
856 var list = new TplList.with_classes(["list-unstyled", "list-definition"])
857 for owner in owners do
858 var li = new Template
859 li.add owner.tpl_anchor
860 if owner.mdoc != null then
861 li.add ": "
862 li.add owner.mdoc.short_comment
863 end
864 var smmodules = owner_map[owner].to_a
865 #if not smmodules.length >= 1 then
866 var slist = new TplList.with_classes(["list-unstyled", "list-definition"])
867 name_sorter.sort smmodules
868 for mmodule in smmodules do
869 if mmodule == owner then continue
870 var sli = new Template
871 sli.add mmodule.tpl_anchor
872 if mmodule.mdoc != null then
873 sli.add ": "
874 sli.add mmodule.mdoc.short_comment
875 end
876 slist.add_li(sli)
877 end
878 li.add slist
879 list.add_li li
880 #end
881 end
882 article.content = list
883 section.add_child article
884 end
885 end
886
887 private fun tpl_inheritance(parent: TplSection) do
888 # parents
889 var hparents = new HashSet[MClass]
890 for c in mclass.in_hierarchy(ctx.mainmodule).direct_greaters do
891 if c.visibility < ctx.min_visibility then continue
892 hparents.add c
893 end
894
895 # ancestors
896 var hancestors = new HashSet[MClass]
897 for c in mclass.in_hierarchy(ctx.mainmodule).greaters do
898 if c == mclass then continue
899 if c.visibility < ctx.min_visibility then continue
900 if hparents.has(c) then continue
901 hancestors.add c
902 end
903
904 # children
905 var hchildren = new HashSet[MClass]
906 for c in mclass.in_hierarchy(ctx.mainmodule).direct_smallers do
907 if c.visibility < ctx.min_visibility then continue
908 hchildren.add c
909 end
910
911 # descendants
912 var hdescendants = new HashSet[MClass]
913 for c in mclass.in_hierarchy(ctx.mainmodule).smallers do
914 if c == mclass then continue
915 if c.visibility < ctx.min_visibility then continue
916 if hchildren.has(c) then continue
917 hdescendants.add c
918 end
919
920 # Display lists
921 var section = new TplSection.with_title("inheritance", "Inheritance")
922
923 # Graph
924 var mclasses = new HashSet[MClass]
925 mclasses.add_all hancestors
926 mclasses.add_all hparents
927 if hchildren.length < 10 then mclasses.add_all hchildren
928 if hdescendants.length < 10 then mclasses.add_all hdescendants
929 mclasses.add mclass
930 var graph = tpl_dot(mclasses)
931 if graph != null then section.add_child graph
932
933 # parents
934 if not hparents.is_empty then
935 var lst = hparents.to_a
936 name_sorter.sort lst
937 section.add_child tpl_list("parents", "Parents", lst)
938 end
939
940 # ancestors
941 if not hancestors.is_empty then
942 var lst = hancestors.to_a
943 name_sorter.sort lst
944 section.add_child tpl_list("ancestors", "Ancestors", lst)
945 end
946
947 # children
948 if not hchildren.is_empty and hchildren.length < 15 then
949 var lst = hchildren.to_a
950 name_sorter.sort lst
951 section.add_child tpl_list("children", "Children", lst)
952 end
953
954 # descendants
955 if not hdescendants.is_empty and hchildren.length < 15 then
956 var lst = hdescendants.to_a
957 name_sorter.sort lst
958 section.add_child tpl_list("descendants", "Descendants", lst)
959 end
960
961 parent.add_child section
962 end
963
964 private fun tpl_list(id: String, title: String, elts: Array[MClass]): TplArticle do
965 var article = new TplArticle.with_title(id, title)
966 var list = new TplList.with_classes(["list-unstyled", "list-definition"])
967 for elt in elts do list.elts.add elt.tpl_list_item
968 article.content = list
969 return article
970 end
971
972 private fun tpl_properties(parent: TplSection) do
973 var mod_map = sort_by_mmodule(mprops2mdefs.keys)
974 var owner_map = sort_by_public_owner(mod_map.keys)
975 var owners = owner_map.keys.to_a
976
977 for owner in owners do
978 var section = new TplSection(owner.nitdoc_anchor)
979 var title = new Template
980 title.add "Introductions in "
981 title.add owner.tpl_link
982 section.title = title
983 section.summary_title = "In {owner.nitdoc_name}"
984 for mmodule in owner_map[owner] do
985 # properties
986 var mprops = mod_map[mmodule]
987 var kind_map = sort_by_kind(mprops)
988
989 # virtual types
990 var elts = kind_map["type"].to_a
991 name_sorter.sort(elts)
992 for elt in elts do
993 var defs = mprops2mdefs[elt].to_a
994 section.add_child tpl_mprop_article(elt, defs)
995 end
996
997 # constructors
998 elts = kind_map["init"].to_a
999 name_sorter.sort(elts)
1000 for elt in elts do
1001 var defs = mprops2mdefs[elt].to_a
1002 section.add_child tpl_mprop_article(elt, defs)
1003 end
1004
1005 # methods
1006 elts = kind_map["fun"].to_a
1007 name_sorter.sort(elts)
1008 for elt in elts do
1009 var defs = mprops2mdefs[elt].to_a
1010 section.add_child tpl_mprop_article(elt, defs)
1011 end
1012 end
1013 parent.add_child section
1014 end
1015 end
1016
1017 redef fun tpl_content do
1018 tpl_sidebar_properties
1019 var top = tpl_intro
1020 tpl_concerns(top)
1021 tpl_inheritance(top)
1022 tpl_properties(top)
1023 tpl_page.add_section top
1024 end
1025
1026 private fun sort_by_mproperty(mpropdefs: Collection[MPropDef]): Map[MProperty, Set[MPropDef]] do
1027 var map = new HashMap[MProperty, Set[MPropDef]]
1028 for mpropdef in mpropdefs do
1029 var mproperty = mpropdef.mproperty
1030 if not map.has_key(mproperty) then map[mproperty] = new HashSet[MPropDef]
1031 map[mproperty].add mpropdef
1032 end
1033 return map
1034 end
1035
1036 private fun sort_by_mmodule(mprops: Collection[MProperty]): Map[MModule, Set[MProperty]] do
1037 var map = new HashMap[MModule, Set[MProperty]]
1038 for mprop in mprops do
1039 var mpropdefs = mprops2mdefs[mprop].to_a
1040 ctx.mainmodule.linearize_mpropdefs(mpropdefs)
1041 var mmodule = mpropdefs.first.mclassdef.mmodule
1042 if not map.has_key(mmodule) then map[mmodule] = new HashSet[MProperty]
1043 map[mmodule].add mprop
1044 end
1045 return map
1046 end
1047
1048 private fun sort_by_kind(mprops: Collection[MProperty]): Map[String, Set[MProperty]] do
1049 var map = new HashMap[String, Set[MProperty]]
1050 map["type"] = new HashSet[MProperty]
1051 map["init"] = new HashSet[MProperty]
1052 map["fun"] = new HashSet[MProperty]
1053 for mprop in mprops do
1054 if mprop isa MVirtualTypeProp then
1055 map["type"].add mprop
1056 else if mprop isa MMethod then
1057 if mprop.is_init then
1058 map["init"].add mprop
1059 else
1060 map["fun"].add mprop
1061 end
1062 end
1063 end
1064 return map
1065 end
1066
1067 private fun mclass_inherited_mprops: Set[MProperty] do
1068 var res = new HashSet[MProperty]
1069 var local = mclass.local_mproperties(ctx.min_visibility)
1070 for mprop in mclass.inherited_mproperties(ctx.mainmodule, ctx.min_visibility) do
1071 if local.has(mprop) then continue
1072 #if mprop isa MMethod and mprop.is_init then continue
1073 if mprop.intro.mclassdef.mclass.name == "Object" and
1074 (mprop.visibility == protected_visibility or
1075 mprop.intro.mclassdef.mmodule.name != "kernel") then continue
1076 res.add mprop
1077 end
1078 res.add_all local
1079 return res
1080 end
1081
1082 private fun collect_mmodules(mprops: Collection[MProperty]): Set[MModule] do
1083 var res = new HashSet[MModule]
1084 for mprop in mprops do
1085 if mprops2mdefs.has_key(mprop) then
1086 for mpropdef in mprops2mdefs[mprop] do res.add mpropdef.mclassdef.mmodule
1087 end
1088 end
1089 return res
1090 end
1091
1092 private fun sort_by_public_owner(mmodules: Collection[MModule]): Map[MModule, Set[MModule]] do
1093 var map = new HashMap[MModule, Set[MModule]]
1094 for mmodule in mmodules do
1095 var owner = mmodule
1096 if mmodule.public_owner != null then owner = mmodule.public_owner.as(not null)
1097 if not map.has_key(owner) then map[owner] = new HashSet[MModule]
1098 map[owner].add mmodule
1099 end
1100 return map
1101 end
1102
1103 # Generate dot hierarchy for classes
1104 fun tpl_dot(mclasses: Collection[MClass]): nullable TplArticle do
1105 var poset = new POSet[MClass]
1106
1107 for mclass in mclasses do
1108 poset.add_node mclass
1109 for oclass in mclasses do
1110 poset.add_node oclass
1111 if mclass.in_hierarchy(ctx.mainmodule) < oclass then
1112 poset.add_edge(mclass, oclass)
1113 end
1114 end
1115 end
1116
1117 var op = new FlatBuffer
1118 var name = "dep_{mclass.name}"
1119 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")
1120 for c in poset do
1121 if c == mclass then
1122 op.append("\"{c.name}\"[shape=box,margin=0.03];\n")
1123 else
1124 op.append("\"{c.name}\"[URL=\"{c.nitdoc_url}\"];\n")
1125 end
1126 for c2 in poset[c].direct_greaters do
1127 op.append("\"{c.name}\"->\"{c2.name}\";\n")
1128 end
1129 end
1130 op.append("\}\n")
1131 return tpl_graph(op, name, "Inheritance graph")
1132 end
1133 end
1134