1 # This file is part of NIT ( http://www.nitlanguage.org ).
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
15 # A parser that create DocCommand from a string
17 # Used by both Nitx and the Markdown doc commands.
18 module commands_parser
20 import commands_catalog
26 # Parse string commands to create DocQueries
29 # Model used to retrieve mentities
32 # Main module for linearization
33 var mainmodule
: MModule
35 # ModelBuilder used to retrieve AST nodes
36 var modelbuilder
: ModelBuilder
38 # Catalog used for catalog commands
39 var catalog
: nullable Catalog
41 # List of allowed command names for this parser
42 var allowed_commands
: Array[String] = [
43 "link", "doc", "code", "lin", "uml", "graph", "search",
44 "parents", "ancestors", "children", "descendants",
45 "param", "return", "new", "call", "defs", "list", "random",
46 "ini-desc", "ini-git", "ini-issues", "ini-maintainer", "ini-contributors", "ini-license",
47 "license-file", "contrib-file", "license-content", "contrib-content", "git-clone",
48 "mains", "main-compile", "main-run", "main-opts", "testing",
49 "catalog", "stats", "tags", "tag", "person", "contrib", "maintain"] is writable
51 # List of commands usage and documentation
52 var commands_usage
: Map[String, String] do
53 var usage
= new ArrayMap[String, String]
54 usage
["search: <string>"] = "list entities matching `string`"
55 usage
["link: <name>"] = "display the link to `name`"
56 usage
["doc: <name>"] = "display the documentation for `name`"
57 usage
["defs: <name>"] = "list all definitions for `name`"
58 usage
["code: <name>"] = "display the code for `name`"
59 usage
["lin: <name>"] = "display the linearization for `name`"
60 usage
["uml: <name>"] = "display the UML diagram for `name`"
61 usage
["graph: <name>"] = "display the inheritance graph for `name`"
62 usage
["parents: <name>"] = "list the direct parents of `name`"
63 usage
["ancestors: <name>"] = "list all ancestors of `name`"
64 usage
["children: <name>"] = "list direct children of `name`"
65 usage
["descendants: <name>"] = "list all descendants of `name`"
66 usage
["param: <type>"] = "list all methods accepting `type` as parameter"
67 usage
["return: <type>"] = "list all methods returning `type`"
68 usage
["new: <class>"] = "list all methods initializing `class`"
69 usage
["call: <property>"] = "list all methods calling `property`"
70 usage
["list: <kind>"] = "list all entities of `kind` from the model"
71 usage
["random: <kind>"] = "list random entities of `kind` from the model"
72 usage
["catalog:"] = "list packages from catalog"
73 usage
["stats:"] = "display catalog statistics"
74 usage
["tags:"] = "list all tabs from catalog"
75 usage
["tag: <tag>"] = "list all packages with `tag`"
76 usage
["maintain: <person>"] = "list all packages maintained by `person`"
77 usage
["contrib: <person>"] = "list all packages contributed by `person`"
79 usage
["ini-desc: <package>"] = "display the description from the `package` ini file"
80 usage
["ini-git: <package>"] = "display the git url from the `package` ini file"
81 usage
["ini-issues: <package>"] = "display the issues url from the `package` ini file"
82 usage
["ini-license: <package>"] = "display the license from the `package` ini file"
83 usage
["ini-maintainer: <package>"] = "display the maintainer from the `package` ini file"
84 usage
["ini-contributors: <package>"] = "display the contributors from the `package` ini file"
85 usage
["license-file: <package>"] = "display the license file for the `package`"
86 usage
["license-content: <package>"] = "display the license file content for the `package`"
87 usage
["contrib-file: <package>"] = "display the contrib file for the `package`"
88 usage
["contrib-content: <package>"] = "display the contrib file content for the `package`"
89 usage
["git-clone: <package>"] = "display the git clone command for the `package`"
91 usage
["mains: <name>"] = "display the list of main methods for `name`"
92 usage
["main-compile: <name>"] = "display the nitc command to compile `name`"
93 usage
["main-run: <name>"] = "display the command to run `name`"
94 usage
["main-opts: <name>"] = "display the command options for `name`"
95 usage
["testing: <name>"] = "display the nitunit command to test `name`"
99 # Parse `string` as a DocCommand
101 # Returns `null` if the string cannot be parsed.
102 # See `error` for the error messages produced by both the parser and the commands.
103 fun parse
(string
: String): nullable DocCommand do
105 var tmp
= new FlatBuffer
109 pos
= string
.read_until
(tmp
, pos
, ':', '|')
110 var name
= tmp
.write_to_string
.trim
112 # Check allowed commands
113 if name
.is_empty
then
114 error
= new CmdParserError("Empty command name", 0)
117 # If the command name contains two consecutive colons or there is no colon in the name,
118 # we certainly have a wiki link to a mentity
119 var is_short_link
= false
120 if (pos
< string
.length
- 2 and string
[pos
] == ':' and string
[pos
+ 1] == ':') or
121 pos
== string
.length
then
123 else if pos
< string
.length
- 1 and string
[pos
] == '|' then
126 else if not allowed_commands
.has
(name
) then
127 error
= new CmdParserError("Unknown command name `{name}`", 0)
133 pos
= string
.read_until
(tmp
, pos
+ 1, '|')
134 var arg
= tmp
.write_to_string
.trim
135 if is_short_link
and not arg
.is_empty
then
137 else if is_short_link
then
141 # Parse command options
142 var opts
= new CmdOptions
143 while pos
< string
.length
do
146 pos
= string
.read_until
(tmp
, pos
+ 1, ':', ',')
147 var oname
= tmp
.write_to_string
.trim
149 if oname
.is_empty
then break
151 if pos
< string
.length
and string
[pos
] == ':' then
153 pos
= string
.read_until
(tmp
, pos
+ 1, ',')
154 oval
= tmp
.write_to_string
.trim
161 if is_short_link
then
162 command
= new CmdEntityLink(model
)
164 command
= new_command
(name
)
166 if command
== null then
167 error
= new CmdParserError("Unknown command name `{name}`", 0)
171 # Initialize command from string options
172 var status
= command
.parser_init
(arg
, opts
)
173 if not status
isa CmdSuccess then error
= status
178 # Init a new DocCommand from its `name`
180 # You must redefine this method to add new custom commands.
181 fun new_command
(name
: String): nullable DocCommand do
183 if name
== "link" then return new CmdEntityLink(model
)
184 if name
== "doc" then return new CmdComment(model
)
185 if name
== "code" then return new CmdEntityCode(model
, modelbuilder
)
186 if name
== "lin" then return new CmdLinearization(model
, mainmodule
)
187 if name
== "defs" then return new CmdFeatures(model
)
188 if name
== "parents" then return new CmdParents(model
, mainmodule
)
189 if name
== "ancestors" then return new CmdAncestors(model
, mainmodule
)
190 if name
== "children" then return new CmdChildren(model
, mainmodule
)
191 if name
== "descendants" then return new CmdDescendants(model
, mainmodule
)
192 if name
== "param" then return new CmdParam(model
)
193 if name
== "return" then return new CmdReturn(model
)
194 if name
== "new" then return new CmdNew(model
, modelbuilder
)
195 if name
== "call" then return new CmdCall(model
, modelbuilder
)
197 if name
== "uml" then return new CmdUML(model
, mainmodule
)
198 if name
== "graph" then return new CmdInheritanceGraph(model
, mainmodule
)
200 if name
== "list" then return new CmdModelEntities(model
)
201 if name
== "random" then return new CmdRandomEntities(model
)
203 if name
== "ini-desc" then return new CmdIniDescription(model
)
204 if name
== "ini-git" then return new CmdIniGitUrl(model
)
205 if name
== "ini-issues" then return new CmdIniIssuesUrl(model
)
206 if name
== "ini-license" then return new CmdIniLicense(model
)
207 if name
== "ini-maintainer" then return new CmdIniMaintainer(model
)
208 if name
== "ini-contributors" then return new CmdIniContributors(model
)
209 if name
== "license-file" then return new CmdLicenseFile(model
)
210 if name
== "license-content" then return new CmdLicenseFileContent(model
)
211 if name
== "contrib-file" then return new CmdContribFile(model
)
212 if name
== "contrib-content" then return new CmdContribFileContent(model
)
213 if name
== "git-clone" then return new CmdIniCloneCommand(model
)
215 if name
== "mains" then return new CmdMains(model
)
216 if name
== "main-compile" then return new CmdMainCompile(model
)
217 if name
== "main-run" then return new CmdManSynopsis(model
)
218 if name
== "main-opts" then return new CmdManOptions(model
)
219 if name
== "testing" then return new CmdTesting(model
)
221 var catalog
= self.catalog
222 if catalog
!= null then
223 if name
== "catalog" then return new CmdCatalogPackages(model
, catalog
)
224 if name
== "stats" then return new CmdCatalogStats(model
, catalog
)
225 if name
== "tags" then return new CmdCatalogTags(model
, catalog
)
226 if name
== "tag" then return new CmdCatalogTag(model
, catalog
)
227 if name
== "person" then return new CmdCatalogPerson(model
, catalog
)
228 if name
== "contrib" then return new CmdCatalogContributing(model
, catalog
)
229 if name
== "maintain" then return new CmdCatalogMaintaining(model
, catalog
)
230 if name
== "search" then return new CmdCatalogSearch(model
, catalog
)
232 if name
== "search" then return new CmdSearch(model
)
237 # Error or warning from last call to `parse`
238 var error
: nullable CmdMessage = null
241 # An error produced by the CmdParser
248 # Column related to the error
249 var column
: nullable Int
251 redef fun to_s
do return message
254 redef class DocCommand
256 # Initialize the command from the CommandParser data
257 fun parser_init
(arg
: String, options
: CmdOptions): CmdMessage do
258 var filter
= cmd_filter
259 var opt_vis
= options
.opt_visibility
("min-visibility")
260 if opt_vis
!= null then filter
.min_visibility
= opt_vis
261 var opt_fictive
= options
.opt_bool
("no-fictive")
262 if opt_fictive
!= null then filter
.accept_fictive
= not opt_fictive
263 var opt_test
= options
.opt_bool
("no-test")
264 if opt_test
!= null then filter
.accept_test
= not opt_test
265 var opt_redef
= options
.opt_bool
("no-redef")
266 if opt_redef
!= null then filter
.accept_redef
= not opt_redef
267 var opt_extern
= options
.opt_bool
("no-extern")
268 if opt_extern
!= null then filter
.accept_extern
= not opt_extern
269 var opt_example
= options
.opt_bool
("no-example")
270 if opt_example
!= null then filter
.accept_example
= not opt_example
271 var opt_attr
= options
.opt_bool
("no-attribute")
272 if opt_attr
!= null then filter
.accept_attribute
= not opt_attr
273 var opt_doc
= options
.opt_bool
("no-empty-doc")
274 if opt_doc
!= null then filter
.accept_empty_doc
= not opt_doc
275 var opt_inh
= options
.opt_mentity
(model
, "inherit")
276 if opt_inh
!= null then filter
.accept_inherited
= opt_inh
277 var opt_match
= options
.opt_string
("match")
278 if opt_match
!= null then filter
.accept_full_name
= opt_match
284 redef class CmdEntity
285 redef fun parser_init
(mentity_name
, options
) do
286 self.mentity_name
= mentity_name
292 redef fun parser_init
(mentity_name
, options
) do
293 var opt_page
= options
.opt_int
("page")
294 if opt_page
!= null then page
= opt_page
295 var opt_limit
= options
.opt_int
("limit")
296 if opt_limit
!= null then limit
= opt_limit
303 redef class CmdComment
304 redef fun parser_init
(mentity_name
, options
) do
305 var opt_full_doc
= options
.opt_bool
("only-synopsis")
306 if opt_full_doc
!= null then full_doc
= not opt_full_doc
307 var opt_fallback
= options
.opt_bool
("no-fallback")
308 if opt_fallback
!= null then fallback
= not opt_fallback
309 var opt_format
= options
.opt_string
("format")
310 if opt_format
!= null then format
= opt_format
315 redef class CmdEntityLink
316 redef fun parser_init
(mentity_name
, options
) do
317 var opt_text
= options
.opt_string
("text")
318 if opt_text
!= null then text
= opt_text
319 var opt_title
= options
.opt_string
("title")
320 if opt_title
!= null then title
= opt_title
326 redef fun parser_init
(mentity_name
, options
) do
327 var opt_format
= options
.opt_string
("format")
328 if opt_format
!= null then format
= opt_format
333 redef class CmdSearch
334 redef fun parser_init
(mentity_name
, options
) do
340 redef class CmdAncestors
341 redef fun parser_init
(mentity_name
, options
) do
342 var opt_parents
= options
.opt_bool
("no-parents")
343 if opt_parents
!= null then parents
= not opt_parents
348 redef class CmdDescendants
349 redef fun parser_init
(mentity_name
, options
) do
350 var opt_children
= options
.opt_bool
("no-children")
351 if opt_children
!= null then children
= not opt_children
356 redef class CmdModelEntities
357 redef fun parser_init
(kind
, options
) do
364 redef fun parser_init
(mentity_name
, options
) do
365 var opt_format
= options
.opt_string
("format")
366 if opt_format
!= null then format
= opt_format
371 redef class CmdInheritanceGraph
372 redef fun parser_init
(mentity_name
, options
) do
373 var opt_pdepth
= options
.opt_int
("pdepth")
374 if opt_pdepth
!= null then pdepth
= opt_pdepth
375 var opt_cdepth
= options
.opt_int
("cdepth")
376 if opt_cdepth
!= null then cdepth
= opt_cdepth
383 redef class CmdCatalogTag
384 redef fun parser_init
(mentity_name
, options
) do
390 redef class CmdCatalogPerson
391 redef fun parser_init
(mentity_name
, options
) do
392 person_name
= mentity_name
401 super HashMap[String, String]
403 # Map String visiblity name to MVisibility object
404 var allowed_visibility
: HashMap[String, MVisibility] is lazy
do
405 var res
= new HashMap[String, MVisibility]
406 res
["public"] = public_visibility
407 res
["protected"] = protected_visibility
408 res
["private"] = private_visibility
412 # Get option value for `key` as String
414 # Return `null` if no option with that `key` or if value is empty.
415 fun opt_string
(key
: String): nullable String do
416 if not has_key
(key
) then return null
417 var value
= self[key
]
418 if value
.is_empty
then return null
422 # Get option value for `key` as Int
424 # Return `null` if no option with that `key` or if value is not an Int.
425 fun opt_int
(key
: String): nullable Int do
426 if not has_key
(key
) then return null
427 var value
= self[key
]
428 if not value
.is_int
then return null
432 # Get option value as bool
434 # Return `true` if the value with that `key` is empty or equals `"true"`.
435 # Return `false` if the value with that `key` equals `"false"`.
436 # Return `null` in any other case.
437 fun opt_bool
(key
: String): nullable Bool do
438 if not has_key
(key
) then return null
439 var value
= self[key
]
440 if value
.is_empty
or value
== "true" then return true
441 if value
== "false" then return false
445 # Get option as a MVisibility
447 # Return `null` if no option with that `key` or if the value is not in
448 # `allowed_visibility`.
449 fun opt_visibility
(key
: String): nullable MVisibility do
450 var value
= opt_string
(key
)
451 if value
== null then return null
452 if not allowed_visibility
.keys
.has
(key
) then return null
453 return allowed_visibility
[value
]
456 # Get option as a MEntity
458 # Lookup first by `MEntity::full_name` then by `MEntity::name`.
459 # Return `null` if the mentity name does not exist or return a conflict.
460 private fun opt_mentity
(model
: Model, key
: String): nullable MEntity do
461 var value
= opt_string
(key
)
462 if value
== null or value
.is_empty
then return null
464 var mentity
= model
.mentity_by_full_name
(value
)
465 if mentity
!= null then return mentity
467 var mentities
= model
.mentities_by_name
(value
)
468 if mentities
.is_empty
or mentities
.length
> 1 then return null
469 return mentities
.first
474 # Read `self` as raw text until `nend` and append it to the `out` buffer.
475 private fun read_until
(out
: FlatBuffer, start
: Int, nend
: Char...): Int do
477 while pos
< length
do
479 var end_reached
= false
486 if end_reached
then break