Merge: model: introduce model_json
authorJean Privat <jean@pryen.org>
Tue, 17 May 2016 20:17:25 +0000 (16:17 -0400)
committerJean Privat <jean@pryen.org>
Tue, 17 May 2016 20:17:25 +0000 (16:17 -0400)
This PR makes all model entities Jsonable when `model_json` is imported.

Not sure if I was supposed to use the serialization process or not in this case? @xymus what do you think?

This is mostly hand picked services chosen to provide a comprehensive model representation usable by other tools like nitdoc, nitx, web tools or else.

Pull-Request: #2024
Reviewed-by: Jean Privat <jean@pryen.org>

44 files changed:
contrib/asteronits/src/android.nit
contrib/asteronits/src/touch_ui.nit
contrib/benitlux/src/benitlux_controller.nit
contrib/benitlux/src/benitlux_db.nit
contrib/benitlux/src/benitlux_model.nit
contrib/benitlux/src/benitlux_web.nit
contrib/tinks/src/client/linux_client.nit
contrib/tnitter/src/action.nit
contrib/tnitter/src/push.nit
contrib/tnitter/src/tnitter_app_android.nit
examples/calculator/src/android_calculator.nit
lib/android/README.md
lib/android/android.nit
lib/android/game.nit [new file with mode: 0644]
lib/android/input_events.nit
lib/android/landscape.nit
lib/android/portrait.nit
lib/android/sensors.nit
lib/app/audio.nit
lib/app/data_store.nit
lib/app/ui.nit
lib/gamnit/display_android.nit
lib/json/serialization.nit
lib/json/static.nit
lib/mnit/android/android_app.nit
lib/nitcorn/restful.nit
lib/postgresql/native_postgres.nit [new file with mode: 0644]
lib/postgresql/package.ini [new file with mode: 0644]
lib/rubix.ini
lib/sqlite3/native_sqlite3.nit
lib/sqlite3/sqlite3.nit
share/man/nitunit.md
src/catalog.nit
src/nitcatalog.nit
src/nitlight.nit
src/nitunit.nit
src/toolcontext.nit
tests/sav/nitcatalog_args1.res
tests/sav/nitlight_args1.res
tests/sav/test_highlight_args1.res
tests/sav/test_json_deserialization_plain.res
tests/sav/test_postgres_native.res [new file with mode: 0644]
tests/test_postgres_native.nit [new file with mode: 0644]
tests/test_sqlite3_native.nit

index 4042f6d..e7ac9df 100644 (file)
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import ::android::platform
+import ::android
 import ::android::vibration
 
 import asteronits
index 3971426..388cd35 100644 (file)
@@ -62,16 +62,16 @@ redef class App
                                        if dy > 0.0 then
                                                # Bottom part of the joystick, turns left or right
                                                if dx < 0.0 then
-                                                       ship.applied_rotation = -1.0
-                                               else
                                                        ship.applied_rotation = 1.0
+                                               else
+                                                       ship.applied_rotation = -1.0
                                                end
                                        else
                                                # Upper part of the joystick, detect action using 45d angles
                                                if dx < dy then
-                                                       ship.applied_rotation = -1.0
-                                               else if dx > -dy then
                                                        ship.applied_rotation = 1.0
+                                               else if dx > -dy then
+                                                       ship.applied_rotation = -1.0
                                                else
                                                        ship.applied_thrust = 1.0
                                                end
@@ -98,20 +98,20 @@ redef class App
 
                # Add the joystick to the UI
                ui_sprites.add new Sprite(spritesheet_controls.forward,
-                       ui_camera.bottom_left.offset(joystick_x, -200.0, 0.0))
+                       ui_camera.bottom_left.offset(joystick_x, 200.0, 0.0))
                ui_sprites.add new Sprite(spritesheet_controls.left,
-                       ui_camera.bottom_left.offset(joystick_x-100.0, -joystick_y, 0.0))
+                       ui_camera.bottom_left.offset(joystick_x-100.0, joystick_y, 0.0))
                ui_sprites.add new Sprite(spritesheet_controls.right,
-                       ui_camera.bottom_left.offset(joystick_x+100.0, -joystick_y, 0.0))
+                       ui_camera.bottom_left.offset(joystick_x+100.0, joystick_y, 0.0))
 
                # Purely cosmetic joystick background
                ui_sprites.add new Sprite(spritesheet_controls.joystick_back,
-                       ui_camera.bottom_left.offset(joystick_x, -joystick_y, -1.0)) # In the back
+                       ui_camera.bottom_left.offset(joystick_x, joystick_y, -1.0)) # In the back
                ui_sprites.add new Sprite(spritesheet_controls.joystick_down,
                        ui_camera.bottom_left.offset(joystick_x, 0.0, 1.0))
 
                # Add the "open fire" button
                ui_sprites.add new Sprite(spritesheet_controls.fire,
-                       ui_camera.bottom_right.offset(-150.0, -150.0, 0.0))
+                       ui_camera.bottom_right.offset(-150.0, 150.0, 0.0))
        end
 end
index 5f5513a..5649345 100644 (file)
@@ -133,6 +133,16 @@ class BenitluxRESTAction
                return new HttpResponse.ok(log)
        end
 
+       # Is `token` valid?
+       #
+       # check_token?token=a -> true | BenitluxError
+       fun check_token(token: String): HttpResponse
+       is restful do
+               var user_id = db.token_to_id(token)
+               if user_id == null then return new HttpResponse.invalid_token
+               return new HttpResponse.ok(true)
+       end
+
        # Search a user
        #
        # search?token=b&query=a&offset=0 -> Array[UserAndFollowing] | BenitluxError
@@ -322,6 +332,57 @@ redef class Sys
 end
 
 # ---
+# Administration
+
+# Path to the secret used to authenticate admin requests
+fun secret_path: String do return "benitlux.secret"
+
+# Services reserved to administrators
+class BenitluxAdminAction
+       super BenitluxAction
+       super RestfulAction
+
+       private fun server_secret: String do return secret_path.to_path.read_all
+
+       # Trigger sending daily menu to connected clients
+       #
+       # This should usually be called by an external cron program.
+       # send_daily_updates?secret=shared_secret -> true | BenitluxError
+       fun send_daily_updates(secret: nullable String): HttpResponse
+       is restful do
+               # Check secrets
+               var server_secret = server_secret
+               if server_secret.is_empty then
+                       print_error "The admin interface needs a secret at '{secret_path}'"
+                       return new HttpResponse.server_error
+               end
+
+               if server_secret != secret then
+                       return new HttpResponse.invalid_token
+               end
+
+               # Load beer menu
+               var list = db.list_beers_and_rating
+               if list == null then return new HttpResponse.server_error
+
+               var msg = new DailyNotification(list)
+
+               # Broadcast updates
+               for conn in push_connections.values.to_a do
+                       if not conn.closed then
+                               conn.respond new HttpResponse.ok(msg)
+                               conn.close
+                       end
+               end
+               push_connections.clear
+
+               return new HttpResponse.ok(true)
+       end
+
+       redef fun answer(request, turi) do return new HttpResponse.bad_request
+end
+
+# ---
 # Misc services
 
 redef class Text
@@ -348,7 +409,7 @@ redef class HttpResponse
        init ok(data: Serializable)
        do
                init 200
-               body = data.to_json_string
+               body = data.serialize_to_json
        end
 
        # Respond with a `BenitluxError` in JSON and a code 403
@@ -356,7 +417,7 @@ redef class HttpResponse
        do
                init 403
                var error = new BenitluxTokenError("Forbidden", "Invalid or outdated token.")
-               body = error.to_json_string
+               body = error.serialize_to_json
        end
 
        # Respond with a `BenitluxError` in JSON and a code 400
@@ -364,7 +425,7 @@ redef class HttpResponse
        do
                init 400
                var error = new BenitluxError("Bad Request", "Application error, or it needs to be updated.")
-               body = error.to_json_string
+               body = error.serialize_to_json
        end
 
        # Respond with a `BenitluxError` in JSON and a code 500
@@ -372,6 +433,6 @@ redef class HttpResponse
        do
                init 500
                var error = new BenitluxError("Internal Server Error", "Server error, try again later.")
-               body = error.to_json_string
+               body = error.serialize_to_json
        end
 end
