Merge: Do not display test suite modules in Nitdoc
[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 toolcontext
19 import doc_model
20 private import json::static
21
22 redef class ToolContext
23 private var opt_dir = new OptionString("output directory", "-d", "--dir")
24 private var opt_source = new OptionString("link for source (%f for filename, %l for first line, %L for last line)", "--source")
25 private var opt_sharedir = new OptionString("directory containing nitdoc assets", "--sharedir")
26 private var opt_shareurl = new OptionString("use shareurl instead of copy shared files", "--shareurl")
27 private var opt_no_attributes = new OptionBool("ignore the attributes",
28 "--no-attributes")
29 private var opt_nodot = new OptionBool("do not generate graphes with graphviz", "--no-dot")
30 private var opt_private = new OptionBool("also generate private API", "--private")
31
32 private var opt_custom_title = new OptionString("custom title for homepage", "--custom-title")
33 private var opt_custom_brand = new OptionString("custom link to external site", "--custom-brand")
34 private var opt_custom_intro = new OptionString("custom intro text for homepage", "--custom-overview-text")
35 private var opt_custom_footer = new OptionString("custom footer text", "--custom-footer-text")
36
37 private var opt_github_upstream = new OptionString("Git branch where edited commits will be pulled into (ex: user:repo:branch)", "--github-upstream")
38 private var opt_github_base_sha1 = new OptionString("Git sha1 of base commit used to create pull request", "--github-base-sha1")
39 private var opt_github_gitdir = new OptionString("Git working directory used to resolve path name (ex: /home/me/myproject/)", "--github-gitdir")
40
41 private var opt_piwik_tracker = new OptionString("Piwik tracker URL (ex: nitlanguage.org/piwik/)", "--piwik-tracker")
42 private var opt_piwik_site_id = new OptionString("Piwik site ID", "--piwik-site-id")
43
44 private var output_dir: String
45 private var min_visibility: MVisibility
46
47 redef init do
48 super
49
50 var opts = option_context
51 opts.add_option(opt_dir, opt_source, opt_sharedir, opt_shareurl,
52 opt_no_attributes, opt_nodot, opt_private)
53 opts.add_option(opt_custom_title, opt_custom_footer, opt_custom_intro, opt_custom_brand)
54 opts.add_option(opt_github_upstream, opt_github_base_sha1, opt_github_gitdir)
55 opts.add_option(opt_piwik_tracker, opt_piwik_site_id)
56
57 var tpl = new Template
58 tpl.add "Usage: nitdoc [OPTION]... <file.nit>...\n"
59 tpl.add "Generates HTML pages of API documentation from Nit source files."
60 tooldescription = tpl.write_to_string
61 end
62
63 redef fun process_options(args) do
64 super
65
66 # output dir
67 var output_dir = opt_dir.value
68 if output_dir == null then
69 output_dir = "doc"
70 end
71 self.output_dir = output_dir
72 # min visibility
73 if opt_private.value then
74 min_visibility = none_visibility
75 else
76 min_visibility = protected_visibility
77 end
78 # github urls
79 var gh_upstream = opt_github_upstream.value
80 var gh_base_sha = opt_github_base_sha1.value
81 var gh_gitdir = opt_github_gitdir.value
82 if not gh_upstream == null or not gh_base_sha == null or not gh_gitdir == null then
83 if gh_upstream == null or gh_base_sha == null or gh_gitdir == null then
84 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"
85 abort
86 end
87 end
88 end
89
90 # Filter the entity based on the options specified by the user.
91 #
92 # Return `true` if the specified entity has to be included in the generated
93 # documentation
94 private fun filter_mclass(mclass: MClass): Bool do
95 return mclass.visibility >= min_visibility
96 end
97
98 # Filter the entity based on the options specified by the user.
99 #
100 # Return `true` if the specified entity has to be included in the generated
101 # documentation
102 private fun filter_mproperty(mproperty: MProperty): Bool do
103 return mproperty.visibility >= min_visibility and
104 not (opt_no_attributes.value and mproperty isa MAttribute)
105 end
106 end
107
108 # The Nitdoc class explores the model and generate pages for each mentities found
109 class Nitdoc
110 var ctx: ToolContext
111 var model: Model
112 var mainmodule: MModule
113
114 fun generate do
115 init_output_dir
116 overview
117 search
118 groups
119 modules
120 classes
121 properties
122 quicksearch_list
123 end
124
125 private fun init_output_dir do
126 # create destination dir if it's necessary
127 var output_dir = ctx.output_dir
128 if not output_dir.file_exists then output_dir.mkdir
129 # locate share dir
130 var sharedir = ctx.opt_sharedir.value
131 if sharedir == null then
132 var dir = ctx.nit_dir
133 sharedir = dir/"share/nitdoc"
134 if not sharedir.file_exists then
135 print "Error: Cannot locate nitdoc share files. Uses --sharedir or envvar NIT_DIR"
136 abort
137 end
138 end
139 # copy shared files
140 if ctx.opt_shareurl.value == null then
141 sys.system("cp -r -- {sharedir.to_s.escape_to_sh}/* {output_dir.to_s.escape_to_sh}/")
142 else
143 sys.system("cp -r -- {sharedir.to_s.escape_to_sh}/resources/ {output_dir.to_s.escape_to_sh}/resources/")
144 end
145
146 end
147
148 private fun overview do
149 var page = new NitdocOverview(ctx, model, mainmodule)
150 page.render.write_to_file("{ctx.output_dir.to_s}/{page.page_url}")
151 end
152
153 private fun search do
154 var page = new NitdocSearch(ctx, model, mainmodule)
155 page.render.write_to_file("{ctx.output_dir.to_s}/{page.page_url}")
156 end
157
158 private fun groups do
159 for mproject in model.mprojects do
160 for mgroup in mproject.mgroups.to_a do
161 var page = new NitdocGroup(ctx, model, mainmodule, mgroup)
162 page.render.write_to_file("{ctx.output_dir.to_s}/{page.page_url}")
163 end
164 end
165 end
166
167 private fun modules do
168 for mmodule in model.mmodules do
169 if mmodule.is_fictive or mmodule.is_test_suite then continue
170 var page = new NitdocModule(ctx, model, mainmodule, mmodule)
171 page.render.write_to_file("{ctx.output_dir.to_s}/{page.page_url}")
172 end
173 end
174
175 private fun classes do
176 for mclass in model.mclasses do
177 if not ctx.filter_mclass(mclass) then continue
178 var page = new NitdocClass(ctx, model, mainmodule, mclass)
179 page.render.write_to_file("{ctx.output_dir.to_s}/{page.page_url}")
180 end
181 end
182
183 private fun properties do
184 for mproperty in model.mproperties do
185 if not ctx.filter_mproperty(mproperty) then continue
186 if mproperty isa MInnerClass then continue
187 var page = new NitdocProperty(ctx, model, mainmodule, mproperty)
188 page.render.write_to_file("{ctx.output_dir.to_s}/{page.page_url}")
189 end
190 end
191
192 private fun quicksearch_list do
193 var quicksearch = new QuickSearch(ctx, model)
194 quicksearch.render.write_to_file("{ctx.output_dir.to_s}/quicksearch-list.js")
195 end
196 end
197
198 # Nitdoc QuickSearch list generator
199 #
200 # Create a JSON object containing links to:
201 # * modules
202 # * mclasses
203 # * mpropdefs
204 # All entities are grouped by name to make the research easier.
205 class QuickSearch
206
207 private var table = new QuickSearchTable
208
209 var ctx: ToolContext
210 var model: Model
211
212 init do
213 for mmodule in model.mmodules do
214 if mmodule.is_fictive or mmodule.is_test_suite then continue
215 add_result_for(mmodule.name, mmodule.full_name, mmodule.nitdoc_url)
216 end
217 for mclass in model.mclasses do
218 if not ctx.filter_mclass(mclass) then continue
219 add_result_for(mclass.name, mclass.full_name, mclass.nitdoc_url)
220 end
221 for mproperty in model.mproperties do
222 if not ctx.filter_mproperty(mproperty) then continue
223 for mpropdef in mproperty.mpropdefs do
224 var full_name = mpropdef.mclassdef.mclass.full_name
225 var cls_url = mpropdef.mclassdef.mclass.nitdoc_url
226 var def_url = "{cls_url}#{mpropdef.mproperty.nitdoc_id}"
227 add_result_for(mproperty.name, full_name, def_url)
228 end
229 end
230 end
231
232 private fun add_result_for(query: String, txt: String, url: String) do
233 table[query].add new QuickSearchResult(txt, url)
234 end
235
236 fun render: Template do
237 var tpl = new Template
238 var buffer = new RopeBuffer
239 tpl.add buffer
240 buffer.append "var nitdocQuickSearchRawList="
241 table.append_json buffer
242 buffer.append ";"
243 return tpl
244 end
245 end
246
247 # The result map for QuickSearch.
248 private class QuickSearchTable
249 super JsonMapRead[String, QuickSearchResultList]
250 super HashMap[String, QuickSearchResultList]
251
252 redef fun provide_default_value(key) do
253 var v = new QuickSearchResultList
254 self[key] = v
255 return v
256 end
257 end
258
259 # A QuickSearch result list.
260 private class QuickSearchResultList
261 super JsonSequenceRead[QuickSearchResult]
262 super Array[QuickSearchResult]
263 end
264
265 # A QuickSearch result.
266 private class QuickSearchResult
267 super Jsonable
268
269 # The text of the link.
270 var txt: String
271
272 # The destination of the link.
273 var url: String
274
275 redef fun to_json do
276 return "\{\"txt\":{txt.to_json},\"url\":{url.to_json}\}"
277 end
278 end
279
280 # Nitdoc base page
281 # Define page structure and properties
282 abstract class NitdocPage
283
284 private var ctx: ToolContext
285 private var model: Model
286 private var mainmodule: MModule
287 private var name_sorter = new MEntityNameSorter
288
289 # Render the page as a html template
290 fun render: Template do
291 var shareurl = "."
292 if ctx.opt_shareurl.value != null then
293 shareurl = ctx.opt_shareurl.value.as(not null)
294 end
295
296 # build page
297 var tpl = tpl_page
298 tpl.title = tpl_title
299 tpl.url = page_url
300 tpl.shareurl = shareurl
301 tpl.topmenu = tpl_topmenu
302 tpl_content
303 tpl.footer = ctx.opt_custom_footer.value
304 tpl.body_attrs.add(new TagAttribute("data-bootstrap-share", shareurl))
305 tpl.sidebar = tpl_sidebar
306
307 # piwik tracking
308 var tracker_url = ctx.opt_piwik_tracker.value
309 var site_id = ctx.opt_piwik_site_id.value
310 if tracker_url != null and site_id != null then
311 tpl.scripts.add new TplPiwikScript(tracker_url, site_id)
312 end
313 return tpl
314 end
315
316 # URL to this page.
317 fun page_url: String is abstract
318
319 # Build page template
320 fun tpl_page: TplPage is abstract
321
322 # Build page sidebar if any
323 fun tpl_sidebar: nullable TplSidebar do return null
324
325 # Build page title string
326 fun tpl_title: String do
327 if ctx.opt_custom_title.value != null then
328 return ctx.opt_custom_title.value.to_s
329 end
330 return "Nitdoc"
331 end
332
333 # Build top menu template
334 fun tpl_topmenu: TplTopMenu do
335 var topmenu = new TplTopMenu(page_url)
336 var brand = ctx.opt_custom_brand.value
337 if brand != null then
338 var tpl = new Template
339 tpl.add "<span class='navbar-brand'>"
340 tpl.add brand
341 tpl.add "</span>"
342 topmenu.brand = tpl
343 end
344 topmenu.add_link new TplLink("index.html", "Overview")
345 topmenu.add_link new TplLink("search.html", "Index")
346 return topmenu
347 end
348
349 # Build page content template
350 fun tpl_content is abstract
351
352 # Clickable graphviz image using dot format
353 # return null if no graph for this page
354 fun tpl_graph(dot: Buffer, name: String, title: nullable String): nullable TplArticle do
355 if ctx.opt_nodot.value then return null
356 var output_dir = ctx.output_dir
357 var path = output_dir / name
358 var path_sh = path.escape_to_sh
359 var file = new OFStream.open("{path}.dot")
360 file.write(dot)
361 file.close
362 sys.system("\{ test -f {path_sh}.png && test -f {path_sh}.s.dot && diff -- {path_sh}.dot {path_sh}.s.dot >/dev/null 2>&1 ; \} || \{ cp -- {path_sh}.dot {path_sh}.s.dot && dot -Tpng -o{path_sh}.png -Tcmapx -o{path_sh}.map {path_sh}.s.dot ; \}")
363 var fmap = new IFStream.open("{path}.map")
364 var map = fmap.read_all
365 fmap.close
366
367 var article = new TplArticle("graph")
368 var alt = ""
369 if title != null then
370 article.title = title
371 alt = "alt='{title.html_escape}'"
372 end
373 article.css_classes.add "text-center"
374 var content = new Template
375 var name_html = name.html_escape
376 content.add "<img src='{name_html}.png' usemap='#{name_html}' style='margin:auto' {alt}/>"
377 content.add map
378 article.content = content
379 return article
380 end
381
382 # A (source) link template for a given location
383 fun tpl_showsource(location: nullable Location): nullable String
384 do
385 if location == null then return null
386 var source = ctx.opt_source.value
387 if source == null then
388 var url = location.file.filename.simplify_path
389 return "<a target='_blank' title='Show source' href=\"{url.html_escape}\">View Source</a>"
390 end
391 # THIS IS JUST UGLY ! (but there is no replace yet)
392 var x = source.split_with("%f")
393 source = x.join(location.file.filename.simplify_path)
394 x = source.split_with("%l")
395 source = x.join(location.line_start.to_s)
396 x = source.split_with("%L")
397 source = x.join(location.line_end.to_s)
398 source = source.simplify_path
399 return "<a target='_blank' title='Show source' href=\"{source.to_s.html_escape}\">View Source</a>"
400 end
401
402 # MProject description template
403 fun tpl_mproject_article(mproject: MProject): TplArticle do
404 var article = mproject.tpl_article
405 article.subtitle = mproject.tpl_declaration
406 article.content = mproject.tpl_definition
407 var mdoc = mproject.mdoc_or_fallback
408 if mdoc != null then
409 article.content = mdoc.tpl_short_comment
410 end
411 return article
412 end
413
414 # MGroup description template
415 fun tpl_mgroup_article(mgroup: MGroup): TplArticle do
416 var article = mgroup.tpl_article
417 article.subtitle = mgroup.tpl_declaration
418 article.content = mgroup.tpl_definition
419 return article
420 end
421
422 # MModule description template
423 fun tpl_mmodule_article(mmodule: MModule): TplArticle do
424 var article = mmodule.tpl_article
425 article.subtitle = mmodule.tpl_declaration
426 article.content = mmodule.tpl_definition
427 # mclassdefs list
428 var intros = mmodule.intro_mclassdefs(ctx.min_visibility).to_a
429 if not intros.is_empty then
430 mainmodule.linearize_mclassdefs(intros)
431 var intros_art = new TplArticle.with_title("{mmodule.nitdoc_id}.intros", "Introduces")
432 var intros_lst = new TplList.with_classes(["list-unstyled", "list-labeled"])
433 for mclassdef in intros do
434 intros_lst.add_li mclassdef.tpl_list_item
435 end
436 if not intros_lst.is_empty then
437 intros_art.content = intros_lst
438 article.add_child intros_art
439 end
440 end
441 var redefs = mmodule.redef_mclassdefs(ctx.min_visibility).to_a
442 if not redefs.is_empty then
443 mainmodule.linearize_mclassdefs(redefs)
444 var redefs_art = new TplArticle.with_title("{mmodule.nitdoc_id}.redefs", "Redefines")
445 var redefs_lst = new TplList.with_classes(["list-unstyled", "list-labeled"])
446 for mclassdef in redefs do
447 redefs_lst.add_li mclassdef.tpl_list_item
448 end
449 if not redefs_lst.is_empty then
450 redefs_art.content = redefs_lst
451 article.add_child redefs_art
452 end
453 end
454 return article
455 end
456
457 # MClassDef description template
458 fun tpl_mclass_article(mclass: MClass, mclassdefs: Array[MClassDef]): TplArticle do
459 var article = mclass.tpl_article
460 if not mclassdefs.has(mclass.intro) then
461 # add intro synopsys
462 var intro_article = mclass.intro.tpl_short_article
463 intro_article.source_link = tpl_showsource(mclass.intro.location)
464 article.add_child intro_article
465 end
466 mainmodule.linearize_mclassdefs(mclassdefs)
467 for mclassdef in mclassdefs do
468 # add mclassdef full description
469 var redef_article = mclassdef.tpl_article
470 redef_article.source_link = tpl_showsource(mclassdef.location)
471 article.add_child redef_article
472 # mpropdefs list
473 var intros = new TplArticle.with_title("{mclassdef.nitdoc_id}.intros", "Introduces")
474 var intros_lst = new TplList.with_classes(["list-unstyled", "list-labeled"])
475 for mpropdef in mclassdef.collect_intro_mpropdefs(ctx.min_visibility) do
476 intros_lst.add_li mpropdef.tpl_list_item
477 end
478 if not intros_lst.is_empty then
479 intros.content = intros_lst
480 redef_article.add_child intros
481 end
482 var redefs = new TplArticle.with_title("{mclassdef.nitdoc_id}.redefs", "Redefines")
483 var redefs_lst = new TplList.with_classes(["list-unstyled", "list-labeled"])
484 for mpropdef in mclassdef.collect_redef_mpropdefs(ctx.min_visibility) do
485 redefs_lst.add_li mpropdef.tpl_list_item
486 end
487 if not redefs_lst.is_empty then
488 redefs.content = redefs_lst
489 redef_article.add_child redefs
490 end
491 end
492 return article
493 end
494
495 # MClassDef description template
496 fun tpl_mclassdef_article(mclassdef: MClassDef): TplArticle do
497 var article = mclassdef.tpl_article
498 if mclassdef.is_intro then article.content = null
499 article.source_link = tpl_showsource(mclassdef.location)
500 return article
501 end
502
503 # MProp description template
504 #
505 # `main_mpropdef`: The most important mpropdef to display
506 # `local_mpropdefs`: List of other locally defined mpropdefs to display
507 # `lin`: full linearization from local_mpropdefs to intro (displayed in redef tree)
508 fun tpl_mprop_article(main_mpropdef: MPropDef, local_mpropdefs: Array[MPropDef],
509 lin: Array[MPropDef]): TplArticle do
510 var mprop = main_mpropdef.mproperty
511 var article = new TplArticle(mprop.nitdoc_id)
512 var title = new Template
513 title.add mprop.tpl_icon
514 title.add "<span id='{main_mpropdef.nitdoc_id}'></span>"
515 if main_mpropdef.is_intro then
516 title.add mprop.tpl_link
517 title.add mprop.intro.tpl_signature
518 else
519 var cls_url = mprop.intro.mclassdef.mclass.nitdoc_url
520 var def_url = "{cls_url}#{mprop.nitdoc_id}"
521 var lnk = new TplLink.with_title(def_url, mprop.nitdoc_name,
522 "Go to introduction")
523 title.add "redef "
524 title.add lnk
525 end
526 article.title = title
527 article.title_classes.add "signature"
528 article.summary_title = "{mprop.nitdoc_name}"
529 article.subtitle = main_mpropdef.tpl_namespace
530 if main_mpropdef.mdoc_or_fallback != null then
531 article.content = main_mpropdef.mdoc_or_fallback.tpl_comment
532 end
533 var subarticle = new TplArticle("{main_mpropdef.nitdoc_id}.redefs")
534 # Add redef in same `MClass`
535 if local_mpropdefs.length > 1 then
536 for mpropdef in local_mpropdefs do
537 if mpropdef == main_mpropdef then continue
538 var redef_article = new TplArticle("{mpropdef.nitdoc_id}")
539 var redef_title = new Template
540 redef_title.add "also redef in "
541 redef_title.add mpropdef.tpl_namespace
542 redef_article.title = redef_title
543 redef_article.title_classes.add "signature info"
544 redef_article.css_classes.add "nospace"
545 var redef_content = new Template
546 if mpropdef.mdoc != null then
547 redef_content.add mpropdef.mdoc.tpl_comment
548 end
549 redef_article.content = redef_content
550 subarticle.add_child redef_article
551 end
552 end
553 # Add linearization
554 if lin.length > 1 then
555 var lin_article = new TplArticle("{main_mpropdef.nitdoc_id}.lin")
556 lin_article.title = "Inheritance"
557 var lst = new TplList.with_classes(["list-unstyled", "list-labeled"])
558 for mpropdef in lin do
559 lst.add_li mpropdef.tpl_inheritance_item
560 end
561 lin_article.content = lst
562 subarticle.add_child lin_article
563 end
564 article.add_child subarticle
565 return article
566 end
567
568 # MProperty description template
569 fun tpl_mpropdef_article(mpropdef: MPropDef): TplArticle do
570 var article = mpropdef.tpl_article
571 article.source_link = tpl_showsource(mpropdef.location)
572 return article
573 end
574 end
575
576 # The overview page
577 # Display a list of modules contained in program
578 class NitdocOverview
579 super NitdocPage
580
581 private var page = new TplPage
582 redef fun tpl_page do return page
583
584 private var sidebar = new TplSidebar
585 redef fun tpl_sidebar do return sidebar
586
587 redef fun tpl_title do
588 if ctx.opt_custom_title.value != null then
589 return ctx.opt_custom_title.value.to_s
590 else
591 return "Overview"
592 end
593 end
594
595 redef fun page_url do return "index.html"
596
597 # intro text
598 private fun tpl_intro: TplSection do
599 var section = new TplSection.with_title("overview", tpl_title)
600 var article = new TplArticle("intro")
601 if ctx.opt_custom_intro.value != null then
602 article.content = ctx.opt_custom_intro.value.to_s
603 end
604 section.add_child article
605 return section
606 end
607
608 # projects list
609 private fun tpl_projects(section: TplSection) do
610 # Projects list
611 var mprojects = model.mprojects.to_a
612 var sorter = new MConcernRankSorter
613 sorter.sort mprojects
614 var ssection = new TplSection.with_title("projects", "Projects")
615 for mproject in mprojects do
616 ssection.add_child tpl_mproject_article(mproject)
617 end
618 section.add_child ssection
619 end
620
621 redef fun tpl_content do
622 var top = tpl_intro
623 tpl_projects(top)
624 tpl_page.add_section top
625 end
626 end
627
628 # The search page
629 # Display a list of modules, classes and properties
630 class NitdocSearch
631 super NitdocPage
632
633 private var page = new TplPage
634 redef fun tpl_page do return page
635
636 redef fun tpl_title do return "Index"
637
638 redef fun page_url do return "search.html"
639
640 redef fun tpl_content do
641 var tpl = new TplSearchPage("search_all")
642 var section = new TplSection("search")
643 # title
644 tpl.title = "Index"
645 # modules list
646 for mmodule in modules_list do
647 tpl.modules.add mmodule.tpl_link
648 end
649 # classes list
650 for mclass in classes_list do
651 tpl.classes.add mclass.tpl_link
652 end
653 # properties list
654 for mproperty in mprops_list do
655 var m = new Template
656 m.add mproperty.intro.tpl_link
657 m.add " ("
658 m.add mproperty.intro.mclassdef.mclass.tpl_link
659 m.add ")"
660 tpl.props.add m
661 end
662 section.add_child tpl
663 tpl_page.add_section section
664 end
665
666 # Extract mmodule list to display (sorted by name)
667 private fun modules_list: Array[MModule] do
668 var sorted = new Array[MModule]
669 for mmodule in model.mmodule_importation_hierarchy do
670 if mmodule.is_fictive or mmodule.is_test_suite then continue
671 sorted.add mmodule
672 end
673 name_sorter.sort(sorted)
674 return sorted
675 end
676
677 # Extract mclass list to display (sorted by name)
678 private fun classes_list: Array[MClass] do
679 var sorted = new Array[MClass]
680 for mclass in model.mclasses do
681 if not ctx.filter_mclass(mclass) then continue
682 sorted.add mclass
683 end
684 name_sorter.sort(sorted)
685 return sorted
686 end
687
688 # Extract mproperty list to display (sorted by name)
689 private fun mprops_list: Array[MProperty] do
690 var sorted = new Array[MProperty]
691 for mproperty in model.mproperties do
692 if ctx.filter_mproperty(mproperty) then sorted.add mproperty
693 end
694 name_sorter.sort(sorted)
695 return sorted
696 end
697 end
698
699 # A group page
700 # Display a flattened view of the group
701 class NitdocGroup
702 super NitdocPage
703
704 private var mgroup: MGroup
705
706 private var concerns: ConcernsTree is noinit
707 private var intros: Set[MClass] is noinit
708 private var redefs: Set[MClass] is noinit
709
710 init do
711 self.concerns = model.concerns_tree(mgroup.collect_mmodules)
712 self.concerns.sort_with(new MConcernRankSorter)
713 self.intros = mgroup.in_nesting_intro_mclasses(ctx.min_visibility)
714 var redefs = new HashSet[MClass]
715 for rdef in mgroup.in_nesting_redef_mclasses(ctx.min_visibility) do
716 if intros.has(rdef) then continue
717 redefs.add rdef
718 end
719 self.redefs = redefs
720 end
721
722 private var page = new TplPage
723 redef fun tpl_page do return page
724
725 private var sidebar = new TplSidebar
726 redef fun tpl_sidebar do return sidebar
727
728 redef fun tpl_title do return mgroup.nitdoc_name
729
730 redef fun page_url do return mgroup.nitdoc_url
731
732 redef fun tpl_topmenu do
733 var topmenu = super
734 var mproject = mgroup.mproject
735 if not mgroup.is_root then
736 topmenu.add_link new TplLink(mproject.nitdoc_url, mproject.nitdoc_name)
737 end
738 topmenu.add_link new TplLink(page_url, mproject.nitdoc_name)
739 return topmenu
740 end
741
742 # Class list to display in sidebar
743 fun tpl_sidebar_mclasses do
744 var mclasses = new HashSet[MClass]
745 mclasses.add_all intros
746 mclasses.add_all redefs
747 if mclasses.is_empty then return
748 var list = new TplList.with_classes(["list-unstyled", "list-labeled"])
749
750 var sorted = mclasses.to_a
751 name_sorter.sort(sorted)
752 for mclass in sorted do
753 list.add_li tpl_sidebar_item(mclass)
754 end
755 tpl_sidebar.boxes.add new TplSideBox.with_content("All classes", list)
756 end
757
758 private fun tpl_sidebar_item(def: MClass): TplListItem do
759 var classes = def.intro.tpl_css_classes.to_a
760 if intros.has(def) then
761 classes.add "intro"
762 else
763 classes.add "redef"
764 end
765 var lnk = new Template
766 lnk.add new TplLabel.with_classes(classes)
767 lnk.add def.tpl_link
768 return new TplListItem.with_content(lnk)
769 end
770
771 # intro text
772 private fun tpl_intro: TplSection do
773 var section = new TplSection.with_title("top", tpl_title)
774 var article = new TplArticle("intro")
775
776 if mgroup.is_root then
777 section.subtitle = mgroup.mproject.tpl_declaration
778 article.content = mgroup.mproject.tpl_definition
779 else
780 section.subtitle = mgroup.tpl_declaration
781 article.content = mgroup.tpl_definition
782 end
783 section.add_child article
784 return section
785 end
786
787 private fun tpl_concerns(section: TplSection) do
788 if concerns.is_empty then return
789 section.add_child new TplArticle.with_content("concerns", "Concerns", concerns.to_tpl)
790 end
791
792 private fun tpl_groups(parent: TplSection) do
793 var lst = concerns.to_a
794 var section = parent
795 for mentity in lst do
796 if mentity isa MProject then
797 section.add_child new TplSection(mentity.nitdoc_id)
798 else if mentity isa MGroup then
799 section.add_child new TplSection(mentity.nitdoc_id)
800 else if mentity isa MModule then
801 section.add_child tpl_mmodule_article(mentity)
802 end
803 end
804 end
805
806 redef fun tpl_content do
807 tpl_sidebar_mclasses
808 var top = tpl_intro
809 tpl_concerns(top)
810 tpl_groups(top)
811 tpl_page.add_section top
812 end
813
814 private fun sort_by_mclass(mclassdefs: Collection[MClassDef]): Map[MClass, Set[MClassDef]] do
815 var map = new HashMap[MClass, Set[MClassDef]]
816 for mclassdef in mclassdefs do
817 var mclass = mclassdef.mclass
818 if not map.has_key(mclass) then map[mclass] = new HashSet[MClassDef]
819 map[mclass].add mclassdef
820 end
821 return map
822 end
823 end
824
825 # A module page
826 # Display the list of introduced and redefined classes in module
827 class NitdocModule
828 super NitdocPage
829
830 private var mmodule: MModule
831 private var concerns: ConcernsTree is noinit
832 private var mclasses2mdefs: Map[MClass, Set[MClassDef]] is noinit
833 private var mmodules2mclasses: Map[MModule, Set[MClass]] is noinit
834
835
836 init do
837 var mclassdefs = new HashSet[MClassDef]
838 mclassdefs.add_all mmodule.intro_mclassdefs(ctx.min_visibility)
839 mclassdefs.add_all mmodule.redef_mclassdefs(ctx.min_visibility)
840 self.mclasses2mdefs = sort_by_mclass(mclassdefs)
841 self.mmodules2mclasses = group_by_mmodule(mclasses2mdefs.keys)
842 self.concerns = model.concerns_tree(mmodules2mclasses.keys)
843 # rank concerns
844 mmodule.mgroup.mproject.booster_rank = -1000
845 mmodule.mgroup.booster_rank = -1000
846 mmodule.booster_rank = -1000
847 self.concerns.sort_with(new MConcernRankSorter)
848 mmodule.mgroup.mproject.booster_rank = 0
849 mmodule.mgroup.booster_rank = 0
850 mmodule.booster_rank = 0
851 end
852
853 private var page = new TplPage
854 redef fun tpl_page do return page
855
856 private var sidebar = new TplSidebar
857 redef fun tpl_sidebar do return sidebar
858
859 redef fun tpl_title do return mmodule.nitdoc_name
860 redef fun page_url do return mmodule.nitdoc_url
861
862 redef fun tpl_topmenu do
863 var topmenu = super
864 var mproject = mmodule.mgroup.mproject
865 topmenu.add_link new TplLink(mproject.nitdoc_url, mproject.nitdoc_name)
866 topmenu.add_link new TplLink(page_url, mmodule.nitdoc_name)
867 return topmenu
868 end
869
870 # Class list to display in sidebar
871 fun tpl_sidebar_mclasses do
872 var mclasses = new HashSet[MClass]
873 mclasses.add_all mmodule.filter_intro_mclasses(ctx.min_visibility)
874 mclasses.add_all mmodule.filter_redef_mclasses(ctx.min_visibility)
875 if mclasses.is_empty then return
876 var list = new TplList.with_classes(["list-unstyled", "list-labeled"])
877
878 var sorted = mclasses.to_a
879 name_sorter.sort(sorted)
880 for mclass in sorted do
881 list.add_li tpl_sidebar_item(mclass)
882 end
883 tpl_sidebar.boxes.add new TplSideBox.with_content("All classes", list)
884 end
885
886 private fun tpl_sidebar_item(def: MClass): TplListItem do
887 var classes = def.intro.tpl_css_classes.to_a
888 if def.intro_mmodule == mmodule then
889 classes.add "intro"
890 else
891 classes.add "redef"
892 end
893 var lnk = new Template
894 lnk.add new TplLabel.with_classes(classes)
895 lnk.add def.tpl_link
896 return new TplListItem.with_content(lnk)
897 end
898
899 # intro text
900 private fun tpl_intro: TplSection do
901 var section = new TplSection.with_title("top", tpl_title)
902 section.subtitle = mmodule.tpl_declaration
903
904 var article = new TplArticle("intro")
905 var def = mmodule.tpl_definition
906 var location = mmodule.location
907 article.source_link = tpl_showsource(location)
908 article.content = def
909 section.add_child article
910 return section
911 end
912
913 # inheritance section
914 private fun tpl_inheritance(parent: TplSection) do
915 # Extract relevent modules
916 var imports = mmodule.in_importation.greaters
917 if imports.length > 10 then imports = mmodule.in_importation.direct_greaters
918 var clients = mmodule.in_importation.smallers
919 if clients.length > 10 then clients = mmodule.in_importation.direct_smallers
920
921 # Display lists
922 var section = new TplSection.with_title("dependencies", "Dependencies")
923
924 # Graph
925 var mmodules = new HashSet[MModule]
926 mmodules.add_all mmodule.nested_mmodules
927 mmodules.add_all imports
928 if clients.length < 10 then mmodules.add_all clients
929 mmodules.add mmodule
930 var graph = tpl_dot(mmodules)
931 if graph != null then section.add_child graph
932
933 # Imports
934 var lst = new Array[MModule]
935 for dep in imports do
936 if dep.is_fictive or dep.is_test_suite then continue
937 if dep == mmodule then continue
938 lst.add(dep)
939 end
940 if not lst.is_empty then
941 name_sorter.sort lst
942 section.add_child tpl_list("imports", "Imports", lst)
943 end
944
945 # Clients
946 lst = new Array[MModule]
947 for dep in clients do
948 if dep.is_fictive or dep.is_test_suite then continue
949 if dep == mmodule then continue
950 lst.add(dep)
951 end
952 if not lst.is_empty then
953 name_sorter.sort lst
954 section.add_child tpl_list("clients", "Clients", lst)
955 end
956
957 parent.add_child section
958 end
959
960 private fun tpl_list(id: String, title: String, mmodules: Array[MModule]): TplArticle do
961 var article = new TplArticle.with_title(id, title)
962 var list = new TplList.with_classes(["list-unstyled", "list-definition"])
963 for mmodule in mmodules do list.elts.add mmodule.tpl_list_item
964 article.content = list
965 return article
966 end
967
968 private fun tpl_concerns(parent: TplSection) do
969 if concerns.is_empty then return
970 parent.add_child new TplArticle.with_content("concerns", "Concerns", concerns.to_tpl)
971 end
972
973 private fun tpl_mclasses(parent: TplSection) do
974 for mentity in concerns do
975 if mentity isa MProject then
976 parent.add_child new TplSection(mentity.nitdoc_id)
977 else if mentity isa MGroup then
978 parent.add_child new TplSection(mentity.nitdoc_id)
979 else if mentity isa MModule then
980 var section = new TplSection(mentity.nitdoc_id)
981 var title = new Template
982 if mentity == mmodule then
983 title.add "in "
984 section.summary_title = "in {mentity.nitdoc_name}"
985 else
986 title.add "from "
987 section.summary_title = "from {mentity.nitdoc_name}"
988 end
989 title.add mentity.tpl_namespace
990 section.title = title
991
992 var mclasses = mmodules2mclasses[mentity].to_a
993 name_sorter.sort(mclasses)
994 for mclass in mclasses do
995 section.add_child tpl_mclass_article(mclass, mclasses2mdefs[mclass].to_a)
996 end
997 parent.add_child section
998 end
999 end
1000 end
1001
1002 private fun group_by_mmodule(mclasses: Collection[MClass]): Map[MModule, Set[MClass]] do
1003 var res = new HashMap[MModule, Set[MClass]]
1004 for mclass in mclasses do
1005 var mmodule = mclass.intro_mmodule
1006 if not res.has_key(mmodule) then
1007 res[mmodule] = new HashSet[MClass]
1008 end
1009 res[mmodule].add(mclass)
1010 end
1011 return res
1012 end
1013
1014 redef fun tpl_content do
1015 tpl_sidebar_mclasses
1016 var top = tpl_intro
1017 tpl_inheritance(top)
1018 tpl_concerns(top)
1019 tpl_mclasses(top)
1020 tpl_page.add_section top
1021 end
1022
1023 # Genrate dot hierarchy for class inheritance
1024 fun tpl_dot(mmodules: Collection[MModule]): nullable TplArticle do
1025 var poset = new POSet[MModule]
1026 for mmodule in mmodules do
1027 if mmodule.is_fictive or mmodule.is_test_suite then continue
1028 poset.add_node mmodule
1029 for omodule in mmodules do
1030 if omodule.is_fictive or omodule.is_test_suite then continue
1031 poset.add_node mmodule
1032 if mmodule.in_importation < omodule then
1033 poset.add_edge(mmodule, omodule)
1034 end
1035 end
1036 end
1037 # build graph
1038 var op = new RopeBuffer
1039 var name = "dep_module_{mmodule.nitdoc_id}"
1040 op.append("digraph \"{name.escape_to_dot}\" \{ 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")
1041 for mmodule in poset do
1042 if mmodule == self.mmodule then
1043 op.append("\"{mmodule.name.escape_to_dot}\"[shape=box,margin=0.03];\n")
1044 else
1045 op.append("\"{mmodule.name.escape_to_dot}\"[URL=\"{mmodule.nitdoc_url.escape_to_dot}\"];\n")
1046 end
1047 for omodule in poset[mmodule].direct_greaters do
1048 op.append("\"{mmodule.name.escape_to_dot}\"->\"{omodule.name.escape_to_dot}\";\n")
1049 end
1050 end
1051 op.append("\}\n")
1052 return tpl_graph(op, name, null)
1053 end
1054
1055 private fun sort_by_mclass(mclassdefs: Collection[MClassDef]): Map[MClass, Set[MClassDef]] do
1056 var map = new HashMap[MClass, Set[MClassDef]]
1057 for mclassdef in mclassdefs do
1058 var mclass = mclassdef.mclass
1059 if not map.has_key(mclass) then map[mclass] = new HashSet[MClassDef]
1060 map[mclass].add mclassdef
1061 end
1062 return map
1063 end
1064 end
1065
1066 # A class page
1067 # Display a list properties defined or redefined for this class
1068 class NitdocClass
1069 super NitdocPage
1070
1071 private var mclass: MClass
1072 private var concerns: ConcernsTree is noinit
1073 private var mprops2mdefs: Map[MProperty, Set[MPropDef]] is noinit
1074 private var mmodules2mprops: Map[MModule, Set[MProperty]] is noinit
1075
1076 init do
1077 var mpropdefs = new HashSet[MPropDef]
1078 mpropdefs.add_all mclass.intro_mpropdefs(ctx.min_visibility)
1079 mpropdefs.add_all mclass.redef_mpropdefs(ctx.min_visibility)
1080 self.mprops2mdefs = sort_by_mproperty(mpropdefs)
1081 self.mmodules2mprops = sort_by_mmodule(mprops2mdefs.keys)
1082 self.concerns = model.concerns_tree(mmodules2mprops.keys)
1083 self.concerns.sort_with(new MConcernRankSorter)
1084 end
1085
1086 private var page = new TplPage
1087 redef fun tpl_page do return page
1088
1089 private var sidebar = new TplSidebar
1090 redef fun tpl_sidebar do return sidebar
1091
1092 redef fun tpl_title do return "{mclass.nitdoc_name}{mclass.tpl_signature.write_to_string}"
1093 redef fun page_url do return mclass.nitdoc_url
1094
1095 redef fun tpl_topmenu do
1096 var topmenu = super
1097 var mproject = mclass.intro_mmodule.mgroup.mproject
1098 topmenu.add_link new TplLink("{mproject.nitdoc_url}", "{mproject.nitdoc_name}")
1099 topmenu.add_link new TplLink(page_url, mclass.nitdoc_name)
1100 return topmenu
1101 end
1102
1103 # Property list to display in sidebar
1104 fun tpl_sidebar_properties do
1105 var by_kind = new PropertiesByKind.with_elements(mclass_inherited_mprops)
1106 var summary = new TplList.with_classes(["list-unstyled"])
1107
1108 by_kind.sort_groups(name_sorter)
1109 for g in by_kind.groups do tpl_sidebar_list(g, summary)
1110 tpl_sidebar.boxes.add new TplSideBox.with_content("All properties", summary)
1111 end
1112
1113 private fun tpl_sidebar_list(mprops: PropertyGroup[MProperty], summary: TplList) do
1114 if mprops.is_empty then return
1115 var entry = new TplListItem.with_content(mprops.title)
1116 var list = new TplList.with_classes(["list-unstyled", "list-labeled"])
1117 for mprop in mprops do
1118 list.add_li tpl_sidebar_item(mprop)
1119 end
1120 entry.append list
1121 summary.elts.add entry
1122 end
1123
1124 private fun tpl_sidebar_item(mprop: MProperty): TplListItem do
1125 var classes = mprop.intro.tpl_css_classes.to_a
1126 if not mprops2mdefs.has_key(mprop) then
1127 classes.add "inherit"
1128 var cls_url = mprop.intro.mclassdef.mclass.nitdoc_url
1129 var def_url = "{cls_url}#{mprop.nitdoc_id}"
1130 var lnk = new TplLink(def_url, mprop.nitdoc_name)
1131 var mdoc = mprop.intro.mdoc_or_fallback
1132 if mdoc != null then lnk.title = mdoc.short_comment
1133 var item = new Template
1134 item.add new TplLabel.with_classes(classes)
1135 item.add lnk
1136 return new TplListItem.with_content(item)
1137 end
1138 var defs = mprops2mdefs[mprop]
1139 if defs.has(mprop.intro) then
1140 classes.add "intro"
1141 else
1142 classes.add "redef"
1143 end
1144 var lnk = new Template
1145 lnk.add new TplLabel.with_classes(classes)
1146 lnk.add mprop.tpl_anchor
1147 return new TplListItem.with_content(lnk)
1148 end
1149
1150 private fun tpl_intro: TplSection do
1151 var section = new TplSection.with_title("top", tpl_title)
1152 section.subtitle = mclass.intro.tpl_declaration
1153 var article = new TplArticle("comment")
1154 var mdoc = mclass.mdoc_or_fallback
1155 if mdoc != null then
1156 article.content = mdoc.tpl_comment
1157 end
1158 section.add_child article
1159 return section
1160 end
1161
1162 private fun tpl_concerns(parent: TplSection) do
1163 # intro title
1164 var section = new TplSection.with_title("intro", "Introduction")
1165 section.summary_title = "Introduction"
1166 section.add_child tpl_mclassdef_article(mclass.intro)
1167 parent.add_child section
1168 # concerns
1169 if concerns.is_empty then return
1170 parent.add_child new TplArticle.with_content("concerns", "Concerns", concerns.to_tpl)
1171 end
1172
1173 private fun tpl_inheritance(parent: TplSection) do
1174 # parents
1175 var hparents = new HashSet[MClass]
1176 for c in mclass.in_hierarchy(mainmodule).direct_greaters do
1177 if ctx.filter_mclass(c) then hparents.add c
1178 end
1179
1180 # ancestors
1181 var hancestors = new HashSet[MClass]
1182 for c in mclass.in_hierarchy(mainmodule).greaters do
1183 if c == mclass then continue
1184 if not ctx.filter_mclass(c) then continue
1185 if hparents.has(c) then continue
1186 hancestors.add c
1187 end
1188
1189 # children
1190 var hchildren = new HashSet[MClass]
1191 for c in mclass.in_hierarchy(mainmodule).direct_smallers do
1192 if ctx.filter_mclass(c) then hchildren.add c
1193 end
1194
1195 # descendants
1196 var hdescendants = new HashSet[MClass]
1197 for c in mclass.in_hierarchy(mainmodule).smallers do
1198 if c == mclass then continue
1199 if not ctx.filter_mclass(c) then continue
1200 if hchildren.has(c) then continue
1201 hdescendants.add c
1202 end
1203
1204 # Display lists
1205 var section = new TplSection.with_title("inheritance", "Inheritance")
1206
1207 # Graph
1208 var mclasses = new HashSet[MClass]
1209 mclasses.add_all hancestors
1210 mclasses.add_all hparents
1211 mclasses.add_all hchildren
1212 mclasses.add_all hdescendants
1213 mclasses.add mclass
1214 var graph = tpl_dot(mclasses)
1215 if graph != null then section.add_child graph
1216
1217 # parents
1218 if not hparents.is_empty then
1219 var lst = hparents.to_a
1220 name_sorter.sort lst
1221 section.add_child tpl_list("parents", "Parents", lst)
1222 end
1223
1224 # ancestors
1225 if not hancestors.is_empty then
1226 var lst = hancestors.to_a
1227 name_sorter.sort lst
1228 section.add_child tpl_list("ancestors", "Ancestors", lst)
1229 end
1230
1231 # children
1232 if not hchildren.is_empty then
1233 var lst = hchildren.to_a
1234 name_sorter.sort lst
1235 section.add_child tpl_list("children", "Children", lst)
1236 end
1237
1238 # descendants
1239 if not hdescendants.is_empty then
1240 var lst = hdescendants.to_a
1241 name_sorter.sort lst
1242 section.add_child tpl_list("descendants", "Descendants", lst)
1243 end
1244
1245 parent.add_child section
1246 end
1247
1248 private fun tpl_list(id: String, title: String, elts: Array[MClass]): TplArticle do
1249 var article = new TplArticle.with_title(id, title)
1250 if elts.length > 20 then
1251 var tpl = new Template
1252 for e in elts do
1253 tpl.add e.tpl_link
1254 if e != elts.last then tpl.add ", "
1255 end
1256 article.content = tpl
1257 else
1258 var list = new TplList.with_classes(["list-unstyled", "list-definition"])
1259 for elt in elts do list.elts.add elt.tpl_list_item
1260 article.content = list
1261 end
1262 return article
1263 end
1264
1265 private fun tpl_properties(parent: TplSection) do
1266 var lst = concerns.to_a
1267 for mentity in lst do
1268 if mentity isa MProject then
1269 parent.add_child new TplSection(mentity.nitdoc_id)
1270 else if mentity isa MGroup then
1271 parent.add_child new TplSection(mentity.nitdoc_id)
1272 else if mentity isa MModule then
1273 var section = new TplSection(mentity.nitdoc_id)
1274 var title = new Template
1275 title.add "in "
1276 title.add mentity.tpl_namespace
1277 section.title = title
1278 section.summary_title = "in {mentity.nitdoc_name}"
1279
1280 # properties
1281 var mprops = mmodules2mprops[mentity]
1282 var by_kind = new PropertiesByKind.with_elements(mprops)
1283
1284 for g in by_kind.groups do
1285 for article in tpl_mproperty_articles(g) do
1286 section.add_child article
1287 end
1288 end
1289 parent.add_child section
1290 end
1291 end
1292 end
1293
1294 private fun tpl_mproperty_articles(elts: Collection[MProperty]):
1295 Sequence[TplArticle] do
1296 var articles = new List[TplArticle]
1297 for elt in elts do
1298 var local_defs = mprops2mdefs[elt]
1299 # var all_defs = elt.mpropdefs
1300 var all_defs = new HashSet[MPropDef]
1301 for local_def in local_defs do
1302 all_defs.add local_def
1303 var mpropdef = local_def
1304 while not mpropdef.is_intro do
1305 mpropdef = mpropdef.lookup_next_definition(mainmodule, mpropdef.mclassdef.bound_mtype)
1306 all_defs.add mpropdef
1307 end
1308 end
1309 var loc_lin = local_defs.to_a
1310 mainmodule.linearize_mpropdefs(loc_lin)
1311 var all_lin = all_defs.to_a
1312 mainmodule.linearize_mpropdefs(all_lin)
1313 articles.add tpl_mprop_article(loc_lin.first, loc_lin, all_lin)
1314 end
1315 return articles
1316 end
1317
1318 redef fun tpl_content do
1319 tpl_sidebar_properties
1320 var top = tpl_intro
1321 tpl_inheritance(top)
1322 tpl_concerns(top)
1323 tpl_properties(top)
1324 tpl_page.add_section top
1325 end
1326
1327 private fun sort_by_mproperty(mpropdefs: Collection[MPropDef]): Map[MProperty, Set[MPropDef]] do
1328 var map = new HashMap[MProperty, Set[MPropDef]]
1329 for mpropdef in mpropdefs do
1330 var mproperty = mpropdef.mproperty
1331 if not map.has_key(mproperty) then map[mproperty] = new HashSet[MPropDef]
1332 map[mproperty].add mpropdef
1333 end
1334 return map
1335 end
1336
1337 private fun sort_by_mmodule(mprops: Collection[MProperty]): Map[MModule, Set[MProperty]] do
1338 var map = new HashMap[MModule, Set[MProperty]]
1339 for mprop in mprops do
1340 var mpropdefs = mprops2mdefs[mprop].to_a
1341 mainmodule.linearize_mpropdefs(mpropdefs)
1342 var mmodule = mpropdefs.first.mclassdef.mmodule
1343 if not map.has_key(mmodule) then map[mmodule] = new HashSet[MProperty]
1344 map[mmodule].add mprop
1345 end
1346 return map
1347 end
1348
1349 private fun mclass_inherited_mprops: Set[MProperty] do
1350 var res = new HashSet[MProperty]
1351 var local = mclass.local_mproperties(ctx.min_visibility)
1352 for mprop in mclass.inherited_mproperties(mainmodule, ctx.min_visibility) do
1353 if local.has(mprop) then continue
1354 #if mprop isa MMethod and mprop.is_init then continue
1355 if mprop.intro.mclassdef.mclass.name == "Object" and
1356 (mprop.visibility == protected_visibility or
1357 mprop.intro.mclassdef.mmodule.name != "kernel") then continue
1358 res.add mprop
1359 end
1360 res.add_all local
1361 return res
1362 end
1363
1364 private fun collect_mmodules(mprops: Collection[MProperty]): Set[MModule] do
1365 var res = new HashSet[MModule]
1366 for mprop in mprops do
1367 if mprops2mdefs.has_key(mprop) then
1368 for mpropdef in mprops2mdefs[mprop] do res.add mpropdef.mclassdef.mmodule
1369 end
1370 end
1371 return res
1372 end
1373
1374 # Generate dot hierarchy for classes
1375 fun tpl_dot(mclasses: Collection[MClass]): nullable TplArticle do
1376 var poset = new POSet[MClass]
1377
1378 for mclass in mclasses do
1379 poset.add_node mclass
1380 for oclass in mclasses do
1381 if mclass == oclass then continue
1382 poset.add_node oclass
1383 if mclass.in_hierarchy(mainmodule) < oclass then
1384 poset.add_edge(mclass, oclass)
1385 end
1386 end
1387 end
1388
1389 var op = new RopeBuffer
1390 var name = "dep_class_{mclass.nitdoc_id}"
1391 op.append("digraph \"{name.escape_to_dot}\" \{ 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")
1392 var classes = poset.to_a
1393 var todo = new Array[MClass]
1394 var done = new HashSet[MClass]
1395 mainmodule.linearize_mclasses(classes)
1396 if not classes.is_empty then todo.add classes.first
1397 while not todo.is_empty do
1398 var c = todo.shift
1399 if done.has(c) then continue
1400 done.add c
1401 if c == mclass then
1402 op.append("\"{c.name.escape_to_dot}\"[shape=box,margin=0.03];\n")
1403 else
1404 op.append("\"{c.name.escape_to_dot}\"[URL=\"{c.nitdoc_url.escape_to_dot}\"];\n")
1405 end
1406 var smallers = poset[c].direct_smallers
1407 if smallers.length < 10 then
1408 for c2 in smallers do
1409 op.append("\"{c2.name.escape_to_dot}\"->\"{c.name.escape_to_dot}\";\n")
1410 end
1411 todo.add_all smallers
1412 else
1413 op.append("\"...\"->\"{c.name.escape_to_dot}\";\n")
1414 end
1415 end
1416 op.append("\}\n")
1417 return tpl_graph(op, name, null)
1418 end
1419 end
1420
1421 # Groups properties by kind.
1422 private class PropertiesByKind
1423 # The virtual types.
1424 var virtual_types = new PropertyGroup[MVirtualTypeProp]("Virtual types")
1425
1426 # The constructors.
1427 var constructors = new PropertyGroup[MMethod]("Contructors")
1428
1429 # The attributes.
1430 var attributes = new PropertyGroup[MAttribute]("Attributes")
1431
1432 # The methods.
1433 var methods = new PropertyGroup[MMethod]("Methods")
1434
1435 # The inner classes.
1436 var inner_classes = new PropertyGroup[MInnerClass]("Inner classes")
1437
1438 # All the groups.
1439 #
1440 # Sorted in the order they are displayed to the user.
1441 var groups: SequenceRead[PropertyGroup[MProperty]] = [
1442 virtual_types,
1443 constructors,
1444 attributes,
1445 methods,
1446 inner_classes: PropertyGroup[MProperty]]
1447
1448 # Add each the specified property to the appropriate list.
1449 init with_elements(properties: Collection[MProperty]) do add_all(properties)
1450
1451 # Add the specified property to the appropriate list.
1452 fun add(property: MProperty) do
1453 if property isa MMethod then
1454 if property.is_init then
1455 constructors.add property
1456 else
1457 methods.add property
1458 end
1459 else if property isa MVirtualTypeProp then
1460 virtual_types.add property
1461 else if property isa MAttribute then
1462 attributes.add property
1463 else if property isa MInnerClass then
1464 inner_classes.add property
1465 else
1466 abort
1467 end
1468 end
1469
1470 # Add each the specified property to the appropriate list.
1471 fun add_all(properties: Collection[MProperty]) do
1472 for p in properties do add(p)
1473 end
1474
1475 # Sort each group with the specified comparator.
1476 fun sort_groups(comparator: Comparator) do
1477 for g in groups do comparator.sort(g)
1478 end
1479 end
1480
1481 # A Group of properties of the same kind.
1482 private class PropertyGroup[E: MProperty]
1483 super Array[E]
1484
1485 # The title of the group, as displayed to the user.
1486 var title: String
1487 end
1488
1489 # A MProperty page
1490 class NitdocProperty
1491 super NitdocPage
1492
1493 private var mproperty: MProperty
1494 private var concerns: ConcernsTree is noinit
1495 private var mmodules2mdefs: Map[MModule, Set[MPropDef]] is noinit
1496
1497 init do
1498 self.mproperty = mproperty
1499 self.mmodules2mdefs = sort_by_mmodule(collect_mpropdefs)
1500 self.concerns = model.concerns_tree(mmodules2mdefs.keys)
1501 self.concerns.sort_with(new MConcernRankSorter)
1502 end
1503
1504 private fun collect_mpropdefs: Set[MPropDef] do
1505 var res = new HashSet[MPropDef]
1506 for mpropdef in mproperty.mpropdefs do
1507 if not mpropdef.is_intro then res.add mpropdef
1508 end
1509 return res
1510 end
1511
1512 private var page = new TplPage
1513 redef fun tpl_page do return page
1514
1515 private var sidebar = new TplSidebar
1516 redef fun tpl_sidebar do return sidebar
1517
1518 redef fun tpl_title do
1519 return "{mproperty.nitdoc_name}{mproperty.tpl_signature.write_to_string}"
1520 end
1521
1522 redef fun page_url do return mproperty.nitdoc_url
1523
1524 redef fun tpl_topmenu do
1525 var topmenu = super
1526 var mmodule = mproperty.intro_mclassdef.mmodule
1527 var mproject = mmodule.mgroup.mproject
1528 var mclass = mproperty.intro_mclassdef.mclass
1529 topmenu.add_link new TplLink("{mproject.nitdoc_url}", "{mproject.nitdoc_name}")
1530 topmenu.add_link new TplLink("{mclass.nitdoc_url}", "{mclass.nitdoc_name}")
1531 topmenu.add_link new TplLink(page_url, mproperty.nitdoc_name)
1532 return topmenu
1533 end
1534
1535 private fun tpl_intro: TplSection do
1536 var title = new Template
1537 title.add mproperty.nitdoc_name
1538 title.add mproperty.intro.tpl_signature
1539 var section = new TplSection.with_title("top", title)
1540 section.subtitle = mproperty.tpl_namespace
1541 section.summary_title = mproperty.nitdoc_name
1542 return section
1543 end
1544
1545 private fun tpl_properties(parent: TplSection) do
1546 # intro title
1547 var ns = mproperty.intro.mclassdef.mmodule.tpl_namespace
1548 var section = new TplSection("intro")
1549 var title = new Template
1550 title.add "Introduction in "
1551 title.add ns
1552 section.title = title
1553 section.summary_title = "Introduction"
1554 section.add_child tpl_mpropdef_article(mproperty.intro)
1555 parent.add_child section
1556
1557 # concerns
1558 if concerns.is_empty then return
1559 parent.add_child new TplArticle.with_content("Concerns", "Concerns", concerns.to_tpl)
1560
1561 # redef list
1562 var lst = concerns.to_a
1563 for mentity in lst do
1564 if mentity isa MProject then
1565 parent.add_child new TplSection(mentity.nitdoc_id)
1566 else if mentity isa MGroup then
1567 parent.add_child new TplSection(mentity.nitdoc_id)
1568 else if mentity isa MModule then
1569 var ssection = new TplSection(mentity.nitdoc_id)
1570 title = new Template
1571 title.add "in "
1572 title.add mentity.tpl_namespace
1573 ssection.title = title
1574 ssection.summary_title = "in {mentity.nitdoc_name}"
1575
1576 # properties
1577 var mpropdefs = mmodules2mdefs[mentity].to_a
1578 name_sorter.sort(mpropdefs)
1579 for mpropdef in mpropdefs do
1580 ssection.add_child tpl_mpropdef_article(mpropdef)
1581 end
1582 parent.add_child ssection
1583 end
1584 end
1585 end
1586
1587 redef fun tpl_content do
1588 var top = tpl_intro
1589 tpl_properties(top)
1590 tpl_page.add_section top
1591 end
1592
1593 private fun sort_by_mmodule(mpropdefs: Collection[MPropDef]): Map[MModule, Set[MPropDef]] do
1594 var map = new HashMap[MModule, Set[MPropDef]]
1595 for mpropdef in mpropdefs do
1596 var mmodule = mpropdef.mclassdef.mmodule
1597 if not map.has_key(mmodule) then map[mmodule] = new HashSet[MPropDef]
1598 map[mmodule].add mpropdef
1599 end
1600 return map
1601 end
1602 end
1603