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