c9fc16636a4ecd1f3989d79eb48fbed883340aa1
[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 toolcontext
20 import exprbuilder
21
22 private class Pager
23 var content: String = ""
24 fun add(text: String) do addn("{text}\n")
25 fun addn(text: String) do content += text.escape
26 fun add_rule do add("\n---\n")
27 fun render do sys.system("echo \"{content}\" | pager -r")
28 end
29
30 class NitIndex
31 private var toolcontext: ToolContext
32 private var model: Model
33 private var mbuilder: ModelBuilder
34 private var mainmodule: MModule
35 private var arguments: Array[String]
36
37 init(toolcontext: ToolContext) do
38 # We need a model to collect stufs
39 self.toolcontext = toolcontext
40 self.arguments = toolcontext.option_context.rest
41
42 if 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 # quitting?
87 if entry.is_empty then exit(0)
88
89 var flag = false
90 for n in mbuilder.nmodules do
91 var m = n.mmodule
92 if m.name == entry then
93 flag = true
94 module_fulldoc(n)
95 end
96 end
97 for c in model.mclasses do
98 if c.name == entry then
99 if not mbuilder.mclassdef2nclassdef[c.intro] isa AStdClassdef then continue
100 flag = true
101 doc_class(c)
102 end
103 end
104 var matches = new List[MProperty]
105 for p in model.mproperties do
106 if p.name == entry then
107 flag = true
108 matches.add(p)
109 end
110 end
111 #if not matches.is_empty then doc_properties
112
113 if not flag then print "Nothing known about '{entry}'"
114 if arguments.length == 1 then prompt
115 end
116
117 private fun module_fulldoc(nmodule: AModule) do
118 var mmodule = nmodule.mmodule
119 var pager = new Pager
120 pager.add("# module {mmodule.name}\n".bold)
121 pager.add("import {mmodule.in_importation.direct_greaters.join(", ")}")
122 pager.add_rule
123 pager.addn(nmodule.comment.green)
124 pager.add_rule
125
126 var cats = new HashMap[String, Collection[MClass]]
127 cats["introduced classes"] = mmodule.intro_mclasses
128 cats["refined classes"] = mmodule.redef_mclasses
129 cats["inherited classes"] = mmodule.inherited_mclasses
130
131 for cat, list in cats do
132 if not list.is_empty then
133 pager.add("# {cat}\n".bold)
134 for mclass in list do
135 var nclass = mbuilder.mclassdef2nclassdef[mclass.intro].as(AStdClassdef)
136 if not nclass.short_comment.is_empty then
137 pager.add("\t# {nclass.short_comment}")
138 end
139 if cat == "refined classes" then
140 pager.add("\tredef {mclass.short_doc}")
141 else
142 pager.add("\t{mclass.short_doc}")
143 end
144 if not mclass.intro_mmodule == mmodule then
145 pager.add("\t\tintroduced in {mmodule.full_name}::{mclass}".gray)
146 end
147 pager.add("")
148 end
149 end
150 end
151 pager.add_rule
152 pager.render
153 end
154
155 fun doc_class(mclass: MClass) do
156 var nclass = mbuilder.mclassdef2nclassdef[mclass.intro].as(AStdClassdef)
157 var pager = new Pager
158 pager.add("# {mclass.intro_mmodule.public_owner.name}::{mclass.name}\n".bold)
159 pager.add("{mclass.short_doc} ")
160 pager.add_rule
161 pager.addn(nclass.comment.green)
162 pager.add_rule
163 #TODO VT
164 pager.add_rule
165
166 var cats = new HashMap[String, Collection[MMethod]]
167 cats["constructors"] = mclass.constructors
168 cats["introduced methods"] = mclass.intro_methods
169 cats["refined methods"] = mclass.redef_methods
170 cats["inherited methods"] = mclass.inherited_methods
171
172 for cat, list in cats do
173 if not list.is_empty then
174 pager.add("# {cat}\n".bold)
175 for mmethod in list do
176 #TODO verifier cast
177 var nmethod = mbuilder.mpropdef2npropdef[mmethod.intro].as(AMethPropdef)
178 if not nmethod.short_comment.is_empty then
179 pager.add("\t# {nmethod.short_comment}")
180 end
181 if cat == "refined methods" then
182 pager.add("\tredef {nmethod}")
183 else
184 pager.add("\t{nmethod}")
185 end
186 if not mmethod.intro_mclassdef == mclass.intro then
187 pager.add("\t\tintroduced in {mmethod.intro_mclassdef.namespace}::{mmethod}".gray)
188 end
189 pager.add("")
190 end
191 end
192 end
193 pager.render
194 end
195 end
196
197 # Model traversing
198
199 redef class MModule
200 # Get the list of mclasses refined in self
201 private fun redef_mclasses: Set[MClass] do
202 var mclasses = new HashSet[MClass]
203 for c in mclassdefs do
204 if not c.is_intro then mclasses.add(c.mclass)
205 end
206 return mclasses
207 end
208
209 private fun inherited_mclasses: Set[MClass] do
210 var mclasses = new HashSet[MClass]
211 for m in in_importation.greaters do
212 for c in m.mclassdefs do mclasses.add(c.mclass)
213 end
214 return mclasses
215 end
216 end
217
218 redef class MClass
219
220 redef fun to_s: String do
221 if arity > 0 then
222 return "{name}[{intro.parameter_names.join(", ")}]"
223 else
224 return name
225 end
226 end
227
228 private fun short_doc: String do
229 var ret = ""
230 if is_interface then ret = "interface {ret}"
231 if is_enum then ret = "enum {ret}"
232 if is_class then ret = "class {ret}"
233 if is_abstract then ret = "abstract {ret}"
234 if visibility.to_s == "public" then ret = "{ret}{to_s.green}"
235 if visibility.to_s == "private" then ret = "{ret}{to_s.red}"
236 if visibility.to_s == "protected" then ret = "{ret}{to_s.yellow}"
237 ret = "{ret} super {supers.join(", ")}"
238 return ret
239 end
240
241 private fun supers: Set[MClass] do
242 var ret = new HashSet[MClass]
243 for mclassdef in mclassdefs do
244 for mclasstype in mclassdef.supertypes do
245 ret.add(mclasstype.mclass)
246 end
247 end
248 return ret
249 end
250
251 # Get ancestors of the class (all super classes)
252 fun ancestors: Set[MClass] do
253 var lst = new HashSet[MClass]
254 for mclassdef in self.mclassdefs do
255 for super_mclassdef in mclassdef.in_hierarchy.greaters do
256 if super_mclassdef == mclassdef then continue # skip self
257 lst.add(super_mclassdef.mclass)
258 end
259 end
260 return lst
261 end
262
263 # Get the list of class constructors
264 private fun constructors: Set[MMethod] do
265 var res = new HashSet[MMethod]
266 for mclassdef in mclassdefs do
267 for mpropdef in mclassdef.mpropdefs do
268 if mpropdef isa MMethodDef then
269 if mpropdef.mproperty.is_init then res.add(mpropdef.mproperty)
270 end
271 end
272 end
273 return res
274 end
275
276 # Get the list of intro methods
277 private fun intro_methods: Set[MMethod] do
278 var res = new HashSet[MMethod]
279 for mclassdef in mclassdefs do
280 for mpropdef in mclassdef.mpropdefs do
281 if mpropdef isa MMethodDef then
282 if mpropdef.is_intro and not mpropdef.mproperty.is_init then res.add(mpropdef.mproperty)
283 end
284 end
285 end
286 return res
287 end
288
289 # Get the list of locally refined methods
290 private fun redef_methods: Set[MMethod] do
291 var res = new HashSet[MMethod]
292 for mclassdef in mclassdefs do
293 for mpropdef in mclassdef.mpropdefs do
294 if mpropdef isa MMethodDef then
295 if not mpropdef.is_intro and not mpropdef.mproperty.is_init then res.add(mpropdef.mproperty)
296 end
297 end
298 end
299 return res
300 end
301
302 # Get the list of locally refined methods
303 private fun inherited_methods: Set[MMethod] do
304 var res = new HashSet[MMethod]
305 for s in ancestors do
306 for m in s.intro_methods do
307 if not self.intro_methods.has(m) and not self.redef_methods.has(m) then res.add(m)
308 end
309 end
310 return res
311 end
312
313 private fun is_class: Bool do
314 return self.kind == concrete_kind or self.kind == abstract_kind
315 end
316
317 private fun is_interface: Bool do
318 return self.kind == interface_kind
319 end
320
321 private fun is_enum: Bool do
322 return self.kind == enum_kind
323 end
324
325 private fun is_abstract: Bool do
326 return self.kind == abstract_kind
327 end
328 end
329
330 redef class MClassDef
331 private fun namespace: String do
332 return "{mmodule.full_name}::{mclass.name}"
333 end
334 end
335
336 # ANode printing
337
338 redef class AModule
339 private fun comment: String do
340 var ret = ""
341 for t in n_moduledecl.n_doc.n_comment do
342 ret += "{t.text.replace("# ", "")}"
343 end
344 return ret
345 end
346 end
347
348 redef class AStdClassdef
349 private fun comment: String do
350 var ret = ""
351 if n_doc != null then
352 for t in n_doc.n_comment do
353 var txt = t.text.replace("# ", "")
354 txt = txt.replace("#", "")
355 ret += "{txt}"
356 end
357 end
358 return ret
359 end
360
361 private fun short_comment: String do
362 var ret = ""
363 if n_doc != null then
364 var txt = n_doc.n_comment.first.text
365 txt = txt.replace("# ", "")
366 txt = txt.replace("\n", "")
367 ret += txt
368 end
369 return ret
370 end
371 end
372
373 redef class AMethPropdef
374 private fun short_comment: String do
375 var ret = ""
376 if n_doc != null then
377 var txt = n_doc.n_comment.first.text
378 txt = txt.replace("# ", "")
379 txt = txt.replace("\n", "")
380 ret += txt
381 end
382 return ret
383 end
384
385 redef fun to_s do
386 var ret = ""
387 if not mpropdef.mproperty.is_init then
388 ret = "fun "
389 end
390 if mpropdef.mproperty.visibility.to_s == "public" then ret = "{ret}{mpropdef.mproperty.name.green}"
391 if mpropdef.mproperty.visibility.to_s == "private" then ret = "{ret}{mpropdef.mproperty.name.red}"
392 if mpropdef.mproperty.visibility.to_s == "protected" then ret = "{ret}{mpropdef.mproperty.name.yellow}"
393 if n_signature != null then ret = "{ret}{n_signature.to_s}"
394 if n_kwredef != null then ret = "redef {ret}"
395 if self isa ADeferredMethPropdef then ret = "{ret} is abstract"
396 if self isa AInternMethPropdef then ret = "{ret} is intern"
397 if self isa AExternMethPropdef then ret = "{ret} is extern"
398 return ret
399 end
400 end
401
402 redef class ASignature
403 redef fun to_s do
404 #TODO closures
405 var ret = ""
406 if not n_params.is_empty then
407 ret = "{ret}({n_params.join(", ")})"
408 end
409 if n_type != null then ret += ": {n_type.to_s}"
410 return ret
411 end
412 end
413
414 redef class AParam
415 redef fun to_s do
416 var ret = "{n_id.text}"
417 if n_type != null then
418 ret = "{ret}: {n_type.to_s}"
419 if n_dotdotdot != null then ret = "{ret}..."
420 end
421 return ret
422 end
423 end
424
425 redef class AType
426 redef fun to_s do
427 var ret = n_id.text
428 if n_kwnullable != null then ret = "nullable {ret}"
429 if not n_types.is_empty then ret = "{ret}[{n_types.join(", ")}]"
430 return ret
431 end
432 end
433
434 # Redef String class to add a function to color the string
435 redef class String
436
437 private fun add_escape_char(escapechar: String): String do
438 return "{escapechar}{self}\\033[0m"
439 end
440
441 private fun esc: Char do return 27.ascii
442 private fun red: String do return add_escape_char("{esc}[1;31m")
443 private fun yellow: String do return add_escape_char("{esc}[1;33m")
444 private fun green: String do return add_escape_char("{esc}[1;32m")
445 private fun blue: String do return add_escape_char("{esc}[1;34m")
446 private fun cyan: String do return add_escape_char("{esc}[1;36m")
447 private fun gray: String do return add_escape_char("{esc}[30;1m")
448 private fun bold: String do return add_escape_char("{esc}[1m")
449 private fun underline: String do return add_escape_char("{esc}[4m")
450
451 private fun escape: String
452 do
453 var b = new Buffer
454 for c in self do
455 if c == '\n' then
456 b.append("\\n")
457 else if c == '\0' then
458 b.append("\\0")
459 else if c == '"' then
460 b.append("\\\"")
461 else if c == '\\' then
462 b.append("\\\\")
463 else if c == '`' then
464 b.append("'")
465 else if c.ascii < 32 then
466 b.append("\\{c.ascii.to_base(8, false)}")
467 else
468 b.add(c)
469 end
470 end
471 return b.to_s
472 end
473 end
474
475 # Create a tool context to handle options and paths
476 var toolcontext = new ToolContext
477 toolcontext.process_options
478
479 # Here we launch the nit index
480 var ni = new NitIndex(toolcontext)
481 ni.start
482
483 # TODO seek methods
484 # TODO lister les methods qui retournent un certain type
485 # TODO lister les methods qui utilisent un certain type
486 # TODO lister les sous-types connus d'une type
487 # TODO sorter par ordre alphabétique