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