doc/commands: parse CmdEntityLink command strings
[nit.git] / src / doc / commands / commands_parser.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 # A parser that create DocCommand from a string
16 #
17 # Used by both Nitx and the Markdown doc commands.
18 module commands_parser
19
20 import commands::commands_model
21 import commands::commands_graph
22 import commands::commands_usage
23 import commands::commands_catalog
24
25 # Parse string commands to create DocQueries
26 class CommandParser
27
28 # ModelView used to retrieve mentities
29 var view: ModelView
30
31 # ModelBuilder used to retrieve AST nodes
32 var modelbuilder: ModelBuilder
33
34 # Catalog used for catalog commands
35 var catalog: nullable Catalog
36
37 # List of allowed command names for this parser
38 var allowed_commands: Array[String] = [
39 "link", "doc", "code", "lin", "uml", "graph", "search",
40 "parents", "ancestors", "children", "descendants",
41 "param", "return", "new", "call", "defs", "list", "random",
42 "catalog", "stats", "tags", "tag", "person", "contrib", "maintain"] is writable
43
44 # List of commands usage and documentation
45 var commands_usage: Map[String, String] do
46 var usage = new ArrayMap[String, String]
47 usage["search: <string>"] = "list entities matching `string`"
48 usage["link: <name>"] = "display the link to `name`"
49 usage["doc: <name>"] = "display the documentation for `name`"
50 usage["defs: <name>"] = "list all definitions for `name`"
51 usage["code: <name>"] = "display the code for `name`"
52 usage["lin: <name>"] = "display the linearization for `name`"
53 usage["uml: <name>"] = "display the UML diagram for `name`"
54 usage["graph: <name>"] = "display the inheritance graph for `name`"
55 usage["parents: <name>"] = "list the direct parents of `name`"
56 usage["ancestors: <name>"] = "list all ancestors of `name`"
57 usage["children: <name>"] = "list direct children of `name`"
58 usage["descendants: <name>"] = "list all descendants of `name`"
59 usage["param: <type>"] = "list all methods accepting `type` as parameter"
60 usage["return: <type>"] = "list all methods returning `type`"
61 usage["new: <class>"] = "list all methods initializing `class`"
62 usage["call: <property>"] = "list all methods calling `property`"
63 usage["list: <kind>"] = "list all entities of `kind` from the model"
64 usage["random: <kind>"] = "list random entities of `kind` from the model"
65 usage["catalog:"] = "list packages from catalog"
66 usage["stats:"] = "display catalog statistics"
67 usage["tags:"] = "list all tabs from catalog"
68 usage["tag: <tag>"] = "list all packages with `tag`"
69 usage["maintain: <person>"] = "list all packages maintained by `person`"
70 usage["contrib: <person>"] = "list all packages contributed by `person`"
71 return usage
72 end
73
74 # Parse `string` as a DocCommand
75 #
76 # Returns `null` if the string cannot be parsed.
77 # See `error` for the error messages produced by both the parser and the commands.
78 fun parse(string: String): nullable DocCommand do
79 var pos = 0
80 var tmp = new FlatBuffer
81 error = null
82
83 # Parse command name
84 pos = string.read_until(tmp, pos, ':', '|')
85 var name = tmp.write_to_string.trim
86
87 # Check allowed commands
88 if name.is_empty then
89 error = new CmdParserError("Empty command name", 0)
90 return null
91 end
92 # If the command name contains two consecutive colons or there is no colon in the name,
93 # we certainly have a wiki link to a mentity
94 var is_short_link = false
95 if (pos < string.length - 2 and string[pos] == ':' and string[pos + 1] == ':') or
96 pos == string.length then
97 is_short_link = true
98 else if pos < string.length - 1 and string[pos] == '|' then
99 is_short_link = true
100 pos -= 1
101 else if not allowed_commands.has(name) then
102 error = new CmdParserError("Unknown command name `{name}`", 0)
103 return null
104 end
105
106 # Parse the argument
107 tmp.clear
108 pos = string.read_until(tmp, pos + 1, '|')
109 var arg = tmp.write_to_string.trim
110 if is_short_link and not arg.is_empty then
111 arg = "{name}:{arg}"
112 else if is_short_link then
113 arg = name
114 end
115
116 # Parse command options
117 var opts = new HashMap[String, String]
118 while pos < string.length do
119 # Parse option name
120 tmp.clear
121 pos = string.read_until(tmp, pos + 1, ':', ',')
122 var oname = tmp.write_to_string.trim
123 var oval = ""
124 if oname.is_empty then break
125 # Parse option value
126 if pos < string.length and string[pos] == ':' then
127 tmp.clear
128 pos = string.read_until(tmp, pos + 1, ',')
129 oval = tmp.write_to_string.trim
130 end
131 opts[oname] = oval
132 end
133
134 # Build the command
135 var command
136 if is_short_link then
137 command = new CmdEntityLink(view)
138 else
139 command = new_command(name)
140 end
141 if command == null then
142 error = new CmdParserError("Unknown command name `{name}`", 0)
143 return null
144 end
145
146 # Initialize command from string options
147 var status = command.parser_init(arg, opts)
148 if not status isa CmdSuccess then error = status
149
150 return command
151 end
152
153 # Init a new DocCommand from its `name`
154 #
155 # You must redefine this method to add new custom commands.
156 fun new_command(name: String): nullable DocCommand do
157 # CmdEntity
158 if name == "link" then return new CmdEntityLink(view)
159 if name == "doc" then return new CmdComment(view)
160 if name == "code" then return new CmdEntityCode(view, modelbuilder)
161 if name == "lin" then return new CmdLinearization(view)
162 if name == "defs" then return new CmdFeatures(view)
163 if name == "parents" then return new CmdParents(view)
164 if name == "ancestors" then return new CmdAncestors(view)
165 if name == "children" then return new CmdChildren(view)
166 if name == "descendants" then return new CmdDescendants(view)
167 if name == "param" then return new CmdParam(view)
168 if name == "return" then return new CmdReturn(view)
169 if name == "new" then return new CmdNew(view, modelbuilder)
170 if name == "call" then return new CmdCall(view, modelbuilder)
171 # CmdGraph
172 if name == "uml" then return new CmdUML(view)
173 if name == "graph" then return new CmdInheritanceGraph(view)
174 # CmdModel
175 if name == "list" then return new CmdModelEntities(view)
176 if name == "random" then return new CmdRandomEntities(view)
177 # CmdCatalog
178 var catalog = self.catalog
179 if catalog != null then
180 if name == "catalog" then return new CmdCatalogPackages(view, catalog)
181 if name == "stats" then return new CmdCatalogStats(view, catalog)
182 if name == "tags" then return new CmdCatalogTags(view, catalog)
183 if name == "tag" then return new CmdCatalogTag(view, catalog)
184 if name == "person" then return new CmdCatalogPerson(view, catalog)
185 if name == "contrib" then return new CmdCatalogContributing(view, catalog)
186 if name == "maintain" then return new CmdCatalogMaintaining(view, catalog)
187 if name == "search" then return new CmdCatalogSearch(view, catalog)
188 else
189 if name == "search" then return new CmdSearch(view)
190 end
191 return null
192 end
193
194 # Error or warning from last call to `parse`
195 var error: nullable CmdMessage = null
196 end
197
198 # An error produced by the CmdParser
199 class CmdParserError
200 super CmdError
201
202 # Error message
203 var message: String
204
205 # Column related to the error
206 var column: nullable Int
207
208 redef fun to_s do return message
209 end
210
211 redef class DocCommand
212
213 # Initialize the command from the CommandParser data
214 fun parser_init(arg: String, options: Map[String, String]): CmdMessage do
215 return init_command
216 end
217 end
218
219 redef class CmdEntity
220 redef fun parser_init(mentity_name, options) do
221 self.mentity_name = mentity_name
222 return super
223 end
224 end
225
226 redef class CmdList
227 redef fun parser_init(mentity_name, options) do
228 if options.has_key("limit") and options["limit"].is_int then limit = options["limit"].to_i
229 return super
230 end
231 end
232
233 # Model commands
234
235 redef class CmdComment
236 redef fun parser_init(mentity_name, options) do
237 full_doc = not options.has_key("only-synopsis")
238 fallback = not options.has_key("no-fallback")
239 if options.has_key("format") then format = options["format"]
240 return super
241 end
242 end
243
244 redef class CmdEntityLink
245 redef fun parser_init(mentity_name, options) do
246 if options.has_key("text") then text = options["text"]
247 if options.has_key("title") then title = options["title"]
248 return super
249 end
250 end
251
252 redef class CmdCode
253 redef fun parser_init(mentity_name, options) do
254 if options.has_key("format") then format = options["format"]
255 return super
256 end
257 end
258
259 redef class CmdSearch
260 redef fun parser_init(mentity_name, options) do
261 query = mentity_name
262 if options.has_key("page") and options["page"].is_int then page = options["page"].to_i
263 return super
264 end
265 end
266
267 redef class CmdAncestors
268 redef fun parser_init(mentity_name, options) do
269 if options.has_key("parents") and options["parents"] == "false" then parents = false
270 return super
271 end
272 end
273
274 redef class CmdDescendants
275 redef fun parser_init(mentity_name, options) do
276 if options.has_key("children") and options["children"] == "false" then children = false
277 return super
278 end
279 end
280
281 redef class CmdModelEntities
282 redef fun parser_init(kind, options) do
283 self.kind = kind
284 return super
285 end
286 end
287
288 redef class CmdGraph
289 redef fun parser_init(mentity_name, options) do
290 if options.has_key("format") then format = options["format"]
291 return super
292 end
293 end
294
295 redef class CmdInheritanceGraph
296 redef fun parser_init(mentity_name, options) do
297 if options.has_key("pdepth") and options["pdepth"].is_int then
298 pdepth = options["pdepth"].to_i
299 end
300 if options.has_key("cdepth") and options["cdepth"].is_int then
301 cdepth = options["cdepth"].to_i
302 end
303 return super
304 end
305 end
306
307 # Catalog commands
308
309 redef class CmdCatalogTag
310 redef fun parser_init(mentity_name, options) do
311 tag = mentity_name
312 return super
313 end
314 end
315
316 redef class CmdCatalogPerson
317 redef fun parser_init(mentity_name, options) do
318 person_name = mentity_name
319 return super
320 end
321 end
322
323 # Utils
324
325 redef class Text
326 # Read `self` as raw text until `nend` and append it to the `out` buffer.
327 private fun read_until(out: FlatBuffer, start: Int, nend: Char...): Int do
328 var pos = start
329 while pos < length do
330 var c = self[pos]
331 var end_reached = false
332 for n in nend do
333 if c == n then
334 end_reached = true
335 break
336 end
337 end
338 if end_reached then break
339 out.add c
340 pos += 1
341 end
342 return pos
343 end
344 end