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