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