doc/api: migrate api to new doc-commands
authorAlexandre Terrasa <alexandre@moz-code.org>
Tue, 28 Nov 2017 18:30:05 +0000 (13:30 -0500)
committerAlexandre Terrasa <alexandre@moz-code.org>
Mon, 18 Dec 2017 18:59:12 +0000 (13:59 -0500)
Signed-off-by: Alexandre Terrasa <alexandre@moz-code.org>

src/doc/api/api.nit
src/doc/api/api_base.nit
src/doc/api/api_catalog.nit [deleted file]
src/doc/api/api_docdown.nit
src/doc/api/api_graph.nit [deleted file]
src/doc/api/api_light.nit
src/doc/api/api_model.nit

index f91958e..2835985 100644 (file)
@@ -17,8 +17,6 @@ module api
 
 import api_auth
 import api_model
-import api_catalog
-import api_graph
 import api_docdown
 import api_metrics
 import api_feedback
index d683dab..e14cf3b 100644 (file)
 # Base classes used by `nitweb`.
 module api_base
 
-import model::model_views
-import model::model_json
-import doc_down
 import popcorn
 import popcorn::pop_config
 import popcorn::pop_repos
 import popcorn::pop_json
 
+import commands::commands_http
+import commands::commands_json
+import commands::commands_html
+
 # Nitweb config file.
 class NitwebConfig
        super AppConfig
@@ -38,8 +39,43 @@ class NitwebConfig
        # Modelbuilder used to access sources.
        var modelbuilder: ModelBuilder
 
-       # ModelView used to access model.
+       # The JSON API does not filter anything by default.
+       #
+       # So we can cache the model view.
        var view: ModelView
+
+       # Catalog to pass to handlers.
+       var catalog: Catalog is noinit
+
+       # Build the catalog
+       #
+       # This method should be called at nitweb startup.
+       # TODO move to nitweb
+       fun build_catalog do
+               var catalog = new Catalog(modelbuilder)
+               # Compute the poset
+               for p in view.mpackages do
+                       var g = p.root
+                       assert g != null
+                       modelbuilder.scan_group(g)
+
+                       catalog.deps.add_node(p)
+                       for gg in p.mgroups do for m in gg.mmodules do
+                               for im in m.in_importation.direct_greaters do
+                                       var ip = im.mpackage
+                                       if ip == null or ip == p then continue
+                                       catalog.deps.add_edge(p, ip)
+                               end
+                       end
+               end
+               # Build the catalog
+               for mpackage in view.mpackages do
+                       catalog.package_page(mpackage)
+                       catalog.git_info(mpackage)
+                       catalog.mpackage_stats(mpackage)
+               end
+               self.catalog = catalog
+       end
 end
 
 # Specific handler for the nitweb API.
@@ -143,27 +179,6 @@ redef class HttpResponse
        fun api_json(req: HttpRequest, serializable: nullable Serializable, status: nullable Int, plain, pretty: nullable Bool) do
                json(serializable, status, plain, req.bool_arg("pretty"))
        end
-
-       # Return the full version of `serializable` as a json string
-       #
-       # Uses `req` to define serializable options.
-       fun api_full_json(req: HttpRequest, serializable: nullable MEntity, status: nullable Int, plain, pretty: nullable Bool) do
-               if serializable == null then
-                       json(null, status)
-               else
-                       raw_json(serializable.serialize_to_full_json(
-                               plain or else true, req.bool_arg("pretty") or else false), status)
-               end
-       end
-       # Write data as JSON and set the right content type header.
-       fun raw_json(json: nullable String, status: nullable Int) do
-               header["Content-Type"] = media_types["json"].as(not null)
-               if json == null then
-                       send(null, status)
-               else
-                       send(json, status)
-               end
-       end
 end
 
 # An error returned by the API.
@@ -179,53 +194,6 @@ class APIError
        var message: String
 end
 
-# Fullname representation that can be used to build decorated links
-#
-# * MPackage: `mpackage_name`
-# * MGroup: `(mpackage_name::)mgroup_name`
-class Namespace
-       super Array[nullable NSEntity]
-       super NSEntity
-       serialize
-
-       redef fun to_s do return self.join("")
-       redef fun serialize_to(v) do to_a.serialize_to(v)
-end
-
-# Something that goes in a Namespace
-#
-# Can be either:
-# * a `NSToken` for tokens like `::`, `>` and `$`
-# * a `MSRef` for references to mentities
-interface NSEntity
-       super Serializable
-end
-
-# A reference to a MEntity that can be rendered as a link.
-#
-# We do not reuse `MEntityRef` ref since NSRef can be found in a ref and create
-# an infinite loop.
-class NSRef
-       super NSEntity
-       serialize
-
-       # The mentity to link to/
-       var mentity: MEntity
-
-       redef fun core_serialize_to(v) do
-               v.serialize_attribute("web_url", mentity.web_url)
-               v.serialize_attribute("api_url", mentity.api_url)
-               v.serialize_attribute("name", mentity.name)
-       end
-end
-
-# A namespace token representation
-#
-# Used for namespace tokens like `::`, `>` and `$`
-redef class String
-       super NSEntity
-end
-
 redef class MEntity
 
        # URL to `self` within the web interface.
@@ -236,166 +204,51 @@ redef class MEntity
 
        redef fun core_serialize_to(v) do
                super
-               v.serialize_attribute("namespace", namespace)
                v.serialize_attribute("web_url", web_url)
                v.serialize_attribute("api_url", api_url)
        end
-
-       # Return `self.full_name` as an object that can be serialized to json.
-       fun namespace: nullable Namespace do return null
-
-       # Return a new NSRef to `self`.
-       fun to_ns_ref: NSRef do return new NSRef(self)
 end
 
 redef class MEntityRef
        redef fun core_serialize_to(v) do
                super
-               v.serialize_attribute("namespace", mentity.namespace)
                v.serialize_attribute("web_url", mentity.web_url)
                v.serialize_attribute("api_url", mentity.api_url)
-               v.serialize_attribute("name", mentity.name)
-               v.serialize_attribute("mdoc", mentity.mdoc_or_fallback)
-               v.serialize_attribute("visibility", mentity.visibility.to_s)
-               v.serialize_attribute("modifiers", mentity.collect_modifiers)
-               v.serialize_attribute("class_name", mentity.class_name)
-               var mentity = self.mentity
-               if mentity isa MMethod then
-                       v.serialize_attribute("msignature", mentity.intro.msignature)
-               else if mentity isa MMethodDef then
-                       v.serialize_attribute("msignature", mentity.msignature)
-               else if mentity isa MVirtualTypeProp then
-                       v.serialize_attribute("bound", to_mentity_ref(mentity.intro.bound))
-               else if mentity isa MVirtualTypeDef then
-                       v.serialize_attribute("bound", to_mentity_ref(mentity.bound))
-               end
-               v.serialize_attribute("location", mentity.location)
-       end
-end
-
-redef class MDoc
-
-       # Add doc down processing
-       redef fun core_serialize_to(v) do
-               v.serialize_attribute("html_synopsis", html_synopsis.write_to_string)
-       end
-end
-
-redef class MPackage
-       redef fun namespace do return new Namespace.from([to_ns_ref])
-end
-
-redef class MGroup
-       redef fun namespace do
-               var p = parent
-               if p == null then
-                       return new Namespace.from([to_ns_ref, ">": nullable NSEntity])
-               end
-               return new Namespace.from([p.namespace, to_ns_ref, ">": nullable NSEntity])
-       end
-end
-
-redef class MModule
-       redef fun namespace do
-               var mgroup = self.mgroup
-               if mgroup == null then
-                       return new Namespace.from([to_ns_ref])
-               end
-               return new Namespace.from([mgroup.mpackage.to_ns_ref, "::", to_ns_ref: nullable NSEntity])
-       end
-
-       private fun ns_for(visibility: MVisibility): nullable Namespace do
-               if visibility <= private_visibility then return namespace
-               var mgroup = self.mgroup
-               if mgroup == null then return namespace
-               return mgroup.mpackage.namespace
-       end
-end
-
-redef class MClass
-       redef fun namespace do
-               return new Namespace.from([intro_mmodule.ns_for(visibility), "::", to_ns_ref: nullable NSEntity])
        end
 end
 
 redef class MClassDef
