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
21 # Main class of the nit index tool
22 # NitIndex build the model using the toolcontext argument
23 # then wait for query on std in to display documentation
25 private var toolcontext
: ToolContext
26 private var model
: Model
27 private var mbuilder
: ModelBuilder
28 private var mainmodule
: MModule
29 private var arguments
: Array[String]
30 private var renderer
: PagerMatchesRenderer
32 # New constructor to use the pre-calculated model when interpreting a module
33 init with_infos
(mbuilder
: ModelBuilder, mmodule
: MModule) do
35 self.model
= mbuilder
.model
36 self.mbuilder
= mbuilder
38 self.mainmodule
= mmodule
39 self.toolcontext
= mbuilder
.toolcontext
40 self.arguments
= toolcontext
.option_context
.rest
42 renderer
= new PagerMatchesRenderer(self)
45 init(toolcontext
: ToolContext) do
46 # We need a model to collect stufs
47 self.toolcontext
= toolcontext
48 self.arguments
= toolcontext
.option_context
.rest
50 if arguments
.length
> 2 then
51 print toolcontext
.tooldescription
56 mbuilder
= new ModelBuilder(model
, toolcontext
)
58 var mmodules
= mbuilder
.parse
([arguments
.first
])
59 if mmodules
.is_empty
then return
61 assert mmodules
.length
== 1
62 self.mainmodule
= mmodules
.first
64 renderer
= new PagerMatchesRenderer(self)
68 if arguments
.length
== 1 then
77 print
"Welcome in the Nit Index."
79 print
"Loaded modules:"
80 var mmodules
= new Array[MModule]
81 mmodules
.add_all
(model
.mmodules
)
82 var sorter
= new MModuleNameSorter
93 print
"\tname\t\tlookup module, class and property with the corresponding 'name'"
94 print
"\tparam: Type\tlookup methods using the corresponding 'Type' as parameter"
95 print
"\treturn: Type\tlookup methods returning the corresponding 'Type'"
96 print
"\tnew: Type\tlookup methods creating new instances of 'Type'"
97 print
"\t:h\t\tdisplay this help message"
104 search
(sys
.stdin
.read_line
)
107 fun search
(entry
: String) do
108 if entry
.is_empty
then
112 if entry
== ":h" then
117 if entry
== ":q" then return
120 var query
= parse_query
(entry
)
123 var matches
= new HashSet[IndexMatch]
124 if query
isa IndexQueryPair then
125 if query
.category
== "return" then
127 matches
.add_all
(search_returns
(query
))
128 else if query
.category
== "param" then
130 matches
.add_all
(search_params
(query
))
131 else if query
.category
== "new" then
133 matches
.add_all
(search_inits
(query
))
136 matches
.add_all
(search_modules
(query
))
137 matches
.add_all
(search_classes
(query
))
138 matches
.add_all
(search_properties
(query
))
141 if matches
.is_empty
then
142 print
"Nothing known about '{query.string}', type ':h' for help"
144 renderer
.render_matches
(query
, matches
)
146 if arguments
.length
== 1 then prompt
149 private fun parse_query
(str
: String): IndexQuery do
150 var parts
= str
.split_with
(":")
151 if parts
.length
== 1 then
152 return new IndexQuery(str
, parts
[0])
154 var category
= parts
[0]
155 var keyword
= parts
[1]
156 if keyword
.chars
.first
== ' ' then keyword
= keyword
.substring_from
(1)
157 return new IndexQueryPair(str
, keyword
, category
)
162 private fun search_modules
(query
: IndexQuery): Set[MModule] do
163 var matches
= new HashSet[MModule]
164 for mmodule
in model
.mmodules
do
165 if mmodule
.name
== query
.keyword
then matches
.add
(mmodule
)
171 private fun search_classes
(query
: IndexQuery): Set[MClass] do
172 var matches
= new HashSet[MClass]
173 for mclass
in model
.mclasses
do
174 if mclass
.name
== query
.keyword
then matches
.add
(mclass
)
179 # search for properties
180 private fun search_properties
(query
: IndexQuery): Set[MProperty] do
181 var matches
= new HashSet[MProperty]
182 for mproperty
in model
.mproperties
do
183 if mproperty
.name
== query
.keyword
then matches
.add
(mproperty
)
188 # search for mpropdef returning keyword
189 private fun search_returns
(query
: IndexQuery): Set[MProperty] do
190 var matches
= new HashSet[MProperty]
191 for mproperty
in model
.mproperties
do
192 var intro
= mproperty
.intro
193 if intro
isa MMethodDef then
194 if intro
.msignature
.return_mtype
!= null and intro
.msignature
.return_mtype
.to_console
.has_prefix
(query
.keyword
) then matches
.add
(mproperty
)
195 else if intro
isa MAttributeDef then
196 if intro
.static_mtype
.to_console
.has_prefix
(query
.keyword
) then matches
.add
(mproperty
)
202 # search for mpropdef taking keyword as parameter
203 private fun search_params
(query
: IndexQuery): Set[MProperty] do
204 var matches
= new HashSet[MProperty]
205 for mproperty
in model
.mproperties
do
206 var intro
= mproperty
.intro
207 if intro
isa MMethodDef then
208 var mparameters
= intro
.msignature
.mparameters
209 for mparameter
in mparameters
do
210 if mparameter
.mtype
.to_console
.has_prefix
(query
.keyword
) then matches
.add
(mproperty
)
212 else if intro
isa MAttributeDef then
213 if intro
.static_mtype
.to_console
.has_prefix
(query
.keyword
) then matches
.add
(mproperty
)
219 # search for mpropdef creating new instance of keyword
220 private fun search_inits
(query
: IndexQuery): Set[MPropDef] do
221 var mtype2mpropdefs
= toolcontext
.nitx_phase
.mtype2mpropdefs
222 var matches
= new HashSet[MPropDef]
223 for mtype
in mtype2mpropdefs
.keys
do
224 if mtype
.to_console
.has_prefix
(query
.keyword
) then
225 for mpropdef
in mtype2mpropdefs
[mtype
] do
226 matches
.add
(mpropdef
)
234 private class IndexQuery
237 init(string
: String, keyword
: String) do
239 self.keyword
= keyword
243 private class IndexQueryPair
246 init(string
: String, keyword
: String, category
: String) do
247 super(string
, keyword
)
248 self.category
= category
252 # A match to a query in the nit index
253 private interface IndexMatch
254 # Short preview of the result for result list display
255 fun preview
(index
: NitIndex, output
: Pager) is abstract
256 fun content
(index
: NitIndex, output
: Pager) is abstract
261 redef class ToolContext
262 private var nitx_phase
: NitxPhase = new NitxPhase(self, [modelize_property_phase
])
265 # Compiler phase for nitx
266 private class NitxPhase
269 var mtype2mpropdefs
= new HashMap[MType, Set[MPropDef]]
270 redef fun process_npropdef
(npropdef
) do
271 var visitor
= new TypeInitVisitor
272 visitor
.enter_visit
(npropdef
)
273 for mtype
in visitor
.inits
do
274 if not mtype2mpropdefs
.has_key
(mtype
) then
275 mtype2mpropdefs
[mtype
] = new HashSet[MPropDef]
277 mtype2mpropdefs
[mtype
].add
(npropdef
.mpropdef
.as(not null))
282 # Visitor looking for initialized mtype (new T)
283 private class TypeInitVisitor
286 var inits
= new HashSet[MType]
287 redef fun visit
(node
)
291 if not node
isa ANewExpr then return
292 var mtype
= node
.n_type
.mtype
293 if mtype
!= null then inits
.add
(mtype
)
297 # Pager output for console
299 private class PagerMatchesRenderer
301 init(index
: NitIndex) do self.index
= index
303 fun render_matches
(query
: IndexQuery, matches
: Collection[IndexMatch]) do
304 var pager
= new Pager
305 if matches
.length
== 1 then
306 pager
.add
("= result for '{query.string}'".bold
)
308 pager
.indent
= pager
.indent
+ 1
309 matches
.first
.content
(index
, pager
)
310 pager
.indent
= pager
.indent
- 1
312 pager
.add
("= multiple results for '{query.string}'".bold
)
313 pager
.indent
= pager
.indent
+ 1
314 for match
in matches
do
316 match
.preview
(index
, pager
)
318 pager
.indent
= pager
.indent
- 1
323 private fun props_fulldoc
(pager
: Pager, raw_mprops
: List[MProperty]) do
325 var cats
= new HashMap[MModule, Array[MProperty]]
326 for mprop
in raw_mprops
do
327 if mprop
isa MAttribute then continue
328 var key
= mprop
.intro
.mclassdef
.mmodule
329 if not cats
.has_key
(key
) then cats
[key
] = new Array[MProperty]
333 var sorter
= new MModuleNameSorter
334 var sorted
= new Array[MModule]
335 sorted
.add_all
(cats
.keys
)
338 for mmodule
in sorted
do
339 var mprops
= cats
[mmodule
]
340 pager
.add
("# matches in module {mmodule.namespace.bold}")
341 var sorterp
= new MPropertyNameSorter
343 for mprop
in mprops
do
352 var content
= new FlatBuffer
354 fun add
(text
: String) do
358 fun add_indent
do addn
(" " * indent
)
359 fun addn
(text
: String) do content
.append
(text
.escape
)
360 fun add_rule
do add
("\n---\n")
361 fun render
do sys
.system
("echo \"{content}\
" | pager -r")
366 # prototype of the module
368 private fun prototype
: String do return "module {name.bold}"
370 # namespace of the module
372 private fun namespace
: String do
373 if mgroup
== null or mgroup
.mproject
.name
== self.name
then
376 return "{mgroup.mproject}::{self.name}"
380 redef fun preview
(index
, pager
) do
383 pager
.add
(mdoc
.short_comment
.green
)
386 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
389 redef fun content
(index
, pager
) do
392 for comment
in mdoc
.content
do pager
.add
(comment
.green
)
395 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
396 pager
.indent
= pager
.indent
+ 1
397 var sorter
= new MModuleNameSorter
399 var imports
= new Array[MModule]
400 for mmodule
in in_importation
.direct_greaters
.to_a
do
403 if not imports
.is_empty
then
406 pager
.add
("== imported modules".bold
)
407 pager
.indent
= pager
.indent
+ 1
408 for mmodule
in imports
do
410 mmodule
.preview
(index
, pager
)
412 pager
.indent
= pager
.indent
- 1
415 var csorter
= new MClassDefNameSorter
416 var intros
= new Array[MClassDef]
417 var redefs
= new Array[MClassDef]
418 for mclassdef
in mclassdefs
do
419 if mclassdef
.is_intro
then
420 intros
.add
(mclassdef
)
422 redefs
.add
(mclassdef
)
426 if not intros
.is_empty
then
429 pager
.add
("== introduced classes".bold
)
430 pager
.indent
= pager
.indent
+ 1
431 for mclass
in intros
do
433 mclass
.preview
(index
, pager
)
435 pager
.indent
= pager
.indent
- 1
438 if not redefs
.is_empty
then
441 pager
.add
("== refined classes".bold
)
442 pager
.indent
= pager
.indent
+ 1
443 for mclass
in redefs
do
445 mclass
.preview
(index
, pager
)
447 pager
.indent
= pager
.indent
- 1
449 pager
.indent
= pager
.indent
- 1
455 # return the generic signature of the class
457 private fun signature
: String do
458 var res
= new FlatBuffer
461 for i
in [0..intro
.parameter_names
.length
[ do
462 res
.append
(intro
.parameter_names
[i
])
463 if i
< intro
.parameter_names
.length
- 1 then res
.append
(", ")
470 # return the prototype of the class
471 # class name is displayed with colors depending on visibility
472 # abstract interface Foo[E]
473 private fun prototype
: String do
474 var res
= new FlatBuffer
475 res
.append
("{kind} ")
476 if visibility
.to_s
== "public" then res
.append
("{name}{signature}".bold
.green
)
477 if visibility
.to_s
== "private" then res
.append
("{name}{signature}".bold
.red
)
478 if visibility
.to_s
== "protected" then res
.append
("{name}{signature}".bold
.yellow
)
482 private fun namespace
: String do
483 return "{intro_mmodule.namespace}::{name}"
486 redef fun preview
(index
, pager
) do
487 intro
.preview
(index
, pager
)
490 redef fun content
(index
, pager
) do
492 var mdoc
= intro
.mdoc
494 for comment
in mdoc
.content
do pager
.add
(comment
.green
)
496 pager
.add
(intro
.to_console
)
497 pager
.add
("{intro.namespace}".bold
.gray
+ " (lines {intro.location.lines})".gray
)
498 pager
.indent
= pager
.indent
+ 1
500 var supers
= self.in_hierarchy
(index
.mainmodule
).direct_greaters
.to_a
501 if not supers
.is_empty
then
502 var csorter
= new MClassNameSorter
505 pager
.add
("== supers".bold
)
506 pager
.indent
= pager
.indent
+ 1
507 for mclass
in supers
do
509 mclass
.preview
(index
, pager
)
511 pager
.indent
= pager
.indent
- 1
514 if not self.parameter_types
.is_empty
then
516 pager
.add
("== formal types".bold
)
517 pager
.indent
= pager
.indent
+ 1
518 for ft
, bound
in self.parameter_types
do
520 pager
.add
("{ft.to_s.bold.green}: {bound.to_console}")
522 pager
.indent
= pager
.indent
- 1
525 var psorter
= new MPropDefNameSorter
526 var mpropdefs
= intro
.mpropdefs
527 index
.mainmodule
.linearize_mpropdefs
(mpropdefs
)
528 for cat
in intro
.cats2mpropdefs
.keys
do
529 var defs
= intro
.cats2mpropdefs
[cat
].to_a
530 if defs
.is_empty
then continue
533 pager
.add
("== {cat}".bold
)
534 pager
.indent
= pager
.indent
+ 1
535 for mpropdef
in defs
do
537 mpropdef
.preview
(index
, pager
)
539 pager
.indent
= pager
.indent
- 1
542 if not self.mclassdefs
.is_empty
then
544 pager
.add
("== refinements".bold
)
545 var mclassdefs
= self.mclassdefs
546 index
.mainmodule
.linearize_mclassdefs
(mclassdefs
)
547 pager
.indent
= pager
.indent
+ 1
548 for mclassdef
in mclassdefs
do
549 if not mclassdef
.is_intro
then
551 mclassdef
.content
(index
, pager
)
554 pager
.indent
= pager
.indent
- 1
556 pager
.indent
= pager
.indent
- 1
560 redef class MClassDef
563 private fun namespace
: String do
564 return "{mmodule.full_name}::{mclass.name}"
567 fun to_console
: String do
568 var res
= new FlatBuffer
569 if not is_intro
then res
.append
("redef ")
570 res
.append
(mclass
.prototype
)
574 redef fun preview
(index
, pager
) do
577 pager
.add
(mdoc
.short_comment
.green
)
579 pager
.add
(to_console
)
580 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
583 redef fun content
(index
, pager
) do
586 for comment
in mdoc
.content
do pager
.add
(comment
.green
)
588 pager
.add
(to_console
)
589 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
590 pager
.indent
= pager
.indent
+ 1
591 var mpropdefs
= self.mpropdefs
592 var psorter
= new MPropDefNameSorter
593 index
.mainmodule
.linearize_mpropdefs
(mpropdefs
)
594 for cat
in cats2mpropdefs
.keys
do
595 var defs
= cats2mpropdefs
[cat
].to_a
597 if defs
.is_empty
then continue
599 pager
.add
("== {cat}".bold
)
600 pager
.indent
= pager
.indent
+ 1
601 for mpropdef
in defs
do
603 mpropdef
.preview
(index
, pager
)
605 pager
.indent
= pager
.indent
- 1
607 pager
.indent
= pager
.indent
- 1
610 # get mpropdefs grouped by categories (vt, init, methods)
611 fun cats2mpropdefs
: Map[String, Set[MPropDef]] do
612 var cats
= new ArrayMap[String, Set[MPropDef]]
613 cats
["virtual types"] = new HashSet[MPropDef]
614 cats
["constructors"] = new HashSet[MPropDef]
615 cats
["methods"] = new HashSet[MPropDef]
617 for mpropdef
in mpropdefs
do
618 if mpropdef
isa MAttributeDef then continue
619 if mpropdef
isa MVirtualTypeDef then cats
["virtual types"].add
(mpropdef
)
620 if mpropdef
isa MMethodDef then
621 if mpropdef
.mproperty
.is_init
then
622 cats
["constructors"].add
(mpropdef
)
624 cats
["methods"].add
(mpropdef
)
632 redef class MProperty
635 fun to_console
: String do
636 if visibility
.to_s
== "public" then return name
.green
637 if visibility
.to_s
== "private" then return name
.red
638 if visibility
.to_s
== "protected" then return name
.yellow
642 redef fun preview
(index
, pager
) do
643 intro
.preview
(index
, pager
)
646 redef fun content
(index
, pager
) do
647 intro
.content
(index
, pager
)
648 pager
.indent
= pager
.indent
+ 1
649 var mpropdefs
= self.mpropdefs
650 index
.mainmodule
.linearize_mpropdefs
(mpropdefs
)
651 for mpropdef
in mpropdefs
do
652 if mpropdef
isa MAttributeDef then continue
653 if not mpropdef
.is_intro
then
655 mpropdef
.preview
(index
, pager
)
658 pager
.indent
= pager
.indent
- 1
665 fun to_console
: String is abstract
667 private fun namespace
: String do
668 return "{mclassdef.namespace}::{mproperty.name}"
671 redef fun preview
(index
, pager
) do
674 pager
.add
(mdoc
.short_comment
.green
)
676 pager
.add
(to_console
)
677 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
680 redef fun content
(index
, pager
) do
683 for comment
in mdoc
.content
do pager
.add
(comment
.green
)
685 pager
.add
(to_console
)
686 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
690 redef class MMethodDef
691 redef fun to_console
do
692 var res
= new FlatBuffer
693 if not is_intro
then res
.append
("redef ")
694 if not mproperty
.is_init
then res
.append
("fun ")
695 res
.append
(mproperty
.to_console
.bold
)
696 if msignature
!= null then res
.append
(msignature
.to_console
)
697 if is_abstract
then res
.append
" is abstract"
698 if is_intern
then res
.append
" is intern"
699 if is_extern
then res
.append
" is extern"
704 redef class MVirtualTypeDef
705 redef fun to_console
do
706 var res
= new FlatBuffer
708 res
.append
(mproperty
.to_console
.bold
)
709 res
.append
(": {bound.to_console}")
714 redef class MAttributeDef
715 redef fun to_console
do
716 var res
= new FlatBuffer
718 res
.append
(mproperty
.to_console
.bold
)
719 res
.append
(": {static_mtype.to_console}")
724 redef class MSignature
725 redef fun to_console
do
726 var res
= new FlatBuffer
727 if not mparameters
.is_empty
then
729 for i
in [0..mparameters
.length
[ do
730 res
.append
(mparameters
[i
].to_console
)
731 if i
< mparameters
.length
- 1 then res
.append
(", ")
735 if return_mtype
!= null then
736 res
.append
(": {return_mtype.to_console}")
742 redef class MParameter
743 fun to_console
: String do
744 var res
= new FlatBuffer
745 res
.append
("{name}: {mtype.to_console}")
746 if is_vararg
then res
.append
("...")
752 fun to_console
: String do return self.to_s
755 redef class MNullableType
756 redef fun to_console
do return "nullable {mtype.to_console}"
759 redef class MGenericType
760 redef fun to_console
do
761 var res
= new FlatBuffer
762 res
.append
("{mclass.name}[")
763 for i
in [0..arguments
.length
[ do
764 res
.append
(arguments
[i
].to_console
)
765 if i
< arguments
.length
- 1 then res
.append
(", ")
772 redef class MParameterType
773 redef fun to_console
do return mclass
.intro
.parameter_names
[rank
]
776 redef class MVirtualType
777 redef fun to_console
do return mproperty
.name
781 private fun short_comment
: String do
786 redef class AAttrPropdef
787 private fun read_accessor
: String do
789 #FIXME bug with standard::stream::FDStream::fd
790 var name
= mreadpropdef
.mproperty
.name
791 if mpropdef
.mproperty
.visibility
.to_s
== "public" then ret
= "{ret}{name.green}"
792 if mpropdef
.mproperty
.visibility
.to_s
== "private" then ret
= "{ret}{name.red}"
793 if mpropdef
.mproperty
.visibility
.to_s
== "protected" then ret
= "{ret}{name.yellow}"
794 ret
= "{ret}: {n_type.to_s}"
795 if n_kwredef
!= null then ret
= "redef {ret}"
799 private fun write_accessor
: String do
801 var name
= "{mreadpropdef.mproperty.name}="
802 if n_readable
!= null and n_readable
.n_visibility
!= null then
803 if n_readable
.n_visibility
isa APublicVisibility then ret
= "{ret}{name.green}"
804 if n_readable
.n_visibility
isa APrivateVisibility then ret
= "{ret}{name.red}"
805 if n_readable
.n_visibility
isa AProtectedVisibility then ret
= "{ret}{name.yellow}"
807 ret
= "{ret}{name.red}"
809 ret
= "{ret}({mreadpropdef.mproperty.name}: {n_type.to_s})"
810 if n_kwredef
!= null then ret
= "redef {ret}"
815 # Redef String class to add a function to color the string
818 private fun add_escape_char
(escapechar
: String): String do
819 return "{escapechar}{self}\\033[0m"
822 private fun esc
: Char do return 27.ascii
823 private fun gray
: String do return add_escape_char
("{esc}[30m")
824 private fun red
: String do return add_escape_char
("{esc}[31m")
825 private fun green
: String do return add_escape_char
("{esc}[32m")
826 private fun yellow
: String do return add_escape_char
("{esc}[33m")
827 private fun blue
: String do return add_escape_char
("{esc}[34m")
828 private fun purple
: String do return add_escape_char
("{esc}[35m")
829 private fun cyan
: String do return add_escape_char
("{esc}[36m")
830 private fun light_gray
: String do return add_escape_char
("{esc}[37m")
831 private fun bold
: String do return add_escape_char
("{esc}[1m")
832 private fun underline
: String do return add_escape_char
("{esc}[4m")
834 private fun escape
: String
836 var b
= new FlatBuffer
837 for c
in self.chars
do
840 else if c
== '\0' then
842 else if c
== '"' then
844 else if c == '\\' then
846 else if c == '`' then
848 else if c.ascii < 32 then
849 b.append("\\{c.ascii.to_base(8, false)}")
860 return "{line_start}-{line_end}"
864 # Create a tool context to handle options and paths
865 var toolcontext = new ToolContext
866 toolcontext.tooldescription = "Usage: nitx [OPTION]... <file.nit> [query]\nDisplays specific pieces of API information from Nit source files."
867 toolcontext.process_options(args)
869 # Here we launch the nit index
870 var ni = new NitIndex(toolcontext)
873 # TODO seek subclasses and super classes <.<class> >.<class>
874 # TODO seek subclasses and super types <:<type> >:<type>
875 # TODO seek with regexp
876 # TODO standardize namespaces with private option