ni_nitdoc: better formal type display
[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 if redef_mclasses.has(c) and c.intro_mmodule.public_owner != mmodule then
594 append("<li class='redef'>")
595 append("<span title='refined in this module'>R </span>")
596 else
597 append("<li class='intro'>")
598 append("<span title='introduced in this module'>I </span>")
599 end
600 append(c.link(mbuilder))
601 append("</li>")
602 end
603 append("</ul>")
604 append("</article>")
605 append("</div>")
606 end
607
608 fun properties do
609 var amodule = mbuilder.mmodule2nmodule[mmodule]
610 var mpropdefs = new HashSet[MPropDef]
611 for m in mmodule.in_nesting.greaters do
612 for c in m.mclassdefs do mpropdefs.add_all(c.mpropdefs)
613 end
614 for c in mmodule.mclassdefs do mpropdefs.add_all(c.mpropdefs)
615 var sorted = mpropdefs.to_a
616 var sorter = new ComparableSorter[MPropDef]
617 sorter.sort(sorted)
618 append("<article class='properties filterable'>")
619 append("<h2>Properties</h2>")
620 append("<ul>")
621 for p in sorted do
622 if p isa MAttributeDef then continue
623 if p.mproperty.visibility <= none_visibility then continue
624 if not mbuilder.mpropdef2npropdef.has_key(p) then continue
625 var nprop = mbuilder.mpropdef2npropdef[p]
626 if p.is_intro then
627 append("<li class='intro'>")
628 append("<span title='introduction'>I</span>&nbsp;{p.link(nprop)} ({p.mclassdef.mclass.name})")
629 append("</li>")
630 else
631 append("<li class='redef'>")
632 append("<span title='redefinition'>R</span>&nbsp;{p.link(nprop)} ({p.mclassdef.mclass.name})")
633 append("</li>")
634 end
635 end
636 append("</ul>")
637 append("</article>")
638 end
639 end
640
641 # A class page
642 class NitdocClass
643 super NitdocPage
644
645 private var mclass: MClass
646 private var mbuilder: ModelBuilder
647 private var nitdoc: Nitdoc
648
649 init(mclass: MClass, nitdoc: Nitdoc, dot_dir: nullable String, source: nullable String) do
650 self.mclass = mclass
651 self.mbuilder = nitdoc.modelbuilder
652 self.nitdoc = nitdoc
653 self.dot_dir = dot_dir
654 self.source = source
655 end
656
657 redef fun head do
658 super
659 var nclass = mbuilder.mclassdef2nclassdef[mclass.intro]
660 if nclass isa AStdClassdef then
661 append("<title>{mclass.name} class | {nclass.short_comment}</title>")
662 else
663 append("<title>{mclass.name} class</title>")
664 end
665 end
666
667 redef fun menu do
668 append("<li><a href='index.html'>Overview</a></li>")
669 var public_owner = mclass.public_owner
670 if public_owner is null then
671 append("<li>{mclass.intro_mmodule.link(mbuilder)}</li>")
672 else
673 append("<li>{public_owner.link(mbuilder)}</li>")
674 end
675 append("<li class='current'>{mclass.name}</li>")
676 append("<li><a href='full-index.html'>Full Index</a></li>")
677 end
678
679 redef fun content do
680 append("<div class='menu'>")
681 properties_column
682 inheritance_column
683 append("</div>")
684 append("<div class='content'>")
685 class_doc
686 append("</div>")
687 end
688
689 fun properties_column do
690 var sorter = new ComparableSorter[MPropDef]
691 append("<nav class='properties filterable'>")
692 append("<h3>Properties</h3>")
693 # get properties
694 var vtypes = new HashSet[MVirtualTypeDef]
695 var consts = new HashSet[MMethodDef]
696 var meths = new HashSet[MMethodDef]
697 for mclassdef in mclass.mclassdefs do
698 for mpropdef in mclassdef.mpropdefs do
699 if not mbuilder.mpropdef2npropdef.has_key(mpropdef) then continue
700 if mpropdef.mproperty.visibility <= none_visibility then continue
701 if mpropdef isa MVirtualTypeDef then vtypes.add(mpropdef)
702 if mpropdef isa MMethodDef then
703 if mpropdef.mproperty.is_init then
704 consts.add(mpropdef)
705 else
706 meths.add(mpropdef)
707 end
708 end
709 end
710 end
711 for mprop in mclass.inherited_methods do
712 var mpropdef = mprop.intro
713 if not mbuilder.mpropdef2npropdef.has_key(mpropdef) then continue
714 if mprop.visibility <= none_visibility then continue
715 if mprop.intro_mclassdef.mclass.name == "Object" then continue
716 meths.add(mpropdef)
717 end
718 # virtual types
719 if vtypes.length > 0 then
720 var vts = new Array[MVirtualTypeDef]
721 vts.add_all(vtypes)
722 sorter.sort(vts)
723 append("<h4>Virtual Types</h4>")
724 display_mpropdef_list(vts)
725 end
726 if consts.length > 0 then
727 var cts = new Array[MMethodDef]
728 cts.add_all(consts)
729 sorter.sort(cts)
730 append("<h4>Constructors</h4>")
731 display_mpropdef_list(cts)
732 end
733 if meths.length > 0 then
734 var mts = new Array[MMethodDef]
735 mts.add_all(meths)
736 sorter.sort(mts)
737 append("<h4>Methods</h4>")
738 display_mpropdef_list(mts)
739 end
740 append("</nav>")
741 end
742
743 private fun display_mpropdef_list(list: Array[MPropDef]) do
744 append("<ul>")
745 for prop in list do
746 var nprop = mbuilder.mpropdef2npropdef[prop]
747 if prop.is_intro and prop.mproperty.intro_mclassdef.mclass != mclass then
748 append("<li class='inherit'>")
749 append("<span title='Inherited'>H</span>")
750 else if prop.is_intro then
751 append("<li class='intro'>")
752 append("<span title='Introduced'>I</span>")
753 else
754 append("<li class='redef'>")
755 append("<span title='Redefined'>R</span>")
756 end
757 append(prop.link(nprop))
758 append("</li>")
759 end
760 append("</ul>")
761 end
762
763 fun inheritance_column do
764 var sorted = new Array[MClass]
765 var sorterp = new ComparableSorter[MClass]
766 append("<nav>")
767 append("<h3>Inheritance</h3>")
768 if mclass.ancestors.length > 1 then
769 sorted = mclass.ancestors.to_a
770 sorterp.sort(sorted)
771 append("<h4>Superclasses</h4>")
772 append("<ul>")
773 for sup in sorted do
774 if sup == mclass then continue
775 append("<li><a href='{sup.name}.html'>{sup.name}</a></li>")
776 end
777 append("</ul>")
778 end
779
780 if mclass.descendants.length <= 1 then
781 append("<h4>No Known Subclasses</h4>")
782 else if mclass.descendants.length <= 100 then
783 sorted = mclass.descendants.to_a
784 sorterp.sort(sorted)
785 append("<h4>Subclasses</h4>")
786 append("<ul>")
787 for sub in sorted do
788 if sub == mclass then continue
789 append("<li><a href='{sub.name}.html'>{sub.name}</a></li>")
790 end
791 append("</ul>")
792 else if mclass.children.length <= 100 then
793 sorted = mclass.children.to_a
794 sorterp.sort(sorted)
795 append("<h4>Direct Subclasses Only</h4>")
796 append("<ul>")
797 for sub in sorted do
798 if sub == mclass then continue
799 append("<li><a href='{sub.name}.html'>{sub.name}</a></li>")
800 end
801 append("</ul>")
802 else
803 append("<h4>Too much Subclasses to list</h4>")
804 end
805 append("</nav>")
806 end
807
808 fun class_doc do
809 # title
810 append("<h1>{mclass.html_signature}</h1>")
811 append("<div class='subtitle'>")
812 var subtitle = ""
813 if mclass.visibility is none_visibility then subtitle += "private "
814 subtitle += "{mclass.kind} {mclass.public_owner.html_namespace(mbuilder)}::{mclass}"
815 append(subtitle)
816 append("</div>")
817 # comment
818 var nclass = mbuilder.mclassdef2nclassdef[mclass.intro]
819 append("<div style=\"float: right;\"><a id=\"lblDiffCommit\"></a></div>")
820 append("<section class='description'>")
821 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>")
822 process_generate_dot
823 append("</section>")
824 # concerns
825 var sorted = new Array[MModule]
826 sorted.add_all(mclass.concerns.keys)
827 var sorterp = new ComparableSorter[MModule]
828 sorterp.sort(sorted)
829 append("<section class='concerns'>")
830 append("<h2 class='section-header'>Concerns</h2>")
831 append("<ul>")
832 for owner in sorted do
833 var nmodule = mbuilder.mmodule2nmodule[owner]
834 var childs = mclass.concerns[owner]
835 append("<li>")
836 append("<a href=\"#MOD_{owner.name}\">{owner.name}</a>: {nmodule.short_comment}")
837 if not childs is null then
838 append("<ul>")
839 var sortedc = childs.to_a
840 var sorterpc = new ComparableSorter[MModule]
841 sorterpc.sort(sortedc)
842 for child in sortedc do
843 var nchild = mbuilder.mmodule2nmodule[child]
844 append("<li><a href=\"#MOD_{child.name}\">{child.name}</a>: {nchild.short_comment} </li>")
845 end
846 append("</ul>")
847 end
848 append("</li>")
849 end
850 append("</ul>")
851 append("</section>")
852 # properties
853 var sorterprop = new ComparableSorter[MProperty]
854 var sorterc = new ComparableSorter[MClass]
855 var lmmodule = new List[MModule]
856 # virtual and formal types
857 if mclass.virtual_types.length > 0 or mclass.arity > 0 then
858 append("<section class='types'>")
859 append("<h2>Formal and Virtual Types</h2>")
860 if mclass.virtual_types.length > 0 then for prop in mclass.virtual_types do description(prop)
861 if mclass.arity > 0 and nclass isa AStdClassdef then
862 for ft, bound in mclass.parameter_types do
863 append("<article id='{ft}'>")
864 append("<h3 class='signature'>{ft}: {bound.link(mbuilder)}</h3>")
865 append("<div class=\"info\">formal generic type</div>")
866 append("</article>")
867 end
868 end
869 append("</section>")
870 end
871 # constructors
872 if mclass.constructors.length > 0 then
873 var sortedc = mclass.constructors.to_a
874 sorterprop.sort(sortedc)
875 append("<section class='constructors'>")
876 append("<h2 class='section-header'>Constructors</h2>")
877 for prop in sortedc do description(prop)
878 append("</section>")
879 end
880 # methods
881 append("<section class='methods'>")
882 append("<h2 class='section-header'>Methods</h2>")
883 for mmodule, mmethods in mclass.all_methods do
884 var nmodule = mbuilder.mmodule2nmodule[mmodule]
885 append("<a id=\"MOD_{mmodule.name}\"></a>")
886 if mmodule != mclass.intro_mmodule and mmodule != mclass.public_owner then
887 if mclass.has_mmodule(mmodule) then
888 append("<p class=\"concern-doc\">{mmodule.name}: {nmodule.short_comment}</p>")
889 else
890 append("<h3 class=\"concern-toplevel\">Methods refined in {mmodule.link(mbuilder)}</h3><p class=\"concern-doc\">{mmodule.name}: {nmodule.short_comment}</p>")
891 end
892 end
893 var sortedc = mmethods.to_a
894 sorterprop.sort(sortedc)
895 for prop in sortedc do description(prop)
896 end
897 # inherited methods
898 if mclass.inherited_methods.length > 0 then
899 var sortedc = new Array[MClass]
900 sortedc.add_all(mclass.inherited.keys)
901 sorterc.sort(sortedc)
902 append("<h3>Inherited Methods</h3>")
903 for imclass in sortedc do
904 var sortedp = mclass.inherited[imclass].to_a
905 sorterprop.sort(sortedp)
906 append("<p>Defined in {imclass.link(mbuilder)}: ")
907 for method in sortedp do
908 #TODO link to inherited propdef
909 append("<a href=\"\">{method.name}</a>")
910 if method != sortedp.last then append(", ")
911 end
912 append("</p>")
913 end
914 end
915 append("</section>")
916 end
917
918 fun description(prop: MProperty) do
919 if not mbuilder.mpropdef2npropdef.has_key(prop.intro) then return
920 var nprop = mbuilder.mpropdef2npropdef[prop.intro]
921 if not nprop isa AMethPropdef then return
922 var classes = new Array[String]
923 if nprop isa AInitPropdef then
924 classes.add("init")
925 else
926 classes.add("fun")
927 end
928 if prop.is_redef then classes.add("redef")
929 if prop.visibility == none_visibility then
930 classes.add("private")
931 else if prop.visibility == protected_visibility then
932 classes.add("protected")
933 else
934 classes.add("public")
935 end
936 append("<article class='{classes.join(" ")}' id='{prop.anchor}'>")
937 var sign = prop.name
938 append("<h3 class='signature'>{prop.name}{nprop.signature}</h3>")
939 append("<div class='info'>")
940 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>")
941 append("</div>")
942 append("<div class='description'>")
943 if nprop.comment == "" then
944 append("<a class=\"newComment\" title=\"32\" tag=\"\">New Comment</a>")
945 else
946 append("<pre class=\"text_label\" title=\"\" name=\"\" tag=\"\" type=\"1\">{nprop.comment}</pre>")
947 end
948 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>")
949 append("<p>")
950 if prop.local_class != mclass then
951 var mredef = prop.local_class.intro_mmodule
952 append("inherited from {mredef.link(mbuilder)} ")
953 end
954 #TODO display show code if doc github
955 var mintro = prop.intro_mclassdef.mmodule
956 append("defined by the module {mintro.link(mbuilder)}{if prop.apropdef is null then "" else show_source(prop.apropdef.location)}.")
957
958 for parent in mclass.parents do
959 var mparent = parent.intro_mmodule
960 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>.")
961 end
962 append("</p>")
963 append("</div>")
964 append("</article>")
965 end
966
967 fun process_generate_dot do
968 var pe = nitdoc.class_hierarchy[mclass]
969 var cla = new HashSet[MClass]
970 var sm = new HashSet[MClass]
971 var sm2 = new HashSet[MClass]
972 sm.add(mclass)
973 while cla.length + sm.length < 10 and sm.length > 0 do
974 cla.add_all(sm)
975 sm2.clear
976 for x in sm do
977 sm2.add_all(pe.poset[x].direct_smallers)
978 end
979 var t = sm
980 sm = sm2
981 sm2 = t
982 end
983 cla.add_all(pe.greaters)
984
985 var op = new Buffer
986 var name = "dep_{mclass.name}"
987 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")
988 for c in cla do
989 if c == mclass then
990 op.append("\"{c.name}\"[shape=box,margin=0.03];\n")
991 else
992 op.append("\"{c.name}\"[URL=\"{c.name}.html\"];\n")
993 end
994 for c2 in pe.poset[c].direct_greaters do
995 if not cla.has(c2) then continue
996 op.append("\"{c.name}\"->\"{c2.name}\";\n")
997 end
998 if not pe.poset[c].direct_smallers.is_empty then
999 var others = true
1000 for c2 in pe.poset[c].direct_smallers do
1001 if cla.has(c2) then others = false
1002 end
1003 if others then
1004 op.append("\"{c.name}...\"[label=\"\"];\n")
1005 op.append("\"{c.name}...\"->\"{c.name}\"[style=dotted];\n")
1006 end
1007 end
1008 end
1009 op.append("\}\n")
1010 generate_dot(op.to_s, name, "Dependency graph for class {mclass.name}")
1011 end
1012 end
1013
1014 redef class AModule
1015 private fun comment: String do
1016 var ret = new Buffer
1017 if n_moduledecl is null or n_moduledecl.n_doc is null then ret
1018 if n_moduledecl.n_doc is null then return ""
1019 for t in n_moduledecl.n_doc.n_comment do
1020 ret.append(t.text.substring_from(1))
1021 end
1022 return ret.to_s.html_escape
1023 end
1024
1025 private fun short_comment: String do
1026 var ret = new Buffer
1027 if n_moduledecl != null and n_moduledecl.n_doc != null then
1028 ret.append(n_moduledecl.n_doc.n_comment.first.text.substring_from(2).replace("\n", ""))
1029 end
1030 return ret.to_s.html_escape
1031 end
1032 end
1033
1034 redef class MModule
1035 super Comparable
1036 redef type OTHER: MModule
1037 redef fun <(other: OTHER): Bool do return self.name < other.name
1038
1039 # Get the list of all methods in a module
1040 fun imported_methods: Set[MMethod] do
1041 var methods = new HashSet[MMethod]
1042 for mclass in imported_mclasses do
1043 for method in mclass.intro_methods do
1044 methods.add(method)
1045 end
1046 end
1047 return methods
1048 end
1049
1050 # Get the list aof all refined methods in a module
1051 fun redef_methods: Set[MMethod] do
1052 var methods = new HashSet[MMethod]
1053 for mclass in redef_mclasses do
1054 for method in mclass.intro_methods do
1055 methods.add(method)
1056 end
1057 end
1058 return methods
1059 end
1060
1061 # Return a link (html a tag) to the nitdoc module page
1062 fun link(mbuilder: ModelBuilder): String do
1063 return "<a href='{name}.html' title='{mbuilder.mmodule2nmodule[self].short_comment}'>{name}</a>"
1064 end
1065
1066 # Return the module signature decorated with html
1067 fun html_signature(mbuilder: ModelBuilder): String do
1068 return "<span>module {html_namespace(mbuilder)}</span>"
1069 end
1070
1071 # Return the module namespace decorated with html
1072 fun html_namespace(mbuilder: ModelBuilder): String do
1073 var res = new Buffer
1074 res.append("<span>")
1075 var mowner = public_owner
1076 if mowner != null then
1077 res.append(public_owner.html_namespace(mbuilder))
1078 res.append("::")
1079 end
1080 res.append(self.link(mbuilder))
1081 res.append("</span>")
1082 return res.to_s
1083 end
1084
1085 # Return the full comment of the module decorated with html
1086 fun html_full_comment(mbuilder: ModelBuilder): String do
1087 var res = new Buffer
1088 res.append("<div id='description'>")
1089 res.append("<pre class='text_label'>{mbuilder.mmodule2nmodule[self].comment}</pre>")
1090 res.append("<textarea class='edit' rows='1' cols='76' id='fileContent'></textarea>")
1091 res.append("<a id='cancelBtn'>Cancel</a>")
1092 res.append("<a id='commitBtn'>Commit</a>")
1093 res.append("<pre class='text_label' id='preSave' type='2'></pre>")
1094 res.append("</div>")
1095 return res.to_s
1096 end
1097 end
1098 redef class MPropDef
1099 super Comparable
1100 redef type OTHER: MPropDef
1101 redef fun <(other: OTHER): Bool do return self.mproperty.name < other.mproperty.name
1102
1103 # Return a link (html a tag) to the nitdoc class page
1104 fun link(nprop: APropdef): String do
1105 return "<a href=\"{mclassdef.mclass.name}.html#{mproperty.anchor}\" title=\"{nprop.short_comment}\">{mproperty.name}</a>"
1106 end
1107 end
1108
1109 redef class MProperty
1110 super Comparable
1111 redef type OTHER: MProperty
1112 redef fun <(other: OTHER): Bool do return self.name < other.name
1113
1114 var is_redef: Bool
1115 var apropdef: nullable APropdef
1116
1117 redef init(intro_mclassdef: MClassDef, name: String, visibility: MVisibility)
1118 do
1119 super
1120 is_redef = false
1121 end
1122
1123 fun local_class: MClass do
1124 var classdef = self.intro_mclassdef
1125 return classdef.mclass
1126 end
1127
1128 fun anchor: String do
1129 return "PROP_{c_name}"
1130 end
1131
1132 # Escape name for html output
1133 redef fun name do return super.html_escape
1134 end
1135
1136 redef class MClass
1137 super Comparable
1138 redef type OTHER: MClass
1139 redef fun <(other: OTHER): Bool do return self.name < other.name
1140
1141 # Add type parameters
1142 fun html_signature: String do
1143 if arity > 0 then
1144 return "{name}[{intro.parameter_names.join(", ")}]"
1145 else
1146 return name
1147 end
1148 end
1149
1150 # Return a link (html a tag) to the nitdoc class page
1151 fun link(mbuilder: ModelBuilder): String do
1152 if mbuilder.mclassdef2nclassdef.has_key(intro) then
1153 var nclass = mbuilder.mclassdef2nclassdef[intro]
1154 if nclass isa AStdClassdef then
1155 return "<a href='{name}.html' title=\"{nclass.short_comment}\">{html_signature}</a>"
1156 else
1157 return "<a href='{name}.html'>{html_signature}</a>"
1158 end
1159 else
1160 return "<a href='{name}.html'>{html_signature}</a>"
1161 end
1162 end
1163
1164 # Associate all MMethods to each MModule concerns
1165 fun all_methods: HashMap[MModule, Set[MMethod]] do
1166 var hm = new HashMap[MModule, Set[MMethod]]
1167 for mmodule, childs in concerns do
1168 if not hm.has_key(mmodule) then hm[mmodule] = new HashSet[MMethod]
1169 for prop in intro_methods do
1170 if mmodule == prop.intro_mclassdef.mmodule then
1171 prop.is_redef = false
1172 hm[mmodule].add(prop)
1173 end
1174 end
1175 for prop in redef_methods do
1176 if mmodule == prop.intro_mclassdef.mmodule then
1177 prop.is_redef = true
1178 hm[mmodule].add(prop)
1179 end
1180 end
1181
1182 if childs != null then
1183 for child in childs do
1184 if not hm.has_key(child) then hm[child] = new HashSet[MMethod]
1185 for prop in intro_methods do
1186 if child == prop.intro_mclassdef.mmodule then
1187 prop.is_redef = false
1188 hm[child].add(prop)
1189 end
1190 end
1191 for prop in redef_methods do
1192 if child == prop.intro_mclassdef.mmodule then
1193 prop.is_redef = true
1194 hm[child].add(prop)
1195 end
1196 end
1197 end
1198 end
1199 end
1200 return hm
1201 end
1202
1203 fun public_owner: MModule do
1204 var owner = intro_mmodule
1205 if owner.public_owner is null then
1206 return owner
1207 else
1208 return owner.public_owner.as(not null)
1209 end
1210 end
1211
1212 # Associate MClass to all MMethod include in 'inherited_methods'
1213 fun inherited: HashMap[MClass, Set[MMethod]] do
1214 var hm = new HashMap[MClass, Set[MMethod]]
1215 for method in inherited_methods do
1216 var mclass = method.intro_mclassdef.mclass
1217 if not hm.has_key(mclass) then hm[mclass] = new HashSet[MMethod]
1218 hm[mclass].add(method)
1219 end
1220 return hm
1221 end
1222
1223 # Return true if MModule concern contain subMModule
1224 fun has_mmodule(sub: MModule): Bool do
1225 for mmodule, childs in concerns do
1226 if childs is null then continue
1227 if childs.has(sub) then return true
1228 end
1229 return false
1230 end
1231
1232 fun mmethod(mprop2npropdef: Map[MProperty, APropdef]) do
1233 for const in constructors do
1234 if mprop2npropdef.has_key(const)then
1235 const.apropdef = mprop2npropdef[const].as(AMethPropdef)
1236 end
1237 end
1238
1239 for intro in intro_methods do
1240 if mprop2npropdef.has_key(intro)then
1241 if mprop2npropdef[intro] isa AMethPropdef then intro.apropdef = mprop2npropdef[intro].as(AMethPropdef)
1242 end
1243 end
1244
1245 for rd in redef_methods do
1246 if mprop2npropdef.has_key(rd)then
1247 if mprop2npropdef[rd] isa AMethPropdef then rd.apropdef = mprop2npropdef[rd].as(AMethPropdef)
1248 end
1249 end
1250 end
1251
1252 fun link_anchor: String do
1253 return "{name}.html"
1254 end
1255
1256 # Escape name for html output
1257 redef fun name do return super.html_escape
1258 end
1259
1260 redef class AStdClassdef
1261 private fun comment: String do
1262 var ret = new Buffer
1263 if n_doc != null then
1264 for t in n_doc.n_comment do ret.append(t.text.substring_from(1))
1265 end
1266 return ret.to_s.html_escape
1267 end
1268
1269 private fun short_comment: String do
1270 var ret = new Buffer
1271 if n_doc != null then ret.append(n_doc.n_comment.first.text.substring_from(2).replace("\n", ""))
1272 return ret.to_s.html_escape
1273 end
1274 end
1275
1276 redef class ASignature
1277 redef fun to_s do
1278 #TODO closures
1279 var ret = ""
1280 if not n_params.is_empty then
1281 ret = "{ret}({n_params.join(", ")})"
1282 end
1283 if n_type != null and n_type.to_s != "" then ret += ": {n_type.to_s}"
1284 return ret
1285 end
1286 end
1287
1288 redef class AParam
1289 redef fun to_s do
1290 var ret = "{n_id.text}"
1291 if n_type != null then
1292 ret = "{ret}: {n_type.to_s}"
1293 if n_dotdotdot != null then ret = "{ret}..."
1294 end
1295 return ret
1296 end
1297 end
1298
1299 redef class MType
1300 fun link(mbuilder: ModelBuilder): String is abstract
1301 end
1302
1303 redef class MClassType
1304 redef fun link(mbuilder) do return mclass.link(mbuilder)
1305 end
1306
1307 redef class MNullableType
1308 redef fun link(mbuilder) do return "nullable {mtype.link(mbuilder)}"
1309 end
1310
1311 redef class AType
1312 fun link: String do
1313 var ret = "<a href=\"{n_id.text}.html\">{n_id.text}</a>"
1314 if n_kwnullable != null then ret = "nullable {ret}"
1315 if not n_types.is_empty then ret = "{ret}[{n_types.join(", ")}]"
1316 return ret
1317 end
1318
1319 fun name: String do return n_id.text.html_escape
1320 end
1321
1322 redef class APropdef
1323 private fun short_comment: String is abstract
1324 private fun signature: String is abstract
1325 private fun comment: String is abstract
1326 end
1327
1328 redef class AAttrPropdef
1329 redef fun short_comment do
1330 var ret = new Buffer
1331 if n_doc != null then ret.append(n_doc.n_comment.first.text.substring_from(1))
1332 return ret.to_s.html_escape
1333 end
1334 end
1335
1336 redef class AMethPropdef
1337 redef fun short_comment do
1338 var ret = new Buffer
1339 if n_doc != null then ret.append(n_doc.n_comment.first.text.substring_from(2).replace("\n", ""))
1340 return ret.to_s.html_escape
1341 end
1342
1343 redef fun signature: String do
1344 var sign = ""
1345 if n_signature != null then sign = "{n_signature.to_s}"
1346 return sign
1347 end
1348
1349 redef private fun comment: String do
1350 var ret = new Buffer
1351 if n_doc != null then
1352 for t in n_doc.n_comment do ret.append(t.text.substring_from(1))
1353 end
1354 return ret.to_s.html_escape
1355 end
1356 end
1357
1358 redef class MClassDef
1359 private fun namespace(mclass: MClass): String do
1360
1361 if mmodule.public_owner is null then
1362 return "{mmodule.full_name}::{mclass.name}"
1363 else if mclass is self.mclass then
1364 return "{mmodule.public_owner.name}::{mclass.name}"
1365 else
1366 return "{mmodule.public_owner.name}::<a href=\"{mclass.name}.html\">{mclass.name}</a>"
1367 end
1368 end
1369 end
1370
1371 redef class Set[E]
1372 fun last: E do
1373 return to_a[length-1]
1374 end
1375 end
1376
1377 # Create a tool context to handle options and paths
1378 var toolcontext = new ToolContext
1379
1380 # Here we launch the nit index
1381 var nitdoc = new Nitdoc(toolcontext)
1382 nitdoc.start