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