e4f6bb1add7552c01f70a182652842615490d25e
[nit.git] / src / doc / doc_phases / doc_console.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
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
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 # Nitx related components
16 #
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.
19 module doc_console
20
21 import semantize
22 import doc_commands
23 import doc_extract
24 import doc_poset
25 import doc::console_templates
26
27 # Nitx handles console I/O.
28 #
29 # Using `prompt`, the command line can be turned on an interactive tool.
30 class Nitx
31
32 # ToolContext used to access options.
33 var ctx: ToolContext
34
35 # DocModel that contains the informations to display.
36 var doc: DocModel
37
38 # Comparator used to sort MEntities.
39 var sorter = new MEntityNameSorter
40
41 # Displays the welcome message and start prompt.
42 fun start do
43 welcome
44 prompt
45 end
46
47 # Displays the welcome message and the list of loaded modules.
48 fun welcome do
49 print "Welcome in the Nit Index."
50 print ""
51 print "Loaded modules:"
52 var mmodules = doc.mmodules.to_a
53 sorter.sort(mmodules)
54 for m in mmodules do
55 print "\t{m.name}"
56 end
57 print ""
58 help
59 end
60
61 # Displays the list of available commands.
62 fun help do
63 print "\nCommands:\n"
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"
78 print "\n"
79 print "\t:h\t\t\tdisplay this help message"
80 print "\t:q\t\t\tquit interactive mode"
81 print ""
82 end
83
84 # Prompts the user for a command.
85 fun prompt do
86 printn ">> "
87 do_query(sys.stdin.read_line)
88 prompt
89 end
90
91 # Processes the query string and performs it.
92 fun do_query(str: String) do
93 var query = new DocCommand(str)
94 if query isa NitxCommand then
95 query.execute(self)
96 return
97 end
98 var res = query.perform(self, doc)
99 var page = query.make_results(self, res)
100 print page.write_to_string
101 end
102 end
103
104 redef interface DocCommand
105
106 redef new(query_string) do
107 if query_string == ":q" then
108 return new NitxQuit
109 else if query_string == ":h" then
110 return new NitxHelp
111 end
112 var cmd = super(query_string)
113 if cmd isa UnknownCommand then
114 return new CommentCommand("comment: {query_string}")
115 end
116 return cmd
117 end
118
119 # Looks up the `doc` model and returns possible matches.
120 fun perform(nitx: Nitx, doc: DocModel): Array[NitxMatch] is abstract
121
122 # Pretty prints the results for the console.
123 fun make_results(nitx: Nitx, results: Array[NitxMatch]): DocPage do
124 var page = new DocPage("results", "Results")
125 page.root.add_child(new QueryResultArticle("results", "Results", self, results))
126 return page
127 end
128 end
129
130 # Something that matches a `DocCommand`.
131 abstract class NitxMatch
132
133 # Query matched by `self`.
134 var query: DocCommand
135
136 # Pretty prints `self` for console.
137 fun make_list_item: String is abstract
138 end
139
140 # A match between a `DocCommand` and a `MEntity`.
141 class MEntityMatch
142 super NitxMatch
143
144 # MEntity matched.
145 var mentity: MEntity
146
147 redef fun make_list_item do return mentity.cs_list_item
148 end
149
150 redef class CommentCommand
151 redef fun perform(nitx, doc) do
152 var name = args.first
153 var res = new Array[NitxMatch]
154 for mentity in doc.mentities_by_name(name) do
155 res.add new MEntityMatch(self, mentity)
156 end
157 return res
158 end
159
160 redef fun make_results(nitx, results) do
161 var len = results.length
162 if len == 1 then
163 var res = results.first.as(MEntityMatch)
164 var mentity = res.mentity
165 var page = new DocPage("resultats", "Results")
166 var article = new DefinitionArticle("results", "Results", mentity)
167 article.cs_title = mentity.name
168 article.cs_subtitle = mentity.cs_declaration
169 page.root.add_child article
170 return page
171 else
172 return super
173 end
174 end
175 end
176
177 # A query to search signatures using a specific `MType` as parameter.
178 redef class ParamCommand
179 redef fun perform(nitx, doc) do
180 var res = new Array[NitxMatch]
181 var mtype_name = args.first
182 for mproperty in doc.mproperties do
183 if not mproperty isa MMethod then continue
184 var msignature = mproperty.intro.msignature
185 if msignature != null then
186 for mparam in msignature.mparameters do
187 if mparam.mtype.name == mtype_name then
188 res.add new MEntityMatch(self, mproperty)
189 end
190 end
191 end
192 end
193 return res
194 end
195 end
196
197 # A query to search signatures using a specific `MType` as return.
198 redef class ReturnCommand
199 redef fun perform(nitx, doc) do
200 var res = new Array[NitxMatch]
201 var mtype_name = args.first
202 for mproperty in doc.mproperties do
203 if not mproperty isa MMethod then continue
204 var msignature = mproperty.intro.msignature
205 if msignature != null then
206 var mreturn = msignature.return_mtype
207 if mreturn != null and mreturn.name == mtype_name then
208 res.add new MEntityMatch(self, mproperty)
209 end
210 end
211 end
212 return res
213 end
214 end
215
216 # A query to search methods creating new instances of a specific `MType`.
217 redef class NewCommand
218 redef fun perform(nitx, doc) do
219 var res = new Array[NitxMatch]
220 var mtype_name = args.first
221 for mpropdef in doc.mpropdefs do
222 var visitor = new TypeInitVisitor(mtype_name)
223 var npropdef = nitx.ctx.modelbuilder.mpropdef2node(mpropdef)
224 if npropdef == null then continue
225 visitor.enter_visit(npropdef)
226 for i in visitor.inits do
227 res.add new MEntityMatch(self, mpropdef)
228 end
229 end
230 return res
231 end
232 end
233
234 # A query to search methods calling a specific `MProperty`.
235 redef class CallCommand
236 redef fun perform(nitx, doc) do
237 var res = new Array[NitxMatch]
238 var mprop_name = args.first
239 for mpropdef in doc.mpropdefs do
240 var visitor = new MPropertyCallVisitor
241 var npropdef = nitx.ctx.modelbuilder.mpropdef2node(mpropdef)
242 if npropdef == null then continue
243 visitor.enter_visit(npropdef)
244 for mprop in visitor.calls do
245 if mprop.name != mprop_name then continue
246 res.add new MEntityMatch(self, mpropdef)
247 end
248 end
249 return res
250 end
251 end
252
253 # A query to search a Nitdoc documentation page by its name.
254 redef class ArticleCommand
255 redef fun perform(nitx, doc) do
256 var res = new Array[NitxMatch]
257 var name = args.first
258 for page in doc.pages.values do
259 if name == "*" then # FIXME dev only
260 res.add new PageMatch(self, page)
261 else if page.title == name then
262 res.add new PageMatch(self, page)
263 else if page isa MEntityPage and page.mentity.cs_namespace == name then
264 res.add new PageMatch(self, page)
265 end
266 end
267 return res
268 end
269
270 redef fun make_results(nitx, results) do
271 var len = results.length
272 # FIXME how to render the pager for one worded namespaces like "core"?
273 if len == 1 then
274 var page = results.first.as(PageMatch).page
275 var pager = new Pager
276 pager.add page.write_to_string
277 pager.render
278 return page
279 else
280 return super
281 end
282 end
283 end
284
285 # A match between a `DocPage` and a `MEntity`.
286 class PageMatch
287 super NitxMatch
288
289 # `DocPage` matched.
290 var page: DocPage
291
292 redef fun make_list_item do
293 var page = self.page
294 if page isa MEntityPage then
295 return page.mentity.cs_list_item
296 end
297 return " * {page.title}"
298 end
299 end
300
301 # Search in class or module hierarchy of a `MEntity`.
302 #
303 # It actually searches for pages about the mentity and extracts the
304 # pre-calculated hierarchies by the `doc_post` phase.
305 abstract class HierarchiesQuery
306 super DocCommand
307
308 redef fun make_results(nitx, results) do
309 var page = new DocPage("hierarchy", "Hierarchy")
310 for result in results do
311 if not result isa PageMatch then continue
312 var rpage = result.page
313 if not rpage isa MClassPage then continue
314 page.root.add_child build_article(rpage)
315 end
316 return page
317 end
318
319 # Build an article containing the hierarchy list depending on subclasses.
320 private fun build_article(page: MClassPage): DocArticle is abstract
321 end
322
323 # List all parents of a `MClass`.
324 class AncestorsQuery
325 super HierarchiesQuery
326
327 redef fun build_article(page) do
328 return new MEntitiesListArticle(
329 "ancerstors",
330 "Ancestors for {page.mentity.name}",
331 page.ancestors.to_a)
332 end
333 end
334
335 # List direct parents of a `MClass`.
336 class ParentsQuery
337 super HierarchiesQuery
338
339 redef fun build_article(page) do
340 return new MEntitiesListArticle(
341 "parents",
342 "Parents for {page.mentity.name}",
343 page.parents.to_a)
344 end
345 end
346
347 # List direct children of a `MClass`.
348 class ChildrenQuery
349 super HierarchiesQuery
350
351 redef fun build_article(page) do
352 return new MEntitiesListArticle(
353 "children",
354 "Children for {page.mentity.name}",
355 page.children.to_a)
356 end
357 end
358
359 # List all descendants of a `MClass`.
360 class DescendantsQuery
361 super HierarchiesQuery
362
363 redef fun build_article(page) do
364 return new MEntitiesListArticle(
365 "descendants",
366 "Descendants for {page.mentity.name}",
367 page.children.to_a)
368 end
369 end
370
371 # A query to search source code from a file name.
372 redef class CodeCommand
373 # FIXME refactor this!
374 redef fun perform(nitx, doc) do
375 var res = new Array[NitxMatch]
376 var name = args.first
377 # if name is an existing sourcefile, opens it
378 if name.file_exists then
379 var fr = new FileReader.open(name)
380 var content = fr.read_all
381 fr.close
382 res.add new CodeMatch(self, name, content)
383 return res
384 end
385 # else, lookup the model by name
386 for mentity in doc.mentities_by_name(name) do
387 if mentity isa MClass then continue
388 if mentity isa MProperty then continue
389 res.add new CodeMatch(self, mentity.cs_location, mentity.cs_source_code)
390 end
391 return res
392 end
393
394 redef fun make_results(nitx, results) do
395 var page = new DocPage("results", "Code Results")
396 for res in results do
397 page.add new CodeQueryArticle("results", "Results", self, res.as(CodeMatch))
398 end
399 return page
400 end
401 end
402
403 # A match between a piece of code and a string.
404 class CodeMatch
405 super NitxMatch
406
407 # Location of the code match.
408 var location: String
409
410 # Piece of code matched.
411 var content: String
412
413 redef fun make_list_item do return "* {location}"
414 end
415
416
417 # A query that contains a nitx command.
418 #
419 # These commands are prefixed with `:` and are used to control the execution of
420 # `nitx` like displaying the help or quiting.
421 interface NitxCommand
422 super DocCommand
423
424 # Executes the command.
425 fun execute(nitx: Nitx) is abstract
426 end
427
428 # Exits nitx.
429 class NitxQuit
430 super NitxCommand
431
432 redef fun execute(nitx) do exit 0
433 end
434
435 # Displays the help message.
436 class NitxHelp
437 super NitxCommand
438
439 redef fun execute(nitx) do nitx.help
440 end
441
442 ## exploration
443
444 # Visitor looking for initialized `MType` (new T).
445 #
446 # See `NewQuery`.
447 private class TypeInitVisitor
448 super Visitor
449
450 # `MType` name to look for.
451 var mtype_name: String
452
453 var inits = new HashSet[MType]
454 redef fun visit(node)
455 do
456 node.visit_all(self)
457 # look for init
458 if not node isa ANewExpr then return
459 var mtype = node.n_type.mtype
460 if mtype != null and mtype.name == mtype_name then inits.add(mtype)
461 end
462 end
463
464 # Visitor looking for calls to a `MProperty` (new T).
465 #
466 # See `CallQuery`.
467 private class MPropertyCallVisitor
468 super Visitor
469
470 var calls = new HashSet[MProperty]
471 redef fun visit(node)
472 do
473 node.visit_all(self)
474 if not node isa ASendExpr then return
475 calls.add node.callsite.as(not null).mproperty
476 end
477 end
478
479 # display
480
481 # A `DocArticle` that displays query results.
482 private class QueryResultArticle
483 super DocArticle
484
485 # Query linked to the results to display.
486 var query: DocCommand
487
488 # Results to display.
489 var results: Array[NitxMatch]
490
491 redef fun render_title do
492 var len = results.length
493 if len == 0 then
494 add "No result found for '{query.string}'..."
495 else
496 add "# {len} result(s) for '{query.string}'".green.bold
497 end
498 end
499
500 redef fun render_body do
501 addn ""
502 for result in results do
503 addn ""
504 addn result.make_list_item
505 end
506 end
507 end
508
509 # An article that displays a piece of code.
510 private class CodeQueryArticle
511 super DocArticle
512
513 # The query linked to the result to display.
514 var query: DocCommand
515
516 # The result to display.
517 var result: CodeMatch
518
519 redef fun render_body do
520 addn ""
521 addn "in {result.location}".gray.bold
522 addn ""
523 add result.content
524 end
525 end
526
527 # A Pager is used to display data into a unix `less` container.
528 private class Pager
529
530 # Content to display.
531 var content = new FlatBuffer
532
533 # Adds text to the pager.
534 fun add(text: String) do
535 content.append(escape(text))
536 end
537
538 fun render do sys.system("echo \"{content}\" | less -r")
539
540 fun escape(str: String): String
541 do
542 var b = new FlatBuffer
543 for c in str.chars do
544 if c == '\n' then
545 b.append("\\n")
546 else if c == '\0' then
547 b.append("\\0")
548 else if c == '"' then
549 b.append("\\\"")
550 else if c == '\\' then
551 b.append("\\\\")
552 else if c == '`' then
553 b.append("'")
554 else if c.ascii < 32 then
555 b.append("\\{c.ascii.to_base(8, false)}")
556 else
557 b.add(c)
558 end
559 end
560 return b.to_s
561 end
562 end