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.toolcontext
.option_context
.options
.clear
49 self.arguments
= toolcontext
.option_context
.rest
51 if arguments
.is_empty
or arguments
.length
> 2 then
52 print
"usage: ni path/to/module.nit [expression]"
53 toolcontext
.option_context
.usage
58 mbuilder
= new ModelBuilder(model
, toolcontext
)
60 # Here we load an process std modules
61 #var dir = "NIT_DIR".environ
62 #var mmodules = modelbuilder.parse_and_build(["{dir}/lib/standard/standard.nit"])
63 var mmodules
= mbuilder
.parse
([arguments
.first
])
64 if mmodules
.is_empty
then return
66 assert mmodules
.length
== 1
67 self.mainmodule
= mmodules
.first
69 renderer
= new PagerMatchesRenderer(self)
73 if arguments
.length
== 1 then
82 print
"Welcome in the Nit Index."
84 print
"Loaded modules:"
85 var mmodules
= new Array[MModule]
86 mmodules
.add_all
(model
.mmodules
)
87 var sorter
= new MModuleNameSorter
98 print
"\tname\t\tlookup module, class and property with the corresponding 'name'"
99 print
"\tparam: Type\tlookup methods using the corresponding 'Type' as parameter"
100 print
"\treturn: Type\tlookup methods returning the corresponding 'Type'"
101 print
"\tnew: Type\tlookup methods creating new instances of 'Type'"
102 print
"\t:h\t\tdisplay this help message"
109 search
(stdin
.read_line
)
112 fun search
(entry
: String) do
113 if entry
.is_empty
then
117 if entry
== ":h" then
122 if entry
== ":q" then return
125 var query
= parse_query
(entry
)
128 var matches
= new HashSet[IndexMatch]
129 if query
isa IndexQueryPair then
130 if query
.category
== "return" then
132 matches
.add_all
(search_returns
(query
))
133 else if query
.category
== "param" then
135 matches
.add_all
(search_params
(query
))
136 else if query
.category
== "new" then
138 matches
.add_all
(search_inits
(query
))
141 matches
.add_all
(search_modules
(query
))
142 matches
.add_all
(search_classes
(query
))
143 matches
.add_all
(search_properties
(query
))
146 if matches
.is_empty
then
147 print
"Nothing known about '{query.string}', type ':h' for help"
149 renderer
.render_matches
(query
, matches
)
151 if arguments
.length
== 1 then prompt
154 private fun parse_query
(str
: String): IndexQuery do
155 var parts
= str
.split_with
(":")
156 if parts
.length
== 1 then
157 return new IndexQuery(str
, parts
[0])
159 var category
= parts
[0]
160 var keyword
= parts
[1]
161 if keyword
.chars
.first
== ' ' then keyword
= keyword
.substring_from
(1)
162 return new IndexQueryPair(str
, keyword
, category
)
167 private fun search_modules
(query
: IndexQuery): Set[MModule] do
168 var matches
= new HashSet[MModule]
169 for mmodule
in model
.mmodules
do
170 if mmodule
.name
== query
.keyword
then matches
.add
(mmodule
)
176 private fun search_classes
(query
: IndexQuery): Set[MClass] do
177 var matches
= new HashSet[MClass]
178 for mclass
in model
.mclasses
do
179 if mclass
.name
== query
.keyword
then matches
.add
(mclass
)
184 # search for properties
185 private fun search_properties
(query
: IndexQuery): Set[MProperty] do
186 var matches
= new HashSet[MProperty]
187 for mproperty
in model
.mproperties
do
188 if mproperty
.name
== query
.keyword
then matches
.add
(mproperty
)
193 # search for mpropdef returning keyword
194 private fun search_returns
(query
: IndexQuery): Set[MProperty] do
195 var matches
= new HashSet[MProperty]
196 for mproperty
in model
.mproperties
do
197 var intro
= mproperty
.intro
198 if intro
isa MMethodDef then
199 if intro
.msignature
.return_mtype
!= null and intro
.msignature
.return_mtype
.to_console
.has_prefix
(query
.keyword
) then matches
.add
(mproperty
)
200 else if intro
isa MAttributeDef then
201 if intro
.static_mtype
.to_console
.has_prefix
(query
.keyword
) then matches
.add
(mproperty
)
207 # search for mpropdef taking keyword as parameter
208 private fun search_params
(query
: IndexQuery): Set[MProperty] do
209 var matches
= new HashSet[MProperty]
210 for mproperty
in model
.mproperties
do
211 var intro
= mproperty
.intro
212 if intro
isa MMethodDef then
213 var mparameters
= intro
.msignature
.mparameters
214 for mparameter
in mparameters
do
215 if mparameter
.mtype
.to_console
.has_prefix
(query
.keyword
) then matches
.add
(mproperty
)
217 else if intro
isa MAttributeDef then
218 if intro
.static_mtype
.to_console
.has_prefix
(query
.keyword
) then matches
.add
(mproperty
)
224 # search for mpropdef creating new instance of keyword
225 private fun search_inits
(query
: IndexQuery): Set[MPropDef] do
226 var mtype2mpropdefs
= toolcontext
.nitx_phase
.mtype2mpropdefs
227 var matches
= new HashSet[MPropDef]
228 for mtype
in mtype2mpropdefs
.keys
do
229 if mtype
.to_console
.has_prefix
(query
.keyword
) then
230 for mpropdef
in mtype2mpropdefs
[mtype
] do
231 matches
.add
(mpropdef
)
239 private class IndexQuery
242 init(string
: String, keyword
: String) do
244 self.keyword
= keyword
248 private class IndexQueryPair
251 init(string
: String, keyword
: String, category
: String) do
252 super(string
, keyword
)
253 self.category
= category
257 # A match to a query in the nit index
258 private interface IndexMatch
259 # Short preview of the result for result list display
260 fun preview
(index
: NitIndex, output
: Pager) is abstract
261 fun content
(index
: NitIndex, output
: Pager) is abstract
266 redef class ToolContext
267 private var nitx_phase
: NitxPhase = new NitxPhase(self, [modelize_property_phase
])
270 # Compiler phase for nitx
271 private class NitxPhase
274 var mtype2mpropdefs
= new HashMap[MType, Set[MPropDef]]
275 redef fun process_npropdef
(npropdef
) do
276 var visitor
= new TypeInitVisitor
277 visitor
.enter_visit
(npropdef
)
278 for mtype
in visitor
.inits
do
279 if not mtype2mpropdefs
.has_key
(mtype
) then
280 mtype2mpropdefs
[mtype
] = new HashSet[MPropDef]
282 mtype2mpropdefs
[mtype
].add
(npropdef
.mpropdef
.as(not null))
287 # Visitor looking for initialized mtype (new T)
288 private class TypeInitVisitor
291 var inits
= new HashSet[MType]
292 redef fun visit
(node
)
296 if not node
isa ANewExpr then return
297 var mtype
= node
.n_type
.mtype
298 if mtype
!= null then inits
.add
(mtype
)
302 # Pager output for console
304 private class PagerMatchesRenderer
306 init(index
: NitIndex) do self.index
= index
308 fun render_matches
(query
: IndexQuery, matches
: Collection[IndexMatch]) do
309 var pager
= new Pager
310 if matches
.length
== 1 then
311 pager
.add
("= result for '{query.string}'".bold
)
313 pager
.indent
= pager
.indent
+ 1
314 matches
.first
.content
(index
, pager
)
315 pager
.indent
= pager
.indent
- 1
317 pager
.add
("= multiple results for '{query.string}'".bold
)
318 pager
.indent
= pager
.indent
+ 1
319 for match
in matches
do
321 match
.preview
(index
, pager
)
323 pager
.indent
= pager
.indent
- 1
328 private fun props_fulldoc
(pager
: Pager, raw_mprops
: List[MProperty]) do
330 var cats
= new HashMap[MModule, Array[MProperty]]
331 for mprop
in raw_mprops
do
332 if mprop
isa MAttribute then continue
333 var key
= mprop
.intro
.mclassdef
.mmodule
334 if not cats
.has_key
(key
) then cats
[key
] = new Array[MProperty]
338 var sorter
= new MModuleNameSorter
339 var sorted
= new Array[MModule]
340 sorted
.add_all
(cats
.keys
)
343 for mmodule
in sorted
do
344 var mprops
= cats
[mmodule
]
345 pager
.add
("# matches in module {mmodule.namespace.bold}")
346 var sorterp
= new MPropertyNameSorter
348 for mprop
in mprops
do
357 var content
= new Buffer
359 fun add
(text
: String) do
363 fun add_indent
do addn
(" " * indent
)
364 fun addn
(text
: String) do content
.append
(text
.escape
)
365 fun add_rule
do add
("\n---\n")
366 fun render
do sys
.system
("echo \"{content}\
" | pager -r")
371 # prototype of the module
373 private fun prototype
: String do return "module {name.bold}"
375 # namespace of the module
377 private fun namespace
: String do
378 if mgroup
== null or mgroup
.mproject
.name
== self.name
then
381 return "{mgroup.mproject}::{self.name}"
385 redef fun preview
(index
, pager
) do
388 pager
.add
(mdoc
.short_comment
.green
)
391 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
394 redef fun content
(index
, pager
) do
397 for comment
in mdoc
.content
do pager
.add
(comment
.green
)
400 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
401 pager
.indent
= pager
.indent
+ 1
402 var sorter
= new MModuleNameSorter
404 var imports
= new Array[MModule]
405 for mmodule
in in_importation
.direct_greaters
.to_a
do
408 if not imports
.is_empty
then
411 pager
.add
("== imported modules".bold
)
412 pager
.indent
= pager
.indent
+ 1
413 for mmodule
in imports
do
415 mmodule
.preview
(index
, pager
)
417 pager
.indent
= pager
.indent
- 1
420 var csorter
= new MClassDefNameSorter
421 var intros
= new Array[MClassDef]
422 var redefs
= new Array[MClassDef]
423 for mclassdef
in mclassdefs
do
424 if mclassdef
.is_intro
then
425 intros
.add
(mclassdef
)
427 redefs
.add
(mclassdef
)
431 if not intros
.is_empty
then
434 pager
.add
("== introduced classes".bold
)
435 pager
.indent
= pager
.indent
+ 1
436 for mclass
in intros
do
438 mclass
.preview
(index
, pager
)
440 pager
.indent
= pager
.indent
- 1
443 if not redefs
.is_empty
then
446 pager
.add
("== refined classes".bold
)
447 pager
.indent
= pager
.indent
+ 1
448 for mclass
in redefs
do
450 mclass
.preview
(index
, pager
)
452 pager
.indent
= pager
.indent
- 1
454 pager
.indent
= pager
.indent
- 1
460 # return the generic signature of the class
462 private fun signature
: String do
466 for i
in [0..intro
.parameter_names
.length
[ do
467 res
.append
(intro
.parameter_names
[i
])
468 if i
< intro
.parameter_names
.length
- 1 then res
.append
(", ")
475 # return the prototype of the class
476 # class name is displayed with colors depending on visibility
477 # abstract interface Foo[E]
478 private fun prototype
: String do
480 res
.append
("{kind} ")
481 if visibility
.to_s
== "public" then res
.append
("{name}{signature}".bold
.green
)
482 if visibility
.to_s
== "private" then res
.append
("{name}{signature}".bold
.red
)
483 if visibility
.to_s
== "protected" then res
.append
("{name}{signature}".bold
.yellow
)
487 private fun namespace
: String do
488 return "{intro_mmodule.namespace}::{name}"
491 redef fun preview
(index
, pager
) do
492 intro
.preview
(index
, pager
)
495 redef fun content
(index
, pager
) do
497 var mdoc
= intro
.mdoc
499 for comment
in mdoc
.content
do pager
.add
(comment
.green
)
501 pager
.add
(intro
.to_console
)
502 pager
.add
("{intro.namespace}".bold
.gray
+ " (lines {intro.location.lines})".gray
)
503 pager
.indent
= pager
.indent
+ 1
505 var supers
= self.in_hierarchy
(index
.mainmodule
).direct_greaters
.to_a
506 if not supers
.is_empty
then
507 var csorter
= new MClassNameSorter
510 pager
.add
("== supers".bold
)
511 pager
.indent
= pager
.indent
+ 1
512 for mclass
in supers
do
514 mclass
.preview
(index
, pager
)
516 pager
.indent
= pager
.indent
- 1
519 if not self.parameter_types
.is_empty
then
521 pager
.add
("== formal types".bold
)
522 pager
.indent
= pager
.indent
+ 1
523 for ft
, bound
in self.parameter_types
do
525 pager
.add
("{ft.to_s.bold.green}: {bound.to_console}")
527 pager
.indent
= pager
.indent
- 1
530 var psorter
= new MPropDefNameSorter
531 var mpropdefs
= intro
.mpropdefs
532 index
.mainmodule
.linearize_mpropdefs
(mpropdefs
)
533 for cat
in intro
.cats2mpropdefs
.keys
do
534 var defs
= intro
.cats2mpropdefs
[cat
].to_a
535 if defs
.is_empty
then continue
538 pager
.add
("== {cat}".bold
)
539 pager
.indent
= pager
.indent
+ 1
540 for mpropdef
in defs
do
542 mpropdef
.preview
(index
, pager
)
544 pager
.indent
= pager
.indent
- 1
547 if not self.mclassdefs
.is_empty
then
549 pager
.add
("== refinements".bold
)
550 var mclassdefs
= self.mclassdefs
551 index
.mainmodule
.linearize_mclassdefs
(mclassdefs
)
552 pager
.indent
= pager
.indent
+ 1
553 for mclassdef
in mclassdefs
do
554 if not mclassdef
.is_intro
then
556 mclassdef
.content
(index
, pager
)
559 pager
.indent
= pager
.indent
- 1
561 pager
.indent
= pager
.indent
- 1
565 redef class MClassDef
568 private fun namespace
: String do
569 return "{mmodule.full_name}::{mclass.name}"
572 fun to_console
: String do
574 if not is_intro
then res
.append
("redef ")
575 res
.append
(mclass
.prototype
)
579 redef fun preview
(index
, pager
) do
582 pager
.add
(mdoc
.short_comment
.green
)
584 pager
.add
(to_console
)
585 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
588 redef fun content
(index
, pager
) do
591 for comment
in mdoc
.content
do pager
.add
(comment
.green
)
593 pager
.add
(to_console
)
594 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
595 pager
.indent
= pager
.indent
+ 1
596 var mpropdefs
= self.mpropdefs
597 var psorter
= new MPropDefNameSorter
598 index
.mainmodule
.linearize_mpropdefs
(mpropdefs
)
599 for cat
in cats2mpropdefs
.keys
do
600 var defs
= cats2mpropdefs
[cat
].to_a
602 if defs
.is_empty
then continue
604 pager
.add
("== {cat}".bold
)
605 pager
.indent
= pager
.indent
+ 1
606 for mpropdef
in defs
do
608 mpropdef
.preview
(index
, pager
)
610 pager
.indent
= pager
.indent
- 1
612 pager
.indent
= pager
.indent
- 1
615 # get mpropdefs grouped by categories (vt, init, methods)
616 fun cats2mpropdefs
: Map[String, Set[MPropDef]] do
617 var cats
= new ArrayMap[String, Set[MPropDef]]
618 cats
["virtual types"] = new HashSet[MPropDef]
619 cats
["constructors"] = new HashSet[MPropDef]
620 cats
["methods"] = new HashSet[MPropDef]
622 for mpropdef
in mpropdefs
do
623 if mpropdef
isa MAttributeDef then continue
624 if mpropdef
isa MVirtualTypeDef then cats
["virtual types"].add
(mpropdef
)
625 if mpropdef
isa MMethodDef then
626 if mpropdef
.mproperty
.is_init
then
627 cats
["constructors"].add
(mpropdef
)
629 cats
["methods"].add
(mpropdef
)
637 redef class MProperty
640 fun to_console
: String do
641 if visibility
.to_s
== "public" then return name
.green
642 if visibility
.to_s
== "private" then return name
.red
643 if visibility
.to_s
== "protected" then return name
.yellow
647 redef fun preview
(index
, pager
) do
648 intro
.preview
(index
, pager
)
651 redef fun content
(index
, pager
) do
652 intro
.content
(index
, pager
)
653 pager
.indent
= pager
.indent
+ 1
654 var mpropdefs
= self.mpropdefs
655 index
.mainmodule
.linearize_mpropdefs
(mpropdefs
)
656 for mpropdef
in mpropdefs
do
657 if mpropdef
isa MAttributeDef then continue
658 if not mpropdef
.is_intro
then
660 mpropdef
.preview
(index
, pager
)
663 pager
.indent
= pager
.indent
- 1
670 fun to_console
: String is abstract
672 private fun namespace
: String do
673 return "{mclassdef.namespace}::{mproperty.name}"
676 redef fun preview
(index
, pager
) do
679 pager
.add
(mdoc
.short_comment
.green
)
681 pager
.add
(to_console
)
682 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
685 redef fun content
(index
, pager
) do
688 for comment
in mdoc
.content
do pager
.add
(comment
.green
)
690 pager
.add
(to_console
)
691 pager
.add
("{namespace}".bold
.gray
+ " (lines {location.lines})".gray
)
695 redef class MMethodDef
696 redef fun to_console
do
698 if not is_intro
then res
.append
("redef ")
699 if not mproperty
.is_init
then res
.append
("fun ")
700 res
.append
(mproperty
.to_console
.bold
)
701 if msignature
!= null then res
.append
(msignature
.to_console
)
702 # FIXME: modifiers should be accessible via the model
703 #if self isa ADeferredMethPropdef then ret = "{ret} is abstract"
704 #if self isa AInternMethPropdef then ret = "{ret} is intern"
705 #if self isa AExternMethPropdef then ret = "{ret} is extern"
710 redef class MVirtualTypeDef
711 redef fun to_console
do
714 res
.append
(mproperty
.to_console
.bold
)
715 res
.append
(": {bound.to_console}")
720 redef class MAttributeDef
721 redef fun to_console
do
724 res
.append
(mproperty
.to_console
.bold
)
725 res
.append
(": {static_mtype.to_console}")
730 redef class MSignature
731 redef fun to_console
do
733 if not mparameters
.is_empty
then
735 for i
in [0..mparameters
.length
[ do
736 res
.append
(mparameters
[i
].to_console
)
737 if i
< mparameters
.length
- 1 then res
.append
(", ")
741 if return_mtype
!= null then
742 res
.append
(": {return_mtype.to_console}")
748 redef class MParameter
749 fun to_console
: String do
751 res
.append
("{name}: {mtype.to_console}")
752 if is_vararg
then res
.append
("...")
758 fun to_console
: String do return self.to_s
761 redef class MNullableType
762 redef fun to_console
do return "nullable {mtype.to_console}"
765 redef class MGenericType
766 redef fun to_console
do
768 res
.append
("{mclass.name}[")
769 for i
in [0..arguments
.length
[ do
770 res
.append
(arguments
[i
].to_console
)
771 if i
< arguments
.length
- 1 then res
.append
(", ")
778 redef class MParameterType
779 redef fun to_console
do return mclass
.intro
.parameter_names
[rank
]
782 redef class MVirtualType
783 redef fun to_console
do return mproperty
.name
787 private fun short_comment
: String do
792 redef class AAttrPropdef
793 private fun read_accessor
: String do
795 #FIXME bug with standard::stream::FDStream::fd
796 var name
= mreadpropdef
.mproperty
.name
797 if mpropdef
.mproperty
.visibility
.to_s
== "public" then ret
= "{ret}{name.green}"
798 if mpropdef
.mproperty
.visibility
.to_s
== "private" then ret
= "{ret}{name.red}"
799 if mpropdef
.mproperty
.visibility
.to_s
== "protected" then ret
= "{ret}{name.yellow}"
800 ret
= "{ret}: {n_type.to_s}"
801 if n_kwredef
!= null then ret
= "redef {ret}"
805 private fun write_accessor
: String do
807 var name
= "{mreadpropdef.mproperty.name}="
808 if n_readable
!= null and n_readable
.n_visibility
!= null then
809 if n_readable
.n_visibility
isa APublicVisibility then ret
= "{ret}{name.green}"
810 if n_readable
.n_visibility
isa APrivateVisibility then ret
= "{ret}{name.red}"
811 if n_readable
.n_visibility
isa AProtectedVisibility then ret
= "{ret}{name.yellow}"
813 ret
= "{ret}{name.red}"
815 ret
= "{ret}({mreadpropdef.mproperty.name}: {n_type.to_s})"
816 if n_kwredef
!= null then ret
= "redef {ret}"
821 # Redef String class to add a function to color the string
824 private fun add_escape_char
(escapechar
: String): String do
825 return "{escapechar}{self}\\033[0m"
828 private fun esc
: Char do return 27.ascii
829 private fun gray
: String do return add_escape_char
("{esc}[30m")
830 private fun red
: String do return add_escape_char
("{esc}[31m")
831 private fun green
: String do return add_escape_char
("{esc}[32m")
832 private fun yellow
: String do return add_escape_char
("{esc}[33m")
833 private fun blue
: String do return add_escape_char
("{esc}[34m")
834 private fun purple
: String do return add_escape_char
("{esc}[35m")
835 private fun cyan
: String do return add_escape_char
("{esc}[36m")
836 private fun light_gray
: String do return add_escape_char
("{esc}[37m")
837 private fun bold
: String do return add_escape_char
("{esc}[1m")
838 private fun underline
: String do return add_escape_char
("{esc}[4m")
840 private fun escape
: String
843 for c
in self.chars
do
846 else if c
== '\0' then
848 else if c
== '"' then
850 else if c == '\\' then
852 else if c == '`' then
854 else if c.ascii < 32 then
855 b.append("\\{c.ascii.to_base(8, false)}")
866 return "{line_start}-{line_end}"
870 # Create a tool context to handle options and paths
871 var toolcontext = new ToolContext
872 toolcontext.process_options
874 # Here we launch the nit index
875 var ni = new NitIndex(toolcontext)
878 # TODO seek subclasses and super classes <.<class> >.<class>
879 # TODO seek subclasses and super types <:<type> >:<type>
880 # TODO seek with regexp
881 # TODO standardize namespaces with private option