--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# A parser that create DocCommand from a string
+#
+# Used by both Nitx and the Markdown doc commands.
+module commands_parser
+
+import commands::commands_model
+import commands::commands_graph
+import commands::commands_usage
+import commands::commands_catalog
+
+# Parse string commands to create DocQueries
+class CommandParser
+
+ # ModelView used to retrieve mentities
+ var view: ModelView
+
+ # ModelBuilder used to retrieve AST nodes
+ var modelbuilder: ModelBuilder
+
+ # Catalog used for catalog commands
+ var catalog: nullable Catalog
+
+ # List of allowed command names for this parser
+ var allowed_commands: Array[String] = [
+ "doc", "code", "lin", "uml", "graph", "search",
+ "parents", "ancestors", "children", "descendants",
+ "param", "return", "new", "call", "defs", "list", "random",
+ "catalog", "stats", "tags", "tag", "person", "contrib", "maintain"] is writable
+
+ # Parse `string` as a DocCommand
+ #
+ # Returns `null` if the string cannot be parsed.
+ # See `error` for the error messages produced by both the parser and the commands.
+ fun parse(string: String): nullable DocCommand do
+ var pos = 0
+ var tmp = new FlatBuffer
+ error = null
+
+ # Parse command name
+ pos = string.read_until(tmp, pos, ':')
+ var name = tmp.write_to_string.trim
+
+ # Check allowed commands
+ if name.is_empty then
+ error = new CmdParserError("empty command name", 0)
+ return null
+ end
+ if not allowed_commands.has(name) then
+ error = new CmdParserError("unknown command name", 0)
+ return null
+ end
+
+ # Parse the argument
+ tmp.clear
+ pos = string.read_until(tmp, pos + 1, '|')
+ var arg = tmp.write_to_string.trim
+
+ # Parse command options
+ var opts = new HashMap[String, String]
+ while pos < string.length do
+ # Parse option name
+ tmp.clear
+ pos = string.read_until(tmp, pos + 1, ':', ',')
+ var oname = tmp.write_to_string.trim
+ var oval = ""
+ if oname.is_empty then break
+ # Parse option value
+ if pos < string.length and string[pos] == ':' then
+ tmp.clear
+ pos = string.read_until(tmp, pos + 1, ',')
+ oval = tmp.write_to_string.trim
+ end
+ opts[oname] = oval
+ end
+
+ # Build the command
+ var command = new_command(name)
+ if command == null then
+ error = new CmdParserError("Unknown command name")
+ return null
+ end
+
+ # Initialize command from string options
+ var status = command.parser_init(arg, opts)
+ if not status isa CmdSuccess then error = status
+
+ return command
+ end
+
+ # Init a new DocCommand from its `name`
+ #
+ # You must redefine this method to add new custom commands.
+ fun new_command(name: String): nullable DocCommand do
+ # CmdEntity
+ if name == "doc" then return new CmdComment(view)
+ if name == "code" then return new CmdCode(view, modelbuilder)
+ if name == "lin" then return new CmdLinearization(view)
+ if name == "defs" then return new CmdFeatures(view)
+ if name == "parents" then return new CmdParents(view)
+ if name == "ancestors" then return new CmdAncestors(view)
+ if name == "children" then return new CmdChildren(view)
+ if name == "descendants" then return new CmdDescendants(view)
+ if name == "param" then return new CmdParam(view)
+ if name == "return" then return new CmdReturn(view)
+ if name == "new" then return new CmdNew(view, modelbuilder)
+ if name == "call" then return new CmdCall(view, modelbuilder)
+ # CmdGraph
+ if name == "uml" then return new CmdUML(view)
+ if name == "graph" then return new CmdInheritanceGraph(view)
+ # CmdModel
+ if name == "list" then return new CmdModelEntities(view)
+ if name == "random" then return new CmdRandomEntities(view)
+ # CmdCatalog
+ var catalog = self.catalog
+ if catalog != null then
+ if name == "catalog" then return new CmdCatalogPackages(view, catalog)
+ if name == "stats" then return new CmdCatalogStats(view, catalog)
+ if name == "tags" then return new CmdCatalogTags(view, catalog)
+ if name == "tag" then return new CmdCatalogTag(view, catalog)
+ if name == "person" then return new CmdCatalogPerson(view, catalog)
+ if name == "contrib" then return new CmdCatalogContributing(view, catalog)
+ if name == "maintain" then return new CmdCatalogMaintaining(view, catalog)
+ if name == "search" then return new CmdCatalogSearch(view, catalog)
+ else
+ if name == "search" then return new CmdSearch(view)
+ end
+ return null
+ end
+
+ # Error or warning from last call to `parse`
+ var error: nullable CmdMessage = null
+end
+
+# An error produced by the CmdParser
+class CmdParserError
+ super CmdError
+
+ # Error message
+ var message: String
+
+ # Column related to the error
+ var column: nullable Int
+
+ redef fun to_s do return message
+end
+
+redef class DocCommand
+
+ # Initialize the command from the CommandParser data
+ fun parser_init(arg: String, options: Map[String, String]): CmdMessage do
+ return init_command
+ end
+end
+
+redef class CmdEntity
+ redef fun parser_init(mentity_name, options) do
+ self.mentity_name = mentity_name
+ return super
+ end
+end
+
+redef class CmdList
+ redef fun parser_init(mentity_name, options) do
+ if options.has_key("limit") and options["limit"].is_int then limit = options["limit"].to_i
+ return super
+ end
+end
+
+# Model commands
+
+redef class CmdComment
+ redef fun parser_init(mentity_name, options) do
+ full_doc = not options.has_key("only-synopsis")
+ fallback = not options.has_key("no-fallback")
+ if options.has_key("format") then format = options["format"]
+ return super
+ end
+end
+
+redef class CmdCode
+ redef fun parser_init(mentity_name, options) do
+ if options.has_key("format") then format = options["format"]
+ return super
+ end
+end
+
+redef class CmdSearch
+ redef fun parser_init(mentity_name, options) do
+ query = mentity_name
+ if options.has_key("page") and options["page"].is_int then page = options["page"].to_i
+ return super
+ end
+end
+
+redef class CmdAncestors
+ redef fun parser_init(mentity_name, options) do
+ if options.has_key("parents") and options["parents"] == "false" then parents = false
+ return super
+ end
+end
+
+redef class CmdDescendants
+ redef fun parser_init(mentity_name, options) do
+ if options.has_key("children") and options["children"] == "false" then children = false
+ return super
+ end
+end
+
+redef class CmdModelEntities
+ redef fun parser_init(kind, options) do
+ self.kind = kind
+ return super
+ end
+end
+
+redef class CmdGraph
+ redef fun parser_init(mentity_name, options) do
+ if options.has_key("format") then format = options["format"]
+ return super
+ end
+end
+
+redef class CmdInheritanceGraph
+ redef fun parser_init(mentity_name, options) do
+ if options.has_key("pdepth") and options["pdepth"].is_int then
+ pdepth = options["pdepth"].to_i
+ end
+ if options.has_key("cdepth") and options["cdepth"].is_int then
+ cdepth = options["cdepth"].to_i
+ end
+ return super
+ end
+end
+
+# Catalog commands
+
+redef class CmdCatalogTag
+ redef fun parser_init(mentity_name, options) do
+ tag = mentity_name
+ return super
+ end
+end
+
+redef class CmdCatalogPerson
+ redef fun parser_init(mentity_name, options) do
+ person_name = mentity_name
+ return super
+ end
+end
+
+# Utils
+
+redef class Text
+ # Read `self` as raw text until `nend` and append it to the `out` buffer.
+ private fun read_until(out: FlatBuffer, start: Int, nend: Char...): Int do
+ var pos = start
+ while pos < length do
+ var c = self[pos]
+ var end_reached = false
+ for n in nend do
+ if c == n then
+ end_reached = true
+ break
+ end
+ end
+ if end_reached then break
+ out.add c
+ pos += 1
+ end
+ return pos
+ end
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module test_commands_parser is test
+
+import test_commands
+import doc::commands::tests::test_commands_catalog
+import doc::commands::commands_parser
+
+class TestCommandsParser
+ super TestCommandsCatalog
+ test
+
+ # CmdEntity
+
+ fun test_cmd_parser_comment is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("doc: test_prog::Character")
+ assert cmd isa CmdComment
+ assert parser.error == null
+ assert cmd.mdoc != null
+ end
+
+ # CmdInheritance
+
+ fun test_cmd_parser_parents is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("parents: test_prog::Warrior")
+ assert cmd isa CmdParents
+ assert parser.error == null
+ assert cmd.results.as(not null).length == 1
+ end
+
+ fun test_cmd_parser_ancestors is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("ancestors: test_prog::Warrior")
+ assert cmd isa CmdAncestors
+ assert parser.error == null
+ assert cmd.results.as(not null).length == 2
+ end
+
+ fun test_cmd_parser_ancestors_without_parents is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("ancestors: test_prog::Warrior | parents: false")
+ assert cmd isa CmdAncestors
+ assert parser.error == null
+ assert cmd.results.as(not null).length == 1
+ end
+
+ fun test_cmd_parser_children is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("children: test_prog::Career")
+ assert cmd isa CmdChildren
+ assert parser.error == null
+ assert cmd.results.as(not null).length == 3
+ end
+
+ fun test_cmd_parser_descendants is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("descendants: Object")
+ assert cmd isa CmdDescendants
+ assert parser.error == null
+ assert cmd.results.as(not null).length == 19
+ end
+
+ fun test_cmd_parser_descendants_without_children is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("descendants: Object | children: false")
+ assert cmd isa CmdDescendants
+ assert parser.error == null
+ print cmd.results.as(not null)
+ assert cmd.results.as(not null).length == 7
+ end
+
+ # CmdSearch
+
+ fun test_cmd_parser_search is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("search: Caracter")
+ assert cmd isa CmdSearch
+ assert parser.error == null
+ assert cmd.results != null
+ end
+
+ fun test_cmd_parser_search_limit is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("search: Caracter | limit: 2")
+ assert cmd isa CmdSearch
+ assert parser.error == null
+ assert cmd.results.as(not null).length == 2
+ end
+
+ # CmdFeatures
+
+ fun test_cmd_parser_features is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("defs: test_prog::Character")
+ assert cmd isa CmdFeatures
+ assert parser.error == null
+ assert cmd.results != null
+ end
+
+ fun test_cmd_parser_features_limit is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("defs: test_prog::Character | limit: 2")
+ assert cmd isa CmdFeatures
+ assert parser.error == null
+ assert cmd.results.as(not null).length == 2
+ end
+
+ # CmdLinearization
+
+ fun test_cmd_parser_lin is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("lin: test_prog::Character")
+ assert cmd isa CmdLinearization
+ assert parser.error == null
+ assert cmd.results != null
+ end
+
+ fun test_cmd_parser_lin_limit is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("lin: test_prog::Character | limit: 2")
+ assert cmd isa CmdLinearization
+ assert parser.error == null
+ assert cmd.results.as(not null).length == 2
+ end
+
+ # CmdCode
+
+ fun test_cmd_parser_code is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("code: test_prog::Character")
+ assert cmd isa CmdCode
+ assert parser.error == null
+ assert cmd.node != null
+ end
+
+ # CmdModel
+
+ fun test_cmd_parser_mentities is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("list: modules")
+ assert cmd isa CmdModelEntities
+ assert parser.error == null
+ assert cmd.results != null
+ end
+
+ fun test_cmd_parser_results_mentities is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("random: modules")
+ assert cmd isa CmdRandomEntities
+ assert parser.error == null
+ assert cmd.results != null
+ end
+
+ # CmdGraph
+
+ fun test_cmd_parser_uml is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("uml: test_prog::Career")
+ assert cmd isa CmdUML
+ assert parser.error == null
+ assert cmd.uml != null
+ end
+
+ fun test_cmd_parser_inh_graph is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("graph: test_prog::Career")
+ assert cmd isa CmdInheritanceGraph
+ assert parser.error == null
+ assert cmd.graph != null
+ end
+
+ fun test_cmd_parser_inh_graph_opts is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("graph: test_prog::Career | cdepth: 2, pdepth: 5")
+ assert cmd isa CmdInheritanceGraph
+ assert parser.error == null
+ assert cmd.graph != null
+ assert cmd.cdepth == 2
+ assert cmd.pdepth == 5
+ end
+
+ # CmdUsage
+
+ fun test_cmd_parser_new is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("new: test_prog::Career")
+ assert cmd isa CmdNew
+ assert parser.error == null
+ assert cmd.results != null
+ end
+
+ fun test_cmd_parser_call is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("call: strength_bonus")
+ assert cmd isa CmdCall
+ assert parser.error == null
+ assert cmd.results != null
+ end
+
+ fun test_cmd_parser_return is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("return: test_prog::Career")
+ assert cmd isa CmdReturn
+ assert parser.error == null
+ assert cmd.results != null
+ end
+
+ fun test_cmd_parser_param is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("param: test_prog::Career")
+ assert cmd isa CmdParam
+ assert parser.error == null
+ assert cmd.results != null
+ end
+
+ # CmdCatalog
+
+ fun test_parser_catalog_search is test do
+ var parser = new CommandParser(test_view, test_builder)
+ var cmd = parser.parse("search: Caracter")
+ assert cmd isa CmdSearch
+ assert parser.error == null
+ assert cmd.results != null
+ end
+
+ fun test_cmd_parser_catalog_packages is test do
+ var parser = new CommandParser(test_view, test_builder, test_catalog)
+ var cmd = parser.parse("catalog:")
+ assert cmd isa CmdCatalogPackages
+ assert parser.error == null
+ assert cmd.results != null
+ end
+
+ fun test_cmd_parser_catalog_stats is test do
+ var parser = new CommandParser(test_view, test_builder, test_catalog)
+ var cmd = parser.parse("stats:")
+ assert cmd isa CmdCatalogStats
+ assert parser.error == null
+ assert cmd.stats != null
+ end
+
+ fun test_cmd_parser_catalog_tags is test do
+ var parser = new CommandParser(test_view, test_builder, test_catalog)
+ var cmd = parser.parse("tags:")
+ assert cmd isa CmdCatalogTags
+ assert parser.error == null
+ assert cmd.packages_count_by_tags != null
+ end
+
+ fun test_cmd_parser_catalog_tag is test do
+ var parser = new CommandParser(test_view, test_builder, test_catalog)
+ var cmd = parser.parse("tag: test")
+ assert cmd isa CmdCatalogTag
+ assert parser.error == null
+ assert cmd.tag == "test"
+ assert cmd.results != null
+ end
+
+ fun test_cmd_parser_catalog_person is test do
+ var parser = new CommandParser(test_view, test_builder, test_catalog)
+ var cmd = parser.parse("person: Alexandre Terrasa")
+ assert cmd isa CmdCatalogPerson
+ assert parser.error == null
+ assert cmd.person.as(not null).name == "Alexandre Terrasa"
+ end
+
+ fun test_cmd_parser_catalog_contributing is test do
+ var parser = new CommandParser(test_view, test_builder, test_catalog)
+ var cmd = parser.parse("contrib: Alexandre Terrasa")
+ assert cmd isa CmdCatalogContributing
+ assert parser.error == null
+ assert cmd.person.as(not null).name == "Alexandre Terrasa"
+ assert cmd.results != null
+ end
+
+ fun test_cmd_parser_catalog_maintaining is test do
+ var parser = new CommandParser(test_view, test_builder, test_catalog)
+ var cmd = parser.parse("maintain: Alexandre Terrasa")
+ assert cmd isa CmdCatalogMaintaining
+ assert parser.error == null
+ assert cmd.person.as(not null).name == "Alexandre Terrasa"
+ assert cmd.results != null
+ end
+end