This PR cleans the Quick&Dirty(c) JSON API of nitweb to use the model_json one.
Modified services:
* /uml -> /api/um/:entity
* /code -> /api/code/:entity
* /random -> /api/random
* /search -> /api/search
Also do some renaming to match `popcorn` philosophy.
Pull-Request: #2137
Reviewed-by: Jean Privat <jean@pryen.org>
var port = toolcontext.opt_port.value
var app = new App
- app.use("/random", new RandomAction(model))
- app.use("/doc/:namespace", new DocAction(model, modelbuilder))
- app.use("/code/:namespace", new CodeAction(model, modelbuilder))
- app.use("/search/:namespace", new SearchAction(model))
- app.use("/uml/:namespace", new UMLDiagramAction(model, mainmodule))
- app.use("/", new TreeAction(model))
+
+ app.use("/api", new APIRouter(model, modelbuilder, mainmodule))
+ app.use("/doc/:namespace", new DocAction(model, mainmodule, modelbuilder))
+ app.use("/", new TreeAction(model, mainmodule))
app.listen(host, port.to_i)
end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module model_api
+
+import web_base
+import highlight
+import uml
+
+# Specific handler for nitweb API.
+abstract class APIHandler
+ super ModelHandler
+
+ # The JSON API does not filter anything by default.
+ #
+ # So we can cache the model view.
+ var view: ModelView is lazy do
+ var view = new ModelView(model)
+ view.min_visibility = private_visibility
+ view.include_fictive = true
+ view.include_empty_doc = true
+ view.include_attribute = true
+ view.include_test_suite = true
+ return view
+ end
+
+ # Try to load the mentity from uri with `/:id`.
+ #
+ # Send 400 if `:id` is null.
+ # Send 404 if no entity is found.
+ # Return null in both cases.
+ fun mentity_from_uri(req: HttpRequest, res: HttpResponse): nullable MEntity do
+ var id = req.param("id")
+ if id == null then
+ res.error 400
+ return null
+ end
+ var mentity = find_mentity(view, id)
+ if mentity == null then
+ res.error 404
+ end
+ return mentity
+ end
+end
+
+# Group all api handlers in one router.
+class APIRouter
+ super Router
+
+ # Model to pass to handlers.
+ var model: Model
+
+ # ModelBuilder to pass to handlers.
+ var modelbuilder: ModelBuilder
+
+ # Mainmodule to pass to handlers.
+ var mainmodule: MModule
+
+ init do
+ use("/list", new APIList(model, mainmodule))
+ use("/search", new APISearch(model, mainmodule))
+ use("/random", new APIRandom(model, mainmodule))
+ use("/entity/:id", new APIEntity(model, mainmodule))
+ use("/code/:id", new APIEntityCode(model, mainmodule, modelbuilder))
+ use("/uml/:id", new APIEntityUML(model, mainmodule))
+ end
+end
+
+# Search mentities from a query string.
+#
+# Example: `GET /search?q=Arr`
+class APISearch
+ super APIHandler
+
+ redef fun get(req, res) do
+ var q = req.string_arg("q")
+ if q == null then
+ res.error 400
+ return
+ end
+ var arr = new JsonArray
+ for mentity in view.mentities do
+ if mentity.name.has_prefix(q) then arr.add mentity
+ end
+ res.json arr
+ 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 view.mpackages do mentities.add mentity
+ else if k == "group" then
+ for mentity in view.mgroups do mentities.add mentity
+ else if k == "module" then
+ for mentity in view.mmodules do mentities.add mentity
+ else if k == "class" then
+ for mentity in view.mclasses do mentities.add mentity
+ else if k == "classdef" then
+ for mentity in view.mclassdefs do mentities.add mentity
+ else if k == "property" then
+ for mentity in view.mproperties do mentities.add mentity
+ else if k == "propdef" then
+ for mentity in view.mpropdefs do mentities.add mentity
+ else
+ for mentity in view.mentities do mentities.add mentity
+ end
+ return mentities
+ 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 = limit_mentities(req, mentities)
+ var arr = new JsonArray
+ for mentity in mentities do arr.add mentity
+ res.json arr
+ 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 = limit_mentities(req, mentities)
+ mentities = randomize_mentities(req, mentities)
+ var arr = new JsonArray
+ for mentity in mentities do arr.add mentity
+ res.json arr
+ 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.json mentity.api_json(self)
+ end
+end
+
+
+# Return a UML representation of MEntity.
+#
+# Example: `GET /entity/core::Array/uml`
+class APIEntityUML
+ super APIHandler
+
+ redef fun get(req, res) do
+ var mentity = mentity_from_uri(req, res)
+ var dot
+ if mentity isa MClassDef then mentity = mentity.mclass
+ if mentity isa MClass then
+ var uml = new UMLModel(view, mainmodule)
+ dot = uml.generate_class_uml.write_to_string
+ else if mentity isa MModule then
+ var uml = new UMLModel(view, mentity)
+ dot = uml.generate_package_uml.write_to_string
+ else
+ res.error 404
+ return
+ end
+ res.send render_svg(dot)
+ end
+
+ # Render a `dot` string as a svg image.
+ fun render_svg(dot: String): String do
+ var proc = new ProcessDuplex("dot", "-Tsvg")
+ var svg = proc.write_and_read(dot)
+ proc.close
+ proc.wait
+ return svg
+ end
+end
+
+# Return the source code of MEntity.
+#
+# Example: `GET /entity/core::Array/code`
+class APIEntityCode
+ super APIHandler
+
+ # Modelbuilder used to access sources.
+ var modelbuilder: ModelBuilder
+
+ 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.error 404
+ return
+ end
+ res.send source
+ end
+
+ # Highlight `mentity` source code.
+ private fun render_source(mentity: MEntity): nullable HTMLTag do
+ var node = modelbuilder.mentity2node(mentity)
+ if node == null then return null
+ var hl = new HighlightVisitor
+ hl.enter_visit node
+ return hl.html
+ end
+end
module web
import web_actions
+import model_api
# Display the tree of all loaded mentities.
class TreeAction
- super ModelAction
+ super ModelHandler
redef fun get(req, res) do
var model = init_model_view(req)
end
end
-# Display the list of mentities matching `namespace`.
-class SearchAction
- super ModelAction
-
- # TODO handle more than full namespaces.
- redef fun get(req, res) do
- var namespace = req.param("namespace")
- var model = init_model_view(req)
- var mentity = find_mentity(model, namespace)
- if mentity == null then
- res.error(404)
- return
- end
- if req.is_json_asked then
- res.json(mentity.to_json)
- return
- end
- var view = new HtmlResultPage(namespace or else "null", [mentity])
- res.send_view(view)
- end
-end
-
-# Display a MEntity source code.
-class CodeAction
- super ModelAction
-
- # Modelbuilder used to access sources.
- var modelbuilder: ModelBuilder
-
- redef fun get(req, res) do
- var namespace = req.param("namespace")
- var model = init_model_view(req)
- var mentity = find_mentity(model, namespace)
- if mentity == null then
- res.error(404)
- return
- end
- var view = new HtmlSourcePage(modelbuilder, mentity)
- res.send_view(view)
- end
-end
-
# Display the doc of a MEntity.
class DocAction
- super ModelAction
+ super ModelHandler
# Modelbuilder used to access sources.
var modelbuilder: ModelBuilder
res.error(404)
return
end
- if req.is_json_asked then
- res.json(mentity.to_json)
- return
- end
-
var view = new HtmlDocPage(modelbuilder, mentity)
res.send_view(view)
end
end
-
-# Return an UML diagram for `namespace`.
-class UMLDiagramAction
- super ModelAction
-
- # Mainmodule used for hierarchy flattening.
- var mainmodule: MModule
-
- redef fun get(req, res) do
- var namespace = req.param("namespace")
- var model = init_model_view(req)
- var mentity = find_mentity(model, namespace)
- if mentity == null then
- res.error(404)
- return
- end
-
- var dot
- if mentity isa MClassDef then mentity = mentity.mclass
- if mentity isa MClass then
- var uml = new UMLModel(model, mainmodule)
- dot = uml.generate_class_uml.write_to_string
- else if mentity isa MModule then
- var uml = new UMLModel(model, mentity)
- dot = uml.generate_package_uml.write_to_string
- else
- res.error(404)
- return
- end
- var view = new HtmlDotPage(dot, mentity.as(not null).html_name)
- res.send_view(view)
- end
-end
-
-# Return a random list of MEntities.
-class RandomAction
- super ModelAction
-
- redef fun get(req, res) do
- var n = req.int_arg("n") or else 10
- var k = req.string_arg("k") or else "modules"
- var model = init_model_view(req)
- var mentities: Array[MEntity]
- if k == "modules" then
- mentities = model.mmodules.to_a
- else if k == "classdefs" then
- mentities = model.mclassdefs.to_a
- else
- mentities = model.mpropdefs.to_a
- end
- mentities.shuffle
- mentities = mentities.sub(0, n)
- if req.is_json_asked then
- var json = new JsonArray
- for mentity in mentities do
- json.add mentity.to_json
- end
- res.json(json)
- return
- end
- var view = new HtmlResultPage("random", mentities)
- res.send_view(view)
- end
-end
import model::model_views
import model::model_json
+import doc_down
import popcorn
# Specific nitcorn Action that uses a Model
-class ModelAction
+class ModelHandler
super Handler
# Model to use.
var model: Model
+ # MModule used to flatten model.
+ var mainmodule: MModule
+
# Find the MEntity ` with `full_name`.
fun find_mentity(model: ModelView, full_name: nullable String): nullable MEntity do
if full_name == null then return null
fun send_view(view: NitView, status: nullable Int) do send(view.render, status)
end
-redef class HttpRequest
- # Does the client asked for a json formatted response?
- #
- # Checks the URL get parameter `?json=true`.
- fun is_json_asked: Bool do return bool_arg("json") or else false
+redef class MEntity
+
+ # URL to `self` within the web interface.
+ fun web_url: String is abstract
+
+ # URL to `self` within the JSON api.
+ fun api_url: String do return "/api/entity/" / full_name
+
+ redef fun json do
+ var obj = super
+ obj["web_url"] = web_url
+ obj["api_url"] = api_url
+ return obj
+ end
+
+ # Get the full json repesentation of `self` with MEntityRefs resolved.
+ fun api_json(handler: ModelHandler): JsonObject do return json
+end
+
+redef class MEntityRef
+ redef fun json do
+ var obj = super
+ obj["web_url"] = mentity.web_url
+ obj["api_url"] = mentity.api_url
+ obj["name"] = mentity.name
+ obj["mdoc"] = mentity.mdoc_or_fallback
+ obj["visibility"] = mentity.visibility
+ obj["location"] = mentity.location
+ var modifiers = new JsonArray
+ for modifier in mentity.collect_modifiers do
+ modifiers.add modifier
+ end
+ obj["modifiers"] = modifiers
+ return obj
+ end
+end
+
+redef class MDoc
+
+ # Add doc down processing
+ redef fun json do
+ var obj = super
+ obj["synopsis"] = synopsis
+ obj["documentation"] = documentation
+ obj["comment"] = comment
+ obj["html_synopsis"] = html_synopsis.write_to_string
+ obj["html_documentation"] = html_documentation.write_to_string
+ obj["html_comment"] = html_comment.write_to_string
+ return obj
+ end
+end
+
+redef class MPackage
+ redef var web_url = "/package/{full_name}" is lazy
+end
+
+redef class MGroup
+ redef var web_url = "/group/{full_name}" is lazy
+end
+
+redef class MModule
+ redef var web_url = "/module/{full_name}" is lazy
+
+ redef fun api_json(handler) do
+ var obj = super
+ obj["intro_mclassdefs"] = to_mentity_refs(collect_intro_mclassdefs(private_view))
+ obj["redef_mclassdefs"] = to_mentity_refs(collect_redef_mclassdefs(private_view))
+ obj["imports"] = to_mentity_refs(in_importation.direct_greaters)
+ return obj
+ end
+end
+
+redef class MClass
+ redef var web_url = "/class/{full_name}" is lazy
+
+ redef fun api_json(handler) do
+ var obj = super
+ obj["all_mproperties"] = to_mentity_refs(collect_accessible_mproperties(private_view))
+ obj["intro_mproperties"] = to_mentity_refs(collect_intro_mproperties(private_view))
+ obj["redef_mproperties"] = to_mentity_refs(collect_redef_mproperties(private_view))
+ var poset = hierarchy_poset(handler.mainmodule, private_view)
+ obj["parents"] = to_mentity_refs(poset[self].direct_greaters)
+ return obj
+ end
+end
+
+redef class MClassDef
+ redef var web_url = "/classdef/{full_name}" is lazy
+
+ redef fun json do
+ var obj = super
+ obj["intro"] = to_mentity_ref(mclass.intro)
+ obj["mpackage"] = to_mentity_ref(mmodule.mpackage)
+ return obj
+ end
+
+ redef fun api_json(handler) do
+ var obj = super
+ obj["intro_mpropdefs"] = to_mentity_refs(collect_intro_mpropdefs(private_view))
+ obj["redef_mpropdefs"] = to_mentity_refs(collect_redef_mpropdefs(private_view))
+ return obj
+ end
+end
+
+redef class MProperty
+ redef var web_url = "/property/{full_name}" is lazy
+
+ redef fun json do
+ var obj = super
+ obj["intro_mclass"] = to_mentity_ref(intro_mclassdef.mclass)
+ obj["mpackage"] = to_mentity_ref(intro_mclassdef.mmodule.mpackage)
+ return obj
+ end
+end
+
+redef class MPropDef
+ redef var web_url = "/propdef/{full_name}" is lazy
+
+ redef fun json do
+ var obj = super
+ obj["intro"] = to_mentity_ref(mproperty.intro)
+ obj["intro_mclassdef"] = to_mentity_ref(mproperty.intro.mclassdef)
+ obj["mmodule"] = to_mentity_ref(mclassdef.mmodule)
+ obj["mgroup"] = to_mentity_ref(mclassdef.mmodule.mgroup)
+ obj["mpackage"] = to_mentity_ref(mclassdef.mmodule.mpackage)
+ return obj
+ end
+end
+
+redef class MClassType
+ redef var web_url = mclass.web_url is lazy
+end
+
+redef class MNullableType
+ redef var web_url = mtype.web_url is lazy
+end
+
+redef class MParameterType
+ redef var web_url = mclass.web_url is lazy
+end
+
+redef class MVirtualType
+ redef var web_url = mproperty.web_url is lazy
end
end
end
-# Display a search results list.
-class HtmlResultPage
- super NitView
-
- # Initial query.
- var query: String
-
- # Result set
- var results: Array[MEntity]
-
- redef fun render do
- var tpl = new Template
- tpl.add new Header(1, "Results for {query}")
- if results.is_empty then
- tpl.add "<p>No result for {query}.<p>"
- return tpl
- end
- var list = new UnorderedList
- for mentity in results do
- var link = mentity.html_link
- link.text = mentity.html_full_name
- list.add_li new ListItem(link)
- end
- tpl.add list
- return tpl
- end
-end
-
# Display the source for each mentities
class HtmlSourcePage
super NitView
return tpl
end
end
-
-# Display the source for each mentities
-class HtmlDotPage
- super NitView
-
- # Dot to process.
- var dot: Text
-
- # Page title.
- var title: String
-
- redef fun render do
- var tpl = new Template
- tpl.add new Header(1, title)
- tpl.add render_dot
- return tpl
- end
-
- private fun render_dot: String do
- var proc = new ProcessDuplex("dot", "-Tsvg", "-Tcmapx")
- var svg = proc.write_and_read(dot)
- proc.close
- proc.wait
- return svg
- end
-end