Merge: Refactorize nitunit
authorJean Privat <jean@pryen.org>
Sun, 22 May 2016 12:56:17 +0000 (08:56 -0400)
committerJean Privat <jean@pryen.org>
Sun, 22 May 2016 12:56:17 +0000 (08:56 -0400)
Future improvement of nitunit require a saner codebase.
This PR does not bring a lot of features but propose instead some code improvements.

Summary:

* a new class UnitTest to factorize DocUnit and TestCase
* DocUnit are created, with its metadata, while discovered
* DocUnit do not enclose a XML node to fill but will generate one with to_xml.
* Easter egg: precise locations are included in the DocUnits. Each line of *collected* code can be located back to the original source code. This feature is not really used yet, except to locate the docunit itself.

The last commits are divided in order to make the reviewing more easy. I tought that is was better than a big stashed change.

Future PR will target usability since unit-tests are now reified in a common way on both sides (docunits and testsuites).

Pull-Request: #2114
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>

24 files changed:
contrib/asteronits/Makefile
contrib/tinks/src/client/tinks_vr.nit [new file with mode: 0644]
lib/gamnit/depth/depth_core.nit
lib/gamnit/depth/more_materials.nit
lib/gamnit/depth/more_meshes.nit
lib/gamnit/depth/more_models.nit
lib/gamnit/display.nit
lib/gamnit/display_android.nit
lib/gamnit/display_linux.nit
lib/gamnit/egl.nit
lib/gamnit/limit_fps.nit
lib/gamnit/programs.nit
lib/gamnit/texture_atlas_parser.nit [moved from contrib/asteronits/src/texture_atlas_parser.nit with 98% similarity]
lib/gamnit/textures.nit
lib/geometry/polygon.nit
misc/docker/Dockerfile
misc/docker/README.md [new file with mode: 0644]
misc/docker/hello/Dockerfile [new file with mode: 0644]
misc/docker/hello/src/hello.nit [new file with mode: 0644]
src/nitweb.nit
src/platform/app_annotations.nit
src/web/web_actions.nit
src/web/web_base.nit
src/web/web_views.nit

index f110c83..a5e910a 100644 (file)
@@ -6,17 +6,17 @@ all: bin/asteronits
 bin/asteronits: $(shell ${NITLS} -M src/asteronits.nit linux) ${NITC} pre-build
        ${NITC} src/asteronits.nit -m linux -o $@
 
-bin/texture_atlas_parser: src/texture_atlas_parser.nit
-       ${NITC} src/texture_atlas_parser.nit -o $@
+bin/texture_atlas_parser: ../../lib/gamnit/texture_atlas_parser.nit
+       ${NITC} ../../lib/gamnit/texture_atlas_parser.nit -o $@
 
 src/controls.nit: art/controls.svg
        make -C ../inkscape_tools/
        ../inkscape_tools/bin/svg_to_png_and_nit art/controls.svg -a assets/ -s src/ -x 2.0 -g
 
-src/spritesheet_city.nit: bin/texture_atlas_parser
+src/spritesheet.nit: bin/texture_atlas_parser
        bin/texture_atlas_parser art/sheet.xml --dir src/ -n spritesheet
 
-pre-build: src/controls.nit src/spritesheet_city.nit
+pre-build: src/controls.nit src/spritesheet.nit
 
 check: bin/asteronits
        NIT_TESTING=true bin/asteronits
diff --git a/contrib/tinks/src/client/tinks_vr.nit b/contrib/tinks/src/client/tinks_vr.nit
new file mode 100644 (file)
index 0000000..0e2a300
--- /dev/null
@@ -0,0 +1,22 @@
+# 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.
+
+# VR mode for Android with Google Cardboard
+#
+# This version is not playable and very laggy as it is not modified
+# or optimized in any way for VR.
+# This module is made available as a minimal example of a VR game.
+module tinks_vr
+
+import gamnit::vr
index 03a3970..f1e5803 100644 (file)
@@ -103,6 +103,9 @@ class Mesh
        # Coordinates on the texture per vertex
        var texture_coords = new Array[Float] is lazy, writable
 
