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