index 81ec6bf..c899ede 100644 (file)
@@ -85,7 +85,7 @@ class BenitluxDB
                        last_weekday = "date('now', 'weekday 6', '-7 day')"
                else last_weekday = "date('now', '-1 day')"
 
-               return beer_events_since(last_weekday)
+               return beer_events_since_sql(last_weekday)
        end
 
        # Build and return a `BeerEvents` for today compared to `prev_day`
@@ -93,26 +93,35 @@ class BenitluxDB
        # Return `null` on error
        fun beer_events_since(prev_day: String): nullable BeerEvents
        do
+               prev_day = prev_day.to_sql_date_string
+               return beer_events_since_sql("date({prev_day})")
+       end
+
+       # `BeerEvents` since the SQLite formatted date command `sql_date`
+       #
+       # Return `null` on error
+       private fun beer_events_since_sql(sql_date: String): nullable BeerEvents
+       do
                var events = new BeerEvents
 
                # New
                var stmt = select("ROWID, name, desc FROM beers WHERE " +
                                  "ROWID IN (SELECT beer FROM daily WHERE day=(SELECT MAX(day) FROM daily)) AND " +
-                                 "NOT ROWID IN (SELECT beer FROM daily WHERE date(day) = date({prev_day}))")
+                                 "NOT ROWID IN (SELECT beer FROM daily WHERE date(day) = {sql_date})")
                if stmt == null then return null
                for row in stmt do events.new_beers.add row.to_beer
 
                # Gone
                stmt = select("ROWID, name, desc FROM beers WHERE " +
                              "NOT ROWID IN (SELECT beer FROM daily WHERE day=(SELECT MAX(day) FROM daily)) AND " +
-                             "ROWID IN (SELECT beer FROM daily WHERE date(day) = date({prev_day}))")
+                             "ROWID IN (SELECT beer FROM daily WHERE date(day) = {sql_date})")
                if stmt == null then return null
                for row in stmt do events.gone_beers.add row.to_beer
 
                # Fix
                stmt = select("ROWID, name, desc FROM beers WHERE " +
                              "ROWID IN (SELECT beer FROM daily WHERE day=(SELECT MAX(day) FROM daily)) AND " +
-                             "ROWID IN (SELECT beer FROM daily WHERE date(day) = date({prev_day}))")
+                             "ROWID IN (SELECT beer FROM daily WHERE date(day) = {sql_date})")
                if stmt == null then return null
                for row in stmt do events.fix_beers.add row.to_beer
 
index 987047f..48a807a 100644 (file)
@@ -234,6 +234,14 @@ class CheckinReport
        var users = new Array[User]
 end
 
+# Daily menu notifications
+class DailyNotification
+       serialize
+
+       # All beers on the menu today
+       var beers: Array[BeerAndRatings]
+end
+
 # Server or API usage error
 class BenitluxError
        super Error
index d5bab54..3b2743b 100644 (file)
@@ -25,6 +25,9 @@ import benitlux_restful
 # Listening interface
 fun iface: String do return "localhost:8080"
 
+# Listening interface for admin commands
+fun iface_admin: String do return "localhost:8081"
+
 # Sqlite3 database
 var db_path = "benitlux_sherbrooke.db"
 var db = new BenitluxDB.open(db_path)
@@ -40,10 +43,14 @@ vh.routes.add new Route("/rest/", new BenitluxRESTAction(db))
 vh.routes.add new Route("/push/", new BenitluxPushAction(db))
 vh.routes.add new Route(null, new BenitluxSubscriptionAction(db))
 
+var vh_admin = new VirtualHost(iface_admin)
+vh_admin.routes.add new Route(null, new BenitluxAdminAction(db))
+
 var factory = new HttpFactory.and_libevent
 factory.config.virtual_hosts.add vh
+factory.config.virtual_hosts.add vh_admin
 
-print "Launching server on http://{iface}/"
+print "Launching server on http://{iface}/ and http://{iface_admin}/"
 factory.run
 
 db.close
index 6ef6b15..bd60702 100644 (file)
@@ -54,8 +54,7 @@ redef class App
 
                # Save the default config to pretty Json
                var cc = new ClientConfig
-               var json = cc.to_plain_json
-               json = json.replace(",", ",\n")
+               var json = cc.serialize_to_json(plain=true, pretty=true)
                json.write_to_file config_path
 
                return cc
index ad41a71..60ee384 100644 (file)
@@ -266,14 +266,14 @@ class TnitterREST
                        db.close
 
                        var response = new HttpResponse(200)
-                       response.body = posts.to_json_string
+                       response.body = posts.serialize_to_json
                        return response
                end
 
                # Format not recognized
                var error = new Error("Bad Request")
                var response = new HttpResponse(400)
-               response.body = error.to_json_string
+               response.body = error.serialize_to_json
                return response
        end
 end
index cdb2945..bc47789 100644 (file)
@@ -46,7 +46,7 @@ redef class DB
                # Everyone gets the same response
                var posts = list_posts(0, 16)
                var response = new HttpResponse(400)
-               response.body = posts.to_json_string
+               response.body = posts.serialize_to_json
 
                for conn in push_connections do
                        # Complete the answer to `conn`
index 74eb1b6..dc24681 100644 (file)
@@ -19,8 +19,7 @@ end
 
 import tnitter_app
 
-import android::ui
-import android::http_request
+import android
 import android::portrait
 
 redef class LabelAuthor
index 7fe192c..1d51073 100644 (file)
@@ -16,7 +16,7 @@
 module android_calculator
 
 import calculator
-import android::ui
+import android
 
 redef class Button
        init do set_android_style(native, (text or else "?").is_int)
index 47e0cec..b6bcf7e 100644 (file)
@@ -64,7 +64,16 @@ integer as argument. They are applied in the Android manifest as
   only be used by low-level implementations of Nit on Android.
   Its usefulness will be extended in the future to customize user applications.
 
-## Project entry points
+## Android implementation
+
+There is two core implementation for Nit apps on Android.
+`android::nit_activity` is used by apps with standard windows and native UI controls.
+`android::game` is used by, well, games and the game frameworks `mnit` and `gamnit`.
+
+Clients don't have to select the core implementation, it is imported by other relevant modules.
+For example, a module importing `app::ui` and `android` will trigger the importation of `android::nit_activity`.
+
+## Lock app orientation
 
 Importing `android::landscape` or `android::portrait` locks the generated
 application in the specified orientation. This can be useful for games and
index b5aa04f..58e296a 100644 (file)
 module android
 
 import platform
-import native_app_glue
 import dalvik
 private import log
-private import assets
-
-redef class App
-       redef fun init_window
-       do
-               super
-               on_create
-               on_restore_state
-               on_start
-       end
-
-       redef fun term_window
-       do
-               super
-               on_stop
-       end
-
-       # Is the application currently paused?
-       var paused = true
-
-       redef fun pause
-       do
-               paused = true
-               on_pause
-               super
-       end
-
-       redef fun resume
-       do
-               paused = false
-               on_resume
-               super
-       end
-
-       redef fun save_state do on_save_state
-
-       redef fun lost_focus
-       do
-               paused = true
-               super
-       end
-
-       redef fun gained_focus
-       do
-               paused = false
-               super
-       end
-
-       redef fun destroy do on_destroy
-end
diff --git a/lib/android/game.nit b/lib/android/game.nit
new file mode 100644 (file)
index 0000000..2d17054
--- /dev/null
@@ -0,0 +1,71 @@
+# 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.
+
+# Android services and implementation of app.nit for gamnit and mnit
+module game
+
+import platform
+import native_app_glue
+import dalvik
+private import log
+private import assets
+
+redef class App
+       redef fun init_window
+       do
+               super
+               on_create
+               on_restore_state
+               on_start
+       end
+
+       redef fun term_window
+       do
+               super
+               on_stop
+       end
+
+       # Is the application currently paused?
+       var paused = true
+
+       redef fun pause
+       do
+               paused = true
+               on_pause
+               super
+       end
+
+       redef fun resume
+       do
+               paused = false
+               on_resume
+               super
+       end
+
+       redef fun save_state do on_save_state
+
+       redef fun lost_focus
+       do
+               paused = true
+               super
+       end
+
+       redef fun gained_focus
+       do
+               paused = false
+               super
+       end
+
+       redef fun destroy do on_destroy
+end
index 9597eea..8aea378 100644 (file)
@@ -18,7 +18,7 @@
 module input_events
 
 import mnit::input