-       redef fun namespace do
-               if is_intro then
-                       return new Namespace.from([mmodule.ns_for(mclass.visibility), "$", to_ns_ref: nullable NSEntity])
-               else if mclass.intro_mmodule.mpackage != mmodule.mpackage then
-                       return new Namespace.from([mmodule.namespace, "$", mclass.namespace: nullable NSEntity])
-               else if mclass.visibility > private_visibility then
-                       return new Namespace.from([mmodule.namespace, "$", mclass.to_ns_ref: nullable NSEntity])
-               end
-               return new Namespace.from([mmodule.full_name, "$::", mclass.intro_mmodule.to_ns_ref: nullable NSEntity])
-       end
-
        redef fun web_url do return "{mclass.web_url}/lin#{full_name}"
 end
 
-redef class MProperty
-       redef fun namespace do
-               if intro_mclassdef.is_intro then
-                       return new Namespace.from([intro_mclassdef.mmodule.ns_for(visibility), "::", intro_mclassdef.mclass.to_ns_ref, "::", to_ns_ref: nullable NSEntity])
-               else
-                       return new Namespace.from([intro_mclassdef.mmodule.namespace, "::", intro_mclassdef.mclass.to_ns_ref, "::", to_ns_ref: nullable NSEntity])
-               end
-       end
+redef class MPropDef
+       redef fun web_url do return "{mproperty.web_url}/lin#{full_name}"
 end
 
-redef class MPropDef
-       redef fun namespace do
-               var res = new Namespace
-               res.add mclassdef.namespace
-               res.add "$"
-
-               if mclassdef.mclass == mproperty.intro_mclassdef.mclass then
-                       res.add to_ns_ref
-               else
-                       if mclassdef.mmodule.mpackage != mproperty.intro_mclassdef.mmodule.mpackage then
-                               res.add mproperty.intro_mclassdef.mmodule.ns_for(mproperty.visibility)
-                               res.add "::"
-                       else if mproperty.visibility <= private_visibility then
-                               if mclassdef.mmodule.namespace_for(mclassdef.mclass.visibility) != mproperty.intro_mclassdef.mmodule.mpackage then
-                                       res.add "::"
-                                       res.add mproperty.intro_mclassdef.mmodule.to_ns_ref
-                                       res.add "::"
-                               end
-                       end
-                       if mclassdef.mclass != mproperty.intro_mclassdef.mclass then
-                               res.add mproperty.intro_mclassdef.to_ns_ref
-                               res.add "::"
-                       end
-                       res.add to_ns_ref
-               end
-               return res
+redef class MType
+       redef fun core_serialize_to(v) do
+               super
+               v.serialize_attribute("web_url", web_url)
+               v.serialize_attribute("api_url", api_url)
        end
-
-       redef fun web_url do return "{mproperty.web_url}/lin#{full_name}"
 end
 
 redef class MClassType
        redef var web_url = mclass.web_url is lazy
+       redef var api_url = mclass.api_url is lazy
 end
 
 redef class MNullableType
        redef var web_url = mtype.web_url is lazy
+       redef var api_url = mtype.api_url is lazy
 end
 
 redef class MParameterType
        redef var web_url = mclass.web_url is lazy
+       redef var api_url = mclass.api_url is lazy
 end
 
 redef class MVirtualType
        redef var web_url = mproperty.web_url is lazy
-end
-
-redef class POSetElement[E]
-       super Serializable
-
-       redef fun core_serialize_to(v) do
-               assert self isa POSetElement[MEntity]
-               v.serialize_attribute("direct_greaters", to_mentity_refs(direct_greaters))
-               v.serialize_attribute("direct_smallers", to_mentity_refs(direct_smallers))
-       end
+       redef var api_url = mproperty.api_url is lazy
 end
