nitdoc: set icons for each kind of cards
[nit.git] / src / web / web_base.nit
index 5d7dbde..c863061 100644 (file)
@@ -17,111 +17,334 @@ module web_base
 
 import model::model_views
 import model::model_json
-import nitcorn
+import doc_down
+import popcorn
+import popcorn::pop_config
+import popcorn::pop_repos
+import popcorn::pop_json
 
-# Nitcorn server runned by `nitweb`.
-#
-# Usage:
+# Nitweb config file.
+class NitwebConfig
+       super AppConfig
+
+       redef fun default_db_name do return "nitweb"
+
+       # Model to use.
+       var model: Model
+
+       # MModule used to flatten model.
+       var mainmodule: MModule
+
+       # Modelbuilder used to access sources.
+       var modelbuilder: ModelBuilder
+
+       # 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
+end
+
+# Specific handler for the nitweb API.
+abstract class APIHandler
+       super Handler
+
+       # App config.
+       var config: NitwebConfig
+
+       # 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
+               return model.mentity_by_full_name(full_name.from_percent_encoding)
+       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.api_error(400, "Expected mentity full name")
+                       return null
+               end
+               var mentity = find_mentity(config.view, id)
+               if mentity == null then
+                       res.api_error(404, "MEntity `{id}` not found")
+               end
+               return mentity
+       end
+end
+
+# A Rooter dedicated to APIHandlers.
+class APIRouter
+       super Router
+
+       # App config
+       var config: NitwebConfig
+end
+
+redef class HttpResponse
+
+       # Return an HTTP error response with `status`
+       #
+       # Like the rest of the API, errors are formated as JSON:
+       # ~~~json
+       # { "status": 404, "message": "Not found" }
+       # ~~~
+       fun api_error(status: Int, message: String) do
+               json(new APIError(status, message), status)
+       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.
 #
-# ~~~nitish
-# var srv = new NitServer("localhost", 3000)
-# srv.routes.add new Route("/", new MyAction)
-# src.listen
-# ~~~
-class NitServer
+# Can be serialized to json.
+class APIError
+       serialize
+
+       # Reponse status
+       var status: Int
+
+       # Response error message
+       var message: String
+end
 
-       # Host to bind.
-       var host: String
+# 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
 
-       # Port to use.
-       var port: Int
+       redef fun to_s do return self.join("")
+       redef fun serialize_to(v) do to_a.serialize_to(v)
+end
 
-       # Routes knwon by the server.
-       var routes = new Array[Route]
+# 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
 
-       # Start listen on `host:port`.
-       fun listen do
-               var iface = "{host}:{port}"
-               print "Launching server on http://{iface}/"
+# 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
 
-               var vh = new VirtualHost(iface)
-               for route in routes do vh.routes.add route
+       # The mentity to link to/
+       var mentity: MEntity
 
-               var fac = new HttpFactory.and_libevent
-               fac.config.virtual_hosts.add vh
-               fac.run
+       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
 
-# Specific nitcorn Action for nitweb.
-class NitAction
-       super Action
+# 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.
+       fun web_url: String do return "/doc/" / full_name
 
-       # Link to the NitServer that runs this action.
-       var srv: NitServer
+       # URL to `self` within the JSON api.
+       fun api_url: String do return "/api/entity/" / full_name
 
-       # Build a custom http response for errors.
-       fun render_error(code: Int, message: String): HttpResponse do
-               var response = new HttpResponse(code)
-               var tpl = new Template
-               tpl.add "<h1>Error {code}</h1>"
-               tpl.add "<pre><code>{message.html_escape}</code></pre>"
-               response.body = tpl.write_to_string
-               return response
+       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
 
-       # Render a view as a HttpResponse 200.
-       fun render_view(view: NitView): HttpResponse do
-               var response = new HttpResponse(200)
-               response.body = view.render.write_to_string
-               return response
+       # 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
 
-       # Return a HttpResponse containing `json`.
-       fun render_json(json: Jsonable): HttpResponse do
-               var response = new HttpResponse(200)
-               response.body = json.to_json
-               return response
+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
 
-# Specific nitcorn Action that uses a Model
-class ModelAction
-       super NitAction
+redef class MPackage
+       redef fun namespace do return new Namespace.from([to_ns_ref])
+end
 
-       # Model to use.
-       var model: Model
+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
 
-       # 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
-               return model.mentity_by_full_name(full_name.from_percent_encoding)
+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
 
-       # Init the model view from the `req` uri parameters.
-       fun init_model_view(req: HttpRequest): ModelView do
-               var view = new ModelView(model)
-               var show_private = req.bool_arg("private") or else false
-               if not show_private then view.min_visibility = protected_visibility
+       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
 
-               view.include_fictive = req.bool_arg("fictive") or else false
-               view.include_empty_doc = req.bool_arg("empty-doc") or else true
-               view.include_test_suite = req.bool_arg("test-suite") or else false
-               view.include_attribute = req.bool_arg("attributes") or else true
+redef class MClass
+       redef fun namespace do
+               return new Namespace.from([intro_mmodule.ns_for(visibility), "::", to_ns_ref: nullable NSEntity])
+       end
+end
 
-               return view
+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
 
-# A NitView is rendered by an action.
-interface NitView
-       # Renders this view and returns something that can be written to a HTTP response.
-       fun render: Writable is abstract
+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
 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 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
+       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
+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
+
+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
 end