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