diff --git a/src/doc/api/api_catalog.nit b/src/doc/api/api_catalog.nit
deleted file mode 100644 (file)
index 61b0d0d..0000000
+++ /dev/null
@@ -1,358 +0,0 @@
-# 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 api_catalog
-
-import api_model
-import catalog::catalog_json
-
-redef class NitwebConfig
-
-       # Catalog to pass to handlers.
-       var catalog: Catalog is noinit
-
-       # Build the catalog
-       #
-       # This method should be called at nitweb startup.
-       fun build_catalog do
-               self.catalog = new Catalog(modelbuilder)
-               self.catalog.build_catalog(model.mpackages)
-       end
-end
-
-redef class APIRouter
-       redef init do
-               super
-               use("/catalog/packages/", new APICatalogPackages(config))
-               use("/catalog/stats", new APICatalogStats(config))
-
-               use("/catalog/tags", new APICatalogTags(config))
-               use("/catalog/tag/:tid", new APICatalogTag(config))
-
-               use("/catalog/person/:pid", new APICatalogPerson(config))
-               use("/catalog/person/:pid/maintaining", new APICatalogMaintaining(config))
-               use("/catalog/person/:pid/contributing", new APICatalogContributing(config))
-       end
-end
-
-abstract class APICatalogHandler
-       super APIHandler
-
-       # Sorter used to sort packages
-       #
-       # Sorting is based on mpackage score.
-       var mpackages_sorter = new CatalogScoreSorter(config.catalog) is lazy
-end
-
-# Get all the packages from the catalog using pagination
-#
-# `GET /packages?p=1&n=10`: get the list of catalog by page
-class APICatalogPackages
-       super APICatalogHandler
-
-       redef fun get(req, res) do
-               var page = req.int_arg("p")
-               var limit = req.int_arg("n")
-               var mpackages = config.catalog.mpackages.values.to_a
-               mpackages_sorter.sort(mpackages)
-               var response = new JsonArray.from(mpackages)
-               res.api_json(req, paginate(response, response.length, page, limit))
-       end
-end
-
-# Get the catalog statistics
-#
-# `GET /stats`: return the catalog statistics
-class APICatalogStats
-       super APICatalogHandler
-
-       redef fun get(req, res) do
-               res.api_json(req, config.catalog.catalog_stats)
-       end
-end
-
-# Get all the tags from the catalog
-#
-# `GET /tags`: the list of tags associated with their number of packages
-class APICatalogTags
-       super APICatalogHandler
-
-       # Sorter to sort tags alphabetically
-       var tags_sorter = new CatalogTagsSorter
-
-       redef fun get(req, res) do
-               var obj = new JsonObject
-
-               var tags = config.catalog.tag2proj.keys.to_a
-               tags_sorter.sort(tags)
-
-               for tag in tags do
-                       if not config.catalog.tag2proj.has_key(tag) then continue
-                       obj[tag] = config.catalog.tag2proj[tag].length
-               end
-               res.api_json(req, obj)
-       end
-end
-
-# Get the packages related to a tag
-#
-# `GET /tag/:tid?p=1&n=10`: return a paginated list of packages
-class APICatalogTag
-       super APICatalogHandler
-
-       redef fun get(req, res) do
-               var page = req.int_arg("p")
-               var limit = req.int_arg("n")
-               var id = req.param("tid")
-               if id == null then
-                       res.api_error(400, "Missing tag")
-                       return
-               end
-               id = id.from_percent_encoding
-               if not config.catalog.tag2proj.has_key(id) then
-                       res.api_error(404, "Tag not found")
-                       return
-               end
-               var obj = new JsonObject
-               obj["tag"] = id
-               var mpackages = config.catalog.tag2proj[id]
-               mpackages_sorter.sort(mpackages)
-               var response = new JsonArray.from(mpackages)
-               obj["packages"] = paginate(response, response.length, page, limit)
-               res.api_json(req, obj)
-       end
-end
-
-# Get a person existing in the catalog
-#
-# `GET /person/:pid`: get the person with `pid`
-class APICatalogPerson
-       super APICatalogHandler
-
-       # Get the person with `:pid` or throw a 404 error
-       fun get_person(req: HttpRequest, res: HttpResponse): nullable Person do
-               var id = req.param("pid")
-               if id == null then
-                       res.api_error(400, "Missing package full_name")
-                       return null
-               end
-               id = id.from_percent_encoding
-               if not config.catalog.name2person.has_key(id) then
-                       res.api_error(404, "Person not found")
-                       return null
-               end
-               return config.catalog.name2person[id]
-       end
-
-       redef fun get(req, res) do
-               var person = get_person(req, res)
-               if person == null then return
-               res.api_json(req, person)
-       end
-end
-
-# Get the list of mpackages maintained by a person
-#
-# `GET /person/:pid/maintaining?p=1&n=10`: return a paginated list of packages
-class APICatalogMaintaining
-       super APICatalogPerson
-
-       redef fun get(req, res) do
-               var person = get_person(req, res)
-               if person == null then return
-
-               var page = req.int_arg("p")
-               var limit = req.int_arg("n")
-               var array = new Array[MPackage]
-               if config.catalog.maint2proj.has_key(person) then
-                       array = config.catalog.maint2proj[person].to_a
-               end
-               mpackages_sorter.sort(array)
-               var response = new JsonArray.from(array)
-               res.api_json(req, paginate(response, response.length, page, limit))
-       end
-end
-
-# Get the list of mpackages contributed by a person
-#
-# `GET /person/:pid/contributing?p=1&n=10`: return a paginated list of packages
-class APICatalogContributing
-       super APICatalogPerson
-
-       redef fun get(req, res) do
-               var person = get_person(req, res)
-               if person == null then return
-
-               var page = req.int_arg("p")
-               var limit = req.int_arg("n")
-               var array = new Array[MPackage]
-               if config.catalog.contrib2proj.has_key(person) then
-                       array = config.catalog.contrib2proj[person].to_a
-               end
-               mpackages_sorter.sort(array)
-               var response = new JsonArray.from(array)
-               res.api_json(req, paginate(response, response.length, page, limit))
-       end
-end
-
-redef class APIEntity
-       redef fun get(req, res) do
-               var mentity = mentity_from_uri(req, res)
-               if mentity == null then return
-
-               # Special case for packages (catalog view)
-               if mentity isa MPackage then
-                       res.raw_json mentity.to_full_catalog_json(config.catalog, plain = true, pretty = req.bool_arg("pretty"))
-               else
-                       res.api_full_json(req, mentity)
-               end
-       end
-end
-
-redef class APISearch
-       super APICatalogHandler
-
-       redef fun search(query, limit) do
-               var index = config.view.index
-
-               # lookup by name prefix
-               var matches = index.find_by_name_prefix(query).uniq.
-                       sort(lname_sorter, name_sorter, kind_sorter)
-               matches = matches.rerank.sort(vis_sorter, score_sorter)
-
-               # lookup by tags
-               var malus = matches.length
-               if config.catalog.tag2proj.has_key(query) then
-                       for mpackage in config.catalog.tag2proj[query] do
-                               matches.add new IndexMatch(mpackage, malus)
-                               malus += 1
-                       end
-                       matches = matches.uniq.rerank.sort(vis_sorter, score_sorter)
-               end
-
-               # lookup by full_name prefix
-               malus = matches.length
-               var full_matches = new IndexMatches
-               for match in index.find_by_full_name_prefix(query).
-                       sort(lfname_sorter, fname_sorter) do
-                       match.score += 1
-                       full_matches.add match
-               end
-               matches = matches.uniq
-
-               # 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
-                       if match.score > query.length then break
-                       match.score += 1
-                       sim_matches.add match
-               end
-               matches.add_all sim_matches
-               matches = matches.uniq
-               return matches.rerank.sort(vis_sorter, score_sorter).mentities
-       end
-
-       private var score_sorter = new ScoreComparator
-       private var vis_sorter = new VisibilityComparator
-       private var name_sorter = new NameComparator
-       private var lname_sorter = new NameLengthComparator
-       private var fname_sorter = new FullNameComparator
-       private var lfname_sorter = new FullNameLengthComparator
-       private var kind_sorter = new MEntityComparator
-end
-
-redef class Catalog
-
-       # Build the catalog from `mpackages`
-       fun build_catalog(mpackages: Array[MPackage]) do
-               # Compute the poset
-               for p in mpackages do
-                       var g = p.root
-                       assert g != null
-                       modelbuilder.scan_group(g)
-
-                       deps.add_node(p)
-                       for gg in p.mgroups do for m in gg.mmodules do
-                               for im in m.in_importation.direct_greaters do
-                                       var ip = im.mpackage
-                                       if ip == null or ip == p then continue
-                                       deps.add_edge(p, ip)
-                               end
-                       end
-               end
-               # Build the catalog
-               for mpackage in mpackages do
-                       package_page(mpackage)
-                       git_info(mpackage)
-                       mpackage_stats(mpackage)
-               end
-       end
-end
-
-redef class MPackage
-       # Serialize the full catalog version of `self` to JSON
-       #
-       # See: `FullCatalogSerializer`
-       fun to_full_catalog_json(mainmodule: MModule, catalog: Catalog, plain, pretty: nullable Bool): String do
-               var stream = new StringWriter
-               var serializer = new FullCatalogSerializer(stream, mainmodule, catalog)
-               serializer.plain_json = plain or else false
-               serializer.pretty_json = pretty or else false
-               serializer.serialize self
-               stream.close
-               return stream.to_s
-       end
-
-       redef fun core_serialize_to(v) do
-               super
-               v.serialize_attribute("metadata", metadata)
-               if v isa FullCatalogSerializer then
-                       v.serialize_attribute("stats", v.catalog.mpackages_stats[self])
-
-                       var parents = v.catalog.deps[self].direct_greaters.to_a
-                       v.serialize_attribute("dependencies", v.deps_to_json(parents))
-                       var children = v.catalog.deps[self].direct_smallers.to_a
-                       v.serialize_attribute("clients", v.deps_to_json(children))
-               end
-       end
-end
-
-# CatalogSerializer decorate the Package JSON with full catalog metadata
-#
-# See MEntity::to_full_catalog_json.
-class FullCatalogSerializer
-       super FullJsonSerializer
-
-       # Catalog used to decorate the MPackages
-       var catalog: Catalog
-
-       private fun deps_to_json(mpackages: Array[MPackage]): JsonArray do
-               var res = new JsonArray
-               for mpackage in mpackages do
-                       res.add dep_to_json(mpackage)
-               end
-               return res
-       end
-
-       private fun dep_to_json(mpackage: MPackage): JsonObject do
-               var obj = new JsonObject
-               obj["name"] = mpackage.name
-               var mdoc = mpackage.mdoc_or_fallback
-               if mdoc != null then
-                       obj["synopsis"] = mdoc.synopsis.write_to_string
-               end
-               return obj
-       end
-end
index 561f0af..0a23542 100644 (file)
 # Nitdoc specific Markdown format handling for Nitweb
 module api_docdown
 
