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 # Nitx related components
17 # This module is a place holder for `nitx` related services.
18 # No `doc_phase` can be found here, only components used by Nitx tool.
24 import doc
::console_templates
25 import model
::model_index
27 # Nitx handles console I/O.
29 # Using `prompt`, the command line can be turned on an interactive tool.
32 # ToolContext used to access options.
35 # DocModel that contains the informations to display.
38 # Comparator used to sort MEntities.
39 var sorter
= new MEntityNameSorter
41 # Displays the welcome message and start prompt.
47 # Displays the welcome message and the list of loaded modules.
49 print
"Welcome in the Nit Index."
51 print
"Loaded modules:"
52 var mmodules
= doc
.mmodules
.to_a
61 # Displays the list of available commands.
64 print
"\tname\t\t\tlookup module, class and property with the corresponding 'name'"
65 print
"\tdoc: <name::space>\tdisplay the documentation page of 'namespace'"
66 print
"\nType lookup:"
67 print
"\tparam: <Type>\t\tlookup methods using the corresponding 'Type' as parameter"
68 print
"\treturn: <Type>\t\tlookup methods returning the corresponding 'Type'"
69 print
"\tnew: <Type>\t\tlookup methods creating new instances of 'Type'"
70 print
"\tcall: <name>\t\tlookup methods calling 'name'"
71 print
"\nHierarchy lookup:"
72 print
"\tparents: <Class>\tlist direct parents of 'Class'"
73 print
"\tancestors: <Class>\tlist all ancestors of 'Class'"
74 print
"\tchildren: <Class>\tlist direct children of 'Class'"
75 print
"\tdescendants: <Class>\tlist all descendants of 'Class'"
76 print
"\nCode lookup:"
77 print
"\tcode: <name>\t\tdisplay the source code associated to the 'name' entity"
79 print
"\t:h\t\t\tdisplay this help message"
80 print
"\t:q\t\t\tquit interactive mode"
84 # Prompts the user for a command.
87 do_query
(sys
.stdin
.read_line
)
91 # Parser used to process doc commands
92 var parser
: DocCommandParser is lazy
do
93 var parser
= new DocCommandParser
94 parser
.allowed_commands
= ["doc", "comment", "list", "param", "return",
95 "new", "call", "code"]
99 # Processes the query string and performs it.
100 fun do_query
(str
: String) do
103 else if str
== ":h" then
107 var query
= parser
.parse
(str
)
108 if query
== null then
109 query
= new CommentCommand(str
)
112 var res
= query
.perform
(self, doc
)
115 suggest
= query
.suggest
(self, doc
)
117 var page
= query
.make_results
(self, res
, suggest
)
118 print page
.write_to_string
122 redef class DocCommand
124 # Looks up the `doc` model and returns possible matches.
125 fun perform
(nitx
: Nitx, doc
: DocModel): Array[NitxMatch] is abstract
127 # Looks up the `doc` model and returns possible suggestions.
128 fun suggest
(nitx
: Nitx, doc
: DocModel): nullable Array[MEntity] do
129 return find_suggestions
(doc
, arg
)
132 # Pretty prints the results for the console.
133 fun make_results
(nitx
: Nitx, results
: Array[NitxMatch], suggest
: nullable Array[MEntity]): DocPage do
134 var page
= new DocPage("results", "Results")
135 page
.root
.add_child
(new QueryResultArticle("results", "Results", self, results
, suggest
))
139 # Lookup mentities based on a `query` string.
141 # 1- lookup by first name (returns always one value)
142 # 2- lookup by name (can return conflicts)
143 fun find_mentities
(doc
: DocModel, query
: String): Array[MEntityMatch] do
144 var res
= new Array[MEntityMatch]
146 # First lookup by full_name
147 var mentity
= doc
.mentity_by_full_name
(query
)
148 if mentity
!= null then
149 res
.add
new MEntityMatch(self, mentity
)
153 # If no results, lookup by name
154 for m
in doc
.mentities_by_name
(query
) do
155 res
.add
new MEntityMatch(self, m
)
161 # Suggest some mentities based on a `query` string.
162 fun find_suggestions
(doc
: DocModel, query
: String): Array[MEntity] do
163 return doc
.find
(query
, 3)
167 # Something that matches a `DocCommand`.
168 abstract class NitxMatch
170 # Query matched by `self`.
171 var query
: DocCommand
173 # Pretty prints `self` for console.
174 fun make_list_item
: String is abstract
177 # A match between a `DocCommand` and a `MEntity`.
184 redef fun make_list_item
do return mentity
.cs_list_item
187 redef class CommentCommand
188 redef fun perform
(nitx
, doc
) do return find_mentities
(doc
, arg
)
190 redef fun make_results
(nitx
, results
, suggest
) do
191 var len
= results
.length
193 var res
= results
.first
.as(MEntityMatch)
194 var mentity
= res
.mentity
195 var page
= new DocPage("resultats", "Results")
196 var article
= new DefinitionArticle("results", "Results", mentity
)
197 article
.cs_title
= mentity
.name
198 article
.cs_subtitle
= mentity
.cs_declaration
199 page
.root
.add_child article
207 # A query to search signatures using a specific `MType` as parameter.
208 redef class ParamCommand
209 redef fun perform
(nitx
, doc
) do
210 var res
= new Array[NitxMatch]
212 for mproperty
in doc
.mproperties
do
213 if not mproperty
isa MMethod then continue
214 var msignature
= mproperty
.intro
.msignature
215 if msignature
!= null then
216 for mparam
in msignature
.mparameters
do
217 if mparam
.mtype
.name
== mtype_name
then
218 res
.add
new MEntityMatch(self, mproperty
)
227 # A query to search signatures using a specific `MType` as return.
228 redef class ReturnCommand
229 redef fun perform
(nitx
, doc
) do
230 var res
= new Array[NitxMatch]
232 for mproperty
in doc
.mproperties
do
233 if not mproperty
isa MMethod then continue
234 var msignature
= mproperty
.intro
.msignature
235 if msignature
!= null then
236 var mreturn
= msignature
.return_mtype
237 if mreturn
!= null and mreturn
.name
== mtype_name
then
238 res
.add
new MEntityMatch(self, mproperty
)
246 # A query to search methods creating new instances of a specific `MType`.
247 redef class NewCommand
248 redef fun perform
(nitx
, doc
) do
249 var res
= new Array[NitxMatch]
251 for mpropdef
in doc
.mpropdefs
do
252 var visitor
= new TypeInitVisitor(mtype_name
)
253 var npropdef
= nitx
.ctx
.modelbuilder
.mpropdef2node
(mpropdef
)
254 if npropdef
== null then continue
255 visitor
.enter_visit
(npropdef
)
256 for i
in visitor
.inits
do
257 res
.add
new MEntityMatch(self, mpropdef
)
264 # A query to search methods calling a specific `MProperty`.
265 redef class CallCommand
266 redef fun perform
(nitx
, doc
) do
267 var res
= new Array[NitxMatch]
269 for mpropdef
in doc
.mpropdefs
do
270 var visitor
= new MPropertyCallVisitor
271 var npropdef
= nitx
.ctx
.modelbuilder
.mpropdef2node
(mpropdef
)
272 if npropdef
== null then continue
273 visitor
.enter_visit
(npropdef
)
274 for mprop
in visitor
.calls
do
275 if mprop
.name
!= mprop_name
then continue
276 res
.add
new MEntityMatch(self, mpropdef
)
283 # A query to search a Nitdoc documentation page by its name.
284 redef class ArticleCommand
285 redef fun perform
(nitx
, doc
) do
286 var res
= new Array[NitxMatch]
288 for page
in doc
.pages
.values
do
289 if name
== "*" then # FIXME dev only
290 res
.add
new PageMatch(self, page
)
291 else if page
.title
== name
then
292 res
.add
new PageMatch(self, page
)
293 else if page
isa MEntityPage and page
.mentity
.cs_namespace
== name
then
294 res
.add
new PageMatch(self, page
)
300 redef fun make_results
(nitx
, results
, suggest
) do
301 var len
= results
.length
302 # FIXME how to render the pager for one worded namespaces like "core"?
304 var page
= results
.first
.as(PageMatch).page
305 var pager
= new Pager
306 pager
.add page
.write_to_string
315 # A match between a `DocPage` and a `MEntity`.
322 redef fun make_list_item
do
324 if page
isa MEntityPage then
325 return page
.mentity
.cs_list_item
327 return " * {page.title}"
331 # Search in class or module hierarchy of a `MEntity`.
333 # It actually searches for pages about the mentity and extracts the
334 # pre-calculated hierarchies by the `doc_post` phase.
335 abstract class HierarchiesQuery
338 redef fun make_results
(nitx
, results
, suggest
) do
339 var page
= new DocPage("hierarchy", "Hierarchy")
340 for result
in results
do
341 if not result
isa PageMatch then continue
342 var rpage
= result
.page
343 if not rpage
isa MClassPage then continue
344 page
.root
.add_child build_article
(rpage
)
349 # Build an article containing the hierarchy list depending on subclasses.
350 private fun build_article
(page
: MClassPage): DocArticle is abstract
353 # List all parents of a `MClass`.
355 super HierarchiesQuery
357 redef fun build_article
(page
) do
358 return new MEntitiesListArticle(
360 "Ancestors for {page.mentity.name}",
365 # List direct parents of a `MClass`.
367 super HierarchiesQuery
369 redef fun build_article
(page
) do
370 return new MEntitiesListArticle(
372 "Parents for {page.mentity.name}",
377 # List direct children of a `MClass`.
379 super HierarchiesQuery
381 redef fun build_article
(page
) do
382 return new MEntitiesListArticle(
384 "Children for {page.mentity.name}",
389 # List all descendants of a `MClass`.
390 class DescendantsQuery
391 super HierarchiesQuery
393 redef fun build_article
(page
) do
394 return new MEntitiesListArticle(
396 "Descendants for {page.mentity.name}",
401 # A query to search source code from a file name.
402 redef class CodeCommand
403 # FIXME refactor this!
404 redef fun perform
(nitx
, doc
) do
405 var res
= new Array[NitxMatch]
407 # if name is an existing sourcefile, opens it
408 if name
.file_exists
then
409 var fr
= new FileReader.open
(name
)
410 var content
= fr
.read_all
412 res
.add
new CodeMatch(self, name
, content
)
415 # else, lookup the model by name
416 for match
in find_mentities
(doc
, name
) do
417 if match
.mentity
isa MClass then continue
418 if match
.mentity
isa MProperty then continue
419 res
.add
new CodeMatch(self, match
.mentity
.cs_location
, match
.mentity
.cs_source_code
)
424 redef fun make_results
(nitx
, results
, suggest
) do
425 var page
= new DocPage("results", "Code Results")
426 for res
in results
do
427 page
.add
new CodeQueryArticle("results", "Results", self, res
.as(CodeMatch))
433 # A match between a piece of code and a string.
437 # Location of the code match.
440 # Piece of code matched.
443 redef fun make_list_item
do return "* {location}"
448 # Visitor looking for initialized `MType` (new T).
451 private class TypeInitVisitor
454 # `MType` name to look for.
455 var mtype_name
: String
457 var inits
= new HashSet[MType]
458 redef fun visit
(node
)
462 if not node
isa ANewExpr then return
463 var mtype
= node
.n_type
.mtype
464 if mtype
!= null and mtype
.name
== mtype_name
then inits
.add
(mtype
)
468 # Visitor looking for calls to a `MProperty` (new T).
471 private class MPropertyCallVisitor
474 var calls
= new HashSet[MProperty]
475 redef fun visit
(node
)
478 if not node
isa ASendExpr then return
479 calls
.add node
.callsite
.as(not null).mproperty
485 # A `DocArticle` that displays query results.
486 private class QueryResultArticle
489 # Query linked to the results to display.
490 var query
: DocCommand
492 # Results to display.
493 var results
: Array[NitxMatch]
495 # Optional suggestion when no matches where found
496 var suggest
: nullable Array[MEntity] = null is optional
498 redef fun render_title
do
499 var len
= results
.length
501 addn
"No result found for '{query.string}'..."
502 var suggest
= self.suggest
503 if suggest
!= null and suggest
.not_empty
then
504 add
"\nDid you mean "
507 add
"`{s.full_name}`"
508 if i
== suggest
.length
- 2 then add
", "
509 if i
== suggest
.length
- 1 then add
" or "
515 add
"# {len} result(s) for '{query.string}'".green
.bold
519 redef fun render_body
do
521 for result
in results
do
523 addn result
.make_list_item
528 # An article that displays a piece of code.
529 private class CodeQueryArticle
532 # The query linked to the result to display.
533 var query
: DocCommand
535 # The result to display.
536 var result
: CodeMatch
538 redef fun render_body
do
540 addn
"in {result.location}".gray
.bold
546 # A Pager is used to display data into a unix `less` container.
549 # Content to display.
550 var content
= new FlatBuffer
552 # Adds text to the pager.
553 fun add
(text
: String) do
554 content
.append
(escape
(text
))
557 fun render
do sys
.system
("echo \"{content}\
" | less -r")
559 fun escape
(str
: String): String
561 var b
= new FlatBuffer
562 for c
in str
.chars
do
565 else if c
== '\0' then
567 else if c
== '"' then
569 else if c == '\\' then
571 else if c == '`' then
573 else if c.code_point < 32 then
574 b.append("\\{c.code_point.to_base(8)}")