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