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