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