Merge: src/doc/commands: clean commands hierarchy
authorJean Privat <jean@pryen.org>
Tue, 26 Jun 2018 13:30:47 +0000 (09:30 -0400)
committerJean Privat <jean@pryen.org>
Tue, 26 Jun 2018 13:30:47 +0000 (09:30 -0400)
This PR does some cleaning around commands hierarchy to prepare for the merge with new-markdown (#2720).

Pull-Request: #2723

1  2 
src/doc/commands/commands_base.nit
src/doc/commands/commands_catalog.nit
src/doc/commands/commands_http.nit
src/doc/commands/commands_parser.nit
src/doc/templates/tests/test_json_commands.nit
src/doc/templates/tests/test_json_commands.sav/test_cmd_mentities.res
src/doc/term/term.nit

@@@ -24,7 -24,6 +24,6 @@@
  module commands_base
  
  import model::model_index
- import catalog
  
  # Documentation command
  #
@@@ -70,13 -69,6 +69,13 @@@ abstract class DocComman
        #
        # Warnings are generally used to distinguish empty list or mdoc from no data at all.
        fun init_command: CmdMessage do return new CmdSuccess
 +
 +      # Return a new filter for that command execution.
 +      fun cmd_filter: ModelFilter do
 +              var filter = self.filter
 +              if filter == null then return new ModelFilter
 +              return new ModelFilter.from(filter)
 +      end
  end
  
  # Command message
@@@ -16,6 -16,7 +16,7 @@@
  module commands_catalog
  
  import commands_model
+ import catalog
  
  # A DocCommand based on a Catalog
  abstract class CmdCatalog
@@@ -44,11 -45,10 +45,11 @@@ class CmdCatalogSearc
                if query == null then return new ErrorNoQuery
                sorter = null
  
 +              var filter = self.filter
                var index = model.index
  
                # lookup by name prefix
 -              var matches = index.find_by_name_prefix(query).uniq.
 +              var matches = index.find_by_name_prefix(query, filter).uniq.
                        sort(lname_sorter, name_sorter, kind_sorter)
                matches = matches.rerank.sort(vis_sorter, score_sorter)
  
@@@ -56,7 -56,6 +57,7 @@@
                var malus = matches.length
                if catalog.tag2proj.has_key(query) then
                        for mpackage in catalog.tag2proj[query] do
 +                              if filter != null and not filter.accept_mentity(mpackage) then continue
                                matches.add new IndexMatch(mpackage, malus)
                                malus += 1
                        end
@@@ -66,7 -65,7 +67,7 @@@
                # lookup by full_name prefix
                malus = matches.length
                var full_matches = new IndexMatches
 -              for match in index.find_by_full_name_prefix(query).
 +              for match in index.find_by_full_name_prefix(query, filter).
                        sort(lfname_sorter, fname_sorter) do
                        match.score += 1
                        full_matches.add match
@@@ -76,7 -75,7 +77,7 @@@
                # lookup by similarity
                malus = matches.length
                var sim_matches = new IndexMatches
 -              for match in index.find_by_similarity(query).sort(score_sorter, lname_sorter, name_sorter) do
 +              for match in index.find_by_similarity(query, filter).sort(score_sorter, kind_sorter, lname_sorter, name_sorter) do
                        if match.score > query.length then break
                        match.score += 1
                        sim_matches.add match
  # To be more generic, param names should be extracted as variables.
  module commands_http
  
- import commands
- import commands::commands_catalog
+ import commands_catalog
+ import commands_graph
+ import commands_ini
+ import commands_main
+ import commands_usage
  import nitcorn::vararg_routes
  
  redef class DocCommand
        # Init the command from an HTTPRequest
 -      fun http_init(req: HttpRequest): CmdMessage do return init_command
 +      fun http_init(req: HttpRequest): CmdMessage do
 +              var filter = cmd_filter
 +              var opt_vis = req.visibility_arg("min-visibility")
 +              if opt_vis != null then filter.min_visibility = opt_vis
 +              var opt_fictive = req.bool_arg("no-fictive")
 +              if opt_fictive != null then filter.accept_fictive = not opt_fictive
 +              var opt_test = req.bool_arg("no-test")
 +              if opt_test != null then filter.accept_test = not opt_test
 +              var opt_redef = req.bool_arg("no-redef")
 +              if opt_redef != null then filter.accept_redef = not opt_redef
 +              var opt_extern = req.bool_arg("no-extern")
 +              if opt_extern != null then filter.accept_extern = not opt_extern
 +              var opt_example = req.bool_arg("no-example")
 +              if opt_example != null then filter.accept_example = not opt_example
 +              var opt_attr = req.bool_arg("no-attribute")
 +              if opt_attr != null then filter.accept_attribute = not opt_attr
 +              var opt_doc = req.bool_arg("no-empty-doc")
 +              if opt_doc != null then filter.accept_empty_doc = not opt_doc
 +              var opt_inh = req.mentity_arg(model, "inherit")
 +              if opt_inh != null then filter.accept_inherited = opt_inh
 +              var opt_match = req.string_arg("match")
 +              if opt_match != null then filter.accept_full_name = opt_match
 +              self.filter = filter
 +              return init_command
 +      end
  end
  
  redef class CmdEntity
@@@ -63,10 -43,8 +67,10 @@@ en
  
  redef class CmdList
        redef fun http_init(req) do
 -              limit = req.int_arg("l")
 -              page = req.int_arg("p")
 +              var opt_limit = req.int_arg("l")
 +              if opt_limit != null then limit = opt_limit
 +              var opt_page = req.int_arg("p")
 +              if opt_page != null then page = opt_page
                return super
        end
  end
@@@ -104,38 -82,31 +108,38 @@@ en
  
  redef class CmdComment
        redef fun http_init(req) do
 -              full_doc = req.bool_arg("full_doc") or else true
 -              fallback = req.bool_arg("fallback") or else true
 -              format = req.string_arg("format") or else "raw"
 +              var opt_full_doc = req.bool_arg("full_doc")
 +              if opt_full_doc != null then full_doc = opt_full_doc
 +              var opt_fallback = req.bool_arg("fallback")
 +              if opt_fallback != null then fallback = opt_fallback
 +              var opt_format = req.string_arg("format")
 +              if opt_format != null then format = opt_format
                return super
        end
  end
  
  redef class CmdEntityLink
        redef fun http_init(req) do
 -              text = req.string_arg("text")
 -              title = req.string_arg("title")
 +              var opt_text = req.string_arg("text")
 +              if opt_text != null then text = opt_text
 +              var opt_title = req.string_arg("title")
 +              if opt_title != null then title = opt_title
                return super
        end
  end
  
  redef class CmdAncestors
        redef fun http_init(req) do
 -              parents = req.bool_arg("parents") or else true
 +              var opt_parents = req.bool_arg("parents")
 +              if opt_parents != null then parents = opt_parents
                return super
        end
  end
  
  redef class CmdDescendants
        redef fun http_init(req) do
 -              children = req.bool_arg("children") or else true
 +              var opt_children = req.bool_arg("children")
 +              if opt_children != null then children = opt_children
                return super
        end
  end
@@@ -154,16 -125,14 +158,16 @@@ en
  
  redef class CmdModelEntities
        redef fun http_init(req) do
 -              kind = req.string_arg("kind") or else "all"
 +              var opt_kind = req.string_arg("kind")
 +              if opt_kind != null then kind = opt_kind
                return super
        end
  end
  
  redef class CmdCode
        redef fun http_init(req) do
 -              format = req.string_arg("format") or else "raw"
 +              var opt_format = req.string_arg("format")
 +              if opt_format != null then format = opt_format
                return super
        end
  end
@@@ -175,8 -144,7 +179,8 @@@ redef class CmdEntityCod
                if name != null then name = name.from_percent_encoding
                mentity_name = name
  
 -              format = req.string_arg("format") or else "raw"
 +              var opt_format = req.string_arg("format")
 +              if opt_format != null then format = opt_format
                return init_command
        end
  end
  
  redef class CmdGraph
        redef fun http_init(req) do
 -              format = req.string_arg("format") or else "dot"
 +              var opt_format = req.string_arg("format")
 +              if opt_format != null then format = opt_format
                return super
        end
  end
  
  redef class CmdInheritanceGraph
        redef fun http_init(req) do
 -              pdepth = req.int_arg("pdepth")
 -              cdepth = req.int_arg("cdepth")
 +              var opt_pdepth = req.int_arg("pdepth")
 +              if opt_pdepth != null then pdepth = opt_pdepth
 +              var opt_cdepth = req.int_arg("cdepth")
 +              if opt_cdepth != null then cdepth = opt_cdepth
                return super
        end
  end
@@@ -220,44 -185,3 +224,44 @@@ redef class CmdCatalogPerso
                return super
        end
  end
 +
 +# Util
 +
 +redef class HttpRequest
 +
 +      # Map String visiblity name to MVisibility object
 +      var allowed_visibility: HashMap[String, MVisibility] is lazy do
 +              var res = new HashMap[String, MVisibility]
 +              res["public"] = public_visibility
 +              res["protected"] = protected_visibility
 +              res["private"] = private_visibility
 +              return res
 +      end
 +
 +      # Get arg as a MVisibility
 +      #
 +      # Return `null` if no option with that `key` or if the value is not in
 +      # `allowed_visibility`.
 +      fun visibility_arg(key: String): nullable MVisibility do
 +              var value = string_arg(key)
 +              if value == null then return null
 +              if not allowed_visibility.keys.has(key) then return null
 +              return allowed_visibility[value]
 +      end
 +
 +      # Get arg as a MEntity
 +      #
 +      # Lookup first by `MEntity::full_name` then by `MEntity::name`.
 +      # Return `null` if the mentity name does not exist or return a conflict.
 +      private fun mentity_arg(model: Model, key: String): nullable MEntity do
 +              var value = string_arg(key)
 +              if value == null or value.is_empty then return null
 +
 +              var mentity = model.mentity_by_full_name(value)
 +              if mentity != null then return mentity
 +
 +              var mentities = model.mentities_by_name(value)
 +              if mentities.is_empty or mentities.length > 1 then return null
 +              return mentities.first
 +      end
 +end
  # 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
- import commands::commands_ini
- import commands::commands_main
+ import commands_catalog
+ import commands_graph
+ import commands_ini
+ import commands_main
+ import commands_usage
  
  # Parse string commands to create DocQueries
  class CommandParser
@@@ -39,6 -38,9 +38,6 @@@
        # Catalog used for catalog commands
        var catalog: nullable Catalog
  
 -      # Filter to apply on model if any
 -      var filter: nullable ModelFilter
 -
        # List of allowed command names for this parser
        var allowed_commands: Array[String] = [
        "link", "doc", "code", "lin", "uml", "graph", "search",
                end
  
                # Parse command options
 -              var opts = new HashMap[String, String]
 +              var opts = new CmdOptions
                while pos < string.length do
                        # Parse option name
                        tmp.clear
                # Build the command
                var command
                if is_short_link then
 -                      command = new CmdEntityLink(model, filter)
 +                      command = new CmdEntityLink(model)
                else
                        command = new_command(name)
                end
        # You must redefine this method to add new custom commands.
        fun new_command(name: String): nullable DocCommand do
                # CmdEntity
 -              if name == "link" then return new CmdEntityLink(model, filter)
 -              if name == "doc" then return new CmdComment(model, filter)
 -              if name == "code" then return new CmdEntityCode(model, modelbuilder, filter)
 -              if name == "lin" then return new CmdLinearization(model, mainmodule, filter)
 -              if name == "defs" then return new CmdFeatures(model, filter)
 -              if name == "parents" then return new CmdParents(model, mainmodule, filter)
 -              if name == "ancestors" then return new CmdAncestors(model, mainmodule, filter)
 -              if name == "children" then return new CmdChildren(model, mainmodule, filter)
 -              if name == "descendants" then return new CmdDescendants(model, mainmodule, filter)
 -              if name == "param" then return new CmdParam(model, filter)
 -              if name == "return" then return new CmdReturn(model, filter)
 -              if name == "new" then return new CmdNew(model, modelbuilder, filter)
 -              if name == "call" then return new CmdCall(model, modelbuilder, filter)
 +              if name == "link" then return new CmdEntityLink(model)
 +              if name == "doc" then return new CmdComment(model)
 +              if name == "code" then return new CmdEntityCode(model, modelbuilder)
 +              if name == "lin" then return new CmdLinearization(model, mainmodule)
 +              if name == "defs" then return new CmdFeatures(model)
 +              if name == "parents" then return new CmdParents(model, mainmodule)
 +              if name == "ancestors" then return new CmdAncestors(model, mainmodule)
 +              if name == "children" then return new CmdChildren(model, mainmodule)
 +              if name == "descendants" then return new CmdDescendants(model, mainmodule)
 +              if name == "param" then return new CmdParam(model)
 +              if name == "return" then return new CmdReturn(model)
 +              if name == "new" then return new CmdNew(model, modelbuilder)
 +              if name == "call" then return new CmdCall(model, modelbuilder)
                # CmdGraph
 -              if name == "uml" then return new CmdUML(model, mainmodule, filter)
 -              if name == "graph" then return new CmdInheritanceGraph(model, mainmodule, filter)
 +              if name == "uml" then return new CmdUML(model, mainmodule)
 +              if name == "graph" then return new CmdInheritanceGraph(model, mainmodule)
                # CmdModel
 -              if name == "list" then return new CmdModelEntities(model, filter)
 -              if name == "random" then return new CmdRandomEntities(model, filter)
 +              if name == "list" then return new CmdModelEntities(model)
 +              if name == "random" then return new CmdRandomEntities(model)
                # Ini
 -              if name == "ini-desc" then return new CmdIniDescription(model, filter)
 -              if name == "ini-git" then return new CmdIniGitUrl(model, filter)
 -              if name == "ini-issues" then return new CmdIniIssuesUrl(model, filter)
 -              if name == "ini-license" then return new CmdIniLicense(model, filter)
 -              if name == "ini-maintainer" then return new CmdIniMaintainer(model, filter)
 -              if name == "ini-contributors" then return new CmdIniContributors(model, filter)
 -              if name == "license-file" then return new CmdLicenseFile(model, filter)
 -              if name == "license-content" then return new CmdLicenseFileContent(model, filter)
 -              if name == "contrib-file" then return new CmdContribFile(model, filter)
 -              if name == "contrib-content" then return new CmdContribFileContent(model, filter)
 -              if name == "git-clone" then return new CmdIniCloneCommand(model, filter)
 +              if name == "ini-desc" then return new CmdIniDescription(model)
 +              if name == "ini-git" then return new CmdIniGitUrl(model)
 +              if name == "ini-issues" then return new CmdIniIssuesUrl(model)
 +              if name == "ini-license" then return new CmdIniLicense(model)
 +              if name == "ini-maintainer" then return new CmdIniMaintainer(model)
 +              if name == "ini-contributors" then return new CmdIniContributors(model)
 +              if name == "license-file" then return new CmdLicenseFile(model)
 +              if name == "license-content" then return new CmdLicenseFileContent(model)
 +              if name == "contrib-file" then return new CmdContribFile(model)
 +              if name == "contrib-content" then return new CmdContribFileContent(model)
 +              if name == "git-clone" then return new CmdIniCloneCommand(model)
                # CmdMain
 -              if name == "mains" then return new CmdMains(model, filter)
 -              if name == "main-compile" then return new CmdMainCompile(model, filter)
 -              if name == "main-run" then return new CmdManSynopsis(model, filter)
 -              if name == "main-opts" then return new CmdManOptions(model, filter)
 -              if name == "testing" then return new CmdTesting(model, filter)
 +              if name == "mains" then return new CmdMains(model)
 +              if name == "main-compile" then return new CmdMainCompile(model)
 +              if name == "main-run" then return new CmdManSynopsis(model)
 +              if name == "main-opts" then return new CmdManOptions(model)
 +              if name == "testing" then return new CmdTesting(model)
                # CmdCatalog
                var catalog = self.catalog
                if catalog != null then
 -                      if name == "catalog" then return new CmdCatalogPackages(model, catalog, filter)
 -                      if name == "stats" then return new CmdCatalogStats(model, catalog, filter)
 -                      if name == "tags" then return new CmdCatalogTags(model, catalog, filter)
 -                      if name == "tag" then return new CmdCatalogTag(model, catalog, filter)
 -                      if name == "person" then return new CmdCatalogPerson(model, catalog, filter)
 -                      if name == "contrib" then return new CmdCatalogContributing(model, catalog, filter)
 -                      if name == "maintain" then return new CmdCatalogMaintaining(model, catalog, filter)
 -                      if name == "search" then return new CmdCatalogSearch(model, catalog, filter)
 +                      if name == "catalog" then return new CmdCatalogPackages(model, catalog)
 +                      if name == "stats" then return new CmdCatalogStats(model, catalog)
 +                      if name == "tags" then return new CmdCatalogTags(model, catalog)
 +                      if name == "tag" then return new CmdCatalogTag(model, catalog)
 +                      if name == "person" then return new CmdCatalogPerson(model, catalog)
 +                      if name == "contrib" then return new CmdCatalogContributing(model, catalog)
 +                      if name == "maintain" then return new CmdCatalogMaintaining(model, catalog)
 +                      if name == "search" then return new CmdCatalogSearch(model, catalog)
                else
 -                      if name == "search" then return new CmdSearch(model, filter)
 +                      if name == "search" then return new CmdSearch(model)
                end
                return null
        end
@@@ -255,29 -257,7 +254,29 @@@ en
  redef class DocCommand
  
        # Initialize the command from the CommandParser data
 -      fun parser_init(arg: String, options: Map[String, String]): CmdMessage do
 +      fun parser_init(arg: String, options: CmdOptions): CmdMessage do
 +              var filter = cmd_filter
 +              var opt_vis = options.opt_visibility("min-visibility")
 +              if opt_vis != null then filter.min_visibility = opt_vis
 +              var opt_fictive = options.opt_bool("no-fictive")
 +              if opt_fictive != null then filter.accept_fictive = not opt_fictive
 +              var opt_test = options.opt_bool("no-test")
 +              if opt_test != null then filter.accept_test = not opt_test
 +              var opt_redef = options.opt_bool("no-redef")
 +              if opt_redef != null then filter.accept_redef = not opt_redef
 +              var opt_extern = options.opt_bool("no-extern")
 +              if opt_extern != null then filter.accept_extern = not opt_extern
 +              var opt_example = options.opt_bool("no-example")
 +              if opt_example != null then filter.accept_example = not opt_example
 +              var opt_attr = options.opt_bool("no-attribute")
 +              if opt_attr != null then filter.accept_attribute = not opt_attr
 +              var opt_doc = options.opt_bool("no-empty-doc")
 +              if opt_doc != null then filter.accept_empty_doc = not opt_doc
 +              var opt_inh = options.opt_mentity(model, "inherit")
 +              if opt_inh != null then filter.accept_inherited = opt_inh
 +              var opt_match = options.opt_string("match")
 +              if opt_match != null then filter.accept_full_name = opt_match
 +              self.filter = filter
                return init_command
        end
  end
@@@ -291,10 -271,7 +290,10 @@@ en
  
  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
 +              var opt_page = options.opt_int("page")
 +              if opt_page != null then page = opt_page
 +              var opt_limit = options.opt_int("limit")
 +              if opt_limit != null then limit = opt_limit
                return super
        end
  end
  
  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"]
 +              var opt_full_doc = options.opt_bool("only-synopsis")
 +              if opt_full_doc != null then full_doc = not opt_full_doc
 +              var opt_fallback = options.opt_bool("no-fallback")
 +              if opt_fallback != null then fallback = not opt_fallback
 +              var opt_format = options.opt_string("format")
 +              if opt_format != null then format = opt_format
                return super
        end
  end
  
  redef class CmdEntityLink
        redef fun parser_init(mentity_name, options) do
 -              if options.has_key("text") then text = options["text"]
 -              if options.has_key("title") then title = options["title"]
 +              var opt_text = options.opt_string("text")
 +              if opt_text != null then text = opt_text
 +              var opt_title = options.opt_string("title")
 +              if opt_title != null then title = opt_title
                return super
        end
  end
  
  redef class CmdCode
        redef fun parser_init(mentity_name, options) do
 -              if options.has_key("format") then format = options["format"]
 +              var opt_format = options.opt_string("format")
 +              if opt_format != null then format = opt_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
 +              var opt_parents = options.opt_bool("no-parents")
 +              if opt_parents != null then parents = not opt_parents
                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
 +              var opt_children = options.opt_bool("no-children")
 +              if opt_children != null then children = not opt_children
                return super
        end
  end
@@@ -363,18 -333,19 +362,18 @@@ en
  
  redef class CmdGraph
        redef fun parser_init(mentity_name, options) do
 -              if options.has_key("format") then format = options["format"]
 +              var opt_format = options.opt_string("format")
 +              if opt_format != null then format = opt_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
 +              var opt_pdepth = options.opt_int("pdepth")
 +              if opt_pdepth != null then pdepth = opt_pdepth
 +              var opt_cdepth = options.opt_int("cdepth")
 +              if opt_cdepth != null then cdepth = opt_cdepth
                return super
        end
  end
@@@ -397,80 -368,6 +396,80 @@@ en
  
  # Utils
  
 +# Commands options
 +class CmdOptions
 +      super HashMap[String,  String]
 +
 +      # Map String visiblity name to MVisibility object
 +      var allowed_visibility: HashMap[String, MVisibility] is lazy do
 +              var res = new HashMap[String, MVisibility]
 +              res["public"] = public_visibility
 +              res["protected"] = protected_visibility
 +              res["private"] = private_visibility
 +              return res
 +      end
 +
 +      # Get option value for `key` as String
 +      #
 +      # Return `null` if no option with that `key` or if value is empty.
 +      fun opt_string(key: String): nullable String do
 +              if not has_key(key) then return null
 +              var value = self[key]
 +              if value.is_empty then return null
 +              return value
 +      end
 +
 +      # Get option value for `key` as Int
 +      #
 +      # Return `null` if no option with that `key` or if value is not an Int.
 +      fun opt_int(key: String): nullable Int do
 +              if not has_key(key) then return null
 +              var value = self[key]
 +              if not value.is_int then return null
 +              return value.to_i
 +      end
 +
 +      # Get option value as bool
 +      #
 +      # Return `true` if the value with that `key` is empty or equals `"true"`.
 +      # Return `false` if the value with that `key` equals `"false"`.
 +      # Return `null` in any other case.
 +      fun opt_bool(key: String): nullable Bool do
 +              if not has_key(key) then return null
 +              var value = self[key]
 +              if value.is_empty or value == "true" then return true
 +              if value == "false" then return false
 +              return null
 +      end
 +
 +      # Get option as a MVisibility
 +      #
 +      # Return `null` if no option with that `key` or if the value is not in
 +      # `allowed_visibility`.
 +      fun opt_visibility(key: String): nullable MVisibility do
 +              var value = opt_string(key)
 +              if value == null then return null
 +              if not allowed_visibility.keys.has(key) then return null
 +              return allowed_visibility[value]
 +      end
 +
 +      # Get option as a MEntity
 +      #
 +      # Lookup first by `MEntity::full_name` then by `MEntity::name`.
 +      # Return `null` if the mentity name does not exist or return a conflict.
 +      private fun opt_mentity(model: Model, key: String): nullable MEntity do
 +              var value = opt_string(key)
 +              if value == null or value.is_empty then return null
 +
 +              var mentity = model.mentity_by_full_name(value)
 +              if mentity != null then return mentity
 +
 +              var mentities = model.mentities_by_name(value)
 +              if mentities.is_empty or mentities.length > 1 then return null
 +              return mentities.first
 +      end
 +end
 +
  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
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
- module test_commands_json is test
+ module test_json_commands is test
  
  import test_commands
  intrude import doc::commands::commands_main
- import doc::commands::commands_json
+ import json_commands
  
  class TestCommandsJson
        super TestCommands
        # CmdEntity
  
        fun test_cmd_entity is test do
 -              var cmd = new CmdEntity(test_model, test_filter, mentity_name = "test_prog::Character")
 +              var cmd = new CmdEntity(test_model, mentity_name = "test_prog::Character")
                cmd.init_command
                print_json cmd.to_json
        end
  
        fun test_cmd_comment is test do
 -              var cmd = new CmdComment(test_model, test_filter, mentity_name = "test_prog::Character")
 +              var cmd = new CmdComment(test_model, mentity_name = "test_prog::Character")
                cmd.init_command
                print_json cmd.to_json
        end
        # CmdInheritance
  
        fun test_cmd_parents is test do
 -              var cmd = new CmdParents(test_model, test_main, test_filter, mentity_name = "test_prog::Warrior")
 +              var cmd = new CmdParents(test_model, test_main, mentity_name = "test_prog::Warrior")
                cmd.init_command
                print_json cmd.to_json
        end
  
        fun test_cmd_ancestors is test do
 -              var cmd = new CmdAncestors(test_model, test_main, test_filter, mentity_name = "test_prog::Warrior", parents = false)
 +              var cmd = new CmdAncestors(test_model, test_main, mentity_name = "test_prog::Warrior", parents = false)
                cmd.init_command
                print_json cmd.to_json
        end
  
        fun test_cmd_children is test do
 -              var cmd = new CmdChildren(test_model, test_main, test_filter, mentity_name = "test_prog::Career")
 +              var cmd = new CmdChildren(test_model, test_main, mentity_name = "test_prog::Career")
                cmd.init_command
                print_json cmd.to_json
        end
  
        fun test_cmd_descendants is test do
 -              var cmd = new CmdDescendants(test_model, test_main, test_filter, mentity_name = "test_prog::Career")
 +              var cmd = new CmdDescendants(test_model, test_main, mentity_name = "test_prog::Career")
                cmd.init_command
                print_json cmd.to_json
        end
@@@ -76,7 -76,7 +76,7 @@@
        # CmdSearch
  
        fun test_cmd_search is test do
 -              var cmd = new CmdSearch(test_model, test_filter, query = "Carer", limit = 10)
 +              var cmd = new CmdSearch(test_model, query = "Carer", limit = 10)
                cmd.init_command
                print_json cmd.to_json
        end
@@@ -84,7 -84,7 +84,7 @@@
        # CmdFeatures
  
        fun test_cmd_features is test do
 -              var cmd = new CmdFeatures(test_model, test_filter, mentity_name = "test_prog::Career")
 +              var cmd = new CmdFeatures(test_model, mentity_name = "test_prog::Career")
                cmd.init_command
                print_json cmd.to_json
        end
@@@ -92,7 -92,7 +92,7 @@@
        # CmdLinearization
  
        fun test_cmd_lin is test do
 -              var cmd = new CmdLinearization(test_model, test_main, test_filter, mentity_name = "init")
 +              var cmd = new CmdLinearization(test_model, test_main, mentity_name = "init")
                cmd.init_command
                print_json cmd.to_json
        end
        # CmdModel
  
        fun test_cmd_mentities is test do
 -              var cmd = new CmdModelEntities(test_model, test_filter, kind = "modules")
 +              var cmd = new CmdModelEntities(test_model, kind = "modules")
                cmd.init_command
                print_json cmd.to_json
        end
        # CmdUsage
  
        fun test_cmd_new is test do
 -              var cmd = new CmdNew(test_model, test_builder, test_filter, mentity_name = "test_prog::Character")
 +              var cmd = new CmdNew(test_model, test_builder, mentity_name = "test_prog::Character")
                cmd.init_command
                print_json cmd.to_json
        end
  
        fun test_cmd_call is test do
 -              var cmd = new CmdCall(test_model, test_builder, test_filter, mentity_name = "strength_bonus")
 +              var cmd = new CmdCall(test_model, test_builder, mentity_name = "strength_bonus")
                cmd.init_command
                print_json cmd.to_json
        end
  
        fun test_cmd_return is test do
 -              var cmd = new CmdReturn(test_model, test_filter, mentity_name = "test_prog::Character")
 +              var cmd = new CmdReturn(test_model, mentity_name = "test_prog::Character")
                cmd.init_command
                print_json cmd.to_json
        end
  
        fun test_cmd_param is test do
 -              var cmd = new CmdParam(test_model, test_filter, mentity_name = "test_prog::Character")
 +              var cmd = new CmdParam(test_model, mentity_name = "test_prog::Character")
                cmd.init_command
                print_json cmd.to_json
        end
                "html_synopsis": "<span class=\"synopsys nitdoc\">A worlg RPG abstraction.</span>",
                "modifiers": ["module"]
        }, {
 +              "name": "test_game",
 +              "namespace": [{
 +                      "name": "test_prog",
 +                      "synopsis": "Test program for model tools."
 +              }, "::", {
 +                      "name": "test_game"
 +              }],
 +              "class_name": "MModule",
 +              "full_name": "test_prog::test_game",
 +              "visibility": "public",
 +              "modifiers": ["module"]
 +      }, {
                "name": "test_prog",
                "synopsis": "A test program with a fake model to check model tools.",
                "namespace": [{
diff --combined src/doc/term/term.nit
@@@ -15,7 -15,8 +15,8 @@@
  module term
  
  import commands::commands_parser
- import templates::templates_term
+ import templates::term_model
+ import templates::md_commands
  
  redef class CommandParser
  
@@@ -25,8 -26,8 +26,8 @@@
  
                # Translate links to doc commands
                if cmd isa CmdEntityLink then
 -                      cmd = new CmdComment(model, filter, mentity_name = query)
 -                      var opts = new HashMap[String, String]
 +                      cmd = new CmdComment(model, mentity_name = query)
 +                      var opts = new CmdOptions
                        var status = cmd.parser_init(query, opts)
                        if not status isa CmdSuccess then error = status
                end
@@@ -221,13 -222,12 +222,12 @@@ redef class CmdEntityCod
                else
                        print title
                end
-               if no_color == null or not no_color then
+               var node = self.node
+               if (no_color == null or not no_color) and node != null then
                        var ansi = render_code(node)
-                       if ansi != null then
-                               print "~~~"
-                               print ansi.write_to_string
-                               print "~~~"
-                       end
+                       print "~~~"
+                       print ansi.write_to_string
+                       print "~~~"
                else
                        printn mentity.cs_source_code
                end