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