-import api_graph
-intrude import doc_down
-intrude import markdown::wikilinks
-import doc_commands
-import model::model_index
+import api_model
+import commands::commands_docdown
 
 redef class NitwebConfig
        # Specific Markdown processor to use within Nitweb
        var md_processor: MarkdownProcessor is lazy do
-               var proc = new MarkdownProcessor
-               proc.decorator = new NitwebDecorator(view, modelbuilder)
+               var parser = new CommandParser(view, modelbuilder)
+               var proc = new CmdMarkdownProcessor(parser)
+               proc.decorator = new CmdDecorator(view)
                return proc
        end
 end
@@ -45,295 +43,3 @@ class APIDocdown
                res.html config.md_processor.process(req.body)
        end
 end
-
-# Specific Markdown decorator for Nitweb
-#
-# We reuse all the implementation of the NitdocDecorator and add the wikilinks handling.
-class NitwebDecorator
-       super NitdocDecorator
-
-       # View used by wikilink commands to find model entities
-       var view: ModelView
-
-       # Modelbuilder used to access code
-       var modelbuilder: ModelBuilder
-
-       redef fun add_span_code(v, buffer, from, to) do
-               var text = new FlatBuffer
-               buffer.read(text, from, to)
-               var name = text.write_to_string
-               name = name.replace("nullable ", "")
-               var mentity = try_find_mentity(view, name)
-               if mentity == null then
-                       super
-               else
-                       v.add "<code>"
-                       v.write_mentity_link(mentity, text.write_to_string)
-                       v.add "</code>"
-               end
-       end
-
-       private fun try_find_mentity(view: ModelView, text: String): nullable MEntity do
-               var mentity = view.mentity_by_full_name(text)
-               if mentity != null then return mentity
-
-               var mentities = view.mentities_by_name(text)
-               if mentities.is_empty then
-                       return null
-               else if mentities.length > 1 then
-                       # TODO smart resolve conflicts
-               end
-               return mentities.first
-       end
-
-       redef fun add_wikilink(v, token) do
-               v.render_wikilink(token, view)
-       end
-end
-
-# Same as `InlineDecorator` but with wikilink commands handling
-class NitwebInlineDecorator
-       super InlineDecorator
-
-       # View used by wikilink commands to find model entities
-       var view: ModelView
-
-       # Modelbuilder used to access code
-       var modelbuilder: ModelBuilder
-
-       redef fun add_wikilink(v, token) do
-               v.render_wikilink(token, view)
-       end
-end
-
-redef class MarkdownProcessor
-
-       # Parser used to process doc commands
-       var parser = new DocCommandParser
-
-       # Render a wikilink
-       fun render_wikilink(token: TokenWikiLink, model: ModelView) do
-               var link = token.link
-               if link == null then return
-               var name = token.name
-               if name != null then link = "{name} | {link}"
-               var cmd = parser.parse(link.write_to_string)
-               if cmd == null then
-                       var full_name = if token.link != null then token.link.as(not null).write_to_string.trim else null
-                       if full_name == null or full_name.is_empty then
-                               write_error("empty wikilink")
-                               return
-                       end
-                       var mentity = find_mentity(model, full_name)
-                       if mentity == null then return
-                       name = if token.name != null then token.name.as(not null).to_s else null
-                       write_mentity_link(mentity, name)
-                       return
-               else
-                       for message in parser.errors do
-                               if message.level == 1 then
-                                       write_error(message.message)
-                               else if message.level > 1 then
-                                       write_warning(message.message)
-                               end
-                       end
-               end
-               cmd.render(self, token, model)
-       end
-
-       # Find the MEntity that matches `name`.
-       #
-       # Write an error if the entity is not found
-       fun find_mentity(model: ModelView, name: nullable String): nullable MEntity do
-               if name == null then
-                       write_error("no MEntity found")
-                       return null
-               end
-               # Lookup by full name
-               var mentity = model.mentity_by_full_name(name)
-               if mentity != null then return mentity
-
-               var mentities = model.mentities_by_name(name)
-               if mentities.is_empty then
-                       var suggest = model.find(name, 3)
-                       var msg = new Buffer
-                       msg.append "no MEntity found for name `{name}`"
-                       if suggest.not_empty then
-                               msg.append " (suggestions: "
-                               var i = 0
-                               for s in suggest do
-                                       msg.append "`{s.full_name}`"
-                                       if i < suggest.length - 1 then msg.append ", "
-                                       i += 1
-                               end
-                               msg.append ")"
-                       end
-                       write_error(msg.write_to_string)
-                       return null
-               else if mentities.length > 1 then
-                       var msg = new Buffer
-                       msg.append "conflicts for name `{name}`"
-                       msg.append " (conflicts: "
-                       var i = 0
-                       for s in mentities do
-                               msg.append "`{s.full_name}`"
-                               if i < mentities.length - 1 then msg.append ", "
-                               i += 1
-                       end
-                       msg.append ")"
-                       write_warning(msg.write_to_string)
-               end
-               return mentities.first
-       end
-
-       # Write a warning in the output
-       fun write_warning(text: String) do
-               emit_text "<p class='text-warning'>Warning: {text}</p>"
-       end
-
-       # Write an error in the output
-       fun write_error(text: String) do
-               emit_text "<p class='text-danger'>Error: {text}</p>"
-       end
-
-       # Write a link to a mentity in the output
-       fun write_mentity_link(mentity: MEntity, text: nullable String) do
-               var link = mentity.web_url
-               var name = text or else mentity.name
-               var mdoc = mentity.mdoc_or_fallback
-               var comment = null
-               if mdoc != null then comment = mdoc.synopsis
-               decorator.add_link(self, link, name, comment)
-       end
-
-       # Write a mentity list in the output
-       fun write_mentity_list(mentities: Collection[MEntity]) do
-               add "<ul>"
-               for mentity in mentities do
-                       var mdoc = mentity.mdoc_or_fallback
-                       add "<li>"
-                       write_mentity_link(mentity)
-                       if mdoc != null then
-                               add " - "
-                               emit_text mdoc.synopsis
-                       end
-                       add "</li>"
-               end
-               add "</ul>"
-       end
-end
-
-redef class DocCommand
-
-       # Emit the HTML related to the execution of this doc command
-       fun render(v: MarkdownProcessor, token: TokenWikiLink, model: ModelView) do
-               v.write_error("not yet implemented command `{token.link or else "null"}`")
-       end
-end
-
-redef class CommentCommand
-       redef fun render(v, token, model) do
-               var name = arg
-               var mentity = v.find_mentity(model, name)
-               if mentity == null then return
-               var mdoc = mentity.mdoc_or_fallback
-               if mdoc == null then
-                       v.write_warning("no MDoc for mentity `{name}`")
-                       return
-               end
-               v.add "<h3>"
-               if not opts.has_key("no-link") then
-                       v.write_mentity_link(mentity)
-               end
-               if not opts.has_key("no-link") and not opts.has_key("no-synopsis") then
-                       v.add " - "
-               end
-               if not opts.has_key("no-synopsis") then
-                       v.emit_text mdoc.html_synopsis.write_to_string
-               end
-               v.add "</h3>"
-               if not opts.has_key("no-comment") then
-                       v.add v.process(mdoc.comment).write_to_string
-               end
-       end
-end
-
-redef class ListCommand
-       redef fun render(v, token, model) do
-               var name = arg
-               var mentity = v.find_mentity(model, name)
-               if mentity == null then return
-               if mentity isa MPackage then
-                       v.write_mentity_list(mentity.mgroups)
-               else if mentity isa MGroup then
-                       var res = new Array[MEntity]
-                       res.add_all mentity.in_nesting.smallers
-                       res.add_all mentity.mmodules
-                       v.write_mentity_list(res)
-               else if mentity isa MModule then
-                       v.write_mentity_list(mentity.mclassdefs)
-               else if mentity isa MClass then
-                       v.write_mentity_list(mentity.collect_intro_mproperties(model))
-               else if mentity isa MClassDef then
-                       v.write_mentity_list(mentity.mpropdefs)
-               else if mentity isa MProperty then
-                       v.write_mentity_list(mentity.mpropdefs)
-               else
-                       v.write_error("no list found for name `{name}`")
-               end
-       end
-end
-
-redef class CodeCommand
-       redef fun render(v, token, model) do
-               var name = arg
-               var mentity = v.find_mentity(model, name)
-               if mentity == null then return
-               if mentity isa MClass then mentity = mentity.intro
-               if mentity isa MProperty then mentity = mentity.intro
-               var source = render_source(mentity, v.decorator.as(NitwebDecorator).modelbuilder)
-               if source == null then
-                       v.write_error("no source for MEntity `{name}`")
-                       return
-               end
-               v.add "<pre>"
-               v.add source
-               v.add "</pre>"
-       end
-
-       # Highlight `mentity` source code.
-       private fun render_source(mentity: MEntity, modelbuilder: ModelBuilder): nullable HTMLTag do
-               var node = modelbuilder.mentity2node(mentity)
-               if node == null then return null
-               var hl = new HtmlightVisitor
-               hl.highlight_node node
-               return hl.html
-       end
-end
-
-redef class GraphCommand
-       redef fun render(v, token, model) do
-               var name = arg
-               var mentity = v.find_mentity(model, name)
-               if mentity == null then return
-               var g = new InheritanceGraph(mentity, model)
-               var pdepth = if opts.has_key("pdepth") and opts["pdepth"].is_int then
-                       opts["pdepth"].to_i else 3
-               var cdepth = if opts.has_key("cdepth") and opts["cdepth"].is_int then
-                       opts["cdepth"].to_i else 3
-               v.add g.draw(pdepth, cdepth).to_svg
-       end
-end
-
-redef class Text
-       # Read `self` between `nstart` and `nend` (excluded) and writte chars to `out`.
-       private fun read(out: FlatBuffer, nstart, nend: Int): Int do
-               var pos = nstart
-               while pos < length and pos < nend do
-                       out.add self[pos]
-                       pos += 1
-               end
-               if pos == length then return -1
-               return pos
-       end
-end
diff --git a/src/doc/api/api_graph.nit b/src/doc/api/api_graph.nit
deleted file mode 100644 (file)
index e1bce36..0000000
+++ /dev/null
@@ -1,264 +0,0 @@
-# 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.
-
-# Render various graph from a model.
-module api_graph
-
-import api_base
-import dot
-import uml
-
-redef class APIRouter
-       init do
-               super
-               use("/graph/inheritance/:id", new APIInheritanceGraph(config))
-       end
-end
-
-# Render a hierarchy graph for `mentity` if any.
-class APIInheritanceGraph
-       super APIHandler
-
-       redef fun get(req, res) do
-               var mentity = mentity_from_uri(req, res)
-               if mentity == null then return
-               var pdepth = req.int_arg("pdepth")
-               var cdepth = req.int_arg("cdepth")
-               var g = new InheritanceGraph(mentity, config.view)
-               res.send g.draw(pdepth, cdepth).to_svg
-       end
-end
-
-# Graph for mentity hierarchies
-#
-# Recursively build parents and children list from a `center`.
-class InheritanceGraph
-       super ModelVisitor
-
-       autoinit center, view, filter
-
-       # MEntity at the center of this graph
-       var center: MEntity
-
-       # ModelView used to filter graph
-       var view: ModelView
-
-       # Graph generated
-       var graph: DotGraph is lazy do
-               var graph = new DotGraph("package_diagram", "digraph")
-
-               graph["compound"] = "true"
-               graph["rankdir"] = "BT"
-               graph["ranksep"] = 0.3
-               graph["nodesep"] = 0.3
-
-               graph.nodes_attrs["margin"] = 0.1
-               graph.nodes_attrs["width"] = 0
-               graph.nodes_attrs["height"] = 0
-               graph.nodes_attrs["fontsize"] = 10
-               graph.nodes_attrs["fontname"] = "helvetica"
-
-               graph.edges_attrs["dir"] = "none"
-               graph.edges_attrs["color"] = "gray"
-
-               return graph
-       end
-
-       # Build the graph
-       fun draw(parents_depth, children_depth: nullable Int): DotGraph do
-               draw_node center
-               draw_parents(center, parents_depth)
-               draw_children(center, children_depth)
-               return graph
-       end
-
-       private var nodes = new HashMap[MEntity, DotElement]
-       private var done_parents = new HashSet[MEntity]
-       private var done_children = new HashSet[MEntity]
-
-       # Recursively draw parents of mentity
-       fun draw_parents(mentity: MEntity, max_depth: nullable Int, current_depth: nullable Int) do
-               if done_parents.has(mentity) then return
-               done_parents.add mentity
-               current_depth = current_depth or else 0
-               if max_depth != null and current_depth >= max_depth then
-                       from_dotdotdot(mentity)
-                       return
-               end
-               var parents = mentity.collect_parents(view)
-               if parents.length > 10 then
-                       from_dotdotdot(mentity)
-                       return
-               end
-               for parent in parents do
-                       if parent isa MModule then
-                               var mgroup = parent.mgroup
-                               if mgroup != null and mgroup.default_mmodule == parent then parent = mgroup
-                       end
-                       if parent isa MGroup then
-                               if parent.mpackage.mgroups.first == parent then parent = parent.mpackage
-                       end
-                       draw_edge(mentity, parent)
-               end
-               for parent in parents do
-                       if parent isa MModule then
-                               var mgroup = parent.mgroup
-                               if mgroup != null and mgroup.default_mmodule == parent then parent = mgroup
-                       end
-                       if parent isa MGroup then
-                               if parent.mpackage.mgroups.first == parent then parent = parent.mpackage
-                       end
-                       draw_parents(parent, max_depth, current_depth + 1)
-               end
-       end
-
-       # Recursively draw children of mentity
-       fun draw_children(mentity: MEntity, max_depth: nullable Int, current_depth: nullable Int) do
-               if done_children.has(mentity) then return
-               done_children.add mentity
-               current_depth = current_depth or else 0
-               if max_depth != null and current_depth >= max_depth then
-                       to_dotdotdot(mentity)
-                       return
-               end
-               var children = mentity.collect_children(view)
-               if children.length > 10 then
-                       to_dotdotdot(mentity)
-                       return
-               end
-               for child in children do
-                       if child isa MGroup then
-                               if child.mpackage.mgroups.first == child then child = child.mpackage
-                       end
-                       draw_edge(child, mentity)
-               end
-               for child in children do
-                       if child isa MGroup then
-                               if child.mpackage.mgroups.first == child then child = child.mpackage
-                       end
-                       draw_children(child, max_depth, current_depth + 1)
-               end
-       end
-
-       # Draw a node from a `mentity`
-       fun draw_node(mentity: MEntity): DotElement do
-               if nodes.has_key(mentity) then return nodes[mentity]
-               var node: DotElement = mentity.to_dot_node
-               if mentity == center then node = highlight(node)
-               nodes[mentity] = node
-               graph.add node
-               return node
-       end
-
-       private var edges = new HashMap2[MEntity, MEntity, DotEdge]
-
-       # Draw a edges between two mentities
-       fun draw_edge(from, to: MEntity): DotEdge do
-               if edges.has(from, to) then return edges[from, to].as(not null)
-               if edges.has(to, from) then return edges[to, from].as(not null)
-               var nfrom = draw_node(from)
-               var nto = draw_node(to)
-               var edge = new DotEdge(nfrom, nto)
-               edges[from, to] = edge
-               graph.add edge
-               return edge
-       end
-
-       private var to_dots = new HashMap[MEntity, DotElement]
-
-       # Create a link from `mentity` to a `...` node
-       fun to_dotdotdot(mentity: MEntity): DotEdge do
-               var nto = draw_node(mentity)
-               var dots = to_dots.get_or_null(mentity)
-               if dots == null then
-                       dots = dotdotdot("{nto.id}...")
-                       to_dots[mentity] = dots
-               end
-               graph.add dots
-               var edge = new DotEdge(dots, nto)
-               graph.add edge
-               return edge
-       end
-
-       private var from_dots = new HashMap[MEntity, DotElement]
-
-       # Create a link from a `...` node to a `mentity`
-       fun from_dotdotdot(mentity: MEntity): DotEdge do
-               var nfrom = draw_node(mentity)
-               var dots = to_dots.get_or_null(mentity)
-               if dots == null then
-                       dots = dotdotdot("...{nfrom.id}")
-                       from_dots[mentity] = dots
-               end
-               graph.add dots
-               var edge = new DotEdge(dots, nfrom)
-               graph.add edge
-               return edge
-       end
-
-       # Change the border color of the node
-       fun highlight(dot: DotElement): DotElement do
-               dot["color"] = "#1E9431"
-               return dot
-       end
-
-       # Generate a `...` node
-       fun dotdotdot(id: String): DotNode do
-               var node = new DotNode(id)
-               node["label"] = "..."
-               node["shape"] = "none"
-               return node
-       end
-end
-
-redef class MEntity
-       private fun to_dot_node: DotNode do
-               var node = new DotNode(full_name)
-               node["URL"] = web_url
-               node["label"] = name
-               return node
-       end
-end
-
-redef class MPackage
-       redef fun to_dot_node do
-               var node = super
-               node["shape"] = "tab"
-               return node
-       end
-end
-
-redef class MGroup
-       redef fun to_dot_node do
-               var node = super
-               node["shape"] = "folder"
-               return node
-       end
-end
-
-redef class MModule
-       redef fun to_dot_node do
-               var node = super
-               node["shape"] = "note"
-               return node
-       end
-end
-
-redef class MClass
-       redef fun to_dot_node do
-               var node = super
-               node["shape"] = "box"
-               return node
-       end
-end
index e595d4a..b6b2657 100644 (file)
@@ -16,7 +16,6 @@
 module api_light
 
 import api_base
