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