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