redef fun load
do
leaves.add new LeafModel(
- new Mesh.uv_sphere(1.0, 2*n_parallels, n_parallels),
+ new UVSphere(1.0, 2*n_parallels, n_parallels),
new GlobeMaterial.surface)
leaves.add new LeafModel(
- new Mesh.uv_sphere(1.1, 2*n_parallels, n_parallels),
+ new UVSphere(1.1, 2*n_parallels, n_parallels),
new GlobeMaterial.clouds)
leaves.add new LeafModel(
- new Mesh.uv_sphere(1.2, 2*n_parallels, n_parallels),
+ new UVSphere(1.2, 2*n_parallels, n_parallels),
new GlobeMaterial.atmo)
end
end
# All available models
var models: Array[Model] = [
- new LeafModel(new Cube, new SmoothMaterial.default),
- new LeafModel(new Mesh.uv_sphere(4.0, 32, 16), new SmoothMaterial.default),
- new LeafModel(new Mesh.uv_sphere(4.0, 32, 16), new NormalsMaterial),
+ new LeafModel(new Cube, new Material),
+ new LeafModel(new UVSphere(4.0, 32, 16), new Material),
+ new LeafModel(new UVSphere(4.0, 32, 16), new NormalsMaterial),
new Model("models/Tree_01.obj"),
new Model("models/Oak_Fall_01.obj"),
new Model("models/Quandtum_BA-2_v1_1.obj"),
# Task: Align columns
# SEE: <http://rosettacode.org/wiki/Align_columns>
#
-# Use `Text::justify` for the standard library.
+# Uses `Text::justify` from the standard library.
module align_columns
fun aligner(text: String, left: Float)
--- /dev/null
+#!/usr/bin/env nit
+#
+# This file is part of NIT ( http://www.nitlanguage.org ).
+# This program is public domain
+
+# Task: Integer Overflow
+# SEE: <rosettacode.org/wiki/Integer_overflow>
+module integer_overflow
+
+# Nit possed integers of 32-bit name Int32 and unsigned
+# integers of 32-bit.
+# Nit does not recognize overflow.
+
+print "Signed 32-bit:"
+print( -(-2147483647i32 - 1i32) )
+print 2000000000i32 + 2000000000i32
+print -2147483647i32 - 2147483647i32
+print 46341i32 * 46341i32
+#print( (-2147483647i32 - 1i32) / -1i32 )
+
+print "unsigned 32-bit:"
+print -4294967295u32
+print 3000000000u32 + 3000000000u32
+print 2147483647u32 - 4294967295u32
+print 65537u32 * 65537u32
interface SimpleCollection[E]
super RemovableCollection[E]
- # Add an item in a collection.
+ # Add `item` to this collection.
#
# var a = [1,2]
# a.add 3
# Used to inform `self` that the operations are over.
# Specific streams can use this to free some resources.
#
- # Is automatically invoked at the end of `woth` structures.
+ # Is automatically invoked at the end of `with` structures.
#
# call `close` by default.
fun finish do close
return res
end
- # Replace all occurences of `pattern` with `string`
+ # Replace all occurrences of `pattern` with `string`
#
- # assert "hlelo".replace("le", "el") == "hello"
- # assert "hello".replace('l', "") == "heo"
- fun replace(pattern: Pattern, string: SELFTYPE): String
+ # assert "hlelo".replace("le", "el") == "hello"
+ # assert "hello".replace('l', "") == "heo"
+ #
+ # var t: Text = "hello"
+ # assert t.replace("hello", new FlatBuffer) == ""
+ fun replace(pattern: Pattern, string: Text): String
do
return self.split_with(pattern).join(string)
end
intrude import gamnit::flat
-# Visible entity in the game world, represented by its `model` modified by the other attributes
+# Visible 3D entity in the game world
+#
+# Similar to `gamnit::Sprite` which is in 2D.
+#
+# Each actor associates a `model` to the position `center`.
+# The appearance is modified by `rotation`, `scale` and `alpha`,
+# as well as the attributes of `model` itself.
+#
+# ~~~
+# import gamnit::depth
+#
+# # Load model from the assets folder
+# var model = new Model("path/in/assets.obj")
+#
+# # Create and configure an actor
+# var actor = new Actor(model, new Point3d[Float](0.0, 0.0, 0.0))
+# actor.scale = 2.0
+#
+# # Add to the visible game world
+# app.actors.add actor
+# ~~~
class Actor
- # Model used to dray this actor
+ # Model used to draw this actor
var model: Model
# Position of this sprite in world coordinates
# Rotation on the Z axis
var rotation = 0.0 is writable
- # Scale applied to this sprite
+ # Scale applied to the model
var scale = 1.0 is writable
- # Transparency applied to the texture on draw
+ # Transparency applied to the model on draw
+ #
+ # This value may be ignored by some materials.
+ # Non-opaque values may result in artifacts as there is no specialized
+ # support for transparent models and the depth buffer.
var alpha = 1.0 is writable
end
-# Entire 3D model defined by its `leaves`, an association of `Mesh` to `Material`
+# 3D model composed of `Mesh` and `Material`, loaded from the assets folder by default
+#
+# Instances can be created at any time and must be loaded after or at the end of `on_create`.
+# If loading fails, the model is replaced by `placeholder_model`.
+#
+# ~~~
+# import gamnit::depth
+#
+# var model = new Model("path/in/assets.obj")
+# model.load
+# ~~~
+#
+# The most simple model is `LeafModel`, composed of a single `Mesh` and `Material`.
+# It can be easily created programmatically to display simple geometries.
+# Whereas `CompositeModel` is composed of one or many `LeafModel` and is usually
+# loaded from the assets folder as a `ModelAsset`.
+# Instances of `ModelAsset` must be in the format OBJ and MAT,
+# and their texture in PNG or JPG.
abstract class Model
# Load this model in memory
redef var leaves = new Array[LeafModel]
end
-# Single model with a `mesh` and `material`
+# Basic model with a single `mesh` and `material`
#
# Only leaves are actually drawn by the `material`.
class LeafModel
redef var leaves = [self]
end
-# Material for a model or how to draw the model
+# Material for models, or how to draw the model
+#
+# To create a simple basic blueish material, use `new Material`.
+#
+# Each class of material is associated to a `GLProgram` and its GPU shaders.
+# The simple material `SmoothMaterial` allows to set an ambient, diffuse and specular color.
+# To which `TextureMaterial` adds three textures, for each kind of light.
+# The `NormalsMaterial` may be useful for debugging, it show the orientation of
+# the normal vectors as colors.
+#
+# ~~~
+# import gamnit::depth
+#
+# var blueish_material = new Material
+# var redish_material = new SmoothMaterial([0.3, 0.0, 0.0],
+# [0.6, 0.0, 0.0],
+# [1.0, 1.0, 1.0])
+# var normals_material = new NormalsMaterial
+# ~~~
abstract class Material
- # Draw `actor`
+ # Draw a `model` from `actor`
#
# This method should be refined by subclasses as the default implementation is a no-op.
#
end
# Mesh with all geometry data
+#
+# May be created via `Plane`, `Cube` or `UVSphere`,
+# or loaded from the assets folder indirectly with a `Model`.
+#
+# ~~~
+# import gamnit::depth
+#
+# var plane = new Plane
+# var cube = new Cube
+# var sphere = new UVSphere(1.0, 32, 16)
+# ~~~
class Mesh
# Vertices coordinates
# `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
- var w = n_meridians
- var h = n_parallels
-
- var vertices = new Array[Float].with_capacity(w*h*3)
- self.vertices = vertices
-
- var texture_coords = new Array[Float].with_capacity(w*h*2)
- self.texture_coords = texture_coords
-
- var normals = new Array[Float].with_capacity(w*h*3)
- self.normals = normals
-
- # Build vertices
- for m in [0..w[ do
- for p in [0..h[ do
- var u = m.to_f * 2.0 * pi / (w-1).to_f
- var v = p.to_f * pi / (h-1).to_f
-
- vertices.add radius * u.cos * v.sin
- vertices.add radius * v.cos
- vertices.add radius * u.sin * v.sin
-
- texture_coords.add (1.0 - m.to_f/(w-1).to_f)
- texture_coords.add(p.to_f/(h-1).to_f)
-
- normals.add u.cos * v.sin
- normals.add v.cos
- normals.add u.sin * v.sin
- end
- end
-
- # Build faces
- var indices = new Array[Int].with_capacity((w-1)*(h-1)*6)
- self.indices = indices
- for m in [0..w-1[ do
- for p in [0..h-1[ do
- var a = m*h + p
-
- indices.add a
- indices.add a+h
- indices.add a+1
-
- indices.add a+h
- indices.add a+h+1
- indices.add a+1
- end
- end
- end
end
# Source of light
intrude import depth_core
intrude import flat
-# Simple material with static colors used for debugging or display abstract objects
-class SmoothMaterial
- super Material
-
+redef class Material
# Get the default blueish material
- init default do init(
+ new do return new SmoothMaterial(
[0.0, 0.0, 0.3, 1.0],
[0.0, 0.0, 0.6, 1.0],
[1.0, 1.0, 1.0, 1.0])
+end
+
+# Simple material with static colors
+class SmoothMaterial
+ super Material
# Ambient color, always visible
var ambient_color: Array[Float] is writable
# If using a texture, set `texture_coords`
program.tex_coord.array_enabled = sample_used_texture != null
if sample_used_texture != null then
- if sample_used_texture isa GamnitRootTexture then
+ if sample_used_texture isa RootTexture then
# Coordinates are directly valid
program.tex_coord.array(mesh.texture_coords, 2)
else
end
# Cube, with 6 faces
+#
+# Occupies `[-0.5..0.5]` on all three axes.
class Cube
super Mesh
redef var center = new Point3d[Float](0.0, 0.0, 0.0) is lazy
end
+
+# Sphere with `radius` and a number of faces set by `n_meridians` and `n_parallels`
+class UVSphere
+ super Mesh
+
+ # Distance between the center and the vertices
+ var radius: Float
+
+ # Number of vertices on a full circle around the Z axis
+ var n_meridians: Int
+
+ # Number of vertices on an arc between both poles
+ var n_parallels: Int
+
+ init
+ do
+ var w = n_meridians
+ var h = n_parallels
+
+ var vertices = new Array[Float].with_capacity(w*h*3)
+ self.vertices = vertices
+
+ var texture_coords = new Array[Float].with_capacity(w*h*2)
+ self.texture_coords = texture_coords
+
+ var normals = new Array[Float].with_capacity(w*h*3)
+ self.normals = normals
+
+ # Build vertices
+ for m in [0..w[ do
+ for p in [0..h[ do
+ var u = m.to_f * 2.0 * pi / (w-1).to_f
+ var v = p.to_f * pi / (h-1).to_f
+
+ vertices.add radius * u.cos * v.sin
+ vertices.add radius * v.cos
+ vertices.add radius * u.sin * v.sin
+
+ texture_coords.add (1.0 - m.to_f/(w-1).to_f)
+ texture_coords.add(p.to_f/(h-1).to_f)
+
+ normals.add u.cos * v.sin
+ normals.add v.cos
+ normals.add u.sin * v.sin
+ end
+ end
+
+ # Build faces
+ var indices = new Array[Int].with_capacity((w-1)*(h-1)*6)
+ self.indices = indices
+ for m in [0..w-1[ do
+ for p in [0..h-1[ do
+ var a = m*h + p
+
+ indices.add a
+ indices.add a+h
+ indices.add a+1
+
+ indices.add a+h
+ indices.add a+h+1
+ indices.add a+1
+ end
+ end
+ end
+end
var content = text_asset.to_s
if content.is_empty then
print_error "Model failed to load: Asset empty at '{self.path}'"
- leaves.add new LeafModel(new Cube, new SmoothMaterial.default)
+ leaves.add new LeafModel(new Cube, new Material)
return
end
var obj_def = parser.parse
if obj_def == null then
print_error "Model failed to load: .obj format error on '{self.path}'"
- leaves.add new LeafModel(new Cube, new SmoothMaterial.default)
+ leaves.add new LeafModel(new Cube, new Material)
return
end
# Create models and store them
for mesh, mtl_def in mesh_to_mtl do
var material = materials.get_or_null(mtl_def)
- if material == null then material = new SmoothMaterial.default
+ if material == null then material = new Material
var model = new LeafModel(mesh, material)
array.add model
# 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
+ var placeholder_model = new LeafModel(new Cube, new Material) is lazy
end
# If using a texture, set `texture_coords`
program.tex_coord.array_enabled = sample_used_texture != null
if sample_used_texture != null then
- if sample_used_texture isa GamnitRootTexture then
+ if sample_used_texture isa RootTexture then
# Coordinates are directly valid
program.tex_coord.array(mesh.texture_coords, 2)
else
import gamnit::limit_fps
import gamnit::camera_control
-# Draw a `texture` at `center`
+# Visible 2D entity in the game world or UI
#
-# An instance of `Sprite` can only belong to a single `SpriteSet` at
-# a time. The on screen position depends on the camera associated
+# Similar to `gamnit::Actor` which is in 3D.
+#
+# Each sprite associates a `texture` to the position `center`.
+# The appearance is modified by `rotation`, `invert_x`,
+# `scale`, `red`, `green`, `blue` and `alpha`.
+# These values can be changed at any time and will trigger an update
+# of the data on the GPU side, having a small performance cost.
+#
+# For a sprite to be visible, it must be added to either the world `sprites`
+# or the `ui_sprites`.
+# However, an instance of `Sprite` can only belong to a single `SpriteSet`
+# at a time. The final on-screen position depends on the camera associated
# to the `SpriteSet`.
+#
+# ~~~
+# # Load texture and create sprite
+# var texture = new Texture("path/in/assets.png")
+# var sprite = new Sprite(texture, new Point3d[Float](0.0, 0.0, 0.0))
+#
+# # Add sprite to the visible game world
+# app.sprites.add sprite
+#
+# # Extra configuration of the sprite
+# sprite.rotation = pi/2.0
+# sprite.scale = 2.0
+#
+# # Show only the blue colors
+# sprite.red = 0.0
+# sprite.green = 0.0
+# ~~~
+#
+# To add a sprite to the UI it can be anchored to screen borders
+# with `ui_camera.top_left` and the likes.
+#
+# ~~~nitish
+# # Place it a bit off the top left of the screen
+# var pos = app.ui_camera.top_left.offset(128.0, -128.0, 0)
+#
+# # Load texture and create sprite
+# var texture = new Texture("path/in/assets.png")
+# var sprite = new Sprite(texture, pos)
+#
+# # Add it to the UI (above world sprites)
+# app.ui_sprites.add sprite
+# ~~~
class Sprite
# Texture drawn to screen
center_direct = value
end
- # Rotation on the Z axis, positive values go counterclockwise
+ # Rotation on the Z axis, positive values turn counterclockwise
var rotation = 0.0 is writable(rotation_direct=)
- # Rotation on the Z axis, positive values go counterclockwise
+ # Rotation on the Z axis, positive values turn counterclockwise
fun rotation=(value: Float)
do
if isset _rotation and value != rotation then needs_update
# Scale applied to this sprite
#
- # The default size of `self` depends on the size in pixels of `texture`.
+ # The basic size of `self` depends on the size in pixels of `texture`.
var scale = 1.0 is writable(scale_direct=)
- # Scale applied to this sprite, see `scale`
+ # Scale applied to this sprite
+ #
+ # The basic size of `self` depends on the size in pixels of `texture`.
fun scale=(value: Float)
do
if isset _scale and value != scale then needs_update
# Camera for `ui_sprites` using an orthogonal view
var ui_camera = new UICamera(app.display.as(not null)) is lazy
- # World sprites to draw as seen by `world_camera`
+ # World sprites drawn as seen by `world_camera`
var sprites: Set[Sprite] = new SpriteSet
- # UI sprites to draw as seen by `ui_camera`, drawn over world `sprites`
+ # UI sprites drawn as seen by `ui_camera`, over world `sprites`
var ui_sprites: Set[Sprite] = new SpriteSet
# Main method to refine in clients to update game logic and `sprites`
super HashSet[Sprite]
# Map texture then static vs dynamic to a `SpriteContext`
- var contexts_map = new HashMap2[GamnitRootTexture, Bool, SpriteContext]
+ var contexts_map = new HashMap2[RootTexture, Bool, SpriteContext]
# Contexts in `contexts_map`
var contexts_items = new Array[SpriteContext]
# Context config and state
# Only root texture drawn by this context
- var texture: nullable GamnitRootTexture
+ var texture: nullable RootTexture
# OpenGL ES usage of `buffer_array` and `buffer_element`
var usage: GLBufferUsage
# The data can be compressed by a call to `defragment`.
#
# ~~~
+# intrude import gamnit::flat
+#
# var array = new GroupedArray[String]
# assert array.to_s == ""
#
# Returns the elements that moved as a list.
#
# ~~~
+ # intrude import gamnit::flat
+ #
# var array = new GroupedArray[String]
# array.add "a"
# array.add "b"
# See the License for the specific language governing permissions and
# limitations under the License.
-# Services to load textures, create subtextures and manage their life-cycle
+# Load textures, create subtextures and manage their life-cycle
module textures
import display
-# Abstract gamnit texture
+# Texture composed of pixels, loaded from the assets folder by default
+#
+# Most textures should be created with `App` (as attributes)
+# for the method `on_create` to load them.
+#
+# ~~~
+# import gamnit::flat
+#
+# redef class App
+# # Create the texture object, it will be loaded automatically
+# var texture = new Texture("path/in/assets.png")
+#
+# redef fun on_create()
+# do
+# # Let `on_create` load the texture
+# super
+#
+# # Use the texture
+# var sprite = new Sprite(texture, new Point3d[Float](0.0, 0.0, 0.0))
+# app.sprites.add sprite
+# end
+# end
+# ~~~
+#
+# Otherwise, they can be loaded and error checked explicitly after `on_create`.
+#
+# ~~~nitish
+# var texture = new Texture("path/in/assets.png")
+# texture.load
+# var error = texture.error
+# if error != null then print_error error
+# ~~~
+#
+# A texture may also be created programmatically, like `CheckerTexture`,
+# or derived from another texture, using `subtexture`.
+# Textures with actual pixel data (not `Subtexture`) are `RootTexture`.
+# Texture loaded from the assets folder may in the PNG or JPG formats.
abstract class Texture
# Prepare a texture located at `path` within the `assets` folder
new (path: Text) do return new TextureAsset(path.to_s)
- # Root texture of which `self` is derived
- fun root: GamnitRootTexture is abstract
+ # Root texture from which `self` is derived
+ fun root: RootTexture is abstract
# Width in pixels of this texture
var width = 0.0
fun gl_texture: Int do return root.gl_texture
# Prepare a subtexture from this texture, from the given pixel offsets
- fun subtexture(left, top, width, height: Numeric): GamnitSubtexture
+ fun subtexture(left, top, width, height: Numeric): Subtexture
do
# Setup the subtexture
- var subtex = new GamnitSubtexture(root, self, left.to_f, top.to_f, width.to_f, height.to_f)
+ var subtex = new Subtexture(root, self, left.to_f, top.to_f, width.to_f, height.to_f)
return subtex
end
# Colorful small texture of 2x2 pixels
class CheckerTexture
- super GamnitRootTexture
+ super RootTexture
redef fun load(force)
do
end
# Texture with its own pixels
-class GamnitRootTexture
+class RootTexture
super Texture
redef fun root do return self
# Texture loaded from the assets folder
class TextureAsset
- super GamnitRootTexture
+ super RootTexture
# Path to this texture within the `assets` folder
var path: String
end
# Texture derived from another texture, does not own its pixels
-class GamnitSubtexture
+class Subtexture
super Texture
redef var root
# See the License for the specific language governing permissions and
# limitations under the License.
-# Provides interfaces and classes to represent basic geometry needs.
+# Interfaces and classes to represent basic geometry needs.
module points_and_lines is serialize
import serialization
-# An abstract 2d point, strongly linked to its implementation `Point`
+# Abstract 2d point, strongly linked to its implementation `Point`
interface IPoint[N: Numeric]
# Horizontal coordinate
redef fun ==(o) do return o isa IPoint[Numeric] and o.x == x and o.y == y
end
-# A 2d point and an implementation of `IPoint`
+# 2D point with `x` and `z`
class Point[N: Numeric]
super IPoint[N]
redef var y: N is writable
end
-# An abstract 3d point, strongly linked to its implementation `Point3d`
+# Abstract 3d point, strongly linked to its implementation `Point3d`
interface IPoint3d[N: Numeric]
super IPoint[N]
- # depth coordinate
+ # Depth coordinate
fun z: N is abstract
redef fun to_s do return "({x}, {y}, {z})"
end
-# A 3d point and an implementation of `IPoint3d`
+# 3D point with `x`, `y` and `z`
class Point3d[N: Numeric]
super IPoint3d[N]
super Point[N]
redef var z: N is writable
end
-# An abstract 2d line segment
+# Abstract 2D line segment between two ordered points
interface ILine[N: Numeric]
# The type of points that ends the segment
type P: IPoint[N]
- # The point that is the left-end of the segment
+ # Point at the left-end of the segment
fun point_left: P is abstract
- # The point that is the right-end of the segment
+ # Point at the right-end of the segment
fun point_right: P is abstract
redef fun to_s do return "{point_left}--{point_right}"
end
-# A 2d line segment
+# 2D line segment between two ordered points
class Line[N: Numeric]
super ILine[N]
end
end
-# An abstract 3d line segment
+# Abstract 3D line segment between two ordered points
interface ILine3d[N: Numeric]
super ILine[N]
redef type P: IPoint3d[N]
end
-# A 3d line segment
+# 3D line segment between two ordered points
class Line3d[N: Numeric]
super Line[N]
super ILine3d[N]
super HashMap[K, Array[V]]
# Add `v` to the array associated with `k`.
+ #
# If there is no array associated, then create it.
+ #
+ # For the inverse operation, see `remove_one`.
+ #
+ # ```
+ # var m = new MultiHashMap[String, Char]
+ # m.add_one("four", 'i')
+ # m.add_one("four", 'i')
+ # m.add_one("four", 'i')
+ # m.add_one("four", 'i')
+ # assert m.has_key("four")
+ # assert m["four"] == ['i', 'i', 'i', 'i']
+ # ```
fun add_one(k: K, v: V)
do
var x = self.get_or_null(k)
self[key] = res
return res
end
+
+ # Remove an occurrence of `v` from the array associated with `k`.
+ #
+ # If the associated array does not contain `v`, do nothing. If the
+ # associated array only contain one element and this element is `v`, remove
+ # the key `k`.
+ #
+ # In a nutshell, does the inverse operation of `add_one`.
+ #
+ # ```
+ # var m = new MultiHashMap[String, Char]
+ # m["four"] = ['4', 'i', 'i', 'i', 'i']
+ # m.remove_one("four", 'i')
+ # assert m["four"] == ['4', 'i', 'i', 'i']
+ #
+ # m = new MultiHashMap[String, Char]
+ # m.add_one("one", '1')
+ # m.remove_one("one", '?')
+ # assert m["one"] == ['1']
+ # m.remove_one("one", '1')
+ # assert not m.has_key("one")
+ # assert m["one"] == new Array[Char]
+ #
+ # m = new MultiHashMap[String, Char]
+ # m.add_one("one", '1')
+ # m.remove_one("two", '2')
+ # assert not m.has_key("two")
+ # assert m["one"] == ['1']
+ # assert m["two"] == new Array[Char]
+ # ```
+ fun remove_one(k: K, v: V)
+ do
+ var x = get_or_null(k)
+ if x != null then
+ x.remove(v)
+ if x.is_empty then keys.remove(k)
+ end
+ end
end
# Simple way to store an `HashMap[K1, HashMap[K2, V]]`
all:
check:
- $(NITUNIT) .
+ $(NITUNIT) . tests/ examples/
system "curl -s {host}:{port}/not_found" # handled by angular controller
end
- fun test_example_param_route do
+ fun test_example_angular do
var app = new App
app.use("/counter", new CounterAPI)
- app.use("/*", new StaticHandler("../examples/angular/www/", "index.html"))
+ app.use("/*", new StaticHandler(test_path / "../www/", "index.html"))
run_test(app)
end
end
<script src='/javascripts/ng-example.js'></script>
</body>
</html>
+
import pop_tests
import example_post_handler
-class TestExampleQueryString
+class TestExamplePostHandler
super TestPopcorn
redef fun client_test do
system "curl -s {host}:{port}/"
end
- fun test_example_glob_route do
+ fun test_example_post_handler do
var app = new App
app.use("/", new PostHandler)
run_test(app)
Body:
[Client] curl -s localhost:*****/ --data 'user'
-POST Error: user format error on user
URI: /
Body: user
<body>
<h1>404 Not Found</h1>
</body>
- </html>
\ No newline at end of file
+ </html>
system "curl -s {host}:{port}/?items=10\\&order=asc"
end
- fun test_example_glob_route do
+ fun test_example_query_string do
var app = new App
app.use("/", new QueryStringHandler)
run_test(app)
Query string: items=10&order=asc
items: 10
order: asc
+
redef fun all(req, res) do req.timer = new Clock
end
-class LogHandler
+class AdvancedLoggerHandler
super Handler
redef fun all(req, res) do
end
end
-class HelloHandler
+class AnotherHandler
super Handler
redef fun get(req, res) do res.send "Hello World!"
var app = new App
app.use_before("/*", new RequestTimeHandler)
-app.use("/", new HelloHandler)
-app.use_after("/*", new LogHandler)
+app.use("/", new AnotherHandler)
+app.use_after("/*", new AdvancedLoggerHandler)
app.listen("localhost", 3000)
end
end
-class HelloHandler
+class SomeHandler
super Handler
redef fun get(req, res) do res.send "Hello World!"
var app = new App
-app.use("/", new HelloHandler)
+app.use("/", new SomeHandler)
app.use("/*", new SimpleErrorHandler)
app.listen("localhost", 3000)
import popcorn
-class LogHandler
+class SimpleLoggerHandler
super Handler
redef fun all(req, res) do print "Request Logged"
end
-class HelloHandler
+class MyOtherHandler
super Handler
redef fun get(req, res) do res.send "Hello World!"
var app = new App
-app.use_before("/*", new LogHandler)
-app.use("/", new HelloHandler)
+app.use_before("/*", new SimpleLoggerHandler)
+app.use("/", new MyOtherHandler)
app.listen("localhost", 3000)
system "curl -s {host}:{port}/about"
end
- fun test_example_param_route do
+ fun test_example_advanced_logger do
var app = new App
app.use_before("/*", new RequestTimeHandler)
- app.use("/", new HelloHandler)
- app.use_after("/*", new LogHandler)
+ app.use("/", new AnotherHandler)
+ app.use_after("/*", new AdvancedLoggerHandler)
run_test(app)
end
end
<body>
<h1>404 Not Found</h1>
</body>
- </html>
\ No newline at end of file
+ </html>
system "curl -s {host}:{port}/about"
end
- fun test_example_param_route do
+ fun test_example_html_error_handler do
var app = new App
app.use("/*", new HtmlErrorHandler)
run_test(app)
<body>
<h1>404 An error occurred!</h1>
</body>
- </html>
\ No newline at end of file
+ </html>
system "curl -s {host}:{port}/about"
end
- fun test_example_param_route do
+ fun test_example_simple_error_handler do
var app = new App
- app.use("/", new HelloHandler)
+ app.use("/", new SomeHandler)
app.use("/*", new SimpleErrorHandler)
run_test(app)
end
[Client] curl -s localhost:*****/
Hello World!
[Client] curl -s localhost:*****/about
-An error occurred!
\ No newline at end of file
+An error occurred!
system "curl -s {host}:{port}/about"
end
- fun test_example_param_route do
+ fun test_example_simple_logger do
var app = new App
- app.use_before("/*", new LogHandler)
- app.use("/", new HelloHandler)
+ app.use_before("/*", new SimpleLoggerHandler)
+ app.use("/", new MyOtherHandler)
run_test(app)
end
end
<body>
<h1>404 Not Found</h1>
</body>
- </html>
\ No newline at end of file
+ </html>
redef fun all(req, res) do print "User logged"
end
-class UserHome
+class UserHomepage
super Handler
redef fun get(req, res) do res.send "User Home"
var user_router = new Router
user_router.use("/*", new UserLogger)
-user_router.use("/", new UserHome)
+user_router.use("/", new UserHomepage)
user_router.use("/profile", new UserProfile)
var app = new App
fun test_example_router do
var user_router = new Router
user_router.use("/*", new UserLogger)
- user_router.use("/", new UserHome)
+ user_router.use("/", new UserHomepage)
user_router.use("/profile", new UserProfile)
var app = new App
app.use("/", new AppHome)
system "curl -s {host}:{port}/products/not_found"
end
- fun test_example_param_route do
+ fun test_example_session do
var app = new App
app.use("/*", new SessionInit)
app.use("/", new AppLogin)
<body>
<h1>404 Not Found</h1>
</body>
- </html>
\ No newline at end of file
+ </html>
system "curl -s {host}:{port}/not_found.nit"
end
- fun test_example_param_route do
+ fun test_example_static do
var app = new App
- app.use("/", new StaticHandler("../examples/static_files/public/"))
+ app.use("/", new StaticHandler(test_path / "../public/"))
run_test(app)
end
end
<body>
<h1>404 Not Found</h1>
</body>
- </html>
\ No newline at end of file
+ </html>
system "curl -s {host}:{port}/not_found.nit"
end
- fun test_example_param_route do
+ fun test_example_static_default do
var app = new App
- app.use("/", new StaticHandler("../examples/static_files/public/", "default.html"))
+ app.use("/", new StaticHandler(test_path / "../public/", "default.html"))
run_test(app)
end
end
<h1>Default Page</h1>
</body>
</html>
+
system "curl -s {host}:{port}/not_found.nit"
end
- fun test_example_param_route do
+ fun test_example_static_multiple do
var app = new App
- app.use("/", new StaticHandler("../examples/static_files/public/"))
- app.use("/", new StaticHandler("../examples/static_files/files/"))
- app.use("/static", new StaticHandler("../examples/static_files/public/"))
- app.use("/static", new StaticHandler("../examples/static_files/files/"))
+ app.use("/", new StaticHandler(test_path / "../public/"))
+ app.use("/", new StaticHandler(test_path / "../files/"))
+ app.use("/static", new StaticHandler(test_path / "../public/"))
+ app.use("/static", new StaticHandler(test_path / "../files/"))
run_test(app)
end
end
<body>
<h1>404 Not Found</h1>
</body>
- </html>
\ No newline at end of file
+ </html>
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2016 Alexandre Terrasa <alexandre@moz-code.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.
+
+# Popcorn threaded tasks
+#
+# Tasks allow you to execute code in another thread than the app listening loop.
+# Useful when you want to run some tasks periodically.
+#
+# Let's say you want to purge the `downloads/` directory of your app every hour:
+#
+# ~~~nitish
+# class PurgeTask
+# super PopTask
+#
+# var dir: String
+#
+# redef fun main do
+# loop
+# dir.rmdir
+# 3600.sleep
+# end
+# end
+# end
+#
+# var app = new App
+#
+# # Register a new task
+# app.register_task(new PurgeTask("downloads/"))
+#
+# # Add your handlers
+# # app.use('/', new MyHandler)
+#
+# # Run the tasks
+# app.run_tasks
+#
+# # Start the app
+# app.listen("0.0.0.0", 3000)
+# ~~~
+module pop_tasks
+
+import pop_handlers
+import pthreads
+
+# An abstract Popcorn task
+#
+# Redefine the `main` method to do something
+#
+# TODO provide a CRON-like syntax like SpringBoot?
+abstract class PopTask
+ super Thread
+
+ redef fun main do return null
+end
+
+redef class App
+
+ # Tasks to run
+ var tasks = new Array[PopTask]
+
+ # Register a new task in `self`
+ fun register_task(task: PopTask) do tasks.add task
+
+ # Run all registered tasks
+ fun run_tasks do for task in tasks do task.start
+end
# Port used to run App.
var port: Int = test_port
+ # Directory of the current test suite
+ #
+ # Useful when your tested app need to load some external files.
+ var test_path: String = "NIT_TESTING_PATH".environ.dirname
+
# Run the test suite on the App.
fun run_test(app: App) do
var server = new AppThread(host, port, app)
# Does `self` contains `errors`?
fun has_error: Bool do return errors.not_empty
- # Render self as a JsonObject
- fun json: JsonObject do
- var obj = new JsonObject
- obj["has_error"] = has_error
- var e = new JsonObject
- for k, v in errors do
- e[k] = new JsonArray.from(v)
- end
- obj["errors"] = e
- return obj
+ redef fun core_serialize_to(v) do
+ var errors = new JsonObject
+ for k, e in self.errors do
+ errors[k] = new JsonArray.from(e)
+ end
+ v.serialize_attribute("has_error", has_error)
+ v.serialize_attribute("errors", errors)
end
- redef fun serialize_to(v) do json.serialize_to(v)
-
# Returns the validation result as a pretty formated string
fun to_pretty_string: String do
var b = new Buffer
redef var re = "(^ISBN [0-9]-[0-9]\{3\}-[0-9]\{5\}-[0-9]?$)".to_re
end
+
+# Check if a field is a valid URL
+#
+# Matched against the following regular expression:
+# ~~~raw
+# ^(http|https):\/\/[a-zA-Z0-9\-_]+(\.[a-zA-Z0-9\-_]+)+([a-zA-Z0-9\-\.,@?^=%&:/~\+#]*[a-zA-Z0-9\-\@?^=%&/~\+#])?
+# ~~~
+# You should redefine the base regular expression `re` with your own.
+#
+# ~~~
+# var validator = new ObjectValidator
+# validator.add new URLField("url")
+# assert not validator.validate("""{ "url": "" }""")
+# assert not validator.validate("""{ "url": "foo" }""")
+# assert not validator.validate("""{ "url": "http://foo" }""")
+# assert validator.validate("""{ "url": "http://nitlanguage.org" }""")
+# assert validator.validate("""{ "url": "http://nitlanguage.org/foo" }""")
+# assert validator.validate("""{ "url": "http://nitlanguage.org/foo?q" }""")
+# assert validator.validate("""{ "url": "http://nitlanguage.org/foo?q&a" }""")
+# assert validator.validate("""{ "url": "http://nitlanguage.org/foo?q&a=1" }""")
+# ~~~
+class URLField
+ super RegexField
+
+ autoinit field, required
+
+ redef var re = "^(http|https):\\/\\/[a-zA-Z0-9\\-_]+(\\.[a-zA-Z0-9\\-_]+)+([a-zA-Z0-9\\-\\.,@?^=%&:/~\\+#]*[a-zA-Z0-9\\-\\@?^=%&/~\\+#])?".to_re
+end
+
+# Check if a field value is already used
+#
+# This class provides a stub validator for fields that should contain a unique value along an
+# application (typically logins or ids).
+#
+# Here an example that uses a `Repository` if an email is unique:
+# ~~~nitish
+# class UniqueEmailField
+# super UniqueField
+#
+# var users: UsersRepository
+#
+# redef fun check_unicity(v, field, val) do
+# var user = users.find_by_email(val)
+# if user != null then
+# v.validation.add_error(field, "Email `{val}` already used")
+# return false
+# end
+# return true
+# end
+# end
+# ~~~
+class UniqueField
+ super StringField
+
+ # Check if `val` is already used somewhere
+ #
+ # You must redefine this method to handle your own validation.
+ fun check_unicity(v: ObjectValidator, field, val: String): Bool is abstract
+
+ redef fun validate_field(v, obj) do
+ if not super then return false
+ var val = obj.get_or_null(field)
+ if not val isa String then return false
+ if not check_unicity(v, field, val) then return false
+ return true
+ end
+end
module popcorn
import nitcorn
+import pop_tasks
import pop_sessions
import pop_logging
intrude import pop_handlers
# (introduction and refinement)
var mclassdefs = new Array[MClassDef]
+ private var mclassdef_sorter: MClassDefSorter is lazy do
+ return new MClassDefSorter(self)
+ end
+
+ private var mpropdef_sorter: MPropDefSorter is lazy do
+ return new MPropDefSorter(self)
+ end
+
# Does the current module has a given class `mclass`?
# Return true if the mmodule introduces, refines or imports a class.
# Visibility is not considered.
# The most general is first, the most specific is last
fun linearize_mclassdefs(mclassdefs: Array[MClassDef])
do
- var sorter = new MClassDefSorter(self)
- sorter.sort(mclassdefs)
+ mclassdef_sorter.sort(mclassdefs)
end
# Sort a given array of property definitions using the linearization order of the module
# The most general is first, the most specific is last
fun linearize_mpropdefs(mpropdefs: Array[MPropDef])
do
- var sorter = new MPropDefSorter(self)
- sorter.sort(mpropdefs)
+ mpropdef_sorter.sort(mpropdefs)
end
private var flatten_mclass_hierarchy_cache: nullable POSet[MClass] = null
super Comparator
redef type COMPARED: MPropDef
var mmodule: MModule
+
redef fun compare(pa, pb)
do
var a = pa.mclassdef
var b = pb.mclassdef
- var ca = a.mclass
- var cb = b.mclass
- if ca != cb then return mmodule.flatten_mclass_hierarchy.compare(ca, cb)
- return mmodule.model.mclassdef_hierarchy.compare(a, b)
+ return mmodule.mclassdef_sorter.compare(a, b)
end
end
end
# A class definition
-# While most definition are `AStdClassdef`
-# There is tow special case of class definition
+#
+# While most definitions are `AStdClassdef`s,
+# there are 2 special cases of class definitions.
abstract class AClassdef
super Prod
# All the declared properties (including the main method)
# live_methods to determine new methoddefs to visit
var live_types = new HashSet[MClassType]
- # The pool of undesolved live types
+ # The pool of unresolved live types
# They are globally resolved at the end of the analaysis
var live_open_types = new HashSet[MClassType]
do
if mclass.loaded then return
- # Recursively load superclasses
- for parent in mclass.in_hierarchy(mainmodule).direct_greaters do load_class_indirect(parent)
+ load_supers(mclass)
if mclass.abstract_loaded then
mclass.allocate_vtable(self)
end
end
+ # Recursively load superclasses.
+ private fun load_supers(mclass: MClass)
+ do
+ for parent in mclass.in_hierarchy(mainmodule).direct_greaters do
+ load_class_indirect(parent)
+ end
+ end
+
# This method is called to handle an implicitly loaded class,
# i.e. a superclass of an explicitly loaded class
# A class loaded implicitly will not be fully allocated
# It the class was already implicitly loaded
if mclass.abstract_loaded then return
- for parent in mclass.in_hierarchy(mainmodule).direct_greaters do load_class_indirect(parent)
+ load_supers(mclass)
mclass.make_vt(self, false)
end
--- /dev/null
+Signed 32-bit:
+-2147483648
+-294967296
+2
+-2147479015
+unsigned 32-bit:
+1
+1705032704
+2147483648
+131073