-import htmlight
 
 redef class APIRouter
        redef init do
index 0829648..9619892 100644 (file)
 module api_model
 
 import api_base
-import htmlight
-import uml
-import model::model_index
 
 redef class APIRouter
        redef init do
                super
                use("/list", new APIList(config))
-               use("/search", new APISearch(config))
                use("/random", new APIRandom(config))
+               use("/search", new APISearch(config))
+
                use("/entity/:id", new APIEntity(config))
-               use("/entity/:id/doc", new APIEntityDoc(config))
+               use("/doc/:id", new APIEntityDoc(config))
                use("/code/:id", new APIEntityCode(config))
-               use("/uml/:id", new APIEntityUML(config))
-               use("/linearization/:id", new APIEntityLinearization(config))
+               use("/lin/:id", new APIEntityLinearization(config))
                use("/defs/:id", new APIEntityDefs(config))
-               use("/inheritance/:id", new APIEntityInheritance(config))
+               use("/intros/:id", new APIEntityIntros(config))
+               use("/redefs/:id", new APIEntityRedefs(config))
+               use("/meta/:id", new APIEntityMetadata(config))
+               use("/all/:id", new APIEntityAll(config))
+
+               use("/ancestors/:id", new APIEntityAncestors(config))
+               use("/parents/:id", new APIEntityParents(config))
+               use("/children/:id", new APIEntityChildren(config))
+               use("/descendants/:id", new APIEntityDescendants(config))
+
+               use("/uml/:id", new APIEntityUML(config))
+               use("/graph/inheritance/:id", new APIInheritanceGraph(config))
+
+               use("/catalog/packages/", new APICatalogPackages(config))
+               use("/catalog/stats", new APICatalogStats(config))
+               use("/catalog/tags", new APICatalogTags(config))
+               use("/catalog/tag/:tid", new APICatalogTag(config))
+               use("/catalog/person/:pid", new APICatalogPerson(config))
+               use("/catalog/person/:pid/maintaining", new APICatalogMaintaining(config))
+               use("/catalog/person/:pid/contributing", new APICatalogContributing(config))
        end
 end
 
