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