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