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