-# List all mentities.
-#
-# MEntities can be filtered on their kind using the `k` parameter.
-# Allowed kinds are `package`, `group`, `module`, `class`, `classdef`, `property`, `propdef`.
-#
-# List size can be limited with the `n` parameter.
-#
-# Example: `GET /list?k=module?n=10`
-class APIList
+# An API Handler that use a DocCommand to respond
+abstract class APICommand
        super APIHandler
 
-       # List mentities depending on the `k` kind parameter.
-       fun list_mentities(req: HttpRequest): Array[MEntity] do
-               var k = req.string_arg("k")
-               var mentities = new Array[MEntity]
-               if k == "package" then
-                       for mentity in config.view.mpackages do mentities.add mentity
-               else if k == "group" then
-                       for mentity in config.view.mgroups do mentities.add mentity
-               else if k == "module" then
-                       for mentity in config.view.mmodules do mentities.add mentity
-               else if k == "class" then
-                       for mentity in config.view.mclasses do mentities.add mentity
-               else if k == "classdef" then
-                       for mentity in config.view.mclassdefs do mentities.add mentity
-               else if k == "property" then
-                       for mentity in config.view.mproperties do mentities.add mentity
-               else if k == "propdef" then
-                       for mentity in config.view.mpropdefs do mentities.add mentity
-               else
-                       for mentity in config.view.mentities do mentities.add mentity
-               end
-               return mentities
-       end
+       # Return the doc command to apply for self
+       fun command: DocCommand is abstract
 
