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