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