ni_nitdoc: Fixed quicksearch query with special characters
[nit.git] / src / ni_nitdoc.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 # Documentation generator for the nit language.
16 # Generate API documentation in HTML format from nit source code.
17 module ni_nitdoc
18
19 import model_utils
20 import modelize_property
21 import markdown
22
23 # The NitdocContext contains all the knowledge used for doc generation
24 class NitdocContext
25
26 private var toolcontext = new ToolContext
27 private var model: Model
28 private var mbuilder: ModelBuilder
29 private var mainmodule: MModule
30 private var class_hierarchy: POSet[MClass]
31 private var arguments: Array[String]
32 private var output_dir: nullable String
33 private var dot_dir: nullable String
34 private var share_dir: nullable String
35 private var source: nullable String
36 private var min_visibility: MVisibility
37
38 private var opt_dir = new OptionString("Directory where doc is generated", "-d", "--dir")
39 private var opt_source = new OptionString("What link for source (%f for filename, %l for first line, %L for last line)", "--source")
40 private var opt_sharedir = new OptionString("Directory containing the nitdoc files", "--sharedir")
41 private var opt_nodot = new OptionBool("Do not generate graphes with graphiviz", "--no-dot")
42 private var opt_private: OptionBool = new OptionBool("Generate the private API", "--private")
43
44 private var opt_custom_title: OptionString = new OptionString("Title displayed in the top of the Overview page and as suffix of all page names", "--custom-title")
45 private var opt_custom_menu_items: OptionString = new OptionString("Items displayed in menu before the 'Overview' item (Each item must be enclosed in 'li' tags)", "--custom-menu-items")
46 private var opt_custom_overview_text: OptionString = new OptionString("Text displayed as introduction of Overview page before the modules list", "--custom-overview-text")
47 private var opt_custom_footer_text: OptionString = new OptionString("Text displayed as footer of all pages", "--custom-footer-text")
48
49 private var opt_github_base: OptionString = new OptionString("The branch (or git ref) edited commits will be pulled into (ex: octocat:master)", "--github-base")
50 private var opt_github_head: OptionString = new OptionString("The reference branch name used to create pull requests (ex: master)", "--github-head")
51
52 init do
53 toolcontext.option_context.add_option(opt_dir)
54 toolcontext.option_context.add_option(opt_source)
55 toolcontext.option_context.add_option(opt_sharedir)
56 toolcontext.option_context.add_option(opt_nodot)
57 toolcontext.option_context.add_option(opt_private)
58 toolcontext.option_context.add_option(opt_custom_title)
59 toolcontext.option_context.add_option(opt_custom_footer_text)
60 toolcontext.option_context.add_option(opt_custom_overview_text)
61 toolcontext.option_context.add_option(opt_custom_menu_items)
62 toolcontext.option_context.add_option(opt_github_base)
63 toolcontext.option_context.add_option(opt_github_head)
64 toolcontext.process_options
65 self.arguments = toolcontext.option_context.rest
66
67 if arguments.length < 1 then
68 print "usage: nitdoc [options] file..."
69 toolcontext.option_context.usage
70 exit(1)
71 end
72
73 model = new Model
74 mbuilder = new ModelBuilder(model, toolcontext)
75 # Here we load an process all modules passed on the command line
76 var mmodules = mbuilder.parse(arguments)
77 if mmodules.is_empty then return
78 mbuilder.run_phases
79
80 if mmodules.length == 1 then
81 mainmodule = mmodules.first
82 else
83 # We need a main module, so we build it by importing all modules
84 mainmodule = new MModule(model, null, "<main>", new Location(null, 0, 0, 0, 0))
85 mainmodule.set_imported_mmodules(mmodules)
86 end
87 self.class_hierarchy = mainmodule.flatten_mclass_hierarchy
88 self.process_options
89 end
90
91 private fun process_options do
92 if not opt_dir.value is null then
93 output_dir = opt_dir.value
94 else
95 output_dir = "doc"
96 end
97 if not opt_sharedir.value is null then
98 share_dir = opt_sharedir.value
99 else
100 var dir = "NIT_DIR".environ
101 if dir.is_empty then
102 dir = "{sys.program_name.dirname}/../share/ni_nitdoc"
103 else
104 dir = "{dir}/share/ni_nitdoc"
105 end
106 share_dir = dir
107 if share_dir is null then
108 print "Error: Cannot locate nitdoc share files. Uses --sharedir or envvar NIT_DIR"
109 abort
110 end
111 dir = "{share_dir.to_s}/scripts/js-facilities.js"
112 if share_dir is null then
113 print "Error: Invalid nitdoc share files. Check --sharedir or envvar NIT_DIR"
114 abort
115 end
116
117 if opt_private.value then
118 min_visibility = none_visibility
119 else
120 min_visibility = protected_visibility
121 end
122 end
123 source = opt_source.value
124 end
125
126 fun generate_nitdoc do
127 # Create destination dir if it's necessary
128 if not output_dir.file_exists then output_dir.mkdir
129 sys.system("cp -r {share_dir.to_s}/* {output_dir.to_s}/")
130 self.dot_dir = null
131 if not opt_nodot.value then self.dot_dir = output_dir.to_s
132 overview
133 search
134 modules
135 classes
136 quicksearch_list
137 end
138
139 private fun overview do
140 var overviewpage = new NitdocOverview(self, dot_dir)
141 overviewpage.save("{output_dir.to_s}/index.html")
142 end
143
144 private fun search do
145 var searchpage = new NitdocSearch(self)
146 searchpage.save("{output_dir.to_s}/search.html")
147 end
148
149 private fun modules do
150 for mmodule in model.mmodules do
151 if mmodule.name == "<main>" then continue
152 var modulepage = new NitdocModule(mmodule, self, dot_dir)
153 modulepage.save("{output_dir.to_s}/{mmodule.url}")
154 end
155 end
156
157 private fun classes do
158 for mclass in mbuilder.model.mclasses do
159 var classpage = new NitdocClass(mclass, self, dot_dir, source)
160 classpage.save("{output_dir.to_s}/{mclass.url}")
161 end
162 end
163
164 private fun quicksearch_list do
165 var file = new OFStream.open("{output_dir.to_s}/quicksearch-list.js")
166 file.write("var entries = \{ ")
167 for mmodule in model.mmodules do
168 file.write("\"{mmodule.name}\": [")
169 file.write("\{txt: \"{mmodule.full_name}\", url:\"{mmodule.url}\" \},")
170 file.write("],")
171 end
172 for mclass in model.mclasses do
173 if mclass.visibility < min_visibility then continue
174 file.write("\"{mclass.name}\": [")
175 file.write("\{txt: \"{mclass.full_name}\", url:\"{mclass.url}\" \},")
176 file.write("],")
177 end
178 var name2mprops = new HashMap[String, Set[MPropDef]]
179 for mproperty in model.mproperties do
180 if mproperty.visibility < min_visibility then continue
181 if mproperty isa MAttribute then continue
182 if not name2mprops.has_key(mproperty.name) then name2mprops[mproperty.name] = new HashSet[MPropDef]
183 name2mprops[mproperty.name].add_all(mproperty.mpropdefs)
184 end
185 for mproperty, mpropdefs in name2mprops do
186 file.write("\"{mproperty}\": [")
187 for mpropdef in mpropdefs do
188 file.write("\{txt: \"{mpropdef.full_name}\", url:\"{mpropdef.url}\" \},")
189 end
190 file.write("],")
191 end
192 file.write(" \};")
193 file.close
194 end
195
196 end
197
198 # Nitdoc base page
199 abstract class NitdocPage
200
201 var dot_dir: nullable String
202 var source: nullable String
203 var ctx: NitdocContext
204
205 init(ctx: NitdocContext) do
206 self.ctx = ctx
207 end
208
209 protected fun head do
210 append("<meta charset='utf-8'/>")
211 append("<script type='text/javascript' src='scripts/jquery-1.7.1.min.js'></script>")
212 append("<script type='text/javascript' src='scripts/ZeroClipboard.min.js'></script>")
213 append("<script type='text/javascript' src='scripts/Markdown.Converter.js'></script>")
214 append("<script type='text/javascript' src='quicksearch-list.js'></script>")
215 append("<script type='text/javascript' src='scripts/base64.js'></script>")
216 append("<script type='text/javascript' src='scripts/github.js'></script>")
217 append("<script type='text/javascript' src='scripts/js-facilities.js'></script>")
218 append("<link rel='stylesheet' href='styles/main.css' type='text/css' media='screen'/>")
219 append("<link rel='stylesheet' href='styles/github.css' type='text/css' media='screen'/>")
220 var title = ""
221 if ctx.opt_custom_title.value != null then
222 title = " | {ctx.opt_custom_title.value.to_s}"
223 end
224 append("<title>{self.title}{title}</title>")
225 end
226
227 protected fun menu do
228 if ctx.opt_custom_menu_items.value != null then
229 append(ctx.opt_custom_menu_items.value.to_s)
230 end
231 end
232
233 protected fun title: String is abstract
234
235 protected fun header do
236 append("<header>")
237 append("<nav class='main'>")
238 append("<ul>")
239 menu
240 append("</ul>")
241 append("</nav>")
242 append("</header>")
243 end
244
245 protected fun content is abstract
246
247 protected fun footer do
248 if ctx.opt_custom_footer_text.value != null then
249 append("<footer>{ctx.opt_custom_footer_text.value.to_s}</footer>")
250 end
251 end
252
253 # Generate a clickable graphviz image using a dot content
254 protected fun generate_dot(dot: String, name: String, alt: String) do
255 var output_dir = dot_dir
256 if output_dir == null then return
257 var file = new OFStream.open("{output_dir}/{name}.dot")
258 file.write(dot)
259 file.close
260 sys.system("\{ test -f {output_dir}/{name}.png && test -f {output_dir}/{name}.s.dot && diff {output_dir}/{name}.dot {output_dir}/{name}.s.dot >/dev/null 2>&1 ; \} || \{ cp {output_dir}/{name}.dot {output_dir}/{name}.s.dot && dot -Tpng -o{output_dir}/{name}.png -Tcmapx -o{output_dir}/{name}.map {output_dir}/{name}.s.dot ; \}")
261 append("<article class='graph'>")
262 append("<img src='{name}.png' usemap='#{name}' style='margin:auto' alt='{alt}'/>")
263 append("</article>")
264 var fmap = new IFStream.open("{output_dir}/{name}.map")
265 append(fmap.read_all)
266 fmap.close
267 end
268
269 # Add a (source) link for a given location
270 protected fun show_source(l: Location): String
271 do
272 if source == null then
273 return "({l.file.filename.simplify_path})"
274 else
275 # THIS IS JUST UGLY ! (but there is no replace yet)
276 var x = source.split_with("%f")
277 source = x.join(l.file.filename.simplify_path)
278 x = source.split_with("%l")
279 source = x.join(l.line_start.to_s)
280 x = source.split_with("%L")
281 source = x.join(l.line_end.to_s)
282 return " (<a target='_blank' title='Show source' href=\"{source.to_s}\">source</a>)"
283 end
284 end
285
286 # Render the page as a html string
287 protected fun render do
288 append("<!DOCTYPE html>")
289 append("<head>")
290 head
291 append("</head>")
292 append("<body")
293 if not ctx.opt_github_base.value == null and not ctx.opt_github_head.value == null then
294 append(" data-github-base='{ctx.opt_github_base.value.as(not null)}'")
295 append(" data-github-head='{ctx.opt_github_head.value.as(not null)}'")
296 end
297 append(">")
298 header
299 append("<div class='page'>")
300 content
301 append("</div>")
302 footer
303 append("</body>")
304 end
305
306 # Append a string to the page
307 fun append(s: String) do out.write(s)
308
309 # Save html page in the specified file
310 fun save(file: String) do
311 self.out = new OFStream.open(file)
312 render
313 self.out.close
314 end
315 private var out: nullable OFStream
316 end
317
318 # The overview page
319 class NitdocOverview
320 super NitdocPage
321 private var mbuilder: ModelBuilder
322 private var mmodules = new Array[MModule]
323
324 init(ctx: NitdocContext, dot_dir: nullable String) do
325 super(ctx)
326 self.mbuilder = ctx.mbuilder
327 self.dot_dir = dot_dir
328 # get modules
329 var mmodules = new HashSet[MModule]
330 for mmodule in mbuilder.model.mmodules do
331 if mmodule.name == "<main>" then continue
332 var owner = mmodule.public_owner
333 if owner != null then
334 mmodules.add(owner)
335 else
336 mmodules.add(mmodule)
337 end
338 end
339 # sort modules
340 var sorter = new MModuleNameSorter
341 self.mmodules.add_all(mmodules)
342 sorter.sort(self.mmodules)
343 end
344
345 redef fun title do return "Overview"
346
347 redef fun menu do
348 super
349 append("<li class='current'>Overview</li>")
350 append("<li><a href='search.html'>Search</a></li>")
351 end
352
353 redef fun content do
354 var footed = ""
355 if ctx.opt_custom_footer_text.value != null then footed = "footed"
356 append("<div class='content fullpage {footed}'>")
357 var title = "Overview"
358 if ctx.opt_custom_title.value != null then
359 title = ctx.opt_custom_title.value.to_s
360 end
361 append("<h1>{title}</h1>")
362 var text = ""
363 if ctx.opt_custom_overview_text.value != null then
364 text = ctx.opt_custom_overview_text.value.to_s
365 end
366 append("<article class='overview'>{text}</article>")
367 append("<article class='overview'>")
368 # module list
369 append("<h2>Modules</h2>")
370 append("<ul>")
371 for mmodule in mmodules do
372 if mbuilder.mmodule2nmodule.has_key(mmodule) then
373 var amodule = mbuilder.mmodule2nmodule[mmodule]
374 append("<li>")
375 mmodule.html_link(self)
376 append("&nbsp;{amodule.short_comment}</li>")
377 end
378 end
379 append("</ul>")
380 # module graph
381 process_generate_dot
382 append("</article>")
383 append("</div>")
384 end
385
386 private fun process_generate_dot do
387 # build poset with public owners
388 var poset = new POSet[MModule]
389 for mmodule in mmodules do
390 poset.add_node(mmodule)
391 for omodule in mmodules do
392 if mmodule == omodule then continue
393 if mmodule.in_importation < omodule then
394 poset.add_node(omodule)
395 poset.add_edge(mmodule, omodule)
396 end
397 end
398 end
399 # build graph
400 var op = new Buffer
401 op.append("digraph dep \{ 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")
402 for mmodule in poset do
403 op.append("\"{mmodule.name}\"[URL=\"{mmodule.url}\"];\n")
404 for omodule in poset[mmodule].direct_greaters do
405 op.append("\"{mmodule.name}\"->\"{omodule.name}\";\n")
406 end
407 end
408 op.append("\}\n")
409 generate_dot(op.to_s, "dep", "Modules hierarchy")
410 end
411 end
412
413 # The search page
414 class NitdocSearch
415 super NitdocPage
416
417 init(ctx: NitdocContext) do
418 super(ctx)
419 self.dot_dir = null
420 end
421
422 redef fun title do return "Search"
423
424 redef fun menu do
425 super
426 append("<li><a href='index.html'>Overview</a></li>")
427 append("<li class='current'>Search</li>")
428 end
429
430 redef fun content do
431 var footed = ""
432 if ctx.opt_custom_footer_text.value != null then footed = "footed"
433 append("<div class='content fullpage {footed}'>")
434 append("<h1>{title}</h1>")
435 module_column
436 classes_column
437 properties_column
438 append("</div>")
439 end
440
441 # Add to content modules column
442 private fun module_column do
443 var sorted = ctx.mbuilder.model.mmodule_importation_hierarchy.to_a
444 var sorter = new MModuleNameSorter
445 sorter.sort(sorted)
446 append("<article class='modules filterable'>")
447 append("<h2>Modules</h2>")
448 append("<ul>")
449 for mmodule in sorted do
450 append("<li>")
451 mmodule.html_link(self)
452 append("</li>")
453 end
454 append("</ul>")
455 append("</article>")
456 end
457
458 # Add to content classes modules
459 private fun classes_column do
460 var sorted = ctx.mbuilder.model.mclasses
461 var sorter = new MClassNameSorter
462 sorter.sort(sorted)
463 append("<article class='modules filterable'>")
464 append("<h2>Classes</h2>")
465 append("<ul>")
466 for mclass in sorted do
467 if mclass.visibility < ctx.min_visibility then continue
468 append("<li>")
469 mclass.html_link(self)
470 append("</li>")
471 end
472 append("</ul>")
473 append("</article>")
474 end
475
476 # Insert the properties column of fullindex page
477 private fun properties_column do
478 var sorted = ctx.mbuilder.model.mproperties
479 var sorter = new MPropertyNameSorter
480 sorter.sort(sorted)
481 append("<article class='modules filterable'>")
482 append("<h2>Properties</h2>")
483 append("<ul>")
484 for mproperty in sorted do
485 if mproperty.visibility < ctx.min_visibility then continue
486 if mproperty isa MAttribute then continue
487 append("<li>")
488 mproperty.intro.html_link(self)
489 append(" (")
490 mproperty.intro.mclassdef.mclass.html_link(self)
491 append(")</li>")
492 end
493 append("</ul>")
494 append("</article>")
495 end
496
497 end
498
499 # A module page
500 class NitdocModule
501 super NitdocPage
502
503 private var mmodule: MModule
504 private var mbuilder: ModelBuilder
505 private var local_mclasses = new HashSet[MClass]
506 private var intro_mclasses = new HashSet[MClass]
507 private var redef_mclasses = new HashSet[MClass]
508 private var inherited_mclasses = new HashSet[MClass]
509
510 init(mmodule: MModule, ctx: NitdocContext, dot_dir: nullable String) do
511 super(ctx)
512 self.mmodule = mmodule
513 self.mbuilder = ctx.mbuilder
514 self.dot_dir = dot_dir
515 # get local mclasses
516 for m in mmodule.in_nesting.greaters do
517 for mclassdef in m.mclassdefs do
518 if mclassdef.mclass.visibility < ctx.min_visibility then continue
519 if mclassdef.is_intro then
520 intro_mclasses.add(mclassdef.mclass)
521 else
522 redef_mclasses.add(mclassdef.mclass)
523 end
524 local_mclasses.add(mclassdef.mclass)
525 end
526 end
527 # get inherited mclasses
528 for m in mmodule.in_importation.greaters do
529 if m == mmodule then continue
530 for mclassdef in m.mclassdefs do
531 if mclassdef.mclass.visibility < ctx.min_visibility then continue
532 if local_mclasses.has(mclassdef.mclass) then continue
533 inherited_mclasses.add(mclassdef.mclass)
534 end
535 end
536 end
537
538 redef fun title do
539 if mbuilder.mmodule2nmodule.has_key(mmodule) and not mbuilder.mmodule2nmodule[mmodule].short_comment.is_empty then
540 var nmodule = mbuilder.mmodule2nmodule[mmodule]
541 return "{mmodule.html_name} module | {nmodule.short_comment}"
542 else
543 return "{mmodule.html_name} module"
544 end
545 end
546
547 redef fun menu do
548 super
549 append("<li><a href='index.html'>Overview</a></li>")
550 append("<li class='current'>{mmodule.html_name}</li>")
551 append("<li><a href='search.html'>Search</a></li>")
552 end
553
554 redef fun content do
555 append("<div class='menu'>")
556 classes_column
557 importation_column
558 append("</div>")
559 var footed = ""
560 if ctx.opt_custom_footer_text.value != null then footed = "footed"
561 append("<div class='content {footed}'>")
562 module_doc
563 append("</div>")
564 end
565
566 private fun classes_column do
567 var sorter = new MClassNameSorter
568 var sorted = new Array[MClass]
569 sorted.add_all(local_mclasses)
570 sorter.sort(sorted)
571 if not sorted.is_empty then
572 append("<nav class='properties filterable'>")
573 append("<h3>Classes</h3>")
574 append("<h4>Classes</h4>")
575 append("<ul>")
576 for mclass in sorted do mclass.html_sidebar_item(self)
577 append("</ul>")
578 append("</nav>")
579 end
580 end
581
582 private fun importation_column do
583 append("<nav>")
584 append("<h3>Module Hierarchy</h3>")
585 var dependencies = new Array[MModule]
586 for dep in mmodule.in_importation.greaters do
587 if dep == mmodule or dep.public_owner != null then continue
588 dependencies.add(dep)
589 end
590 if mmodule.in_nesting.direct_greaters.length > 0 then
591 append("<h4>Nested Modules</h4>")
592 display_module_list(mmodule.in_nesting.direct_greaters.to_a)
593 end
594 if dependencies.length > 0 then
595 append("<h4>All dependencies</h4>")
596 display_module_list(dependencies)
597 end
598 var clients = new Array[MModule]
599 for dep in mmodule.in_importation.smallers do
600 if dep.name == "<main>" then continue
601 if dep == mmodule or dep.public_owner != null then continue
602 clients.add(dep)
603 end
604 if clients.length > 0 then
605 append("<h4>All clients</h4>")
606 display_module_list(clients)
607 end
608 append("</nav>")
609 end
610
611 private fun display_module_list(list: Array[MModule]) do
612 append("<ul>")
613 var sorter = new MModuleNameSorter
614 sorter.sort(list)
615 for m in list do
616 append("<li>")
617 m.html_link(self)
618 append("</li>")
619 end
620 append("</ul>")
621 end
622
623 private fun module_doc do
624 # title
625 append("<h1>{mmodule.html_name}</h1>")
626 append("<div class='subtitle info'>")
627 mmodule.html_signature(self)
628 append("</div>")
629 # comment
630 mmodule.html_comment(self)
631 # classes
632 var class_sorter = new MClassNameSorter
633 # intro
634 if not intro_mclasses.is_empty then
635 var sorted = new Array[MClass]
636 sorted.add_all(intro_mclasses)
637 class_sorter.sort(sorted)
638 append("<section class='classes'>")
639 append("<h2 class='section-header'>Introduced classes</h2>")
640 for mclass in sorted do mclass.html_full_desc(self)
641 append("</section>")
642 end
643 # redefs
644 var redefs = new Array[MClass]
645 for mclass in redef_mclasses do if not intro_mclasses.has(mclass) then redefs.add(mclass)
646 class_sorter.sort(redefs)
647 if not redefs.is_empty then
648 append("<section class='classes'>")
649 append("<h2 class='section-header'>Refined classes</h2>")
650 for mclass in redefs do mclass.html_full_desc(self)
651 append("</section>")
652 end
653 # inherited properties
654 var inherited = new Array[MClass]
655 inherited.add_all(inherited_mclasses)
656 if inherited_mclasses.length > 0 then
657 var modules2classes = new ArrayMap[MModule, Array[MClass]]
658 for mclass in inherited_mclasses do
659 if not modules2classes.has_key(mclass.intro_mmodule) then modules2classes[mclass.intro_mmodule] = new Array[MClass]
660 modules2classes[mclass.intro_mmodule].add(mclass)
661 end
662 append("<section class='classes'>")
663 append("<h2 class='section-header'>Inherited Classes</h2>")
664 var mmodules = new Array[MModule]
665 mmodules.add_all(modules2classes.keys)
666 var msorter = new MModuleNameSorter
667 msorter.sort(mmodules)
668 for m in mmodules do
669 var mclasses = modules2classes[m]
670 class_sorter.sort(mclasses)
671 append("<p>Defined in ")
672 m.html_link(self)
673 append(": ")
674 for i in [0..mclasses.length[ do
675 var mclass = mclasses[i]
676 mclass.html_link(self)
677 if i <= mclasses.length - 1 then append(", ")
678 end
679 append("</p>")
680 end
681 append("</section>")
682 end
683 end
684
685 private fun process_generate_dot do
686 # build poset with public owners
687 var poset = new POSet[MModule]
688 for mmodule in self.mmodule.in_importation.poset do
689 if mmodule.name == "<main>" then continue
690 if mmodule.public_owner != null then continue
691 if not mmodule.in_importation < self.mmodule and not self.mmodule.in_importation < mmodule and mmodule != self.mmodule then continue
692 poset.add_node(mmodule)
693 for omodule in mmodule.in_importation.poset do
694 if mmodule == omodule then continue
695 if omodule.name == "<main>" then continue
696 if omodule.public_owner != null then continue
697 if mmodule.in_importation < omodule then
698 poset.add_node(omodule)
699 poset.add_edge(mmodule, omodule)
700 end
701 end
702 end
703 # build graph
704 var op = new Buffer
705 var name = "dep_{mmodule.name}"
706 op.append("digraph {name} \{ rankdir=BT; node[shape=none,margin=0,width=0,height=0,fontsize=10]; edge[dir=none,color=gray]; ranksep=0.2; nodesep=0.1;\n")
707 for mmodule in poset do
708 if mmodule == self.mmodule then
709 op.append("\"{mmodule.name}\"[shape=box,margin=0.03];\n")
710 else
711 op.append("\"{mmodule.name}\"[URL=\"{mmodule.url}\"];\n")
712 end
713 for omodule in poset[mmodule].direct_greaters do
714 op.append("\"{mmodule.name}\"->\"{omodule.name}\";\n")
715 end
716 end
717 op.append("\}\n")
718 generate_dot(op.to_s, name, "Dependency graph for module {mmodule.name}")
719 end
720 end
721
722 # A class page
723 class NitdocClass
724 super NitdocPage
725
726 private var mclass: MClass
727 private var vtypes = new HashSet[MVirtualTypeDef]
728 private var consts = new HashSet[MMethodDef]
729 private var meths = new HashSet[MMethodDef]
730 private var inherited = new HashSet[MPropDef]
731
732 init(mclass: MClass, ctx: NitdocContext, dot_dir: nullable String, source: nullable String) do
733 super(ctx)
734 self.mclass = mclass
735 self.dot_dir = dot_dir
736 self.source = source
737 # load properties
738 var locals = new HashSet[MProperty]
739 for mclassdef in mclass.mclassdefs do
740 for mpropdef in mclassdef.mpropdefs do
741 if mpropdef.mproperty.visibility < ctx.min_visibility then continue
742 if mpropdef isa MVirtualTypeDef then vtypes.add(mpropdef)
743 if mpropdef isa MMethodDef then
744 if mpropdef.mproperty.is_init then
745 consts.add(mpropdef)
746 else
747 meths.add(mpropdef)
748 end
749 end
750 locals.add(mpropdef.mproperty)
751 end
752 end
753 # get inherited properties
754 for pclass in mclass.in_hierarchy(ctx.mainmodule).greaters do
755 if pclass == mclass then continue
756 for pclassdef in pclass.mclassdefs do
757 for mprop in pclassdef.intro_mproperties do
758 var mpropdef = mprop.intro
759 if mprop.visibility < ctx.min_visibility then continue
760 if locals.has(mprop) then continue
761 if mpropdef isa MVirtualTypeDef then vtypes.add(mpropdef)
762 if mpropdef isa MMethodDef then
763 if mpropdef.mproperty.is_init then
764 consts.add(mpropdef)
765 else
766 meths.add(mpropdef)
767 end
768 end
769 inherited.add(mpropdef)
770 end
771 end
772 end
773 end
774
775 redef fun title do
776 var nclass = ctx.mbuilder.mclassdef2nclassdef[mclass.intro]
777 if nclass isa AStdClassdef then
778 return "{mclass.html_name} class | {nclass.short_comment}"
779 else
780 return "{mclass.html_name} class"
781 end
782 end
783
784 redef fun menu do
785 super
786 append("<li><a href='index.html'>Overview</a></li>")
787 var public_owner = mclass.public_owner
788 if public_owner is null then
789 append("<li>")
790 mclass.intro_mmodule.html_link(self)
791 append("</li>")
792 else
793 append("<li>")
794 public_owner.html_link(self)
795 append("</li>")
796 end
797 append("<li class='current'>{mclass.html_name}</li>")
798 append("<li><a href='search.html'>Search</a></li>")
799 end
800
801 redef fun content do
802 append("<div class='menu'>")
803 properties_column
804 inheritance_column
805 append("</div>")
806 var footed = ""
807 if ctx.opt_custom_footer_text.value != null then footed = "footed"
808 append("<div class='content {footed}'>")
809 class_doc
810 append("</div>")
811 end
812
813 private fun properties_column do
814 var sorter = new MPropDefNameSorter
815 append("<nav class='properties filterable'>")
816 append("<h3>Properties</h3>")
817 # virtual types
818 if vtypes.length > 0 then
819 var vts = new Array[MVirtualTypeDef]
820 vts.add_all(vtypes)
821 sorter.sort(vts)
822 append("<h4>Virtual Types</h4>")
823 append("<ul>")
824 for mprop in vts do
825 mprop.html_sidebar_item(self)
826 end
827 append("</ul>")
828 end
829 # constructors
830 if consts.length > 0 then
831 var cts = new Array[MMethodDef]
832 cts.add_all(consts)
833 sorter.sort(cts)
834 append("<h4>Constructors</h4>")
835 append("<ul>")
836 for mprop in cts do
837 if mprop.mproperty.name == "init" and mprop.mclassdef.mclass != mclass then continue
838 mprop.html_sidebar_item(self)
839 end
840 append("</ul>")
841 end
842 # methods
843 if meths.length > 0 then
844 var mts = new Array[MMethodDef]
845 mts.add_all(meths)
846 sorter.sort(mts)
847 append("<h4>Methods</h4>")
848 append("<ul>")
849 for mprop in mts do
850 if mclass.name != "Object" and mprop.mproperty.intro_mclassdef.mclass.name == "Object" and mprop.mproperty.visibility <= protected_visibility then continue
851 mprop.html_sidebar_item(self)
852 end
853 append("</ul>")
854 end
855 append("</nav>")
856 end
857
858 private fun inheritance_column do
859 var sorted = new Array[MClass]
860 var sorterp = new MClassNameSorter
861 append("<nav>")
862 append("<h3>Inheritance</h3>")
863 var greaters = mclass.in_hierarchy(ctx.mainmodule).greaters.to_a
864 if greaters.length > 1 then
865 ctx.mainmodule.linearize_mclasses(greaters)
866 append("<h4>Superclasses</h4>")
867 append("<ul>")
868 for sup in greaters do
869 if sup == mclass then continue
870 append("<li>")
871 sup.html_link(self)
872 append("</li>")
873 end
874 append("</ul>")
875 end
876 var smallers = mclass.in_hierarchy(ctx.mainmodule).smallers.to_a
877 var direct_smallers = mclass.in_hierarchy(ctx.mainmodule).direct_smallers.to_a
878 if smallers.length <= 1 then
879 append("<h4>No Known Subclasses</h4>")
880 else if smallers.length <= 100 then
881 ctx.mainmodule.linearize_mclasses(smallers)
882 append("<h4>Subclasses</h4>")
883 append("<ul>")
884 for sub in smallers do
885 if sub == mclass then continue
886 append("<li>")
887 sub.html_link(self)
888 append("</li>")
889 end
890 append("</ul>")
891 else if direct_smallers.length <= 100 then
892 ctx.mainmodule.linearize_mclasses(direct_smallers)
893 append("<h4>Direct Subclasses Only</h4>")
894 append("<ul>")
895 for sub in direct_smallers do
896 if sub == mclass then continue
897 append("<li>")
898 sub.html_link(self)
899 append("</li>")
900 end
901 append("</ul>")
902 else
903 append("<h4>Too much Subclasses to list</h4>")
904 end
905 append("</nav>")
906 end
907
908 private fun class_doc do
909 # title
910 append("<h1>{mclass.html_name}{mclass.html_short_signature}</h1>")
911 append("<div class='subtitle info'>")
912 if mclass.visibility < public_visibility then append("{mclass.visibility.to_s} ")
913 append("{mclass.kind.to_s} ")
914 mclass.html_namespace(self)
915 append("{mclass.html_short_signature}</div>")
916 # comment
917 mclass.html_comment(self)
918 process_generate_dot
919 # concerns
920 var concern2meths = new ArrayMap[MModule, Array[MMethodDef]]
921 var sorted_meths = new Array[MMethodDef]
922 var sorted = new Array[MModule]
923 sorted_meths.add_all(meths)
924 ctx.mainmodule.linearize_mpropdefs(sorted_meths)
925 for meth in meths do
926 if inherited.has(meth) then continue
927 var mmodule = meth.mclassdef.mmodule
928 if not concern2meths.has_key(mmodule) then
929 sorted.add(mmodule)
930 concern2meths[mmodule] = new Array[MMethodDef]
931 end
932 concern2meths[mmodule].add(meth)
933 end
934 var sections = new ArrayMap[MModule, Array[MModule]]
935 for mmodule in concern2meths.keys do
936 var owner = mmodule.public_owner
937 if owner == null then owner = mmodule
938 if not sections.has_key(owner) then sections[owner] = new Array[MModule]
939 if owner != mmodule then sections[owner].add(mmodule)
940 end
941 append("<section class='concerns'>")
942 append("<h2 class='section-header'>Concerns</h2>")
943 append("<ul>")
944 for owner, mmodules in sections do
945 var nowner = ctx.mbuilder.mmodule2nmodule[owner]
946 append("<li>")
947 if nowner.short_comment.is_empty then
948 append("<a href=\"#{owner.anchor}\">{owner.html_name}</a>")
949 else
950 append("<a href=\"#{owner.anchor}\">{owner.html_name}</a>: {nowner.short_comment}")
951 end
952 if not mmodules.is_empty then
953 append("<ul>")
954 for mmodule in mmodules do
955 var nmodule = ctx.mbuilder.mmodule2nmodule[mmodule]
956 if nmodule.short_comment.is_empty then
957 append("<li><a href=\"#{mmodule.anchor}\">{mmodule.html_name}</a></li>")
958 else
959 append("<li><a href=\"#{mmodule.anchor}\">{mmodule.html_name}</a>: {nmodule.short_comment}</li>")
960 end
961 end
962 append("</ul>")
963 end
964 append("</li>")
965 end
966 append("</ul>")
967 append("</section>")
968 # properties
969 var prop_sorter = new MPropDefNameSorter
970 var lmmodule = new List[MModule]
971 var nclass = ctx.mbuilder.mclassdef2nclassdef[mclass.intro]
972 # virtual and formal types
973 var local_vtypes = new Array[MVirtualTypeDef]
974 for vt in vtypes do if not inherited.has(vt) then local_vtypes.add(vt)
975 if local_vtypes.length > 0 or mclass.arity > 0 then
976 append("<section class='types'>")
977 append("<h2>Formal and Virtual Types</h2>")
978 # formal types
979 if mclass.arity > 0 and nclass isa AStdClassdef then
980 for ft, bound in mclass.parameter_types do
981 append("<article id='FT_{ft}'>")
982 append("<h3 class='signature' data-untyped-signature='{ft.to_s}'><span>{ft}: ")
983 bound.html_link(self)
984 append("</span></h3>")
985 append("<div class=\"info\">formal generic type</div>")
986 append("</article>")
987 end
988 end
989 # virtual types
990 prop_sorter.sort(local_vtypes)
991 for prop in local_vtypes do prop.html_full_desc(self, self.mclass)
992 append("</section>")
993 end
994 # constructors
995 var local_consts = new Array[MMethodDef]
996 for const in consts do if not inherited.has(const) then local_consts.add(const)
997 prop_sorter.sort(local_consts)
998 if local_consts.length > 0 then
999 append("<section class='constructors'>")
1000 append("<h2 class='section-header'>Constructors</h2>")
1001 for prop in local_consts do prop.html_full_desc(self, self.mclass)
1002 append("</section>")
1003 end
1004 # methods
1005 if not concern2meths.is_empty then
1006 append("<section class='methods'>")
1007 append("<h2 class='section-header'>Methods</h2>")
1008 for owner, mmodules in sections do
1009 append("<a id=\"{owner.anchor}\"></a>")
1010 if owner != mclass.intro_mmodule and owner != mclass.public_owner then
1011 var nowner = ctx.mbuilder.mmodule2nmodule[owner]
1012 append("<h3 class=\"concern-toplevel\">Methods refined in ")
1013 owner.html_link(self)
1014 append("</h3>")
1015 append("<p class=\"concern-doc\">")
1016 owner.html_link(self)
1017 if not nowner.short_comment.is_empty then
1018 append(": {nowner.short_comment}")
1019 end
1020 append("</p>")
1021 end
1022 if concern2meths.has_key(owner) then
1023 var mmethods = concern2meths[owner]
1024 prop_sorter.sort(mmethods)
1025 for prop in mmethods do prop.html_full_desc(self, self.mclass)
1026 end
1027 for mmodule in mmodules do
1028 append("<a id=\"{mmodule.anchor}\"></a>")
1029 var nmodule = ctx.mbuilder.mmodule2nmodule[mmodule]
1030 if mmodule != mclass.intro_mmodule and mmodule != mclass.public_owner then
1031 append("<p class=\"concern-doc\">")
1032 mmodule.html_link(self)
1033 if not nmodule.short_comment.is_empty then
1034 append(": {nmodule.short_comment}")
1035 end
1036 append("</p>")
1037 end
1038 var mmethods = concern2meths[mmodule]
1039 prop_sorter.sort(mmethods)
1040 for prop in mmethods do prop.html_full_desc(self, self.mclass)
1041 end
1042 end
1043 append("</section>")
1044 end
1045 # inherited properties
1046 if inherited.length > 0 then
1047 var sorted_inherited = new Array[MPropDef]
1048 sorted_inherited.add_all(inherited)
1049 ctx.mainmodule.linearize_mpropdefs(sorted_inherited)
1050 var classes = new ArrayMap[MClass, Array[MPropDef]]
1051 for mmethod in sorted_inherited.reversed do
1052 var mclass = mmethod.mclassdef.mclass
1053 if not classes.has_key(mclass) then classes[mclass] = new Array[MPropDef]
1054 classes[mclass].add(mmethod)
1055 end
1056 append("<section class='inherited'>")
1057 append("<h2 class='section-header'>Inherited Properties</h2>")
1058 for c, mmethods in classes do
1059 prop_sorter.sort(mmethods)
1060 append("<p>Defined in ")
1061 c.html_link(self)
1062 append(": ")
1063 for i in [0..mmethods.length[ do
1064 var mmethod = mmethods[i]
1065 mmethod.html_link(self)
1066 if i <= mmethods.length - 1 then append(", ")
1067 end
1068 append("</p>")
1069 end
1070 append("</section>")
1071 end
1072 end
1073
1074 private fun process_generate_dot do
1075 var pe = ctx.class_hierarchy[mclass]
1076 var cla = new HashSet[MClass]
1077 var sm = new HashSet[MClass]
1078 var sm2 = new HashSet[MClass]
1079 sm.add(mclass)
1080 while cla.length + sm.length < 10 and sm.length > 0 do
1081 cla.add_all(sm)
1082 sm2.clear
1083 for x in sm do
1084 sm2.add_all(pe.poset[x].direct_smallers)
1085 end
1086 var t = sm
1087 sm = sm2
1088 sm2 = t
1089 end
1090 cla.add_all(pe.greaters)
1091
1092 var op = new Buffer
1093 var name = "dep_{mclass.name}"
1094 op.append("digraph {name} \{ rankdir=BT; node[shape=none,margin=0,width=0,height=0,fontsize=10]; edge[dir=none,color=gray]; ranksep=0.2; nodesep=0.1;\n")
1095 for c in cla do
1096 if c == mclass then
1097 op.append("\"{c.name}\"[shape=box,margin=0.03];\n")
1098 else
1099 op.append("\"{c.name}\"[URL=\"{c.url}\"];\n")
1100 end
1101 for c2 in pe.poset[c].direct_greaters do
1102 if not cla.has(c2) then continue
1103 op.append("\"{c.name}\"->\"{c2.name}\";\n")
1104 end
1105 if not pe.poset[c].direct_smallers.is_empty then
1106 var others = true
1107 for c2 in pe.poset[c].direct_smallers do
1108 if cla.has(c2) then others = false
1109 end
1110 if others then
1111 op.append("\"{c.name}...\"[label=\"\"];\n")
1112 op.append("\"{c.name}...\"->\"{c.name}\"[style=dotted];\n")
1113 end
1114 end
1115 end
1116 op.append("\}\n")
1117 generate_dot(op.to_s, name, "Dependency graph for class {mclass.name}")
1118 end
1119 end
1120
1121 #
1122 # Model redefs
1123 #
1124
1125 redef class MModule
1126 # Return the HTML escaped name of the module
1127 private fun html_name: String do return name.html_escape
1128
1129 # URL to nitdoc page
1130 # module_owner_name.html
1131 private fun url: String do
1132 if url_cache == null then
1133 var res = new Buffer
1134 res.append("module_")
1135 var mowner = public_owner
1136 if mowner != null then
1137 res.append("{public_owner.name}_")
1138 end
1139 res.append("{self.name}.html")
1140 url_cache = res.to_s
1141 end
1142 return url_cache.as(not null)
1143 end
1144 private var url_cache: nullable String
1145
1146 # html anchor id for the module in a nitdoc page
1147 # MOD_owner_name
1148 private fun anchor: String do
1149 if anchor_cache == null then
1150 var res = new Buffer
1151 res.append("MOD_")
1152 var mowner = public_owner
1153 if mowner != null then
1154 res.append("{public_owner.name}_")
1155 end
1156 res.append(self.name)
1157 anchor_cache = res.to_s
1158 end
1159 return anchor_cache.as(not null)
1160 end
1161 private var anchor_cache: nullable String
1162
1163 # Return a link (html a tag) to the nitdoc module page
1164 # <a href="url" title="short_comment">html_name</a>
1165 private fun html_link(page: NitdocPage) do
1166 if html_link_cache == null then
1167 var res = new Buffer
1168 if page.ctx.mbuilder.mmodule2nmodule.has_key(self) then
1169 res.append("<a href='{url}' title='{page.ctx.mbuilder.mmodule2nmodule[self].short_comment}'>{html_name}</a>")
1170 else
1171 res.append("<a href='{url}'>{html_name}</a>")
1172 end
1173 html_link_cache = res.to_s
1174 end
1175 page.append(html_link_cache.as(not null))
1176 end
1177 private var html_link_cache: nullable String
1178
1179 # Return the module signature decorated with html
1180 # <span>module html_full_namespace</span>
1181 private fun html_signature(page: NitdocPage) do
1182 page.append("<span>module ")
1183 html_full_namespace(page)
1184 page.append("</span>")
1185 end
1186
1187 # Return the module full namespace decorated with html
1188 # <span>public_owner.html_namespace::html_link</span>
1189 private fun html_full_namespace(page: NitdocPage) do
1190 page.append("<span>")
1191 var mowner = public_owner
1192 if mowner != null then
1193 public_owner.html_namespace(page)
1194 page.append("::")
1195 end
1196 html_link(page)
1197 page.append("</span>")
1198 end
1199
1200 # Return the module full namespace decorated with html
1201 # <span>public_owner.html_namespace</span>
1202 private fun html_namespace(page: NitdocPage) do
1203 page.append("<span>")
1204 var mowner = public_owner
1205 if mowner != null then
1206 public_owner.html_namespace(page)
1207 else
1208 html_link(page)
1209 end
1210 page.append("</span>")
1211 end
1212
1213 # Return the full comment of the module decorated with html
1214 private fun html_comment(page: NitdocPage) do
1215 page.append("<div class='description'>")
1216 if page.ctx.mbuilder.mmodule2nmodule.has_key(self) then
1217 var nmodule = page.ctx.mbuilder.mmodule2nmodule[self]
1218 page.append("<textarea class='baseComment' data-comment-namespace='{full_name}' data-comment-location='{nmodule.doc_location.to_s}'>{nmodule.full_comment}</textarea>")
1219 if nmodule.full_comment == "" then
1220 page.append("<p class='info inheritance'>")
1221 page.append("<span class=\"noComment\">no comment for </span>")
1222 else
1223 page.append("<div class='comment'>{nmodule.full_markdown}</div>")
1224 page.append("<p class='info inheritance'>")
1225 end
1226 page.append("definition in ")
1227 self.html_full_namespace(page)
1228 page.append(" {page.show_source(nmodule.location)}</p>")
1229 end
1230 page.append("</div>")
1231 end
1232
1233 private fun has_mclassdef_for(mclass: MClass): Bool do
1234 for mmodule in self.in_nesting.greaters do
1235 for mclassdef in mmodule.mclassdefs do
1236 if mclassdef.mclass == mclass then return true
1237 end
1238 end
1239 return false
1240 end
1241
1242 private fun has_mclassdef(mclassdef: MClassDef): Bool do
1243 for mmodule in self.in_nesting.greaters do
1244 for oclassdef in mmodule.mclassdefs do
1245 if mclassdef == oclassdef then return true
1246 end
1247 end
1248 return false
1249 end
1250 end
1251
1252 redef class MClass
1253 # Return the HTML escaped name of the module
1254 private fun html_name: String do return name.html_escape
1255
1256 # URL to nitdoc page
1257 # class_owner_name.html
1258 private fun url: String do
1259 return "class_{public_owner}_{name}.html"
1260 end
1261
1262 # html anchor id for the class in a nitdoc page
1263 # MOD_owner_name
1264 private fun anchor: String do
1265 if anchor_cache == null then
1266 anchor_cache = "CLASS_{public_owner.name}_{name}"
1267 end
1268 return anchor_cache.as(not null)
1269 end
1270 private var anchor_cache: nullable String
1271
1272 # Return a link (with signature) to the nitdoc class page
1273 # <a href="url" title="short_comment">html_name(signature)</a>
1274 private fun html_link(page: NitdocPage) do
1275 if html_link_cache == null then
1276 var res = new Buffer
1277 res.append("<a href='{url}'")
1278 if page.ctx.mbuilder.mclassdef2nclassdef.has_key(intro) then
1279 var nclass = page.ctx.mbuilder.mclassdef2nclassdef[intro]
1280 if nclass isa AStdClassdef then
1281 res.append(" title=\"{nclass.short_comment}\"")
1282 end
1283 end
1284 res.append(">{html_name}{html_short_signature}</a>")
1285 html_link_cache = res.to_s
1286 end
1287 page.append(html_link_cache.as(not null))
1288 end
1289 private var html_link_cache: nullable String
1290
1291 # Return a short link (without signature) to the nitdoc class page
1292 # <a href="url" title="short_comment">html_name</a>
1293 private fun html_short_link(page: NitdocPage) do
1294 if html_short_link_cache == null then
1295 var res = new Buffer
1296 res.append("<a href='{url}'")
1297 if page.ctx.mbuilder.mclassdef2nclassdef.has_key(intro) then
1298 var nclass = page.ctx.mbuilder.mclassdef2nclassdef[intro]
1299 if nclass isa AStdClassdef then
1300 res.append(" title=\"{nclass.short_comment}\"")
1301 end
1302 end
1303 res.append(">{html_name}</a>")
1304 html_short_link_cache = res.to_s
1305 end
1306 page.append(html_short_link_cache.as(not null))
1307 end
1308 private var html_short_link_cache: nullable String
1309
1310 # Return a link (with signature) to the class anchor
1311 # <a href="url" title="short_comment">html_name</a>
1312 private fun html_link_anchor(page: NitdocPage) do
1313 if html_link_anchor_cache == null then
1314 var res = new Buffer
1315 res.append("<a href='#{anchor}'")
1316 if page.ctx.mbuilder.mclassdef2nclassdef.has_key(intro) then
1317 var nclass = page.ctx.mbuilder.mclassdef2nclassdef[intro]
1318 if nclass isa AStdClassdef then
1319 res.append(" title=\"{nclass.short_comment}\"")
1320 end
1321 end
1322 res.append(">{html_name}{html_short_signature}</a>")
1323 html_link_anchor_cache = res.to_s
1324 end
1325 page.append(html_link_anchor_cache.as(not null))
1326 end
1327 private var html_link_anchor_cache: nullable String
1328
1329 # Return the generic signature of the class with bounds
1330 # [E: <a>MType</a>, F: <a>MType</a>]
1331 private fun html_signature(page: NitdocPage) do
1332 if arity > 0 then
1333 page.append("[")
1334 for i in [0..intro.parameter_names.length[ do
1335 page.append("{intro.parameter_names[i]}: ")
1336 intro.bound_mtype.arguments[i].html_link(page)
1337 if i < intro.parameter_names.length - 1 then page.append(", ")
1338 end
1339 page.append("]")
1340 end
1341 end
1342
1343 # Return the generic signature of the class without bounds
1344 # [E, F]
1345 private fun html_short_signature: String do
1346 if arity > 0 then
1347 return "[{intro.parameter_names.join(", ")}]"
1348 else
1349 return ""
1350 end
1351 end
1352
1353 # Return the class namespace decorated with html
1354 # <span>intro_module::html_short_link</span>
1355 private fun html_namespace(page: NitdocPage) do
1356 intro_mmodule.html_namespace(page)
1357 page.append("::<span>")
1358 html_short_link(page)
1359 page.append("</span>")
1360 end
1361
1362 # Return a list item for the mclass
1363 # <li>html_link</li>
1364 private fun html_sidebar_item(page: NitdocModule) do
1365 if page.mmodule.in_nesting.greaters.has(intro.mmodule) then
1366 page.append("<li class='intro'>")
1367 page.append("<span title='Introduced'>I</span>")
1368 html_link_anchor(page)
1369 else if page.mmodule.has_mclassdef_for(self) then
1370 page.append("<li class='redef'>")
1371 page.append("<span title='Redefined'>R</span>")
1372 html_link_anchor(page)
1373 else
1374 page.append("<li class='inherit'>")
1375 page.append("<span title='Inherited'>H</span>")
1376 html_link(page)
1377 end
1378 page.append("</li>")
1379 end
1380
1381 private fun html_full_desc(page: NitdocModule) do
1382 var classes = new Array[String]
1383 classes.add(kind.to_s)
1384 if not page.mmodule.in_nesting.greaters.has(intro.mmodule) then classes.add("redef")
1385 classes.add(visibility.to_s)
1386 page.append("<article class='{classes.join(" ")}' id='{anchor}'>")
1387 page.append("<h3 class='signature' data-untyped-signature='{html_name}{html_short_signature}'>")
1388 page.append("<span>")
1389 html_short_link(page)
1390 html_signature(page)
1391 page.append("</span></h3>")
1392 html_info(page)
1393 html_comment(page)
1394 html_redefs(page)
1395 page.append("</article>")
1396 end
1397
1398 private fun html_info(page: NitdocModule) do
1399 page.append("<div class='info'>")
1400 if visibility < public_visibility then page.append("{visibility.to_s} ")
1401 if not page.mmodule.in_nesting.greaters.has(intro.mmodule) then page.append("redef ")
1402 page.append("{kind} ")
1403 html_namespace(page)
1404 page.append("{html_short_signature}</div>")
1405 end
1406
1407 private fun html_comment(page: NitdocPage) do
1408 page.append("<div class='description'>")
1409 if page isa NitdocModule then
1410 page.mmodule.linearize_mclassdefs(mclassdefs)
1411 # comments for each mclassdef contained in current mmodule
1412 for mclassdef in mclassdefs do
1413 if not mclassdef.is_intro and not page.mmodule.mclassdefs.has(mclassdef) then continue
1414 if page.ctx.mbuilder.mclassdef2nclassdef.has_key(mclassdef) then
1415 var nclass = page.ctx.mbuilder.mclassdef2nclassdef[mclassdef]
1416 if nclass isa AStdClassdef then
1417 page.append("<textarea class='baseComment' data-comment-namespace='{mclassdef.mmodule.full_name}::{name}' data-comment-location='{nclass.doc_location.to_s}'>{nclass.full_comment}</textarea>")
1418 if nclass.full_comment == "" then
1419 page.append("<p class='info inheritance'>")
1420 page.append("<span class=\"noComment\">no comment for </span>")
1421 else
1422 page.append("<div class='comment'>{nclass.full_markdown}</div>")
1423 page.append("<p class='info inheritance'>")
1424 end
1425 if mclassdef.is_intro then
1426 page.append("introduction in ")
1427 else
1428 page.append("refinement in ")
1429 end
1430 mclassdef.mmodule.html_full_namespace(page)
1431 page.append(" {page.show_source(nclass.location)}</p>")
1432 end
1433 end
1434 end
1435 else
1436 # comments for intro
1437 if page.ctx.mbuilder.mclassdef2nclassdef.has_key(intro) then
1438 var nclass = page.ctx.mbuilder.mclassdef2nclassdef[intro]
1439 if nclass isa AStdClassdef then
1440 page.append("<textarea class='baseComment' data-comment-namespace='{intro.mmodule.full_name}::{name}' data-comment-location='{nclass.doc_location.to_s}'>{nclass.full_comment}</textarea>")
1441 if nclass.full_comment == "" then
1442 page.append("<p class='info inheritance'>")
1443 page.append("<span class=\"noComment\">no comment for </span>")
1444 else
1445 page.append("<div class='comment'>{nclass.full_markdown}</div>")
1446 page.append("<p class='info inheritance'>")
1447 end
1448 page.append("introduction in ")
1449 intro.mmodule.html_full_namespace(page)
1450 page.append(" {page.show_source(nclass.location)}</p>")
1451 end
1452 end
1453 end
1454 page.append("</div>")
1455 end
1456
1457 private fun html_redefs(page: NitdocModule) do
1458 page.mmodule.linearize_mclassdefs(mclassdefs)
1459 page.append("<div class='refinements'>")
1460 # comments for each mclassdef contained in current mmodule
1461 for mclassdef in mclassdefs do
1462 if not page.mmodule.mclassdefs.has(mclassdef) then continue
1463 if mclassdef.is_intro then continue
1464 for mpropdef in mclassdef.mpropdefs do
1465 if mpropdef isa MAttributeDef then continue
1466 mpropdef.html_full_desc(page, self)
1467 end
1468 end
1469 page.append("</div>")
1470 end
1471 end
1472
1473 redef class MProperty
1474 # Escape name for html output
1475 private fun html_name: String do return name.html_escape
1476
1477 # Return the property namespace decorated with html
1478 # <span>intro_module::intro_class::html_link</span>
1479 private fun html_namespace(page: NitdocPage) do
1480 intro_mclassdef.mclass.html_namespace(page)
1481 page.append(intro_mclassdef.mclass.html_short_signature)
1482 page.append("::<span>")
1483 intro.html_link(page)
1484 page.append("</span>")
1485 end
1486 end
1487
1488 redef class MType
1489 # Link to the type definition in the nitdoc page
1490 private fun html_link(page: NitdocPage) is abstract
1491 end
1492
1493 redef class MClassType
1494 redef fun html_link(page) do mclass.html_link(page)
1495 end
1496
1497 redef class MNullableType
1498 redef fun html_link(page) do
1499 page.append("nullable ")
1500 mtype.html_link(page)
1501 end
1502 end
1503
1504 redef class MGenericType
1505 redef fun html_link(page) do
1506 page.append("<a href='{mclass.url}'>{mclass.html_name}</a>[")
1507 for i in [0..arguments.length[ do
1508 arguments[i].html_link(page)
1509 if i < arguments.length - 1 then page.append(", ")
1510 end
1511 page.append("]")
1512 end
1513 end
1514
1515 redef class MParameterType
1516 redef fun html_link(page) do
1517 var name = mclass.intro.parameter_names[rank]
1518 page.append("<a href='{mclass.url}#FT_{name}' title='formal type'>{name}</a>")
1519 end
1520 end
1521
1522 redef class MVirtualType
1523 redef fun html_link(page) do mproperty.intro.html_link(page)
1524 end
1525
1526 redef class MClassDef
1527 # Return the classdef namespace decorated with html
1528 private fun html_namespace(page: NitdocPage) do
1529 mmodule.html_full_namespace(page)
1530 page.append("::<span>")
1531 mclass.html_link(page)
1532 page.append("</span>")
1533 end
1534 end
1535
1536 redef class MPropDef
1537 # Return the full qualified name of the mpropdef
1538 # module::classdef::name
1539 private fun full_name: String do
1540 return "{mclassdef.mclass.public_owner.name}::{mclassdef.mclass.name}::{mproperty.name}"
1541 end
1542
1543 # URL into the nitdoc page
1544 # class_owner_name.html#anchor
1545 private fun url: String do
1546 if url_cache == null then
1547 url_cache = "{mclassdef.mclass.url}#{anchor}"
1548 end
1549 return url_cache.as(not null)
1550 end
1551 private var url_cache: nullable String
1552
1553 # html anchor id for the property in a nitdoc class page
1554 # PROP_mclass_propertyname
1555 private fun anchor: String do
1556 if anchor_cache == null then
1557 anchor_cache = "PROP_{mclassdef.mclass.public_owner.name}_{mproperty.name.replace(" ", "_")}"
1558 end
1559 return anchor_cache.as(not null)
1560 end
1561 private var anchor_cache: nullable String
1562
1563 # Return a link to property into the nitdoc class page
1564 # <a href="url" title="short_comment">html_name</a>
1565 private fun html_link(page: NitdocPage) do
1566 if html_link_cache == null then
1567 var res = new Buffer
1568 if page.ctx.mbuilder.mpropdef2npropdef.has_key(self) then
1569 var nprop = page.ctx.mbuilder.mpropdef2npropdef[self]
1570 res.append("<a href=\"{url}\" title=\"{nprop.short_comment}\">{mproperty.html_name}</a>")
1571 else
1572 res.append("<a href=\"{url}\">{mproperty.html_name}</a>")
1573 end
1574 html_link_cache = res.to_s
1575 end
1576 page.append(html_link_cache.as(not null))
1577 end
1578 private var html_link_cache: nullable String
1579
1580 # Return a list item for the mpropdef
1581 # <li>html_link</li>
1582 private fun html_sidebar_item(page: NitdocClass) do
1583 if is_intro and mclassdef.mclass == page.mclass then
1584 page.append("<li class='intro'>")
1585 page.append("<span title='Introduced'>I</span>")
1586 else if is_intro and mclassdef.mclass != page.mclass then
1587 page.append("<li class='inherit'>")
1588 page.append("<span title='Inherited'>H</span>")
1589 else
1590 page.append("<li class='redef'>")
1591 page.append("<span title='Redefined'>R</span>")
1592 end
1593 html_link(page)
1594 page.append("</li>")
1595 end
1596
1597 private fun html_full_desc(page: NitdocPage, ctx: MClass) is abstract
1598 private fun html_info(page: NitdocPage, ctx: MClass) is abstract
1599
1600 private fun html_comment(page: NitdocPage) do
1601 page.append("<div class='description'>")
1602 if not is_intro then
1603 if page.ctx.mbuilder.mpropdef2npropdef.has_key(mproperty.intro) then
1604 var intro_nprop = page.ctx.mbuilder.mpropdef2npropdef[mproperty.intro]
1605 page.append("<textarea class='baseComment' data-comment-namespace='{mproperty.intro.mclassdef.mmodule.full_name}::{mproperty.intro.mclassdef.mclass.name}::{mproperty.name}' data-comment-location='{intro_nprop.doc_location.to_s}'>{intro_nprop.full_comment}</textarea>")
1606 if intro_nprop.full_comment.is_empty then
1607 page.append("<p class='info inheritance'>")
1608 page.append("<span class=\"noComment\">no comment for </span>")
1609 else
1610 page.append("<div class='comment'>{intro_nprop.full_markdown}</div>")
1611 page.append("<p class='info inheritance'>")
1612 end
1613 page.append("introduction in ")
1614 mproperty.intro.mclassdef.html_namespace(page)
1615 page.append(" {page.show_source(intro_nprop.location)}</p>")
1616 end
1617 end
1618 if page.ctx.mbuilder.mpropdef2npropdef.has_key(self) then
1619 var nprop = page.ctx.mbuilder.mpropdef2npropdef[self]
1620 page.append("<textarea class='baseComment' data-comment-namespace='{mclassdef.mmodule.full_name}::{mclassdef.mclass.name}::{mproperty.name}' data-comment-location='{nprop.doc_location.to_s}'>{nprop.full_comment}</textarea>")
1621 if nprop.full_comment == "" then
1622 page.append("<p class='info inheritance'>")
1623 page.append("<span class=\"noComment\">no comment for </span>")
1624 else
1625 page.append("<div class='comment'>{nprop.full_markdown}</div>")
1626 page.append("<p class='info inheritance'>")
1627 end
1628 if is_intro then
1629 page.append("introduction in ")
1630 else
1631 page.append("redefinition in ")
1632 end
1633 mclassdef.html_namespace(page)
1634 page.append(" {page.show_source(nprop.location)}</p>")
1635 end
1636 page.append("</div>")
1637 end
1638 end
1639
1640 redef class MMethodDef
1641 redef fun html_full_desc(page, ctx) do
1642 var classes = new Array[String]
1643 var is_redef = mproperty.intro_mclassdef.mclass != ctx
1644 if mproperty.is_init then
1645 classes.add("init")
1646 else
1647 classes.add("fun")
1648 end
1649 if is_redef then classes.add("redef")
1650 classes.add(mproperty.visibility.to_s)
1651 page.append("<article class='{classes.join(" ")}' id='{anchor}'>")
1652 if page.ctx.mbuilder.mpropdef2npropdef.has_key(self) then
1653 page.append("<h3 class='signature' data-untyped-signature='{mproperty.name}{msignature.untyped_signature(page)}'>")
1654 page.append("<span>{mproperty.html_name}")
1655 msignature.html_signature(page)
1656 page.append("</span></h3>")
1657 else
1658 page.append("<h3 class='signature' data-untyped-signature='init{msignature.untyped_signature(page)}'>")
1659 page.append("<span>init")
1660 msignature.html_signature(page)
1661 page.append("</span></h3>")
1662 end
1663 html_info(page, ctx)
1664 html_comment(page)
1665 page.append("</article>")
1666 end
1667
1668 redef fun html_info(page, ctx) do
1669 page.append("<div class='info'>")
1670 if mproperty.visibility < public_visibility then page.append("{mproperty.visibility.to_s} ")
1671 if mproperty.intro_mclassdef.mclass != ctx then page.append("redef ")
1672 if mproperty.is_init then
1673 page.append("init ")
1674 else
1675 page.append("fun ")
1676 end
1677 mproperty.html_namespace(page)
1678 page.append("</div>")
1679 end
1680 end
1681
1682 redef class MVirtualTypeDef
1683 redef fun html_full_desc(page, ctx) do
1684 var is_redef = mproperty.intro_mclassdef.mclass != ctx
1685 var classes = new Array[String]
1686 classes.add("type")
1687 if is_redef then classes.add("redef")
1688 classes.add(mproperty.visibility.to_s)
1689 page.append("<article class='{classes.join(" ")}' id='{anchor}'>")
1690 page.append("<h3 class='signature' data-untyped-signature='{mproperty.name}'><span>{mproperty.html_name}: ")
1691 bound.html_link(page)
1692 page.append("</span></h3>")
1693 html_info(page, ctx)
1694 html_comment(page)
1695 page.append("</article>")
1696 end
1697
1698 redef fun html_info(page, ctx) do
1699 page.append("<div class='info'>")
1700 if mproperty.intro_mclassdef.mclass != ctx then page.append("redef ")
1701 page.append("type ")
1702 mproperty.html_namespace(page)
1703 page.append("</div>")
1704 end
1705 end
1706
1707 redef class MSignature
1708 private fun html_signature(page: NitdocPage) do
1709 if not mparameters.is_empty then
1710 page.append("(")
1711 for i in [0..mparameters.length[ do
1712 mparameters[i].html_link(page)
1713 if i < mparameters.length - 1 then page.append(", ")
1714 end
1715 page.append(")")
1716 end
1717 if return_mtype != null then
1718 page.append(": ")
1719 return_mtype.html_link(page)
1720 end
1721 end
1722
1723 private fun untyped_signature(page: NitdocPage): String do
1724 var res = new Buffer
1725 if not mparameters.is_empty then
1726 res.append("(")
1727 for i in [0..mparameters.length[ do
1728 res.append(mparameters[i].name)
1729 if i < mparameters.length - 1 then res.append(", ")
1730 end
1731 res.append(")")
1732 end
1733 return res.to_s
1734 end
1735 end
1736
1737 redef class MParameter
1738 private fun html_link(page: NitdocPage) do
1739 page.append("{name}: ")
1740 mtype.html_link(page)
1741 if is_vararg then page.append("...")
1742 end
1743 end
1744
1745 #
1746 # Nodes redefs
1747 #
1748
1749 redef class ADoc
1750 private fun short_comment: String do
1751 return n_comment.first.text.substring_from(2).replace("\n", "").html_escape
1752 end
1753
1754 private fun full_comment: String do
1755 var res = new Buffer
1756 for t in n_comment do
1757 var text = t.text
1758 text = text.substring_from(1)
1759 if text.first == ' ' then text = text.substring_from(1)
1760 res.append(text.html_escape)
1761 end
1762 return res.to_s
1763 end
1764 end
1765
1766 redef class AModule
1767 private fun short_comment: String do
1768 if n_moduledecl != null and n_moduledecl.n_doc != null then
1769 return n_moduledecl.n_doc.short_comment
1770 end
1771 return ""
1772 end
1773
1774 private fun full_comment: String do
1775 if n_moduledecl != null and n_moduledecl.n_doc != null then
1776 return n_moduledecl.n_doc.full_comment
1777 end
1778 return ""
1779 end
1780
1781 private fun full_markdown: String do
1782 if n_moduledecl != null and n_moduledecl.n_doc != null then
1783 return n_moduledecl.n_doc.full_markdown.html
1784 end
1785 return ""
1786 end
1787
1788 private fun doc_location: Location do
1789 if n_moduledecl != null and n_moduledecl.n_doc != null then
1790 return n_moduledecl.n_doc.location
1791 end
1792 return location
1793 end
1794 end
1795
1796 redef class AStdClassdef
1797 private fun short_comment: String do
1798 if n_doc != null then return n_doc.short_comment
1799 return ""
1800 end
1801
1802 private fun full_comment: String do
1803 if n_doc != null then return n_doc.full_comment
1804 return ""
1805 end
1806
1807 private fun full_markdown: String do
1808 if n_doc != null then return n_doc.full_markdown.html
1809 return ""
1810 end
1811
1812 private fun doc_location: Location do
1813 if n_doc != null then return n_doc.location
1814 return location
1815 end
1816 end
1817
1818 redef class APropdef
1819 private fun short_comment: String do
1820 if n_doc != null then return n_doc.short_comment
1821 return ""
1822 end
1823
1824 private fun full_comment: String do
1825 if n_doc != null then return n_doc.full_comment
1826 return ""
1827 end
1828
1829 private fun full_markdown: String do
1830 if n_doc != null then return n_doc.full_markdown.html
1831 return ""
1832 end
1833
1834 private fun doc_location: Location do
1835 if n_doc != null then return n_doc.location
1836 return location
1837 end
1838 end
1839
1840
1841 var nitdoc = new NitdocContext
1842 nitdoc.generate_nitdoc