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