nitdoc: Escape links’ attributes.
[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 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 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.name, "Go to introduction")
522 title.add "redef "
523 title.add lnk
524 end
525 article.title = title
526 article.title_classes.add "signature"
527 article.summary_title = "{mprop.nitdoc_name}"
528 article.subtitle = main_mpropdef.tpl_namespace
529 if main_mpropdef.mdoc_or_fallback != null then
530 article.content = main_mpropdef.mdoc_or_fallback.tpl_comment
531 end
532 var subarticle = new TplArticle("{main_mpropdef.nitdoc_id}.redefs")
533 # Add redef in same `MClass`
534 if local_mpropdefs.length > 1 then
535 for mpropdef in local_mpropdefs do
536 if mpropdef == main_mpropdef then continue
537 var redef_article = new TplArticle("{mpropdef.nitdoc_id}")
538 var redef_title = new Template
539 redef_title.add "also redef in "
540 redef_title.add mpropdef.tpl_namespace
541 redef_article.title = redef_title
542 redef_article.title_classes.add "signature info"
543 redef_article.css_classes.add "nospace"
544 var redef_content = new Template
545 if mpropdef.mdoc_or_fallback != null then
546 redef_content.add mpropdef.mdoc_or_fallback.tpl_comment
547 end
548 redef_article.content = redef_content
549 subarticle.add_child redef_article
550 end
551 end
552 # Add linearization
553 if lin.length > 1 then
554 var lin_article = new TplArticle("{main_mpropdef.nitdoc_id}.lin")
555 lin_article.title = "Inheritance"
556 var lst = new TplList.with_classes(["list-unstyled", "list-labeled"])
557 for mpropdef in lin do
558 lst.add_li mpropdef.tpl_inheritance_item
559 end
560 lin_article.content = lst
561 subarticle.add_child lin_article
562 end
563 article.add_child subarticle
564 return article
565 end
566
567 # MProperty description template
568 fun tpl_mpropdef_article(mpropdef: MPropDef): TplArticle do
569 var article = mpropdef.tpl_article
570 article.source_link = tpl_showsource(mpropdef.location)
571 return article
572 end
573 end
574
575 # The overview page
576 # Display a list of modules contained in program
577 class NitdocOverview
578 super NitdocPage
579
580 private var page = new TplPage
581 redef fun tpl_page do return page
582
583 private var sidebar = new TplSidebar
584 redef fun tpl_sidebar do return sidebar
585
586 redef fun tpl_title do
587 if ctx.opt_custom_title.value != null then
588 return ctx.opt_custom_title.value.to_s
589 else
590 return "Overview"
591 end
592 end
593
594 redef fun page_url do return "index.html"
595
596 # intro text
597 private fun tpl_intro: TplSection do
598 var section = new TplSection.with_title("overview", tpl_title)
599 var article = new TplArticle("intro")
600 if ctx.opt_custom_intro.value != null then
601 article.content = ctx.opt_custom_intro.value.to_s
602 end
603 section.add_child article
604 return section
605 end
606
607 # projects list
608 private fun tpl_projects(section: TplSection) do
609 # Projects list
610 var mprojects = model.mprojects.to_a
611 var sorter = new MConcernRankSorter
612 sorter.sort mprojects
613 var ssection = new TplSection.with_title("projects", "Projects")
614 for mproject in mprojects do
615 ssection.add_child tpl_mproject_article(mproject)
616 end
617 section.add_child ssection
618 end
619
620 redef fun tpl_content do
621 var top = tpl_intro
622 tpl_projects(top)
623 tpl_page.add_section top
624 end
625 end
626
627 # The search page
628 # Display a list of modules, classes and properties
629 class NitdocSearch
630 super NitdocPage
631
632 private var page = new TplPage
633 redef fun tpl_page do return page
634
635 redef fun tpl_title do return "Index"
636
637 redef fun page_url do return "search.html"
638
639 redef fun tpl_content do
640 var tpl = new TplSearchPage("search_all")
641 var section = new TplSection("search")
642 # title
643 tpl.title = "Index"
644 # modules list
645 for mmodule in modules_list do
646 tpl.modules.add mmodule.tpl_link
647 end
648 # classes list
649 for mclass in classes_list do
650 tpl.classes.add mclass.tpl_link
651 end
652 # properties list
653 for mproperty in mprops_list do
654 var m = new Template
655 m.add mproperty.intro.tpl_link
656 m.add " ("
657 m.add mproperty.intro.mclassdef.mclass.tpl_link
658 m.add ")"
659 tpl.props.add m
660 end
661 section.add_child tpl
662 tpl_page.add_section section
663 end
664
665 # Extract mmodule list to display (sorted by name)
666 private fun modules_list: Array[MModule] do
667 var sorted = new Array[MModule]
668 for mmodule in model.mmodule_importation_hierarchy do
669 if mmodule.is_fictive then continue
670 sorted.add mmodule
671 end
672 name_sorter.sort(sorted)
673 return sorted
674 end
675
676 # Extract mclass list to display (sorted by name)
677 private fun classes_list: Array[MClass] do
678 var sorted = new Array[MClass]
679 for mclass in model.mclasses do
680 if not ctx.filter_mclass(mclass) then continue
681 sorted.add mclass
682 end
683 name_sorter.sort(sorted)
684 return sorted
685 end
686
687 # Extract mproperty list to display (sorted by name)
688 private fun mprops_list: Array[MProperty] do
689 var sorted = new Array[MProperty]
690 for mproperty in model.mproperties do
691 if ctx.filter_mproperty(mproperty) then sorted.add mproperty
692 end
693 name_sorter.sort(sorted)
694 return sorted
695 end
696 end
697
698 # A group page
699 # Display a flattened view of the group
700 class NitdocGroup
701 super NitdocPage
702
703 private var mgroup: MGroup
704
705 private var concerns: ConcernsTree is noinit
706 private var intros: Set[MClass] is noinit
707 private var redefs: Set[MClass] is noinit
708
709 init do
710 self.concerns = model.concerns_tree(mgroup.collect_mmodules)
711 self.concerns.sort_with(new MConcernRankSorter)
712 self.intros = mgroup.in_nesting_intro_mclasses(ctx.min_visibility)
713 var redefs = new HashSet[MClass]
714 for rdef in mgroup.in_nesting_redef_mclasses(ctx.min_visibility) do
715 if intros.has(rdef) then continue
716 redefs.add rdef
717 end
718 self.redefs = redefs
719 end
720
721 private var page = new TplPage
722 redef fun tpl_page do return page
723
724 private var sidebar = new TplSidebar
725 redef fun tpl_sidebar do return sidebar
726
727 redef fun tpl_title do return mgroup.nitdoc_name
728
729 redef fun page_url do return mgroup.nitdoc_url
730
731 redef fun tpl_topmenu do
732 var topmenu = super
733 var mproject = mgroup.mproject
734 if not mgroup.is_root then
735 topmenu.add_link new TplLink(mproject.nitdoc_url, mproject.nitdoc_name)
736 end
737 topmenu.add_link new TplLink(page_url, mproject.nitdoc_name)
738 return topmenu
739 end
740
741 # Class list to display in sidebar
742 fun tpl_sidebar_mclasses do
743 var mclasses = new HashSet[MClass]
744 mclasses.add_all intros
745 mclasses.add_all redefs
746 if mclasses.is_empty then return
747 var list = new TplList.with_classes(["list-unstyled", "list-labeled"])
748
749 var sorted = mclasses.to_a
750 name_sorter.sort(sorted)
751 for mclass in sorted do
752 list.add_li tpl_sidebar_item(mclass)
753 end
754 tpl_sidebar.boxes.add new TplSideBox.with_content("All classes", list)
755 end
756
757 private fun tpl_sidebar_item(def: MClass): TplListItem do
758 var classes = def.intro.tpl_css_classes.to_a
759 if intros.has(def) then
760 classes.add "intro"
761 else
762 classes.add "redef"
763 end
764 var lnk = new Template
765 lnk.add new TplLabel.with_classes(classes)
766 lnk.add def.tpl_link
767 return new TplListItem.with_content(lnk)
768 end
769
770 # intro text
771 private fun tpl_intro: TplSection do
772 var section = new TplSection.with_title("top", tpl_title)
773 var article = new TplArticle("intro")
774
775 if mgroup.is_root then
776 section.subtitle = mgroup.mproject.tpl_declaration
777 article.content = mgroup.mproject.tpl_definition
778 else
779 section.subtitle = mgroup.tpl_declaration
780 article.content = mgroup.tpl_definition
781 end
782 section.add_child article
783 return section
784 end
785
786 private fun tpl_concerns(section: TplSection) do
787 if concerns.is_empty then return
788 section.add_child new TplArticle.with_content("concerns", "Concerns", concerns.to_tpl)
789 end
790
791 private fun tpl_groups(parent: TplSection) do
792 var lst = concerns.to_a
793 var section = parent
794 for mentity in lst do
795 if mentity isa MProject then
796 section.add_child new TplSection(mentity.nitdoc_id)
797 else if mentity isa MGroup then
798 section.add_child new TplSection(mentity.nitdoc_id)
799 else if mentity isa MModule then
800 section.add_child tpl_mmodule_article(mentity)
801 end
802 end
803 end
804
805 redef fun tpl_content do
806 tpl_sidebar_mclasses
807 var top = tpl_intro
808 tpl_concerns(top)
809 tpl_groups(top)
810 tpl_page.add_section top
811 end
812
813 private fun sort_by_mclass(mclassdefs: Collection[MClassDef]): Map[MClass, Set[MClassDef]] do
814 var map = new HashMap[MClass, Set[MClassDef]]
815 for mclassdef in mclassdefs do
816 var mclass = mclassdef.mclass
817 if not map.has_key(mclass) then map[mclass] = new HashSet[MClassDef]
818 map[mclass].add mclassdef
819 end
820 return map
821 end
822 end
823
824 # A module page
825 # Display the list of introduced and redefined classes in module
826 class NitdocModule
827 super NitdocPage
828
829 private var mmodule: MModule
830 private var concerns: ConcernsTree is noinit
831 private var mclasses2mdefs: Map[MClass, Set[MClassDef]] is noinit
832 private var mmodules2mclasses: Map[MModule, Set[MClass]] is noinit
833
834
835 init do
836 var mclassdefs = new HashSet[MClassDef]
837 mclassdefs.add_all mmodule.intro_mclassdefs(ctx.min_visibility)
838 mclassdefs.add_all mmodule.redef_mclassdefs(ctx.min_visibility)
839 self.mclasses2mdefs = sort_by_mclass(mclassdefs)
840 self.mmodules2mclasses = group_by_mmodule(mclasses2mdefs.keys)
841 self.concerns = model.concerns_tree(mmodules2mclasses.keys)
842 # rank concerns
843 mmodule.mgroup.mproject.booster_rank = -1000
844 mmodule.mgroup.booster_rank = -1000
845 mmodule.booster_rank = -1000
846 self.concerns.sort_with(new MConcernRankSorter)
847 mmodule.mgroup.mproject.booster_rank = 0
848 mmodule.mgroup.booster_rank = 0
849 mmodule.booster_rank = 0
850 end
851
852 private var page = new TplPage
853 redef fun tpl_page do return page
854
855 private var sidebar = new TplSidebar
856 redef fun tpl_sidebar do return sidebar
857
858 redef fun tpl_title do return mmodule.nitdoc_name
859 redef fun page_url do return mmodule.nitdoc_url
860
861 redef fun tpl_topmenu do
862 var topmenu = super
863 var mproject = mmodule.mgroup.mproject
864 topmenu.add_link new TplLink(mproject.nitdoc_url, mproject.nitdoc_name)
865 topmenu.add_link new TplLink(page_url, mmodule.nitdoc_name)
866 return topmenu
867 end
868
869 # Class list to display in sidebar
870 fun tpl_sidebar_mclasses do
871 var mclasses = new HashSet[MClass]
872 mclasses.add_all mmodule.filter_intro_mclasses(ctx.min_visibility)
873 mclasses.add_all mmodule.filter_redef_mclasses(ctx.min_visibility)
874 if mclasses.is_empty then return
875 var list = new TplList.with_classes(["list-unstyled", "list-labeled"])
876
877 var sorted = mclasses.to_a
878 name_sorter.sort(sorted)
879 for mclass in sorted do
880 list.add_li tpl_sidebar_item(mclass)
881 end
882 tpl_sidebar.boxes.add new TplSideBox.with_content("All classes", list)
883 end
884
885 private fun tpl_sidebar_item(def: MClass): TplListItem do
886 var classes = def.intro.tpl_css_classes.to_a
887 if def.intro_mmodule == mmodule then
888 classes.add "intro"
889 else
890 classes.add "redef"
891 end
892 var lnk = new Template
893 lnk.add new TplLabel.with_classes(classes)
894 lnk.add def.tpl_link
895 return new TplListItem.with_content(lnk)
896 end
897
898 # intro text
899 private fun tpl_intro: TplSection do
900 var section = new TplSection.with_title("top", tpl_title)
901 section.subtitle = mmodule.tpl_declaration
902
903 var article = new TplArticle("intro")
904 var def = mmodule.tpl_definition
905 var location = mmodule.location
906 article.source_link = tpl_showsource(location)
907 article.content = def
908 section.add_child article
909 return section
910 end
911
912 # inheritance section
913 private fun tpl_inheritance(parent: TplSection) do
914 # Extract relevent modules
915 var imports = mmodule.in_importation.greaters
916 if imports.length > 10 then imports = mmodule.in_importation.direct_greaters
917 var clients = mmodule.in_importation.smallers
918 if clients.length > 10 then clients = mmodule.in_importation.direct_smallers
919
920 # Display lists
921 var section = new TplSection.with_title("dependencies", "Dependencies")
922
923 # Graph
924 var mmodules = new HashSet[MModule]
925 mmodules.add_all mmodule.nested_mmodules
926 mmodules.add_all imports
927 if clients.length < 10 then mmodules.add_all clients
928 mmodules.add mmodule
929 var graph = tpl_dot(mmodules)
930 if graph != null then section.add_child graph
931
932 # Imports
933 var lst = new Array[MModule]
934 for dep in imports do
935 if dep.is_fictive then continue
936 if dep == mmodule then continue
937 lst.add(dep)
938 end
939 if not lst.is_empty then
940 name_sorter.sort lst
941 section.add_child tpl_list("imports", "Imports", lst)
942 end
943
944 # Clients
945 lst = new Array[MModule]
946 for dep in clients do
947 if dep.is_fictive then continue
948 if dep == mmodule then continue
949 lst.add(dep)
950 end
951 if not lst.is_empty then
952 name_sorter.sort lst
953 section.add_child tpl_list("clients", "Clients", lst)
954 end
955
956 parent.add_child section
957 end
958
959 private fun tpl_list(id: String, title: String, mmodules: Array[MModule]): TplArticle do
960 var article = new TplArticle.with_title(id, title)
961 var list = new TplList.with_classes(["list-unstyled", "list-definition"])
962 for mmodule in mmodules do list.elts.add mmodule.tpl_list_item
963 article.content = list
964 return article
965 end
966
967 private fun tpl_concerns(parent: TplSection) do
968 if concerns.is_empty then return
969 parent.add_child new TplArticle.with_content("concerns", "Concerns", concerns.to_tpl)
970 end
971
972 private fun tpl_mclasses(parent: TplSection) do
973 for mentity in concerns do
974 if mentity isa MProject then
975 parent.add_child new TplSection(mentity.nitdoc_id)
976 else if mentity isa MGroup then
977 parent.add_child new TplSection(mentity.nitdoc_id)
978 else if mentity isa MModule then
979 var section = new TplSection(mentity.nitdoc_id)
980 var title = new Template
981 if mentity == mmodule then
982 title.add "in "
983 section.summary_title = "in {mentity.nitdoc_name}"
984 else
985 title.add "from "
986 section.summary_title = "from {mentity.nitdoc_name}"
987 end
988 title.add mentity.tpl_namespace
989 section.title = title
990
991 var mclasses = mmodules2mclasses[mentity].to_a
992 name_sorter.sort(mclasses)
993 for mclass in mclasses do
994 section.add_child tpl_mclass_article(mclass, mclasses2mdefs[mclass].to_a)
995 end
996 parent.add_child section
997 end
998 end
999 end
1000
1001 private fun group_by_mmodule(mclasses: Collection[MClass]): Map[MModule, Set[MClass]] do
1002 var res = new HashMap[MModule, Set[MClass]]
1003 for mclass in mclasses do
1004 var mmodule = mclass.intro_mmodule
1005 if not res.has_key(mmodule) then
1006 res[mmodule] = new HashSet[MClass]
1007 end
1008 res[mmodule].add(mclass)
1009 end
1010 return res
1011 end
1012
1013 redef fun tpl_content do
1014 tpl_sidebar_mclasses
1015 var top = tpl_intro
1016 tpl_inheritance(top)
1017 tpl_concerns(top)
1018 tpl_mclasses(top)
1019 tpl_page.add_section top
1020 end
1021
1022 # Genrate dot hierarchy for class inheritance
1023 fun tpl_dot(mmodules: Collection[MModule]): nullable TplArticle do
1024 var poset = new POSet[MModule]
1025 for mmodule in mmodules do
1026 if mmodule.is_fictive then continue
1027 poset.add_node mmodule
1028 for omodule in mmodules do
1029 if mmodule.is_fictive then continue
1030 poset.add_node mmodule
1031 if mmodule.in_importation < omodule then
1032 poset.add_edge(mmodule, omodule)
1033 end
1034 end
1035 end
1036 # build graph
1037 var op = new RopeBuffer
1038 var name = "dep_module_{mmodule.nitdoc_id}"
1039 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")
1040 for mmodule in poset do
1041 if mmodule == self.mmodule then
1042 op.append("\"{mmodule.name.escape_to_dot}\"[shape=box,margin=0.03];\n")
1043 else
1044 op.append("\"{mmodule.name.escape_to_dot}\"[URL=\"{mmodule.nitdoc_url.escape_to_dot}\"];\n")
1045 end
1046 for omodule in poset[mmodule].direct_greaters do
1047 op.append("\"{mmodule.name.escape_to_dot}\"->\"{omodule.name.escape_to_dot}\";\n")
1048 end
1049 end
1050 op.append("\}\n")
1051 return tpl_graph(op, name, null)
1052 end
1053
1054 private fun sort_by_mclass(mclassdefs: Collection[MClassDef]): Map[MClass, Set[MClassDef]] do
1055 var map = new HashMap[MClass, Set[MClassDef]]
1056 for mclassdef in mclassdefs do
1057 var mclass = mclassdef.mclass
1058 if not map.has_key(mclass) then map[mclass] = new HashSet[MClassDef]
1059 map[mclass].add mclassdef
1060 end
1061 return map
1062 end
1063 end
1064
1065 # A class page
1066 # Display a list properties defined or redefined for this class
1067 class NitdocClass
1068 super NitdocPage
1069
1070 private var mclass: MClass
1071 private var concerns: ConcernsTree is noinit
1072 private var mprops2mdefs: Map[MProperty, Set[MPropDef]] is noinit
1073 private var mmodules2mprops: Map[MModule, Set[MProperty]] is noinit
1074
1075 init do
1076 var mpropdefs = new HashSet[MPropDef]
1077 mpropdefs.add_all mclass.intro_mpropdefs(ctx.min_visibility)
1078 mpropdefs.add_all mclass.redef_mpropdefs(ctx.min_visibility)
1079 self.mprops2mdefs = sort_by_mproperty(mpropdefs)
1080 self.mmodules2mprops = sort_by_mmodule(mprops2mdefs.keys)
1081 self.concerns = model.concerns_tree(mmodules2mprops.keys)
1082 self.concerns.sort_with(new MConcernRankSorter)
1083 end
1084
1085 private var page = new TplPage
1086 redef fun tpl_page do return page
1087
1088 private var sidebar = new TplSidebar
1089 redef fun tpl_sidebar do return sidebar
1090
1091 redef fun tpl_title do return "{mclass.nitdoc_name}{mclass.tpl_signature.write_to_string}"
1092 redef fun page_url do return mclass.nitdoc_url
1093
1094 redef fun tpl_topmenu do
1095 var topmenu = super
1096 var mproject = mclass.intro_mmodule.mgroup.mproject
1097 topmenu.add_link new TplLink("{mproject.nitdoc_url}", "{mproject.nitdoc_name}")
1098 topmenu.add_link new TplLink(page_url, mclass.nitdoc_name)
1099 return topmenu
1100 end
1101
1102 # Property list to display in sidebar
1103 fun tpl_sidebar_properties do
1104 var by_kind = new PropertiesByKind.with_elements(mclass_inherited_mprops)
1105 var summary = new TplList.with_classes(["list-unstyled"])
1106
1107 by_kind.sort_groups(name_sorter)
1108 for g in by_kind.groups do tpl_sidebar_list(g, summary)
1109 tpl_sidebar.boxes.add new TplSideBox.with_content("All properties", summary)
1110 end
1111
1112 private fun tpl_sidebar_list(mprops: PropertyGroup[MProperty], summary: TplList) do
1113 if mprops.is_empty then return
1114 var entry = new TplListItem.with_content(mprops.title)
1115 var list = new TplList.with_classes(["list-unstyled", "list-labeled"])
1116 for mprop in mprops do
1117 list.add_li tpl_sidebar_item(mprop)
1118 end
1119 entry.append list
1120 summary.elts.add entry
1121 end
1122
1123 private fun tpl_sidebar_item(mprop: MProperty): TplListItem do
1124 var classes = mprop.intro.tpl_css_classes.to_a
1125 if not mprops2mdefs.has_key(mprop) then
1126 classes.add "inherit"
1127 var cls_url = mprop.intro.mclassdef.mclass.nitdoc_url
1128 var def_url = "{cls_url}#{mprop.nitdoc_id}"
1129 var lnk = new TplLink(def_url, mprop.name)
1130 var mdoc = mprop.intro.mdoc_or_fallback
1131 if mdoc != null then lnk.title = mdoc.short_comment
1132 var item = new Template
1133 item.add new TplLabel.with_classes(classes)
1134 item.add lnk
1135 return new TplListItem.with_content(item)
1136 end
1137 var defs = mprops2mdefs[mprop]
1138 if defs.has(mprop.intro) then
1139 classes.add "intro"
1140 else
1141 classes.add "redef"
1142 end
1143 var lnk = new Template
1144 lnk.add new TplLabel.with_classes(classes)
1145 lnk.add mprop.tpl_anchor
1146 return new TplListItem.with_content(lnk)
1147 end
1148
1149 private fun tpl_intro: TplSection do
1150 var section = new TplSection.with_title("top", tpl_title)
1151 section.subtitle = mclass.intro.tpl_declaration
1152 var article = new TplArticle("comment")
1153 var mdoc = mclass.mdoc_or_fallback
1154 if mdoc != null then
1155 article.content = mdoc.tpl_comment
1156 end
1157 section.add_child article
1158 return section
1159 end
1160
1161 private fun tpl_concerns(parent: TplSection) do
1162 # intro title
1163 var section = new TplSection.with_title("intro", "Introduction")
1164 section.summary_title = "Introduction"
1165 section.add_child tpl_mclassdef_article(mclass.intro)
1166 parent.add_child section
1167 # concerns
1168 if concerns.is_empty then return
1169 parent.add_child new TplArticle.with_content("concerns", "Concerns", concerns.to_tpl)
1170 end
1171
1172 private fun tpl_inheritance(parent: TplSection) do
1173 # parents
1174 var hparents = new HashSet[MClass]
1175 for c in mclass.in_hierarchy(mainmodule).direct_greaters do
1176 if ctx.filter_mclass(c) then hparents.add c
1177 end
1178
1179 # ancestors
1180 var hancestors = new HashSet[MClass]
1181 for c in mclass.in_hierarchy(mainmodule).greaters do
1182 if c == mclass then continue
1183 if not ctx.filter_mclass(c) then continue
1184 if hparents.has(c) then continue
1185 hancestors.add c
1186 end
1187
1188 # children
1189 var hchildren = new HashSet[MClass]
1190 for c in mclass.in_hierarchy(mainmodule).direct_smallers do
1191 if ctx.filter_mclass(c) then hchildren.add c
1192 end
1193
1194 # descendants
1195 var hdescendants = new HashSet[MClass]
1196 for c in mclass.in_hierarchy(mainmodule).smallers do
1197 if c == mclass then continue
1198 if not ctx.filter_mclass(c) then continue
1199 if hchildren.has(c) then continue
1200 hdescendants.add c
1201 end
1202
1203 # Display lists
1204 var section = new TplSection.with_title("inheritance", "Inheritance")
1205
1206 # Graph
1207 var mclasses = new HashSet[MClass]
1208 mclasses.add_all hancestors
1209 mclasses.add_all hparents
1210 mclasses.add_all hchildren
1211 mclasses.add_all hdescendants
1212 mclasses.add mclass
1213 var graph = tpl_dot(mclasses)
1214 if graph != null then section.add_child graph
1215
1216 # parents
1217 if not hparents.is_empty then
1218 var lst = hparents.to_a
1219 name_sorter.sort lst
1220 section.add_child tpl_list("parents", "Parents", lst)
1221 end
1222
1223 # ancestors
1224 if not hancestors.is_empty then
1225 var lst = hancestors.to_a
1226 name_sorter.sort lst
1227 section.add_child tpl_list("ancestors", "Ancestors", lst)
1228 end
1229
1230 # children
1231 if not hchildren.is_empty then
1232 var lst = hchildren.to_a
1233 name_sorter.sort lst
1234 section.add_child tpl_list("children", "Children", lst)
1235 end
1236
1237 # descendants
1238 if not hdescendants.is_empty then
1239 var lst = hdescendants.to_a
1240 name_sorter.sort lst
1241 section.add_child tpl_list("descendants", "Descendants", lst)
1242 end
1243
1244 parent.add_child section
1245 end
1246
1247 private fun tpl_list(id: String, title: String, elts: Array[MClass]): TplArticle do
1248 var article = new TplArticle.with_title(id, title)
1249 if elts.length > 20 then
1250 var tpl = new Template
1251 for e in elts do
1252 tpl.add e.tpl_link
1253 if e != elts.last then tpl.add ", "
1254 end
1255 article.content = tpl
1256 else
1257 var list = new TplList.with_classes(["list-unstyled", "list-definition"])
1258 for elt in elts do list.elts.add elt.tpl_list_item
1259 article.content = list
1260 end
1261 return article
1262 end
1263
1264 private fun tpl_properties(parent: TplSection) do
1265 var lst = concerns.to_a
1266 for mentity in lst do
1267 if mentity isa MProject then
1268 parent.add_child new TplSection(mentity.nitdoc_id)
1269 else if mentity isa MGroup then
1270 parent.add_child new TplSection(mentity.nitdoc_id)
1271 else if mentity isa MModule then
1272 var section = new TplSection(mentity.nitdoc_id)
1273 var title = new Template
1274 title.add "in "
1275 title.add mentity.tpl_namespace
1276 section.title = title
1277 section.summary_title = "in {mentity.nitdoc_name}"
1278
1279 # properties
1280 var mprops = mmodules2mprops[mentity]
1281 var by_kind = new PropertiesByKind.with_elements(mprops)
1282
1283 for g in by_kind.groups do
1284 for article in tpl_mproperty_articles(g) do
1285 section.add_child article
1286 end
1287 end
1288 parent.add_child section
1289 end
1290 end
1291 end
1292
1293 private fun tpl_mproperty_articles(elts: Collection[MProperty]):
1294 Sequence[TplArticle] do
1295 var articles = new List[TplArticle]
1296 for elt in elts do
1297 var local_defs = mprops2mdefs[elt]
1298 # var all_defs = elt.mpropdefs
1299 var all_defs = new HashSet[MPropDef]
1300 for local_def in local_defs do
1301 all_defs.add local_def
1302 var mpropdef = local_def
1303 while not mpropdef.is_intro do
1304 mpropdef = mpropdef.lookup_next_definition(mainmodule, mpropdef.mclassdef.bound_mtype)
1305 all_defs.add mpropdef
1306 end
1307 end
1308 var loc_lin = local_defs.to_a
1309 mainmodule.linearize_mpropdefs(loc_lin)
1310 var all_lin = all_defs.to_a
1311 mainmodule.linearize_mpropdefs(all_lin)
1312 articles.add tpl_mprop_article(loc_lin.first, loc_lin, all_lin)
1313 end
1314 return articles
1315 end
1316
1317 redef fun tpl_content do
1318 tpl_sidebar_properties
1319 var top = tpl_intro
1320 tpl_inheritance(top)
1321 tpl_concerns(top)
1322 tpl_properties(top)
1323 tpl_page.add_section top
1324 end
1325
1326 private fun sort_by_mproperty(mpropdefs: Collection[MPropDef]): Map[MProperty, Set[MPropDef]] do
1327 var map = new HashMap[MProperty, Set[MPropDef]]
1328 for mpropdef in mpropdefs do
1329 var mproperty = mpropdef.mproperty
1330 if not map.has_key(mproperty) then map[mproperty] = new HashSet[MPropDef]
1331 map[mproperty].add mpropdef
1332 end
1333 return map
1334 end
1335
1336 private fun sort_by_mmodule(mprops: Collection[MProperty]): Map[MModule, Set[MProperty]] do
1337 var map = new HashMap[MModule, Set[MProperty]]
1338 for mprop in mprops do
1339 var mpropdefs = mprops2mdefs[mprop].to_a
1340 mainmodule.linearize_mpropdefs(mpropdefs)
1341 var mmodule = mpropdefs.first.mclassdef.mmodule
1342 if not map.has_key(mmodule) then map[mmodule] = new HashSet[MProperty]
1343 map[mmodule].add mprop
1344 end
1345 return map
1346 end
1347
1348 private fun mclass_inherited_mprops: Set[MProperty] do
1349 var res = new HashSet[MProperty]
1350 var local = mclass.local_mproperties(ctx.min_visibility)
1351 for mprop in mclass.inherited_mproperties(mainmodule, ctx.min_visibility) do
1352 if local.has(mprop) then continue
1353 #if mprop isa MMethod and mprop.is_init then continue
1354 if mprop.intro.mclassdef.mclass.name == "Object" and
1355 (mprop.visibility == protected_visibility or
1356 mprop.intro.mclassdef.mmodule.name != "kernel") then continue
1357 res.add mprop
1358 end
1359 res.add_all local
1360 return res
1361 end
1362
1363 private fun collect_mmodules(mprops: Collection[MProperty]): Set[MModule] do
1364 var res = new HashSet[MModule]
1365 for mprop in mprops do
1366 if mprops2mdefs.has_key(mprop) then
1367 for mpropdef in mprops2mdefs[mprop] do res.add mpropdef.mclassdef.mmodule
1368 end
1369 end
1370 return res
1371 end
1372
1373 # Generate dot hierarchy for classes
1374 fun tpl_dot(mclasses: Collection[MClass]): nullable TplArticle do
1375 var poset = new POSet[MClass]
1376
1377 for mclass in mclasses do
1378 poset.add_node mclass
1379 for oclass in mclasses do
1380 if mclass == oclass then continue
1381 poset.add_node oclass
1382 if mclass.in_hierarchy(mainmodule) < oclass then
1383 poset.add_edge(mclass, oclass)
1384 end
1385 end
1386 end
1387
1388 var op = new RopeBuffer
1389 var name = "dep_class_{mclass.nitdoc_id}"
1390 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")
1391 var classes = poset.to_a
1392 var todo = new Array[MClass]
1393 var done = new HashSet[MClass]
1394 mainmodule.linearize_mclasses(classes)
1395 if not classes.is_empty then todo.add classes.first
1396 while not todo.is_empty do
1397 var c = todo.shift
1398 if done.has(c) then continue
1399 done.add c
1400 if c == mclass then
1401 op.append("\"{c.name.escape_to_dot}\"[shape=box,margin=0.03];\n")
1402 else
1403 op.append("\"{c.name.escape_to_dot}\"[URL=\"{c.nitdoc_url.escape_to_dot}\"];\n")
1404 end
1405 var smallers = poset[c].direct_smallers
1406 if smallers.length < 10 then
1407 for c2 in smallers do
1408 op.append("\"{c2.name.escape_to_dot}\"->\"{c.name.escape_to_dot}\";\n")
1409 end
1410 todo.add_all smallers
1411 else
1412 op.append("\"...\"->\"{c.name.escape_to_dot}\";\n")
1413 end
1414 end
1415 op.append("\}\n")
1416 return tpl_graph(op, name, null)
1417 end
1418 end
1419
1420 # Groups properties by kind.
1421 private class PropertiesByKind
1422 # The virtual types.
1423 var virtual_types = new PropertyGroup[MVirtualTypeProp]("Virtual types")
1424
1425 # The constructors.
1426 var constructors = new PropertyGroup[MMethod]("Contructors")
1427
1428 # The attributes.
1429 var attributes = new PropertyGroup[MAttribute]("Attributes")
1430
1431 # The methods.
1432 var methods = new PropertyGroup[MMethod]("Methods")
1433
1434 # The inner classes.
1435 var inner_classes = new PropertyGroup[MInnerClass]("Inner classes")
1436
1437 # All the groups.
1438 #
1439 # Sorted in the order they are displayed to the user.
1440 var groups: SequenceRead[PropertyGroup[MProperty]] = [
1441 virtual_types,
1442 constructors,
1443 attributes,
1444 methods,
1445 inner_classes: PropertyGroup[MProperty]]
1446
1447 # Add each the specified property to the appropriate list.
1448 init with_elements(properties: Collection[MProperty]) do add_all(properties)
1449
1450 # Add the specified property to the appropriate list.
1451 fun add(property: MProperty) do
1452 if property isa MMethod then
1453 if property.is_init then
1454 constructors.add property
1455 else
1456 methods.add property
1457 end
1458 else if property isa MVirtualTypeProp then
1459 virtual_types.add property
1460 else if property isa MAttribute then
1461 attributes.add property
1462 else if property isa MInnerClass then
1463 inner_classes.add property
1464 else
1465 abort
1466 end
1467 end
1468
1469 # Add each the specified property to the appropriate list.
1470 fun add_all(properties: Collection[MProperty]) do
1471 for p in properties do add(p)
1472 end
1473
1474 # Sort each group with the specified comparator.
1475 fun sort_groups(comparator: Comparator) do
1476 for g in groups do comparator.sort(g)
1477 end
1478 end
1479
1480 # A Group of properties of the same kind.
1481 private class PropertyGroup[E: MProperty]
1482 super Array[E]
1483
1484 # The title of the group, as displayed to the user.
1485 var title: String
1486 end
1487
1488 # A MProperty page
1489 class NitdocProperty
1490 super NitdocPage
1491
1492 private var mproperty: MProperty
1493 private var concerns: ConcernsTree is noinit
1494 private var mmodules2mdefs: Map[MModule, Set[MPropDef]] is noinit
1495
1496 init do
1497 self.mproperty = mproperty
1498 self.mmodules2mdefs = sort_by_mmodule(collect_mpropdefs)
1499 self.concerns = model.concerns_tree(mmodules2mdefs.keys)
1500 self.concerns.sort_with(new MConcernRankSorter)
1501 end
1502
1503 private fun collect_mpropdefs: Set[MPropDef] do
1504 var res = new HashSet[MPropDef]
1505 for mpropdef in mproperty.mpropdefs do
1506 if not mpropdef.is_intro then res.add mpropdef
1507 end
1508 return res
1509 end
1510
1511 private var page = new TplPage
1512 redef fun tpl_page do return page
1513
1514 private var sidebar = new TplSidebar
1515 redef fun tpl_sidebar do return sidebar
1516
1517 redef fun tpl_title do
1518 return "{mproperty.nitdoc_name}{mproperty.tpl_signature.write_to_string}"
1519 end
1520
1521 redef fun page_url do return mproperty.nitdoc_url
1522
1523 redef fun tpl_topmenu do
1524 var topmenu = super
1525 var mmodule = mproperty.intro_mclassdef.mmodule
1526 var mproject = mmodule.mgroup.mproject
1527 var mclass = mproperty.intro_mclassdef.mclass
1528 topmenu.add_link new TplLink("{mproject.nitdoc_url}", "{mproject.nitdoc_name}")
1529 topmenu.add_link new TplLink("{mclass.nitdoc_url}", "{mclass.nitdoc_name}")
1530 topmenu.add_link new TplLink(page_url, mproperty.nitdoc_name)
1531 return topmenu
1532 end
1533
1534 private fun tpl_intro: TplSection do
1535 var title = new Template
1536 title.add mproperty.nitdoc_name
1537 title.add mproperty.intro.tpl_signature
1538 var section = new TplSection.with_title("top", title)
1539 section.subtitle = mproperty.tpl_namespace
1540 section.summary_title = mproperty.nitdoc_name
1541 return section
1542 end
1543
1544 private fun tpl_properties(parent: TplSection) do
1545 # intro title
1546 var ns = mproperty.intro.mclassdef.mmodule.tpl_namespace
1547 var section = new TplSection("intro")
1548 var title = new Template
1549 title.add "Introduction in "
1550 title.add ns
1551 section.title = title
1552 section.summary_title = "Introduction"
1553 section.add_child tpl_mpropdef_article(mproperty.intro)
1554 parent.add_child section
1555
1556 # concerns
1557 if concerns.is_empty then return
1558 parent.add_child new TplArticle.with_content("Concerns", "Concerns", concerns.to_tpl)
1559
1560 # redef list
1561 var lst = concerns.to_a
1562 for mentity in lst do
1563 if mentity isa MProject then
1564 parent.add_child new TplSection(mentity.nitdoc_id)
1565 else if mentity isa MGroup then
1566 parent.add_child new TplSection(mentity.nitdoc_id)
1567 else if mentity isa MModule then
1568 var ssection = new TplSection(mentity.nitdoc_id)
1569 title = new Template
1570 title.add "in "
1571 title.add mentity.tpl_namespace
1572 ssection.title = title
1573 ssection.summary_title = "in {mentity.nitdoc_name}"
1574
1575 # properties
1576 var mpropdefs = mmodules2mdefs[mentity].to_a
1577 name_sorter.sort(mpropdefs)
1578 for mpropdef in mpropdefs do
1579 ssection.add_child tpl_mpropdef_article(mpropdef)
1580 end
1581 parent.add_child ssection
1582 end
1583 end
1584 end
1585
1586 redef fun tpl_content do
1587 var top = tpl_intro
1588 tpl_properties(top)
1589 tpl_page.add_section top
1590 end
1591
1592 private fun sort_by_mmodule(mpropdefs: Collection[MPropDef]): Map[MModule, Set[MPropDef]] do
1593 var map = new HashMap[MModule, Set[MPropDef]]
1594 for mpropdef in mpropdefs do
1595 var mmodule = mpropdef.mclassdef.mmodule
1596 if not map.has_key(mmodule) then map[mmodule] = new HashSet[MPropDef]
1597 map[mmodule].add mpropdef
1598 end
1599 return map
1600 end
1601 end
1602