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