ni: added general comment for module ni
[nit.git] / src / ni.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 # ni or nit index, is a command tool used to display documentation
16 module ni
17
18 import model_utils
19
20 private class Pager
21 var content: String = ""
22 fun add(text: String) do addn("{text}\n")
23 fun addn(text: String) do content += text.escape
24 fun add_rule do add("\n---\n")
25 fun render do sys.system("echo \"{content}\" | pager -r")
26 end
27
28 class NitIndex
29 private var toolcontext: ToolContext
30 private var model: Model
31 private var mbuilder: ModelBuilder
32 private var mainmodule: MModule
33 private var arguments: Array[String]
34
35 init(toolcontext: ToolContext) do
36 # We need a model to collect stufs
37 self.toolcontext = toolcontext
38 self.toolcontext.option_context.options.clear
39 self.arguments = toolcontext.option_context.rest
40
41 if arguments.is_empty or arguments.length > 2 then
42 print "usage: ni path/to/module.nit [expression]"
43 toolcontext.option_context.usage
44 exit(1)
45 end
46
47 model = new Model
48 mbuilder = new ModelBuilder(model, toolcontext)
49
50 # Here we load an process std modules
51 #var dir = "NIT_DIR".environ
52 #var mmodules = modelbuilder.parse_and_build(["{dir}/lib/standard/standard.nit"])
53 var mmodules = mbuilder.parse_and_build([arguments.first])
54 if mmodules.is_empty then return
55 mbuilder.full_propdef_semantic_analysis
56 assert mmodules.length == 1
57 self.mainmodule = mmodules.first
58 end
59
60 fun start do
61 if arguments.length == 1 then
62 welcome
63 prompt
64 else
65 seek(arguments[1])
66 end
67 end
68
69 fun welcome do
70 print "Welcome in Nit Index.\n"
71 print "Loaded modules"
72 for m in mbuilder.nmodules do
73 print " - {m.mmodule.name}"
74 end
75 print "\nEnter the module, class or property name you want to look up."
76 print "Enter a blank line to exit.\n"
77 end
78
79 fun prompt do
80 printn ">> "
81 seek(stdin.read_line)
82 end
83
84 fun seek(entry: String) do
85 if entry.is_empty then exit(0)
86 var flag = false
87 # seek return types
88 if entry.has_prefix("return:") then
89 var ret = entry.split_with(":")[1].replace(" ", "")
90 var matches = seek_returns(ret)
91 if not matches.is_empty then
92 flag = true
93 props_fulldoc(matches)
94 end
95 else if entry.has_prefix("param:") then
96 var param = entry.split_with(":")[1].replace(" ", "")
97 var matches = seek_params(param)
98 if not matches.is_empty then
99 flag = true
100 props_fulldoc(matches)
101 end
102 else
103 # seek for modules
104 var mmatches = new List[MModule]
105 for m in model.mmodules do
106 if m.name == entry then
107 flag = true
108 mmatches.add(m)
109 end
110 end
111 if not mmatches.is_empty then modules_fulldoc(mmatches)
112 # seek for classes
113 var cmatches = new List[MClass]
114 for c in model.mclasses do
115 if c.name == entry then
116 flag = true
117 cmatches.add(c)
118 end
119 end
120 if not cmatches.is_empty then classes_fulldoc(cmatches)
121 # seek for properties
122 var matches = new List[MProperty]
123 for p in model.mproperties do
124 if p.name == entry then
125 flag = true
126 matches.add(p)
127 end
128 end
129 if not matches.is_empty then props_fulldoc(matches)
130 end
131 # no matches
132 if not flag then print "Nothing known about '{entry}'"
133 if arguments.length == 1 then prompt
134 end
135
136 private fun modules_fulldoc(mmodules: List[MModule]) do
137 var pager = new Pager
138 for mmodule in mmodules do
139 var nmodule = mbuilder.mmodule2nmodule[mmodule]
140 pager.add("# module {mmodule.namespace}\n".bold)
141 if not mmodule.in_importation.direct_greaters.is_empty then
142 pager.add("import ".bold + "{mmodule.in_importation.direct_greaters.join(", ")}\n")
143 end
144 if not mmodule.in_importation.direct_smallers.is_empty then
145 pager.add("known clients: ".bold + "{mmodule.in_importation.direct_smallers.join(", ")}\n")
146 end
147 pager.add_rule
148 pager.addn(nmodule.comment.green)
149 pager.add_rule
150
151 var cats = new HashMap[String, Collection[MClass]]
152 cats["introduced classes"] = mmodule.intro_mclasses
153 cats["refined classes"] = mmodule.redef_mclasses
154 cats["imported classes"] = mmodule.imported_mclasses
155
156 for cat, list in cats do
157 if not list.is_empty then
158 pager.add("\n# {cat}".bold)
159 #sort list
160 var sorted = new Array[MClass]
161 sorted.add_all(list)
162 var sorter = new ComparableSorter[MClass]
163 sorter.sort(sorted)
164 for mclass in sorted do
165 var nclass = mbuilder.mclassdef2nclassdef[mclass.intro].as(AStdClassdef)
166 pager.add("")
167 if not nclass.short_comment.is_empty then
168 pager.add("\t# {nclass.short_comment}")
169 end
170 if cat == "refined classes" then
171 pager.add("\tredef {mclass.short_doc}")
172 else
173 pager.add("\t{mclass.short_doc}")
174 end
175 if cat != "introduced classes" then
176 pager.add("\t\t" + "introduced in {mmodule.full_name}::{mclass}".gray)
177 end
178 for mclassdef in mclass.mclassdefs do
179 if mclassdef != mclass.intro then
180 pager.add("\t\t" + "refined in {mclassdef.namespace}".gray)
181 end
182 end
183 end
184 end
185 end
186 pager.add_rule
187 end
188 pager.render
189 end
190
191 private fun classes_fulldoc(mclasses: List[MClass]) do
192 var pager = new Pager
193 for mclass in mclasses do
194 var nclass = mbuilder.mclassdef2nclassdef[mclass.intro].as(AStdClassdef)
195
196 pager.add("# {mclass.namespace}\n".bold)
197 pager.add("{mclass.short_doc}")
198 pager.add_rule
199 pager.addn(nclass.comment.green)
200 pager.add_rule
201 if not mclass.parameter_types.is_empty then
202 pager.add("# formal types".bold)
203 for ft, bound in mclass.parameter_types do
204 pager.add("")
205 pager.add("\t{ft.to_s.green}: {bound}")
206 end
207 end
208 if not mclass.virtual_types.is_empty then
209 pager.add("# virtual types".bold)
210 for vt in mclass.virtual_types do
211 pager.add("")
212 vt_fulldoc(pager, vt)
213 end
214 end
215 pager.add_rule
216
217 var cats = new HashMap[String, Collection[MMethod]]
218 cats["constructors"] = mclass.constructors
219 cats["introduced methods"] = mclass.intro_methods
220 cats["refined methods"] = mclass.redef_methods
221 cats["inherited methods"] = mclass.inherited_methods
222
223 for cat, list in cats do
224 if not list.is_empty then
225 #sort list
226 var sorted = new Array[MMethod]
227 sorted.add_all(list)
228 var sorter = new ComparableSorter[MMethod]
229 sorter.sort(sorted)
230 pager.add("\n# {cat}".bold)
231 for mprop in sorted do
232 pager.add("")
233 method_fulldoc(pager, mprop)
234 end
235 end
236 end
237 pager.add_rule
238 end
239 pager.render
240 end
241
242 private fun props_fulldoc(raw_mprops: List[MProperty]) do
243 var pager = new Pager
244 # group by module
245 var cats = new HashMap[MClass, Array[MProperty]]
246 for mprop in raw_mprops do
247 if not mbuilder.mpropdef2npropdef.has_key(mprop.intro) then continue
248 if mprop isa MAttribute then continue
249 var mclass = mprop.intro_mclassdef.mclass
250 if not cats.has_key(mclass) then cats[mclass] = new Array[MProperty]
251 cats[mclass].add(mprop)
252 end
253 #sort groups
254 var sorter = new ComparableSorter[MClass]
255 var sorted = new Array[MClass]
256 sorted.add_all(cats.keys)
257 sorter.sort(sorted)
258 # display
259 for mclass in sorted do
260 var mprops = cats[mclass]
261 pager.add("# {mclass.namespace}".bold)
262 var sorterp = new ComparableSorter[MProperty]
263 sorterp.sort(mprops)
264 for mprop in mprops do
265 if mprop isa MMethod and mbuilder.mpropdef2npropdef.has_key(mprop.intro) then
266 pager.add("")
267 method_fulldoc(pager, mprop)
268 else if mprop isa MVirtualTypeProp then
269 pager.add("")
270 vt_fulldoc(pager, mprop)
271 end
272 end
273 pager.add_rule
274 end
275 pager.render
276 end
277
278 private fun seek_returns(entry: String): List[MProperty] do
279 # TODO how to match with generic types?
280 var matches = new List[MProperty]
281 for mprop in model.mproperties do
282 var intro = mprop.intro
283 if intro isa MMethodDef then
284 if intro.msignature.return_mtype != null and intro.msignature.return_mtype.to_s == entry then matches.add(mprop)
285 else if intro isa MAttributeDef then
286 if intro.static_mtype.to_s == entry then matches.add(mprop)
287 end
288 end
289 return matches
290 end
291
292 private fun seek_params(entry: String): List[MProperty] do
293 # TODO how to match with generic types?
294 var matches = new List[MProperty]
295 for mprop in model.mproperties do
296 var intro = mprop.intro
297 if intro isa MMethodDef then
298 var mparameters = intro.msignature.mparameters
299 for mparameter in mparameters do
300 if mparameter.mtype.to_s == entry then matches.add(mprop)
301 end
302 else if intro isa MAttributeDef then
303 if intro.static_mtype.to_s == entry then matches.add(mprop)
304 end
305 end
306 return matches
307 end
308
309 private fun method_fulldoc(pager: Pager, mprop: MMethod) do
310 if mbuilder.mpropdef2npropdef.has_key(mprop.intro) then
311 var nprop = mbuilder.mpropdef2npropdef[mprop.intro]
312 if not nprop.short_comment.is_empty then
313 pager.add("\t# {nprop.short_comment}")
314 end
315 if nprop isa AAttrPropdef then
316 pager.add("\t{nprop.read_accessor}")
317 pager.add("\t{nprop.write_accessor}")
318 else if nprop isa AMethPropdef then
319 pager.add("\t{nprop}")
320 end
321 pager.add("\t\t" + "introduced in {mprop.intro_mclassdef.namespace}".gray)
322 for mpropdef in mprop.mpropdefs do
323 if mpropdef != mprop.intro then
324 pager.add("\t\t" + "refined in {mpropdef.mclassdef.namespace}".gray)
325 end
326 end
327 end
328 end
329
330 private fun vt_fulldoc(pager: Pager, vt: MVirtualTypeProp) do
331 pager.add("\t{vt.short_doc}")
332 pager.add("\t\t" + "introduced in {vt.intro_mclassdef.namespace}::{vt}".gray)
333 for mpropdef in vt.mpropdefs do
334 if mpropdef != vt.intro then
335 pager.add("\t\t" + "refined in {mpropdef.mclassdef.namespace}".gray)
336 end
337 end
338 end
339 end
340
341 # Printing facilities
342
343 redef class MModule
344 super Comparable
345 redef type OTHER: MModule
346 redef fun <(other: OTHER): Bool do return self.name < other.name
347
348 private fun namespace: String do
349 return full_name
350 end
351 end
352
353 redef class MClass
354 super Comparable
355 redef type OTHER: MClass
356 redef fun <(other: OTHER): Bool do return self.name < other.name
357
358 redef fun to_s: String do
359 if arity > 0 then
360 return "{name}[{intro.parameter_names.join(", ")}]"
361 else
362 return name
363 end
364 end
365
366 private fun short_doc: String do
367 var ret = ""
368 if is_interface then ret = "interface {ret}"
369 if is_enum then ret = "enum {ret}"
370 if is_class then ret = "class {ret}"
371 if is_abstract then ret = "abstract {ret}"
372 if visibility.to_s == "public" then ret = "{ret}{to_s.green}"
373 if visibility.to_s == "private" then ret = "{ret}{to_s.red}"
374 if visibility.to_s == "protected" then ret = "{ret}{to_s.yellow}"
375 if not parents.is_empty then
376 ret = "{ret} super {parents.join(", ")}"
377 end
378 return ret
379 end
380
381 private fun namespace: String do
382 return "{intro_mmodule.public_owner.name}::{name}"
383 end
384 end
385
386 redef class MClassDef
387 private fun namespace: String do
388 return "{mmodule.full_name}::{mclass.name}"
389 end
390 end
391
392 redef class MProperty
393 super Comparable
394 redef type OTHER: MProperty
395 redef fun <(other: OTHER): Bool do return self.name < other.name
396 end
397
398 redef class MVirtualTypeProp
399 private fun short_doc: String do
400 var ret = ""
401 if visibility.to_s == "public" then ret = "{to_s.green}: {intro.bound.to_s}"
402 if visibility.to_s == "private" then ret = "\t{to_s.red}: {intro.bound.to_s}"
403 if visibility.to_s == "protected" then ret = "\t{to_s.yellow}: {intro.bound.to_s}"
404 return ret
405 end
406 end
407
408 redef class AModule
409 private fun comment: String do
410 var ret = ""
411 for t in n_moduledecl.n_doc.n_comment do
412 ret += "{t.text.replace("# ", "")}"
413 end
414 return ret
415 end
416 end
417
418 redef class AStdClassdef
419 private fun comment: String do
420 var ret = ""
421 if n_doc != null then
422 for t in n_doc.n_comment do
423 var txt = t.text.replace("# ", "")
424 txt = txt.replace("#", "")
425 ret += "{txt}"
426 end
427 end
428 return ret
429 end
430
431 private fun short_comment: String do
432 var ret = ""
433 if n_doc != null then
434 var txt = n_doc.n_comment.first.text
435 txt = txt.replace("# ", "")
436 txt = txt.replace("\n", "")
437 ret += txt
438 end
439 return ret
440 end
441 end
442
443 redef class APropdef
444 private fun comment: String do
445 var ret = ""
446 if n_doc != null then
447 for t in n_doc.n_comment do
448 var txt = t.text.replace("# ", "")
449 txt = txt.replace("#", "")
450 ret += "{txt}"
451 end
452 end
453 return ret
454 end
455
456 private fun short_comment: String do
457 var ret = ""
458 if n_doc != null then
459 var txt = n_doc.n_comment.first.text
460 txt = txt.replace("# ", "")
461 txt = txt.replace("\n", "")
462 ret += txt
463 end
464 return ret
465 end
466 end
467
468 redef class AAttrPropdef
469 private fun read_accessor: String do
470 var ret = "fun "
471 #FIXME bug with standard::stream::FDStream::fd
472 var name = mreadpropdef.mproperty.name
473 if mpropdef.mproperty.visibility.to_s == "public" then ret = "{ret}{name.green}"
474 if mpropdef.mproperty.visibility.to_s == "private" then ret = "{ret}{name.red}"
475 if mpropdef.mproperty.visibility.to_s == "protected" then ret = "{ret}{name.yellow}"
476 ret = "{ret}: {n_type.to_s}"
477 if n_kwredef != null then ret = "redef {ret}"
478 return ret
479 end
480
481 private fun write_accessor: String do
482 var ret = "fun "
483 var name = "{mreadpropdef.mproperty.name}="
484 if n_readable != null and n_readable.n_visibility != null then
485 if n_readable.n_visibility isa APublicVisibility then ret = "{ret}{name.green}"
486 if n_readable.n_visibility isa APrivateVisibility then ret = "{ret}{name.red}"
487 if n_readable.n_visibility isa AProtectedVisibility then ret = "{ret}{name.yellow}"
488 else
489 ret = "{ret}{name.red}"
490 end
491 ret = "{ret}({mreadpropdef.mproperty.name}: {n_type.to_s})"
492 if n_kwredef != null then ret = "redef {ret}"
493 return ret
494 end
495 end
496
497 redef class AMethPropdef
498 redef fun to_s do
499 var ret = ""
500 if not mpropdef.mproperty.is_init then
501 ret = "fun "
502 end
503 if mpropdef.mproperty.visibility.to_s == "public" then ret = "{ret}{mpropdef.mproperty.name.green}"
504 if mpropdef.mproperty.visibility.to_s == "private" then ret = "{ret}{mpropdef.mproperty.name.red}"
505 if mpropdef.mproperty.visibility.to_s == "protected" then ret = "{ret}{mpropdef.mproperty.name.yellow}"
506 if n_signature != null then ret = "{ret}{n_signature.to_s}"
507 if n_kwredef != null then ret = "redef {ret}"
508 if self isa ADeferredMethPropdef then ret = "{ret} is abstract"
509 if self isa AInternMethPropdef then ret = "{ret} is intern"
510 if self isa AExternMethPropdef then ret = "{ret} is extern"
511 return ret
512 end
513 end
514
515 redef class ASignature
516 redef fun to_s do
517 #TODO closures
518 var ret = ""
519 if not n_params.is_empty then
520 ret = "{ret}({n_params.join(", ")})"
521 end
522 if n_type != null then ret += ": {n_type.to_s}"
523 return ret
524 end
525 end
526
527 redef class AParam
528 redef fun to_s do
529 var ret = "{n_id.text}"
530 if n_type != null then
531 ret = "{ret}: {n_type.to_s}"
532 if n_dotdotdot != null then ret = "{ret}..."
533 end
534 return ret
535 end
536 end
537
538 redef class AType
539 redef fun to_s do
540 var ret = n_id.text
541 if n_kwnullable != null then ret = "nullable {ret}"
542 if not n_types.is_empty then ret = "{ret}[{n_types.join(", ")}]"
543 return ret
544 end
545 end
546
547 # Redef String class to add a function to color the string
548 redef class String
549
550 private fun add_escape_char(escapechar: String): String do
551 return "{escapechar}{self}\\033[0m"
552 end
553
554 private fun esc: Char do return 27.ascii
555 private fun red: String do return add_escape_char("{esc}[1;31m")
556 private fun yellow: String do return add_escape_char("{esc}[1;33m")
557 private fun green: String do return add_escape_char("{esc}[1;32m")
558 private fun blue: String do return add_escape_char("{esc}[1;34m")
559 private fun cyan: String do return add_escape_char("{esc}[1;36m")
560 private fun gray: String do return add_escape_char("{esc}[30;1m")
561 private fun bold: String do return add_escape_char("{esc}[1m")
562 private fun underline: String do return add_escape_char("{esc}[4m")
563
564 private fun escape: String
565 do
566 var b = new Buffer
567 for c in self do
568 if c == '\n' then
569 b.append("\\n")
570 else if c == '\0' then
571 b.append("\\0")
572 else if c == '"' then
573 b.append("\\\"")
574 else if c == '\\' then
575 b.append("\\\\")
576 else if c == '`' then
577 b.append("'")
578 else if c.ascii < 32 then
579 b.append("\\{c.ascii.to_base(8, false)}")
580 else
581 b.add(c)
582 end
583 end
584 return b.to_s
585 end
586 end
587
588 # Create a tool context to handle options and paths
589 var toolcontext = new ToolContext
590 toolcontext.process_options
591
592 # Here we launch the nit index
593 var ni = new NitIndex(toolcontext)
594 ni.start
595
596 # TODO seek subclasses and super classes <.<class> >.<class>
597 # TODO seek subclasses and super types <:<type> >:<type>
598 # TODO seek with regexp
599 # TODO standardize namespaces with private option