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