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 # Here we load an process std modules
59 #var dir = "NIT_DIR".environ
60 #var mmodules = modelbuilder.parse_and_build(["{dir}/lib/standard/standard.nit"])
61 var mmodules
= mbuilder
.parse
([arguments
.first
])
62 if mmodules
.is_empty
then return
64 assert mmodules
.length
== 1
65 self.mainmodule
= mmodules
.first
67 renderer
= new PagerMatchesRenderer(self)
71 if arguments
.length
== 1 then
80 print
"Welcome in the Nit Index."
82 print
"Loaded modules:"
83 var mmodules
= new Array[MModule]
84 mmodules
.add_all
(model
.mmodules
)
85 var sorter
= new MModuleNameSorter
96 print
"\tname\t\tlookup module, class and property with the corresponding 'name'"
97 print
"\tparam: Type\tlookup methods using the corresponding 'Type' as parameter"
98 print
"\treturn: Type\tlookup methods returning the corresponding 'Type'"
99 print
"\tnew: Type\tlookup methods creating new instances of 'Type'"
100 print
"\t:h\t\tdisplay this help message"
107 search
(stdin
.read_line
)
110 fun search
(entry
: String) do
111 if entry
.is_empty
then
115 if entry
== ":h" then
120 if entry
== ":q" then return
123 var query
= parse_query
(entry
)
126 var matches
= new HashSet[IndexMatch]
127 if query
isa IndexQueryPair then
128 if query
.category
== "return" then
130 matches
.add_all
(search_returns
(query
))
131 else if query
.category
== "param" then
133 matches
.add_all
(search_params
(query
))
134 else if query
.category
== "new" then
136 matches
.add_all
(search_inits
(query
))
139 matches
.add_all
(search_modules
(query
))
140 matches
.add_all
(search_classes
(query
))
141 matches
.add_all
(search_properties
(query
))
144 if matches
.is_empty
then
145 print
"Nothing known about '{query.string}', type ':h' for help"
147 renderer
.render_matches
(query
, matches
)
149 if arguments
.length
== 1 then prompt
152 private fun parse_query
(str
: String): IndexQuery do
153 var parts
= str
.split_with
(":")
154 if parts
.length
== 1 then
155 return new IndexQuery(str
, parts
[0])
157 var category
= parts
[0]
158 var keyword
= parts
[1]
159 if keyword
.chars
.first
== ' ' then keyword
= keyword
.substring_from
(1)
160 return new IndexQueryPair(str
, keyword
, category
)
165 private fun search_modules
(query
: IndexQuery): Set[MModule] do
166 var matches
= new HashSet[MModule]
167 for mmodule
in model
.mmodules
do
168 if mmodule
.name
== query
.keyword
then matches
.add
(mmodule
)
174 private fun search_classes
(query
: IndexQuery): Set[MClass] do
175 var matches
= new HashSet[MClass]
176 for mclass
in model
.mclasses
do
177 if mclass
.name
== query
.keyword
then matches
.add
(mclass
)
182 # search for properties
183 private fun search_properties
(query
: IndexQuery): Set[MProperty] do
184 var matches
= new HashSet[MProperty]
185 for mproperty
in model
.mproperties
do
186 if mproperty
.name
== query
.keyword
then matches
.add
(mproperty
)
191 # search for mpropdef returning keyword
192 private fun search_returns
(query
: IndexQuery): Set[MProperty] do
193 var matches
= new HashSet[MProperty]
194 for mproperty
in model
.mproperties
do
195 var intro
= mproperty
.intro
196 if intro
isa MMethodDef then
197 if intro
.msignature
.return_mtype
!= null and intro
.msignature
.return_mtype
.to_console
.has_prefix
(query
.keyword
) then matches
.add
(mproperty
)
198 else if intro
isa MAttributeDef then
199 if intro
.static_mtype
.to_console
.has_prefix
(query
.keyword
) then matches
.add
(mproperty
)
205 # search for mpropdef taking keyword as parameter
206 private fun search_params
(query
: IndexQuery): Set[MProperty] do
207 var matches
= new HashSet[MProperty]
208 for mproperty
in model
.mproperties
do
209 var intro
= mproperty
.intro
210 if intro
isa MMethodDef then
211 var mparameters
= intro
.msignature
.mparameters
212 for mparameter
in mparameters
do
213 if mparameter
.mtype
.to_console
.has_prefix
(query
.keyword
) then matches
.add
(mproperty
)
215 else if intro
isa MAttributeDef then
216 if intro
.static_mtype
.to_console
.has_prefix
(query
.keyword
) then matches
.add
(mproperty
)
222 # search for mpropdef creating new instance of keyword
223 private fun search_inits
(query
: IndexQuery): Set[MPropDef] do
224 var mtype2mpropdefs
= toolcontext
.nitx_phase
.mtype2mpropdefs
225 var matches
= new HashSet[MPropDef]
226 for mtype
in mtype2mpropdefs
.keys
do
227 if mtype
.to_console
.has_prefix
(query
.keyword
) then
228 for mpropdef
in mtype2mpropdefs
[mtype
] do
229 matches
.add
(mpropdef
)
237 private class IndexQuery
240 init(string
: String, keyword
: String) do
242 self.keyword
= keyword
246 private class IndexQueryPair
249 init(string
: String, keyword
: String, category
: String) do
250 super(string
, keyword
)
251 self.category
= category
255 # A match to a query in the nit index
256 private interface IndexMatch
257 # Short preview of the result for result list display
258 fun preview
(index
: NitIndex, output
: Pager) is abstract
259 fun content
(index
: NitIndex, output
: Pager) is abstract
264 redef class ToolContext
265 private var nitx_phase
: NitxPhase = new NitxPhase(self, [modelize_property_phase
])
268 # Compiler phase for nitx
269 private class NitxPhase
272 var mtype2mpropdefs
= new HashMap[MType, Set[MPropDef]]
273 redef fun process_npropdef
(npropdef
) do
274 var visitor
= new TypeInitVisitor
275 visitor
.enter_visit
(npropdef
)
276 for mtype
in visitor
.inits
do
277 if not mtype2mpropdefs
.has_key
(mtype
) then
278 mtype2mpropdefs
[mtype
] = new HashSet[MPropDef]
280 mtype2mpropdefs
[mtype
].add
(npropdef
.mpropdef
.as(not null))
285 # Visitor looking for initialized mtype (new T)
286 private class TypeInitVisitor
289 var inits
= new HashSet[MType]
290 redef fun visit
(node
)
294 if not node
isa ANewExpr then return
295 var mtype
= node
.n_type
.mtype
296 if mtype
!= null then inits
.add
(mtype
)
300 # Pager output for console
302 private class PagerMatchesRenderer
304 init(index
: NitIndex) do self.index
= index
306 fun render_matches
(query
: IndexQuery, matches
: Collection[IndexMatch]) do
307 var pager
= new Pager
308 if matches
.length
== 1 then
309 pager
.add
("= result for '{query.string}'".bold
)
311 pager
.indent
= pager
.indent
+ 1
312 matches
.first
.content
(index
, pager
)
313 pager
.indent
= pager
.indent
- 1
315 pager
.add
("= multiple results for '{query.string}'".bold
)
316 pager
.indent
= pager
.indent
+ 1
317 for match
in matches
do
319 match
.preview
(index
, pager
)
321 pager
.indent
= pager
.indent
- 1
326 private fun props_fulldoc
(pager
: Pager, raw_mprops
: List[MProperty]) do
328 var cats
= new HashMap[MModule, Array[MProperty]]
329 for mprop
in raw_mprops
do
330 if mprop
isa MAttribute then continue
331 var key
= mprop
.intro
.mclassdef
.mmodule
332 if not cats
.has_key
(key
) then cats
[key
] = new Array[MProperty]
336 var sorter
= new MModuleNameSorter
337 var sorted
= new Array[MModule]
338 sorted
.add_all
(cats
.keys
)
341 for mmodule
in sorted
do
342 var mprops
= cats
[mmodule
]
343 pager
.add
("# matches in module {mmodule.namespace.bold}")
344 var sorterp
= new MPropertyNameSorter
346 for mprop
in mprops
do
355 var content
= new FlatBuffer
357 fun add
(text
: String) do
361 fun add_indent
do addn
(" " * indent
)
362 fun addn
(text
: String) do content
.append
(text
.escape
)
363 fun add_rule
do add
("\n---\n")
364 fun render
do sys
.system
("echo \"{content}\
" | pager -r")
369 # prototype of the module
371 private fun prototype
: String do return "module {name.bold}"
373 # namespace of the module
375 private fun namespace
: String do
376 if mgroup
== null or mgroup
.mproject
.name
== self.name
then
379 return "{mgroup.mproject}::{self.name}"
383 redef fun preview
(index
, pager
) do
386 pager
.add
(mdoc
.short_comment
.green
)
389 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
392 redef fun content
(index
, pager
) do
395 for comment
in mdoc
.content
do pager
.add
(comment
.green
)
398 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
399 pager
.indent
= pager
.indent
+ 1
400 var sorter
= new MModuleNameSorter
402 var imports
= new Array[MModule]
403 for mmodule
in in_importation
.direct_greaters
.to_a
do
406 if not imports
.is_empty
then
409 pager
.add
("== imported modules".bold
)
410 pager
.indent
= pager
.indent
+ 1
411 for mmodule
in imports
do
413 mmodule
.preview
(index
, pager
)
415 pager
.indent
= pager
.indent
- 1
418 var csorter
= new MClassDefNameSorter
419 var intros
= new Array[MClassDef]
420 var redefs
= new Array[MClassDef]
421 for mclassdef
in mclassdefs
do
422 if mclassdef
.is_intro
then
423 intros
.add
(mclassdef
)
425 redefs
.add
(mclassdef
)
429 if not intros
.is_empty
then
432 pager
.add
("== introduced classes".bold
)
433 pager
.indent
= pager
.indent
+ 1
434 for mclass
in intros
do
436 mclass
.preview
(index
, pager
)
438 pager
.indent
= pager
.indent
- 1
441 if not redefs
.is_empty
then
444 pager
.add
("== refined classes".bold
)
445 pager
.indent
= pager
.indent
+ 1
446 for mclass
in redefs
do
448 mclass
.preview
(index
, pager
)
450 pager
.indent
= pager
.indent
- 1
452 pager
.indent
= pager
.indent
- 1
458 # return the generic signature of the class
460 private fun signature
: String do
461 var res
= new FlatBuffer
464 for i
in [0..intro
.parameter_names
.length
[ do
465 res
.append
(intro
.parameter_names
[i
])
466 if i
< intro
.parameter_names
.length
- 1 then res
.append
(", ")
473 # return the prototype of the class
474 # class name is displayed with colors depending on visibility
475 # abstract interface Foo[E]
476 private fun prototype
: String do
477 var res
= new FlatBuffer
478 res
.append
("{kind} ")
479 if visibility
.to_s
== "public" then res
.append
("{name}{signature}".bold
.green
)
480 if visibility
.to_s
== "private" then res
.append
("{name}{signature}".bold
.red
)
481 if visibility
.to_s
== "protected" then res
.append
("{name}{signature}".bold
.yellow
)
485 private fun namespace
: String do
486 return "{intro_mmodule.namespace}::{name}"
489 redef fun preview
(index
, pager
) do
490 intro
.preview
(index
, pager
)
493 redef fun content
(index
, pager
) do
495 var mdoc
= intro
.mdoc
497 for comment
in mdoc
.content
do pager
.add
(comment
.green
)
499 pager
.add
(intro
.to_console
)
500 pager
.add
("{intro.namespace}".bold
.gray
+ " (lines {intro.location.lines})".gray
)
501 pager
.indent
= pager
.indent
+ 1
503 var supers
= self.in_hierarchy
(index
.mainmodule
).direct_greaters
.to_a
504 if not supers
.is_empty
then
505 var csorter
= new MClassNameSorter
508 pager
.add
("== supers".bold
)
509 pager
.indent
= pager
.indent
+ 1
510 for mclass
in supers
do
512 mclass
.preview
(index
, pager
)
514 pager
.indent
= pager
.indent
- 1
517 if not self.parameter_types
.is_empty
then
519 pager
.add
("== formal types".bold
)
520 pager
.indent
= pager
.indent
+ 1
521 for ft
, bound
in self.parameter_types
do
523 pager
.add
("{ft.to_s.bold.green}: {bound.to_console}")
525 pager
.indent
= pager
.indent
- 1
528 var psorter
= new MPropDefNameSorter
529 var mpropdefs
= intro
.mpropdefs
530 index
.mainmodule
.linearize_mpropdefs
(mpropdefs
)
531 for cat
in intro
.cats2mpropdefs
.keys
do
532 var defs
= intro
.cats2mpropdefs
[cat
].to_a
533 if defs
.is_empty
then continue
536 pager
.add
("== {cat}".bold
)
537 pager
.indent
= pager
.indent
+ 1
538 for mpropdef
in defs
do
540 mpropdef
.preview
(index
, pager
)
542 pager
.indent
= pager
.indent
- 1
545 if not self.mclassdefs
.is_empty
then
547 pager
.add
("== refinements".bold
)
548 var mclassdefs
= self.mclassdefs
549 index
.mainmodule
.linearize_mclassdefs
(mclassdefs
)
550 pager
.indent
= pager
.indent
+ 1
551 for mclassdef
in mclassdefs
do
552 if not mclassdef
.is_intro
then
554 mclassdef
.content
(index
, pager
)
557 pager
.indent
= pager
.indent
- 1
559 pager
.indent
= pager
.indent
- 1
563 redef class MClassDef
566 private fun namespace
: String do
567 return "{mmodule.full_name}::{mclass.name}"
570 fun to_console
: String do
571 var res
= new FlatBuffer
572 if not is_intro
then res
.append
("redef ")
573 res
.append
(mclass
.prototype
)
577 redef fun preview
(index
, pager
) do
580 pager
.add
(mdoc
.short_comment
.green
)
582 pager
.add
(to_console
)
583 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
586 redef fun content
(index
, pager
) do
589 for comment
in mdoc
.content
do pager
.add
(comment
.green
)
591 pager
.add
(to_console
)
592 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
593 pager
.indent
= pager
.indent
+ 1
594 var mpropdefs
= self.mpropdefs
595 var psorter
= new MPropDefNameSorter
596 index
.mainmodule
.linearize_mpropdefs
(mpropdefs
)
597 for cat
in cats2mpropdefs
.keys
do
598 var defs
= cats2mpropdefs
[cat
].to_a
600 if defs
.is_empty
then continue
602 pager
.add
("== {cat}".bold
)
603 pager
.indent
= pager
.indent
+ 1
604 for mpropdef
in defs
do
606 mpropdef
.preview
(index
, pager
)
608 pager
.indent
= pager
.indent
- 1
610 pager
.indent
= pager
.indent
- 1
613 # get mpropdefs grouped by categories (vt, init, methods)
614 fun cats2mpropdefs
: Map[String, Set[MPropDef]] do
615 var cats
= new ArrayMap[String, Set[MPropDef]]
616 cats
["virtual types"] = new HashSet[MPropDef]
617 cats
["constructors"] = new HashSet[MPropDef]
618 cats
["methods"] = new HashSet[MPropDef]
620 for mpropdef
in mpropdefs
do
621 if mpropdef
isa MAttributeDef then continue
622 if mpropdef
isa MVirtualTypeDef then cats
["virtual types"].add
(mpropdef
)
623 if mpropdef
isa MMethodDef then
624 if mpropdef
.mproperty
.is_init
then
625 cats
["constructors"].add
(mpropdef
)
627 cats
["methods"].add
(mpropdef
)
635 redef class MProperty
638 fun to_console
: String do
639 if visibility
.to_s
== "public" then return name
.green
640 if visibility
.to_s
== "private" then return name
.red
641 if visibility
.to_s
== "protected" then return name
.yellow
645 redef fun preview
(index
, pager
) do
646 intro
.preview
(index
, pager
)
649 redef fun content
(index
, pager
) do
650 intro
.content
(index
, pager
)
651 pager
.indent
= pager
.indent
+ 1
652 var mpropdefs
= self.mpropdefs
653 index
.mainmodule
.linearize_mpropdefs
(mpropdefs
)
654 for mpropdef
in mpropdefs
do
655 if mpropdef
isa MAttributeDef then continue
656 if not mpropdef
.is_intro
then
658 mpropdef
.preview
(index
, pager
)
661 pager
.indent
= pager
.indent
- 1
668 fun to_console
: String is abstract
670 private fun namespace
: String do
671 return "{mclassdef.namespace}::{mproperty.name}"
674 redef fun preview
(index
, pager
) do
677 pager
.add
(mdoc
.short_comment
.green
)
679 pager
.add
(to_console
)
680 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
683 redef fun content
(index
, pager
) do
686 for comment
in mdoc
.content
do pager
.add
(comment
.green
)
688 pager
.add
(to_console
)
689 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
693 redef class MMethodDef
694 redef fun to_console
do
695 var res
= new FlatBuffer
696 if not is_intro
then res
.append
("redef ")
697 if not mproperty
.is_init
then res
.append
("fun ")
698 res
.append
(mproperty
.to_console
.bold
)
699 if msignature
!= null then res
.append
(msignature
.to_console
)
700 # FIXME: modifiers should be accessible via the model
701 #if self isa ADeferredMethPropdef then ret = "{ret} is abstract"
702 #if self isa AInternMethPropdef then ret = "{ret} is intern"
703 #if self isa AExternMethPropdef then ret = "{ret} is extern"
708 redef class MVirtualTypeDef
709 redef fun to_console
do
710 var res
= new FlatBuffer
712 res
.append
(mproperty
.to_console
.bold
)
713 res
.append
(": {bound.to_console}")
718 redef class MAttributeDef
719 redef fun to_console
do
720 var res
= new FlatBuffer
722 res
.append
(mproperty
.to_console
.bold
)
723 res
.append
(": {static_mtype.to_console}")
728 redef class MSignature
729 redef fun to_console
do
730 var res
= new FlatBuffer
731 if not mparameters
.is_empty
then
733 for i
in [0..mparameters
.length
[ do
734 res
.append
(mparameters
[i
].to_console
)
735 if i
< mparameters
.length
- 1 then res
.append
(", ")
739 if return_mtype
!= null then
740 res
.append
(": {return_mtype.to_console}")
746 redef class MParameter
747 fun to_console
: String do
748 var res
= new FlatBuffer
749 res
.append
("{name}: {mtype.to_console}")
750 if is_vararg
then res
.append
("...")
756 fun to_console
: String do return self.to_s
759 redef class MNullableType
760 redef fun to_console
do return "nullable {mtype.to_console}"
763 redef class MGenericType
764 redef fun to_console
do
765 var res
= new FlatBuffer
766 res
.append
("{mclass.name}[")
767 for i
in [0..arguments
.length
[ do
768 res
.append
(arguments
[i
].to_console
)
769 if i
< arguments
.length
- 1 then res
.append
(", ")
776 redef class MParameterType
777 redef fun to_console
do return mclass
.intro
.parameter_names
[rank
]
780 redef class MVirtualType
781 redef fun to_console
do return mproperty
.name
785 private fun short_comment
: String do
790 redef class AAttrPropdef
791 private fun read_accessor
: String do
793 #FIXME bug with standard::stream::FDStream::fd
794 var name
= mreadpropdef
.mproperty
.name
795 if mpropdef
.mproperty
.visibility
.to_s
== "public" then ret
= "{ret}{name.green}"
796 if mpropdef
.mproperty
.visibility
.to_s
== "private" then ret
= "{ret}{name.red}"
797 if mpropdef
.mproperty
.visibility
.to_s
== "protected" then ret
= "{ret}{name.yellow}"
798 ret
= "{ret}: {n_type.to_s}"
799 if n_kwredef
!= null then ret
= "redef {ret}"
803 private fun write_accessor
: String do
805 var name
= "{mreadpropdef.mproperty.name}="
806 if n_readable
!= null and n_readable
.n_visibility
!= null then
807 if n_readable
.n_visibility
isa APublicVisibility then ret
= "{ret}{name.green}"
808 if n_readable
.n_visibility
isa APrivateVisibility then ret
= "{ret}{name.red}"
809 if n_readable
.n_visibility
isa AProtectedVisibility then ret
= "{ret}{name.yellow}"
811 ret
= "{ret}{name.red}"
813 ret
= "{ret}({mreadpropdef.mproperty.name}: {n_type.to_s})"
814 if n_kwredef
!= null then ret
= "redef {ret}"
819 # Redef String class to add a function to color the string
822 private fun add_escape_char
(escapechar
: String): String do
823 return "{escapechar}{self}\\033[0m"
826 private fun esc
: Char do return 27.ascii
827 private fun gray
: String do return add_escape_char
("{esc}[30m")
828 private fun red
: String do return add_escape_char
("{esc}[31m")
829 private fun green
: String do return add_escape_char
("{esc}[32m")
830 private fun yellow
: String do return add_escape_char
("{esc}[33m")
831 private fun blue
: String do return add_escape_char
("{esc}[34m")
832 private fun purple
: String do return add_escape_char
("{esc}[35m")
833 private fun cyan
: String do return add_escape_char
("{esc}[36m")
834 private fun light_gray
: String do return add_escape_char
("{esc}[37m")
835 private fun bold
: String do return add_escape_char
("{esc}[1m")
836 private fun underline
: String do return add_escape_char
("{esc}[4m")
838 private fun escape
: String
840 var b
= new FlatBuffer
841 for c
in self.chars
do
844 else if c
== '\0' then
846 else if c
== '"' then
848 else if c == '\\' then
850 else if c == '`' then
852 else if c.ascii < 32 then
853 b.append("\\{c.ascii.to_base(8, false)}")
864 return "{line_start}-{line_end}"
868 # Create a tool context to handle options and paths
869 var toolcontext = new ToolContext
870 toolcontext.tooldescription = "Usage: nitx [OPTION]... <file.nit> [query]\nDisplays specific pieces of API information from Nit source files."
871 toolcontext.process_options(args)
873 # Here we launch the nit index
874 var ni = new NitIndex(toolcontext)
877 # TODO seek subclasses and super classes <.<class> >.<class>
878 # TODO seek subclasses and super types <:<type> >:<type>
879 # TODO seek with regexp
880 # TODO standardize namespaces with private option