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