-       # Filter mentities based on the config view filters
-       fun filter_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
-               var res = new Array[MEntity]
-               for mentity in mentities do
-                       if config.view.filter.accept_mentity(mentity) then res.add mentity
+       redef fun get(req, res) do
+               var command = self.command
+               var status = command.http_init(req)
+               if status isa CmdError then
+                       res.api_error(status.http_status_code, status.to_s)
+                       return
                end
-               return res
+               res.api_json(req, command.to_json)
        end
+end
 
-       # Sort mentities by lexicographic order
-       #
-       # TODO choose order from request
-       fun sort_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
-               var sorted = mentities.to_a
-               var sorter = new MEntityNameSorter
-               sorter.sort(sorted)
-               return sorted
-       end
+# CmdModel
 
-       # Limit mentities depending on the `n` parameter.
-       fun limit_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
-               var n = req.int_arg("n")
-               if n != null then
-                       return mentities.sub(0, n)
-               end
-               return mentities
-       end
+# List all mentities.
+#
+# Example: `GET /list?kind=modules?limit=10`
+class APIList
+       super APICommand
 
-       redef fun get(req, res) do
-               var mentities = list_mentities(req)
-               mentities = sort_mentities(req, mentities)
-               mentities = limit_mentities(req, mentities)
-               res.api_json(req, new JsonArray.from(mentities))
-       end
+       redef fun command do return new CmdModelEntities(config.view)
 end
 
-# Search mentities from a query string.
+# Return a random list of MEntities.
 #
-# Example: `GET /search?q=Arr`
-class APISearch
-       super APIList
-
-       redef fun get(req, res) do
-               var query = req.string_arg("q")
-               if query == null then
-                       res.api_error(400, "Missing search string")
-                       return
-               end
-               var page = req.int_arg("p")
-               var limit = req.int_arg("n")
-               var response = new JsonArray.from(search(query, limit))
-               res.api_json(req, paginate(response, response.length, page, limit))
-       end
+# Example: `GET /random?kind=modules&limit=10`
+class APIRandom
+       super APICommand
 
-       fun search(query: String, limit: nullable Int): Array[MEntity] do
-               return config.view.find(query)
-       end
+       redef fun command do return new CmdRandomEntities(config.view)
 end
 
-# Return a random list of MEntities.
+# Search mentities from a cmd string.
 #
-# Example: `GET /random?n=10&k=module`
-class APIRandom
+# Example: `GET /search?q=Arr`
+class APISearch
        super APIList
 
-       # Randomize mentities order.
-       fun randomize_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
-               var res = mentities.to_a
-               res.shuffle
-               return res
-       end
-
-       redef fun get(req, res) do
-               var mentities = list_mentities(req)
-               mentities = filter_mentities(req, mentities)
-               mentities = randomize_mentities(req, mentities)
-               mentities = limit_mentities(req, mentities)
-               res.api_json(req, new JsonArray.from(mentities))
-       end
+       redef fun command do return new CmdCatalogSearch(config.view, config.catalog)
 end
 
+# CmdEntity
+
 # Return the JSON representation of a MEntity.
 #
 # Example: `GET /entity/core::Array`
 class APIEntity
-       super APIHandler
+       super APICommand
 
-       redef fun get(req, res) do
-               var mentity = mentity_from_uri(req, res)
-               if mentity == null then return
-               res.api_full_json(req, mentity)
-       end
+       redef fun command do return new CmdEntity(config.view)
 end
 
 # Return the full MDoc of a MEntity.
 #
 # Example: `GET /entity/core::Array/doc`
 class APIEntityDoc
-       super APIHandler
+       super APICommand
 
-       redef fun get(req, res) do
-               var mentity = mentity_from_uri(req, res)
-               if mentity == null then return
-
-               var obj = new JsonObject
-               var mdoc = mentity.mdoc_or_fallback
-               if mdoc != null then
-                       obj["documentation"] = mdoc.html_documentation.write_to_string
-                       obj["location"] = mdoc.location
-               end
-               res.api_json(req, obj)
-       end
+       redef fun command do return new CmdComment(config.view)
 end
 
-# List ancestors, parents, child and descendants of MEntity
+# List MEntity ancestors
 #
-# Example: `GET /entity/core::Array/inheritance`
-class APIEntityInheritance
-       super APIHandler
+# Example: `GET /ancestors/core::Array`
+class APIEntityAncestors
+       super APICommand
 
-       redef fun get(req, res) do
-               var mentity = mentity_from_uri(req, res)
-               if mentity == null then return
-               res.api_json(req, mentity.hierarchy_poset(config.view)[mentity])
-       end
+       redef fun command do return new CmdAncestors(config.view)
+end
+
+# List MEntity parents
+#
+# Example: `GET /parents/core::Array`
+class APIEntityParents
+       super APICommand
+
+       redef fun command do return new CmdParents(config.view)
+end
+
+# List MEntity children
+#
+# Example: `GET /children/core::Array`
+class APIEntityChildren
+       super APICommand
+
+       redef fun command do return new CmdChildren(config.view)
+end
+
+# List MEntity descendants
+#
+# Example: `GET /descendants/core::Array`
+class APIEntityDescendants
+       super APICommand
+
+       redef fun command do return new CmdDescendants(config.view)
 end
 
 # Linearize super definitions of a MClassDef or a MPropDef if any.
 #
-# Example: `GET /entity/core::Array/linearization`
+# Example: `GET /linearization/core::Array`
 class APIEntityLinearization
-       super APIHandler
+       super APICommand
 
