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