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
23 var content
= new Buffer
24 fun add
(text
: String) do addn
("{text}\n")
25 fun addn
(text
: String) do content
.append
(text
.escape
)
26 fun add_rule
do add
("\n---\n")
27 fun render
do sys
.system
("echo \"{content}\
" | pager -r")
32 init(keyword
: String) do
33 self.keyword
= keyword
37 private class QueryPair
40 init(keyword
: String, category
: String) do
42 self.category
= category
46 # Main class of the nit index tool
47 # NitIndex build the model using the toolcontext argument
48 # then wait for query on std in to display documentation
50 private var toolcontext
: ToolContext
51 private var model
: Model
52 private var mbuilder
: ModelBuilder
53 private var mainmodule
: MModule
54 private var arguments
: Array[String]
56 init(toolcontext
: ToolContext) do
57 # We need a model to collect stufs
58 self.toolcontext
= toolcontext
59 self.toolcontext
.option_context
.options
.clear
60 self.arguments
= toolcontext
.option_context
.rest
62 if arguments
.is_empty
or arguments
.length
> 2 then
63 print
"usage: ni path/to/module.nit [expression]"
64 toolcontext
.option_context
.usage
69 mbuilder
= new ModelBuilder(model
, toolcontext
)
71 # Here we load an process std modules
72 #var dir = "NIT_DIR".environ
73 #var mmodules = modelbuilder.parse_and_build(["{dir}/lib/standard/standard.nit"])
74 var mmodules
= mbuilder
.parse
([arguments
.first
])
75 if mmodules
.is_empty
then return
77 assert mmodules
.length
== 1
78 self.mainmodule
= mmodules
.first
82 if arguments
.length
== 1 then
91 print
"Welcome in the Nit Index."
93 print
"Loaded modules:"
94 var mmodules
= new Array[MModule]
95 mmodules
.add_all
(model
.mmodules
)
96 var sorter
= new MModuleNameSorter
107 print
"\tname\t\tlookup module, class and property with the corresponding 'name'"
108 print
"\tparam: Type\tlookup methods using the corresponding 'Type' as parameter"
109 print
"\treturn: Type\tlookup methods returning the corresponding 'Type'"
110 print
"\tnew: Type\tlookup methods creating new instances of 'Type'"
111 print
"\t:h\t\tdisplay this help message"
118 seek
(stdin
.read_line
)
121 fun seek
(entry
: String) do
122 if entry
.is_empty
then
126 if entry
== ":h" then
131 if entry
== ":q" then exit
(0)
132 var pager
= new Pager
133 var query
= parse_query
(entry
)
134 if query
isa QueryPair then
136 if query
.category
== "return" then
137 var matches
= seek_returns
(query
.keyword
)
138 props_fulldoc
(pager
, matches
)
140 else if query
.category
== "param" then
141 var matches
= seek_params
(query
.keyword
)
142 props_fulldoc
(pager
, matches
)
144 else if query
.category
== "new" then
145 var matches
= seek_inits
(query
.keyword
)
146 props_fulldoc
(pager
, matches
)
150 var mmatches
= new List[MModule]
151 for m
in model
.mmodules
do
152 if m
.name
== entry
then mmatches
.add
(m
)
154 if not mmatches
.is_empty
then modules_fulldoc
(pager
, mmatches
)
156 var cmatches
= new List[MClass]
157 for c
in model
.mclasses
do
158 if c
.name
== entry
then cmatches
.add
(c
)
160 if not cmatches
.is_empty
then classes_fulldoc
(pager
, cmatches
)
161 # seek for properties
162 var matches
= new List[MProperty]
163 for p
in model
.mproperties
do
164 if p
.name
== entry
then matches
.add
(p
)
166 if not matches
.is_empty
then props_fulldoc
(pager
, matches
)
169 if pager
.content
.is_empty
then
170 print
"Nothing known about '{entry}', type ':h' for help"
174 if arguments
.length
== 1 then prompt
177 private fun parse_query
(str
: String): Query do
178 var parts
= str
.split_with
(":")
179 if parts
.length
== 1 then
180 return new Query(parts
[0])
182 var category
= parts
[0]
183 var keyword
= parts
[1]
184 if keyword
.first
== ' ' then keyword
= keyword
.substring_from
(1)
185 return new QueryPair(keyword
, category
)
189 private fun modules_fulldoc
(pager
: Pager, mmodules
: List[MModule]) do
190 for mmodule
in mmodules
do
192 pager
.add
("# {mmodule.namespace}\n".bold
)
194 if mbuilder
.mmodule2nmodule
.has_key
(mmodule
) then
195 var nmodule
= mbuilder
.mmodule2nmodule
[mmodule
]
196 if not nmodule
.n_moduledecl
.n_doc
== null then
197 for comment
in nmodule
.n_moduledecl
.n_doc
.comment
do pager
.add
(comment
.green
)
200 pager
.add
("{mmodule.prototype}\n")
202 var msorter
= new MModuleNameSorter
203 var ms
= mmodule
.in_importation
.greaters
.to_a
204 if ms
.length
> 1 then
206 pager
.add
("## imported modules".bold
)
208 for i
in [0..ms
.length
[ do
209 if ms
[i
] == mmodule
then continue
210 pager
.addn
(ms
[i
].name
)
211 if i
< ms
.length
- 1 then pager
.addn
(", ")
216 ms
= mmodule
.in_importation
.smallers
.to_a
217 if ms
.length
> 1 then
219 pager
.add
("## known modules".bold
)
221 for i
in [0..ms
.length
[ do
222 if ms
[i
] == mmodule
then continue
223 pager
.addn
(ms
[i
].name
)
224 if i
< ms
.length
- 1 then pager
.addn
(", ")
228 # local classes and interfaces
229 var sorter
= new MClassDefNameSorter
230 var intro_mclassdefs
= new Array[MClassDef]
231 var redef_mclassdefs
= new Array[MClassDef]
232 for mclassdef
in mmodule
.mclassdefs
do
233 if mclassdef
.is_intro
then
234 intro_mclassdefs
.add
(mclassdef
)
236 redef_mclassdefs
.add
(mclassdef
)
240 if not intro_mclassdefs
.is_empty
then
241 sorter
.sort
(intro_mclassdefs
)
242 pager
.add
("\n## introduced classes".bold
)
243 for mclassdef
in intro_mclassdefs
do
245 var nclass
= mbuilder
.mclassdef2nclassdef
[mclassdef
]
246 if nclass
isa AStdClassdef and not nclass
.n_doc
== null and not nclass
.n_doc
.short_comment
.is_empty
then
247 pager
.add
("\t{nclass.n_doc.short_comment.green}")
249 pager
.add
("\t{mclassdef.mclass.prototype}")
254 if not redef_mclassdefs
.is_empty
then
255 sorter
.sort
(redef_mclassdefs
)
256 pager
.add
("\n## refined classes".bold
)
257 for mclassdef
in redef_mclassdefs
do
260 var nclass
= mbuilder
.mclassdef2nclassdef
[mclassdef
]
261 if nclass
isa AStdClassdef and not nclass
.n_doc
== null and not nclass
.n_doc
.short_comment
.is_empty
then
262 pager
.add
("\t# {nclass.n_doc.short_comment.green}")
264 pager
.add
("\t{mclassdef.mclass.prototype}")
265 pager
.add
("\t\t" + "introduced in {mclassdef.mclass.intro.mmodule.namespace.bold}".gray
)
266 for odef
in mclassdef
.mclass
.mclassdefs
do
267 if odef
.is_intro
or odef
== mclassdef
or mclassdef
.mmodule
== mmodule
then continue
268 pager
.add
("\t\t" + "refined in {mclassdef.mmodule.namespace.bold}".gray
)
272 #TODO add inherited classes?
277 private fun classes_fulldoc
(pager
: Pager, mclasses
: List[MClass]) do
278 for mclass
in mclasses
do
280 pager
.add
("# {mclass.namespace}\n".bold
)
282 if mbuilder
.mclassdef2nclassdef
.has_key
(mclass
.intro
) then
283 var nclass
= mbuilder
.mclassdef2nclassdef
[mclass
.intro
]
284 if nclass
isa AStdClassdef and not nclass
.n_doc
== null then
285 for comment
in nclass
.n_doc
.comment
do pager
.add
(comment
.green
)
288 pager
.addn
("{mclass.prototype}")
289 if not mclass
.in_hierarchy
(mainmodule
).direct_greaters
.is_empty
then
290 var supers
= mclass
.in_hierarchy
(mainmodule
).direct_greaters
.to_a
291 pager
.addn
(" super ")
292 for i
in [0..supers
.length
[ do
293 if supers
[i
] == mclass
then continue
294 pager
.addn
("{supers[i].name}{supers[i].signature}")
295 if i
< mclass
.in_hierarchy
(mainmodule
).direct_greaters
.length
-1 then pager
.addn
(", ")
300 if not mclass
.parameter_types
.is_empty
then
301 pager
.add
("## formal types".bold
)
302 for ft
, bound
in mclass
.parameter_types
do
304 pager
.add
("\t{ft.to_s.green}: {bound.to_console}")
309 var cats
= new ArrayMap[String, Set[MPropDef]]
310 cats
["virtual types"] = new HashSet[MPropDef]
311 cats
["constructors"] = new HashSet[MPropDef]
312 cats
["introduced methods"] = new HashSet[MPropDef]
313 cats
["refined methods"] = new HashSet[MPropDef]
315 for mclassdef
in mclass
.mclassdefs
do
316 for mpropdef
in mclassdef
.mpropdefs
do
317 if mpropdef
isa MAttributeDef then continue
318 if mpropdef
isa MVirtualTypeDef then cats
["virtual types"].add
(mpropdef
)
319 if mpropdef
isa MMethodDef then
320 if mpropdef
.mproperty
.is_init
then
321 cats
["constructors"].add
(mpropdef
)
322 else if mpropdef
.is_intro
then
323 cats
["introduced methods"].add
(mpropdef
)
325 cats
["refined methods"].add
(mpropdef
)
331 for cat
, list
in cats
do
332 if not list
.is_empty
then
334 var sorted
= new Array[MPropDef]
336 var sorter
= new MPropDefNameSorter
338 pager
.add
("## {cat}".bold
)
339 for mpropdef
in sorted
do
341 if mbuilder
.mpropdef2npropdef
.has_key
(mpropdef
) then
342 var nprop
= mbuilder
.mpropdef2npropdef
[mpropdef
]
343 if not nprop
.n_doc
== null and not nprop
.n_doc
.comment
.is_empty
then
344 for comment
in nprop
.n_doc
.comment
do pager
.add
("\t{comment.green}")
346 nprop
= mbuilder
.mpropdef2npropdef
[mpropdef
.mproperty
.intro
]
347 if not nprop
.n_doc
== null and not nprop
.n_doc
.comment
.is_empty
then
348 for comment
in nprop
.n_doc
.comment
do pager
.add
("\t{comment.green}")
352 pager
.add
("\t{mpropdef.to_console}")
353 mainmodule
.linearize_mpropdefs
(mpropdef
.mproperty
.mpropdefs
)
354 var previous_defs
= new Array[MPropDef]
355 for def
in mpropdef
.mproperty
.mpropdefs
do
356 if def
== mpropdef
then continue
357 if def
.is_intro
then continue
358 if mclass
.in_hierarchy
(mainmodule
) < def
.mclassdef
.mclass
then
359 previous_defs
.add
(def
)
362 if not mpropdef
.is_intro
then
363 pager
.add
("\t\t" + "introduced by {mpropdef.mproperty.intro.mclassdef.namespace.bold}".gray
)
365 if not previous_defs
.is_empty
then
366 for def
in previous_defs
do pager
.add
("\t\t" + "inherited from {def.mclassdef.namespace.bold}".gray
)
372 # inherited mproperties
373 var inhs
= new ArrayMap[MClass, Array[MProperty]]
374 var ancestors
= mclass
.in_hierarchy
(mainmodule
).greaters
.to_a
375 mainmodule
.linearize_mclasses
(ancestors
)
376 for a
in ancestors
do
377 if a
== mclass
then continue
378 for c
in a
.mclassdefs
do
379 for p
in c
.intro_mproperties
do
380 if p
.intro_mclassdef
== c
then
381 if not inhs
.has_key
(a
) then inhs
[a
] = new Array[MProperty]
387 if not inhs
.is_empty
then
388 pager
.add
("## inherited properties".bold
)
390 pager
.add
("\n\tfrom {a.namespace.bold}: {ps.join(", ")}")
397 private fun props_fulldoc
(pager
: Pager, raw_mprops
: List[MProperty]) do
399 var cats
= new HashMap[MModule, Array[MProperty]]
400 for mprop
in raw_mprops
do
401 if mprop
isa MAttribute then continue
402 var key
= mprop
.intro
.mclassdef
.mmodule
403 if not cats
.has_key
(key
) then cats
[key
] = new Array[MProperty]
407 var sorter
= new MModuleNameSorter
408 var sorted
= new Array[MModule]
409 sorted
.add_all
(cats
.keys
)
412 for mmodule
in sorted
do
413 var mprops
= cats
[mmodule
]
414 pager
.add
("# matches in module {mmodule.namespace.bold}")
415 var sorterp
= new MPropertyNameSorter
417 for mprop
in mprops
do
419 if mbuilder
.mpropdef2npropdef
.has_key
(mprop
.intro
) then
420 var nprop
= mbuilder
.mpropdef2npropdef
[mprop
.intro
]
421 if not nprop
.n_doc
== null and not nprop
.n_doc
.comment
.is_empty
then
422 for comment
in nprop
.n_doc
.comment
do pager
.add
("\t{comment.green}")
425 pager
.add
("\t{mprop.intro.to_console}")
426 pager
.add
("\t\t" + "introduced in {mprop.intro_mclassdef.namespace.bold}".gray
)
427 var mpropdefs
= mprop
.mpropdefs
428 mainmodule
.linearize_mpropdefs
(mpropdefs
)
429 for mpdef
in mpropdefs
do
430 if not mpdef
.is_intro
then
431 pager
.add
("\t\t" + "refined in {mpdef.mclassdef.namespace.bold}".gray
)
439 private fun seek_returns
(entry
: String): List[MProperty] do
440 var matches
= new List[MProperty]
441 for mprop
in model
.mproperties
do
442 var intro
= mprop
.intro
443 if intro
isa MMethodDef then
444 if intro
.msignature
.return_mtype
!= null and intro
.msignature
.return_mtype
.to_console
.has_prefix
(entry
) then matches
.add
(mprop
)
445 else if intro
isa MAttributeDef then
446 if intro
.static_mtype
.to_console
.has_prefix
(entry
) then matches
.add
(mprop
)
452 private fun seek_params
(entry
: String): List[MProperty] do
453 var matches
= new List[MProperty]
454 for mprop
in model
.mproperties
do
455 var intro
= mprop
.intro
456 if intro
isa MMethodDef then
457 var mparameters
= intro
.msignature
.mparameters
458 for mparameter
in mparameters
do
459 print mparameter
.mtype
.to_console
460 if mparameter
.mtype
.to_console
.has_prefix
(entry
) then matches
.add
(mprop
)
462 else if intro
isa MAttributeDef then
463 if intro
.static_mtype
.to_console
.has_prefix
(entry
) then matches
.add
(mprop
)
469 #TODO should be returning a List[MPropDef]
470 private fun seek_inits
(entry
: String): List[MProperty] do
471 var mtype2mpropdefs
= toolcontext
.nitx_phase
.mtype2mpropdefs
472 var matches
= new List[MProperty]
473 for mtype
in mtype2mpropdefs
.keys
do
474 if mtype
.to_console
.has_prefix
(entry
) then
475 for mpropdef
in mtype2mpropdefs
[mtype
] do
476 matches
.add
(mpropdef
.mproperty
)
486 redef class ToolContext
487 var nitx_phase
: NitxPhase = new NitxPhase(self, [typing_phase
])
490 # Compiler phase for nitx
491 private class NitxPhase
494 var mtype2mpropdefs
= new HashMap[MType, Set[MPropDef]]
495 redef fun process_npropdef
(npropdef
) do
496 var visitor
= new TypeInitVisitor
497 visitor
.enter_visit
(npropdef
)
498 for mtype
in visitor
.inits
do
499 if not mtype2mpropdefs
.has_key
(mtype
) then
500 mtype2mpropdefs
[mtype
] = new HashSet[MPropDef]
502 mtype2mpropdefs
[mtype
].add
(npropdef
.mpropdef
.as(not null))
507 # Visitor looking for initialized mtype (new T)
508 private class TypeInitVisitor
511 var inits
= new HashSet[MType]
512 redef fun visit
(node
)
516 if not node
isa ANewExpr then return
517 var mtype
= node
.n_type
.mtype
518 if mtype
!= null then inits
.add
(mtype
)
522 # Printing facilities
525 # prototype of the module
526 # module ownername::name
527 private fun prototype
: String do return "module {name}"
529 # namespace of the module
531 private fun namespace
: String do
532 if public_owner
== null then
535 return "{public_owner.namespace}::{self.name}"
541 # return the generic signature of the class
543 private fun signature
: String do
547 for i
in [0..intro
.parameter_names
.length
[ do
548 res
.append
(intro
.parameter_names
[i
])
549 if i
< intro
.parameter_names
.length
- 1 then res
.append
(", ")
556 # return the prototype of the class
557 # class name is displayed with colors depending on visibility
558 # abstract interface Foo[E]
559 private fun prototype
: String do
561 res
.append
("{kind} ")
562 if visibility
.to_s
== "public" then res
.append
("{name}{signature}".bold
.green
)
563 if visibility
.to_s
== "private" then res
.append
("{name}{signature}".bold
.red
)
564 if visibility
.to_s
== "protected" then res
.append
("{name}{signature}".bold
.yellow
)
568 private fun namespace
: String do
569 return "{intro_mmodule.namespace}::{name}"
573 redef class MClassDef
574 private fun namespace
: String do
575 return "{mmodule.full_name}::{mclass.name}"
579 redef class MProperty
580 fun to_console
: String do
581 if visibility
.to_s
== "public" then return name
.green
582 if visibility
.to_s
== "private" then return name
.red
583 if visibility
.to_s
== "protected" then return name
.yellow
589 fun to_console
: String is abstract
592 redef class MMethodDef
593 redef fun to_console
do
595 if not is_intro
then res
.append
("redef ")
596 if not mproperty
.is_init
then res
.append
("fun ")
597 res
.append
(mproperty
.to_console
.bold
)
598 if msignature
!= null then res
.append
(msignature
.to_console
)
599 # FIXME: modifiers should be accessible via the model
600 #if self isa ADeferredMethPropdef then ret = "{ret} is abstract"
601 #if self isa AInternMethPropdef then ret = "{ret} is intern"
602 #if self isa AExternMethPropdef then ret = "{ret} is extern"
607 redef class MVirtualTypeDef
608 redef fun to_console
do
611 res
.append
(mproperty
.to_console
.bold
)
612 res
.append
(": {bound.to_console}")
617 redef class MSignature
618 redef fun to_console
do
620 if not mparameters
.is_empty
then
622 for i
in [0..mparameters
.length
[ do
623 res
.append
(mparameters
[i
].to_console
)
624 if i
< mparameters
.length
- 1 then res
.append
(", ")
628 if return_mtype
!= null then
629 res
.append
(": {return_mtype.to_console}")
635 redef class MParameter
636 fun to_console
: String do
638 res
.append
("{name}: {mtype.to_console}")
639 if is_vararg
then res
.append
("...")
645 fun to_console
: String do return self.to_s
648 redef class MNullableType
649 redef fun to_console
do return "nullable {mtype.to_console}"
652 redef class MGenericType
653 redef fun to_console
do
655 res
.append
("{mclass.name}[")
656 for i
in [0..arguments
.length
[ do
657 res
.append
(arguments
[i
].to_console
)
658 if i
< arguments
.length
- 1 then res
.append
(", ")
665 redef class MParameterType
666 redef fun to_console
do return mclass
.intro
.parameter_names
[rank
]
669 redef class MVirtualType
670 redef fun to_console
do return mproperty
.name
674 private fun comment
: List[String] do
675 var res
= new List[String]
676 for t
in n_comment
do
677 res
.add
(t
.text
.replace
("\n", ""))
682 private fun short_comment
: String do
683 return n_comment
.first
.text
.replace
("\n", "")
687 redef class AAttrPropdef
688 private fun read_accessor
: String do
690 #FIXME bug with standard::stream::FDStream::fd
691 var name
= mreadpropdef
.mproperty
.name
692 if mpropdef
.mproperty
.visibility
.to_s
== "public" then ret
= "{ret}{name.green}"
693 if mpropdef
.mproperty
.visibility
.to_s
== "private" then ret
= "{ret}{name.red}"
694 if mpropdef
.mproperty
.visibility
.to_s
== "protected" then ret
= "{ret}{name.yellow}"
695 ret
= "{ret}: {n_type.to_s}"
696 if n_kwredef
!= null then ret
= "redef {ret}"
700 private fun write_accessor
: String do
702 var name
= "{mreadpropdef.mproperty.name}="
703 if n_readable
!= null and n_readable
.n_visibility
!= null then
704 if n_readable
.n_visibility
isa APublicVisibility then ret
= "{ret}{name.green}"
705 if n_readable
.n_visibility
isa APrivateVisibility then ret
= "{ret}{name.red}"
706 if n_readable
.n_visibility
isa AProtectedVisibility then ret
= "{ret}{name.yellow}"
708 ret
= "{ret}{name.red}"
710 ret
= "{ret}({mreadpropdef.mproperty.name}: {n_type.to_s})"
711 if n_kwredef
!= null then ret
= "redef {ret}"
716 # Redef String class to add a function to color the string
719 private fun add_escape_char
(escapechar
: String): String do
720 return "{escapechar}{self}\\033[0m"
723 private fun esc
: Char do return 27.ascii
724 private fun gray
: String do return add_escape_char
("{esc}[30m")
725 private fun red
: String do return add_escape_char
("{esc}[31m")
726 private fun green
: String do return add_escape_char
("{esc}[32m")
727 private fun yellow
: String do return add_escape_char
("{esc}[33m")
728 private fun blue
: String do return add_escape_char
("{esc}[34m")
729 private fun purple
: String do return add_escape_char
("{esc}[35m")
730 private fun cyan
: String do return add_escape_char
("{esc}[36m")
731 private fun light_gray
: String do return add_escape_char
("{esc}[37m")
732 private fun bold
: String do return add_escape_char
("{esc}[1m")
733 private fun underline
: String do return add_escape_char
("{esc}[4m")
735 private fun escape
: String
741 else if c
== '\0' then
743 else if c
== '"' then
745 else if c == '\\' then
747 else if c == '`' then
749 else if c.ascii < 32 then
750 b.append("\\{c.ascii.to_base(8, false)}")
759 # Create a tool context to handle options and paths
760 var toolcontext = new ToolContext
761 toolcontext.process_options
763 # Here we launch the nit index
764 var ni = new NitIndex(toolcontext)
767 # TODO seek subclasses and super classes <.<class> >.<class>
768 # TODO seek subclasses and super types <:<type> >:<type>
769 # TODO seek with regexp
770 # TODO standardize namespaces with private option