1 # This file is part of NIT ( http://www.nitlanguage.org ).
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
15 # nit index, is a command tool used to display documentation
19 import modelize_property
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")
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
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]
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
45 if arguments
.is_empty
or arguments
.length
> 2 then
46 print
"usage: ni path/to/module.nit [expression]"
47 toolcontext
.option_context
.usage
52 mbuilder
= new ModelBuilder(model
, toolcontext
)
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
60 assert mmodules
.length
== 1
61 self.mainmodule
= mmodules
.first
65 if arguments
.length
== 1 then
74 print
"Welcome in the Nit Index."
76 print
"Loaded modules:"
77 var mmodules
= new Array[MModule]
78 mmodules
.add_all
(model
.mmodules
)
79 var sorter
= new MModuleNameSorter
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"
100 seek
(stdin
.read_line
)
103 fun seek
(entry
: String) do
104 if entry
.is_empty
then
108 if entry
== ":h" then
113 if entry
== ":q" then exit
(0)
114 var pager
= new Pager
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
)
126 var mmatches
= new List[MModule]
127 for m
in model
.mmodules
do
128 if m
.name
== entry
then mmatches
.add
(m
)
130 if not mmatches
.is_empty
then modules_fulldoc
(pager
, mmatches
)
132 var cmatches
= new List[MClass]
133 for c
in model
.mclasses
do
134 if c
.name
== entry
then cmatches
.add
(c
)
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
)
142 if not matches
.is_empty
then props_fulldoc
(pager
, matches
)
145 if pager
.content
.is_empty
then
146 print
"Nothing known about '{entry}', type ':h' for help"
150 if arguments
.length
== 1 then prompt
153 private fun modules_fulldoc
(pager
: Pager, mmodules
: List[MModule]) do
154 for mmodule
in mmodules
do
156 pager
.add
("# {mmodule.namespace}\n".bold
)
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
)
164 pager
.add
("{mmodule.prototype}\n")
166 var msorter
= new MModuleNameSorter
167 var ms
= mmodule
.in_importation
.greaters
.to_a
168 if ms
.length
> 1 then
170 pager
.add
("## imported modules".bold
)
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
(", ")
180 ms
= mmodule
.in_importation
.smallers
.to_a
181 if ms
.length
> 1 then
183 pager
.add
("## known modules".bold
)
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
(", ")
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
)
200 redef_mclassdefs
.add
(mclassdef
)
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
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}")
213 pager
.add
("\t{mclassdef.mclass.prototype}")
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
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}")
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
)
236 #TODO add inherited classes?
241 private fun classes_fulldoc
(pager
: Pager, mclasses
: List[MClass]) do
242 for mclass
in mclasses
do
244 pager
.add
("# {mclass.namespace}\n".bold
)
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
)
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
(", ")
264 if not mclass
.parameter_types
.is_empty
then
265 pager
.add
("## formal types".bold
)
266 for ft
, bound
in mclass
.parameter_types
do
268 pager
.add
("\t{ft.to_s.green}: {bound}")
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]
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
)
289 cats
["refined methods"].add
(mpropdef
)
295 for cat
, list
in cats
do
296 if not list
.is_empty
then
298 var sorted
= new Array[MPropDef]
300 var sorter
= new MPropDefNameSorter
302 pager
.add
("## {cat}".bold
)
303 for mpropdef
in sorted
do
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}")
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}")
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
)
326 if not mpropdef
.is_intro
then
327 pager
.add
("\t\t" + "introduced by {mpropdef.mproperty.intro.mclassdef.namespace.bold}".gray
)
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
)
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]
351 if not inhs
.is_empty
then
352 pager
.add
("## inherited properties".bold
)
354 pager
.add
("\n\tfrom {a.namespace.bold}: {ps.join(", ")}")
361 private fun props_fulldoc
(pager
: Pager, raw_mprops
: List[MProperty]) do
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]
371 var sorter
= new MModuleNameSorter
372 var sorted
= new Array[MModule]
373 sorted
.add_all
(cats
.keys
)
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
381 for mprop
in mprops
do
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}")
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
)
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
)
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
)
427 else if intro
isa MAttributeDef then
428 if intro
.static_mtype
.to_s
== entry
then matches
.add
(mprop
)
435 # Printing facilities
438 # prototype of the module
439 # module ownername::name
440 private fun prototype
: String do return "module {name}"
442 # namespace of the module
444 private fun namespace
: String do
445 if public_owner
== null then
448 return "{public_owner.namespace}::{self.name}"
454 # return the generic signature of the class
456 private fun signature
: String do
458 return "[{intro.parameter_names.join(", ")}]"
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
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
)
476 private fun namespace
: String do
477 return "{intro_mmodule.namespace}::{name}"
481 redef class MClassDef
482 private fun namespace
: String do
483 return "{mmodule.full_name}::{mclass.name}"
487 redef class MProperty
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
496 redef class MMethodDef
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"
511 redef class MVirtualTypeDef
515 res
.append
(mproperty
.to_s
.bold
)
516 res
.append
(": {bound.to_s}")
521 redef class MSignature
524 if not mparameters
.is_empty
then
526 for i
in [0..mparameters
.length
[ do
527 res
.append
(mparameters
[i
].to_s
)
528 if i
< mparameters
.length
- 1 then res
.append
(", ")
532 if return_mtype
!= null then
533 res
.append
(": {return_mtype.to_s}")
539 redef class MParameter
542 res
.append
("{name}: {mtype}")
543 if is_vararg
then res
.append
("...")
548 redef class MNullableType
549 redef fun to_s
do return "nullable {mtype}"
552 redef class MGenericType
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
(", ")
565 redef class MParameterType
566 redef fun to_s
do return mclass
.intro
.parameter_names
[rank
]
569 redef class MVirtualType
570 redef fun to_s
do return mproperty
.intro
.to_s
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", ""))
582 private fun short_comment
: String do
583 return n_comment
.first
.text
.replace
("\n", "")
587 redef class AAttrPropdef
588 private fun read_accessor
: String do
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}"
600 private fun write_accessor
: String do
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}"
608 ret
= "{ret}{name.red}"
610 ret
= "{ret}({mreadpropdef.mproperty.name}: {n_type.to_s})"
611 if n_kwredef
!= null then ret
= "redef {ret}"
616 # Redef String class to add a function to color the string
619 private fun add_escape_char
(escapechar
: String): String do
620 return "{escapechar}{self}\\033[0m"
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")
635 private fun escape
: String
641 else if c
== '\0' then
643 else if c
== '"' then
645 else if c == '\\' then
647 else if c == '`' then
649 else if c.ascii < 32 then
650 b.append("\\{c.ascii.to_base(8, false)}")
659 # Create a tool context to handle options and paths
660 var toolcontext = new ToolContext
661 toolcontext.process_options
663 # Here we launch the nit index
664 var ni = new NitIndex(toolcontext)
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