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