# Depth-first path in the serialized object tree.
private var path = new Array[Map[String, nullable Object]]
+ # Names of the attributes from the root to the object currently being deserialized
+ var attributes_path = new Array[String]
+
# Last encountered object reference id.
#
# See `id_to_object`.
var value = current[name]
- return convert_object(value)
+ attributes_path.add name
+ var res = convert_object(value)
+ attributes_path.pop
+ return res
end
# This may be called multiple times by the same object from constructors
return convert_object(root)
end
- # User customizable heuristic to get the name of the Nit class to deserialize `json_object`
+ # User customizable heuristic to infer the name of the Nit class to deserialize `json_object`
#
# This method is called only when deserializing an object without the metadata `__class`.
- # Return the class name as a `String` when it can be inferred.
- # Return `null` when the class name cannot be found.
+ # Use the content of `json_object` to identify what Nit class it should be deserialized into.
+ # Or use `self.attributes_path` indicating where the deserialized object will be stored,
+ # is is less reliable as some objects don't have an associated attribute:
+ # the root/first deserialized object and collection elements.
+ #
+ # Return the class name as a `String` when it can be inferred,
+ # or `null` when the class name cannot be found.
#
# If a valid class name is returned, `json_object` will then be deserialized normally.
# So it must contain the attributes of the corresponding class, as usual.
# serialize
#
# var error: String
+ # var related_data: MyData
# end
#
# class MyJsonDeserializer
#
# redef fun class_name_heuristic(json_object)
# do
+ # # Infer the Nit class from the content of the JSON object.
# if json_object.keys.has("error") then return "MyError"
# if json_object.keys.has("data") then return "MyData"
+ #
+ # # Infer the Nit class from the attribute where it will be stored.
+ # # This line duplicates a previous line, and would only apply when
+ # # `MyData` is within a `MyError`.
+ # if attributes_path.not_empty and attributes_path.last == "related_data" then return "MyData"
+ #
# return null
# end
# end
#
- # var json = """{"data": "some other data"}"""
+ # var json = """{"data": "some data"}"""
# var deserializer = new MyJsonDeserializer(json)
# var deserialized = deserializer.deserialize
# assert deserialized isa MyData
#
- # json = """{"error": "some error message"}"""
+ # json = """{"error": "some error message",
+ # "related_data": {"data": "some other data"}"""
# deserializer = new MyJsonDeserializer(json)
# deserialized = deserializer.deserialize
# assert deserialized isa MyError
# Basic usage example:
# ~~~~
# class MyAction
-# super Action
-#
-# redef fun answer(http_request, turi)
-# do
-# var response = new HttpResponse(200)
-# response.body = """
-# <!DOCTYPE html>
-# <head>
-# <meta charset="utf-8">
-# <title>Hello World</title>
-# </head>
-# <body>
-# <p>Hello World</p>
-# </body>
-# </html>"""
-# return response
-# end
+# super Action
+#
+# redef fun answer(http_request, turi)
+# do
+# var response = new HttpResponse(200)
+# response.body = """
+# <!DOCTYPE html>
+# <head>
+# <meta charset="utf-8">
+# <title>Hello World</title>
+# </head>
+# <body>
+# <p>Hello World</p>
+# </body>
+# </html>"""
+# return response
+# end
# end
#
-# var vh = new VirtualHost("localhost:80")
+# # Listen to port 8080 on all interfaces
+# var vh = new VirtualHost("0.0.0.0:8080")
#
# # Serve index.html with our custom handler
# vh.routes.add new Route("/index.html", new MyAction)
redef fun add(e)
do
super
- var config = vh.server_config
- if config != null then sys.listen_on(e, config.factory)
+ var config = virtual_host.server_config
+ if config != null then register_and_listen(e, config)
+ end
+
+ # Indirection to `listen_on` and check if this targets all addresses
+ private fun register_and_listen(e: Interface, config: ServerConfig)
+ do
+ listen_on(e, config.factory)
+ if e.name == "0.0.0.0" or e.name == "::0" then config.default_virtual_host = virtual_host
end
# TODO remove
redef fun add(e)
do
super
- for i in e.interfaces do sys.listen_on(i, config.factory)
+ for i in e.interfaces do e.interfaces.register_and_listen(i, config)
end
# TODO remove
var virtual_hosts = new VirtualHosts(self)
# Default `VirtualHost` to respond to requests not handled by any of the `virtual_hosts`
- var default_virtual_host: nullable VirtualHost = null
+ var default_virtual_host: nullable VirtualHost = null is writable
end
# A `VirtualHost` configuration
super Array[Interface]
# Back reference to the associtated `VirtualHost`
- var vh: VirtualHost
+ var virtual_host: VirtualHost
# Add an `Interface` described by `text` formatted as `interface.name.com:port`
fun add_from_string(text: String)
# Abstract deserialization service
#
-# After initialization of one of its sub-classes, call `deserialize`
+# The main service is `deserialize`.
abstract class Deserializer
- # Main method of this class, returns a Nit object
+ # Deserialize and return an object, storing errors in the attribute `errors`
+ #
+ # This method behavior varies according to the implementation engines.
fun deserialize: nullable Object is abstract
- # Internal method to be implemented by sub-classes
+ # Deserialize the attribute with `name` from the object open for deserialization
+ #
+ # Internal method to be implemented by the engines.
fun deserialize_attribute(name: String): nullable Object is abstract
- # Internal method called by objects in creation,
- # to be implemented by sub-classes
+ # Register a newly allocated object (even if not completely built)
+ #
+ # Internal method called by objects in creation, to be implemented by the engines.
fun notify_of_creation(new_object: Object) is abstract
# Deserialize the next available object as an instance of `class_name`
#
- # Returns the deserialized object on success, aborts on error.
+ # Return the deserialized object on success and
+ # record in `errors` if `class_name` is unknown.
#
# This method should be redefined for each custom subclass of `Serializable`.
# All refinement should look for a precise `class_name` and call super
# Runs a webserver based on nitcorn that render things from model.
module nitweb
+import popcorn::pop_config
import frontend
import web
redef class ToolContext
- # Host name to bind on.
+ # Path to app config file.
+ var opt_config = new OptionString("Path to app config file", "--config")
+
+ # Host name to bind on (will overwrite the config one).
var opt_host = new OptionString("Host to bind the server on", "--host")
- # Port number to bind on.
- var opt_port = new OptionInt("Port number to use", 3000, "--port")
+ # Port number to bind on (will overwrite the config one).
+ var opt_port = new OptionInt("Port number to use", -1, "--port")
# Web rendering phase.
var webphase: Phase = new NitwebPhase(self, null)
init do
super
- option_context.add_option(opt_host, opt_port)
+ option_context.add_option(opt_config, opt_host, opt_port)
end
end
# Phase that builds the model and wait for http request to serve pages.
private class NitwebPhase
super Phase
- redef fun process_mainmodule(mainmodule, mmodules)
- do
- var model = mainmodule.model
- var modelbuilder = toolcontext.modelbuilder
- # Build catalog
+ # Build the nitweb config from `toolcontext` options.
+ fun build_config(toolcontext: ToolContext, mainmodule: MModule): NitwebConfig do
+ var config_file = toolcontext.opt_config.value
+ if config_file == null then config_file = "nitweb.ini"
+ var config = new NitwebConfig(
+ config_file,
+ toolcontext.modelbuilder.model,
+ mainmodule,
+ toolcontext.modelbuilder)
+ var opt_host = toolcontext.opt_host.value
+ if opt_host != null then config["app.host"] = opt_host
+ var opt_port = toolcontext.opt_port.value
+ if opt_port >= 0 then config["app.port"] = opt_port.to_s
+ return config
+ end
+
+ # Build the nit catalog used in homepage.
+ fun build_catalog(model: Model, modelbuilder: ModelBuilder): Catalog do
var catalog = new Catalog(modelbuilder)
for mpackage in model.mpackages do
catalog.deps.add_node(mpackage)
catalog.git_info(mpackage)
catalog.package_page(mpackage)
end
+ return catalog
+ end
- # Prepare mongo connection
- var mongo = new MongoClient("mongodb://localhost:27017/")
- var db = mongo.database("nitweb")
- var collection = db.collection("stars")
-
- # Run the server
- var host = toolcontext.opt_host.value or else "localhost"
- var port = toolcontext.opt_port.value
+ redef fun process_mainmodule(mainmodule, mmodules)
+ do
+ var model = mainmodule.model
+ var modelbuilder = toolcontext.modelbuilder
+ var config = build_config(toolcontext, mainmodule)
+ var catalog = build_catalog(model, modelbuilder)
var app = new App
app.use_before("/*", new RequestClock)
- app.use("/api", new APIRouter(model, modelbuilder, mainmodule, catalog, collection))
+ app.use("/api", new NitwebAPIRouter(config, catalog))
app.use("/*", new StaticHandler(toolcontext.share_dir / "nitweb", "index.html"))
app.use_after("/*", new ConsoleLog)
- app.listen(host, port.to_i)
+ app.listen(config.app_host, config.app_port)
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
+class NitwebAPIRouter
+ super APIRouter
# Catalog to pass to handlers.
var catalog: Catalog
- # Mongo collection used to store ratings.
- var collection: MongoCollection
-
init do
- use("/catalog", new APICatalogRouter(model, mainmodule, catalog))
- 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))
- use("/linearization/:id", new APIEntityLinearization(model, mainmodule))
- use("/defs/:id", new APIEntityDefs(model, mainmodule))
- use("/feedback/", new APIFeedbackRouter(model, mainmodule, collection))
- use("/inheritance/:id", new APIEntityInheritance(model, mainmodule))
- use("/graph/", new APIGraphRouter(model, mainmodule))
- use("/docdown/", new APIDocdown(model, mainmodule, modelbuilder))
- use("/metrics/", new APIMetricsRouter(model, mainmodule))
+ use("/catalog", new APICatalogRouter(config, catalog))
+ use("/list", new APIList(config))
+ use("/search", new APISearch(config))
+ use("/random", new APIRandom(config))
+ use("/entity/:id", new APIEntity(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("/feedback/", new APIFeedbackRouter(config))
+ use("/inheritance/:id", new APIEntityInheritance(config))
+ use("/graph/", new APIGraphRouter(config))
+ use("/docdown/", new APIDocdown(config))
+ use("/metrics/", new APIMetricsRouter(config))
end
end
var toolcontext = new ToolContext
var tpl = new Template
tpl.add "Usage: nitweb [OPTION]... <file.nit>...\n"
-tpl.add "Run a webserver based on nitcorn that serve pages about model."
+tpl.add "Run a webserver based on nitcorn that serves pages about model."
toolcontext.tooldescription = tpl.write_to_string
# process options
# Group all api handlers in one router.
class APICatalogRouter
- super Router
-
- # Model to pass to handlers.
- var model: Model
-
- # Mainmodule to pass to handlers.
- var mainmodule: MModule
+ super APIRouter
# Catalog to pass to handlers.
var catalog: Catalog
init do
- use("/highlighted", new APICatalogHighLighted(model, mainmodule, catalog))
- use("/required", new APICatalogMostRequired(model, mainmodule, catalog))
- use("/bytags", new APICatalogByTags(model, mainmodule, catalog))
- use("/contributors", new APICatalogContributors(model, mainmodule, catalog))
- use("/stats", new APICatalogStats(model, mainmodule, catalog))
+ use("/highlighted", new APICatalogHighLighted(config, catalog))
+ use("/required", new APICatalogMostRequired(config, catalog))
+ use("/bytags", new APICatalogByTags(config, catalog))
+ use("/contributors", new APICatalogContributors(config, catalog))
+ use("/stats", new APICatalogStats(config, catalog))
end
end
redef fun get(req, res) do
var obj = new JsonObject
- obj["packages"] = model.mpackages.length
+ obj["packages"] = config.model.mpackages.length
obj["maintainers"] = catalog.maint2proj.length
obj["contributors"] = catalog.contrib2proj.length
obj["modules"] = catalog.mmodules.sum
redef fun get(req, res) do
if catalog.deps.not_empty then
var reqs = new Counter[MPackage]
- for p in model.mpackages do
+ for p in config.model.mpackages do
reqs[p] = catalog.deps[p].smallers.length - 1
end
res.json list_best(reqs)
class APIDocdown
super APIHandler
- # Modelbuilder used by the commands
- var modelbuilder: ModelBuilder
-
# Specific Markdown processor to use within Nitweb
var md_processor: MarkdownProcessor is lazy do
var proc = new MarkdownProcessor
- proc.emitter.decorator = new NitwebDecorator(view, modelbuilder)
+ proc.emitter.decorator = new NitwebDecorator(view, config.modelbuilder)
return proc
end
import web_base
import mongodb
-# Group all api handlers in one router
-class APIFeedbackRouter
- super Router
+redef class NitwebConfig
+
+ # MongoDB uri used for data persistence.
+ #
+ # * key: `mongo.uri`
+ # * default: `mongodb://localhost:27017/`
+ var mongo_uri: String is lazy do
+ return value_or_default("mongo.uri", "mongodb://localhost:27017/")
+ end
- # Model to pass to handlers
- var model: Model
+ # MongoDB DB used for data persistence.
+ #
+ # * key: `mongo.db`
+ # * default: `nitweb`
+ var mongo_db: String is lazy do return value_or_default("mongo.db", "nitweb")
- # Mainmodule to pass to handlers
- var mainmodule: MModule
+ # Mongo instance
+ var mongo: MongoClient is lazy do return new MongoClient(mongo_uri)
- # Mongo collection used to store ratings
- var collection: MongoCollection
+ # Database instance
+ var db: MongoDb is lazy do return mongo.database(mongo_db)
+
+ # MongoDB collection used to store stars.
+ var stars: MongoCollection is lazy do return db.collection("stars")
+end
+
+# Group all api handlers in one router
+class APIFeedbackRouter
+ super APIRouter
init do
- use("/stars/:id", new APIStars(model, mainmodule, collection))
+ use("/stars/:id", new APIStars(config))
end
end
class APIStars
super APIHandler
- # Collection used to store ratings
- var collection: MongoCollection
-
redef fun get(req, res) do
var mentity = mentity_from_uri(req, res)
if mentity == null then
end
var val = new MEntityRating(mentity.full_name, rating, get_time)
- collection.insert(val.json)
+ config.stars.insert(val.json)
res.json mentity_ratings(mentity)
end
var req = new JsonObject
req["mentity"] = mentity.full_name
- var rs = collection.find_all(req)
+ var rs = config.stars.find_all(req)
for r in rs do ratings.ratings.add new MEntityRating.from_json(r)
return ratings
end
# Group all api handlers in one router.
class APIGraphRouter
- super Router
-
- # Model to pass to handlers.
- var model: Model
-
- # Mainmodule to pass to handlers.
- var mainmodule: MModule
+ super APIRouter
init do
- use("/inheritance/:id", new APIInheritanceGraph(model, mainmodule))
+ use("/inheritance/:id", new APIInheritanceGraph(config))
end
end
# Group all api handlers in one router.
class APIMetricsRouter
- super Router
-
- # Model to pass to handlers.
- var model: Model
-
- # Mainmodule to pass to handlers.
- var mainmodule: MModule
+ super APIRouter
init do
- use("/structural/:id", new APIStructuralMetrics(model, mainmodule))
+ use("/structural/:id", new APIStructuralMetrics(config))
end
end
super APIHandler
private fun mclasses_metrics: MetricSet do
+ var mainmodule = config.mainmodule
var metrics = new MetricSet
metrics.register(new CNOA(mainmodule, view))
metrics.register(new CNOP(mainmodule, view))
end
private fun mmodules_metrics: MetricSet do
+ var mainmodule = config.mainmodule
var metrics = new MetricSet
metrics.register(new MNOA(mainmodule, view))
metrics.register(new MNOP(mainmodule, view))
res.error 404
return
end
- var lin = mentity.collect_linearization(mainmodule)
+ var lin = mentity.collect_linearization(config.mainmodule)
if lin == null then
res.error 404
return
var dot
if mentity isa MClassDef then mentity = mentity.mclass
if mentity isa MClass then
- var uml = new UMLModel(view, mainmodule)
+ var uml = new UMLModel(view, config.mainmodule)
dot = uml.generate_class_uml.write_to_string
else if mentity isa MModule then
var uml = new UMLModel(view, mentity)
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
# Highlight `mentity` source code.
private fun render_source(mentity: MEntity): nullable HTMLTag do
- var node = modelbuilder.mentity2node(mentity)
+ var node = config.modelbuilder.mentity2node(mentity)
if node == null then return null
var hl = new HighlightVisitor
hl.enter_visit node
import model::model_json
import doc_down
import popcorn
+import popcorn::pop_config
-# Specific nitcorn Action that uses a Model
-class ModelHandler
- super Handler
+# Nitweb config file.
+class NitwebConfig
+ super AppConfig
# Model to use.
var model: Model
# MModule used to flatten model.
var mainmodule: MModule
+ # Modelbuilder used to access sources.
+ var modelbuilder: ModelBuilder
+end
+
+# Specific nitcorn Action that uses a Model
+class ModelHandler
+ 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
# Init the model view from the `req` uri parameters.
fun init_model_view(req: HttpRequest): ModelView do
- var view = new ModelView(model)
+ var view = new ModelView(config.model)
var show_private = req.bool_arg("private") or else false
if not show_private then view.min_visibility = protected_visibility
#
# So we can cache the model view.
var view: ModelView is lazy do
- var view = new ModelView(model)
+ var view = new ModelView(config.model)
view.min_visibility = private_visibility
view.include_fictive = true
view.include_empty_doc = true
end
end
+# A Rooter dedicated to APIHandlers.
+class APIRouter
+ super Router
+
+ # App config.
+ var config: NitwebConfig
+end
+
redef class MEntity
# URL to `self` within the web interface.
Usage: nitweb [OPTION]... <file.nit>...
-Run a webserver based on nitcorn that serve pages about model.
+Run a webserver based on nitcorn that serves pages about model.
Use --help for help