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