--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module 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("/entity/:id", new APIEntity(config))
+ use("/entity/:id/doc", new APIEntityDoc(config))
+ use("/code/:id", new APIEntityCode(config))
+ use("/uml/:id", new APIEntityUML(config))
+ use("/linearization/:id", new APIEntityLinearization(config))
+ use("/defs/:id", new APIEntityDefs(config))
+ use("/inheritance/:id", new APIEntityInheritance(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
+ 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
+
+ # 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
+ end
+ return res
+ 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
+
+ # 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
+
+ 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
+end
+
+# Search mentities from a query string.
+#
+# 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
+
+ fun search(query: String, limit: nullable Int): Array[MEntity] do
+ return config.view.find(query)
+ end
+end
+
+# Return a random list of MEntities.
+#
+# Example: `GET /random?n=10&k=module`
+class APIRandom
+ 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
+end
+
+# Return the JSON representation of a MEntity.
+#
+# Example: `GET /entity/core::Array`
+class APIEntity
+ super APIHandler
+
+ 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
+end
+
+# Return the full MDoc of a MEntity.
+#
+# Example: `GET /entity/core::Array/doc`
+class APIEntityDoc
+ super APIHandler
+
+ 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
+end
+
+# List ancestors, parents, child and descendants of MEntity
+#
+# Example: `GET /entity/core::Array/inheritance`
+class APIEntityInheritance
+ super APIHandler
+
+ 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
+end
+
+# Linearize super definitions of a MClassDef or a MPropDef if any.
+#
+# Example: `GET /entity/core::Array/linearization`
+class APIEntityLinearization
+ super APIHandler
+
+ 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
+end
+
+# List definitions of a MEntity.
+#
+# Example: `GET /defs/core::Array`
+class APIEntityDefs
+ super APIList
+
+ 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
+end
+
+abstract class SVGHandler
+ super APIHandler
+
+ # 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
+end
+
+# Return a UML representation of MEntity.
+#
+# Example: `GET /entity/core::Array/uml`
+class APIEntityUML
+ super SVGHandler
+
+ 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
+end
+
+# Return the source code of MEntity.
+#
+# Example: `GET /entity/core::Array/code`
+class APIEntityCode
+ super APIHandler
+
+ 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
+
+ # 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
+end