+       # `GLDrawMode` used to display this mesh, defaults to `gl_TRIANGLES`
+       fun draw_mode: GLDrawMode do return gl_TRIANGLES
+
        # Create an UV sphere of `radius` with `n_meridians` and `n_parallels`
        init uv_sphere(radius: Float, n_meridians, n_parallels: Int)
        do
index 613ff1a..b77c85e 100644 (file)
@@ -75,9 +75,9 @@ class SmoothMaterial
 
                # Execute draw
                if mesh.indices.is_empty then
-                       glDrawArrays(gl_TRIANGLES, 0, mesh.vertices.length/3)
+                       glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
                else
-                       glDrawElements(gl_TRIANGLES, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
+                       glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
                end
        end
 end
@@ -154,11 +154,13 @@ class TexturedMaterial
                                var xd = sample_used_texture.offset_right - xa
                                var ya = sample_used_texture.offset_top
                                var yd = sample_used_texture.offset_bottom - ya
+                               xd *= 0.999
+                               yd *= 0.999
 
                                var tex_coords = new Array[Float].with_capacity(mesh.texture_coords.length)
                                for i in [0..mesh.texture_coords.length/2[ do
                                        tex_coords[i*2]   = xa + xd * mesh.texture_coords[i*2]
-                                       tex_coords[i*2+1] = ya + yd * mesh.texture_coords[i*2+1]
+                                       tex_coords[i*2+1] = 1.0 - (ya + yd * mesh.texture_coords[i*2+1])
                                end
 
                                program.tex_coord.array(tex_coords, 2)
@@ -180,9 +182,9 @@ class TexturedMaterial
                program.camera.uniform(app.world_camera.position.x, app.world_camera.position.y, app.world_camera.position.z)
 
                if mesh.indices.is_empty then
-                       glDrawArrays(gl_TRIANGLES, 0, mesh.vertices.length/3)
+                       glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
                else
-                       glDrawElements(gl_TRIANGLES, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
+                       glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
                end
        end
 end
@@ -218,9 +220,9 @@ class NormalsMaterial
                program.normal.array(mesh.normals, 3)
 
                if mesh.indices.is_empty then
-                       glDrawArrays(gl_TRIANGLES, 0, mesh.vertices.length/3)
+                       glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
                else
-                       glDrawElements(gl_TRIANGLES, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
+                       glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
                end
        end
 end
index 9f2318a..569973b 100644 (file)
@@ -124,7 +124,8 @@ class Cube
                var d = [1.0, 0.0]
 
                var texture_coords = new Array[Float]
-               for v in [c, d, a, a, d, b] do for i in 6.times do texture_coords.add_all v
+               var face = [a, c, d, a, d, b]
+               for i in 6.times do for v in face do texture_coords.add_all v
                return texture_coords
        end
 
index a126223..c2b9664 100644 (file)
@@ -50,7 +50,7 @@ class ModelAsset
 
                if leaves.is_empty then
                        # Nothing was loaded, use a cube with the default material
-                       var leaf = new LeafModel(new Cube, new SmoothMaterial.default)
+                       var leaf = placeholder_model
                        leaves.add leaf
                end
        end
@@ -168,7 +168,7 @@ private class ModelFromObj
                # Load textures need for these materials
                for name in texture_names do
                        if not asset_textures_by_name.keys.has(name) then
-                               var tex = new GamnitAssetTexture(name)
+                               var tex = new TextureAsset(name)
                                asset_textures_by_name[name] = tex
                        end
                end
@@ -379,8 +379,13 @@ end
 
 redef class Sys
        # Textures loaded from .mtl files for models
-       var asset_textures_by_name = new Map[String, GamnitAssetTexture]
+       var asset_textures_by_name = new Map[String, TextureAsset]
 
        # All instantiated asset models
        var models = new Set[ModelAsset]
+
+       # Blue cube of 1 unit on each side, acting as placeholder for models failing to load
+       #
+       # This model can be freely used by any `Actor` as placeholder or for debugging.
+       var placeholder_model = new LeafModel(new Cube, new SmoothMaterial.default) is lazy
 end
index 69ddb41..6e7c58c 100644 (file)
@@ -44,6 +44,15 @@ class GamnitDisplay
        # Only affects the desktop implementations.
        var show_cursor: Bool = true is writable
 
+       # Number of bits used for the red value in the color buffer
+       fun red_bits: Int do return 8
+
+       # Number of bits used for the green value in the color buffer
+       fun green_bits: Int do return 8
+
+       # Number of bits used for the blue value in the color buffer
+       fun blue_bits: Int do return 8
+
        # Prepare this display
        #
        # The implementation varies per platform.
index c412e9f..e9d1b4a 100644 (file)
@@ -37,7 +37,7 @@ redef class GamnitDisplay
                setup_egl_display native_display
 
                # We need 8 bits per color for selection by color
-               select_egl_config(8, 8, 8, 0, 8, 0, 0)
+               select_egl_config(red_bits, green_bits, blue_bits, 0, 8, 0, 0)
 
                var format = egl_config.attribs(egl_display).native_visual_id
                native_window.set_buffers_geometry(0, 0, format)
@@ -48,7 +48,7 @@ redef class GamnitDisplay
        redef fun close do close_egl
 end
 
-redef class GamnitAssetTexture
+redef class TextureAsset
 
        redef fun load_from_platform
        do
index 8f115e7..1dbee21 100644 (file)
@@ -48,7 +48,7 @@ redef class GamnitDisplay
                setup_egl_display x11_display
 
                if debug_gamnit then print "Setting up EGL context"
-               select_egl_config(8, 8, 8, 8, 8, 0, 0)
+               select_egl_config(red_bits, green_bits, blue_bits, 8, 8, 0, 0)
                setup_egl_context window_handle
        end
 
@@ -95,7 +95,7 @@ redef class GamnitDisplay
        end
 end
 
-redef class GamnitAssetTexture
+redef class TextureAsset
 
        redef fun load_from_platform
        do
index 3fcb8c8..b451362 100644 (file)
@@ -46,14 +46,14 @@ redef class GamnitDisplay
        end
 
        # Select an EGL config
-       protected fun select_egl_config(blue, green, red, alpha, depth, stencil, sample: Int)
+       protected fun select_egl_config(red, green, blue, alpha, depth, stencil, sample: Int)
        do
                var config_chooser = new EGLConfigChooser
                config_chooser.renderable_type_egl
                config_chooser.surface_type_egl
-               config_chooser.blue_size = blue
-               config_chooser.green_size = green
                config_chooser.red_size = red
+               config_chooser.green_size = green
+               config_chooser.blue_size = blue
                if alpha > 0 then config_chooser.alpha_size = alpha
                if depth > 0 then config_chooser.depth_size = depth
                if stencil > 0 then config_chooser.stencil_size = stencil
index a300b54..f16609f 100644 (file)
@@ -29,8 +29,8 @@ redef class App
 
        # Current frame-rate
        #
-       # Updated each 5 seconds.
-       var current_fps = 0.0
+       # Updated each 5 seconds, initialized at the value of `maximum_fps`.
+       var current_fps: Float = maximum_fps is lazy
 
        redef fun frame_full
        do
@@ -47,7 +47,7 @@ redef class App
        private var frame_count = 0
 
        # Deadline used to compute `current_fps`
-       private var frame_count_deadline = 0
+       private var frame_count_deadline = 5
 
        # Check and sleep to maintain a frame-rate bellow `maximum_fps`
        #
index 1a995ef..6859c76 100644 (file)
@@ -428,6 +428,23 @@ abstract class GamnitProgram
                end
        end
 
+       # Diagnose possible problems with the shaders of the program
+       #
+       # Lists to the console inactive uniforms and attributes.
+       # These may not be problematic but they can help to debug the program.
+       fun diagnose
+       do
+               if gl_program == null then compile_and_link
+
+               print "# Diagnose {class_name}"
+               for k,v in uniforms do
+                       if not v.is_active then print "* Uniform {v.name} is inactive"
+               end
+               for k,v in attributes do
+                       if not v.is_active then print "* Attribute {v.name} is inactive"
+               end
+       end
+
        # Attributes of this program organized by name
        #
        # Active attributes are gathered at `compile_and_link`.
similarity index 98%
rename from contrib/asteronits/src/texture_atlas_parser.nit
rename to lib/gamnit/texture_atlas_parser.nit
index 113b887..a0e4d5f 100644 (file)
@@ -44,7 +44,7 @@ var attributes = new Array[String]
 # Insert the first attribute, to load the root texture
 var png_file = "images" / xml_file.basename("xml") + "png"
 attributes.add """
-       var root_texture = new Texture("{{{png_file}}}")"""
+       var root_texture = new TextureAsset("{{{png_file}}}")"""
 
 # Read XML file
 var content = xml_file.to_path.read_all
index 415aa90..d644d60 100644 (file)
@@ -21,7 +21,7 @@ import display
 abstract class Texture
 
        # Prepare a texture located at `path` within the `assets` folder
-       new (path: Text) do return new GamnitAssetTexture(path.to_s)
+       new (path: Text) do return new TextureAsset(path.to_s)
 
        # Root texture of which `self` is derived
        fun root: GamnitRootTexture is abstract
@@ -41,7 +41,7 @@ abstract class Texture
        # OpenGL handle to this texture
        fun gl_texture: Int do return root.gl_texture
 
-       # Prepare a subtexture from this texture
+       # Prepare a subtexture from this texture, from the given pixel offsets
        fun subtexture(left, top, width, height: Numeric): GamnitSubtexture
        do
                # Setup the subtexture
@@ -143,7 +143,7 @@ class GamnitRootTexture
 end
 
 # Texture loaded from the assets folder
-class GamnitAssetTexture
+class TextureAsset
        super GamnitRootTexture
 
        # Path to this texture within the `assets` folder
index 94ad1c9..1be832e 100644 (file)
@@ -441,31 +441,40 @@ fun turn_left(p1, p2, p3: Point[Float]): Bool do
 end
 
 # Split a polygon into triangles
-# Useful for converting a concave polygon into multiple convex ones
-fun triangulate(pts: Array[Point[Float]], results: Array[ConvexPolygon]) do
-       var poly = new Polygon(pts)
-       pts = poly.points
-       recursive_triangulate(pts, results)
+#
+# Useful for converting a concave polygon into multiple convex ones.
+#
+# See: the alternative `triangulate_recursive` uses arrays in-place.
+fun triangulate(points: Array[Point[Float]]): Array[ConvexPolygon]
+do
+       var results = new Array[ConvexPolygon]
+       triangulate_recursive(points.clone, results)
+       return results
 end
 
-private fun recursive_triangulate(pts: Array[Point[Float]], results: Array[ConvexPolygon]) do
-       if pts.length == 3 then
-               results.add(new ConvexPolygon(pts))
+# Split a polygon into triangles using arrays in-place
+#
+# Consumes the content of `points` and add the triangles to `results`.
+#
+# See: the alternative `triangulate` which does not modify `points` and returns a new array.
+fun triangulate_recursive(points: Array[Point[Float]], results: Array[ConvexPolygon]) do
+       if points.length == 3 then
+               results.add(new ConvexPolygon(points))
                return
        end
-       var prev = pts[pts.length - 1]
-       var curr = pts[0]
-       var next = pts[1]
-       for i in [1..pts.length[ do
+       var prev = points[points.length - 1]
+       var curr = points[0]
+       var next = points[1]
+       for i in [1..points.length[ do
                if turn_left(prev, curr, next) then
-                       prev = pts[i-1]
+                       prev = points[i-1]
                        curr = next
-                       if i+1 == pts.length then next = pts[pts.length - 1] else next = pts[i+1]
+                       if i+1 == points.length then next = points[points.length - 1] else next = points[i+1]
                        continue
                end
                var contains = false
                var triangle = new ConvexPolygon(new Array[Point[Float]].with_items(prev, curr, next))
-               for p in pts do
+               for p in points do
                        if p != prev and p != curr and p != next then
                                if triangle.contain(p) then
                                        contains = true
@@ -475,12 +484,12 @@ private fun recursive_triangulate(pts: Array[Point[Float]], results: Array[Conve
                end
                if not contains then
                        results.add(triangle)
-                       pts.remove(curr)
-                       recursive_triangulate(pts, results)
+                       points.remove(curr)
+                       triangulate_recursive(points, results)
                        break
                end
-               prev = pts[i-1]
+               prev = points[i-1]
                curr = next
-               if i+1 == pts.length then next = pts[pts.length - 1] else next = pts[i+1]
+               if i+1 == points.length then next = points[points.length - 1] else next = points[i+1]
        end
 end
index d7a3f49..95995bf 100644 (file)
@@ -29,3 +29,7 @@ RUN git clone https://github.com/nitlang/nit.git /root/nit \
        && strip c_src/nitc bin/nit* \
        && ccache -C \
        && rm -rf .git
+
+ENV NIT_DIR /root/nit
+ENV PATH $NIT_DIR/bin:$PATH
+WORKDIR $NIT_DIR
diff --git a/misc/docker/README.md b/misc/docker/README.md
new file mode 100644 (file)
index 0000000..d255285
--- /dev/null
@@ -0,0 +1,64 @@
+# Supported tags and respective Dockerfile links
+
+* [latest](https://github.com/nitlang/nit/blob/master/misc/docker/Dockerfile)
+* [full](https://github.com/nitlang/nit/blob/master/misc/docker/full/Dockerfile)
+
+## What is Nit?
+
+Nit is an expressive language with a script-like syntax, a friendly type-system and aims at elegance, simplicity and intuitiveness.
+
+Nit has a simple straightforward style and can usually be picked up quickly, particularly by anyone who has programmed before.
+While object-oriented, it allows procedural styles.
+
+More information on
+
+* Website <http://www.nitlanguage.org>
+* Github <https://github.com/nitlang/nit>
+* Chatroom <https://gitter.im/nitlang/nit>
+
+## How to use this image
+
+You can use these images to build then run your programs.
+
+### Experimenting with Nit
+
+~~~
+host$ docker run -ti nitlang/nit
+root@ce9b671dd9fc:/root/nit# nitc examples/hello_world.nit
+root@ce9b671dd9fc:/root/nit# ./hello_world
+hello world
+~~~
+
+### Build and Run Programs
+
+In your Dockerfile, write something like:
+
+~~~Dockerfile
+FROM nitlang/nit
+
+# Create a workdir
+RUN mkdir -p /root/work
+WORKDIR /root/work
+
+# Copy the source code in /root/work/
+COPY . /root/work/
+
+# Compile
+RUN nitc src/hello.nit --dir . \
+       # Clear disk space
+       && ccache -C
+# You can also use a Makefile or any build system you want.
+
+# Run
+CMD ["./hello"]
+~~~
+
+Then, build and execute
+
+~~~
+host$ docker build -t nithello .
+host$ docker run --rm nithello
+hello!
+~~~
+
+See the full example at <https://github.com/nitlang/nit/blob/master/misc/docker/hello/Dockerfile>
diff --git a/misc/docker/hello/Dockerfile b/misc/docker/hello/Dockerfile
new file mode 100644 (file)
index 0000000..c0a018b
--- /dev/null
@@ -0,0 +1,17 @@
+FROM nitlang/nit
+
+# Create a workdir
+RUN mkdir -p /root/work
+WORKDIR /root/work
+
+# Copy the source code in /root/work/
+COPY . /root/work/
+
+# Compile
+RUN nitc src/hello.nit --dir . \
+        # Clear disk space
+        && ccache -C
+# You can also use a Makefile or what you want
+
+# Say what to run
+CMD ["./hello"]
diff --git a/misc/docker/hello/src/hello.nit b/misc/docker/hello/src/hello.nit
new file mode 100644 (file)
index 0000000..b732142
--- /dev/null
@@ -0,0 +1 @@
+print "hello"
index b924ec2..c055ee9 100644 (file)
@@ -47,15 +47,15 @@ private class NitwebPhase
                var host = toolcontext.opt_host.value or else "localhost"
                var port = toolcontext.opt_port.value
 
-               var srv = new NitServer(host, port.to_i)
-               srv.routes.add new Route("/random", new RandomAction(srv, model))
-               srv.routes.add new Route("/doc/:namespace", new DocAction(srv, model, modelbuilder))
-               srv.routes.add new Route("/code/:namespace", new CodeAction(srv, model, modelbuilder))
-               srv.routes.add new Route("/search/:namespace", new SearchAction(srv, model))
-               srv.routes.add new Route("/uml/:namespace", new UMLDiagramAction(srv, model, mainmodule))
-               srv.routes.add new Route("/", new TreeAction(srv, model))
+               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))
 
-               srv.listen
+               app.listen(host, port.to_i)
        end
 end
 
index 62fbbb0..1d60f15 100644 (file)
@@ -88,8 +88,6 @@ redef class AAnnotation
                        return ""
                else
                        for arg in args do
-                               var format_error = "Syntax Eror: `{name}` expects its arguments to be of type Int or a call to `git_revision`."
-
                                var value
                                value = arg.as_int
                                if value != null then
@@ -107,7 +105,13 @@ redef class AAnnotation
                                        # Get Git short revision
                                        var proc = new ProcessReader("git", "rev-parse", "--short", "HEAD")
                                        proc.wait
-                                       assert proc.status == 0
+                                       if proc.status != 0 then
+                                               # Fallback if this is not a git repository or git bins are missing
+                                               version_fields.add "0"
+                                               modelbuilder.warning(self, "git_revision", "Warning: `git_revision` used outside of a git repository or git binaries not available")
+                                               continue
+                                       end
+
                                        var lines = proc.read_all
                                        var revision = lines.split("\n").first
 
@@ -122,6 +126,7 @@ redef class AAnnotation
                                        continue
                                end
 
+                               var format_error = "Syntax Error: `{name}` expects its arguments to be of type Int or a call to `git_revision`."
                                modelbuilder.error(self, format_error)
                                return ""
                        end
index 60d39c2..e9198ae 100644 (file)
@@ -22,10 +22,10 @@ import uml
 class TreeAction
        super ModelAction
 
-       redef fun answer(request, url) do
-               var model = init_model_view(request)
+       redef fun get(req, res) do
+               var model = init_model_view(req)
                var view = new HtmlHomePage(model.to_tree)
-               return render_view(view)
+               res.send_view(view)
        end
 end
 
@@ -34,18 +34,20 @@ class SearchAction
        super ModelAction
 
        # TODO handle more than full namespaces.
-       redef fun answer(request, url) do
-               var namespace = request.param("namespace")
-               var model = init_model_view(request)
+       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
-                       return render_error(404, "No mentity found")
+                       res.error(404)
+                       return
                end
-               if request.is_json_asked then
-                       return render_json(mentity.to_json)
+               if req.is_json_asked then
+                       res.json(mentity.to_json)
+                       return
                end
                var view = new HtmlResultPage(namespace or else "null", [mentity])
-               return render_view(view)
+               res.send_view(view)
        end
 end
 
@@ -56,15 +58,16 @@ class CodeAction
        # Modelbuilder used to access sources.
        var modelbuilder: ModelBuilder
 
-       redef fun answer(request, url) do
-               var namespace = request.param("namespace")
-               var model = init_model_view(request)
+       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
-                       return render_error(404, "No mentity found")
+                       res.error(404)
+                       return
                end
                var view = new HtmlSourcePage(modelbuilder, mentity)
-               return render_view(view)
+               res.send_view(view)
        end
 end
 
@@ -75,16 +78,21 @@ class DocAction
        # Modelbuilder used to access sources.
        var modelbuilder: ModelBuilder
 
-       # TODO handle more than full namespaces.
-       redef fun answer(request, url) do
-               var namespace = request.param("namespace")
-               var model = init_model_view(request)
+       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
-                       return render_error(404, "No mentity found")
+                       res.error(404)
+                       return
+               end
+               if req.is_json_asked then
+                       res.json(mentity.to_json)
+                       return
                end
+
                var view = new HtmlDocPage(modelbuilder, mentity)
-               return render_view(view)
+               res.send_view(view)
        end
 end
 
@@ -95,12 +103,13 @@ class UMLDiagramAction
        # Mainmodule used for hierarchy flattening.
        var mainmodule: MModule
 
-       redef fun answer(request, url) do
-               var namespace = request.param("namespace")
-               var model = init_model_view(request)
+       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
-                       return render_error(404, "No mentity found")
+                       res.error(404)
+                       return
                end
 
                var dot
@@ -112,10 +121,11 @@ class UMLDiagramAction
                        var uml = new UMLModel(model, mentity)
                        dot = uml.generate_package_uml.write_to_string
                else
-                       return render_error(404, "No diagram matching this namespace.")
+                       res.error(404)
+                       return
                end
                var view = new HtmlDotPage(dot, mentity.as(not null).html_name)
-               return render_view(view)
+               res.send_view(view)
        end
 end
 
@@ -123,11 +133,10 @@ end
 class RandomAction
        super ModelAction
 
-       # TODO handle more than full namespaces.
-       redef fun answer(request, url) do
-               var n = request.int_arg("n") or else 10
-               var k = request.string_arg("k") or else "modules"
-               var model = init_model_view(request)
+       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
@@ -138,14 +147,15 @@ class RandomAction
                end
                mentities.shuffle
                mentities = mentities.sub(0, n)
-               if request.is_json_asked then
+               if req.is_json_asked then
                        var json = new JsonArray
                        for mentity in mentities do
                                json.add mentity.to_json
                        end
-                       return render_json(json)
+                       res.json(json)
+                       return
                end
                var view = new HtmlResultPage("random", mentities)
-               return render_view(view)
+               res.send_view(view)
        end
 end
index f37bdb4..258ec60 100644 (file)
@@ -17,77 +17,11 @@ module web_base
 
 import model::model_views
 import model::model_json
-import nitcorn
-
-# Nitcorn server runned by `nitweb`.
-#
-# Usage:
-#
-# ~~~nitish
-# var srv = new NitServer("localhost", 3000)
-# srv.routes.add new Route("/", new MyAction)
-# src.listen
-# ~~~
-class NitServer
-
-       # Host to bind.
-       var host: String
-
-       # Port to use.
-       var port: Int
-
-       # Routes knwon by the server.
-       var routes = new Array[Route]
-
-       # Start listen on `host:port`.
-       fun listen do
-               var iface = "{host}:{port}"
-               print "Launching server on http://{iface}/"
-
-               var vh = new VirtualHost(iface)
-               for route in routes do vh.routes.add route
-
-               var fac = new HttpFactory.and_libevent
-               fac.config.virtual_hosts.add vh
-               fac.run
-       end
-end
-
-# Specific nitcorn Action for nitweb.
-class NitAction
-       super Action
-
-       # Link to the NitServer that runs this action.
-       var srv: NitServer
-
-       # 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
-       end
-
-       # Render a view as a HttpResponse 200.
-       fun render_view(view: NitView): HttpResponse do
-               var response = new HttpResponse(200)
-               response.body = view.render(srv).write_to_string
-               return response
-       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
-       end
-end
+import popcorn
 
 # Specific nitcorn Action that uses a Model
 class ModelAction
-       super NitAction
+       super Handler
 
        # Model to use.
        var model: Model
@@ -116,7 +50,12 @@ 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(srv: NitServer): Writable is abstract
+       fun render: Writable is abstract
+end
+
+redef class HttpResponse
+       # Render a NitView as response.
+       fun send_view(view: NitView, status: nullable Int) do send(view.render, status)
 end
 
 redef class HttpRequest
index 7273187..58016e2 100644 (file)
@@ -27,7 +27,7 @@ class HtmlHomePage
        # Loaded model to display.
        var tree: MEntityTree
 
-       redef fun render(srv) do
+       redef fun render do
                var tpl = new Template
                tpl.add new Header(1, "Loaded model")
                tpl.add tree.html_list
@@ -45,7 +45,7 @@ class HtmlResultPage
        # Result set
        var results: Array[MEntity]
 
-       redef fun render(srv) do
+       redef fun render do
                var tpl = new Template
                tpl.add new Header(1, "Results for {query}")
                if results.is_empty then
@@ -76,7 +76,7 @@ class HtmlSourcePage
        # HiglightVisitor used to hilight the source code
        var hl = new HighlightVisitor
 
-       redef fun render(srv) do
+       redef fun render do
                var tpl = new Template
                tpl.add new Header(1, "Source Code")
                tpl.add render_source
@@ -103,7 +103,7 @@ end
 class HtmlDocPage
        super HtmlSourcePage
 
-       redef fun render(srv) do
+       redef fun render do
                var tpl = new Template
                tpl.add new Header(1, mentity.html_name)
                tpl.add "<p>"
@@ -130,7 +130,7 @@ class HtmlDotPage
        # Page title.
        var title: String
 
-       redef fun render(srv) do
+       redef fun render do
                var tpl = new Template
                tpl.add new Header(1, title)
                tpl.add render_dot