ni_nitdoc: get rid of HTML lib for performances considerations
[nit.git] / src / ni_nitdoc.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2008 Jean Privat <jean@pryen.org>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 module ni_nitdoc
18
19 import model_utils
20 import abstract_compiler
21
22 class Nitdoc
23 private var toolcontext: ToolContext
24 private var model: Model
25 private var modelbuilder: ModelBuilder
26 private var mainmodule: MModule
27 private var class_hierarchy: POSet[MClass]
28 private var arguments: Array[String]
29 private var output_dir: nullable String
30 private var dot_dir: nullable String
31 private var share_dir: nullable String
32 private var source: nullable String
33
34 private var opt_dir = new OptionString("Directory where doc is generated", "-d", "--dir")
35 private var opt_source = new OptionString("What link for source (%f for filename, %l for first line, %L for last line)", "--source")
36 private var opt_sharedir = new OptionString("Directory containing the nitdoc files", "--sharedir")
37 private var opt_nodot = new OptionBool("Do not generate graphes with graphiviz", "--no-dot")
38
39 init(toolcontext: ToolContext) do
40 # We need a model to collect stufs
41 self.toolcontext = toolcontext
42 self.arguments = toolcontext.option_context.rest
43 toolcontext.option_context.options.clear
44 toolcontext.option_context.add_option(opt_dir)
45 toolcontext.option_context.add_option(opt_source)
46 toolcontext.option_context.add_option(opt_sharedir)
47 toolcontext.option_context.add_option(opt_nodot)
48 toolcontext.process_options
49 process_options
50
51 if arguments.length < 1 then
52 toolcontext.option_context.usage
53 exit(1)
54 end
55
56 model = new Model
57 modelbuilder = new ModelBuilder(model, toolcontext)
58
59 # Here we load an process std modules
60 var mmodules = modelbuilder.parse_and_build([arguments.first])
61 if mmodules.is_empty then return
62 modelbuilder.full_propdef_semantic_analysis
63 assert mmodules.length == 1
64 self.mainmodule = mmodules.first
65 self.class_hierarchy = mainmodule.flatten_mclass_hierarchy
66 end
67
68 private fun process_options do
69 if not opt_dir.value is null then
70 output_dir = opt_dir.value
71 else
72 output_dir = "doc"
73 end
74 if not opt_sharedir.value is null then
75 share_dir = opt_sharedir.value
76 else
77 var dir = "NIT_DIR".environ
78 if dir.is_empty then
79 dir = "{sys.program_name.dirname}/../share/nitdoc"
80 else
81 dir = "{dir}/share/nitdoc"
82 end
83 share_dir = dir
84 if share_dir is null then
85 print "Error: Cannot locate nitdoc share files. Uses --sharedir or envvar NIT_DIR"
86 abort
87 end
88 dir = "{share_dir.to_s}/scripts/js-facilities.js"
89 if share_dir is null then
90 print "Error: Invalid nitdoc share files. Check --sharedir or envvar NIT_DIR"
91 abort
92 end
93 end
94 if not opt_source.value is null then
95 source = ""
96 else
97 source = opt_source.value
98 end
99 end
100
101 fun start do
102 if arguments.length == 1 then
103 # Create destination dir if it's necessary
104 if not output_dir.file_exists then output_dir.mkdir
105 sys.system("cp -r {share_dir.to_s}/* {output_dir.to_s}/")
106 self.dot_dir = null
107 if not opt_nodot.value then self.dot_dir = output_dir.to_s
108 overview
109 #fullindex
110 modules
111 classes
112 #quicksearch_list
113 end
114 end
115
116 fun overview do
117 var overviewpage = new NitdocOverview(modelbuilder, dot_dir)
118 overviewpage.save("{output_dir.to_s}/index.html")
119 end
120
121 fun fullindex do
122 var fullindex = new NitdocFullindex(model.mmodules)
123 fullindex.save("{output_dir.to_s}/full-index.html")
124 end
125
126 fun modules do
127 for mmodule in model.mmodules do
128 var modulepage = new NitdocModule(mmodule, modelbuilder, dot_dir)
129 modulepage.save("{output_dir.to_s}/{mmodule.name}.html")
130 end
131 end
132
133 fun classes do
134 for mclass in modelbuilder.model.mclasses do
135 var classpage = new NitdocClass(mclass, self, dot_dir, source)
136 classpage.save("{output_dir.to_s}/{mclass.name}.html")
137 end
138 end
139
140 # Generate QuickSearch file
141 fun quicksearch_list do
142 var file = new OFStream.open("{output_dir.to_s}/quicksearch-list.js")
143 var content = new Buffer
144 content.append("var entries = \{ ")
145 for prop in model.mproperties do
146 if not prop isa MMethod then continue
147 content.append("\"{prop.name}\": [")
148 for propdef in prop.mpropdefs do
149 content.append("\{txt: \"{propdef.mproperty.full_name}\", url:\"{propdef.mproperty.anchor}\" \}")
150 if not propdef is prop.mpropdefs.last then content.append(", ")
151 end
152 content.append("]")
153 content.append(", ")
154 end
155
156 for mclass in model.mclasses do
157 content.append("\"{mclass.name}\": [")
158 for mclassdef in mclass.mclassdefs do
159 content.append("\{txt: \"{mclassdef.mclass.full_name}\", url:\"{mclass.link_anchor}\" \}")
160 if not mclassdef is mclass.mclassdefs.last then content.append(", ")
161 end
162 content.append("]")
163 if not mclass is model.mclasses.last then content.append(", ")
164 end
165
166 content.append(" \};")
167 file.write(content.to_s)
168 file.close
169 end
170
171 end
172
173 # Nitdoc base page
174 abstract class NitdocPage
175
176 var dot_dir: nullable String
177 var source: nullable String
178
179 init do end
180
181 fun append(str: String) do html.append(str)
182 var html = new Buffer
183
184 fun head do
185 append("<meta charset='utf-8'/>")
186 append("<script type='text/javascript' src='scripts/jquery-1.7.1.min.js'></script>")
187 append("<script type='text/javascript' src='quicksearch-list.js'></script>")
188 append("<script type='text/javascript' src='scripts/js-facilities.js'></script>")
189 append("<link rel='stylesheet' href='styles/main.css' type='text/css' media='screen'/>")
190 end
191
192 fun menu is abstract
193
194 fun header do
195 append("<header>")
196 append("<nav class='main'>")
197 append("<ul>")
198 menu
199 append("<li id='liGitHub'>")
200 append("<a class='btn' id='logGitHub'>")
201 append("<img id='imgGitHub' src='resources/icons/github-icon.png' alt='GitHub'/>")
202 append("</a>")
203 append("<div class='popover bottom'>")
204 append("<div class='arrow'>&nbsp;</div>")
205 append("<div class='githubTitle'>")
206 append("<h3>Github Sign In</h3>")
207 append("</div>")
208 append("<div>")
209 append("<label id='lbloginGit'>Username</label>")
210 append("<input id='loginGit' name='login' type='text'/>")
211 append("<label id='logginMessage'>Hello ")
212 append("<a id='githubAccount'><strong id='nickName'></strong></a>")
213 append("</label>")
214 append("</div>")
215 append("<div>")
216 append("<label id='lbpasswordGit'>Password</label>")
217 append("<input id='passwordGit' name='password' type='password'/>")
218 append("<div id='listBranches'>")
219 append("<label id='lbBranches'>Branch</label>")
220 append("<select class='dropdown' id='dropBranches' name='dropBranches' tabindex='1'></select>")
221 append("</div>")
222 append("</div>")
223 append("<div>")
224 append("<label id='lbrepositoryGit'>Repository</label>")
225 append("<input id='repositoryGit' name='repository' type='text'/>")
226 append("</div>")
227 append("<div>")
228 append("<label id='lbbranchGit'>Branch</label>")
229 append("<input id='branchGit' name='branch' type='text'/>")
230 append("</div>")
231 append("<div>")
232 append("<a id='signIn'>Sign In</a>")
233 append("</div>")
234 append("</div>")
235 append("</li>")
236 append("</ul>")
237 append("</nav>")
238 append("</header>")
239 end
240
241 fun content is abstract
242
243 fun footer do
244 append("<footer>Nit standard library. Version jenkins-component=stdlib-19.</footer>")
245 end
246
247 # Generate a clickable graphviz image using a dot content
248 fun generate_dot(dot: String, name: String, alt: String) do
249 var output_dir = dot_dir
250 if output_dir == null then return
251 var file = new OFStream.open("{output_dir}/{name}.dot")
252 file.write(dot)
253 file.close
254 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 ; \}")
255 append("<article class='graph'>")
256 append("<img src='{name}.png' usemap='#{name}' style='margin:auto' alt='{alt}'/>")
257 append("</article>")
258 var fmap = new IFStream.open("{output_dir}/{name}.map")
259 append(fmap.read_all)
260 fmap.close
261 end
262
263 # Add a (source) link for a given location
264 fun show_source(l: Location): String
265 do
266 if source == null then
267 return "({l.file.filename.simplify_path})"
268 else
269 # THIS IS JUST UGLY ! (but there is no replace yet)
270 var x = source.split_with("%f")
271 source = x.join(l.file.filename.simplify_path)
272 x = source.split_with("%l")
273 source = x.join(l.line_start.to_s)
274 x = source.split_with("%L")
275 source = x.join(l.line_end.to_s)
276 return " (<a href=\"{source.to_s}\">show code</a>)"
277 end
278 end
279
280 # Render the page as a html string
281 fun render: String do
282 append("<!DOCTYPE html>")
283 append("<head>")
284 head
285 append("</head>")
286 append("<body>")
287 header
288 append("<div class='page'>")
289 content
290 append("</div>")
291 footer
292 append("</body>")
293 return html.to_s
294 end
295
296 # Save html page in the specified file
297 fun save(file: String) do
298 var out = new OFStream.open(file)
299 out.write(render)
300 out.close
301 end
302 end
303
304 # The overview page
305 class NitdocOverview
306 super NitdocPage
307 private var mbuilder: ModelBuilder
308 private var mmodules = new Array[MModule]
309
310 init(mbuilder: ModelBuilder, dot_dir: nullable String) do
311 self.mbuilder = mbuilder
312 self.dot_dir = dot_dir
313 # get modules
314 var mmodules = new HashSet[MModule]
315 for mmodule in mbuilder.model.mmodules do
316 var owner = mmodule.public_owner
317 if owner != null then
318 mmodules.add(owner)
319 else
320 mmodules.add(mmodule)
321 end
322 end
323 # sort modules
324 var sorter = new ComparableSorter[MModule]
325 self.mmodules.add_all(mmodules)
326 sorter.sort(self.mmodules)
327 end
328
329 redef fun head do
330 super
331 append("<title>Overview | Nit Standard Library</title>")
332 end
333
334 redef fun menu do
335 append("<li class='current'>Overview</li>")
336 append("<li><a href='full-index.html'>Full Index</a></li>")
337 end
338
339 redef fun content do
340 append("<div class='content fullpage'>")
341 append("<h1>Nit Standard Library</h1>")
342 append("<article class='overview'><p>Documentation for the standard library of Nit<br />Version jenkins-component=stdlib-19<br />Date: TODAY</p></article>")
343 append("<article class='overview'>")
344 # module list
345 append("<h2>Modules</h2>")
346 append("<ul>")
347 for mmodule in mmodules do
348 var amodule = mbuilder.mmodule2nmodule[mmodule]
349 append("<li>{mmodule.link(mbuilder)}&nbsp;{amodule.short_comment}</li>")
350 end
351 append("</ul>")
352 # module graph
353 process_generate_dot
354 append("</article>")
355 append("</div>")
356 end
357
358 fun process_generate_dot do
359 var op = new Buffer
360 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")
361 for mmodule in mmodules do
362 op.append("\"{mmodule.name}\"[URL=\"{mmodule.name}.html\"];\n")
363 for imported in mmodule.in_importation.direct_greaters do
364 if imported.direct_owner == null then
365 op.append("\"{mmodule.name}\"->\"{imported.name}\";\n")
366 end
367 end
368 end
369 op.append("\}\n")
370 generate_dot(op.to_s, "dep", "Modules hierarchy")
371 end
372 end
373
374 # The full index page
375 class NitdocFullindex
376 super NitdocPage
377
378 private var mmodules: Array[MModule]
379
380 init(mmodules: Array[MModule]) do
381 self.mmodules = mmodules
382 self.dot_dir = null
383 end
384
385 redef fun head do
386 super
387 append("<title>Full Index | Nit Standard Library</title>")
388 end
389
390 redef fun menu do
391 append("<li><a href='index.html'>Overview</a></li>")
392 append("<li class='current'>Full Index</li>")
393 end
394
395 redef fun content do
396 append("<div class='content fullpage'>")
397 append("<h1>Full Index</h1>")
398 module_column
399 classes_column
400 properties_column
401 append("</div>")
402 end
403
404 # Add to content modules column
405 fun module_column do
406 var ls = new List[nullable MModule]
407 var sorted = mmodules
408 var sorterp = new ComparableSorter[MModule]
409 sorterp.sort(sorted)
410 append("<article class='modules filterable'></article>")
411 append("<h2>Modules</h2>")
412 append("<ul>")
413 for mmodule in sorted do
414 if mmodule.public_owner != null and not ls.has(mmodule.public_owner) then
415 ls.add(mmodule.public_owner)
416 append("<li><a href='{mmodule.public_owner.name}.html'>(mmodule.public_owner.name)</a></li>")
417 end
418 end
419 append("</ul>")
420 append("</article>")
421 end
422
423 # Add to content classes modules
424 fun classes_column do
425 var sorted = mmodules.first.imported_mclasses.to_a
426 var sorterp = new ComparableSorter[MClass]
427 sorterp.sort(sorted)
428 append("<article class='classes filterable'>")
429 append("<h2>Classes</h2>")
430 append("<ul>")
431 for mclass in sorted do
432 append("<li><a href='{mclass}.html'>(mclass.name)</a></li>")
433 end
434 append("</ul>")
435 append("</article>")
436 end
437
438 # Insert the properties column of fullindex page
439 fun properties_column do
440 append("<article class='classes filterable'>")
441 append("<h2>Properties</h2>")
442 append("<ul>")
443 var sorted_imported = mmodules.first.imported_methods.to_a
444 var sorted_redef = mmodules.first.redef_methods.to_a
445 var sorterp = new ComparableSorter[MProperty]
446 sorterp.sort(sorted_imported)
447 sorterp.sort(sorted_redef)
448
449 for method in sorted_imported do
450 if method.visibility is none_visibility or method.visibility is intrude_visibility then continue
451 append("<li class='intro'</li>")
452 append("<span title='introduction'>I</span>&nbsp;")
453 append("<a href='{method.local_class.name}.html' title=''>{method.name} ({method.local_class.name})</a>")
454 append("</li>")
455 end
456
457 for method in sorted_redef do
458 if method.visibility is none_visibility or method.visibility is intrude_visibility then continue
459 append("<li class='redef'>")
460 append("<span title='redefinition'>R</span>&nbsp;")
461 append("<a href='{method.local_class.name}.html' title=''>{method.name} ({method.local_class.name})</span>")
462 append("</li>")
463 end
464 append("</ul>")
465 append("</article>")
466 end
467
468 end
469
470 # A module page
471 class NitdocModule
472 super NitdocPage
473
474 private var mmodule: MModule
475 private var mbuilder: ModelBuilder
476
477 init(mmodule: MModule, mbuilder: ModelBuilder, dot_dir: nullable String) do
478 self.mmodule = mmodule
479 self.mbuilder = mbuilder
480 self.dot_dir = dot_dir
481 end
482
483 redef fun head do
484 super
485 var amodule = mbuilder.mmodule2nmodule[mmodule]
486 append("<title>{mmodule.name} module | {amodule.short_comment}</title>")
487 end
488
489 redef fun menu do
490 append("<li><a href='index.html'>Overview</a></li>")
491 append("<li class='current'>{mmodule.name}</li>")
492 append("<li><a href='full-index.html'>Full Index</a></li>")
493 end
494
495 redef fun content do
496 sidebar
497 append("<div class='content'>")
498 append("<h1>{mmodule.name}</h1>")
499 append("<div class='subtitle'>{mmodule.html_signature(mbuilder)}</div>")
500 append(mmodule.html_full_comment(mbuilder))
501 process_generate_dot
502 classes
503 properties
504 append("</div>")
505 end
506
507 fun process_generate_dot do
508 var name = "dep_{mmodule.name}"
509 var op = new Buffer
510 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")
511 for m in mmodule.in_importation.poset do
512 var public_owner = m.public_owner
513 if public_owner == null then
514 public_owner = m
515 if m == mmodule then
516 op.append("\"{m.name}\"[shape=box,margin=0.03];\n")
517 else
518 op.append("\"{m.name}\"[URL=\"{m.name}.html\"];\n")
519 end
520 end
521 for imported in m.in_importation.direct_greaters do
522 if imported.public_owner == null then
523 op.append("\"{public_owner.name}\"->\"{imported.name}\";\n")
524 end
525 end
526 end
527 op.append("\}\n")
528 generate_dot(op.to_s, name, "Dependency graph for module {mmodule.name}")
529 end
530
531 fun sidebar do
532 var amodule = mbuilder.mmodule2nmodule[mmodule]
533 append("<div class='menu'>")
534 append("<nav>")
535 append("<h3>Module Hierarchy</h3>")
536 var dependencies = new Array[MModule]
537 for dep in mmodule.in_importation.greaters do
538 if dep == mmodule or dep.public_owner != null then continue
539 dependencies.add(dep)
540 end
541 if dependencies.length > 0 then
542 append("<h4>All dependencies</h4>")
543 display_module_list(dependencies)
544 end
545 var clients = new Array[MModule]
546 for dep in mmodule.in_importation.smallers do
547 if dep == mmodule or dep.public_owner != null then continue
548 clients.add(dep)
549 end
550 if clients.length > 0 then
551 append("<h4>All clients</h4>")
552 display_module_list(clients)
553 end
554 append("</nav>")
555 if mmodule.in_nesting.direct_greaters.length > 0 then
556 append("<nav>")
557 append("<h3>Nested Modules</h3>")
558 display_module_list(mmodule.in_nesting.direct_greaters.to_a)
559 append("</nav>")
560 end
561 append("</div>")
562 end
563
564 private fun display_module_list(list: Array[MModule]) do
565 append("<ul>")
566 var sorter = new ComparableSorter[MModule]
567 sorter.sort(list)
568 for m in list do append("<li>{m.link(mbuilder)}</li>")
569 append("</ul>")
570 end
571
572 fun classes do
573 var amodule = mbuilder.mmodule2nmodule[mmodule]
574 var intro_mclasses = mmodule.intro_mclasses
575 var redef_mclasses = mmodule.redef_mclasses
576 var all_mclasses = new HashSet[MClass]
577 for m in mmodule.in_nesting.greaters do
578 all_mclasses.add_all(m.intro_mclasses)
579 all_mclasses.add_all(m.redef_mclasses)
580 end
581 all_mclasses.add_all(intro_mclasses)
582 all_mclasses.add_all(redef_mclasses)
583
584 var sorted = new Array[MClass]
585 sorted.add_all(all_mclasses)
586 var sorter = new ComparableSorter[MClass]
587 sorter.sort(sorted)
588 append("<div class='module'>")
589 append("<article class='classes filterable'>")
590 append("<h2>Classes</h2>")
591 append("<ul>")
592 for c in sorted do
593 var nclass = mbuilder.mclassdef2nclassdef[c.intro].as(AStdClassdef)
594 if redef_mclasses.has(c) and c.intro_mmodule.public_owner != mmodule then
595 append("<li class='redef'>")
596 append("<span title='refined in this module'>R </span>")
597 else
598 append("<li class='intro'>")
599 append("<span title='introduced in this module'>I </span>")
600 end
601 append(c.link(nclass))
602 append("</li>")
603 end
604 append("</ul>")
605 append("</article>")
606 append("</div>")
607 end
608
609 fun properties do
610 var amodule = mbuilder.mmodule2nmodule[mmodule]
611 var mpropdefs = new HashSet[MPropDef]
612 for m in mmodule.in_nesting.greaters do
613 for c in m.mclassdefs do mpropdefs.add_all(c.mpropdefs)
614 end
615 for c in mmodule.mclassdefs do mpropdefs.add_all(c.mpropdefs)
616 var sorted = mpropdefs.to_a
617 var sorter = new ComparableSorter[MPropDef]
618 sorter.sort(sorted)
619 append("<article class='properties filterable'>")
620 append("<h2>Properties</h2>")
621 append("<ul>")
622 for p in sorted do
623 if p isa MAttributeDef then continue
624 if p.mproperty.visibility <= none_visibility then continue
625 if not mbuilder.mpropdef2npropdef.has_key(p) then continue
626 var nprop = mbuilder.mpropdef2npropdef[p]
627 if p.is_intro then
628 append("<li class='intro'>")
629 append("<span title='introduction'>I</span>&nbsp;{p.link(nprop)} ({p.mclassdef.mclass.name})")
630 append("</li>")
631 else
632 append("<li class='redef'>")
633 append("<span title='redefinition'>R</span>&nbsp;{p.link(nprop)} ({p.mclassdef.mclass.name})")
634 append("</li>")
635 end
636 end
637 append("</ul>")
638 append("</article>")
639 end
640 end
641
642 # A class page
643 class NitdocClass
644 super NitdocPage
645
646 private var mclass: MClass
647 private var mbuilder: ModelBuilder
648 private var nitdoc: Nitdoc
649
650 init(mclass: MClass, nitdoc: Nitdoc, dot_dir: nullable String, source: nullable String) do
651 self.mclass = mclass
652 self.mbuilder = nitdoc.modelbuilder
653 self.nitdoc = nitdoc
654 self.dot_dir = dot_dir
655 self.source = source
656 end
657
658 redef fun head do
659 super
660 var nclass = mbuilder.mclassdef2nclassdef[mclass.intro]
661 if nclass isa AStdClassdef then
662 append("<title>{mclass.name} class | {nclass.short_comment}</title>")
663 else
664 append("<title>{mclass.name} class</title>")
665 end
666 end
667
668 redef fun menu do
669 append("<li><a href='index.html'>Overview</a></li>")
670 var public_owner = mclass.public_owner
671 if public_owner is null then
672 append("<li>{mclass.intro_mmodule.link(mbuilder)}</li>")
673 else
674 append("<li>{public_owner.link(mbuilder)}</li>")
675 end
676 append("<li class='current'>{mclass.name}</li>")
677 append("<li><a href='full-index.html'>Full Index</a></li>")
678 end
679
680 redef fun content do
681 append("<div class='menu'>")
682 properties_column
683 inheritance_column
684 append("</div>")
685 append("<div class='content'>")
686 class_doc
687 append("</div>")
688 end
689
690 fun properties_column do
691 var sorter = new ComparableSorter[MPropDef]
692 append("<nav class='properties filterable'>")
693 append("<h3>Properties</h3>")
694 # get properties
695 var vtypes = new HashSet[MVirtualTypeDef]
696 var consts = new HashSet[MMethodDef]
697 var meths = new HashSet[MMethodDef]
698 for mclassdef in mclass.mclassdefs do
699 for mpropdef in mclassdef.mpropdefs do
700 if not mbuilder.mpropdef2npropdef.has_key(mpropdef) then continue
701 if mpropdef.mproperty.visibility <= none_visibility then continue
702 if mpropdef isa MVirtualTypeDef then vtypes.add(mpropdef)
703 if mpropdef isa MMethodDef then
704 if mpropdef.mproperty.is_init then
705 consts.add(mpropdef)
706 else
707 meths.add(mpropdef)
708 end
709 end
710 end
711 end
712 for mprop in mclass.inherited_methods do
713 var mpropdef = mprop.intro
714 if not mbuilder.mpropdef2npropdef.has_key(mpropdef) then continue
715 if mprop.visibility <= none_visibility then continue
716 if mprop.intro_mclassdef.mclass.name == "Object" then continue
717 meths.add(mpropdef)
718 end
719 # virtual types
720 if vtypes.length > 0 then
721 var vts = new Array[MVirtualTypeDef]
722 vts.add_all(vtypes)
723 sorter.sort(vts)
724 append("<h4>Virtual Types</h4>")
725 display_mpropdef_list(vts)
726 end
727 if consts.length > 0 then
728 var cts = new Array[MMethodDef]
729 cts.add_all(consts)
730 sorter.sort(cts)
731 append("<h4>Constructors</h4>")
732 display_mpropdef_list(cts)
733 end
734 if meths.length > 0 then
735 var mts = new Array[MMethodDef]
736 mts.add_all(meths)
737 sorter.sort(mts)
738 append("<h4>Methods</h4>")
739 display_mpropdef_list(mts)
740 end
741 append("</nav>")
742 end
743
744 private fun display_mpropdef_list(list: Array[MPropDef]) do
745 append("<ul>")
746 for prop in list do
747 var nprop = mbuilder.mpropdef2npropdef[prop]
748 if prop.is_intro and prop.mproperty.intro_mclassdef.mclass != mclass then
749 append("<li class='inherit'>")
750 append("<span title='Inherited'>H</span>")
751 else if prop.is_intro then
752 append("<li class='intro'>")
753 append("<span title='Introduced'>I</span>")
754 else
755 append("<li class='redef'>")
756 append("<span title='Redefined'>R</span>")
757 end
758 append(prop.link(nprop))
759 append("</li>")
760 end
761 append("</ul>")
762 end
763
764 fun inheritance_column do
765 var sorted = new Array[MClass]
766 var sorterp = new ComparableSorter[MClass]
767 append("<nav>")
768 append("<h3>Inheritance</h3>")
769 if mclass.ancestors.length > 1 then
770 sorted = mclass.ancestors.to_a
771 sorterp.sort(sorted)
772 append("<h4>Superclasses</h4>")
773 append("<ul>")
774 for sup in sorted do
775 if sup == mclass then continue
776 append("<li><a href='{sup.name}.html'>{sup.name}</a></li>")
777 end
778 append("</ul>")
779 end
780
781 if mclass.descendants.length <= 1 then
782 append("<h4>No Known Subclasses</h4>")
783 else if mclass.descendants.length <= 100 then
784 sorted = mclass.descendants.to_a
785 sorterp.sort(sorted)
786 append("<h4>Subclasses</h4>")
787 append("<ul>")
788 for sub in sorted do
789 if sub == mclass then continue
790 append("<li><a href='{sub.name}.html'>{sub.name}</a></li>")
791 end
792 append("</ul>")
793 else if mclass.children.length <= 100 then
794 sorted = mclass.children.to_a
795 sorterp.sort(sorted)
796 append("<h4>Direct Subclasses Only</h4>")
797 append("<ul>")
798 for sub in sorted do
799 if sub == mclass then continue
800 append("<li><a href='{sub.name}.html'>{sub.name}</a></li>")
801 end
802 append("</ul>")
803 else
804 append("<h4>Too much Subclasses to list</h4>")
805 end
806 append("</nav>")
807 end
808
809 fun class_doc do
810 # title
811 append("<h1>{mclass.to_s}</h1>")
812 append("<div class='subtitle'>")
813 var subtitle = ""
814 if mclass.visibility is none_visibility then subtitle += "private "
815 subtitle += "{mclass.kind} {mclass.public_owner.html_namespace(mbuilder)}::{mclass}"
816 append(subtitle)
817 append("</div>")
818 # comment
819 var nclass = mbuilder.mclassdef2nclassdef[mclass.intro]
820 append("<div style=\"float: right;\"><a id=\"lblDiffCommit\"></a></div>")
821 append("<section class='description'>")
822 if nclass isa AStdClassdef and not nclass.comment.is_empty then append("<pre class=\"text_label\" title=\"122\" name=\"\" tag=\"{mclass.mclassdefs.first.location.to_s}\" type=\"2\">{nclass.comment}</pre><textarea id=\"fileContent\" class=\"edit\" cols=\"76\" rows=\"1\" style=\"display: none;\"></textarea><a id=\"cancelBtn\" style=\"display: none;\">Cancel</a><a id=\"commitBtn\" style=\"display: none;\">Commit</a><pre id=\"preSave\" class=\"text_label\" type=\"2\"></pre>")
823 process_generate_dot
824 append("</section>")
825 # concerns
826 var sorted = new Array[MModule]
827 sorted.add_all(mclass.concerns.keys)
828 var sorterp = new ComparableSorter[MModule]
829 sorterp.sort(sorted)
830 append("<section class='concerns'>")
831 append("<h2 class='section-header'>Concerns</h2>")
832 append("<ul>")
833 for owner in sorted do
834 var nmodule = mbuilder.mmodule2nmodule[owner]
835 var childs = mclass.concerns[owner]
836 append("<li>")
837 append("<a href=\"#MOD_{owner.name}\">{owner.name}</a>: {nmodule.short_comment}")
838 if not childs is null then
839 append("<ul>")
840 var sortedc = childs.to_a
841 var sorterpc = new ComparableSorter[MModule]
842 sorterpc.sort(sortedc)
843 for child in sortedc do
844 var nchild = mbuilder.mmodule2nmodule[child]
845 append("<li><a href=\"#MOD_{child.name}\">{child.name}</a>: {nchild.short_comment} </li>")
846 end
847 append("</ul>")
848 end
849 append("</li>")
850 end
851 append("</ul>")
852 append("</section>")
853 # properties
854 var sorterprop = new ComparableSorter[MProperty]
855 var sorterc = new ComparableSorter[MClass]
856 var lmmodule = new List[MModule]
857 # virtual and formal types
858 if mclass.virtual_types.length > 0 or mclass.arity > 0 then
859 append("<section class='types'>")
860 append("<h2>Formal and Virtual Types</h2>")
861 if mclass.virtual_types.length > 0 then for prop in mclass.virtual_types do description(prop)
862 #TODO this is incorrect
863 if mclass.arity > 0 and nclass isa AStdClassdef then
864 for prop in nclass.n_formaldefs do
865 append("<article id='FT_Object_{prop.collect_text}'>")
866 append("<h3 class='signature'>{prop.collect_text}: nullable ")
867 append("<a title=\"The root of the class hierarchy.\" href=\"Object.html\">Object</a>")
868 append("</h3>")
869 append("<div class=\"info\">formal generic type</div>")
870 append("</article>")
871 end
872 end
873 append("</section>")
874 end
875 # constructors
876 if mclass.constructors.length > 0 then
877 var sortedc = mclass.constructors.to_a
878 sorterprop.sort(sortedc)
879 append("<section class='constructors'>")
880 append("<h2 class='section-header'>Constructors</h2>")
881 for prop in sortedc do description(prop)
882 append("</section>")
883 end
884 # methods
885 append("<section class='methods'>")
886 append("<h2 class='section-header'>Methods</h2>")
887 for mmodule, mmethods in mclass.all_methods do
888 var nmodule = mbuilder.mmodule2nmodule[mmodule]
889 append("<a id=\"MOD_{mmodule.name}\"></a>")
890 if mmodule != mclass.intro_mmodule and mmodule != mclass.public_owner then
891 if mclass.has_mmodule(mmodule) then
892 append("<p class=\"concern-doc\">{mmodule.name}: {nmodule.short_comment}</p>")
893 else
894 append("<h3 class=\"concern-toplevel\">Methods refined in {mmodule.link(mbuilder)}</h3><p class=\"concern-doc\">{mmodule.name}: {nmodule.short_comment}</p>")
895 end
896 end
897 var sortedc = mmethods.to_a
898 sorterprop.sort(sortedc)
899 for prop in sortedc do description(prop)
900 end
901 # inherited methods
902 if mclass.inherited_methods.length > 0 then
903 var sortedc = new Array[MClass]
904 sortedc.add_all(mclass.inherited.keys)
905 sorterc.sort(sortedc)
906 append("<h3>Inherited Methods</h3>")
907 for imclass in sortedc do
908 var inclass = mbuilder.mclassdef2nclassdef[imclass.intro].as(AStdClassdef)
909 var sortedp = mclass.inherited[imclass].to_a
910 sorterprop.sort(sortedp)
911 append("<p>Defined in {imclass.link(inclass)}: ")
912 for method in sortedp do
913 #TODO link to inherited propdef
914 append("<a href=\"\">{method.name}</a>")
915 if method != sortedp.last then append(", ")
916 end
917 append("</p>")
918 end
919 end
920 append("</section>")
921 end
922
923 fun description(prop: MProperty) do
924 if not mbuilder.mpropdef2npropdef.has_key(prop.intro) then return
925 var nprop = mbuilder.mpropdef2npropdef[prop.intro]
926 if not nprop isa AMethPropdef then return
927 var classes = new Array[String]
928 if nprop isa AInitPropdef then
929 classes.add("init")
930 else
931 classes.add("fun")
932 end
933 if prop.is_redef then classes.add("redef")
934 if prop.visibility == none_visibility then
935 classes.add("private")
936 else if prop.visibility == protected_visibility then
937 classes.add("protected")
938 else
939 classes.add("public")
940 end
941 append("<article class='{classes.join(" ")}' id='{prop.anchor}'>")
942 var sign = prop.name
943 append("<h3 class='signature'>{prop.name}{nprop.signature}</h3>")
944 append("<div class='info'>")
945 append("{if prop.is_redef then "redef" else ""} fun {prop.intro_mclassdef.namespace(mclass)}::{prop.name}</div><div style=\"float: right;\"><a id=\"lblDiffCommit\"></a>")
946 append("</div>")
947 append("<div class='description'>")
948 if nprop.comment == "" then
949 append("<a class=\"newComment\" title=\"32\" tag=\"\">New Comment</a>")
950 else
951 append("<pre class=\"text_label\" title=\"\" name=\"\" tag=\"\" type=\"1\">{nprop.comment}</pre>")
952 end
953 append("<textarea id=\"fileContent\" class=\"edit\" cols=\"76\" rows=\"1\" style=\"display: none;\"></textarea><a id=\"cancelBtn\" style=\"display: none;\">Cancel</a><a id=\"commitBtn\" style=\"display: none;\">Commit</a><pre id=\"preSave\" class=\"text_label\" type=\"2\"></pre>")
954 append("<p>")
955 if prop.local_class != mclass then
956 var mredef = prop.local_class.intro_mmodule
957 append("inherited from {mredef.link(mbuilder)} ")
958 end
959 #TODO display show code if doc github
960 var mintro = prop.intro_mclassdef.mmodule
961 append("defined by the module {mintro.link(mbuilder)}{if prop.apropdef is null then "" else show_source(prop.apropdef.location)}.")
962
963 for parent in mclass.parents do
964 var mparent = parent.intro_mmodule
965 if prop isa MMethod then if parent.constructors.has(prop) then append(" Previously defined by: {mparent.link(mbuilder)} for <a href=\"{parent.name}.html\">{parent.name}</a>.")
966 end
967 append("</p>")
968 append("</div>")
969 append("</article>")
970 end
971
972 fun process_generate_dot do
973 var pe = nitdoc.class_hierarchy[mclass]
974 var cla = new HashSet[MClass]
975 var sm = new HashSet[MClass]
976 var sm2 = new HashSet[MClass]
977 sm.add(mclass)
978 while cla.length + sm.length < 10 and sm.length > 0 do
979 cla.add_all(sm)
980 sm2.clear
981 for x in sm do
982 sm2.add_all(pe.poset[x].direct_smallers)
983 end
984 var t = sm
985 sm = sm2
986 sm2 = t
987 end
988 cla.add_all(pe.greaters)
989
990 var op = new Buffer
991 var name = "dep_{mclass.name}"
992 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")
993 for c in cla do
994 if c == mclass then
995 op.append("\"{c.name}\"[shape=box,margin=0.03];\n")
996 else
997 op.append("\"{c.name}\"[URL=\"{c.name}.html\"];\n")
998 end
999 for c2 in pe.poset[c].direct_greaters do
1000 if not cla.has(c2) then continue
1001 op.append("\"{c.name}\"->\"{c2.name}\";\n")
1002 end
1003 if not pe.poset[c].direct_smallers.is_empty then
1004 var others = true
1005 for c2 in pe.poset[c].direct_smallers do
1006 if cla.has(c2) then others = false
1007 end
1008 if others then
1009 op.append("\"{c.name}...\"[label=\"\"];\n")
1010 op.append("\"{c.name}...\"->\"{c.name}\"[style=dotted];\n")
1011 end
1012 end
1013 end
1014 op.append("\}\n")
1015 generate_dot(op.to_s, name, "Dependency graph for class {mclass.name}")
1016 end
1017 end
1018
1019 redef class AModule
1020 private fun comment: String do
1021 var ret = new Buffer
1022 if n_moduledecl is null or n_moduledecl.n_doc is null then ret
1023 if n_moduledecl.n_doc is null then return ""
1024 for t in n_moduledecl.n_doc.n_comment do
1025 ret.append(t.text.substring_from(1))
1026 end
1027 return ret.to_s.html_escape
1028 end
1029
1030 private fun short_comment: String do
1031 var ret = new Buffer
1032 if n_moduledecl != null and n_moduledecl.n_doc != null then
1033 ret.append(n_moduledecl.n_doc.n_comment.first.text.substring_from(2).replace("\n", ""))
1034 end
1035 return ret.to_s.html_escape
1036 end
1037 end
1038
1039 redef class MModule
1040 super Comparable
1041 redef type OTHER: MModule
1042 redef fun <(other: OTHER): Bool do return self.name < other.name
1043
1044 # Get the list of all methods in a module
1045 fun imported_methods: Set[MMethod] do
1046 var methods = new HashSet[MMethod]
1047 for mclass in imported_mclasses do
1048 for method in mclass.intro_methods do
1049 methods.add(method)
1050 end
1051 end
1052 return methods
1053 end
1054
1055 # Get the list aof all refined methods in a module
1056 fun redef_methods: Set[MMethod] do
1057 var methods = new HashSet[MMethod]
1058 for mclass in redef_mclasses do
1059 for method in mclass.intro_methods do
1060 methods.add(method)
1061 end
1062 end
1063 return methods
1064 end
1065
1066 # Return a link (html a tag) to the nitdoc module page
1067 fun link(mbuilder: ModelBuilder): String do
1068 return "<a href='{name}.html' title='{mbuilder.mmodule2nmodule[self].short_comment}'>{name}</a>"
1069 end
1070
1071 # Return the module signature decorated with html
1072 fun html_signature(mbuilder: ModelBuilder): String do
1073 return "<span>module {html_namespace(mbuilder)}</span>"
1074 end
1075
1076 # Return the module namespace decorated with html
1077 fun html_namespace(mbuilder: ModelBuilder): String do
1078 var res = new Buffer
1079 res.append("<span>")
1080 var mowner = public_owner
1081 if mowner != null then
1082 res.append(public_owner.html_namespace(mbuilder))
1083 res.append("::")
1084 end
1085 res.append(self.link(mbuilder))
1086 res.append("</span>")
1087 return res.to_s
1088 end
1089
1090 # Return the full comment of the module decorated with html
1091 fun html_full_comment(mbuilder: ModelBuilder): String do
1092 var res = new Buffer
1093 res.append("<div id='description'>")
1094 res.append("<pre class='text_label'>{mbuilder.mmodule2nmodule[self].comment}</pre>")
1095 res.append("<textarea class='edit' rows='1' cols='76' id='fileContent'></textarea>")
1096 res.append("<a id='cancelBtn'>Cancel</a>")
1097 res.append("<a id='commitBtn'>Commit</a>")
1098 res.append("<pre class='text_label' id='preSave' type='2'></pre>")
1099 res.append("</div>")
1100 return res.to_s
1101 end
1102 end
1103 redef class MPropDef
1104 super Comparable
1105 redef type OTHER: MPropDef
1106 redef fun <(other: OTHER): Bool do return self.mproperty.name < other.mproperty.name
1107
1108 # Return a link (html a tag) to the nitdoc class page
1109 fun link(nprop: APropdef): String do
1110 return "<a href=\"{mclassdef.mclass.name}.html#{mproperty.anchor}\" title=\"{nprop.short_comment}\">{mproperty.name}</a>"
1111 end
1112 end
1113
1114 redef class MProperty
1115 super Comparable
1116 redef type OTHER: MProperty
1117 redef fun <(other: OTHER): Bool do return self.name < other.name
1118
1119 var is_redef: Bool
1120 var apropdef: nullable APropdef
1121
1122 redef init(intro_mclassdef: MClassDef, name: String, visibility: MVisibility)
1123 do
1124 super
1125 is_redef = false
1126 end
1127
1128 fun local_class: MClass do
1129 var classdef = self.intro_mclassdef
1130 return classdef.mclass
1131 end
1132
1133 fun anchor: String do
1134 return "PROP_{c_name}"
1135 end
1136
1137 # Escape name for html output
1138 redef fun name do return super.html_escape
1139 end
1140
1141 redef class MClass
1142 super Comparable
1143 redef type OTHER: MClass
1144 redef fun <(other: OTHER): Bool do return self.name < other.name
1145
1146 # Add type parameters
1147 fun html_signature: String do
1148 if arity > 0 then
1149 return "{name}[{intro.parameter_names.join(", ")}]"
1150 else
1151 return name
1152 end
1153 end
1154
1155 # Return a link (html a tag) to the nitdoc class page
1156 fun link(aclass: AStdClassdef): String do
1157 return "<a href='{name}.html' title=\"{aclass.short_comment}\">{html_signature}</a>"
1158 end
1159
1160 # Associate all MMethods to each MModule concerns
1161 fun all_methods: HashMap[MModule, Set[MMethod]] do
1162 var hm = new HashMap[MModule, Set[MMethod]]
1163 for mmodule, childs in concerns do
1164 if not hm.has_key(mmodule) then hm[mmodule] = new HashSet[MMethod]
1165 for prop in intro_methods do
1166 if mmodule == prop.intro_mclassdef.mmodule then
1167 prop.is_redef = false
1168 hm[mmodule].add(prop)
1169 end
1170 end
1171 for prop in redef_methods do
1172 if mmodule == prop.intro_mclassdef.mmodule then
1173 prop.is_redef = true
1174 hm[mmodule].add(prop)
1175 end
1176 end
1177
1178 if childs != null then
1179 for child in childs do
1180 if not hm.has_key(child) then hm[child] = new HashSet[MMethod]
1181 for prop in intro_methods do
1182 if child == prop.intro_mclassdef.mmodule then
1183 prop.is_redef = false
1184 hm[child].add(prop)
1185 end
1186 end
1187 for prop in redef_methods do
1188 if child == prop.intro_mclassdef.mmodule then
1189 prop.is_redef = true
1190 hm[child].add(prop)
1191 end
1192 end
1193 end
1194 end
1195 end
1196 return hm
1197 end
1198
1199 fun public_owner: MModule do
1200 var owner = intro_mmodule
1201 if owner.public_owner is null then
1202 return owner
1203 else
1204 return owner.public_owner.as(not null)
1205 end
1206 end
1207
1208 # Associate MClass to all MMethod include in 'inherited_methods'
1209 fun inherited: HashMap[MClass, Set[MMethod]] do
1210 var hm = new HashMap[MClass, Set[MMethod]]
1211 for method in inherited_methods do
1212 var mclass = method.intro_mclassdef.mclass
1213 if not hm.has_key(mclass) then hm[mclass] = new HashSet[MMethod]
1214 hm[mclass].add(method)
1215 end
1216 return hm
1217 end
1218
1219 # Return true if MModule concern contain subMModule
1220 fun has_mmodule(sub: MModule): Bool do
1221 for mmodule, childs in concerns do
1222 if childs is null then continue
1223 if childs.has(sub) then return true
1224 end
1225 return false
1226 end
1227
1228 fun mmethod(mprop2npropdef: Map[MProperty, APropdef]) do
1229 for const in constructors do
1230 if mprop2npropdef.has_key(const)then
1231 const.apropdef = mprop2npropdef[const].as(AMethPropdef)
1232 end
1233 end
1234
1235 for intro in intro_methods do
1236 if mprop2npropdef.has_key(intro)then
1237 if mprop2npropdef[intro] isa AMethPropdef then intro.apropdef = mprop2npropdef[intro].as(AMethPropdef)
1238 end
1239 end
1240
1241 for rd in redef_methods do
1242 if mprop2npropdef.has_key(rd)then
1243 if mprop2npropdef[rd] isa AMethPropdef then rd.apropdef = mprop2npropdef[rd].as(AMethPropdef)
1244 end
1245 end
1246 end
1247
1248 fun link_anchor: String do
1249 return "{name}.html"
1250 end
1251
1252 # Escape name for html output
1253 redef fun name do return super.html_escape
1254 end
1255
1256 redef class AStdClassdef
1257 private fun comment: String do
1258 var ret = new Buffer
1259 if n_doc != null then
1260 for t in n_doc.n_comment do ret.append(t.text.substring_from(1))
1261 end
1262 return ret.to_s.html_escape
1263 end
1264
1265 private fun short_comment: String do
1266 var ret = new Buffer
1267 if n_doc != null then ret.append(n_doc.n_comment.first.text.substring_from(2).replace("\n", ""))
1268 return ret.to_s.html_escape
1269 end
1270 end
1271
1272 redef class ASignature
1273 redef fun to_s do
1274 #TODO closures
1275 var ret = ""
1276 if not n_params.is_empty then
1277 ret = "{ret}({n_params.join(", ")})"
1278 end
1279 if n_type != null and n_type.to_s != "" then ret += ": {n_type.to_s}"
1280 return ret
1281 end
1282 end
1283
1284 redef class AParam
1285 redef fun to_s do
1286 var ret = "{n_id.text}"
1287 if n_type != null then
1288 ret = "{ret}: {n_type.to_s}"
1289 if n_dotdotdot != null then ret = "{ret}..."
1290 end
1291 return ret
1292 end
1293 end
1294
1295 redef class AType
1296 redef fun to_s do
1297 var ret = "<a href=\"{n_id.text}.html\">{n_id.text}</a>"
1298 if n_kwnullable != null then ret = "nullable {ret}"
1299 if not n_types.is_empty then ret = "{ret}[{n_types.join(", ")}]"
1300 return ret
1301 end
1302 end
1303
1304 redef class APropdef
1305 private fun short_comment: String is abstract
1306 private fun signature: String is abstract
1307 private fun comment: String is abstract
1308 end
1309
1310 redef class AAttrPropdef
1311 redef fun short_comment do
1312 var ret = new Buffer
1313 if n_doc != null then ret.append(n_doc.n_comment.first.text.substring_from(1))
1314 return ret.to_s.html_escape
1315 end
1316 end
1317
1318 redef class AMethPropdef
1319 redef fun short_comment do
1320 var ret = new Buffer
1321 if n_doc != null then ret.append(n_doc.n_comment.first.text.substring_from(2).replace("\n", ""))
1322 return ret.to_s.html_escape
1323 end
1324
1325 redef fun signature: String do
1326 var sign = ""
1327 if n_signature != null then sign = "{n_signature.to_s}"
1328 return sign
1329 end
1330
1331 redef private fun comment: String do
1332 var ret = new Buffer
1333 if n_doc != null then
1334 for t in n_doc.n_comment do ret.append(t.text.substring_from(1))
1335 end
1336 return ret.to_s.html_escape
1337 end
1338 end
1339
1340 redef class MClassDef
1341 private fun namespace(mclass: MClass): String do
1342
1343 if mmodule.public_owner is null then
1344 return "{mmodule.full_name}::{mclass.name}"
1345 else if mclass is self.mclass then
1346 return "{mmodule.public_owner.name}::{mclass.name}"
1347 else
1348 return "{mmodule.public_owner.name}::<a href=\"{mclass.name}.html\">{mclass.name}</a>"
1349 end
1350 end
1351 end
1352
1353 redef class Set[E]
1354 fun last: E do
1355 return to_a[length-1]
1356 end
1357 end
1358
1359 # Create a tool context to handle options and paths
1360 var toolcontext = new ToolContext
1361
1362 # Here we launch the nit index
1363 var nitdoc = new Nitdoc(toolcontext)
1364 nitdoc.start