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