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
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 is noinit
27 private var mbuilder
: ModelBuilder is noinit
28 private var mainmodule
: MModule is noinit
29 private var arguments
: Array[String] is noinit
30 private var renderer
: PagerMatchesRenderer is noinit
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)
46 # We need a model to collect stufs
47 self.arguments
= toolcontext
.option_context
.rest
49 if arguments
.length
> 2 then
50 print toolcontext
.tooldescription
55 mbuilder
= new ModelBuilder(model
, toolcontext
)
57 var mmodules
= mbuilder
.parse
([arguments
.first
])
58 if mmodules
.is_empty
then return
60 assert mmodules
.length
== 1
61 self.mainmodule
= mmodules
.first
63 renderer
= new PagerMatchesRenderer(self)
67 if arguments
.length
== 1 then
76 print
"Welcome in the Nit Index."
78 print
"Loaded modules:"
79 var mmodules
= new Array[MModule]
80 mmodules
.add_all
(model
.mmodules
)
81 var sorter
= new MEntityNameSorter
92 print
"\tname\t\tlookup module, class and property with the corresponding 'name'"
93 print
"\tparam: Type\tlookup methods using the corresponding 'Type' as parameter"
94 print
"\treturn: Type\tlookup methods returning the corresponding 'Type'"
95 print
"\tnew: Type\tlookup methods creating new instances of 'Type'"
96 print
"\t:h\t\tdisplay this help message"
103 search
(sys
.stdin
.read_line
)
106 fun search
(entry
: String) do
107 if entry
.is_empty
then
111 if entry
== ":h" then
116 if entry
== ":q" then return
119 var query
= parse_query
(entry
)
122 var matches
= new HashSet[IndexMatch]
123 if query
isa IndexQueryPair then
124 if query
.category
== "return" then
126 matches
.add_all
(search_returns
(query
))
127 else if query
.category
== "param" then
129 matches
.add_all
(search_params
(query
))
130 else if query
.category
== "new" then
132 matches
.add_all
(search_inits
(query
))
135 matches
.add_all
(search_modules
(query
))
136 matches
.add_all
(search_classes
(query
))
137 matches
.add_all
(search_properties
(query
))
140 if matches
.is_empty
then
141 print
"Nothing known about '{query.string}', type ':h' for help"
143 renderer
.render_matches
(query
, matches
)
145 if arguments
.length
== 1 then prompt
148 private fun parse_query
(str
: String): IndexQuery do
149 var parts
= str
.split_with
(":")
150 if parts
.length
== 1 then
151 return new IndexQuery(str
, parts
[0])
153 var category
= parts
[0]
154 var keyword
= parts
[1]
155 if keyword
.chars
.first
== ' ' then keyword
= keyword
.substring_from
(1)
156 return new IndexQueryPair(str
, keyword
, category
)
161 private fun search_modules
(query
: IndexQuery): Set[MModule] do
162 var matches
= new HashSet[MModule]
163 for mmodule
in model
.mmodules
do
164 if mmodule
.name
== query
.keyword
then matches
.add
(mmodule
)
170 private fun search_classes
(query
: IndexQuery): Set[MClass] do
171 var matches
= new HashSet[MClass]
172 for mclass
in model
.mclasses
do
173 if mclass
.name
== query
.keyword
then matches
.add
(mclass
)
178 # search for properties
179 private fun search_properties
(query
: IndexQuery): Set[MProperty] do
180 var matches
= new HashSet[MProperty]
181 for mproperty
in model
.mproperties
do
182 if mproperty
.name
== query
.keyword
then matches
.add
(mproperty
)
187 # search for mpropdef returning keyword
188 private fun search_returns
(query
: IndexQuery): Set[MProperty] do
189 var matches
= new HashSet[MProperty]
190 for mproperty
in model
.mproperties
do
191 var intro
= mproperty
.intro
192 if intro
isa MMethodDef then
193 if intro
.msignature
.return_mtype
!= null and intro
.msignature
.return_mtype
.to_console
.has_prefix
(query
.keyword
) then matches
.add
(mproperty
)
194 else if intro
isa MAttributeDef then
195 if intro
.static_mtype
.to_console
.has_prefix
(query
.keyword
) then matches
.add
(mproperty
)
201 # search for mpropdef taking keyword as parameter
202 private fun search_params
(query
: IndexQuery): Set[MProperty] do
203 var matches
= new HashSet[MProperty]
204 for mproperty
in model
.mproperties
do
205 var intro
= mproperty
.intro
206 if intro
isa MMethodDef then
207 var mparameters
= intro
.msignature
.mparameters
208 for mparameter
in mparameters
do
209 if mparameter
.mtype
.to_console
.has_prefix
(query
.keyword
) then matches
.add
(mproperty
)
211 else if intro
isa MAttributeDef then
212 if intro
.static_mtype
.to_console
.has_prefix
(query
.keyword
) then matches
.add
(mproperty
)
218 # search for mpropdef creating new instance of keyword
219 private fun search_inits
(query
: IndexQuery): Set[MPropDef] do
220 var mtype2mpropdefs
= toolcontext
.nitx_phase
.mtype2mpropdefs
221 var matches
= new HashSet[MPropDef]
222 for mtype
in mtype2mpropdefs
.keys
do
223 if mtype
.to_console
.has_prefix
(query
.keyword
) then
224 for mpropdef
in mtype2mpropdefs
[mtype
] do
225 matches
.add
(mpropdef
)
233 private class IndexQuery
238 private class IndexQueryPair
243 # A match to a query in the nit index
244 private interface IndexMatch
245 # Short preview of the result for result list display
246 fun preview
(index
: NitIndex, output
: Pager) is abstract
247 fun content
(index
: NitIndex, output
: Pager) is abstract
252 redef class ToolContext
253 private var nitx_phase
: NitxPhase = new NitxPhase(self, [modelize_property_phase
])
256 # Compiler phase for nitx
257 private class NitxPhase
260 var mtype2mpropdefs
= new HashMap[MType, Set[MPropDef]]
261 redef fun process_npropdef
(npropdef
) do
262 var visitor
= new TypeInitVisitor
263 visitor
.enter_visit
(npropdef
)
264 for mtype
in visitor
.inits
do
265 if not mtype2mpropdefs
.has_key
(mtype
) then
266 mtype2mpropdefs
[mtype
] = new HashSet[MPropDef]
268 mtype2mpropdefs
[mtype
].add
(npropdef
.mpropdef
.as(not null))
273 # Visitor looking for initialized mtype (new T)
274 private class TypeInitVisitor
277 var inits
= new HashSet[MType]
278 redef fun visit
(node
)
282 if not node
isa ANewExpr then return
283 var mtype
= node
.n_type
.mtype
284 if mtype
!= null then inits
.add
(mtype
)
288 # Pager output for console
290 private class PagerMatchesRenderer
293 fun render_matches
(query
: IndexQuery, matches
: Collection[IndexMatch]) do
294 var pager
= new Pager
295 if matches
.length
== 1 then
296 pager
.add
("= result for '{query.string}'".bold
)
298 pager
.indent
= pager
.indent
+ 1
299 matches
.first
.content
(index
, pager
)
300 pager
.indent
= pager
.indent
- 1
302 pager
.add
("= multiple results for '{query.string}'".bold
)
303 pager
.indent
= pager
.indent
+ 1
304 for match
in matches
do
306 match
.preview
(index
, pager
)
308 pager
.indent
= pager
.indent
- 1
313 fun props_fulldoc
(pager
: Pager, raw_mprops
: List[MProperty]) do
315 var cats
= new HashMap[MModule, Array[MProperty]]
316 for mprop
in raw_mprops
do
317 if mprop
isa MAttribute then continue
318 var key
= mprop
.intro
.mclassdef
.mmodule
319 if not cats
.has_key
(key
) then cats
[key
] = new Array[MProperty]
323 var sorter
= new MEntityNameSorter
324 var sorted
= new Array[MModule]
325 sorted
.add_all
(cats
.keys
)
328 for mmodule
in sorted
do
329 var mprops
= cats
[mmodule
]
330 pager
.add
("# matches in module {mmodule.namespace.bold}")
332 for mprop
in mprops
do
341 var content
= new FlatBuffer
343 fun add
(text
: String) do
347 fun add_indent
do addn
(" " * indent
)
348 fun addn
(text
: String) do content
.append
(text
.escape
)
349 fun add_rule
do add
("\n---\n")
350 fun render
do sys
.system
("echo \"{content}\
" | less -r")
355 # prototype of the module
357 private fun prototype
: String do return "module {name.bold}"
359 # namespace of the module
361 private fun namespace
: String do
362 if mgroup
== null or mgroup
.mproject
.name
== self.name
then
365 return "{mgroup.mproject}::{self.name}"
369 redef fun preview
(index
, pager
) do
372 pager
.add
(mdoc
.short_comment
.green
)
375 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
378 redef fun content
(index
, pager
) do
381 for comment
in mdoc
.content
do pager
.add
(comment
.green
)
384 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
385 pager
.indent
= pager
.indent
+ 1
386 var sorter
= new MEntityNameSorter
388 var imports
= new Array[MModule]
389 for mmodule
in in_importation
.direct_greaters
.to_a
do
392 if not imports
.is_empty
then
395 pager
.add
("== imported modules".bold
)
396 pager
.indent
= pager
.indent
+ 1
397 for mmodule
in imports
do
399 mmodule
.preview
(index
, pager
)
401 pager
.indent
= pager
.indent
- 1
404 var intros
= new Array[MClassDef]
405 var redefs
= new Array[MClassDef]
406 for mclassdef
in mclassdefs
do
407 if mclassdef
.is_intro
then
408 intros
.add
(mclassdef
)
410 redefs
.add
(mclassdef
)
414 if not intros
.is_empty
then
417 pager
.add
("== introduced classes".bold
)
418 pager
.indent
= pager
.indent
+ 1
419 for mclass
in intros
do
421 mclass
.preview
(index
, pager
)
423 pager
.indent
= pager
.indent
- 1
426 if not redefs
.is_empty
then
429 pager
.add
("== refined classes".bold
)
430 pager
.indent
= pager
.indent
+ 1
431 for mclass
in redefs
do
433 mclass
.preview
(index
, pager
)
435 pager
.indent
= pager
.indent
- 1
437 pager
.indent
= pager
.indent
- 1
443 # return the generic signature of the class
445 private fun signature
: String do
446 var res
= new FlatBuffer
449 for i
in [0..mparameters
.length
[ do
450 res
.append
(mparameters
[i
].name
)
451 if i
< mparameters
.length
- 1 then res
.append
(", ")
458 # return the prototype of the class
459 # class name is displayed with colors depending on visibility
460 # abstract interface Foo[E]
461 private fun prototype
: String do
462 var res
= new FlatBuffer
463 res
.append
("{kind} ")
464 if visibility
.to_s
== "public" then res
.append
("{name}{signature}".bold
.green
)
465 if visibility
.to_s
== "private" then res
.append
("{name}{signature}".bold
.red
)
466 if visibility
.to_s
== "protected" then res
.append
("{name}{signature}".bold
.yellow
)
470 private fun namespace
: String do
471 return "{intro_mmodule.namespace}::{name}"
474 redef fun preview
(index
, pager
) do
475 intro
.preview
(index
, pager
)
478 redef fun content
(index
, pager
) do
480 var sorter
= new MEntityNameSorter
481 var mdoc
= intro
.mdoc
483 for comment
in mdoc
.content
do pager
.add
(comment
.green
)
485 pager
.add
(intro
.to_console
)
486 pager
.add
("{intro.namespace}".bold
.gray
+ " (lines {intro.location.lines})".gray
)
487 pager
.indent
= pager
.indent
+ 1
489 var supers
= self.in_hierarchy
(index
.mainmodule
).direct_greaters
.to_a
490 if not supers
.is_empty
then
493 pager
.add
("== supers".bold
)
494 pager
.indent
= pager
.indent
+ 1
495 for mclass
in supers
do
497 mclass
.preview
(index
, pager
)
499 pager
.indent
= pager
.indent
- 1
502 if not self.parameter_types
.is_empty
then
504 pager
.add
("== formal types".bold
)
505 pager
.indent
= pager
.indent
+ 1
506 for ft
, bound
in self.parameter_types
do
508 pager
.add
("{ft.to_s.bold.green}: {bound.to_console}")
510 pager
.indent
= pager
.indent
- 1
513 var mpropdefs
= intro
.mpropdefs
514 index
.mainmodule
.linearize_mpropdefs
(mpropdefs
)
515 for cat
in intro
.cats2mpropdefs
.keys
do
516 var defs
= intro
.cats2mpropdefs
[cat
].to_a
517 if defs
.is_empty
then continue
520 pager
.add
("== {cat}".bold
)
521 pager
.indent
= pager
.indent
+ 1
522 for mpropdef
in defs
do
524 mpropdef
.preview
(index
, pager
)
526 pager
.indent
= pager
.indent
- 1
529 if not self.mclassdefs
.is_empty
then
531 pager
.add
("== refinements".bold
)
532 var mclassdefs
= self.mclassdefs
533 index
.mainmodule
.linearize_mclassdefs
(mclassdefs
)
534 pager
.indent
= pager
.indent
+ 1
535 for mclassdef
in mclassdefs
do
536 if not mclassdef
.is_intro
then
538 mclassdef
.content
(index
, pager
)
541 pager
.indent
= pager
.indent
- 1
543 pager
.indent
= pager
.indent
- 1
547 redef class MClassDef
550 private fun namespace
: String do
551 return "{mmodule.full_name}::{mclass.name}"
554 fun to_console
: String do
555 var res
= new FlatBuffer
556 if not is_intro
then res
.append
("redef ")
557 res
.append
(mclass
.prototype
)
561 redef fun preview
(index
, pager
) do
564 pager
.add
(mdoc
.short_comment
.green
)
566 pager
.add
(to_console
)
567 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
570 redef fun content
(index
, pager
) do
573 for comment
in mdoc
.content
do pager
.add
(comment
.green
)
575 pager
.add
(to_console
)
576 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
577 pager
.indent
= pager
.indent
+ 1
578 var mpropdefs
= self.mpropdefs
579 var sorter
= new MEntityNameSorter
580 index
.mainmodule
.linearize_mpropdefs
(mpropdefs
)
581 for cat
in cats2mpropdefs
.keys
do
582 var defs
= cats2mpropdefs
[cat
].to_a
584 if defs
.is_empty
then continue
586 pager
.add
("== {cat}".bold
)
587 pager
.indent
= pager
.indent
+ 1
588 for mpropdef
in defs
do
590 mpropdef
.preview
(index
, pager
)
592 pager
.indent
= pager
.indent
- 1
594 pager
.indent
= pager
.indent
- 1
597 # get mpropdefs grouped by categories (vt, init, methods)
598 fun cats2mpropdefs
: Map[String, Set[MPropDef]] do
599 var cats
= new ArrayMap[String, Set[MPropDef]]
600 cats
["virtual types"] = new HashSet[MPropDef]
601 cats
["constructors"] = new HashSet[MPropDef]
602 cats
["methods"] = new HashSet[MPropDef]
604 for mpropdef
in mpropdefs
do
605 if mpropdef
isa MAttributeDef then continue
606 if mpropdef
isa MVirtualTypeDef then cats
["virtual types"].add
(mpropdef
)
607 if mpropdef
isa MMethodDef then
608 if mpropdef
.mproperty
.is_init
then
609 cats
["constructors"].add
(mpropdef
)
611 cats
["methods"].add
(mpropdef
)
619 redef class MProperty
622 fun to_console
: String do
623 if visibility
.to_s
== "public" then return name
.green
624 if visibility
.to_s
== "private" then return name
.red
625 if visibility
.to_s
== "protected" then return name
.yellow
629 redef fun preview
(index
, pager
) do
630 intro
.preview
(index
, pager
)
633 redef fun content
(index
, pager
) do
634 intro
.content
(index
, pager
)
635 pager
.indent
= pager
.indent
+ 1
636 var mpropdefs
= self.mpropdefs
637 index
.mainmodule
.linearize_mpropdefs
(mpropdefs
)
638 for mpropdef
in mpropdefs
do
639 if mpropdef
isa MAttributeDef then continue
640 if not mpropdef
.is_intro
then
642 mpropdef
.preview
(index
, pager
)
645 pager
.indent
= pager
.indent
- 1
652 fun to_console
: String is abstract
654 private fun namespace
: String do
655 return "{mclassdef.namespace}::{mproperty.name}"
658 redef fun preview
(index
, pager
) do
661 pager
.add
(mdoc
.short_comment
.green
)
663 pager
.add
(to_console
)
664 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
667 redef fun content
(index
, pager
) do
670 for comment
in mdoc
.content
do pager
.add
(comment
.green
)
672 pager
.add
(to_console
)
673 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
677 redef class MMethodDef
678 redef fun to_console
do
679 var res
= new FlatBuffer
680 if not is_intro
then res
.append
("redef ")
681 if not mproperty
.is_init
then res
.append
("fun ")
682 res
.append
(mproperty
.to_console
.bold
)
683 if msignature
!= null then res
.append
(msignature
.to_console
)
684 if is_abstract
then res
.append
" is abstract"
685 if is_intern
then res
.append
" is intern"
686 if is_extern
then res
.append
" is extern"
691 redef class MVirtualTypeDef
692 redef fun to_console
do
693 var res
= new FlatBuffer
695 res
.append
(mproperty
.to_console
.bold
)
696 res
.append
(": {bound.to_console}")
701 redef class MAttributeDef
702 redef fun to_console
do
703 var res
= new FlatBuffer
705 res
.append
(mproperty
.to_console
.bold
)
706 res
.append
(": {static_mtype.to_console}")
711 redef class MSignature
712 redef fun to_console
do
713 var res
= new FlatBuffer
714 if not mparameters
.is_empty
then
716 for i
in [0..mparameters
.length
[ do
717 res
.append
(mparameters
[i
].to_console
)
718 if i
< mparameters
.length
- 1 then res
.append
(", ")
722 if return_mtype
!= null then
723 res
.append
(": {return_mtype.to_console}")
729 redef class MParameter
730 fun to_console
: String do
731 var res
= new FlatBuffer
732 res
.append
("{name}: {mtype.to_console}")
733 if is_vararg
then res
.append
("...")
739 fun to_console
: String do return self.to_s
742 redef class MNullableType
743 redef fun to_console
do return "nullable {mtype.to_console}"
746 redef class MGenericType
747 redef fun to_console
do
748 var res
= new FlatBuffer
749 res
.append
("{mclass.name}[")
750 for i
in [0..arguments
.length
[ do
751 res
.append
(arguments
[i
].to_console
)
752 if i
< arguments
.length
- 1 then res
.append
(", ")
759 redef class MParameterType
760 redef fun to_console
do return name
763 redef class MVirtualType
764 redef fun to_console
do return mproperty
.name
768 private fun short_comment
: String do
773 # Redef String class to add a function to color the string
776 private fun add_escape_char
(escapechar
: String): String do
777 return "{escapechar}{self}\\033[0m"
780 private fun esc
: Char do return 27.ascii
781 private fun gray
: String do return add_escape_char
("{esc}[30m")
782 private fun red
: String do return add_escape_char
("{esc}[31m")
783 private fun green
: String do return add_escape_char
("{esc}[32m")
784 private fun yellow
: String do return add_escape_char
("{esc}[33m")
785 private fun blue
: String do return add_escape_char
("{esc}[34m")
786 private fun purple
: String do return add_escape_char
("{esc}[35m")
787 private fun cyan
: String do return add_escape_char
("{esc}[36m")
788 private fun light_gray
: String do return add_escape_char
("{esc}[37m")
789 private fun bold
: String do return add_escape_char
("{esc}[1m")
790 private fun underline
: String do return add_escape_char
("{esc}[4m")
792 private fun escape
: String
794 var b
= new FlatBuffer
795 for c
in self.chars
do
798 else if c
== '\0' then
800 else if c
== '"' then
802 else if c == '\\' then
804 else if c == '`' then
806 else if c.ascii < 32 then
807 b.append("\\{c.ascii.to_base(8, false)}")
818 return "{line_start}-{line_end}"
822 # Create a tool context to handle options and paths
823 var toolcontext = new ToolContext
824 toolcontext.tooldescription = "Usage: nitx [OPTION]... <file.nit> [query]\nDisplays specific pieces of API information from Nit source files."
825 toolcontext.process_options(args)
827 # Here we launch the nit index
828 var ni = new NitIndex(toolcontext)
831 # TODO seek subclasses and super classes <.<class> >.<class>
832 # TODO seek subclasses and super types <:<type> >:<type>
833 # TODO seek with regexp
834 # TODO standardize namespaces with private option