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