nitx: introduce doc page search
[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 "\t:h\t\tdisplay this help message"
64 print "\t:q\t\tquit interactive mode"
65 print ""
66 end
67
68 # Prompts the user for a command.
69 fun prompt do
70 printn ">> "
71 do_query(sys.stdin.read_line)
72 prompt
73 end
74
75 # Processes the query string and performs it.
76 fun do_query(str: String) do
77 var query = parse_query(str)
78 var res = query.perform(self, doc)
79 var page = query.make_results(self, res)
80 print page.write_to_string
81 end
82
83 # Returns an `NitxQuery` from a raw query string.
84 fun parse_query(str: String): NitxQuery do
85 var query = new NitxQuery(str)
86 if query isa NitxCommand then
87 query.execute(self)
88 end
89 return query
90 end
91 end
92
93 # A query performed on Nitx.
94 #
95 # Queries are responsible to collect matching results and render them as a
96 # DocPage.
97 #
98 # Used as a factory to concrete instances.
99 interface NitxQuery
100
101 # Original query string.
102 fun query_string: String is abstract
103
104 # Query factory.
105 #
106 # Will return a concrete instance of NitxQuery.
107 new(query_string: String) do
108 if query_string == ":q" then
109 return new NitxQuit
110 else if query_string == ":h" then
111 return new NitxHelp
112 else if query_string.has_prefix("comment:") then
113 return new CommentQuery(query_string)
114 else if query_string.has_prefix("doc:") then
115 return new DocQuery(query_string)
116 end
117 return new CommentQuery("comment: {query_string}")
118 end
119
120 # Looks up the `doc` model and returns possible matches.
121 fun perform(nitx: Nitx, doc: DocModel): Array[NitxMatch] is abstract
122
123 # Pretty prints the results for the console.
124 fun make_results(nitx: Nitx, results: Array[NitxMatch]): DocPage do
125 var page = new DocPage("Results")
126 page.root.add_child(new QueryResultArticle(self, results))
127 return page
128 end
129
130 redef fun to_s do return query_string
131 end
132
133 # Something that matches a `NitxQuery`.
134 abstract class NitxMatch
135
136 # Query matched by `self`.
137 var query: NitxQuery
138
139 # Pretty prints `self` for console.
140 fun make_list_item: String is abstract
141 end
142
143 # A query that contains a meta command.
144 #
145 # In Nitx, commands are written such as `command: args...`.
146 abstract class MetaQuery
147 super NitxQuery
148
149 redef var query_string
150
151 # Meta command used.
152 var command: String is noinit
153
154 # Arguments passed to the `command`.
155 var args = new Array[String]
156
157 init do
158 # parse command
159 var str = new FlatBuffer
160 var i = 0
161 while i < query_string.length do
162 var c = query_string[i]
163 i += 1
164 if c == ':' then break
165 str.add c
166 end
167 command = str.write_to_string
168 # parse args
169 args.add query_string.substring_from(i).trim
170 end
171 end
172
173 # A match between a `NitxQuery` and a `MEntity`.
174 class MEntityMatch
175 super NitxMatch
176
177 # MEntity matched.
178 var mentity: MEntity
179
180 redef fun make_list_item do return mentity.cs_list_item
181 end
182
183 # A query to search a `MEntity` comment by its name or namespace.
184 class CommentQuery
185 super MetaQuery
186
187 redef fun perform(nitx, doc) do
188 var name = args.first
189 var res = new Array[NitxMatch]
190 for mentity in doc.search_mentities(name) do
191 res.add new MEntityMatch(self, mentity)
192 end
193 return res
194 end
195
196 redef fun make_results(nitx, results) do
197 var len = results.length
198 if len == 1 then
199 var res = results.first.as(MEntityMatch)
200 var mentity = res.mentity
201 var page = new DocPage("Results")
202 var article = new DefinitionArticle(mentity)
203 article.cs_title = mentity.name
204 article.cs_subtitle = mentity.cs_declaration
205 page.root.add_child article
206 return page
207 else
208 return super
209 end
210 end
211 end
212
213 # A query to search a Nitdoc documentation page by its name.
214 class DocQuery
215 super MetaQuery
216
217 redef fun perform(nitx, doc) do
218 var res = new Array[NitxMatch]
219 var name = args.first
220 for page in doc.pages do
221 if name == "*" then # FIXME dev only
222 res.add new PageMatch(self, page)
223 else if page.title == name then
224 res.add new PageMatch(self, page)
225 else if page isa MEntityPage and page.mentity.cs_namespace == name then
226 res.add new PageMatch(self, page)
227 end
228 end
229 return res
230 end
231
232 redef fun make_results(nitx, results) do
233 var len = results.length
234 # FIXME how to render the pager for one worded namespaces like "standard"?
235 if len == 1 then
236 var page = results.first.as(PageMatch).page
237 var pager = new Pager
238 pager.add page.write_to_string
239 pager.render
240 return page
241 else
242 return super
243 end
244 end
245 end
246
247 # A match between a `DocPage` and a `MEntity`.
248 class PageMatch
249 super NitxMatch
250
251 # `DocPage` matched.
252 var page: DocPage
253
254 redef fun make_list_item do
255 var page = self.page
256 if page isa MEntityPage then
257 return page.mentity.cs_list_item
258 end
259 return " * {page.title}"
260 end
261 end
262
263 # A query that contains a nitx command.
264 #
265 # These commands are prefixed with `:` and are used to control the execution of
266 # `nitx` like displaying the help or quiting.
267 interface NitxCommand
268 super NitxQuery
269
270 # Executes the command.
271 fun execute(nitx: Nitx) is abstract
272 end
273
274 # Exits nitx.
275 class NitxQuit
276 super NitxCommand
277
278 redef fun execute(nitx) do exit 0
279 end
280
281 # Displays the help message.
282 class NitxHelp
283 super NitxCommand
284
285 redef fun execute(nitx) do nitx.help
286 end
287
288 ## exploration
289
290 redef class DocModel
291
292 # Lists all MEntities in the model.
293 private var mentities: Collection[MEntity] is lazy do
294 var res = new HashSet[MEntity]
295 res.add_all mprojects
296 res.add_all mgroups
297 res.add_all mmodules
298 res.add_all mclasses
299 res.add_all mclassdefs
300 res.add_all mproperties
301 res.add_all mpropdefs
302 return res
303 end
304
305 # Search MEntities that match `name` by their name or namespace.
306 private fun search_mentities(name: String): Array[MEntity] do
307 var res = new Array[MEntity]
308 for mentity in mentities do
309 if mentity.name != name and mentity.cs_namespace != name then continue
310 res.add mentity
311 end
312 return res
313 end
314 end
315
316 # display
317
318 # A `DocArticle` that displays query results.
319 private class QueryResultArticle
320 super DocArticle
321
322 # Query linked to the results to display.
323 var query: NitxQuery
324
325 # Results to display.
326 var results: Array[NitxMatch]
327
328 redef fun render_title do
329 var len = results.length
330 if len == 0 then
331 add "No result found for '{query.query_string}'..."
332 else
333 add "# {len} result(s) for '{query.query_string}'".green.bold
334 end
335 end
336
337 redef fun render_body do
338 addn ""
339 for result in results do
340 addn ""
341 addn result.make_list_item
342 end
343 end
344 end
345
346 # A Pager is used to display data into a unix `less` container.
347 private class Pager
348
349 # Content to display.
350 var content = new FlatBuffer
351
352 # Adds text to the pager.
353 fun add(text: String) do
354 content.append(escape(text))
355 end
356
357 fun render do sys.system("echo \"{content}\" | less -r")
358
359 fun escape(str: String): String
360 do
361 var b = new FlatBuffer
362 for c in str.chars do
363 if c == '\n' then
364 b.append("\\n")
365 else if c == '\0' then
366 b.append("\\0")
367 else if c == '"' then
368 b.append("\\\"")
369 else if c == '\\' then
370 b.append("\\\\")
371 else if c == '`' then
372 b.append("'")
373 else if c.ascii < 32 then
374 b.append("\\{c.ascii.to_base(8, false)}")
375 else
376 b.add(c)
377 end
378 end
379 return b.to_s
380 end
381 end