-       redef fun get(req, res) do
-               var mentity = mentity_from_uri(req, res)
-               if mentity == null then return
-               var lin = mentity.collect_linearization(config.mainmodule)
-               if lin == null then
-                       res.api_error(404, "No linearization for mentity `{mentity.full_name}`")
-                       return
-               end
-               var mentities = new JsonArray
-               for e in lin do mentities.add e
-               res.api_json(req, mentities)
-       end
+       redef fun command do return new CmdLinearization(config.view)
 end
 
 # List definitions of a MEntity.
 #
 # Example: `GET /defs/core::Array`
 class APIEntityDefs
-       super APIList
+       super APICommand
 
-       redef fun get(req, res) do
-               var mentity = mentity_from_uri(req, res)
-               if mentity == null then return
-               var mentities = new Array[MEntity]
-               if mentity isa MPackage then
-                       mentities.add_all mentity.collect_mgroups(config.view)
-                       mentities.add_all mentity.collect_mmodules(config.view)
-               else if mentity isa MGroup then
-                       mentities.add_all mentity.collect_mgroups(config.view)
-                       mentities.add_all mentity.collect_mmodules(config.view)
-               else if mentity isa MModule then
-                       mentities.add_all mentity.collect_local_mclassdefs(config.view)
-               else if mentity isa MClass then
-                       mentities.add_all mentity.collect_mclassdefs(config.view)
-               else if mentity isa MClassDef then
-                       mentities.add_all mentity.collect_mpropdefs(config.view)
-               else if mentity isa MProperty then
-                       mentities.add_all mentity.collect_mpropdefs(config.view)
-               else
-                       res.api_error(404, "No definition list for mentity `{mentity.full_name}`")
-                       return
-               end
-               mentities = filter_mentities(req, mentities)
-               mentities = sort_mentities(req, mentities)
-               mentities = limit_mentities(req, mentities)
-               res.api_json(req, new JsonArray.from(mentities))
-       end
+       redef fun command do return new CmdFeatures(config.view)
 end
 
-abstract class SVGHandler
-       super APIHandler
+# List intro definitions of a MEntity.
+#
+# Example: `GET /intros/core::Array`
+class APIEntityIntros
+       super APICommand
 
-       # Render a `dot` string as a svg image.
-       fun render_dot(dot: Text): String do
-               var proc = new ProcessDuplex("dot", "-Tsvg")
-               var svg = proc.write_and_read(dot)
-               proc.close
-               proc.wait
-               return svg
-       end
+       redef fun command do return new CmdIntros(config.view)
 end
 
-# Return a UML representation of MEntity.
+# List redef definitions of a MEntity.
 #
-# Example: `GET /entity/core::Array/uml`
-class APIEntityUML
-       super SVGHandler
+# Example: `GET /redefs/core::Array`
+class APIEntityRedefs
+       super APICommand
 
-       redef fun get(req, res) do
-               var mentity = mentity_from_uri(req, res)
-               if mentity == null then return
-               var dot
-               if mentity isa MClassDef then mentity = mentity.mclass
-               if mentity isa MClass then
-                       var uml = new UMLModel(config.view, config.mainmodule)
-                       dot = uml.generate_class_uml.write_to_string
-               else if mentity isa MModule then
-                       var uml = new UMLModel(config.view, mentity)
-                       dot = uml.generate_package_uml.write_to_string
-               else
-                       res.api_error(404, "No UML for mentity `{mentity.full_name}`")
-                       return
-               end
-               res.send render_dot(dot)
-       end
+       redef fun command do return new CmdRedefs(config.view)
+end
+
+# List all definitions accessible from a MEntity.
+#
+# Example: `GET /all/core::Array`
+class APIEntityAll
+       super APICommand
+
+       redef fun command do return new CmdAllProps(config.view)
 end
 
 # Return the source code of MEntity.
 #
-# Example: `GET /entity/core::Array/code`
+# Example: `GET /code/core::Array`
 class APIEntityCode
-       super APIHandler
+       super APICommand
 
-       redef fun get(req, res) do
-               var mentity = mentity_from_uri(req, res)
-               if mentity == null then return
-               var source = render_source(mentity)
-               if source == null then
-                       res.api_error(404, "No code for mentity `{mentity.full_name}`")
-                       return
-               end
-               res.send source
-       end
+       redef fun command do return new CmdCode(config.view, config.modelbuilder)
+end
 
-       # Highlight `mentity` source code.
-       private fun render_source(mentity: MEntity): nullable HTMLTag do
-               var node = config.modelbuilder.mentity2node(mentity)
-               if node == null then return null
-               var hl = new HtmlightVisitor
-               hl.highlight_node node
-               return hl.html
-       end
+# Return the UML diagram for MEntity.
+#
+# Example: `GET /uml/core::Array`
+class APIEntityUML
+       super APICommand
+
+       redef fun command do return new CmdUML(config.view)
+end
+
+# Return the inheritance graph for MEntity.
+#
+# Example: `GET /inheritance/core::Array`
+class APIInheritanceGraph
+       super APICommand
+
+       redef fun command do return new CmdInheritanceGraph(config.view)
+end
+
+# CmdCatalog
+
+# Get all the packages from the catalog using pagination
+#
+# `GET /packages?p=1&n=10`: get the list of catalog by page
+class APICatalogPackages
+       super APICommand
+
+       redef fun command do return new CmdCatalogPackages(config.view, config.catalog)
+end
+
+# Get the catalog statistics
+#
+# `GET /stats`: return the catalog statistics
+class APICatalogStats
+       super APICommand
+
+       redef fun command do return new CmdCatalogStats(config.view, config.catalog)
+end
+
+# Get the package metadata
+#
+# `GET /:id/metadata`: return a paginated list of packages
+class APIEntityMetadata
+       super APICommand
+
+       redef fun command do return new CmdMetadata(config.view)
+end
+
+# Get all the tags from the catalog
+#
+# `GET /tags`: the list of tags associated with their number of packages
+class APICatalogTags
+       super APICommand
+
+       redef fun command do return new CmdCatalogTags(config.view, config.catalog)
+end
+
+# Get the packages related to a tag
+#
+# `GET /tag/:tid?p=1&n=10`: return a paginated list of packages
+class APICatalogTag
+       super APICommand
+
+       redef fun command do return new CmdCatalogTag(config.view, config.catalog)
+end
+
+# Get a person existing in the catalog
+#
+# `GET /person/:pid`: get the person with `pid`
+class APICatalogPerson
+       super APICommand
+
+       redef fun command do return new CmdCatalogPerson(config.view, config.catalog)
+end
+
+# Get the list of mpackages maintained by a person
+#
+# `GET /person/:pid/maintaining?p=1&n=10`: return a paginated list of packages
+class APICatalogMaintaining
+       super APICommand
+
+       redef fun command do return new CmdCatalogMaintaining(config.view, config.catalog)
+end
+
+# Get the list of mpackages contributed by a person
+#
+# `GET /person/:pid/contributing?p=1&n=10`: return a paginated list of packages
+class APICatalogContributing
+       super APICommand
+
+       redef fun command do return new CmdCatalogContributing(config.view, config.catalog)
 end