-import android
+import android::game
 
 in "C header" `{
        #include <android/log.h>
index be79eb5..c313efb 100644 (file)
@@ -20,4 +20,4 @@ module landscape is
        android_manifest_activity """android:screenOrientation="sensorLandscape" """
 end
 
-import platform
+import android
index e8f5720..0759c40 100644 (file)
@@ -17,4 +17,4 @@ module portrait is android_manifest_activity """
                android:screenOrientation="portrait"
 """
 
-import platform
+import android
index b969389..0681e4a 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# This module is used to manipulate android sensors
-# The sensor support is implemented in android_app module, so the user can enable the type of sensor he wants to use.
-# There is an example of how you can use the android sensors in nit/examples/mnit_ballz :
+# Access Android sensors
+#
+# Sensors are to be enabled when `App` is created.
+# The following example enables all sensors.
+# The events (`SensorEvent`, `ASensorAccelerometer`, `ASensorMagneticField`...)
+# are sent to the `input` callback of `App`
 #
 # ~~~~nitish
-# #FIXME rewrite the example
 # redef class App
-#      sensors_support_enabled = true
-#      accelerometer.enabled = true
-#      accelerometer.eventrate = 10000
-#      magnetic_field.enabled = true
-#      gyroscope.enabled = true
-#      light.enabled = true
-#      proximity.enabled = true
+#     init
+#     do
+#         sensors_support_enabled = true
+#         accelerometer.enabled = true
+#         accelerometer.eventrate = 10000
+#         magnetic_field.enabled = true
+#         gyroscope.enabled = true
+#         light.enabled = true
+#         proximity.enabled = true
+#     end
 # end
 # ~~~~
-#
-# In this example, we enable the sensor support, then enable all types of sensors supported by the API, directly with `App` attributes
-# As a result, you get all type of SensorEvent (ASensorAccelerometer, ASensorMagneticField ...) in the `input` callback of `App`
 module sensors
 
-import android
+import game
 import mnit
 
 in "C header" `{
index 42d90ae..7f22c78 100644 (file)
@@ -27,7 +27,6 @@ import app_base
 import core::error
 
 # Platform variations
-# TODO: move on the platform once qualified names are understand in the condition
 import linux::audio is conditional(linux)
 import android::audio is conditional(android)
 
index 4db4b22..ebebe83 100644 (file)
@@ -23,7 +23,6 @@ import app_base
 import serialization
 
 # Platform variations
-# TODO: move on the platform once qualified names are understand in the condition
 import linux::data_store is conditional(linux)
 import android::data_store is conditional(android)
 import ios::data_store is conditional(ios)
index 4efbb87..0c7d524 100644 (file)
@@ -18,9 +18,8 @@ module ui
 import app_base
 
 # Platform variations
-# TODO: move on the platform once qualified names are understand in the condition
 import linux::ui is conditional(linux)
-import android::ui is conditional(android) # FIXME it should be conditional to `android::platform`
+import android::ui is conditional(android)
 import ios::ui is conditional(ios)
 
 redef class App
index 3997c4a..c412e9f 100644 (file)
@@ -21,7 +21,7 @@ module display_android is
        android_manifest """<uses-feature android:glEsVersion="0x00020000"/>"""
 end
 
-import ::android
+import ::android::game
 intrude import android::load_image
 
 private import gamnit::egl
index 4d00782..45578f2 100644 (file)
 
 # Handles serialization and deserialization of objects to/from JSON
 #
-# ## Nity JSON
+# ## Writing JSON with metadata
 #
 # `JsonSerializer` write Nit objects that subclass `Serializable` to JSON,
-# and `JsonDeserializer` can read them. They both use meta-data added to the
+# and `JsonDeserializer` can read them. They both use metadata added to the
 # generated JSON to recreate the Nit instances with the exact original type.
 #
 # For more information on Nit serialization, see: ../serialization/README.md
 #
-# ## Plain JSON
+# ## Writing plain JSON
 #
 # The attribute `JsonSerializer::plain_json` triggers generating plain and
 # clean JSON. This format is easier to read for an human and a non-Nit program,
 # but it cannot be fully deserialized. It can still be read by services from
 # `json::static` and `json::dynamic`.
 #
-# A shortcut to this service is provided by `Serializable::to_plain_json`.
+# A shortcut to these writing services is provided by `Serializable::serialize_to_json`.
 #
 # ### Usage Example
 #
 # var bob = new Person("Bob", 1986)
 # var alice = new Person("Alice", 1978, bob)
 #
-# assert bob.to_plain_json == """
-# {"name": "Bob", "year_of_birth": 1986, "next_of_kin": null}"""
+# assert bob.serialize_to_json(pretty=true, plain=true) == """
+#{
+#      "name": "Bob",
+#      "year_of_birth": 1986,
+#      "next_of_kin": null
+#}"""
 #
-# assert alice.to_plain_json == """
-# {"name": "Alice", "year_of_birth": 1978, "next_of_kin": {"name": "Bob", "year_of_birth": 1986, "next_of_kin": null}}"""
+# assert alice.serialize_to_json(pretty=true, plain=true) == """
+#{
+#      "name": "Alice",
+#      "year_of_birth": 1978,
+#      "next_of_kin": {
+#              "name": "Bob",
+#              "year_of_birth": 1986,
+#              "next_of_kin": null
+#      }
+#}"""
 # ~~~
 #
 # ## JSON to Nit objects
 #
-# The `JsonDeserializer` support reading JSON code with minimal meta-data
+# The `JsonDeserializer` support reading JSON code with minimal metadata
 # to easily create Nit object from client-side code or configuration files.
 # Each JSON object must define the `__class` attribute with the corresponding
 # Nit class and the expected attributes with its name in Nit followed by its value.
 # var deserializer = new JsonDeserializer(json_code)
 #
 # var meet = deserializer.deserialize
+#
+# # Check for errors
+# assert deserializer.errors.is_empty
+#
 # assert meet isa MeetupConfig
 # assert meet.description == "My Awesome Meetup"
 # assert meet.max_participants == null
@@ -90,7 +106,8 @@ module serialization
 
 import ::serialization::caching
 private import ::serialization::engine_tools
-import static
+private import static
+private import string_parser
 
 # Serializer of Nit objects to Json string.
 class JsonSerializer
@@ -103,14 +120,14 @@ class JsonSerializer
        #
        # If `false`, the default, serialize to support deserialization:
        #
-       # * Write meta-data, including the types of the serialized objects so they can
+       # * Write metadata, including the types of the serialized objects so they can
        #   be deserialized to their original form using `JsonDeserializer`.
        # * Use references when an object has already been serialized so to not duplicate it.
        # * Support cycles in references.
        # * Preserve the Nit `Char` type as an object because it does not exist in JSON.
        # * The generated JSON is standard and can be read by non-Nit programs.
        #   However, some Nit types are not represented by the simplest possible JSON representation.
-       #   With the added meta-data, it can be complex to read.
+       #   With the added metadata, it can be complex to read.
        #
        # If `true`, serialize for other programs:
        #
@@ -119,7 +136,7 @@ class JsonSerializer
        # * Nit objects are serialized for every references, so they can be duplicated.
        #   It is easier to read but it creates a larger output.
        # * Does not support cycles, will replace the problematic references by `null`.
-       # * Does not serialize the meta-data needed to deserialize the objects
+       # * Does not serialize the metadata needed to deserialize the objects
        #   back to regular Nit objects.
        # * Keys of Nit `HashMap` are converted to their string representation using `to_s`.
        var plain_json = false is writable
@@ -162,7 +179,7 @@ class JsonSerializer
                        end
 
                        first_attribute = true
-                       object.serialize_to_json self
+                       object.accept_json_serializer self
                        first_attribute = false
 
                        if plain_json then open_objects.pop
@@ -220,10 +237,10 @@ class JsonDeserializer
        private var text: Text
 
        # Root json object parsed from input text.
-       private var root: nullable Jsonable is noinit
+       private var root: nullable Object is noinit
 
        # Depth-first path in the serialized object tree.
-       private var path = new Array[JsonObject]
+       private var path = new Array[Map[String, nullable Object]]
 
        # Last encountered object reference id.
        #
@@ -232,7 +249,7 @@ class JsonDeserializer
 
        init do
                var root = text.parse_json
-               if root isa JsonObject then path.add(root)
+               if root isa Map[String, nullable Object] then path.add(root)
                self.root = root
        end
 
@@ -268,7 +285,7 @@ class JsonDeserializer
                        return null
                end
 
-               if object isa JsonObject then
+               if object isa Map[String, nullable Object] then
                        var kind = null
                        if object.keys.has("__kind") then
                                kind = object["__kind"]
@@ -467,7 +484,7 @@ class JsonDeserializer
        # deserialized = deserializer.deserialize
        # assert deserialized isa MyError
        # ~~~
-       protected fun class_name_heuristic(json_object: JsonObject): nullable String
+       protected fun class_name_heuristic(json_object: Map[String, nullable Object]): nullable String
        do
                return null
        end
@@ -492,11 +509,44 @@ redef class Text
                return res
        end
 
-       redef fun serialize_to_json(v) do v.stream.write(to_json)
+       redef fun accept_json_serializer(v) do v.stream.write(to_json)
 end
 
 redef class Serializable
-       private fun serialize_to_json(v: JsonSerializer)
+
+       # Serialize `self` to JSON
+       #
+       # Set `plain = true` to generate standard JSON, without deserialization metadata.
+       # Use this option if the generated JSON will be read by other programs or humans.
+       # Use the default, `plain = false`, if the JSON is to be deserialized by a Nit program.
+       #
+       # Set `pretty = true` to generate pretty JSON for human eyes.
+       # Use the default, `pretty = false`, to generate minified JSON.
+       #
+       # This method should not be refined by subclasses,
+       # instead `accept_json_serializer` can customize the serialization of an object.
+       #
+       # See: `JsonSerializer`
+       fun serialize_to_json(plain, pretty: nullable Bool): String
+       do
+               var stream = new StringWriter
+               var serializer = new JsonSerializer(stream)
+               serializer.plain_json = plain or else false
+               serializer.pretty_json = pretty or else false
+               serializer.serialize self
+               stream.close
+               return stream.to_s
+       end
+
+       # Refinable service to customize the serialization of this class to JSON
+       #
+       # This method can be refined to customize the serialization by either
+       # writing pure JSON directly on the stream `v.stream` or
+       # by using other services of `JsonSerializer`.
+       #
+       # Most of the time, it is preferable to refine the method `core_serialize_to`
+       # which is used by all the serialization engines, not just JSON.
+       protected fun accept_json_serializer(v: JsonSerializer)
        do
                var id = v.cache.new_id_for(self)
                v.stream.write "\{"
@@ -515,59 +565,35 @@ redef class Serializable
                v.new_line_and_indent
                v.stream.write "\}"
        end
-
-       # Serialize this object to a JSON string with metadata for deserialization
-       fun to_json_string: String
-       do
-               var stream = new StringWriter
-               var serializer = new JsonSerializer(stream)
-               serializer.serialize self
-               stream.close
-               return stream.to_s
-       end
-
-       # Serialize this object to plain JSON
-       #
-       # This is a shortcut using `JsonSerializer::plain_json`,
-       # see its documentation for more information.
-       fun to_plain_json: String
-       do
-               var stream = new StringWriter
-               var serializer = new JsonSerializer(stream)
-               serializer.plain_json = true
-               serializer.serialize self
-               stream.close
-               return stream.to_s
-       end
 end
 
 redef class Int
-       redef fun serialize_to_json(v) do v.stream.write(to_s)
+       redef fun accept_json_serializer(v) do v.stream.write to_s
 end
 
 redef class Float
-       redef fun serialize_to_json(v) do v.stream.write(to_s)
+       redef fun accept_json_serializer(v) do v.stream.write to_s
 end
 
 redef class Bool
-       redef fun serialize_to_json(v) do v.stream.write(to_s)
+       redef fun accept_json_serializer(v) do v.stream.write to_s
 end
 
 redef class Char
-       redef fun serialize_to_json(v)
+       redef fun accept_json_serializer(v)
        do
                if v.plain_json then
-                       v.stream.write to_s.to_json
+                       to_s.accept_json_serializer v
                else
                        v.stream.write "\{\"__kind\": \"char\", \"__val\": "
-                       v.stream.write to_s.to_json
+                       to_s.accept_json_serializer v
                        v.stream.write "\}"
                end
        end
 end
 
 redef class NativeString
-       redef fun serialize_to_json(v) do to_s.serialize_to_json(v)
+       redef fun accept_json_serializer(v) do to_s.accept_json_serializer(v)
 end
 
 redef class Collection[E]
@@ -595,7 +621,7 @@ redef class Collection[E]
 end
 
 redef class SimpleCollection[E]
-       redef fun serialize_to_json(v)
+       redef fun accept_json_serializer(v)
        do
                # Register as pseudo object
                if not v.plain_json then
@@ -638,7 +664,7 @@ redef class SimpleCollection[E]
 end
 
 redef class Map[K, V]
-       redef fun serialize_to_json(v)
+       redef fun accept_json_serializer(v)
        do
                # Register as pseudo object
                var id = v.cache.new_id_for(self)
@@ -654,7 +680,7 @@ redef class Map[K, V]
                                v.new_line_and_indent
 
                                var k = key or else "null"
-                               v.stream.write k.to_s.to_json
+                               k.to_s.accept_json_serializer v
                                v.stream.write ": "
                                if not v.try_to_serialize(val) then
                                        assert val != null # null would have been serialized
index 565da1f..e333b85 100644 (file)
@@ -31,6 +31,9 @@ private import json_lexer
 interface Jsonable
        # Encode `self` in JSON.
        #
+       # This is a recursive method which can be refined by any subclasses.
+       # To write any `Serializable` object to JSON, see `serialize_to_json`.
+       #
        # SEE: `append_json`
        fun to_json: String is abstract
 
index c8f02e3..e78da16 100644 (file)
@@ -22,7 +22,7 @@ module android_app is android_manifest_activity """
 
 import mnit
 import mnit::opengles1
-import ::android
+import ::android::game
 intrude import ::android::input_events
 
 in "C" `{
index bccfd2c..158643a 100644 (file)
@@ -35,11 +35,13 @@ class RestfulAction
                if val == null then return null
 
                var deserializer = new JsonDeserializer(val)
+               var obj = deserializer.deserialize
+
                if deserializer.errors.not_empty then
                        print_error deserializer.errors.join("\n")
                        return null
                end
 
-               return deserializer.deserialize
+               return obj
        end
 end
diff --git a/lib/postgresql/native_postgres.nit b/lib/postgresql/native_postgres.nit
new file mode 100644 (file)
index 0000000..4d22201
--- /dev/null
@@ -0,0 +1,142 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2015-2016 Guilherme Mansur <guilhermerpmansur@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module native_postgres is pkgconfig("libpq")
+
+in "C header" `{
+  #include <libpq-fe.h>
+`}
+
+extern class ExecStatusType `{int`}
+  new empty           `{ return PGRES_EMPTY_QUERY; `}
+  new command_ok      `{ return PGRES_COMMAND_OK; `}
+  new tuples_ok       `{ return PGRES_TUPLES_OK; `}
+  new copy_out        `{ return PGRES_COPY_OUT; `}
+  new copy_in         `{ return PGRES_COPY_IN; `}
+  new bad_response    `{ return PGRES_BAD_RESPONSE; `}
+  new nonfatal_error  `{ return PGRES_NONFATAL_ERROR; `}
+  new fatal_error     `{ return PGRES_FATAL_ERROR; `}
+
+  fun is_ok: Bool `{return self == PGRES_TUPLES_OK || self == PGRES_COMMAND_OK; `}
+
+  redef fun to_s import NativeString.to_s `{
+    char * err = PQresStatus(self);
+    if(err == NULL) err = "";
+    return NativeString_to_s(err);
+  `}
+end
+
+extern class ConnStatusType `{int`}
+  new connection_ok `{ return CONNECTION_OK; `}
+  new connection_bad `{ return CONNECTION_BAD; `}
+
+  fun is_ok: Bool `{return self == CONNECTION_OK; `}
+end
+
+extern class PGResult `{PGresult *`}
+  # Frees the memory block associated with the result
+  fun clear `{PQclear(self); `}
+
+  # Returns the number of rows in the query result
+  fun ntuples:Int `{ return PQntuples(self); `}
+
+  # Returns the number of columns in each row of the query result
+  fun nfields:Int `{return PQnfields(self); `}
+
+  # Returns the ExecStatusType of a result
+  fun status: ExecStatusType `{ return PQresultStatus(self); `}
+
+  # Returns the field name of a given column_number
+  fun fname(column_number:Int):String import NativeString.to_s `{
+    return NativeString_to_s( PQfname(self, column_number));
+  `}
+
+  # Returns the column number associated with the column name
+  fun fnumber(column_name:String):Int import String.to_cstring `{
+    return PQfnumber(self, String_to_cstring(column_name));
+  `}
+
+  # Returns a single field value of one row of the result at row_number, column_number
+  fun value(row_number:Int, column_number:Int):String import NativeString.to_s `{
+    return NativeString_to_s(PQgetvalue(self, row_number, column_number));
+  `}
+
+  # Tests wether a field is a null value
+  fun is_null(row_number:Int, column_number: Int): Bool `{
+    return PQgetisnull(self, row_number, column_number);
+  `}
+
+end
+extern class NativePostgres `{PGconn *`}
+
+  # Connect to a new database using the conninfo string as a parameter
+  new connectdb(conninfo: String) import String.to_cstring `{
+    PGconn * self = NULL;
+    self = PQconnectdb(String_to_cstring(conninfo));
+    return self;
+  `}
+
+  # Submits a query to the server and waits for the result returns the ExecStatustype of the query
+  fun exec(query: String): PGResult import String.to_cstring `{
+    PGresult *res = PQexec(self, String_to_cstring(query));
+    return res;
+  `}
+
+  # Prepares a statement with the given parameters
+  fun prepare(stmt: String, query: String, nParams: Int):PGResult import String.to_cstring `{
+    const char * stmtName = String_to_cstring(stmt);
+    const char * queryStr = String_to_cstring(query);
+    PGresult * res = PQprepare(self, stmtName, queryStr, nParams, NULL);
+    return res;
+  `}
+
+  fun exec_prepared(stmt: String, nParams: Int, values: Array[String], pLengths: Array[Int], pFormats: Array[Int], resultFormat: Int):PGResult import String.to_cstring, Array[String].[], Array[Int].[] `{
+    const char * stmtName = String_to_cstring(stmt);
+    const char * paramValues[nParams];
+    int paramLengths[nParams];
+    int paramFormats[nParams];
+    int i;
+    for(i = 0; i < nParams; i++)
+      paramValues[i] = String_to_cstring(Array_of_String__index(values, i));
+    for(i = 0; i < nParams; i++)
+      paramLengths[i] = Array_of_Int__index(pLengths, i);
+    for(i = 0; i < nParams; i++)
+      paramFormats[i] = Array_of_Int__index(pFormats, i);
+    PGresult * res = PQexecPrepared(self, stmtName, nParams, paramValues, paramLengths, paramFormats, resultFormat);
+    return res;
+  `}
+
+  # Returns the error message of the last operation on the connection
+  fun error: String import NativeString.to_s `{
+    char * error = PQerrorMessage(self);
+    return NativeString_to_s(error);
+  `}
+
+  # Returns the status of this connection
+  fun status: ConnStatusType `{
+    return PQstatus(self);
+  `}
+
+  # Closes the connection to the server
+  fun finish  `{
+    PQfinish(self);
+  `}
+
+  # Closes the connection to the server and attempts to reconnect with the previously used params
+  fun reset `{
+    PQreset(self);
+  `}
+end
diff --git a/lib/postgresql/package.ini b/lib/postgresql/package.ini
new file mode 100644 (file)
index 0000000..dc82d85
--- /dev/null
@@ -0,0 +1,11 @@
+[package]
+name=postgresql
+tags=database,lib
+maintainer=Guilherme Mansur <guilhermerpmansur@gmail.com>
+license=Apache-2.0
+[upstream]
+browse=https://github.com/nitlang/nit/tree/master/lib/postgresql/
+git=https://github.com/nitlang/nit.git
+git.directory=lib/postgresql/
+homepage=http://nitlanguage.org
+issues=https://github.com/nitlang/nit/issues
\ No newline at end of file
index 169335a..c644c2e 100644 (file)
@@ -1,7 +1,7 @@
 [package]
 name=rubix
 tags=algo,lib
-maintainer=Lucas Bajolet<lucas.bajolet@hotmail.com>
+maintainer=Lucas Bajolet<r4pass@hotmail.com>
 license=Apache-2.0
 [upstream]
 browse=https://github.com/nitlang/nit/tree/master/lib/rubix.nit
index f441545..4b92786 100644 (file)
@@ -22,9 +22,14 @@ in "C header" `{
        #include <sqlite3.h>
 `}
 
+in "C" `{
+       // Return code of the last call to the constructor of `NativeSqlite3`
+       static int nit_sqlite_open_error = SQLITE_OK;
+`}
+
 redef class Sys
        # Last error raised when calling `Sqlite3::open`
-       var sqlite_open_error: nullable Sqlite3Code = null
+       fun sqlite_open_error: Sqlite3Code `{ return nit_sqlite_open_error; `}
 end
 
 extern class Sqlite3Code `{int`}
@@ -75,11 +80,10 @@ extern class Sqlite3Code `{int`}
 
        private fun native_to_s: NativeString `{
 #if SQLITE_VERSION_NUMBER >= 3007015
-               char *err = (char *)sqlite3_errstr(self);
+               return (char *)sqlite3_errstr(self);
 #else
-               char *err = "sqlite3_errstr supported only by version >= 3.7.15";
+               return "sqlite3_errstr is not supported in version < 3.7.15";
 #endif
-               return err;
        `}
 end
 
@@ -91,13 +95,8 @@ extern class NativeStatement `{sqlite3_stmt*`}
                return sqlite3_step(self);
        `}
 
-       fun column_name(i: Int) : String import NativeString.to_s `{
-               const char * name = (sqlite3_column_name(self, i));
-               if(name == NULL){
-                       name = "";
-               }
-               char * ret = (char *) name;
-               return NativeString_to_s(ret);
+       fun column_name(i: Int): NativeString `{
+               return (char *)sqlite3_column_name(self, i);
        `}
 
        # Number of bytes in the blob or string at row `i`
@@ -139,25 +138,18 @@ end
 extern class NativeSqlite3 `{sqlite3 *`}
 
        # Open a connection to a database in UTF-8
-       new open(filename: NativeString) import set_sys_sqlite_open_error `{
+       new open(filename: NativeString) `{
                sqlite3 *self = NULL;
                int err = sqlite3_open(filename, &self);
-               NativeSqlite3_set_sys_sqlite_open_error(self, (void*)(long)err);
-               // The previous cast is a hack, using non pointers in extern classes is not
-               // yet in the spec of the FFI.
+               nit_sqlite_open_error = err;
                return self;
        `}
 
-       # Utility method to set `Sys.sqlite_open_error`
-       private fun set_sys_sqlite_open_error(err: Sqlite3Code) do sys.sqlite_open_error = err
-
        # Has this DB been correctly opened?
        #
        # To know if it has been closed or interrupted, you must check for errors with `error`.
        fun is_valid: Bool do return not address_is_null
 
-       fun destroy do close
-
        # Close this connection
        fun close `{
 #if SQLITE_VERSION_NUMBER >= 3007014
@@ -171,18 +163,18 @@ extern class NativeSqlite3 `{sqlite3 *`}
        `}
 
        # Execute a SQL statement
-       fun exec(sql: String): Sqlite3Code import String.to_cstring `{
-               return sqlite3_exec(self, String_to_cstring(sql), 0, 0, 0);
+       fun exec(sql: NativeString): Sqlite3Code `{
+               return sqlite3_exec(self, sql, NULL, NULL, NULL);
        `}
 
        # Prepare a SQL statement
-       fun prepare(sql: String): nullable NativeStatement import String.to_cstring, NativeStatement.as nullable `{
+       fun prepare(sql: NativeString): NativeStatement `{
                sqlite3_stmt *stmt;
-               int res = sqlite3_prepare_v2(self, String_to_cstring(sql), -1, &stmt, 0);
+               int res = sqlite3_prepare_v2(self, sql, -1, &stmt, 0);
                if (res == SQLITE_OK)
-                       return NativeStatement_as_nullable(stmt);
+                       return stmt;
                else
-                       return null_NativeStatement();
+                       return NULL;
        `}
 
        fun last_insert_rowid: Int `{
index 62fe8e5..322e7d7 100644 (file)
@@ -57,8 +57,8 @@ class Sqlite3DB
        # Prepare and return a `Statement`, return `null` on error
        fun prepare(sql: Text): nullable Statement
        do
-               var native_stmt = native_connection.prepare(sql.to_s)
-               if native_stmt == null then return null
+               var native_stmt = native_connection.prepare(sql.to_cstring)
+               if native_stmt.address_is_null then return null
 
                var stmt = new Statement(native_stmt)
                open_statements.add stmt
@@ -68,7 +68,7 @@ class Sqlite3DB
        # Execute the `sql` statement and return `true` on success
        fun execute(sql: Text): Bool
        do
-               var err = native_connection.exec(sql.to_s)
+               var err = native_connection.exec(sql.to_cstring)
                return err.is_ok
        end
 
@@ -99,7 +99,7 @@ class Sqlite3DB
        do
                if not native_connection.is_valid then
                        var err = sys.sqlite_open_error
-                       if err == null then return null
+                       if err.is_ok then return null
                        return err.to_s
                end
 
@@ -182,7 +182,9 @@ class StatementEntry
        var name: String is lazy do
                assert statement_closed: statement.is_open
 
-               return statement.native_statement.column_name(index)
+               var cname = statement.native_statement.column_name(index)
+               assert not cname.address_is_null
+               return cname.to_s
        end
 
        # Get the value of this entry according to its Sqlite type
@@ -305,8 +307,9 @@ redef universal Int super Sqlite3Data end
 
 redef universal Float super Sqlite3Data end
 
-redef class String
-       super Sqlite3Data
+redef class String super Sqlite3Data end
+
+redef class Text
 
        # Return `self` between `'`s, escaping `\` and `'`
        #
@@ -315,6 +318,24 @@ redef class String
        do
                return "'{self.replace('\\', "\\\\").replace('\'', "''")}'"
        end
+
+       # Format the date represented by `self` into an escaped string for SQLite
+       #
+       # `self` must be composed of 1 to 3 integers separated by '-'.
+       # An incompatible format will result in an invalid date string.
+       #
+       #     assert "2016-5-1".to_sql_date_string == "'2016-05-01'"
+       #     assert "2016".to_sql_date_string == "'2016-01-01'"
+       fun to_sql_date_string: String
+       do
+               var parts = self.split("-")
+               for i in [parts.length .. 3[ do parts[i] = "1"
+
+               var year = parts[0].justify(4, 1.0, '0')
+               var month = parts[1].justify(2, 1.0, '0')
+               var day = parts[2].justify(2, 1.0, '0')
+               return "{year}-{month}-{day}".to_sql_string
+       end
 end
 
 # A Sqlite3 blob
index e01dbd2..4f9ad61 100644 (file)
@@ -118,6 +118,15 @@ Finally, standard markdown documents can be checked with:
 
     $ nitunit foo.md
 
+When testing, the environment variable `NIT_TESTING` is set to `true`.
+This flag can be used by libraries and program to prevent (or limit) the execution of dangerous pieces of code.
+
+~~~~~
+# NIT_TESTING is automatically set.
+#
+#     assert "NIT_TESTING".environ == "true"
+~~~~
+
 ## Working with `TestSuites`
 
 TestSuites are Nit files that define a set of TestCases for a particular module.
index 3ebc01e..d8a04ad 100644 (file)
@@ -229,6 +229,15 @@ class Catalog
        # Number of line of code by package
        var loc = new Counter[MPackage]
 
+       # Number of errors
+       var errors = new Counter[MPackage]
+
+       # Number of warnings and advices
+       var warnings = new Counter[MPackage]
+
+       # Documentation score (between 0 and 100)
+       var documentation_score = new Counter[MPackage]
+
        # Number of commits by package
        var commits = new Counter[MPackage]
 
@@ -338,9 +347,26 @@ class Catalog
                var mclasses = 0
                var mmethods = 0
                var loc = 0
+               var errors = 0
+               var warnings = 0
+               # The documentation value of each entity is ad hoc.
+               var entity_score = 0.0
+               var doc_score = 0.0
                for g in mpackage.mgroups do
                        mmodules += g.mmodules.length
+                       entity_score += 1.0
+                       if g.mdoc != null then doc_score += 1.0
                        for m in g.mmodules do
+                               var source = m.location.file
+                               if source != null then
+                                       for msg in source.messages do
+                                               if msg.level == 2 then
+                                                       errors += 1
+                                               else
+                                                       warnings += 1
+                                               end
+                                       end
+                               end
                                var am = modelbuilder.mmodule2node(m)
                                if am != null then
                                        var file = am.location.file
@@ -348,9 +374,21 @@ class Catalog
                                                loc += file.line_starts.length - 1
                                        end
                                end
+                               entity_score += 1.0
+                               if m.mdoc != null then doc_score += 1.0
                                for cd in m.mclassdefs do
+                                       var s = 0.2
+                                       if not cd.is_intro then s /= 10.0
+                                       if not cd.mclass.visibility <= private_visibility then s /= 10.0
+                                       entity_score += s
+                                       if cd.mdoc != null then doc_score += s
                                        mclasses += 1
                                        for pd in cd.mpropdefs do
+                                               s = 0.1
+                                               if not pd.is_intro then s /= 10.0
+                                               if not pd.mproperty.visibility <= private_visibility then s /= 10.0
+                                               entity_score += s
+                                               if pd.mdoc != null then doc_score += s
                                                if not pd isa MMethodDef then continue
                                                mmethods += 1
                                        end
@@ -361,11 +399,16 @@ class Catalog
                self.mclasses[mpackage] = mclasses
                self.mmethods[mpackage] = mmethods
                self.loc[mpackage] = loc
+               self.errors[mpackage] = errors
+               self.warnings[mpackage] = warnings
+               var documentation_score =  (100.0 * doc_score / entity_score).to_i
+               self.documentation_score[mpackage] = documentation_score
 
                #score += mmodules.score
                score += mclasses.score
                score += mmethods.score
                score += loc.score
+               score += documentation_score.score
 
                self.score[mpackage] = score.to_i
        end
index b3dcd32..9066e0f 100644 (file)
@@ -315,6 +315,15 @@ redef class Catalog
                end
                res.add "</ul>\n"
 
+               res.add "<h3>Quality</h3>\n<ul class=\"box\">\n"
+               var errors = errors[mpackage]
+               if errors > 0 then
+                       res.add "<li>{errors} errors</li>\n"
+               end
+               res.add "<li>{warnings[mpackage]} warnings</li>\n"
+               res.add "<li>{documentation_score[mpackage]}% documented</li>\n"
+               res.add "</ul>\n"
+
                res.add "<h3>Tags</h3>\n"
                var ts2 = new Array[String]
                for t in mpackage.tags do
@@ -474,6 +483,9 @@ redef class Catalog
                res.add "<th data-field=\"met\" data-sortable=\"true\">methods</th>\n"
                res.add "<th data-field=\"loc\" data-sortable=\"true\">lines</th>\n"
                res.add "<th data-field=\"score\" data-sortable=\"true\">score</th>\n"
+               res.add "<th data-field=\"errors\" data-sortable=\"true\">errors</th>\n"
+               res.add "<th data-field=\"warnings\" data-sortable=\"true\">warnings</th>\n"
+               res.add "<th data-field=\"doc\" data-sortable=\"true\">doc</th>\n"
                res.add "</tr></thead>"
                for p in mpackages do
                        res.add "<tr>"
@@ -493,6 +505,9 @@ redef class Catalog
                        res.add "<td>{mmethods[p]}</td>"
                        res.add "<td>{loc[p]}</td>"
                        res.add "<td>{score[p]}</td>"
+                       res.add "<td>{errors[p]}</td>"
+                       res.add "<td>{warnings[p]}</td>"
+                       res.add "<td>{documentation_score[p]}</td>"
                        res.add "</tr>\n"
                end
                res.add "</table>\n"
index 165ea13..176ec5f 100644 (file)
@@ -41,7 +41,7 @@ var args = toolcontext.option_context.rest
 var mmodules = modelbuilder.parse_full(args)
 modelbuilder.run_phases
 
-if opt_full.value then mmodules = model.mmodules
+if opt_full.value then mmodules = modelbuilder.parsed_modules
 
 var dir = opt_dir.value
 if dir != null then
index 522591d..cc9ea85 100644 (file)
@@ -63,6 +63,8 @@ if toolcontext.opt_gen_unit.value then
        exit(0)
 end
 
+"NIT_TESTING".setenv("true")
+
 var page = new HTMLTag("testsuites")
 
 if toolcontext.opt_full.value then mmodules = model.mmodules
index 850781e..4583868 100644 (file)
@@ -58,6 +58,13 @@ class Message
        # * enclose identifiers, keywords and pieces of code with back-quotes.
        var text: String
 
+       # The severity level
+       #
+       # * 0 is advices (see `ToolContext::advice`)
+       # * 1 is warnings (see `ToolContext::warning`)
+       # * 2 is errors (see `ToolContext::error`)
+       var level: Int
+
        # Comparisons are made on message locations.
        redef fun <(other: OTHER): Bool do
                if location == null then return true
@@ -123,9 +130,16 @@ redef class Location
                        messages = ms
                end
                ms.add m
+               var s = file
+               if s != null then s.messages.add m
        end
 end
 
+redef class SourceFile
+       # Errors and warnings associated to the whole source.
+       var messages = new Array[Message]
+end
+
 # Global context for tools
 class ToolContext
        # Number of errors
@@ -225,7 +239,7 @@ class ToolContext
        # Return the message (to add information)
        fun error(l: nullable Location, s: String): Message
        do
-               var m = new Message(l,null,s)
+               var m = new Message(l, null, s, 2)
                if messages.has(m) then return m
                if l != null then l.add_message m
                messages.add m
@@ -257,12 +271,12 @@ class ToolContext
        # Return the message (to add information) or null if the warning is disabled
        fun warning(l: nullable Location, tag: String, text: String): nullable Message
        do
-               if opt_warning.value.has("no-{tag}") then return null
-               if not opt_warning.value.has(tag) and opt_warn.value == 0 then return null
                if is_warning_blacklisted(l, tag) then return null
-               var m = new Message(l, tag, text)
+               var m = new Message(l, tag, text, 1)
                if messages.has(m) then return null
                if l != null then l.add_message m
+               if opt_warning.value.has("no-{tag}") then return null
+               if not opt_warning.value.has(tag) and opt_warn.value == 0 then return null
                messages.add m
                warning_count = warning_count + 1
                if opt_stop_on_first_error.value then check_errors
@@ -286,12 +300,12 @@ class ToolContext
        # Return the message (to add information) or null if the warning is disabled
        fun advice(l: nullable Location, tag: String, text: String): nullable Message
        do
-               if opt_warning.value.has("no-{tag}") then return null
-               if not opt_warning.value.has(tag) and opt_warn.value <= 1 then return null
                if is_warning_blacklisted(l, tag) then return null
-               var m = new Message(l, tag, text)
+               var m = new Message(l, tag, text, 0)
                if messages.has(m) then return null
                if l != null then l.add_message m
+               if opt_warning.value.has("no-{tag}") then return null
+               if not opt_warning.value.has(tag) and opt_warn.value <= 1 then return null
                messages.add m
                warning_count = warning_count + 1
                if opt_stop_on_first_error.value then check_errors
index 143e589..95abffa 100644 (file)
 <li><a href="https:&#47;&#47;github.com&#47;nitlang&#47;nit&#47;tree&#47;master&#47;tests&#47;test_prog">https:&#47;&#47;github.com&#47;nitlang&#47;nit&#47;tree&#47;master&#47;tests&#47;test_prog</a></li>
 <li><tt>https:&#47;&#47;github.com&#47;nitlang&#47;nit.git</tt></li>
 </ul>
+<h3>Quality</h3>
+<ul class="box">
+<li>28 warnings</li>
+<li>93% documented</li>
+</ul>
 <h3>Tags</h3>
 <a href="../index.html#tag_test">test</a>, <a href="../index.html#tag_game">game</a><h3>Requirements</h3>
 none<h3>Clients</h3>
index 20d487e..fdae4a5 100644 (file)
@@ -33,7 +33,7 @@
 </span></span><span class="line" id="L33">
 </span><span class="nc_cdef foldable" id="base_simple3$B"><span class="line" id="L34"><span class="nc_k">class</span> <span class="nc_t">B</span>
 </span><span class="nc_pdef foldable" id="base_simple3$B$_val"><a id="base_simple3$B$val"></a><a id="base_simple3$B$val="></a><span class="line" id="L35">     <span class="nc_k">var</span> <span class="nc_def nc_i">val</span><span>:</span> <span class="nc_t">Int</span>
-</span></span><span class="nc_pdef foldable" id="base_simple3$B$init"><span class="line" id="L36">     <span class="nc_k">init</span><span>(</span><span class="nc_v nc_i">v</span><span>:</span> <span class="nc_t">Int</span><span>)</span>
+</span></span><span class="nc_pdef foldable" id="base_simple3$B$init"><span class="line" id="L36">     <span class="nc_k popupable" style="border-bottom: solid 2px red" title="Messages" data-content="&lt;div&gt;&lt;div class=&#34;dropdown&#34;&gt; &lt;a data-toggle=&#34;dropdown&#34; href=&#34;#&#34;&gt;&lt;b&gt;1 message(s)&lt;&#47;b&gt; &lt;span class=&#34;caret&#34;&gt;&lt;&#47;span&gt;&lt;&#47;a&gt;&lt;ul class=&#34;dropdown-menu&#34; role=&#34;menu&#34; aria-labelledby=&#34;dLabel&#34;&gt;&lt;li&gt;Warning: init with signature in base_simple3$B&lt;&#47;li&gt;&lt;&#47;ul&gt;&lt;&#47;div&gt;&lt;&#47;div&gt;" data-toggle="popover">init</span><span>(</span><span class="nc_v nc_i">v</span><span>:</span> <span class="nc_t">Int</span><span>)</span>
 </span><span class="line" id="L37">    <span class="nc_k">do</span>
 </span><span class="line" id="L38">            <span class="nc_l">7</span><span>.</span><span class="nc_i">output</span>
 </span><span class="line" id="L39">            <span class="nc_k">self</span><span>.</span><span class="nc_i">val</span> <span>=</span> <span class="nc_v nc_i">v</span>
index fe9ca03..449fa7f 100644 (file)
@@ -53,7 +53,7 @@
 <h1>base_simple3$B$val=</h1>
 <pre><code><span class="nitcode"><span class="nc_pdef foldable" id="base_simple3$B$_val"><a id="base_simple3$B$val"></a><a id="base_simple3$B$val="></a><span class="line" id="L35">   <span class="nc_k">var</span> <span class="nc_def nc_i">val</span><span>:</span> <span class="nc_t">Int</span></span></span></span></code></pre>
 <h1>base_simple3$B$init</h1>
-<pre><code><span class="nitcode"><span class="nc_pdef foldable" id="base_simple3$B$init"><span class="line" id="L36">  <span class="nc_k">init</span><span>(</span><span class="nc_v nc_i">v</span><span>:</span> <span class="nc_t">Int</span><span>)</span>
+<pre><code><span class="nitcode"><span class="nc_pdef foldable" id="base_simple3$B$init"><span class="line" id="L36">  <span class="nc_k popupable" style="border-bottom: solid 2px red" title="Messages" data-content="&lt;div&gt;&lt;div class=&#34;dropdown&#34;&gt; &lt;a data-toggle=&#34;dropdown&#34; href=&#34;#&#34;&gt;&lt;b&gt;1 message(s)&lt;&#47;b&gt; &lt;span class=&#34;caret&#34;&gt;&lt;&#47;span&gt;&lt;&#47;a&gt;&lt;ul class=&#34;dropdown-menu&#34; role=&#34;menu&#34; aria-labelledby=&#34;dLabel&#34;&gt;&lt;li&gt;Warning: init with signature in base_simple3$B&lt;&#47;li&gt;&lt;&#47;ul&gt;&lt;&#47;div&gt;&lt;&#47;div&gt;" data-toggle="popover">init</span><span>(</span><span class="nc_v nc_i">v</span><span>:</span> <span class="nc_t">Int</span><span>)</span>
 </span><span class="line" id="L37">    <span class="nc_k">do</span>
 </span><span class="line" id="L38">            <span class="nc_l">7</span><span>.</span><span class="nc_i">output</span>
 </span><span class="line" id="L39">            <span class="nc_k">self</span><span>.</span><span class="nc_i">val</span> <span>=</span> <span class="nc_v nc_i">v</span>
 </span></span><span class="line" id="L33">
 </span><span class="nc_cdef foldable" id="base_simple3$B"><span class="line" id="L34"><span class="nc_k">class</span> <span class="nc_t">B</span>
 </span><span class="nc_pdef foldable" id="base_simple3$B$_val"><a id="base_simple3$B$val"></a><a id="base_simple3$B$val="></a><span class="line" id="L35">     <span class="nc_k">var</span> <span class="nc_def nc_i">val</span><span>:</span> <span class="nc_t">Int</span>
-</span></span><span class="nc_pdef foldable" id="base_simple3$B$init"><span class="line" id="L36">     <span class="nc_k">init</span><span>(</span><span class="nc_v nc_i">v</span><span>:</span> <span class="nc_t">Int</span><span>)</span>
+</span></span><span class="nc_pdef foldable" id="base_simple3$B$init"><span class="line" id="L36">     <span class="nc_k popupable" style="border-bottom: solid 2px red" title="Messages" data-content="&lt;div&gt;&lt;div class=&#34;dropdown&#34;&gt; &lt;a data-toggle=&#34;dropdown&#34; href=&#34;#&#34;&gt;&lt;b&gt;1 message(s)&lt;&#47;b&gt; &lt;span class=&#34;caret&#34;&gt;&lt;&#47;span&gt;&lt;&#47;a&gt;&lt;ul class=&#34;dropdown-menu&#34; role=&#34;menu&#34; aria-labelledby=&#34;dLabel&#34;&gt;&lt;li&gt;Warning: init with signature in base_simple3$B&lt;&#47;li&gt;&lt;&#47;ul&gt;&lt;&#47;div&gt;&lt;&#47;div&gt;" data-toggle="popover">init</span><span>(</span><span class="nc_v nc_i">v</span><span>:</span> <span class="nc_t">Int</span><span>)</span>
 </span><span class="line" id="L37">    <span class="nc_k">do</span>
 </span><span class="line" id="L38">            <span class="nc_l">7</span><span>.</span><span class="nc_i">output</span>
 </span><span class="line" id="L39">            <span class="nc_k">self</span><span>.</span><span class="nc_i">val</span> <span>=</span> <span class="nc_v nc_i">v</span>
index eb9da24..71dc9e7 100644 (file)
@@ -27,6 +27,6 @@
 # Nit: <MyClass i:123 s:hello f:123.456 a:[one, two] o:<null>>
 
 # JSON: not valid json
-# Errors: 'Error Parsing JSON: [1:1-1:2] Unexpected character 'n'; is acceptable instead: value'
+# Errors: 'Parsing error at line 1, position 1: Error: bad JSON entity'
 # Nit: null
 
diff --git a/tests/sav/test_postgres_native.res b/tests/sav/test_postgres_native.res
new file mode 100644 (file)
index 0000000..0c511fe
--- /dev/null
@@ -0,0 +1,3 @@
+aname   class   sex   
+Whale   mammal   1   
+Snake   reptile   0   
diff --git a/tests/test_postgres_native.nit b/tests/test_postgres_native.nit
new file mode 100644 (file)
index 0000000..ff3be6f
--- /dev/null
@@ -0,0 +1,73 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2016 Guilherme Mansur <guilhermerpmansur@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module test_postgres_native
+
+import postgresql::native_postgres
+
+var db = new NativePostgres.connectdb("dbname=postgres")
+assert postgres_open: db.status.is_ok else print_error db.error
+
+var result = db.exec("CREATE TABLE IF NOT EXISTS animals (aname TEXT PRIMARY KEY, class TEXT NOT NULL, sex INTEGER)")
+assert postgres_create_table: result.status.is_ok else print_error db.error
+
+result = db.exec("INSERT INTO animals VALUES('Whale', 'mammal', 1)")
+assert postgres_insert_1: result.status.is_ok else print_error db.error
+
+result = db.exec("INSERT INTO animals VALUES('Snake', 'reptile', 0)")
+assert postgres_insert_2: result.status.is_ok else print_error db.error
+
+result = db.exec("SELECT * FROM animals")
+assert postgres_select: result.status.is_ok else print_error db.error
+
+assert postgres_ntuples: result.ntuples == 2 else print_error db.error
+assert postgres_nfields: result.nfields == 3 else print_error db.error
+assert postgres_fname: result.fname(0) == "aname" else print_error db.error
+assert postgres_isnull: result.is_null(0,0) == false else print_error db.error
+assert postgres_value: result.value(0,0) == "Whale" else print_error db.error
+
+var cols: Int = result.nfields
+var rows: Int = result.ntuples
+var fields: String = ""
+for c in [0..cols[ do fields += result.fname(c) + "   "
+print fields
+for i in [0..rows[ do
+  fields = ""
+  for j in [0..cols[ do fields +=  result.value(i, j) + "   "
+  print fields
+end
+
+result = db.exec("DELETE FROM animals WHERE aname = 'Lioness'")
+assert postgres_delete_1: result.status.is_ok else print_error db.error
+
+result = db.exec("DELETE FROM animals WHERE aname = 'Snake'")
+assert postgres_delete_2: result.status.is_ok else print_error db.error
+
+result = db.prepare("PREPARED_INSERT", "INSERT INTO animals(aname, class, sex) VALUES ($1, $2, $3)", 3)
+assert postgres_prepare: result.status.is_ok else print_error db.error
+
+result = db.exec("DELETE FROM animals WHERE aname = 'Frog'")
+assert postgres_delete_3: result.status.is_ok else print_error db.error
+
+var values = ["Frog", "Anphibian", "1"]
+var lengths = [values[0].length, values[1].length, values[2].length]
+var formats = [0,0,0]
+result = db.exec_prepared("PREPARED_INSERT", 3, values, lengths, formats,0)
+assert postgres_exec_prepared: result.status.is_ok else print_error db.error
+
+result = db.exec("DROP TABLE animals")
+assert postgres_drop_table: result.status.is_ok else print_error db.error
+db.finish
index 0573a88..1e16375 100644 (file)
@@ -29,18 +29,18 @@ var select_req = "SELECT * FROM users"
 var db = new NativeSqlite3.open(filename.to_cstring)
 assert sqlite_open: db.error.is_ok
 
-db.exec(create_req)
+db.exec(create_req.to_cstring)
 assert sqlite_create_table: db.error.is_ok
 
-db.exec(insert_req_1)
+db.exec(insert_req_1.to_cstring)
 assert sqlite_insert_1: db.error.is_ok
 
-db.exec(insert_req_2)
+db.exec(insert_req_2.to_cstring)
 assert sqlite_insert_2: db.error.is_ok
 
-var stmt = db.prepare(select_req)
+var stmt = db.prepare(select_req.to_cstring)
 assert sqlite_select: db.error.is_ok
-if stmt == null then
+if stmt.address_is_null then
        print "Prepared failed got: {db.error.to_s}"
        abort
 end
@@ -56,8 +56,8 @@ db.close
 db = new NativeSqlite3.open(filename.to_cstring)
 assert sqlite_reopen: db.error.is_ok
 
-stmt = db.prepare(select_req)
+stmt = db.prepare(select_req.to_cstring)
 assert sqlite_reselect: db.error.is_ok
-assert stmt != null
+assert not stmt.address_is_null
 stmt.step
 assert sqlite_column_0_0_reopened: stmt.column_text(0).to_s == "Bob"