ni_nitdoc: added fast copy past utility to signatures.
[nit.git] / src / nitx.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 # nit index, is a command tool used to display documentation
16 module nitx
17
18 import model_utils
19 import modelize_property
20
21 private class Pager
22 var content = new Buffer
23 fun add(text: String) do addn("{text}\n")
24 fun addn(text: String) do content.append(text.escape)
25 fun add_rule do add("\n---\n")
26 fun render do sys.system("echo \"{content}\" | pager -r")
27 end
28
29 # Main class of the nit index tool
30 # NitIndex build the model using the toolcontext argument
31 # then wait for query on std in to display documentation
32 class NitIndex
33 private var toolcontext: ToolContext
34 private var model: Model
35 private var mbuilder: ModelBuilder
36 private var mainmodule: MModule
37 private var arguments: Array[String]
38
39 init(toolcontext: ToolContext) do
40 # We need a model to collect stufs
41 self.toolcontext = toolcontext
42 self.toolcontext.option_context.options.clear
43 self.arguments = toolcontext.option_context.rest
44
45 if arguments.is_empty or arguments.length > 2 then
46 print "usage: ni path/to/module.nit [expression]"
47 toolcontext.option_context.usage
48 exit(1)
49 end
50
51 model = new Model
52 mbuilder = new ModelBuilder(model, toolcontext)
53
54 # Here we load an process std modules
55 #var dir = "NIT_DIR".environ
56 #var mmodules = modelbuilder.parse_and_build(["{dir}/lib/standard/standard.nit"])
57 var mmodules = mbuilder.parse([arguments.first])
58 if mmodules.is_empty then return
59 mbuilder.run_phases
60 assert mmodules.length == 1
61 self.mainmodule = mmodules.first
62 end
63
64 fun start do
65 if arguments.length == 1 then
66 welcome
67 prompt
68 else
69 seek(arguments[1])
70 end
71 end
72
73 fun welcome do
74 print "Welcome in the Nit Index."
75 print ""
76 print "Loaded modules:"
77 var mmodules = new Array[MModule]
78 mmodules.add_all(model.mmodules)
79 var sorter = new MModuleNameSorter
80 sorter.sort(mmodules)
81 for m in mmodules do
82 print "\t{m.name}"
83 end
84 print ""
85 help
86 end
87
88 fun help do
89 print "\nCommands:"
90 print "\tname\t\tlookup module, class and property with the corresponding 'name'"
91 print "\tparam: Type\tlookup methods using the corresponding 'Type' as parameter"
92 print "\treturn: Type\tlookup methods returning the corresponding 'Type'"
93 print "\tEnter ':q' to exit"
94 print "\tEnter ':h' to display this help message"
95 print ""
96 end
97
98 fun prompt do
99 printn ">> "
100 seek(stdin.read_line)
101 end
102
103 fun seek(entry: String) do
104 if entry.is_empty then
105 prompt
106 return
107 end
108 if entry == ":h" then
109 help
110 prompt
111 return
112 end
113 if entry == ":q" then exit(0)
114 var pager = new Pager
115 # seek return types
116 if entry.has_prefix("return:") then
117 var ret = entry.split_with(":")[1].replace(" ", "")
118 var matches = seek_returns(ret)
119 props_fulldoc(pager, matches)
120 else if entry.has_prefix("param:") then
121 var param = entry.split_with(":")[1].replace(" ", "")
122 var matches = seek_params(param)
123 props_fulldoc(pager, matches)
124 else
125 # seek for modules
126 var mmatches = new List[MModule]
127 for m in model.mmodules do
128 if m.name == entry then mmatches.add(m)
129 end
130 if not mmatches.is_empty then modules_fulldoc(pager, mmatches)
131 # seek for classes
132 var cmatches = new List[MClass]
133 for c in model.mclasses do
134 if c.name == entry then cmatches.add(c)
135 end
136 if not cmatches.is_empty then classes_fulldoc(pager, cmatches)
137 # seek for properties
138 var matches = new List[MProperty]
139 for p in model.mproperties do
140 if p.name == entry then matches.add(p)
141 end
142 if not matches.is_empty then props_fulldoc(pager, matches)
143 end
144 # no matches
145 if pager.content.is_empty then
146 print "Nothing known about '{entry}', type ':h' for help"
147 else
148 pager.render
149 end
150 if arguments.length == 1 then prompt
151 end
152
153 private fun modules_fulldoc(pager: Pager, mmodules: List[MModule]) do
154 for mmodule in mmodules do
155 # name and prototype
156 pager.add("# {mmodule.namespace}\n".bold)
157 # comment
158 if mbuilder.mmodule2nmodule.has_key(mmodule) then
159 var nmodule = mbuilder.mmodule2nmodule[mmodule]
160 if not nmodule.n_moduledecl.n_doc == null then
161 for comment in nmodule.n_moduledecl.n_doc.comment do pager.add(comment.green)
162 end
163 end
164 pager.add("{mmodule.prototype}\n")
165 # imports
166 var msorter = new MModuleNameSorter
167 var ms = mmodule.in_importation.greaters.to_a
168 if ms.length > 1 then
169 msorter.sort(ms)
170 pager.add("## imported modules".bold)
171 pager.addn("\t")
172 for i in [0..ms.length[ do
173 if ms[i] == mmodule then continue
174 pager.addn(ms[i].name)
175 if i < ms.length - 1 then pager.addn(", ")
176 end
177 pager.add("\n")
178 end
179 # clients
180 ms = mmodule.in_importation.smallers.to_a
181 if ms.length > 1 then
182 msorter.sort(ms)
183 pager.add("## known modules".bold)
184 pager.addn("\t")
185 for i in [0..ms.length[ do
186 if ms[i] == mmodule then continue
187 pager.addn(ms[i].name)
188 if i < ms.length - 1 then pager.addn(", ")
189 end
190 pager.add("\n")
191 end
192 # local classes and interfaces
193 var sorter = new MClassDefNameSorter
194 var intro_mclassdefs = new Array[MClassDef]
195 var redef_mclassdefs = new Array[MClassDef]
196 for mclassdef in mmodule.mclassdefs do
197 if mclassdef.is_intro then
198 intro_mclassdefs.add(mclassdef)
199 else
200 redef_mclassdefs.add(mclassdef)
201 end
202 end
203 # intro
204 if not intro_mclassdefs.is_empty then
205 sorter.sort(intro_mclassdefs)
206 pager.add("\n## introduced classes".bold)
207 for mclassdef in intro_mclassdefs do
208 pager.add("")
209 var nclass = mbuilder.mclassdef2nclassdef[mclassdef]
210 if nclass isa AStdClassdef and not nclass.n_doc == null and not nclass.n_doc.short_comment.is_empty then
211 pager.add("\t{nclass.n_doc.short_comment.green}")
212 end
213 pager.add("\t{mclassdef.mclass.prototype}")
214 #TODO add redefs?
215 end
216 end
217 # redefs
218 if not redef_mclassdefs.is_empty then
219 sorter.sort(redef_mclassdefs)
220 pager.add("\n## refined classes".bold)
221 for mclassdef in redef_mclassdefs do
222 pager.add("")
223 #TODO intro comment?
224 var nclass = mbuilder.mclassdef2nclassdef[mclassdef]
225 if nclass isa AStdClassdef and not nclass.n_doc == null and not nclass.n_doc.short_comment.is_empty then
226 pager.add("\t# {nclass.n_doc.short_comment.green}")
227 end
228 pager.add("\t{mclassdef.mclass.prototype}")
229 pager.add("\t\t" + "introduced in {mclassdef.mclass.intro.mmodule.namespace.bold}".gray)
230 for odef in mclassdef.mclass.mclassdefs do
231 if odef.is_intro or odef == mclassdef or mclassdef.mmodule == mmodule then continue
232 pager.add("\t\t" + "refined in {mclassdef.mmodule.namespace.bold}".gray)
233 end
234 end
235 end
236 #TODO add inherited classes?
237 pager.add_rule
238 end
239 end
240
241 private fun classes_fulldoc(pager: Pager, mclasses: List[MClass]) do
242 for mclass in mclasses do
243 # title
244 pager.add("# {mclass.namespace}\n".bold)
245 # comment
246 if mbuilder.mclassdef2nclassdef.has_key(mclass.intro) then
247 var nclass = mbuilder.mclassdef2nclassdef[mclass.intro]
248 if nclass isa AStdClassdef and not nclass.n_doc == null then
249 for comment in nclass.n_doc.comment do pager.add(comment.green)
250 end
251 end
252 pager.addn("{mclass.prototype}")
253 if mclass.in_hierarchy(mainmodule).direct_greaters.length > 1 then
254 var supers = mclass.in_hierarchy(mainmodule).direct_greaters.to_a
255 pager.addn(" super ")
256 for i in [0..supers.length[ do
257 if supers[i] == mclass then continue
258 pager.addn(supers[i].name)
259 if i < mclass.in_hierarchy(mainmodule).direct_greaters.length -1 then pager.addn(", ")
260 end
261 pager.add("\n")
262 end
263 # formal types
264 if not mclass.parameter_types.is_empty then
265 pager.add("## formal types".bold)
266 for ft, bound in mclass.parameter_types do
267 pager.add("")
268 pager.add("\t{ft.to_s.green}: {bound}")
269 end
270 pager.add("")
271 end
272 # get properties
273 var cats = new ArrayMap[String, Set[MPropDef]]
274 cats["virtual types"] = new HashSet[MPropDef]
275 cats["constructors"] = new HashSet[MPropDef]
276 cats["introduced methods"] = new HashSet[MPropDef]
277 cats["refined methods"] = new HashSet[MPropDef]
278
279 for mclassdef in mclass.mclassdefs do
280 for mpropdef in mclassdef.mpropdefs do
281 if mpropdef isa MAttributeDef then continue
282 if mpropdef isa MVirtualTypeDef then cats["virtual types"].add(mpropdef)
283 if mpropdef isa MMethodDef then
284 if mpropdef.mproperty.is_init then
285 cats["constructors"].add(mpropdef)
286 else if mpropdef.is_intro then
287 cats["introduced methods"].add(mpropdef)
288 else
289 cats["refined methods"].add(mpropdef)
290 end
291 end
292 end
293 end
294 # local mproperties
295 for cat, list in cats do
296 if not list.is_empty then
297 #sort list
298 var sorted = new Array[MPropDef]
299 sorted.add_all(list)
300 var sorter = new MPropDefNameSorter
301 sorter.sort(sorted)
302 pager.add("## {cat}".bold)
303 for mpropdef in sorted do
304 pager.add("")
305 if mbuilder.mpropdef2npropdef.has_key(mpropdef) then
306 var nprop = mbuilder.mpropdef2npropdef[mpropdef]
307 if not nprop.n_doc == null and not nprop.n_doc.comment.is_empty then
308 for comment in nprop.n_doc.comment do pager.add("\t{comment.green}")
309 else
310 nprop = mbuilder.mpropdef2npropdef[mpropdef.mproperty.intro]
311 if not nprop.n_doc == null and not nprop.n_doc.comment.is_empty then
312 for comment in nprop.n_doc.comment do pager.add("\t{comment.green}")
313 end
314 end
315 end
316 pager.add("\t{mpropdef}")
317 mainmodule.linearize_mpropdefs(mpropdef.mproperty.mpropdefs)
318 var previous_defs = new Array[MPropDef]
319 for def in mpropdef.mproperty.mpropdefs do
320 if def == mpropdef then continue
321 if def.is_intro then continue
322 if mclass.in_hierarchy(mainmodule) < def.mclassdef.mclass then
323 previous_defs.add(def)
324 end
325 end
326 if not mpropdef.is_intro then
327 pager.add("\t\t" + "introduced by {mpropdef.mproperty.intro.mclassdef.namespace.bold}".gray)
328 end
329 if not previous_defs.is_empty then
330 for def in previous_defs do pager.add("\t\t" + "inherited from {def.mclassdef.namespace.bold}".gray)
331 end
332 end
333 pager.add("")
334 end
335 end
336 # inherited mproperties
337 var inhs = new ArrayMap[MClass, Array[MProperty]]
338 var ancestors = mclass.in_hierarchy(mainmodule).greaters.to_a
339 mainmodule.linearize_mclasses(ancestors)
340 for a in ancestors do
341 if a == mclass then continue
342 for c in a.mclassdefs do
343 for p in c.intro_mproperties do
344 if p.intro_mclassdef == c then
345 if not inhs.has_key(a) then inhs[a] = new Array[MProperty]
346 inhs[a].add(p)
347 end
348 end
349 end
350 end
351 if not inhs.is_empty then
352 pager.add("## inherited properties".bold)
353 for a, ps in inhs do
354 pager.add("\n\tfrom {a.namespace.bold}: {ps.join(", ")}")
355 end
356 end
357 pager.add_rule
358 end
359 end
360
361 private fun props_fulldoc(pager: Pager, raw_mprops: List[MProperty]) do
362 # group by module
363 var cats = new HashMap[MModule, Array[MProperty]]
364 for mprop in raw_mprops do
365 if mprop isa MAttribute then continue
366 var key = mprop.intro.mclassdef.mmodule
367 if not cats.has_key(key) then cats[key] = new Array[MProperty]
368 cats[key].add(mprop)
369 end
370 #sort groups
371 var sorter = new MModuleNameSorter
372 var sorted = new Array[MModule]
373 sorted.add_all(cats.keys)
374 sorter.sort(sorted)
375 # display
376 for mmodule in sorted do
377 var mprops = cats[mmodule]
378 pager.add("# matches in module {mmodule.namespace.bold}")
379 var sorterp = new MPropertyNameSorter
380 sorterp.sort(mprops)
381 for mprop in mprops do
382 pager.add("")
383 if mbuilder.mpropdef2npropdef.has_key(mprop.intro) then
384 var nprop = mbuilder.mpropdef2npropdef[mprop.intro]
385 if not nprop.n_doc == null and not nprop.n_doc.comment.is_empty then
386 for comment in nprop.n_doc.comment do pager.add("\t{comment.green}")
387 end
388 end
389 pager.add("\t{mprop.intro}")
390 pager.add("\t\t" + "introduced in {mprop.intro_mclassdef.namespace.bold}".gray)
391 var mpropdefs = mprop.mpropdefs
392 mainmodule.linearize_mpropdefs(mpropdefs)
393 for mpdef in mpropdefs do
394 if not mpdef.is_intro then
395 pager.add("\t\t" + "refined in {mpdef.mclassdef.namespace.bold}".gray)
396 end
397 end
398 end
399 pager.add_rule
400 end
401 end
402
403 private fun seek_returns(entry: String): List[MProperty] do
404 # TODO how to match with generic types?
405 var matches = new List[MProperty]
406 for mprop in model.mproperties do
407 var intro = mprop.intro
408 if intro isa MMethodDef then
409 if intro.msignature.return_mtype != null and intro.msignature.return_mtype.to_s == entry then matches.add(mprop)
410 else if intro isa MAttributeDef then
411 if intro.static_mtype.to_s == entry then matches.add(mprop)
412 end
413 end
414 return matches
415 end
416
417 private fun seek_params(entry: String): List[MProperty] do
418 # TODO how to match with generic types?
419 var matches = new List[MProperty]
420 for mprop in model.mproperties do
421 var intro = mprop.intro
422 if intro isa MMethodDef then
423 var mparameters = intro.msignature.mparameters
424 for mparameter in mparameters do
425 if mparameter.mtype.to_s == entry then matches.add(mprop)
426 end
427 else if intro isa MAttributeDef then
428 if intro.static_mtype.to_s == entry then matches.add(mprop)
429 end
430 end
431 return matches
432 end
433 end
434
435 # Printing facilities
436
437 redef class MModule
438 # prototype of the module
439 # module ownername::name
440 private fun prototype: String do return "module {name}"
441
442 # namespace of the module
443 # ownername::name
444 private fun namespace: String do
445 if public_owner == null then
446 return self.name
447 else
448 return "{public_owner.namespace}::{self.name}"
449 end
450 end
451 end
452
453 redef class MClass
454 # return the generic signature of the class
455 # [E, F]
456 private fun signature: String do
457 if arity > 0 then
458 return "[{intro.parameter_names.join(", ")}]"
459 else
460 return ""
461 end
462 end
463
464 # return the prototype of the class
465 # class name is displayed with colors depending on visibility
466 # abstract interface Foo[E]
467 private fun prototype: String do
468 var res = new Buffer
469 res.append("{kind} ")
470 if visibility.to_s == "public" then res.append("{name}{signature}".bold.green)
471 if visibility.to_s == "private" then res.append("{name}{signature}".bold.red)
472 if visibility.to_s == "protected" then res.append("{name}{signature}".bold.yellow)
473 return res.to_s
474 end
475
476 private fun namespace: String do
477 return "{intro_mmodule.namespace}::{name}"
478 end
479 end
480
481 redef class MClassDef
482 private fun namespace: String do
483 return "{mmodule.full_name}::{mclass.name}"
484 end
485 end
486
487 redef class MProperty
488 redef fun to_s do
489 if visibility.to_s == "public" then return name.green
490 if visibility.to_s == "private" then return name.red
491 if visibility.to_s == "protected" then return name.yellow
492 return name.bold
493 end
494 end
495
496 redef class MMethodDef
497 redef fun to_s do
498 var res = new Buffer
499 if not is_intro then res.append("redef ")
500 if not mproperty.is_init then res.append("fun ")
501 res.append(mproperty.to_s.bold)
502 if msignature != null then res.append(msignature.to_s)
503 # FIXME: modifiers should be accessible via the model
504 #if self isa ADeferredMethPropdef then ret = "{ret} is abstract"
505 #if self isa AInternMethPropdef then ret = "{ret} is intern"
506 #if self isa AExternMethPropdef then ret = "{ret} is extern"
507 return res.to_s
508 end
509 end
510
511 redef class MVirtualTypeDef
512 redef fun to_s do
513 var res = new Buffer
514 res.append("type ")
515 res.append(mproperty.to_s.bold)
516 res.append(": {bound.to_s}")
517 return res.to_s
518 end
519 end
520
521 redef class MSignature
522 redef fun to_s do
523 var res = new Buffer
524 if not mparameters.is_empty then
525 res.append("(")
526 for i in [0..mparameters.length[ do
527 res.append(mparameters[i].to_s)
528 if i < mparameters.length - 1 then res.append(", ")
529 end
530 res.append(")")
531 end
532 if return_mtype != null then
533 res.append(": {return_mtype.to_s}")
534 end
535 return res.to_s
536 end
537 end
538
539 redef class MParameter
540 redef fun to_s do
541 var res = new Buffer
542 res.append("{name}: {mtype}")
543 if is_vararg then res.append("...")
544 return res.to_s
545 end
546 end
547
548 redef class MNullableType
549 redef fun to_s do return "nullable {mtype}"
550 end
551
552 redef class MGenericType
553 redef fun to_s do
554 var res = new Buffer
555 res.append("{mclass.name}[")
556 for i in [0..arguments.length[ do
557 res.append(arguments[i].to_s)
558 if i < arguments.length - 1 then res.append(", ")
559 end
560 res.append("]")
561 return res.to_s
562 end
563 end
564
565 redef class MParameterType
566 redef fun to_s do return mclass.intro.parameter_names[rank]
567 end
568
569 redef class MVirtualType
570 redef fun to_s do return mproperty.intro.to_s
571 end
572
573 redef class ADoc
574 private fun comment: List[String] do
575 var res = new List[String]
576 for t in n_comment do
577 res.add(t.text.replace("\n", ""))
578 end
579 return res
580 end
581
582 private fun short_comment: String do
583 return n_comment.first.text.replace("\n", "")
584 end
585 end
586
587 redef class AAttrPropdef
588 private fun read_accessor: String do
589 var ret = "fun "
590 #FIXME bug with standard::stream::FDStream::fd
591 var name = mreadpropdef.mproperty.name
592 if mpropdef.mproperty.visibility.to_s == "public" then ret = "{ret}{name.green}"
593 if mpropdef.mproperty.visibility.to_s == "private" then ret = "{ret}{name.red}"
594 if mpropdef.mproperty.visibility.to_s == "protected" then ret = "{ret}{name.yellow}"
595 ret = "{ret}: {n_type.to_s}"
596 if n_kwredef != null then ret = "redef {ret}"
597 return ret
598 end
599
600 private fun write_accessor: String do
601 var ret = "fun "
602 var name = "{mreadpropdef.mproperty.name}="
603 if n_readable != null and n_readable.n_visibility != null then
604 if n_readable.n_visibility isa APublicVisibility then ret = "{ret}{name.green}"
605 if n_readable.n_visibility isa APrivateVisibility then ret = "{ret}{name.red}"
606 if n_readable.n_visibility isa AProtectedVisibility then ret = "{ret}{name.yellow}"
607 else
608 ret = "{ret}{name.red}"
609 end
610 ret = "{ret}({mreadpropdef.mproperty.name}: {n_type.to_s})"
611 if n_kwredef != null then ret = "redef {ret}"
612 return ret
613 end
614 end
615
616 # Redef String class to add a function to color the string
617 redef class String
618
619 private fun add_escape_char(escapechar: String): String do
620 return "{escapechar}{self}\\033[0m"
621 end
622
623 private fun esc: Char do return 27.ascii
624 private fun gray: String do return add_escape_char("{esc}[30m")
625 private fun red: String do return add_escape_char("{esc}[31m")
626 private fun green: String do return add_escape_char("{esc}[32m")
627 private fun yellow: String do return add_escape_char("{esc}[33m")
628 private fun blue: String do return add_escape_char("{esc}[34m")
629 private fun purple: String do return add_escape_char("{esc}[35m")
630 private fun cyan: String do return add_escape_char("{esc}[36m")
631 private fun light_gray: String do return add_escape_char("{esc}[37m")
632 private fun bold: String do return add_escape_char("{esc}[1m")
633 private fun underline: String do return add_escape_char("{esc}[4m")
634
635 private fun escape: String
636 do
637 var b = new Buffer
638 for c in self do
639 if c == '\n' then
640 b.append("\\n")
641 else if c == '\0' then
642 b.append("\\0")
643 else if c == '"' then
644 b.append("\\\"")
645 else if c == '\\' then
646 b.append("\\\\")
647 else if c == '`' then
648 b.append("'")
649 else if c.ascii < 32 then
650 b.append("\\{c.ascii.to_base(8, false)}")
651 else
652 b.add(c)
653 end
654 end
655 return b.to_s
656 end
657 end
658
659 # Create a tool context to handle options and paths
660 var toolcontext = new ToolContext
661 toolcontext.process_options
662
663 # Here we launch the nit index
664 var ni = new NitIndex(toolcontext)
665 ni.start
666
667 # TODO seek subclasses and super classes <.<class> >.<class>
668 # TODO seek subclasses and super types <:<type> >:<type>
669 # TODO seek with regexp
670 # TODO standardize namespaces with private option