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