Merge: nitweb: fully integrate catalog
authorJean Privat <jean@pryen.org>
Thu, 28 Sep 2017 23:39:21 +0000 (19:39 -0400)
committerJean Privat <jean@pryen.org>
Thu, 28 Sep 2017 23:39:21 +0000 (19:39 -0400)
Some work to integrate the nit catalog with nitweb:
* make the catalog API more usable
* serves the catalog API through JSON/REST
* integrate with current frontend

New features:
* catalog integration
* new search process and ui
* pagination for results and catalog (faster!)
* cards cleaning
* tabs cleaning

Demo: DIY

Pull-Request: #2552

149 files changed:
contrib/asteronits/src/touch_ui.nit
contrib/jwrapper/src/code_generator.nit
contrib/jwrapper/src/model.nit
contrib/nitrpg/src/test_achievements.nit
contrib/nitrpg/src/test_events.nit
contrib/nitrpg/src/test_game.nit
contrib/nitrpg/src/test_helper.nit
contrib/nitrpg/src/test_listener.nit
contrib/nitrpg/src/test_statistics.nit
lib/bitmap/test_bitmap.nit
lib/core/bytes.nit
lib/core/core.nit
lib/core/text/test_abstract_text.nit
lib/gamnit/bmfont.nit
lib/gamnit/depth/depth_core.nit
lib/gamnit/depth/more_lights.nit
lib/gamnit/depth/more_materials.nit
lib/gamnit/depth/more_models.nit
lib/gamnit/display_android.nit
lib/gamnit/examples/fonts_showcase/assets/corner.png [new file with mode: 0644]
lib/gamnit/examples/fonts_showcase/src/fonts_showcase.nit
lib/gamnit/model_parsers/obj.nit
lib/gamnit/network/client.nit
lib/gamnit/network/common.nit
lib/gamnit/network/server.nit
lib/gamnit/textures.nit
lib/gamnit/virtual_gamepad/virtual_gamepad.nit
lib/github/test_github_curl.nit
lib/glesv2/glesv2.nit
lib/gmp/test_native_gmp.nit
lib/markdown/test_markdown.nit
lib/markdown/test_wikilinks.nit
lib/msgpack/ext.nit [new file with mode: 0644]
lib/msgpack/msgpack.nit [new file with mode: 0644]
lib/msgpack/msgpack_to_json.nit [new file with mode: 0644]
lib/msgpack/package.ini [new file with mode: 0644]
lib/msgpack/read.nit [new file with mode: 0644]
lib/msgpack/serialization_common.nit [new file with mode: 0644]
lib/msgpack/serialization_read.nit [new file with mode: 0644]
lib/msgpack/serialization_write.nit [new file with mode: 0644]
lib/msgpack/write.nit [new file with mode: 0644]
lib/popcorn/examples/angular/tests/test_example_angular.nit
lib/popcorn/examples/handlers/tests/test_example_post_handler.nit
lib/popcorn/examples/handlers/tests/test_example_query_string.nit
lib/popcorn/examples/hello_world/tests/test_example_hello.nit
lib/popcorn/examples/middlewares/tests/test_example_advanced_logger.nit
lib/popcorn/examples/middlewares/tests/test_example_html_error_handler.nit
lib/popcorn/examples/middlewares/tests/test_example_simple_error_handler.nit
lib/popcorn/examples/middlewares/tests/test_example_simple_logger.nit
lib/popcorn/examples/routing/tests/test_example_glob_route.nit
lib/popcorn/examples/routing/tests/test_example_param_route.nit
lib/popcorn/examples/routing/tests/test_example_router.nit
lib/popcorn/examples/sessions/tests/test_example_session.nit
lib/popcorn/examples/static_files/tests/test_example_static.nit
lib/popcorn/examples/static_files/tests/test_example_static_default.nit
lib/popcorn/examples/static_files/tests/test_example_static_multiple.nit
lib/popcorn/examples/templates/tests/test_example_templates.nit
lib/popcorn/pop_tests.nit
lib/popcorn/tests/test_pop_routes.nit
lib/popcorn/tests/test_popcorn.nit
lib/poset.nit
lib/sax/helpers/test_attributes_impl.nit
lib/sax/helpers/test_namespace_support.nit
lib/saxophonit/test_saxophonit.nit
lib/saxophonit/test_testing.nit
lib/saxophonit/testing.nit
lib/serialization/caching.nit
lib/serialization/engine_tools.nit
lib/serialization/inspect.nit [new file with mode: 0644]
lib/serialization/serialization.nit
lib/serialization/serialization_core.nit [new file with mode: 0644]
lib/template/test_macro.nit
lib/test_suite.ini [deleted file]
lib/test_suite.nit [deleted file]
share/man/nitunit.md
share/nitweb/directives/contributor-list.html [deleted file]
share/nitweb/directives/entity/card.html
share/nitweb/directives/search/card.html [deleted file]
share/nitweb/directives/search/field.html [deleted file]
share/nitweb/directives/ui/pagination.html [new file with mode: 0644]
share/nitweb/directives/ui/search-field.html
share/nitweb/index.html
share/nitweb/javascripts/catalog.js [new file with mode: 0644]
share/nitweb/javascripts/entities.js
share/nitweb/javascripts/grades.js
share/nitweb/javascripts/index.js [deleted file]
share/nitweb/javascripts/nitweb.js
share/nitweb/javascripts/ui.js
share/nitweb/stylesheets/cards.css
share/nitweb/stylesheets/nitweb.css
share/nitweb/stylesheets/search.css
share/nitweb/views/catalog/by_tags.html [deleted file]
share/nitweb/views/catalog/highlighted.html [deleted file]
share/nitweb/views/catalog/index.html
share/nitweb/views/catalog/most_required.html [deleted file]
share/nitweb/views/catalog/person.html [new file with mode: 0644]
share/nitweb/views/catalog/tag.html [new file with mode: 0644]
share/nitweb/views/doc/doc.html
share/nitweb/views/doc/entity.html
share/nitweb/views/doc/grades.html [new file with mode: 0644]
share/nitweb/views/search.html [new file with mode: 0644]
src/catalog.nit
src/compiler/test_coloring.nit
src/frontend/no_warning.nit
src/frontend/parse_annotations.nit
src/frontend/serialization_model_phase.nit
src/loader.nit
src/model/mmodule.nit
src/model/model.nit
src/model/model_base.nit
src/model/model_views.nit
src/model/model_visitor.nit
src/model/test_model_json.nit
src/nitcatalog.nit
src/testing/README.md
src/testing/testing_gen.nit
src/testing/testing_suite.nit
src/web/api_catalog.nit
src/web/api_model.nit
src/web/web_base.nit
tests/sav/nitce/test_inspect_serialization.res [new file with mode: 0644]
tests/sav/nitce/test_msgpack_deserialization_alt2.res [new file with mode: 0644]
tests/sav/nitce/test_msgpack_deserialization_alt3.res [new file with mode: 0644]
tests/sav/nitce/test_msgpack_deserialization_alt4.res [new file with mode: 0644]
tests/sav/niti/fixme/test_msgpack_deserialization_alt2.res [new file with mode: 0644]
tests/sav/nitserial_args1.res
tests/sav/nitunit_args1.res
tests/sav/nitunit_args11.res
tests/sav/nitunit_args12.res
tests/sav/nitunit_args2.res
tests/sav/nitunit_args3.res
tests/sav/nitunit_args9.res
tests/sav/syntax_annotations3.res
tests/sav/test_inspect_serialization.res [new file with mode: 0644]
tests/sav/test_msgpack_deserialization.res [new file with mode: 0644]
tests/sav/test_msgpack_deserialization_alt1.res [new file with mode: 0644]
tests/sav/test_msgpack_deserialization_alt2.res [new file with mode: 0644]
tests/sav/test_msgpack_deserialization_alt3.res [new file with mode: 0644]
tests/sav/test_msgpack_deserialization_alt4.res [new file with mode: 0644]
tests/test_inspect_serialization.nit [new file with mode: 0644]
tests/test_msgpack_deserialization.nit [new file with mode: 0644]
tests/test_nitunit4/test_bad_comp.nit
tests/test_nitunit4/test_bad_comp2.nit
tests/test_nitunit4/test_nitunit4.nit
tests/test_nitunit4/test_nitunit4_base.nit
tests/test_nitunit5.nit
tests/test_nitunit6.nit
tests/test_nitunit7.nit
tests/test_test_nitunit.nit

index bd8bfa5..5cae0d4 100644 (file)
@@ -25,8 +25,10 @@ redef class App
                super
 
                var gamepad = new VirtualGamepad
-               gamepad.add_dpad
-               gamepad.controls.first.as(DPad).show_down = false
+
+               var dpad = gamepad.add_dpad
+               if dpad != null then dpad.show_down = false
+
                gamepad.add_button("space", gamepad_spritesheet.fire)
                gamepad.visible = true
                self.gamepad = gamepad
index 4014c75..7d49919 100644 (file)
@@ -153,7 +153,7 @@ class CodeGenerator
                # Write the model to file next to the Nit module
                var model_path = file_name.strip_extension + ".jwrapper.bin"
                var model_stream = model_path.to_path.open_wo
-               var serializer = new BinarySerializer(model_stream)
+               var serializer = new MsgPackSerializer(model_stream)
                serializer.serialize model
                model_stream.close
        end
index e2c8ac8..b4d437a 100644 (file)
@@ -21,7 +21,7 @@ module model is serialize
 import more_collections
 import opts
 import poset
-import binary::serialization
+import msgpack
 
 import jtype_converter
 
@@ -107,16 +107,16 @@ class JavaType
        end
 
        # Short name of the class, mangled to remove `$` (e.g. `Set`)
-       var id: String is lazy do return identifier.last.replace("$", "")
+       var id: String is lazy, noserialize do return identifier.last.replace("$", "")
 
        # Full name of this class as used in java code (e.g. `java.lang.Set`)
-       var java_full_name: String is lazy do return identifier.join(".").replace("$", ".")
+       var java_full_name: String is lazy, noserialize do return identifier.join(".").replace("$", ".")
 
        # Full name of this class as used by jni (e.g. `android.graphics.BitmapFactory$Options`)
-       var jni_full_name: String is lazy do return identifier.join(".")
+       var jni_full_name: String is lazy, noserialize do return identifier.join(".")
 
        # Name of this class for the extern declaration in Nit (e.g. `java.lang.Set[]`)
-       var extern_equivalent: String is lazy do return jni_full_name + "[]" * array_dimension
+       var extern_equivalent: String is lazy, noserialize do return jni_full_name + "[]" * array_dimension
 
        # Full name of this class with arrays and generic values (e.g. `java.lang.Set<E>[]`)
        redef fun to_s do
@@ -270,12 +270,12 @@ class JavaModel
                        end
 
                        var file = model_path.to_path.open_ro
-                       var d = new BinaryDeserializer(file)
+                       var d = new MsgPackDeserializer(file)
                        var model = d.deserialize
                        file.close
 
                        if d.errors.not_empty then
-                               print_error "Error: failed to deserialize model file '{model_path}' with: {d.errors.join(", ")}"
+                               print_error "Error: failed to deserialize model file '{model_path}' with:\n* {d.errors.join("\n* ")}"
                                continue
                        end
 
@@ -291,7 +291,7 @@ class JavaModel
        end
 
        # Does this model have access to the `java.lang.Object`?
-       var knows_the_object_class: Bool = all_classes.keys.has("java.lang.Object") is lazy
+       var knows_the_object_class: Bool = all_classes.keys.has("java.lang.Object") is lazy, noserialize
 
        # Add a class in `classes`
        fun add_class(jclass: JavaClass)
@@ -487,7 +487,7 @@ class NitModuleRef
        var path: String
 
        # Name of the module
-       var name: String is lazy do return path.basename(".nit")
+       var name: String is lazy, noserialize do return path.basename(".nit")
 
        redef fun to_s do return self.name
        redef fun ==(other) do return other isa NitModuleRef and self.path == other.path
@@ -558,7 +558,7 @@ redef class Sys
        var opt_extern_class_prefix = new OptionString("Prefix to extern classes (By default uses the full namespace)", "-p")
 
        # Prefix used to name extern classes, if `null` use the full namespace
-       var extern_class_prefix: nullable String is lazy do return opt_extern_class_prefix.value
+       var extern_class_prefix: nullable String is lazy, noserialize do return opt_extern_class_prefix.value
 
        # Libraries to search for existing wrappers
        var opt_libs = new OptionArray("Paths to libraries with wrappers of Java classes ('auto' to use the full Nit lib)", "-i")
index c210c60..41d9dd1 100644 (file)
 # limitations under the License.
 
 # Test module for `achievements.nit`
-module test_achievements is test_suite
+module test_achievements is test
 
 import test_helper
 import achievements
 
 class TestGame
        super NitrpgTestHelper
+       test
 
-       fun test_add_achievement do
+       fun test_add_achievement is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var a1 = new Achievement(game, "test_id1", "test_name", "test_desc", 15)
@@ -33,7 +34,7 @@ class TestGame
                assert game.load_achievements.length == 2
        end
 
-       fun test_load_achievement do
+       fun test_load_achievement is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var a1 = new Achievement(game, "test_id1", "test_name", "test_desc", 15)
@@ -43,7 +44,7 @@ class TestGame
                assert game.load_achievement(a2.id) == null
        end
 
-       fun test_load_achievements do
+       fun test_load_achievements is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var a1 = new Achievement(game, "test_id1", "test_name", "test_desc", 15)
@@ -61,8 +62,9 @@ end
 
 class TestPlayer
        super NitrpgTestHelper
+       test
 
-       fun test_add_achievement do
+       fun test_add_achievement is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var player1 = new Player(game, "Morriar")
@@ -73,7 +75,7 @@ class TestPlayer
                assert player1.load_achievements.length == 2
        end
 
-       fun test_load_achievement do
+       fun test_load_achievement is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var player1 = new Player(game, "Morriar")
@@ -88,7 +90,7 @@ class TestPlayer
                assert player2.load_achievement(a1.id) == null
        end
 
-       fun test_load_achievements do
+       fun test_load_achievements is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var player1 = new Player(game, "Morriar")
@@ -108,8 +110,9 @@ end
 
 class TestAchievement
        super NitrpgTestHelper
+       test
 
-       fun test_init do
+       fun test_init is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var a = new Achievement(game, "test_id", "test_name", "test_desc", 15)
@@ -119,7 +122,7 @@ class TestAchievement
                assert a.reward == 15
        end
 
-       fun test_init_from_json do
+       fun test_init_from_json is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var json = """{
index 640e8dc..efe24cf 100644 (file)
 # limitations under the License.
 
 # Test module for `events.nit`
-module test_events is test_suite
+module test_events is test
 
 import test_helper
 import events
 
 class TestGame
        super NitrpgTestHelper
+       test
 
-       fun test_add_event do
+       fun test_add_event is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var event1 = new GameEvent(game, "test_kind", new JsonObject)
@@ -33,7 +34,7 @@ class TestGame
                assert game.load_events.length == 2
        end
 
-       fun test_load_event do
+       fun test_load_event is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var event1 = new GameEvent(game, "test_kind", new JsonObject)
@@ -43,7 +44,7 @@ class TestGame
                assert game.load_event(event2.internal_id) == null
        end
 
-       fun test_load_events do
+       fun test_load_events is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var event1 = new GameEvent(game, "test_kind", new JsonObject)
@@ -61,8 +62,9 @@ end
 
 class TestPlayer
        super NitrpgTestHelper
+       test
 
-       fun test_add_event do
+       fun test_add_event is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var player1 = new Player(game, "Morriar")
@@ -75,7 +77,7 @@ class TestPlayer
                assert player2.load_events.length == 0
        end
 
-       fun test_load_event do
+       fun test_load_event is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var player1 = new Player(game, "Morriar")
@@ -90,7 +92,7 @@ class TestPlayer
                assert player2.load_event(event1.internal_id) == null
        end
 
-       fun test_load_events do
+       fun test_load_events is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var player1 = new Player(game, "Morriar")
@@ -110,15 +112,16 @@ end
 
 class TestGameEvent
        super NitrpgTestHelper
+       test
 
-       fun test_init do
+       fun test_init is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var event = new GameEvent(game, "test_kind", new JsonObject)
                assert event.to_json_object["kind"] == "test_kind"
        end
 
-       fun test_init_from_json do
+       fun test_init_from_json is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var json = """{
index 25ddcad..c2aa677 100644 (file)
 # limitations under the License.
 
 # Test module for `game.nit`.
-module test_game is test_suite
+module test_game is test
 
 import test_helper
 
 class TestGame
        super NitrpgTestHelper
+       test
 
-       fun test_add_player do
+       fun test_add_player is test do
                var db = gen_test_db
                var game = load_game("privat/nit", db)
                var users = ["Morriar", "xymus"]
@@ -36,7 +37,7 @@ class TestGame
                end
        end
 
-       fun test_load_player do
+       fun test_load_player is test do
                var db = gen_test_db
                var game = load_game("privat/nit", db)
                var ogame = load_game("Morriar/nit", db)
@@ -52,7 +53,7 @@ class TestGame
                assert ogame.load_player("Morriar") == null
        end
 
-       fun test_load_players do
+       fun test_load_players is test do
                var db = gen_test_db
                var game = load_game("privat/nit", db)
                var ogame = load_game("Morriar/nit", db)
@@ -72,8 +73,9 @@ end
 
 class TestPlayer
        super NitrpgTestHelper
+       test
 
-       fun test_init do
+       fun test_init is test do
                var db = gen_test_db
                var game = load_game("privat/nit", db)
                var player = new Player(game, "Morriar")
@@ -82,7 +84,7 @@ class TestPlayer
                assert player.nitcoins == 0
        end
 
-       fun test_init_from_json do
+       fun test_init_from_json is test do
                var db = gen_test_db
                var game = load_game("privat/nit", db)
                var json = """{"name": "Morriar", "nitcoins": 10}""".parse_json
@@ -92,7 +94,7 @@ class TestPlayer
                assert player.nitcoins == 10
        end
 
-       fun test_save do
+       fun test_save is test do
                var db = gen_test_db
                var game = load_game("privat/nit", db)
                var json = """{"name": "Morriar", "nitcoins": 10}""".parse_json.as(JsonObject)
@@ -101,7 +103,7 @@ class TestPlayer
                assert game.db.collection("players").find(json) != null
        end
 
-       fun test_game_add_player do
+       fun test_game_add_player is test do
                var db = gen_test_db
                var game = load_game("privat/nit", db)
                game.add_player(game.api.load_user("Morriar").as(not null))
@@ -109,7 +111,7 @@ class TestPlayer
                assert game.db.collection("players").find(json) != null
        end
 
-       fun test_game_load_player do
+       fun test_game_load_player is test do
                var db = gen_test_db
                var game = load_game("privat/nit", db)
                var json = """{"name": "Morriar", "nitcoins": 10}""".parse_json.as(JsonObject)
@@ -123,8 +125,9 @@ end
 
 class TestUser
        super NitrpgTestHelper
+       test
 
-       fun test_player do
+       fun test_player is test do
                var db = gen_test_db
                var api = new GithubAPI(get_github_oauth)
                var game = load_game("privat/nit", db)
index ecdc358..919ccfc 100644 (file)
 # Test tools for NitRPG.
 module test_helper
 
-import test_suite
 import game
 import github::cache
 
 # Used to factorize test treatments.
 abstract class NitrpgTestHelper
-       super TestSuite
 
        # Github API client
        var api: GithubAPI do
@@ -70,5 +68,6 @@ abstract class NitrpgTestHelper
                db.drop
        end
 
-       redef fun after_test do drop_test_db
+       # Drop the databse after each test
+       fun after_test is after do drop_test_db
 end
index 9cfa059..d70f3aa 100644 (file)
@@ -15,7 +15,7 @@
 # limitations under the License.
 
 # Test module for `listener.nit`
-module test_listener is test_suite
+module test_listener is test
 
 import test_helper
 import reactors
@@ -24,6 +24,7 @@ import events_generator
 
 private class DummyListener
        super NitrpgTestHelper
+       test
 
        var reactors = new Array[GameReactor]
 
@@ -39,12 +40,13 @@ end
 
 class TestListener
        super NitrpgTestHelper
+       test
 
        var generator = new EventsGenerator(api)
 
        var repo: Repo is lazy do return load_repo("Morriar/nit")
 
-       fun test_game_issue_stats do
+       fun test_game_issue_stats is test do
                var db = gen_test_db
                var l = new DummyListener
                l.add_reactor(new StatisticsReactor)
@@ -66,7 +68,7 @@ class TestListener
                assert game.stats.overall["issues_open"] == 1
        end
 
-       fun test_player_issue_stats do
+       fun test_player_issue_stats is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var l = new DummyListener
@@ -89,7 +91,7 @@ class TestListener
                assert player.stats.overall["issues_open"] == 1
        end
 
-       fun test_game_pr_stats do
+       fun test_game_pr_stats is test do
                var db = gen_test_db
                var l = new DummyListener
                l.add_reactor(new StatisticsReactor)
@@ -121,7 +123,7 @@ class TestListener
                assert game.stats.overall["commits"] == 2
        end
 
-       fun test_game_issue_comment_stats do
+       fun test_game_issue_comment_stats is test do
                var db = gen_test_db
                var l = new DummyListener
                l.add_reactor(new StatisticsReactor)
@@ -143,7 +145,7 @@ class TestListener
                assert game.stats.overall["reviews"] == 1
        end
 
-       fun test_player_pull_reactor do
+       fun test_player_pull_reactor is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var l = new DummyListener
@@ -168,7 +170,7 @@ class TestListener
                assert player.stats.overall["nitcoins"] == 12
        end
 
-       fun test_player_review_reactor do
+       fun test_player_review_reactor is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var l = new DummyListener
@@ -211,7 +213,7 @@ class TestListener
                assert player.stats.overall["nitcoins"] == 4
        end
 
-       fun test_X_issues_achievements do
+       fun test_X_issues_achievements is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var l = new DummyListener
@@ -234,7 +236,7 @@ class TestListener
                assert player.stats.overall["nitcoins"] == 1110
        end
 
-       fun test_X_pulls_achievements do
+       fun test_X_pulls_achievements is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var l = new DummyListener
@@ -257,7 +259,7 @@ class TestListener
                assert player.stats.overall["nitcoins"] == 1110
        end
 
-       fun test_X_commits_achievements do
+       fun test_X_commits_achievements is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var l = new DummyListener
@@ -283,7 +285,7 @@ class TestListener
                assert player.stats.overall["nitcoins"] == 11110
        end
 
-       fun test_X_comments_achievements do
+       fun test_X_comments_achievements is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var l = new DummyListener
@@ -308,7 +310,7 @@ class TestListener
                assert player.stats.overall["nitcoins"] == 1110
        end
 
-    fun test_issues_achievements do
+    fun test_issues_achievements is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var l = new DummyListener
@@ -325,7 +327,7 @@ class TestListener
                assert player.stats.overall["nitcoins"] == 20
        end
 
-       fun test_comments_reactor do
+       fun test_comments_reactor is test do
                var db = gen_test_db
                var game = load_game("Morriar/nit", db)
                var l = new DummyListener
index 766726c..9bdf8fa 100644 (file)
 # limitations under the License.
 
 # Test module for `stats.nit`
-module test_statistics is test_suite
+module test_statistics is test
 
 import test_helper
 import statistics
 
 class TestGame
        super NitrpgTestHelper
+       test
 
-       fun test_game_stats do
+       fun test_game_stats is test do
                var db = gen_test_db
                var game = load_game("privat/nit", db)
                var stats = game.stats
@@ -40,8 +41,9 @@ end
 
 class TestPlayer
        super NitrpgTestHelper
+       test
 
-       fun test_player_stats do
+       fun test_player_stats is test do
                var db = gen_test_db
                var game = load_game("privat/nit", db)
                var player = new Player(game, "Morriar")
@@ -59,8 +61,9 @@ end
 
 class TestGameStats
        super NitrpgTestHelper
+       test
 
-       fun test_init_from_json do
+       fun test_init_from_json is test do
                var db = gen_test_db
                var game = load_game("privat/nit", db)
                var owner = new Player(game, "Morriar")
index 4475689..2f76da6 100644 (file)
 #
 #
 # A module for testing the classes in the bitmap module
-module test_bitmap is test_suite
+module test_bitmap is test
 
 import bitmap
-import test_suite
 
 class TestBitmap
-       super TestSuite
+       test
 
-       fun test_grayscale do
+       fun test_grayscale is test do
                var bitmap = new Bitmap.with_size(400, 300)
                for y in [0..300] do
                        for x in [0..200] do bitmap.set_pixel(x, y, 0x0077AAAA)
index e375e60..84276ac 100644 (file)
@@ -328,35 +328,48 @@ class Bytes
                return new FlatString.full(ns, elen, 0, elen)
        end
 
-       # Interprets `self` as a big-endian positive integer.
+       # Interprets `self` as a big-endian integer (unsigned by default)
        #
        # ~~~
        # var b = "0102".hexdigest_to_bytes
        # assert b.to_i == 258
+       #
+       # assert   "01".hexdigest_to_bytes.to_i == 1
+       # assert   "FF".hexdigest_to_bytes.to_i == 255
+       # assert "0000".hexdigest_to_bytes.to_i == 0
        # ~~~
        #
-       # Nul bytes on the left are trimmed.
-       # 0 is returned for an empty Bytes object.
+       # If `self.is_empty`, 0 is returned.
        #
        # ~~~
-       # assert "01".hexdigest_to_bytes.to_i == 1
-       # assert "0001".hexdigest_to_bytes.to_i == 1
-       #
-       # assert "0000".hexdigest_to_bytes.to_i == 0
-       # assert "00".hexdigest_to_bytes.to_i == 0
        # assert "".hexdigest_to_bytes.to_i == 0
        # ~~~
        #
+       # If `signed == true`, the bytes are read as a signed integer.
+       # As usual, the sign bit is the left most bit, no matter the
+       # `length` of `self`.
+       #
+       # ~~~
+       # assert     "01".hexdigest_to_bytes.to_i(true) ==      1
+       # assert     "FF".hexdigest_to_bytes.to_i(true) ==     -1
+       # assert   "00FF".hexdigest_to_bytes.to_i(true) ==    255
+       # assert     "E0".hexdigest_to_bytes.to_i(true) ==    -32
+       # assert   "FE00".hexdigest_to_bytes.to_i(true) ==   -512
+       # assert "FEFEFE".hexdigest_to_bytes.to_i(true) == -65794
+       # ~~~
+       #
        # `Int::to_bytes` is a loosely reverse method.
        #
        # ~~~
        # assert b.to_i.to_bytes == b
        # assert (b.to_i + 1).to_bytes.hexdigest == "0103"
        # assert "0001".hexdigest_to_bytes.to_i.to_bytes.hexdigest == "01"
+       #
+       # assert (-32).to_bytes.to_i(true) == -32
        # ~~~
        #
        # Warning: `Int` might overflow for bytes with more than 60 bits.
-       fun to_i: Int do
+       fun to_i(signed: nullable Bool): Int do
                var res = 0
                var i = 0
                while i < length do
@@ -364,6 +377,18 @@ class Bytes
                        res += self[i].to_i
                        i += 1
                end
+
+               # Two's complement is `signed`
+               if signed == true and not_empty and first > 0x80u8 then
+                       var ff = 0
+                       for j in [0..length[ do
+                               ff *= 0x100
+                               ff += 0xFF
+                       end
+
+                       res = -((res ^ ff) + 1)
+               end
+
                return res
        end
 
@@ -651,7 +676,7 @@ private class BytesIterator
 end
 
 redef class Int
-       # A big-endian representation of self.
+       # A signed big-endian representation of `self`
        #
        # ~~~
        # assert     1.to_bytes.hexdigest ==     "01"
@@ -661,43 +686,86 @@ redef class Int
        # assert 65536.to_bytes.hexdigest == "010000"
        # ~~~
        #
+       # Negative values are converted to their two's complement.
+       # Be careful as the result can be ambiguous.
+       #
+       # ~~~
+       # assert     (-1).to_bytes.hexdigest ==     "FF"
+       # assert    (-32).to_bytes.hexdigest ==     "E0"
+       # assert   (-512).to_bytes.hexdigest ==   "FE00"
+       # assert (-65794).to_bytes.hexdigest == "FEFEFE"
+       # ~~~
+       #
+       # Optionally, set `n_bytes` to the desired number of bytes in the output.
+       # This setting can disambiguate the result between positive and negative
+       # integers. Be careful with this parameter as the result may overflow.
+       #
+       # ~~~
+       # assert        1.to_bytes(2).hexdigest ==     "0001"
+       # assert    65535.to_bytes(2).hexdigest ==     "FFFF"
+       # assert     (-1).to_bytes(2).hexdigest ==     "FFFF"
+       # assert   (-512).to_bytes(4).hexdigest == "FFFFFE00"
+       # assert 0x123456.to_bytes(2).hexdigest ==     "3456"
+       # ~~~
+       #
        # For 0, a Bytes object with single nul byte is returned (instead of an empty Bytes object).
        #
        # ~~~
        # assert 0.to_bytes.hexdigest == "00"
        # ~~~
        #
-       # `Bytes::to_i` can be used to do the reverse operation.
+       # For positive integers, `Bytes::to_i` can reverse the operation.
        #
        # ~~~
        # assert 1234.to_bytes.to_i == 1234
        # ~~~
        #
        # Require self >= 0
-       fun to_bytes: Bytes do
-               if self == 0 then return "\0".to_bytes
-               assert self > 0
+       fun to_bytes(n_bytes: nullable Int): Bytes do
+
+               # If 0, force using at least one byte
+               if self == 0 and n_bytes == null then n_bytes = 1
 
                # Compute the len (log256)
                var len = 1
                var max = 256
-               while self >= max do
+               var s = self.abs
+               while s >= max do
                        len += 1
                        max *= 256
                end
 
+               # Two's complement
+               s = self
+               if self < 0 then
+                       var ff = 0
+                       for j in [0..len[ do
+                               ff *= 0x100
+                               ff += 0xFF
+                       end
+
+                       s = ((-self) ^ ff) + 1
+               end
+
+               # Cut long values
+               if n_bytes != null and len > n_bytes then len = n_bytes
+
                # Allocate the buffer
-               var res = new Bytes.with_capacity(len)
-               for i in [0..len[ do res[i] = 0u8
+               var cap = n_bytes or else len
+               var res = new Bytes.with_capacity(cap)
+
+               var filler = if self < 0 then 0xFFu8 else 0u8
+               for i in [0..cap[ do res[i] = filler
 
                # Fill it starting with the end
-               var i = len
-               var sum = self
-               while i > 0 do
+               var i = cap
+               var sum = s
+               while i > cap - len do
                        i -= 1
                        res[i] = (sum % 256).to_b
                        sum /= 256
                end
+
                return res
        end
 end
index 97a4271..6eaff6e 100644 (file)
 
 # Standard classes and methods used by default by Nit programs and libraries.
 # This module is implicitly imported by every module.
-module core
+module core is
+       new_annotation test
+       new_annotation before
+       new_annotation before_all
+       new_annotation after
+       new_annotation after_all
+end
 
 import posix
 import environ
index af7dc00..ec9bda6 100644 (file)
@@ -8,21 +8,20 @@
 # You  are  allowed  to  redistribute it and sell it, alone or is a part of
 # another product.
 
-module test_abstract_text is test_suite
+module test_abstract_text is test
 
-import test_suite
 import text
 intrude import ropes
 
 class TestText
-       super TestSuite
+       test
 
        private var factories: Collection[TextFactory] = [
                new ConcatFactory,
                new FlatBufferFactory
        : TextFactory]
 
-       fun test_escape_to_c do
+       fun test_escape_to_c is test do
                for f in factories do
                        assert f.create("abAB12<>&").escape_to_c       == "abAB12<>&"
                        assert f.create("\n\"'\\").escape_to_c         == "\\n\\\"\\'\\\\"
index c2c8be5..cdeef2d 100644 (file)
@@ -367,6 +367,10 @@ class BMFontAsset
                var dy = 0.0
                var text_width = 0.0
                var line_sprites = new Array[Sprite]
+               var height = 0.0
+
+               # Has the current line height been added to `height`?
+               var line_height_counted = false
 
                # TextSprite customization
                var max_width = text_sprites.max_width
@@ -397,14 +401,21 @@ class BMFontAsset
                                dy -= line_height
                                if max_height != null and max_height < -dy + line_height then break
                                dx = 0.0
+                               if not line_height_counted then
+                                       # Force to account for empty lines
+                                       height += line_height
+                               end
+                               line_height_counted = false
                                prev_char = null
                                continue
                        else if c == pld then
                                dy -= partial_line_skip
+                               height += partial_line_skip
                                word_break = true
                                continue
                        else if c == plu then
                                dy += partial_line_skip
+                               height -= partial_line_skip # We could keep two heights and return the max
                                word_break = true
                                continue
                        else if c.is_whitespace then
@@ -477,24 +488,27 @@ class BMFontAsset
                                                var w = text[wi]
 
                                                if w == '\n' or w == pld or w == plu or w.is_whitespace or (in_link and w == ']') then break
+
+                                               if not desc.chars.keys.has(w) then
+                                                       var rc = replacement_char
+                                                       if rc == null then continue
+                                                       w = rc
+                                               end
+
                                                word_len += advance(prev_w, w) * scale
                                                prev_w = w
                                        end
 
                                        # Would the line be too long?
                                        if dx + word_len > max_width then
-                                               if text_sprites.wrap then
-                                                       # Wrap
-                                                       justify(line_sprites, text_sprites.align, dx)
-                                                       dy -= line_height
-                                                       if max_height != null and max_height < -dy + line_height then break
-                                                       dx = 0.0
-                                               else
-                                                       # Cut short
-                                                       justify(line_sprites, text_sprites.align, dx)
-                                                       dy -= line_height
-                                                       if max_height != null and max_height < -dy + line_height then break
-                                                       dx = 0.0
+                                               justify(line_sprites, text_sprites.align, dx)
+                                               dy -= line_height
+                                               if max_height != null and max_height < -dy + line_height then break
+                                               dx = 0.0
+                                               line_height_counted = false
+
+                                               if not text_sprites.wrap then
+                                                       # Cut short, skip everything until the next new line
                                                        while c != '\n' and i < text.length - 1 do
                                                                i += 1
                                                                c = text[i]
@@ -531,6 +545,12 @@ class BMFontAsset
                        prev_char = c
 
                        text_width = text_width.max(dx)
+
+                       if not line_height_counted then
+                               # Increase `height` only once per line iff there's a caracter
+                               line_height_counted = true
+                               height += line_height
+                       end
                end
 
                justify(line_sprites, text_sprites.align, dx)
@@ -542,7 +562,7 @@ class BMFontAsset
                end
 
                text_sprites.width = text_width.max(dx)
-               text_sprites.height = dy + line_height
+               text_sprites.height = height
        end
 
        # Character replacing other characters missing from the font
index 56e0575..588b321 100644 (file)
@@ -113,6 +113,9 @@ abstract class Model
        # Usually, there is one `LeafModel` per material.
        # At each frame, each material is asked to draw all the live `LeafModel` instaces.
        fun leaves: Array[LeafModel] is abstract
+
+       # Sub-models with names, usually declared in the asset file
+       var named_parts = new Map[Text, Model]
 end
 
 # Model composed of one or many other `LeafModel`
index 7637e02..3512f27 100644 (file)
@@ -16,6 +16,7 @@
 module more_lights
 
 import depth_core
+intrude import cameras_cache
 
 # TODO
 #class PointLight
@@ -57,10 +58,8 @@ private class ParallelLightCamera
                return view
        end
 
-       redef fun mvp_matrix
+       private fun create_mvp_matrix: Matrix
        do
-               # TODO cache
-
                var near = -light.depth/2.0
                var far = light.depth/2.0
 
@@ -72,4 +71,40 @@ private class ParallelLightCamera
                                                       near, far)
                return view * projection
        end
+
+       redef fun mvp_matrix
+       do
+               var m = mvp_matrix_cache
+               if m == null or check_position_changed then
+                       m = create_mvp_matrix
+                       mvp_matrix_cache = m
+               end
+               return m
+       end
+
+       private var pitch_cache = 0.0
+       private var yaw_cache = 0.0
+       private var width_cache = 0.0
+       private var height_cache = 0.0
+       private var depth_cache = 0.0
+
+       redef fun check_position_changed
+       do
+               if super then return true
+
+               if light.pitch != pitch_cache or
+                  light.yaw != yaw_cache or
+                  light.width != width_cache or
+                  light.height != height_cache or
+                  light.depth != depth_cache then
+                       pitch_cache = light.pitch
+                       yaw_cache = light.yaw
+                       width_cache = light.width
+                       height_cache = light.height
+                       depth_cache = light.depth
+                       return true
+               end
+
+               return false
+       end
 end
index d153954..2546da3 100644 (file)
@@ -329,7 +329,7 @@ class BlinnPhongProgram
                uniform mat4 rotation;
 
                // Lights config
-               uniform int light_kind;
+               uniform lowp int light_kind;
                uniform vec3 light_center;
                uniform mat4 light_mvp;
 
@@ -402,7 +402,7 @@ class BlinnPhongProgram
                uniform sampler2D map_normal;
 
                // Shadow
-               uniform int light_kind;
+               uniform lowp int light_kind;
                uniform bool use_shadows;
                uniform sampler2D depth_texture;
                uniform float depth_texture_size;
index eb148c9..9ec6d2e 100644 (file)
@@ -43,6 +43,8 @@ class ModelAsset
 
        redef fun load
        do
+               if loaded then return
+
                var ext = path.file_extension
                if ext == "obj" then
                        load_obj_file
@@ -59,6 +61,22 @@ class ModelAsset
                loaded = true
        end
 
+       private fun lazy_load
+       do
+               if loaded then return
+
+               # Lazy load
+               load
+
+               # Print errors when lazy loading only
+               if errors.length == 1 then
+                       print_error errors.first
+               else if errors.length > 1 then
+                       print_error "Loading model at '{path}' raised {errors.length} errors:\n* "
+                       print_error errors.join("\n* ")
+               end
+       end
+
        private fun load_obj_file
        do
                # Read .obj description from assets
@@ -83,37 +101,33 @@ class ModelAsset
                if debug_gamnit then assert obj_def.is_coherent
 
                # Build models
-               var converter = new ModelFromObj(path, obj_def)
-               converter.models leaves_cache
+               var converter = new BuildModelFromObj(path, obj_def)
+               converter.fill_leaves self
                errors.add_all converter.errors
        end
 
        redef fun leaves
        do
-               if not loaded then
-                       # Lazy load
-                       load
-
-                       # Print errors when lazy loading only
-                       if errors.length == 1 then
-                               print_error errors.first
-                       else if errors.length > 1 then
-                               print_error "Loading model at '{path}' raised {errors.length} errors:\n* "
-                               print_error errors.join("\n* ")
-                       end
-               end
-
+               lazy_load
                return leaves_cache
        end
 
        private var leaves_cache = new Array[LeafModel]
+
+       redef fun named_parts
+       do
+               lazy_load
+               return named_leaves_cache
+       end
+
+       private var named_leaves_cache = new Map[String, Model]
 end
 
-# Short-lived service to convert an `ObjDef` to `models`
+# Short-lived service to convert an `ObjDef` to `fill_leaves`
 #
 # Limitations: This service only support faces with 3 or 4 vertices.
 # Faces with more vertices should be triangulated by the modeling tool.
-private class ModelFromObj
+private class BuildModelFromObj
 
        # Path to the .obj file in the assets folder, used to find .mtl files
        var path: String
@@ -121,22 +135,28 @@ private class ModelFromObj
        # Parsed .obj definition
        var obj_def: ObjDef
 
-       # Errors raised by calls to `models`
+       # Errors raised by calls to `fill_leaves`
        var errors = new Array[Error]
 
-       # Fill `leaves` with models described in `obj_def`
-       fun models(leaves: Array[LeafModel])
+       # Fill `leaves` with objects described in `obj_def`
+       fun fill_leaves(target_model: ModelAsset)
        do
-               # Sort faces by material
-               var mtl_to_faces = new MultiHashMap[String, ObjFace]
-               for face in obj_def.faces do
-                       var mtl_lib_name = face.material_lib
-                       var mtl_name = face.material_name
-
-                       var full_name = ""
-                       if mtl_lib_name != null and mtl_name != null then full_name = mtl_lib_name / mtl_name
+               var leaves = target_model.leaves_cache
 
-                       mtl_to_faces[full_name].add face
+               # Sort faces by material
+               var obj_mtl_to_faces = new Map[ObjObj, MultiHashMap[String, ObjFace]]
+               for obj in obj_def.objects do
+                       var mtl_to_faces = new MultiHashMap[String, ObjFace]
+                       obj_mtl_to_faces[obj] = mtl_to_faces
+                       for face in obj.faces do
+                               var mtl_lib_name = face.material_lib
+                               var mtl_name = face.material_name
+
+                               var full_name = ""
+                               if mtl_lib_name != null and mtl_name != null then full_name = mtl_lib_name / mtl_name
+
+                               mtl_to_faces[full_name].add face
+                       end
                end
 
                # Load material libs
@@ -158,38 +178,43 @@ private class ModelFromObj
                        mtl_libs[asset_path] = mtl_lib
                end
 
-               # Create 1 mesh per material, and prepare materials
+               # Create 1 mesh per material per object, and prepare materials
                var mesh_to_mtl = new Map[Mesh, nullable MtlDef]
+               var mesh_to_name = new Map[Mesh, String]
                var texture_names = new Set[String]
-               for full_name, faces in mtl_to_faces do
-
-                       # Create mesh
-                       var mesh = new Mesh
-                       mesh.vertices = vertices(faces)
-                       mesh.normals = normals(faces)
-                       mesh.texture_coords = texture_coords(faces)
-
-                       # Material
-                       var mtl_def = null
-
-                       var mtl_lib_name = faces.first.material_lib
-                       var mtl_name = faces.first.material_name
-                       if mtl_lib_name != null and mtl_name != null then
-                               var asset_path = self.path.dirname / mtl_lib_name
-                               var mtl_lib = mtl_libs[asset_path]
-                               var mtl = mtl_lib.get_or_null(mtl_name)
-                               if mtl != null then
-                                       mtl_def = mtl
-
-                                       for e in mtl.maps do
-                                               texture_names.add self.path.dirname / e
+               for obj in obj_def.objects do
+                       var mtl_to_faces = obj_mtl_to_faces[obj]
+                       for mtl_path, faces in mtl_to_faces do
+
+                               # Create mesh
+                               var mesh = new Mesh
+                               mesh.vertices = vertices(faces)
+                               mesh.normals = normals(faces)
+                               mesh.texture_coords = texture_coords(faces)
+
+                               # Material
+                               var mtl_def = null
+
+                               var mtl_lib_name = faces.first.material_lib
+                               var mtl_name = faces.first.material_name
+                               if mtl_lib_name != null and mtl_name != null then
+                                       var asset_path = self.path.dirname / mtl_lib_name
+                                       var mtl_lib = mtl_libs[asset_path]
+                                       var mtl = mtl_lib.get_or_null(mtl_name)
+                                       if mtl != null then
+                                               mtl_def = mtl
+
+                                               for e in mtl.maps do
+                                                       texture_names.add self.path.dirname / e
+                                               end
+                                       else
+                                               errors.add new Error("Error loading model at '{path}': mtl '{mtl_name}' not found in '{asset_path}'")
                                        end
-                               else
-                                       errors.add new Error("Error loading model at '{path}': mtl '{mtl_name}' not found in '{asset_path}'")
                                end
-                       end
 
-                       mesh_to_mtl[mesh] = mtl_def
+                               mesh_to_mtl[mesh] = mtl_def
+                               mesh_to_name[mesh] = obj.name
+                       end
                end
 
                # Load textures need for these materials
@@ -241,12 +266,27 @@ private class ModelFromObj
                end
 
                # Create models and store them
+               var name_to_leaves = new MultiHashMap[String, LeafModel]
                for mesh, mtl_def in mesh_to_mtl do
+
                        var material = materials.get_or_null(mtl_def)
                        if material == null then material = new Material
 
                        var model = new LeafModel(mesh, material)
                        leaves.add model
+
+                       name_to_leaves[mesh_to_name[mesh]].add model
+               end
+
+               # Collect objects with a name
+               for name, models in name_to_leaves do
+                       if models.length == 1 then
+                               target_model.named_leaves_cache[name] = models.first
+                       else
+                               var named_model = new CompositeModel
+                               named_model.leaves.add_all models
+                               target_model.named_leaves_cache[name] = named_model
+                       end
                end
        end
 
index 8e5566c..b14cb33 100644 (file)
@@ -78,3 +78,8 @@ redef class TextureAsset
                jni_env.pop_local_frame
        end
 end
+
+redef class Pointer
+       # Disable out premultiply as we use only the one from Android
+       redef fun premultiply_alpha(width, height) do end
+end
diff --git a/lib/gamnit/examples/fonts_showcase/assets/corner.png b/lib/gamnit/examples/fonts_showcase/assets/corner.png
new file mode 100644 (file)
index 0000000..12f7986
Binary files /dev/null and b/lib/gamnit/examples/fonts_showcase/assets/corner.png differ
index d64c304..33a342c 100644 (file)
@@ -22,9 +22,12 @@ redef class App
        # Asset font used to display text
        var font = new BMFontAsset("Josefin_Sans/font.fnt")
 
-       # Anchor texture used to identify the anchor coordinate of each `TextSprites`
+       # Anchor texture identifying the anchor coordinates of each `TextSprites`
        var anchor = new Texture("anchor.png")
 
+       # Bottom right corner
+       var corner = new Texture("corner.png")
+
        redef fun on_create
        do
                super
@@ -170,6 +173,12 @@ redef class App
 
                # Add the anchor effects to all TextSprites
                for t in texts do ui_sprites.add new Sprite(anchor, t.anchor)
+
+               for t in texts do
+                       # Bottom right
+                       var br = t.anchor.offset(t.width*(1.0-t.align), -t.height*(1.0-t.valign), 1.0)
+                       ui_sprites.add new Sprite(corner, br)
+               end
        end
 
        redef fun accept_event(event)
index e9f8499..069c653 100644 (file)
@@ -47,6 +47,7 @@ import model_parser_base
 # var parser = new ObjFileParser(obj_src)
 # var parsed_obj = parser.parse
 # assert parsed_obj.is_coherent
+# assert parsed_obj.objects.first.name == "Cube"
 # ~~~
 class ObjFileParser
        super StringProcessor
@@ -60,6 +61,7 @@ class ObjFileParser
        # Execute parsing of `src` to extract an `ObjDef`
        fun parse: nullable ObjDef
        do
+               var obj_obj = null
                while not eof do
                        var token = read_token
                        if token.is_empty or token == "#" then
@@ -78,7 +80,7 @@ class ObjFileParser
                                geometry.params.add vec
                        else if token == "f" then # Faces
                                var face = read_face
-                               geometry.faces.add face
+                               if obj_obj != null then obj_obj.faces.add face
                        else if token == "mtllib" then
                                current_material_lib = read_until_eol_or_comment
                        else if token == "usemtl" then
@@ -87,6 +89,8 @@ class ObjFileParser
                        # TODO other line type headers
                        else if token == "s" then
                        else if token == "o" then
+                               obj_obj = new ObjObj(read_until_eol_or_comment)
+                               geometry.objects.add obj_obj
                        else if token == "g" then
                        end
                        skip_eol
@@ -152,15 +156,17 @@ class ObjDef
        # Surface parameters
        var params = new Array[Vec3]
 
-       # Faces
-       var faces = new Array[ObjFace]
+       # Sub-objects
+       var objects = new Array[ObjObj]
 
        # Relative paths to referenced material libraries
        fun material_libs: Set[String] do
                var libs = new Set[String]
-               for face in faces do
-                       var lib = face.material_lib
-                       if lib != null then libs.add lib
+               for obj in objects do
+                       for face in obj.faces do
+                               var lib = face.material_lib
+                               if lib != null then libs.add lib
+                       end
                end
                return libs
        end
@@ -173,10 +179,12 @@ class ObjDef
        # be executed at each execution of a game.
        fun is_coherent: Bool
        do
-               for f in faces do
-                       if f.vertices.length < 3 then return error("ObjFace with less than 3 vertices")
+               for obj in objects do
+                       for f in obj.faces do
+                               if f.vertices.length < 3 then return error("Face with less than 3 vertices")
+                       end
 
-                       for v in f.vertices do
+                       for f in obj.faces do for v in f.vertices do
                                var i = v.vertex_point_index
                                if i < 1 then return error("Vertex point index < 1")
                                if i > vertex_points.length then return error("Vertex point index > than length")
@@ -205,9 +213,19 @@ class ObjDef
        end
 end
 
+# Sub-object within an `ObjDef`
+class ObjObj
+
+       # Sub-object name as declared in the source file
+       var name: String
+
+       # Sub-object faces
+       var faces = new Array[ObjFace]
+end
+
 # Flat surface of an `ObjDef`
 class ObjFace
-       # Vertex composing this surface, thene should be 3 or more
+       # Vertex composing this surface, there should be 3 or more
        var vertices = new Array[ObjVertex]
 
        # Relative path to the .mtl material lib
index 95afff8..dc4fc7b 100644 (file)
@@ -62,13 +62,17 @@ class RemoteServer
        var socket: nullable TCPStream = null
 
        # Is this connection connected?
-       fun connected: Bool do return socket != null and socket.connected
+       fun connected: Bool
+       do
+               var socket = socket
+               return socket != null and socket.connected
+       end
 
-       # `BinarySerializer` used to send data to this client through `socket`
-       var writer: BinarySerializer is noinit
+       # `MsgPackSerializer` used to send data to this client through `socket`
+       var writer: MsgPackSerializer is noinit
 
-       # `BinaryDeserializer` used to receive data from this client through `socket`
-       var reader: BinaryDeserializer is noinit
+       # `MsgPackDeserializer` used to receive data from this client through `socket`
+       var reader: MsgPackDeserializer is noinit
 
        # Attempt connection with the remote server
        fun connect: Bool
@@ -83,9 +87,9 @@ class RemoteServer
                end
 
                # Setup serialization
-               writer = new BinarySerializer(socket)
+               writer = new MsgPackSerializer(socket)
                writer.cache = new AsyncCache(false)
-               reader = new BinaryDeserializer(socket)
+               reader = new MsgPackDeserializer(socket)
                writer.link reader
 
                return true
@@ -104,21 +108,21 @@ class RemoteServer
 
                # App name
                var app_name = sys.handshake_app_name
-               socket.write_string app_name
+               socket.serialize_msgpack app_name
 
-               var server_app = socket.read_string
+               var server_app = socket.deserialize_msgpack("String")
                if server_app != app_name then
-                       print_error "Handshake Error: server app name is '{server_app}'"
+                       print_error "Handshake Error: server app name is '{server_app or else "<invalid>"}'"
                        socket.close
                        return false
                end
 
                # App version
-               socket.write_string sys.handshake_app_version
+               socket.serialize_msgpack sys.handshake_app_version
 
-               var server_version = socket.read_string
+               var server_version = socket.deserialize_msgpack("String")
                if server_version != sys.handshake_app_version then
-                       print_error "Handshake Error: server version is different '{server_version}'"
+                       print_error "Handshake Error: server version is different '{server_version or else "<invalid>"}'"
                        socket.close
                        return false
                end
index 579145b..339aa9b 100644 (file)
@@ -16,7 +16,7 @@
 module common
 
 import socket
-import binary::serialization
+import msgpack
 
 # Unique name of the application to use in the handshake
 #
index 70cf002..0a78d43 100644 (file)
@@ -160,18 +160,18 @@ class RemoteClient
        # Is this client connected?
        fun connected: Bool do return socket.connected
 
-       # `BinarySerializer` used to send data to this client through `socket`
-       var writer: BinarySerializer is noinit
+       # `MsgPackSerializer` used to send data to this client through `socket`
+       var writer: MsgPackSerializer is noinit
 
-       # `BinaryDeserializer` used to receive data from this client through `socket`
-       var reader: BinaryDeserializer is noinit
+       # `MsgPackDeserializer` used to receive data from this client through `socket`
+       var reader: MsgPackDeserializer is noinit
 
        init
        do
                # Setup serialization
-               writer = new BinarySerializer(socket)
+               writer = new MsgPackSerializer(socket)
                writer.cache = new AsyncCache(true)
-               reader = new BinaryDeserializer(socket)
+               reader = new MsgPackDeserializer(socket)
                writer.link reader
        end
 
@@ -182,29 +182,29 @@ class RemoteClient
 
                # Make sure it is the same app
                var server_app = sys.handshake_app_name
-               var client_app = socket.read_string
+               var client_app = socket.deserialize_msgpack
                if server_app != client_app then
-                       print_error "Server Error: Client app name is '{client_app}'"
+                       print_error "Server Error: Client app name is '{client_app or else "<invalid>"}'"
 
                        # Send an empty string so the client read it and give up
-                       socket.write_string ""
+                       socket.serialize_msgpack ""
                        socket.close
                        return false
                end
 
-               socket.write_string server_app
+               socket.serialize_msgpack server_app
 
                # App version
                var app_version = sys.handshake_app_version
-               var client_version = socket.read_string
+               var client_version = socket.deserialize_msgpack
                if client_version != app_version then
-                       print_error "Handshake Error: client version is different '{client_version}'"
-                       socket.write_string ""
+                       print_error "Handshake Error: client version is different '{client_version or else "<invalid>"}'"
+                       socket.serialize_msgpack ""
                        socket.close
                        return false
                end
 
-               socket.write_string app_version
+               socket.serialize_msgpack app_version
 
                return true
        end
index 4fb98ac..2aa405d 100644 (file)
@@ -168,13 +168,13 @@ class CustomTexture
                for i in [0..4[ do cpixels[offset+i] = bytes[i]
        end
 
-       # Overwrite all pixels with `color`
+       # Overwrite all pixels with `color`, return `self`
        #
        # The argument `color` should be an array of up to 4 floats (RGBA).
        # If `color` has less than 4 items, the missing items are replaced by 1.0.
        #
        # Require: `not loaded`
-       fun fill(color: Array[Float])
+       fun fill(color: Array[Float]): SELF
        do
                assert not loaded else print_error "{class_name}::fill already loaded"
 
@@ -189,6 +189,8 @@ class CustomTexture
                                i += 4
                        end
                end
+
+               return self
        end
 
        redef fun load(force)
@@ -236,8 +238,11 @@ class RootTexture
        private fun load_from_pixels(pixels: Pointer, width, height: Int, format: GLPixelFormat)
        do
                var max_texture_size = glGetIntegerv(gl_MAX_TEXTURE_SIZE, 0)
-               if width > max_texture_size or height > max_texture_size then
-                       error = new Error("Texture {self} width or height is over the GL_MAX_TEXTURE_SIZE of {max_texture_size}")
+               if width > max_texture_size then
+                       error = new Error("Texture width larger than gl_MAX_TEXTURE_SIZE ({max_texture_size}) in {self} at {width}")
+                       return
+               else if height > max_texture_size then
+                       error = new Error("Texture height larger than gl_MAX_TEXTURE_SIZE ({max_texture_size}) in {self} at {height}")
                        return
                end
 
index 2ffb84e..f5b6bec 100644 (file)
@@ -58,7 +58,7 @@ import virtual_gamepad_spritesheet
 redef class App
 
        # Current touch gamepad, still may be invisible
-       var gamepad: nullable VirtualGamepad = null
+       var gamepad: nullable VirtualGamepad = null is writable
 
        # Textures used for `DPad` and available to clients
        var gamepad_spritesheet = new VirtualGamepadSpritesheet
@@ -86,7 +86,7 @@ class VirtualGamepad
        # and `add_button`.
        var controls = new Array[RoundControl]
 
-       # Add a directional pad (`DPad`) to a default location
+       # Add and return a directional pad (`DPad`) to a default location
        #
        # The 4 buttons fire events with the corresponding name in `names`.
        # Items in `names` should be in order of top, left, down and right.
@@ -101,17 +101,22 @@ class VirtualGamepad
        # added by `add_button`.
        #
        # Require: `names == null or names.length == 4`
-       fun add_dpad(names: nullable Array[String])
+       fun add_dpad(names: nullable Array[String]): nullable DPad
        do
                if names == null then names = ["w","a","s","d"]
                assert names.length == 4
 
                if n_dpads == 0 then
-                       controls.add new DPad(app.ui_camera.bottom_left.offset(200.0, 100.0, 0.0), names)
+                       var dpad = new DPad(app.ui_camera.bottom_left.offset(200.0, 100.0, 0.0), names)
+                       controls.add dpad
+                       return dpad
                else if n_dpads == 1 then
-                       controls.add new DPad(app.ui_camera.bottom_right.offset(-200.0, 100.0, 0.0), names)
+                       var dpad = new DPad(app.ui_camera.bottom_right.offset(-200.0, 100.0, 0.0), names)
+                       controls.add dpad
+                       return dpad
                else
                        print_error "Too many DPad ({n_dpads}) in {self}"
+                       return null
                end
        end
 
@@ -132,7 +137,7 @@ class VirtualGamepad
                new Point[Float](-350.0, 350.0),
                new Point[Float](-350.0, 550.0))
 
-       # Add a round button to a default location
+       # Add and return a round button to a default location
        #
        # Fired events use `name`, it should usually correspond to a
        # keyboard key like "space" or "a".
@@ -144,7 +149,7 @@ class VirtualGamepad
        #
        # A maximum of 6 buttons may be added using this method when
        # there is less than 2 `DPad`. Otherwise, only 2 buttons can be added.
-       fun add_button(name: String, texture: Texture)
+       fun add_button(name: String, texture: Texture): nullable RoundButton
        do
                if n_dpads == 2 and button_positions.length == 6 then
                        # Drop the bottom 4 buttons
@@ -156,8 +161,10 @@ class VirtualGamepad
 
                assert button_positions.not_empty else print_error "Too many buttons in {self}"
                var pos = button_positions.shift
-               controls.add new RoundButton(
+               var but = new RoundButton(
                        app.ui_camera.bottom_right.offset(pos.x, pos.y, 0.0), name, texture)
+               controls.add but
+               return but
        end
 
        private fun prepare
index e0369ed..25ce970 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_github_curl is test_suite
+module test_github_curl is test
 
 import github::github_curl
-import test_suite
 
 class TestGithubCurl
-       super TestSuite
+       test
 
        var auth: String = get_github_oauth
        var user_agent: String = "nit"
        var testee: GithubCurl is noinit
 
-       redef fun before_test do
+       fun before_test is before do
                testee = new GithubCurl(auth, user_agent)
        end
 
-       fun test_get_repo do
+       fun test_get_repo is test do
                var uri = "https://api.github.com/repos/nitlang/nit"
                var res = testee.get_and_check(uri)
 
@@ -37,7 +36,7 @@ class TestGithubCurl
                assert res["owner"] isa JsonObject
        end
 
-       fun test_get_user do
+       fun test_get_user is test do
                var uri = "https://api.github.com/users/Morriar"
                var res = testee.get_and_check(uri)
 
index 7b02c8d..c4ac331 100644 (file)
@@ -31,6 +31,7 @@
 # http://www.khronos.org/opengles/sdk/docs/man/
 module glesv2 is
        pkgconfig
+       no_warning "missing-doc"
        new_annotation glsl_vertex_shader
        new_annotation glsl_fragment_shader
        ldflags("-lGLESv2")@android
index b27a3c0..fc00782 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_native_gmp is test_suite
+module test_native_gmp is test
 
-import test_suite
 import native_gmp
 
 class TestNativeMPZ
-    super TestSuite
+       test
 
     var op1: NativeMPZ
     var op2: NativeMPZ
@@ -26,9 +25,7 @@ class TestNativeMPZ
     var r: NativeMPQ
     var res: NativeMPZ
 
-    init do end
-
-    redef fun before_test do
+    fun before_test is before do
         op1 = new NativeMPZ
         op2 = new NativeMPZ
         ui = new UInt64
@@ -36,7 +33,7 @@ class TestNativeMPZ
         res = new NativeMPZ
     end
 
-    redef fun after_test do
+    fun after_test is after do
         op1.finalize
         op2.finalize
         ui.free
@@ -212,23 +209,21 @@ class TestNativeMPZ
 end
 
 class TestNativeMPQ
-    super TestSuite
+       test
 
     var op1: NativeMPQ
     var op2: NativeMPQ
     var l: NativeMPZ
     var res: NativeMPQ
 
-    init do end
-
-    redef fun before_test do
+    fun before_test is before do
         op1 = new NativeMPQ
         op2 = new NativeMPQ
         l = new NativeMPZ
         res = new NativeMPQ
     end
 
-    redef fun after_test do
+    fun after_test is after do
         op1.finalize
         op2.finalize
         l.finalize
index 8a2c7a0..ad58649 100644 (file)
 # limitations under the License.
 
 # Test suites for module `markdown`
-module test_markdown is test_suite
+module test_markdown is test
 
-import test_suite
 intrude import markdown
 
 class TestMarkdownProcessor
-       super TestSuite
+       test
 
-       fun test_process_empty do
+       fun test_process_empty is test do
                var test = ""
                var exp = ""
                var res = test.md_to_html.write_to_string
                assert res == exp
        end
 
-       fun test_process_tabs do
+       fun test_process_tabs is test do
                var test = """
        some code
 """
@@ -40,14 +39,14 @@ class TestMarkdownProcessor
        end
 
 
-       fun test_process_par1 do
+       fun test_process_par1 is test do
                var test = "test"
                var exp = "<p>test</p>\n"
                var res = test.md_to_html.write_to_string
                assert res == exp
        end
 
-       fun test_process_par2 do
+       fun test_process_par2 is test do
                var test = """
 line1
 line2
@@ -65,7 +64,7 @@ line2</p>
                assert res == exp
        end
 
-       fun test_process_par3 do
+       fun test_process_par3 is test do
                var test = """
 Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
 Aliquam hendrerit mi posuere lectus.
@@ -85,7 +84,7 @@ id sem consectetuer libero luctus adipiscing.</p>
                assert res == exp
        end
 
-       fun test_process_headings_1 do
+       fun test_process_headings_1 is test do
                var test = """
 This is a H1
 =============
@@ -101,7 +100,7 @@ This is a H2
                assert res == exp
        end
 
-       fun test_process_headings_2 do
+       fun test_process_headings_2 is test do
                var test = """
 # This is a H1
 
@@ -117,7 +116,7 @@ This is a H2
                assert res == exp
        end
 
-       fun test_process_headings_3 do
+       fun test_process_headings_3 is test do
                var test = """
 # This is a H1 #
 
@@ -134,7 +133,7 @@ This is a H2
                assert res == exp
        end
 
-       fun test_process_hr do
+       fun test_process_hr is test do
                var test = """
 * * *
 
@@ -151,7 +150,7 @@ This is a H2
                assert res == exp
        end
 
-       fun test_process_bquote1 do
+       fun test_process_bquote1 is test do
                var test = """
 > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
 > consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
@@ -172,7 +171,7 @@ id sem consectetuer libero luctus adipiscing.</p>
                assert res == exp
        end
 
-       fun test_process_bquote2 do
+       fun test_process_bquote2 is test do
                var test = """
 > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
 consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
@@ -193,7 +192,7 @@ id sem consectetuer libero luctus adipiscing.</p>
                assert res == exp
        end
 
-       fun test_process_bquote3 do
+       fun test_process_bquote3 is test do
                var test = """
 > This is the first level of quoting.
 >
@@ -213,7 +212,7 @@ id sem consectetuer libero luctus adipiscing.</p>
                assert res == exp
        end
 
-       fun test_process_list1 do
+       fun test_process_list1 is test do
                var test = """
 *   Red
 *   Green
@@ -229,7 +228,7 @@ id sem consectetuer libero luctus adipiscing.</p>
                assert res == exp
        end
 
-       fun test_process_list2 do
+       fun test_process_list2 is test do
                var test = """
 +   Red
 +   Green
@@ -245,7 +244,7 @@ id sem consectetuer libero luctus adipiscing.</p>
                assert res == exp
        end
 
-       fun test_process_list3 do
+       fun test_process_list3 is test do
                var test = """
 -   Red
 -   Green
@@ -261,7 +260,7 @@ id sem consectetuer libero luctus adipiscing.</p>
                assert res == exp
        end
 
-       fun test_process_list4 do
+       fun test_process_list4 is test do
                var test = """
 1.  Bird
 2.  McHale
@@ -277,7 +276,7 @@ id sem consectetuer libero luctus adipiscing.</p>
                assert res == exp
        end
 
-       fun test_process_list5 do
+       fun test_process_list5 is test do
                var test = """
 3. Bird
 1. McHale
@@ -293,7 +292,7 @@ id sem consectetuer libero luctus adipiscing.</p>
                assert res == exp
        end
 
-       fun test_process_list6 do
+       fun test_process_list6 is test do
                var test = """
 *   Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
     Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
@@ -314,7 +313,7 @@ Suspendisse id sem consectetuer libero luctus adipiscing.</li>
                assert res == exp
        end
 
-       fun test_process_list7 do
+       fun test_process_list7 is test do
                var test = """
 *   Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
 Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
@@ -335,7 +334,7 @@ Suspendisse id sem consectetuer libero luctus adipiscing.</li>
                assert res == exp
        end
 
-       fun test_process_list8 do
+       fun test_process_list8 is test do
                var test = """
 *   Bird
 
@@ -353,7 +352,7 @@ Suspendisse id sem consectetuer libero luctus adipiscing.</li>
                assert res == exp
        end
 
-       fun test_process_list9 do
+       fun test_process_list9 is test do
                var test = """
 1.  This is a list item with two paragraphs. Lorem ipsum dolor
     sit amet, consectetuer adipiscing elit. Aliquam hendrerit
@@ -382,7 +381,7 @@ sit amet velit.</p>
                assert res == exp
        end
 
-       fun test_process_list10 do
+       fun test_process_list10 is test do
                var test = """
 *   This is a list item with two paragraphs.
 
@@ -407,7 +406,7 @@ sit amet, consectetuer adipiscing elit.</p>
                assert res == exp
        end
 
-       fun test_process_list11 do
+       fun test_process_list11 is test do
                var test = """
 This is a paragraph
 * and this is not a list
@@ -422,7 +421,7 @@ This is a paragraph
                assert res == exp
        end
 
-       fun test_process_list_ext do
+       fun test_process_list_ext is test do
                var test = """
 This is a paragraph
 * and this is not a list
@@ -437,7 +436,7 @@ This is a paragraph
                assert res == exp
        end
 
-       fun test_process_code1 do
+       fun test_process_code1 is test do
                var test = """
 This is a normal paragraph:
 
@@ -451,7 +450,7 @@ This is a normal paragraph:
                assert res == exp
        end
 
-       fun test_process_code2 do
+       fun test_process_code2 is test do
                var test = """
 Here is an example of AppleScript:
 
@@ -478,7 +477,7 @@ end tell
                assert res == exp
        end
 
-       fun test_process_code_ext1 do
+       fun test_process_code_ext1 is test do
                var test = """
 Here is an example of AppleScript:
 ~~~
@@ -506,7 +505,7 @@ end tell
                assert res == exp
        end
 
-       fun test_process_code_ext2 do
+       fun test_process_code_ext2 is test do
                var test = """
 Here is an example of AppleScript:
 ```
@@ -534,7 +533,7 @@ end tell
                assert res == exp
        end
 
-       fun test_process_code_ext3 do
+       fun test_process_code_ext3 is test do
                var proc = new MarkdownProcessor
                proc.ext_mode = false
 
@@ -550,7 +549,7 @@ beep</p>
                assert res == exp
        end
 
-       fun test_process_code_ext4 do
+       fun test_process_code_ext4 is test do
                var test = """
 Here is an example of AppleScript:
     beep
@@ -564,7 +563,7 @@ Here is an example of AppleScript:
                assert res == exp
        end
 
-       fun test_process_code_ext5 do
+       fun test_process_code_ext5 is test do
                var test = """
 ```nit
 print "Hello World!"
@@ -578,7 +577,7 @@ print "Hello World!"
                assert res == exp
        end
 
-       fun test_process_code_ext6 do
+       fun test_process_code_ext6 is test do
                var test = """
 ~~~
 print "Hello"
@@ -597,7 +596,7 @@ print "World"
                assert res == exp
        end
 
-       fun test_process_code_ext7 do
+       fun test_process_code_ext7 is test do
                var test = """
 ~~~
 print "Hello"
@@ -616,7 +615,7 @@ print "World"
                assert res == exp
        end
 
-       fun test_process_nesting1 do
+       fun test_process_nesting1 is test do
                var test = """
 > ## This is a header.
 >
@@ -643,7 +642,7 @@ print "World"
                assert res == exp
        end
 
-       fun test_process_nesting2 do
+       fun test_process_nesting2 is test do
                var test = """
 *   A list item with a blockquote:
 
@@ -664,7 +663,7 @@ inside a list item.</p>
                assert res == exp
        end
 
-       fun test_process_nesting3 do
+       fun test_process_nesting3 is test do
                var test = """
 *   A list item with a code block:
 
@@ -682,7 +681,7 @@ inside a list item.</p>
                assert res == exp
        end
 
-       fun test_process_nesting4 do
+       fun test_process_nesting4 is test do
                var test = """
 *      Tab
        *       Tab
@@ -704,7 +703,7 @@ inside a list item.</p>
        end
 
        # TODO
-       #       fun test_process_nesting5 do
+       #       fun test_process_nesting5 is test do
        #               var test = """
        # *     this
        #
@@ -726,7 +725,7 @@ inside a list item.</p>
        #               assert res == exp
        #       end
 
-       fun test_process_emph1 do
+       fun test_process_emph1 is test do
                var test = """
 *single asterisks*
 
@@ -745,14 +744,14 @@ __double underscores__
                assert res == exp
        end
 
-       fun test_process_emph2 do
+       fun test_process_emph2 is test do
                var test = "un*frigging*believable"
                var exp = "<p>un<em>frigging</em>believable</p>\n"
                var res = test.md_to_html.write_to_string
                assert res == exp
        end
 
-       fun test_process_emph3 do
+       fun test_process_emph3 is test do
                var proc = new MarkdownProcessor
                proc.ext_mode = false
                var test = "Con_cat_this"
@@ -761,14 +760,14 @@ __double underscores__
                assert res == exp
        end
 
-       fun test_process_emph_ext do
+       fun test_process_emph_ext is test do
                var test = "Con_cat_this"
                var exp = "<p>Con_cat_this</p>\n"
                var res = test.md_to_html.write_to_string
                assert res == exp
        end
 
-       fun test_process_xml1 do
+       fun test_process_xml1 is test do
                var test = """
 This is a regular paragraph.
 
@@ -793,7 +792,7 @@ This is another regular paragraph.
                assert res == exp
        end
 
-       fun test_process_xml2 do
+       fun test_process_xml2 is test do
                var test = """
 This is an image <img src="foo/bar" alt="baz"/> in a regular paragraph.
 """
@@ -803,7 +802,7 @@ This is an image <img src="foo/bar" alt="baz"/> in a regular paragraph.
                assert res == exp
        end
 
-       fun test_process_xml3 do
+       fun test_process_xml3 is test do
                var test = """
 <div style=">"/>
 """
@@ -814,7 +813,7 @@ This is an image <img src="foo/bar" alt="baz"/> in a regular paragraph.
                assert res == exp
        end
 
-       fun test_process_xml4 do
+       fun test_process_xml4 is test do
                var test = """
 <p>This is an example of a block element that should be escaped.</p>
 <p>Idem for the second paragraph.</p>
@@ -824,7 +823,7 @@ This is an image <img src="foo/bar" alt="baz"/> in a regular paragraph.
                assert res == exp
        end
 
-       fun test_process_xml5 do
+       fun test_process_xml5 is test do
                var test = """
 # Some more XML tests
 
@@ -844,21 +843,21 @@ With a *md paragraph*!
                assert res == exp
        end
 
-       fun test_process_span_code1 do
+       fun test_process_span_code1 is test do
                var test = "Use the `printf()` function."
                var exp = "<p>Use the <code>printf()</code> function.</p>\n"
                var res = test.md_to_html.write_to_string
                assert res == exp
        end
 
-       fun test_process_span_code2 do
+       fun test_process_span_code2 is test do
                var test = "``There is a literal backtick (`) here.``"
                var exp = "<p><code>There is a literal backtick (`) here.</code></p>\n"
                var res = test.md_to_html.write_to_string
                assert res == exp
        end
 
-       fun test_process_span_code3 do
+       fun test_process_span_code3 is test do
                var test = """
 A single backtick in a code span: `` ` ``
 
@@ -872,42 +871,42 @@ A backtick-delimited string in a code span: `` `foo` ``
                assert res == exp
        end
 
-       fun test_process_span_code4 do
+       fun test_process_span_code4 is test do
                var test = "Please don't use any `<blink>` tags."
                var exp = "<p>Please don't use any <code>&lt;blink&gt;</code> tags.</p>\n"
                var res = test.md_to_html.write_to_string
                assert res == exp
        end
 
-       fun test_process_span_code5 do
+       fun test_process_span_code5 is test do
                var test = "`&#8212;` is the decimal-encoded equivalent of `&mdash;`."
                var exp = "<p><code>&amp;#8212;</code> is the decimal-encoded equivalent of <code>&amp;mdash;</code>.</p>\n"
                var res = test.md_to_html.write_to_string
                assert res == exp
        end
 
-       fun test_process_escape1 do
+       fun test_process_escape1 is test do
                var test = "\\*this text is surrounded by literal asterisks\\*"
                var exp = "<p>*this text is surrounded by literal asterisks*</p>\n"
                var res = test.md_to_html.write_to_string
                assert res == exp
        end
 
-       fun test_process_escape2 do
+       fun test_process_escape2 is test do
                var test = "1986\\. What a great season."
                var exp = "<p>1986. What a great season.</p>\n"
                var res = test.md_to_html.write_to_string
                assert res == exp
        end
 
-       fun test_process_escape3 do
+       fun test_process_escape3 is test do
                var test = "Ben & Lux"
                var exp = "<p>Ben &amp; Lux</p>\n"
                var res = test.md_to_html.write_to_string
                assert res == exp
        end
 
-       fun test_process_link1 do
+       fun test_process_link1 is test do
                var test = """
 This is [an example](http://example.com/ "Title") inline link.
 
@@ -920,14 +919,14 @@ This is [an example](http://example.com/ "Title") inline link.
                assert res == exp
        end
 
-       fun test_process_link2 do
+       fun test_process_link2 is test do
                var test = "See my [About](/about/) page for details."
                var exp = "<p>See my <a href=\"/about/\">About</a> page for details.</p>\n"
                var res = test.md_to_html.write_to_string
                assert res == exp
        end
 
-       fun test_process_link3 do
+       fun test_process_link3 is test do
                var test = """
 This is [an example][id] reference-style link.
 
@@ -949,7 +948,7 @@ Some other lipsum
                assert res == exp
        end
 
-       fun test_process_link4 do
+       fun test_process_link4 is test do
                var test = """
 This is multiple examples: [foo][1], [bar][2], [baz][3].
 
@@ -964,7 +963,7 @@ This is multiple examples: [foo][1], [bar][2], [baz][3].
                assert res == exp
        end
 
-       fun test_process_link5 do
+       fun test_process_link5 is test do
                var test = """
 This is multiple examples: [foo][a], [bar][A], [a].
 
@@ -976,7 +975,7 @@ This is multiple examples: [foo][a], [bar][A], [a].
                assert res == exp
        end
 
-       fun test_process_link6 do
+       fun test_process_link6 is test do
                var test = """
 I get 10 times more traffic from [Google][] than from [Yahoo][] or [MSN][].
 
@@ -990,7 +989,7 @@ I get 10 times more traffic from [Google][] than from [Yahoo][] or [MSN][].
                assert res == exp
        end
 
-       fun test_process_link7 do
+       fun test_process_link7 is test do
                var test = """
 Visit [Daring Fireball][] for more information.
 
@@ -1002,7 +1001,7 @@ Visit [Daring Fireball][] for more information.
                assert res == exp
        end
 
-       fun test_process_link8 do
+       fun test_process_link8 is test do
                var test = """
 This one has a [line
 break].
@@ -1023,7 +1022,7 @@ break</a> with a line-ending space.</p>
        end
 
        # FIXME unignore test once escape strings fixed
-       #       fun test_process_link9 do
+       #       fun test_process_link9 is test do
        #               var test = """
        # Foo [bar][].
        #
@@ -1040,7 +1039,7 @@ break</a> with a line-ending space.</p>
        #               assert res == exp
        #       end
 
-       fun test_process_img1 do
+       fun test_process_img1 is test do
                var test = """
 ![Alt text](/path/to/img.jpg)
 
@@ -1053,7 +1052,7 @@ break</a> with a line-ending space.</p>
                assert res == exp
        end
 
-       fun test_process_img2 do
+       fun test_process_img2 is test do
                var test = """
 ![Alt text][id]
 
@@ -1065,7 +1064,7 @@ break</a> with a line-ending space.</p>
                assert res == exp
        end
 
-       fun test_process_strike do
+       fun test_process_strike is test do
                var proc = new MarkdownProcessor
                proc.ext_mode = false
                var test = "This is how you ~~strike text~~"
@@ -1074,21 +1073,21 @@ break</a> with a line-ending space.</p>
                assert exp == res
        end
 
-       fun test_process_strike_ext do
+       fun test_process_strike_ext is test do
                var test = "This is how you ~~strike text~~"
                var exp = "<p>This is how you <del>strike text</del></p>\n"
                var res = test.md_to_html.write_to_string
                assert exp == res
        end
 
-       fun test_escape_bad_html do
+       fun test_escape_bad_html is test do
                        var test = "-1 if < , +1 if > and 0 otherwise"
                        var exp = "<p>-1 if &lt; , +1 if > and 0 otherwise</p>\n"
                var res = test.md_to_html.write_to_string
                assert exp == res
        end
 
-       fun test_daring_encoding do
+       fun test_daring_encoding is test do
                var test = """
 AT&T has an ampersand in their name.
 
@@ -1129,7 +1128,7 @@ Here's an inline [link](</script?foo=1&bar=2>).
 
        end
 
-       fun test_daring_autolinks do
+       fun test_daring_autolinks is test do
                var test = """
 Link: <http://example.com/>.
 
@@ -1165,7 +1164,7 @@ Auto-links should not occur here: `<http://example.com/>`
                assert res == exp
        end
 
-       fun test_daring_escape do
+       fun test_daring_escape is test do
                var test = """
 These should all get escaped:
 
@@ -1367,7 +1366,7 @@ other Markdown constructs:</p>
                assert res == exp
        end
 
-       fun test_daring_blockquotes do
+       fun test_daring_blockquotes is test do
                var test = """
 > Example:
 >
@@ -1400,7 +1399,7 @@ other Markdown constructs:</p>
                assert res == exp
        end
 
-       fun test_daring_code_blocks do
+       fun test_daring_code_blocks is test do
                var test = """
        code block on the first line
 
@@ -1436,7 +1435,7 @@ all contain trailing spaces
                assert res == exp
        end
 
-       fun test_daring_code_spans do
+       fun test_daring_code_spans is test do
                var test = """
 `<test a="` content of attribute `">`
 
@@ -1454,7 +1453,7 @@ Here's how you put `` `backticks` `` in a code span.
                assert res == exp
        end
 
-       fun test_daring_pars do
+       fun test_daring_pars is test do
                var proc = new MarkdownProcessor
                proc.ext_mode = false
 
@@ -1482,7 +1481,7 @@ list item.</p>
                assert res == exp
        end
 
-       fun test_daring_rules do
+       fun test_daring_rules is test do
                var test = """
 Dashes:
 
@@ -1598,7 +1597,7 @@ _ _ _
                assert res == exp
        end
 
-       fun test_daring_images do
+       fun test_daring_images is test do
                var test = """
 ![Alt text](/path/to/img.jpg)
 
@@ -1645,7 +1644,7 @@ Inline within a paragraph: [alt text](/url/).
                assert res == exp
        end
 
-       fun test_daring_inline_html1 do
+       fun test_daring_inline_html1 is test do
                var test = """
 Here's a simple block:
 
@@ -1768,7 +1767,7 @@ Blah
                assert res == exp
        end
 
-       fun test_daring_inline_html2 do
+       fun test_daring_inline_html2 is test do
                var test = """
 Simple block on one line:
 
@@ -1831,7 +1830,7 @@ foo
                assert res == exp
        end
 
-       fun test_daring_inline_html3 do
+       fun test_daring_inline_html3 is test do
                var test = """
 Paragraph one.
 
@@ -1862,7 +1861,7 @@ The end.
                assert res == exp
        end
 
-       fun test_daring_links1 do
+       fun test_daring_links1 is test do
                var test = """
 Just a [URL](/url/).
 
@@ -1908,7 +1907,7 @@ Just a [URL](/url/).
                assert res == exp
        end
 
-       fun test_daring_links2 do
+       fun test_daring_links2 is test do
                var test = """
 Foo [bar] [1].
 
@@ -2017,7 +2016,7 @@ breaks</a> across lines, but with a line-ending space.</p>
                assert res == exp
        end
 
-       fun test_daring_links3 do
+       fun test_daring_links3 is test do
                var test = """
 This is the [simple case].
 
@@ -2053,7 +2052,7 @@ break</a> with a line-ending space.</p>
                assert res == exp
        end
 
-       fun test_daring_nested do
+       fun test_daring_nested is test do
                var test = """
 > foo
 >
@@ -2075,7 +2074,7 @@ break</a> with a line-ending space.</p>
                assert res == exp
        end
 
-       fun test_daring_list do
+       fun test_daring_list is test do
                var test = """
 ## Unordered
 
@@ -2332,7 +2331,7 @@ back.</p>
                assert res == exp
        end
 
-       fun test_daring_strong_em do
+       fun test_daring_strong_em is test do
                var test = """
 ***This is strong and em.***
 
@@ -2353,7 +2352,7 @@ So is ___this___ word.
                assert res == exp
        end
 
-       fun test_daring_tabs do
+       fun test_daring_tabs is test do
                var test = """
 +      this is a list item
        indented with tabs
@@ -2405,7 +2404,7 @@ indented with spaces</p>
                assert res == exp
        end
 
-       fun test_daring_tidyness do
+       fun test_daring_tidyness is test do
                var test = """
 > A list within a blockquote:
 >
@@ -2432,19 +2431,19 @@ indented with spaces</p>
 end
 
 class TestBlock
-       super TestSuite
+       test
 
        # A dummy location for testing purposes.
        var loc = new MDLocation(0, 0, 0, 0)
 
-       fun test_has_blocks do
+       fun test_has_blocks is test do
                var subject = new MDBlock(loc)
                assert not subject.has_blocks
                subject.first_block = new MDBlock(loc)
                assert subject.has_blocks
        end
 
-       fun test_count_blocks do
+       fun test_count_blocks is test do
                var subject = new MDBlock(loc)
                assert subject.count_blocks == 0
                subject.first_block = new MDBlock(loc)
@@ -2453,14 +2452,14 @@ class TestBlock
                assert subject.count_blocks == 2
        end
 
-       fun test_has_lines do
+       fun test_has_lines is test do
                var subject = new MDBlock(loc)
                assert not subject.has_lines
                subject.first_line = new MDLine(loc, "")
                assert subject.has_lines
        end
 
-       fun test_count_lines do
+       fun test_count_lines is test do
                var subject = new MDBlock(loc)
                assert subject.count_lines == 0
                subject.first_line = new MDLine(loc, "")
@@ -2469,7 +2468,7 @@ class TestBlock
                assert subject.count_lines == 2
        end
 
-       fun test_split do
+       fun test_split is test do
                var line1 = new MDLine(loc, "line1")
                var line2 = new MDLine(loc, "line2")
                var line3 = new MDLine(loc, "line3")
@@ -2487,7 +2486,7 @@ class TestBlock
                assert block.last_line == line2
        end
 
-       fun test_add_line do
+       fun test_add_line is test do
                var subject = new MDBlock(loc)
                assert subject.count_lines == 0
                subject.add_line new MDLine(loc, "")
@@ -2496,7 +2495,7 @@ class TestBlock
                assert subject.count_lines == 2
        end
 
-       fun test_remove_line do
+       fun test_remove_line is test do
                var line1 = new MDLine(loc, "line1")
                var line2 = new MDLine(loc, "line2")
                var line3 = new MDLine(loc, "line3")
@@ -2512,7 +2511,7 @@ class TestBlock
                assert subject.last_line == line3
        end
 
-       fun test_transform_headline1 do
+       fun test_transform_headline1 is test do
                var subject = new MDBlock(loc)
                var kind = new BlockHeadline(subject)
                subject.add_line new MDLine(loc, " #   Title 1   ")
@@ -2521,7 +2520,7 @@ class TestBlock
                assert subject.first_line.value == "Title 1"
        end
 
-       fun test_transform_headline2 do
+       fun test_transform_headline2 is test do
                var subject = new MDBlock(loc)
                var kind = new BlockHeadline(subject)
                subject.add_line new MDLine(loc, " #####Title 5   ")
@@ -2530,7 +2529,7 @@ class TestBlock
                assert subject.first_line.value == "Title 5"
        end
 
-       fun test_remove_quote_prefix do
+       fun test_remove_quote_prefix is test do
                var subject = new MDBlock(loc)
                var kind = new BlockQuote(subject)
                subject.add_line new MDLine(loc, " > line 1")
@@ -2542,7 +2541,7 @@ class TestBlock
                assert subject.first_line.next.next.value == "line 3"
        end
 
-       fun test_remove_leading_empty_lines_1 do
+       fun test_remove_leading_empty_lines_1 is test do
                var block = new MDBlock(loc)
                block.add_line new MDLine(loc, "")
                block.add_line new MDLine(loc, "")
@@ -2554,14 +2553,14 @@ class TestBlock
                assert block.first_line.value == "   text"
        end
 
-       fun test_remove_leading_empty_lines_2 do
+       fun test_remove_leading_empty_lines_2 is test do
                var block = new MDBlock(loc)
                block.add_line new MDLine(loc, "   text")
                block.remove_leading_empty_lines
                assert block.first_line.value == "   text"
        end
 
-       fun test_remove_trailing_empty_lines_1 do
+       fun test_remove_trailing_empty_lines_1 is test do
                var block = new MDBlock(loc)
                block.add_line new MDLine(loc, "")
                block.add_line new MDLine(loc, "text")
@@ -2573,14 +2572,14 @@ class TestBlock
                assert block.last_line.value == "text"
        end
 
-       fun test_remove_trailing_empty_lines_2 do
+       fun test_remove_trailing_empty_lines_2 is test do
                var block = new MDBlock(loc)
                block.add_line new MDLine(loc, "text  ")
                assert not block.remove_trailing_empty_lines
                assert block.last_line.value == "text  "
        end
 
-       fun test_remove_surrounding_empty_lines do
+       fun test_remove_surrounding_empty_lines is test do
                var block = new MDBlock(loc)
                block.add_line new MDLine(loc, "")
                block.add_line new MDLine(loc, "text")
@@ -2595,14 +2594,14 @@ class TestBlock
 end
 
 class TestLine
-       super TestSuite
+       test
 
        # A dummy location for testing purposes.
        var loc = new MDLocation(0, 0, 0, 0)
 
        var subject: MDLine
 
-       fun test_is_empty do
+       fun test_is_empty is test do
                subject = new MDLine(loc, "")
                assert subject.is_empty
                subject = new MDLine(loc, "    ")
@@ -2613,7 +2612,7 @@ class TestLine
                assert not subject.is_empty
        end
 
-       fun test_leading do
+       fun test_leading is test do
                subject = new MDLine(loc, "")
                assert subject.leading == 0
                subject = new MDLine(loc, "    ")
@@ -2624,7 +2623,7 @@ class TestLine
                assert subject.leading == 4
        end
 
-       fun test_trailing do
+       fun test_trailing is test do
                subject = new MDLine(loc, "")
                assert subject.trailing == 0
                subject = new MDLine(loc, "    ")
@@ -2635,7 +2634,7 @@ class TestLine
                assert subject.trailing == 1
        end
 
-       fun test_line_type do
+       fun test_line_type is test do
                var v = new MarkdownProcessor
                subject = new MDLine(loc, "")
                assert v.line_kind(subject) isa LineEmpty
@@ -2681,7 +2680,7 @@ class TestLine
                assert v.line_kind(subject) isa LineOList
        end
 
-       fun test_line_type_ext do
+       fun test_line_type_ext is test do
                var v = new MarkdownProcessor
                subject = new MDLine(loc, "  ~~~")
                assert v.line_kind(subject) isa LineFence
@@ -2691,7 +2690,7 @@ class TestLine
                assert v.line_kind(subject) isa LineFence
        end
 
-       fun test_count_chars do
+       fun test_count_chars is test do
                subject = new MDLine(loc, "")
                assert subject.count_chars('*') == 0
                subject = new MDLine(loc, "* ")
@@ -2704,7 +2703,7 @@ class TestLine
                assert subject.count_chars('*') == 0
        end
 
-       fun test_count_chars_start do
+       fun test_count_chars_start is test do
                subject = new MDLine(loc, "")
                assert subject.count_chars_start('*') == 0
                subject = new MDLine(loc, "* ")
@@ -2719,9 +2718,9 @@ class TestLine
 end
 
 class TestHTMLDecorator
-       super TestSuite
+       test
 
-       fun test_headlines do
+       fun test_headlines is test do
                var test = """
 # **a**
 ## a.a
@@ -2760,9 +2759,9 @@ c:c
 end
 
 class TestTokenLocation
-       super TestSuite
+       test
 
-       fun test_token_location1 do
+       fun test_token_location1 is test do
                var string = "**Hello** `World`"
                var stack =  [
                        "TokenStrongStar at 1,1--1,1",
@@ -2772,7 +2771,7 @@ class TestTokenLocation
                (new TestTokenProcessor(stack)).process(string)
        end
 
-       fun test_token_location2 do
+       fun test_token_location2 is test do
                var string = "**Hello**\n`World`\n*Bonjour*\n[le monde]()"
                var stack =  [
                        "TokenStrongStar at 1,1--1,1",
@@ -2785,7 +2784,7 @@ class TestTokenLocation
                (new TestTokenProcessor(stack)).process(string)
        end
 
-       fun test_token_location3 do
+       fun test_token_location3 is test do
                var string = """**Hello**
                `World`
                *Bonjour*
@@ -2801,7 +2800,7 @@ class TestTokenLocation
                (new TestTokenProcessor(stack)).process(string)
        end
 
-       fun test_token_location4 do
+       fun test_token_location4 is test do
                var string = "**Hello**\n\n`World`"
                var stack =  [
                        "TokenStrongStar at 1,1--1,1",
@@ -2811,7 +2810,7 @@ class TestTokenLocation
                (new TestTokenProcessor(stack)).process(string)
        end
 
-       fun test_token_location5 do
+       fun test_token_location5 is test do
                var string = "# *Title1*\n\n# *Title2*"
                var stack =  [
                        "TokenEmStar at 1,3--1,3",
@@ -2841,11 +2840,11 @@ class TestTokenProcessor
 end
 
 class TestBlockLocation
-       super TestSuite
+       test
 
        var proc = new MarkdownProcessor
 
-       fun test_block_location1 do
+       fun test_block_location1 is test do
                var stack = [
                        "BlockHeadline: 1,1--1,8",
                        "BlockListItem: 2,1--2,6",
@@ -2857,7 +2856,7 @@ class TestBlockLocation
                proc.process(string)
        end
 
-       fun test_block_location2 do
+       fun test_block_location2 is test do
                var stack = [
                        "BlockHeadline: 1,1--1,11",
                        "BlockFence: 3,1--5,4",
@@ -2875,7 +2874,7 @@ some code
                proc.process(string)
        end
 
-       fun test_block_location3 do
+       fun test_block_location3 is test do
                var stack = [
                        "BlockHeadline: 1,1--1,8",
                        "BlockHeadline: 3,1--3,10"]
index 5c4fb7e..133493d 100644 (file)
 # limitations under the License.
 
 # Test suites for module `markdown`
-module test_wikilinks is test_suite
+module test_wikilinks is test
 
 import test_markdown
 import wikilinks
 
 class TestTokenWikilink
-       super TestSuite
+       test
 
-       fun test_token_location1 do
+       fun test_token_location1 is test do
                var string = "[[wikilink]]"
                var stack =  ["TokenWikiLink at 1,1--1,1"]
                (new TestTokenProcessor(stack)).process(string)
        end
 
-       fun test_token_location2 do
+       fun test_token_location2 is test do
                var string = "Hello [[World]]"
                var stack =  ["TokenWikiLink at 1,7--1,7"]
                (new TestTokenProcessor(stack)).process(string)
        end
 
-       fun test_token_location3 do
+       fun test_token_location3 is test do
                var string = "\nHello\nworld [[wikilink]] !"
                var stack =  ["TokenWikiLink at 3,7--3,7"]
                (new TestTokenProcessor(stack)).process(string)
        end
 
-       fun test_token_location4 do
+       fun test_token_location4 is test do
                var string = "[[link1]]\n\n[[link2]]"
                var stack =  [
                        "TokenWikiLink at 1,1--1,1",
@@ -47,7 +47,7 @@ class TestTokenWikilink
                (new TestTokenProcessor(stack)).process(string)
        end
 
-       fun test_token_location5 do
+       fun test_token_location5 is test do
                var string = "[[link1]]\n[[link2]]"
                var stack =  [
                        "TokenWikiLink at 1,1--1,1",
@@ -55,7 +55,7 @@ class TestTokenWikilink
                (new TestTokenProcessor(stack)).process(string)
        end
 
-       fun test_token_location6 do
+       fun test_token_location6 is test do
                var string = """
 [[doc: github]]
 
diff --git a/lib/msgpack/ext.nit b/lib/msgpack/ext.nit
new file mode 100644 (file)
index 0000000..327dbff
--- /dev/null
@@ -0,0 +1,33 @@
+# 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.
+
+# Application specific MessagePack extension `MsgPackExt`
+module ext
+
+import serialization
+
+# Application specific MessagePack extension
+class MsgPackExt
+       serialize
+
+       # Custom type code, in [0..127]
+       var typ: Byte
+
+       # Data bytes
+       var data: Bytes
+
+       redef fun hash do return typ.hash + data.hash*8
+       redef fun ==(o) do return o isa MsgPackExt and o.typ == typ and o.data == data
+       redef fun to_s do return "<{class_name} typ: {typ}, data: {data.chexdigest}>"
+end
diff --git a/lib/msgpack/msgpack.nit b/lib/msgpack/msgpack.nit
new file mode 100644 (file)
index 0000000..929c330
--- /dev/null
@@ -0,0 +1,164 @@
+# 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.
+
+# MessagePack, an efficient binary serialization format
+#
+# This modules provides services at different levels:
+#
+# * Serialize Nit objects using either the quick and easy `Serializable::serialize_msgpack`
+#   and `Write::serialize_msgpack`, or the extensible `MsgPackSerializer`.
+#
+# * Deserialize MessagePack to Nit objects using the quick and easy methods
+#   `Reader|Bytes::deserialize_msgpack`, or inspect errors with the extensible
+#   `MsgPackDeserializer`.
+#
+# * Read and write MessagePack data at a low-level (for engines and the likes)
+#   by importing `msgpack::read` or `msgpack::write`. These services support
+#   only which support only core Nit types with a corresponding MessagePack type.
+#   See `Reader::read_msgpack` and services on `Writer` including `write_msgpack_ext`.
+#
+# Here we discuss the recommended serialization services supporting core
+# Nit and MessagePack types as well as full Nit objects.
+#
+# ## Primitive MessagePack types
+#
+# Most Nit core types are serialized to the smallest corresponding MessagePack type.
+#
+# ~~~
+# assert false.serialize_msgpack == b"\xC2"
+# assert b"\xC2".deserialize_msgpack == false
+#
+# assert true.serialize_msgpack  == b"\xC3"
+# assert b"\xC3".deserialize_msgpack == true
+#
+# assert 1.234.serialize_msgpack == b"\xCB\x3F\xF3\xBE\x76\xC8\xB4\x39\x58"
+# assert b"\xCB\x3F\xF3\xBE\x76\xC8\xB4\x39\x58".deserialize_msgpack == 1.234
+#
+# assert "ABC".serialize_msgpack == b"\xA3ABC"
+# assert b"\xA3ABC".deserialize_msgpack == "ABC"
+#
+# assert [0x11, 0x22, 0x33].serialize_msgpack(plain=true) == b"\x93\x11\x22\x33"
+# assert b"\x93\x11\x22\x33".deserialize_msgpack == [0x11, 0x22, 0x33]
+#
+# var map = new Map[String, nullable Object]
+# map["i"] = 1
+# map["o"] = null
+# assert map.serialize_msgpack(plain=true) == b"\x82\xA1\x69\x01\xA1\x6F\xC0"
+# ~~~
+#
+# Ints are serialized to the smallest MessagePack type, so a small integer fits
+# in a single byte and larger integers take more bytes as needed.
+#
+# ~~~
+# assert 1.serialize_msgpack            == b"\x01"
+# assert (-32).serialize_msgpack        == b"\xE0"
+# assert 0x7F.serialize_msgpack         == b"\x7F"
+# assert 0x80.serialize_msgpack         == b"\xCC\x80"
+# assert 0x1234.serialize_msgpack       == b"\xCD\x12\x34"
+# assert (-0x1234).serialize_msgpack    == b"\xD1\xED\xCC"
+# assert 0x12345678.serialize_msgpack   == b"\xCE\x12\x34\x56\x78"
+# assert 0x0123456789.serialize_msgpack == b"\xCF\x00\x00\x00\x01\x23\x45\x67\x89"
+#
+# assert b"\x01".deserialize_msgpack                 == 1
+# assert b"\xE0".deserialize_msgpack                 == -32
+# assert b"\x7F".deserialize_msgpack                 == 0x7F
+# assert b"\xCC\x80".deserialize_msgpack             == 0x80
+# assert b"\xCD\x12\x34".deserialize_msgpack         == 0x1234
+# assert b"\xD1\xED\xCC".deserialize_msgpack         == -0x1234
+# assert b"\xCE\x12\x34\x56\x78".deserialize_msgpack == 0x12345678
+# assert b"\xCF\x00\x00\x00\x01\x23\x45\x67\x89".deserialize_msgpack == 0x0123456789
+# ~~~
+#
+# ## Primitive Nit type without a MessagePack equivalent
+#
+# Chars are serialized as a string in plain mode.
+#
+# ~~~
+# assert 'A'.serialize_msgpack(plain=true)      == b"\xA1\x41"
+# assert b"\xA1\x41".deserialize_msgpack == "A" # Not a Char
+# ~~~
+#
+# Or, with metadata, chars are serialized to an ext with id 0x7C.
+#
+# ~~~
+# assert 'A'.serialize_msgpack                      == b"\xD4\x7C\x41"
+# assert b"\xD4\x7C\x41".deserialize_msgpack == 'A'
+# ~~~
+#
+# Byte instances are serialized as an integer in plain mode.
+#
+# ~~~
+# assert 0x01u8.serialize_msgpack(plain=true) == b"\x01"
+# assert b"\x01".deserialize_msgpack   == 1 # Not a Byte
+# ~~~
+#
+# Or, with metadata, byte instances are serialized to an ext with id 0x7E.
+#
+# ~~~
+# assert 0x01u8.serialize_msgpack                   == b"\xD4\x7E\x01"
+# assert b"\xD4\x7E\x01".deserialize_msgpack == 0x01u8
+# ~~~
+#
+# ## Full objects
+#
+# Objects are serialized to a map in plain mode, replacing cycles by `null` values.
+# This creates plain MessagePack easy to read for other non-Nit programs,
+# but cycles and the dynamic type information are lost.
+#
+# ~~~
+# class A
+#     serialize
+#
+#     var i = 1
+#     var o: nullable A = self
+#
+#     redef fun ==(o) do return o isa A and o.i == i # Skip the cyclic `o`
+# end
+#
+# var a = new A
+# var bytes = a.serialize_msgpack(plain=true)
+# assert bytes == b"\x82\xA1\x69\x01\xA1\x6F\xC0"
+# assert bytes.deserialize_msgpack isa Map[nullable Serializable, nullable Serializable] # Not an A
+# ~~~
+#
+# Or, with metadata, the same object is serialized with information on object
+# uniqueness (with an id and references) and its dynamic type.
+# The whole object is contained in a MessagePack array:
+#
+# * The array holds the metadata and attributes or each object,
+#   here it is a fixarray of 3 items: 0x93
+# * Define an object (ext type 0x7B) with the id 0, here a fixext1: 0xD47B00
+# * The dynamic type name, here a fixstr with the letter 'A': 0xA141
+# * The attributes as a map, here a fixmap of 2 items: 0x82
+# * First attribute name, here a fixstr for "i": 0xA169
+# * First attribute value, here a fixint for 1: 0x01
+# * Second attribute name, here a fixstr for "o": 0xA16F
+# * Second attribute value, a reference (ext type 0x7D) to object id 0,
+#   here a fixext1: 0xD47D00
+#
+# ~~~
+# bytes = a.serialize_msgpack
+# assert bytes == b"\x93\xD4\x7B\x00\xA1\x41\x82\xA1\x69\x01\xA1\x6F\xD4\x7D\x00"
+# assert bytes.deserialize_msgpack == a
+# ~~~
+#
+# ## References
+#
+# Format description and other implementations: http://msgpack.org/
+#
+# Format specification: https://github.com/msgpack/msgpack/blob/master/spec.md
+module msgpack
+
+import serialization_write
+import serialization_read
diff --git a/lib/msgpack/msgpack_to_json.nit b/lib/msgpack/msgpack_to_json.nit
new file mode 100644 (file)
index 0000000..86337cd
--- /dev/null
@@ -0,0 +1,39 @@
+# 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.
+
+# Convert MessagePack format to JSON
+module msgpack_to_json
+
+import msgpack::read
+import json
+
+if args.has("-h") then
+       print "Usage: nit msgpack::msgpack_to_json [source_file.msgpack]"
+       print "Convert MessagePack format to JSON. Read from stdin if no source_file is given."
+       exit 0
+end
+
+var reader = if args.length >= 1 then
+               new FileReader.open(args.first)
+       else stdin
+
+while reader.last_error == null and not reader.eof do
+       var deserialized = reader.read_msgpack
+
+       if deserialized != null then
+               print deserialized.serialize_to_json(plain=true, pretty=true)
+       else
+               print "null"
+       end
+end
diff --git a/lib/msgpack/package.ini b/lib/msgpack/package.ini
new file mode 100644 (file)
index 0000000..2820571
--- /dev/null
@@ -0,0 +1,11 @@
+[package]
+name=msgpack
+tags=format,lib
+maintainer=Alexis Laferrière <alexis.laf@xymus.net>
+license=Apache-2.0
+[upstream]
+browse=https://github.com/nitlang/nit/tree/master/lib/msgpack/
+git=https://github.com/nitlang/nit.git
+git.directory=lib/msgpack/
+homepage=http://nitlanguage.org
+issues=https://github.com/nitlang/nit/issues
diff --git a/lib/msgpack/read.nit b/lib/msgpack/read.nit
new file mode 100644 (file)
index 0000000..b068a81
--- /dev/null
@@ -0,0 +1,216 @@
+# 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.
+
+# Low-level read MessagePack format from `Reader` streams
+module read
+
+import serialization
+private import binary
+
+import ext
+
+redef class Reader
+
+       # Read the next MessagePack object and return it as a simple Nit object
+       #
+       # The return value is composed of:
+       # * the simple types `null`, `Bool`, `Int`, `Float`, `String` and `Bytes`,
+       # * collections of simple Nit objects `Array[nullable Serializable]`
+       #   and `Map[nullable Serializable, nullable Serializable]`,
+       # * and `MsgPackExt` for custom MessagePack *ext* data.
+       #
+       # This method reads plain MessagePack data, as written by `MsgPackSerializer`
+       # when `plain_msgpack == true`. To deserialize full Nit objects from
+       # MessagePack with metadata use `Reader::deserialize_msgpack`.
+       fun read_msgpack: nullable Serializable
+       do
+               if last_error != null then return 0
+
+               var typ = read_byte
+               if typ == null then
+                       # Error, return default `null`
+                       return null
+
+               else if typ & 0b1000_0000u8 == 0u8 or typ & 0b1110_0000u8 == 0b1110_0000u8 then
+                       # fixint
+                       var bytes = new Bytes.with_capacity(1)
+                       bytes.add typ
+                       return bytes.to_i(signed=true)
+
+               else if typ & 0b1111_0000u8 == 0b1000_0000u8 then
+                       # fixmap
+                       var len = typ & 0b0000_1111u8
+                       return read_msgpack_map_data(len.to_i)
+
+               else if typ & 0b1111_0000u8 == 0b1001_0000u8 then
+                       # fixarray
+                       var len = typ & 0b0000_1111u8
+                       return read_msgpack_array_data(len.to_i)
+
+               else if typ & 0b1110_0000u8 == 0b1010_0000u8 then
+                       # fixstr
+                       var len = typ & 0b0001_1111u8
+                       return read_bytes(len.to_i).to_s
+
+               else if typ == 0xC0u8 then
+                       return null
+               else if typ == 0xC2u8 then
+                       return false
+               else if typ == 0xC3u8 then
+                       return true
+
+               else if typ == 0xCCu8 then
+                       # uint8
+                       return (read_byte or else 0u8).to_i
+               else if typ == 0xCDu8 then
+                       # uint16
+                       return read_bytes(2).to_i
+               else if typ == 0xCEu8 then
+                       # uint32
+                       return read_bytes(4).to_i
+               else if typ == 0xCFu8 then
+                       # uint64
+                       return read_bytes(8).to_i
+               else if typ == 0xD0u8 then
+                       # int8
+                       return read_bytes(1).to_i(true)
+               else if typ == 0xD1u8 then
+                       # int16
+                       return read_bytes(2).to_i(true)
+               else if typ == 0xD2u8 then
+                       # int32
+                       return read_bytes(4).to_i(true)
+               else if typ == 0xD3u8 then
+                       # int64
+                       return read_int64
+
+               else if typ == 0xCAu8 then
+                       return read_float
+               else if typ == 0xCBu8 then
+                       return read_double
+
+               else if typ == 0xD9u8 then
+                       # str8
+                       var len = read_byte
+                       if len == null then return null
+                       return read_bytes(len.to_i).to_s
+               else if typ == 0xDAu8 then
+                       # str16
+                       var len = read_bytes(2)
+                       return read_bytes(len.to_i).to_s
+               else if typ == 0xDBu8 then
+                       # str32
+                       var len = read_bytes(4)
+                       return read_bytes(len.to_i).to_s
+
+               else if typ == 0xC4u8 then
+                       # bin8
+                       var len = read_byte
+                       if len == null then return null
+                       return read_bytes(len.to_i)
+               else if typ == 0xC5u8 then
+                       # bin16
+                       var len = read_bytes(2)
+                       return read_bytes(len.to_i)
+               else if typ == 0xC6u8 then
+                       # bin32
+                       var len = read_bytes(4)
+                       return read_bytes(len.to_i)
+
+               else if typ == 0xDCu8 then
+                       # array16
+                       var len = read_bytes(2)
+                       return read_msgpack_array_data(len.to_i)
+               else if typ == 0xDDu8 then
+                       # array32
+                       var len = read_bytes(4)
+                       return read_msgpack_array_data(len.to_i)
+
+               else if typ == 0xDEu8 then
+                       # map16
+                       var len = read_bytes(2)
+                       return read_msgpack_map_data(len.to_i)
+               else if typ == 0xDFu8 then
+                       # map32
+                       var len = read_bytes(4)
+                       return read_msgpack_map_data(len.to_i)
+
+               else if typ == 0xD4u8 then
+                       # fixext1
+                       return read_msgpack_fixext_data(1)
+               else if typ == 0xD5u8 then
+                       # fixext2
+                       return read_msgpack_fixext_data(2)
+               else if typ == 0xD6u8 then
+                       # fixext4
+                       return read_msgpack_fixext_data(4)
+               else if typ == 0xD7u8 then
+                       # fixext8
+                       return read_msgpack_fixext_data(8)
+               else if typ == 0xD8u8 then
+                       # fixext16
+                       return read_msgpack_fixext_data(16)
+
+               else if typ == 0xC7u8 then
+                       # ext1
+                       return read_msgpack_ext_data(1)
+               else if typ == 0xC8u8 then
+                       # ext2
+                       return read_msgpack_ext_data(2)
+               else if typ == 0xC9u8 then
+                       # ext4
+                       return read_msgpack_ext_data(4)
+               end
+
+               print_error "MessagePack Warning: Found no match for typ {typ} / 0b{typ.to_i.to_base(2)}"
+               return null
+       end
+
+       # Read the content of a map, `len` keys and values
+       private fun read_msgpack_map_data(len: Int): Map[nullable Serializable, nullable Serializable]
+       do
+               var map = new Map[nullable Serializable, nullable Serializable]
+               for i in [0..len.to_i[ do map[read_msgpack] = read_msgpack
+               return map
+       end
+
+       # Read the content of an array of `len` items
+       private fun read_msgpack_array_data(len: Int): Array[nullable Serializable]
+       do
+               return [for i in [0..len[ do read_msgpack]
+       end
+
+       # Read the content of a *fixext* of `len` bytes
+       #
+       # ~~~
+       # var reader = new BytesReader(b"\xC7\x03\x0A\x0B\x0C\x0D")
+       # var ext = reader.read_msgpack
+       # assert ext isa MsgPackExt
+       # assert ext.typ == 0x0Au8
+       # assert ext.data == b"\x0B\x0C\x0D"
+       # ~~~
+       private fun read_msgpack_fixext_data(len: Int): MsgPackExt
+       do
+               var exttyp = read_byte or else 0u8
+               var data = read_bytes(len)
+               return new MsgPackExt(exttyp, data)
+       end
+
+       # Read the content of a dynamic *ext* including the length on `len_len` bytes
+       private fun read_msgpack_ext_data(len_len: Int): MsgPackExt
+       do
+               var len = read_bytes(len_len).to_i
+               return read_msgpack_fixext_data(len)
+       end
+end
diff --git a/lib/msgpack/serialization_common.nit b/lib/msgpack/serialization_common.nit
new file mode 100644 (file)
index 0000000..a9e9ddf
--- /dev/null
@@ -0,0 +1,32 @@
+# 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.
+
+# Serialization services for `serialization_write` and `serialization_read`
+module serialization_common
+
+# MessagePack serialization or deserialization engine
+abstract class MsgPackEngine
+
+       # *ext type* byte for object definitions, defaults to 0x7Bu8 or '{'
+       var ext_typ_obj: Byte = 0x7Bu8 is writable
+
+       # *ext type* byte for object references, defaults to 0x7Du8 or '}'
+       var ext_typ_ref: Byte = 0x7Du8 is writable
+
+       # *ext type* byte to identify a char, defaults to 0x7Cu8 or '~'
+       var ext_typ_char: Byte = 0x7Cu8 is writable
+
+       # *ext type* byte to identify a byte, defaults to 0x7Eu8 or '|'
+       var ext_typ_byte: Byte = 0x7Eu8 is writable
+end
diff --git a/lib/msgpack/serialization_read.nit b/lib/msgpack/serialization_read.nit
new file mode 100644 (file)
index 0000000..75f3874
--- /dev/null
@@ -0,0 +1,410 @@
+# 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.
+
+# Deserialize full Nit objects from MessagePack format
+#
+# See the package `msgpack` for more details on the serialization
+# of Nit objects.
+module serialization_read
+
+import serialization::caching
+import serialization::safe
+private import json # for class_inheritance_metamodel
+private import serialization::engine_tools
+
+import serialization_common
+private import read
+import ext
+
+# ---
+# Easy services
+
+redef class Bytes
+
+       # Deserialize full Nit `nullable Object` from MessagePack formated data
+       #
+       # The dynamic type of the deserialized object can be limited to `static_type`.
+       #
+       # Warning: Deserialization errors are reported with `print_error`,
+       # the returned object may be partial or fall back on `null`.
+       # To handle the errors programmatically, use a `MsgPackDeserializer`.
+       fun deserialize_msgpack(static_type: nullable String): nullable Object
+       do
+               var stream = new BytesReader(self)
+               var res = stream.deserialize_msgpack(static_type)
+               stream.close
+               return res
+       end
+end
+
+redef class Reader
+
+       # Deserialize full Nit `nullable Object` from MessagePack formated data
+       #
+       # This method use metadata in the MessagePack source to recreate full
+       # Nit objects serialized by `Writer::serialize_msgpack` or
+       # `MsgPackSerializer`.
+       #
+       # The dynamic type of the deserialized object can be limited to `static_type`.
+       #
+       # Warning: Deserialization errors are reported with `print_error`,
+       # the returned object may be partial or fall back on `null`.
+       # To handle the errors programmatically, use a `MsgPackDeserializer`.
+       fun deserialize_msgpack(static_type: nullable String): nullable Object
+       do
+               var deserializer = new MsgPackDeserializer(self)
+               var res = deserializer.deserialize(static_type)
+
+               if deserializer.errors.length == 1 then
+                       print_error deserializer.errors.join("")
+               else if deserializer.errors.not_empty then
+                       print_error "Deserialization Errors:\n* {deserializer.errors.join("\n* ")}"
+               end
+
+               return res
+       end
+end
+
+# ---
+# Engine
+
+# Deserialize MessagePack format to full Nit objects
+class MsgPackDeserializer
+       super CachingDeserializer
+       super MsgPackEngine
+       super SafeDeserializer
+
+       # Source stream
+       var stream: Reader
+
+       # Map of attributes from the root deserialized object to the current object
+       private var path = new Array[Map[nullable Serializable, nullable Serializable]]
+
+       # Metadata arrays with from the root deserialized object to the current object
+       var path_arrays = new Array[nullable Array[nullable Object]]
+
+       # Names of the attributes from the root to the object currently being deserialized
+       var attributes_path = new Array[String]
+
+       # Last encountered object reference id.
+       #
+       # See `id_to_object`.
+       private var just_opened_id: nullable Int = null
+
+       redef fun deserialize_attribute(name, static_type)
+       do
+               if path.is_empty then
+                       # The was a parsing error or the root is not an object
+                       deserialize_attribute_missing = false
+                       return null
+               end
+
+               var current = path.last
+
+               var serialized_value = null
+               var serialized_value_found = false
+               if current.keys.has(name) then
+                       # Non-cached string
+                       serialized_value = current[name]
+                       serialized_value_found = true
+               else
+                       # It may be cached, deserialize all keys until we find it
+                       for key in current.keys.to_a do
+                               if key isa Array[nullable Serializable] or key isa MsgPackExt then
+                                       var str = convert_object(key, "String")
+                                       if str isa String then
+                                               var value = current[key]
+                                               current.keys.remove key
+                                               current[str] = value
+
+                                               if str == name then
+                                                       serialized_value = value
+                                                       serialized_value_found = true
+                                                       break
+                                               end
+                                       end
+                               end
+                       end
+               end
+
+               if not serialized_value_found then
+                       # Let the generated code / caller of `deserialize_attribute` raise the missing attribute error
+                       deserialize_attribute_missing = true
+                       return null
+               end
+
+               attributes_path.add name
+               var res = convert_object(serialized_value, static_type)
+               attributes_path.pop
+
+               deserialize_attribute_missing = false
+               return res
+       end
+
+       # This may be called multiple times by the same object from defs of a same constructor
+       redef fun notify_of_creation(new_object)
+       do
+               var id = just_opened_id
+               if id == null then return
+               cache[id] = new_object
+       end
+
+       # Convert the simple JSON `object` to a Nit object
+       private fun convert_object(object: nullable Object, static_type: nullable String): nullable Object
+       do
+               #print "convert_object {if object != null then object.class_name else "null"}"
+               if object isa Array[nullable Object] and object.length >= 1 then
+                       # Serialized object?
+                       var first = object.first
+                       if first isa MsgPackExt then
+                               if first.typ == ext_typ_obj then
+                                       # An array starts with a *ext*, it must be a serialized object
+
+                                       # New object declaration
+                                       var id = first.data.to_i
+
+                                       if cache.has_id(id) then
+                                               # FIXME use Warning
+                                               errors.add new Error("Deserialization Error: object with id {id} is deserialized twice.")
+                                               # Keep going
+                                       end
+
+                                       var type_name = null
+                                       var i = 1
+
+                                       # Read dynamic type
+                                       if object.length >= 2 then
+
+                                               # Try to get the type name as a string
+                                               var o = object[i]
+                                               if o isa String and static_type == "String" and object.length == 2 then
+                                                       cache[id] = o
+                                                       return o
+                                               else
+                                                       var typ = convert_object(object[i], "String")
+                                                       if typ isa String then
+                                                               type_name = typ
+                                                               i += 1
+                                                       end
+                                               end
+                                       end
+
+                                       if type_name == null then
+                                               # There was no dynamic type
+
+                                               # We could use a `class_name_heuristic` here...
+
+                                               # Fallback to the static type
+                                               if static_type != null then
+                                                       type_name = static_type.strip_nullable
+                                               end
+
+                                               if type_name == null then
+                                                       errors.add new Error("Deserialization Error: could not determine dynamic type of `{object}`.")
+                                                       return null
+                                               end
+                                       end
+
+                                       if not accept(type_name, static_type) then return null
+
+                                       var attributes = null
+                                       if object.length > i then attributes = object[i]
+                                       if not attributes isa Map[nullable Serializable, nullable Serializable] then
+                                               # Some other type (could be an error), or there's no attributes
+                                               attributes = new Map[nullable Serializable, nullable Serializable]
+                                       end
+
+                                       # advance on path
+                                       path.push attributes
+                                       path_arrays.push object
+
+                                       just_opened_id = id
+                                       var value = deserialize_class(type_name)
+                                       just_opened_id = null
+
+                                       # revert on path
+                                       path.pop
+                                       path_arrays.pop
+
+                                       return value
+                               else
+                                       errors.add new Error("Deserialization Error: unknown MessagePack ext '{first.typ}'.")
+                               end
+                       end
+
+                       # Plain array? Try to convert it to the desired static_type
+                       if static_type != null then
+                               return deserialize_class(static_type.strip_nullable)
+                       end
+                       return object
+               end
+
+               if object isa Map[nullable Serializable, nullable Serializable] then
+                       # Plain map
+                       # TODO parse it as an instance of `static_type`
+
+                       if static_type != null then
+                               path.push object
+                               path_arrays.push null
+
+                               just_opened_id = null
+                               var value = deserialize_class(static_type.strip_nullable)
+
+                               path.pop
+                               path_arrays.pop
+
+                               return value
+                       end
+
+                       return object
+               end
+
+               if object isa MsgPackExt then
+
+                       # First try the custom extensions
+                       var custom = deserialize_ext(object, static_type)
+                       if custom == null then
+
+                               # No custom, go for deser standard references
+                               if object.typ == ext_typ_ref then
+                                       # Reference to an object
+                                       var id = object.data.to_i
+                                       if not cache.has_id(id) then
+                                               errors.add new Error("Deserialization Error: object reference id unknown.")
+                                               return object
+                                       end
+                                       return cache.object_for(id)
+
+                               else if object.typ == ext_typ_char then
+                                       # Char
+                                       return object.data.to_s.first
+
+                               else if object.typ == ext_typ_byte then
+                                       # Byte
+                                       return object.data.first
+                               end
+                       end
+               end
+
+               if object isa String and object.length == 1 and static_type == "Char" then
+                       # Char serialized as a string
+                       return object.chars.first
+               end
+
+               if object isa Int and static_type == "Byte" then
+                       # Byte serialized as an integer
+                       return object.to_b
+               end
+
+               return object
+       end
+
+       redef fun deserialize(static_type)
+       do
+               errors.clear
+
+               var root = stream.read_msgpack
+               return convert_object(root, static_type)
+       end
+
+       # Hook to customize the deserialization of MessagePack extensions
+       #
+       # Redefine this method in subclasses to return custom Nit objects from
+       # an application specific extension.
+       #
+       # This method is invoked before dealing with the extensions used by the
+       # Nit serialization metadata [0x40..0x43]. In general, you should ignore
+       # them by returning `null`, but they can also be intercepted to comply to
+       # a format from a remote server.
+       protected fun deserialize_ext(ext: MsgPackExt, static_type: nullable String): nullable Object
+       do
+               return null
+       end
+end
+
+redef class SimpleCollection[E]
+       redef init from_deserializer(v)
+       do
+               super
+               if v isa MsgPackDeserializer then
+                       v.notify_of_creation self
+                       init
+
+                       var open_array = v.path_arrays.last
+                       var msgpack_items = null
+                       if open_array != null then msgpack_items = open_array.last
+
+                       if not msgpack_items isa Array[nullable Serializable] then
+                               v.errors.add new Error("Deserialization Error: no items in source of `{class_name}`")
+                               return
+                       end
+
+                       # Name of the dynamic name of E
+                       var items_type_name = (new GetName[E]).to_s
+
+                       # Fill array
+                       for o in msgpack_items do
+                               var obj = v.convert_object(o, items_type_name)
+                               if obj isa E then
+                                       add obj
+                               else v.errors.add new AttributeTypeError(self, "items", obj, items_type_name)
+                       end
+               end
+       end
+end
+
+redef class Map[K, V]
+       redef init from_deserializer(v)
+       do
+               super
+
+               if v isa MsgPackDeserializer then
+                       v.notify_of_creation self
+                       init
+
+                       var open_object = v.path_arrays.last
+                       var msgpack_items
+                       if open_object != null then
+                               # Metadata available
+                               msgpack_items = open_object.last
+                       else
+                               msgpack_items = v.path.last
+                       end
+
+                       if not msgpack_items isa Map[nullable Object, nullable Object] then
+                               v.errors.add new Error("Deserialization Error: no key/values in source of `{class_name}`")
+                               return
+                       end
+
+                       var keys_type_name = (new GetName[K]).to_s
+                       var values_type_name = (new GetName[V]).to_s
+
+                       for key_src, value_src in msgpack_items do
+                               var key = v.convert_object(key_src, keys_type_name)
+                               if not key isa K then
+                                       v.errors.add new AttributeTypeError(self, "keys", key, keys_type_name)
+                                       continue
+                               end
+
+                               var value = v.convert_object(value_src, values_type_name)
+                               if not value isa V then
+                                       v.errors.add new AttributeTypeError(self, "values", value, values_type_name)
+                                       continue
+                               end
+
+                               self[key] = value
+                       end
+               end
+       end
+end
diff --git a/lib/msgpack/serialization_write.nit b/lib/msgpack/serialization_write.nit
new file mode 100644 (file)
index 0000000..0b5b4f9
--- /dev/null
@@ -0,0 +1,349 @@
+# 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.
+
+# Serialize full Nit objects to MessagePack format
+#
+# There are 3 main entrypoint services:
+# * `Writer::serialize_msgpack` adds an object to any stream writer.
+# * `Serializable::serialize_msgpack` serializes the object to bytes.
+# * `MsgPackSerializer` gives full control over the serialization of
+#   Nit objets to the MessagePack format.
+module serialization_write
+
+import serialization::caching
+private import serialization::engine_tools
+
+import serialization_common
+private import write
+import ext
+
+# MessagePack deserialization engine
+class MsgPackSerializer
+       super CachingSerializer
+       super MsgPackEngine
+
+       # Target writing stream
+       var stream: Writer
+
+       # Write plain MessagePack without metadata for deserialization?
+       #
+       # If `false`, the default, serialize to support deserialization:
+       #
+       # * Each object is encapsulated in an array that contains metadata and
+       #   the actual object attributes in a map. The metadata includes the type
+       #   name and references to already serialized object. This information
+       #   supports deserializing the message, including cycles.
+       # * Preserve the Nit `Char` and `Byte` types as an object.
+       # * The generated MessagePack is standard and can be read by non-Nit programs.
+       #   However, it contains some complexity that may make it harder to use.
+       #
+       # If `true`, serialize only the real data or non-Nit programs:
+       #
+       # * Nit objects are serialized to pure and standard MessagePack so they can
+       #   be easily read by non-Nit programs.
+       # * Nit objects are serialized at every reference, so they may be duplicated.
+       #   It is easier to read but it creates a larger output and it does not support
+       #   cycles. Cyclic references are replaced by `null`.
+       # * The serialized data can only be deserialized to their expected static
+       #   types, losing the knowledge of their dynamic type.
+       var plain_msgpack = false is writable
+
+       # Should strings declaring the objects type and attributes name be cached?
+       #
+       # If `true` metadata strings are cached using `cache`.
+       # The first occurrence is written as an object declaration,
+       # successive occurrences are written as an object reference.
+       #
+       # If `false`, the default, metadata strings are written as pure MessagePack
+       # strings, without their own metadata.
+       #
+       # Using the cache may save some space by avoiding the repetition of
+       # names used by many types or attributes.
+       # However, it adds complexity to the generated message and may be less
+       # safe for versioning.
+       var cache_metadata_strings = false is writable
+
+       # List of the current open objects, the first is the main target of the serialization
+       #
+       # Used only when `plain_msgpack == true` to detect cycles in serialization.
+       private var open_objects = new Array[Object]
+
+       redef var current_object = null
+
+       redef fun serialize(object)
+       do
+               if object == null then
+                       stream.write_msgpack_null
+               else
+                       if plain_msgpack then
+                               for o in open_objects do
+                                       if object.is_same_serialized(o) then
+                                               # Cycle, can't be managed in plain_msgpack mode
+                                               warn "Cycle detected in serialized object, replacing reference with 'null'."
+                                               stream.write_msgpack_null
+                                               return
+                                       end
+                               end
+
+                               open_objects.add object
+                       end
+
+                       var last_object = current_object
+                       current_object = object
+                       object.accept_msgpack_serializer self
+                       current_object = last_object
+
+                       if plain_msgpack then open_objects.pop
+               end
+       end
+
+       redef fun serialize_attribute(name, value)
+       do
+               serialize_meta_string name
+               super
+       end
+
+       redef fun serialize_reference(object)
+       do
+               if not plain_msgpack and cache.has_object(object) then
+                       # if already serialized, add local reference
+                       var id = cache.id_for(object)
+                       stream.write_msgpack_ext(ext_typ_ref, id.to_bytes)
+               else
+                       # serialize
+                       serialize object
+               end
+       end
+
+       private fun serialize_meta_string(type_name: String)
+       do
+               if plain_msgpack or not cache_metadata_strings then
+                       # String only version
+                       stream.write_msgpack_str type_name
+                       return
+               end
+
+               if cache.has_object(type_name) then
+                       # if already serialized, add reference
+                       var id = cache.id_for(type_name)
+                       stream.write_msgpack_ext(ext_typ_ref, id.to_bytes)
+               else
+                       # serialize
+                       var id = cache.new_id_for(type_name)
+                       stream.write_msgpack_array 2 # obj+id, type_name
+                       stream.write_msgpack_ext(ext_typ_obj, id.to_bytes)
+                       stream.write_msgpack_str type_name
+               end
+       end
+end
+
+# Serialization visitor to count attribute in `Serializable` objects
+class AttributeCounter
+       super Serializer
+
+       # Number of attributes counted
+       var count = 0
+
+       redef fun serialize_attribute(name, object) do count += 1
+end
+
+# ---
+# Services and serializables
+
+redef class Writer
+       # Serialize `value` in MessagePack format
+       fun serialize_msgpack(value: nullable Serializable, plain: nullable Bool)
+       do
+               var serializer = new MsgPackSerializer(self)
+               serializer.plain_msgpack = plain or else false
+               serializer.serialize value
+       end
+end
+
+redef class Serializable
+
+       # Serialize `self` to MessagePack bytes
+       #
+       # Set `plain = true` to generate standard MessagePack, without deserialization metadata.
+       # Use this option if the generated MessagePack will be read by non-Nit programs.
+       # Use the default, `plain = false`, if the MessagePack bytes are to be deserialized by a Nit program.
+       fun serialize_msgpack(plain: nullable Bool): Bytes
+       do
+               var stream = new BytesWriter
+               stream.serialize_msgpack(self, plain)
+               stream.close
+               return stream.bytes
+       end
+
+       # Hook to customize the serialization of this class to MessagePack
+       #
+       # 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 `MsgPackSerializer`.
+       #
+       # Most of the time, it is better to refine the method `core_serialize_to`
+       # which is used by all the serialization engines, not just MessagePack.
+       protected fun accept_msgpack_serializer(v: MsgPackSerializer)
+       do
+
+               # Count the number of attributes
+               var attribute_counter = new AttributeCounter
+               accept_msgpack_attribute_counter attribute_counter
+               var n_attributes = attribute_counter.count
+
+               if not v.plain_msgpack then
+
+                       var n_meta_items = 2
+                       if n_attributes > 0 then n_meta_items += 1
+                       n_meta_items += msgpack_extra_array_items # obj+id, class_name, attributes
+
+                       # Metadata
+                       var id = v.cache.new_id_for(self)
+                       v.stream.write_msgpack_array n_meta_items
+                       v.stream.write_msgpack_ext(v.ext_typ_obj, id.to_bytes)
+                       v.serialize_meta_string class_name
+
+                       if n_attributes > 0 then v.stream.write_msgpack_map n_attributes
+               else
+                       v.stream.write_msgpack_map n_attributes
+               end
+
+               v.serialize_core self
+       end
+
+       # Hook to customize the behavior of the `AttributeCounter`
+       #
+       # By default, this method makes `v` visits all serializable attributes.
+       protected fun accept_msgpack_attribute_counter(v: AttributeCounter)
+       do
+               v.serialize_core self
+       end
+
+       # Hook to request a larger than usual metadata array
+       #
+       # Use by `SimpleCollection` and `Map` to append the items after
+       # the metadata and attributes.
+       protected fun msgpack_extra_array_items: Int do return 0
+end
+
+redef class MsgPackExt
+       redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_ext(typ, data)
+end
+
+redef class Text
+       redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_str self
+end
+
+redef class Int
+       redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_int self
+end
+
+redef class Float
+       redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_double self
+end
+
+redef class Bool
+       redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_bool self
+end
+
+redef class Byte
+       redef fun accept_msgpack_serializer(v)
+       do
+               if v.plain_msgpack then
+                       # Write as a string
+                       v.stream.write_msgpack_int to_i
+               else
+                       # Write as ext
+                       var bytes = new Bytes.with_capacity(1)
+                       bytes.add self
+                       v.stream.write_msgpack_ext(v.ext_typ_byte, bytes)
+               end
+       end
+end
+
+redef class Char
+       redef fun accept_msgpack_serializer(v)
+       do
+               if v.plain_msgpack then
+                       # Write as a string
+                       v.stream.write_msgpack_fixstr to_s
+               else
+                       # Write as ext
+                       var bytes = to_s.to_bytes
+                       v.stream.write_msgpack_ext(v.ext_typ_char, bytes)
+               end
+       end
+end
+
+redef class Bytes
+       redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_bin self
+end
+
+redef class CString
+       redef fun accept_msgpack_serializer(v) do to_s.accept_msgpack_serializer(v)
+end
+
+redef class SimpleCollection[E]
+       redef fun accept_msgpack_serializer(v)
+       do
+               if not v.plain_msgpack then
+                       # Add metadata and other attributes
+                       super
+               end
+
+               # Header
+               v.stream.write_msgpack_array length
+
+               # Items
+               for e in self do
+                       if not v.try_to_serialize(e) then
+                               assert e != null # null would have been serialized
+                               v.warn "element of type {e.class_name} is not serializable."
+                               v.stream.write_msgpack_null
+                       end
+               end
+       end
+
+       redef fun msgpack_extra_array_items do return 1
+end
+
+redef class Map[K, V]
+       redef fun accept_msgpack_serializer(v)
+       do
+               if not v.plain_msgpack then
+                       # Add metadata and other attributes
+                       super
+               end
+
+               # Header
+               v.stream.write_msgpack_map keys.length
+
+               # Key / values, alternating
+               for key, val in self do
+                       if not v.try_to_serialize(key) then
+                               assert val != null # null would have been serialized
+                               v.warn "element of type {val.class_name} is not serializable."
+                               v.stream.write_msgpack_null
+                       end
+
+                       if not v.try_to_serialize(val) then
+                               assert val != null # null would have been serialized
+                               v.warn "element of type {val.class_name} is not serializable."
+                               v.stream.write_msgpack_null
+                       end
+               end
+       end
+
+       redef fun msgpack_extra_array_items do return 1
+end
diff --git a/lib/msgpack/write.nit b/lib/msgpack/write.nit
new file mode 100644 (file)
index 0000000..15e5985
--- /dev/null
@@ -0,0 +1,533 @@
+# 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.
+
+# Low-level write in MessagePack format to `Writer` streams
+module write
+
+import binary
+
+redef class Writer
+
+       # Write `null`, or nil, in MessagePack format
+       fun write_msgpack_null do write_byte 0xC0u8
+
+       # Write `bool` in MessagePack format
+       fun write_msgpack_bool(bool: Bool)
+       do write_byte(if bool then 0xC3u8 else 0xC2u8)
+
+       # ---
+       # Integers
+
+       # Write the integer `value` either as the shortest possible MessagePack _int_
+       fun write_msgpack_int(value: Int)
+       do
+               if value >= -0x20 and value <= 0x7F then
+                       write_msgpack_fixint value
+               else if value >= 0 then
+                       if value <= 0xFF then
+                               write_msgpack_uint8 value
+                       else if value <= 0xFFFF then
+                               write_msgpack_uint16 value
+                       else if value <= 0xFFFF_FFFF then
+                               write_msgpack_uint32 value
+                       else #if value <= 0xFFFF_FFFF_FFFF_FFFF then
+                               write_msgpack_uint64 value
+                       end
+               else if value >= -128 then
+                       write_msgpack_int8 value
+               else if value >= -32768 then
+                       write_msgpack_int16 value
+               else if value >= -2147483648 then
+                       write_msgpack_int32 value
+               else
+                       write_msgpack_int64 value
+               end
+       end
+
+       # Write `value` as a single byte with metadata
+       #
+       # Require: `value >= -0x20 and value <= 0x7F`
+       fun write_msgpack_fixint(value: Int)
+       do
+               assert value >= -0x20 and value <= 0x7F
+               write_byte value.to_b
+       end
+
+       # Write `value` over one unsigned byte, following 1 metadata byte
+       #
+       # Require: `value >= 0x00 and value <= 0xFF`
+       fun write_msgpack_uint8(value: Int)
+       do
+               write_byte 0xCCu8
+               write_bytes value.to_bytes(n_bytes=1)
+       end
+
+       # Write `value` over two unsigned bytes, following 1 metadata byte
+       #
+       # Require: `value >= 0x00 and value <= 0xFFFF`
+       fun write_msgpack_uint16(value: Int)
+       do
+               write_byte 0xCDu8
+               write_bytes value.to_bytes(n_bytes=2)
+       end
+
+       # Write `value` over 4 unsigned bytes, following 1 metadata byte
+       #
+       # Require: `value >= 0x00 and value <= 0xFFFF_FFFF`
+       fun write_msgpack_uint32(value: Int)
+       do
+               write_byte 0xCEu8
+               write_bytes value.to_bytes(n_bytes=4)
+       end
+
+       # Write `value` over 8 unsigned bytes, following 1 metadata byte
+       #
+       # Require: `value >= 0x00 and value <= 0xFFFF_FFFF_FFFF_FFFF`
+       fun write_msgpack_uint64(value: Int)
+       do
+               write_byte 0xCFu8
+               write_bytes value.to_bytes(n_bytes=8)
+       end
+
+       # Write `value` over one signed byte, following 1 metadata byte
+       #
+       # Require: `value >= -128  and value <= 127`
+       fun write_msgpack_int8(value: Int)
+       do
+               write_byte 0xD0u8
+               write_bytes value.to_bytes(n_bytes=1)
+       end
+
+       # Write `value` over two signed bytes, following 1 metadata byte
+       fun write_msgpack_int16(value: Int)
+       do
+               write_byte 0xD1u8
+               write_bytes value.to_bytes(n_bytes=2)
+       end
+
+       # Write `value` over 4 signed bytes, following 1 metadata byte
+       fun write_msgpack_int32(value: Int)
+       do
+               write_byte 0xD2u8
+               write_bytes value.to_bytes(n_bytes=4)
+       end
+
+       # Write `value` over 8 signed bytes, following 1 metadata byte
+       fun write_msgpack_int64(value: Int)
+       do
+               write_byte 0xD3u8
+               write_int64 value
+       end
+
+       # ---
+       # Floats
+
+       # Write `value` as a MessagePack float (losing precision)
+       fun write_msgpack_float(value: Float)
+       do
+               write_byte 0xCAu8
+               write_float value
+       end
+
+       # Write `value` as a MessagePack double
+       fun write_msgpack_double(value: Float)
+       do
+               write_byte 0xCBu8
+               write_double value
+       end
+
+       # ---
+       # Strings
+
+       # Write `text` in the shortest possible MessagePack format
+       #
+       # Require: `text.byte_length <= 0xFFFF_FFFF`
+       fun write_msgpack_str(text: Text)
+       do
+               var len = text.byte_length
+               if len <= 0x1F then
+                       write_msgpack_fixstr text
+               else if len <= 0xFF then
+                       write_msgpack_str8 text
+               else if len <= 0xFFFF then
+                       write_msgpack_str16 text
+               else if len <= 0xFFFF_FFFF then
+                       write_msgpack_str32 text
+               else
+                       abort
+               end
+       end
+
+       # Write `text` in _fixstr_ format, max of 0x1F bytes
+       #
+       # Require: `text.byte_length <= 0x1F`
+       fun write_msgpack_fixstr(text: Text)
+       do
+               var len = text.byte_length
+               assert len <= 0x1F
+
+               var b = 0b1010_0000u8 | len.to_b
+               write_byte b
+
+               write text
+       end
+
+       # Write `text` in _str8_ format, max of 0xFF bytes
+       #
+       # Require: `text.byte_length <= 0xFF`
+       fun write_msgpack_str8(text: Text)
+       do
+               var len = text.byte_length
+               assert len <= 0xFF
+
+               write_byte 0xD9u8
+               write_byte len.to_b
+               write text
+       end
+
+       # Write `text` in _str16_ format, max of 0xFFFF bytes
+       #
+       # Require: `text.byte_length <= 0xFFFF`
+       fun write_msgpack_str16(text: Text)
+       do
+               var len = text.byte_length
+               assert len <= 0xFFFF
+
+               write_byte 0xDAu8
+               var len_bytes = len.to_bytes
+               write_byte len_bytes[0]
+               write_byte if len_bytes.length > 1 then len_bytes[1] else 0u8
+               write text
+       end
+
+       # Write `text` in _str32_ format, max of 0xFFFF_FFFF bytes
+       #
+       # Require: `text.byte_length <= 0xFFFF_FFFF`
+       fun write_msgpack_str32(text: Text)
+       do
+               var len = text.byte_length
+               assert len <= 0xFFFF_FFFF
+
+               write_byte 0xDBu8
+               var len_bytes = len.to_bytes
+               write_byte len_bytes[0]
+               for i in [1..4[ do
+                       write_byte if len_bytes.length > i then len_bytes[i] else 0u8
+               end
+               write text
+       end
+
+       # ---
+       # Binary data
+
+       # Write `data` in the shortest possible MessagePack _bin_ format
+       #
+       # Require: `data.length <= 0xFFFF_FFFF`
+       fun write_msgpack_bin(data: Bytes)
+       do
+               var len = data.length
+               if len <= 0xFF then
+                       write_msgpack_bin8 data
+               else if len <= 0xFFFF then
+                       write_msgpack_bin16 data
+               else if len <= 0xFFFF_FFFF then
+                       write_msgpack_bin32 data
+               else abort
+       end
+
+       # Write `data` in _bin8_ format, max of 0xFF bytes
+       #
+       # Require: `data.length <= 0xFF`
+       fun write_msgpack_bin8(data: Bytes)
+       do
+               var len = data.length
+               assert len <= 0xFF
+
+               write_byte 0xC4u8
+               write_byte len.to_b
+               write_bytes data
+       end
+
+       # Write `data` in _bin16_ format, max of 0xFFFF bytes
+       #
+       # Require: `data.length <= 0xFFFF`
+       fun write_msgpack_bin16(data: Bytes)
+       do
+               var len = data.length
+               assert len <= 0xFFFF
+
+               write_byte 0xC5u8
+               write_bytes len.to_bytes(n_bytes=2)
+               write_bytes data
+       end
+
+       # Write `data` in _bin32_ format, max of 0xFFFF_FFFF bytes
+       #
+       # Require: `data.length <= 0xFFFF_FFFF`
+       fun write_msgpack_bin32(data: Bytes)
+       do
+               var len = data.length
+               assert len <= 0xFFFF_FFFF
+
+               write_byte 0xC6u8
+               write_bytes len.to_bytes(n_bytes=4)
+               write_bytes data
+       end
+
+       # ---
+       # Arrays
+
+       # Write an array header for `len` items in the shortest possible MessagePack _array_ format
+       #
+       # After writing the header, clients should write the array items.
+       #
+       # Require: `len <= 0xFFFF_FFFF`
+       fun write_msgpack_array(len: Int)
+       do
+               if len <= 0x0F then
+                       write_msgpack_fixarray len
+               else if len <= 0xFFFF then
+                       write_msgpack_array16 len
+               else if len <= 0xFFFF_FFFF then
+                       write_msgpack_array32 len
+               else
+                       abort
+               end
+       end
+
+       # Write an array header for `len` items, max of 0x0F items
+       #
+       # After writing the header, clients should write the array items.
+       #
+       # Require: `len <= 0x0F`
+       fun write_msgpack_fixarray(len: Int)
+       do
+               assert len <= 0x0F
+               write_byte 0b1001_0000u8 | len.to_b
+       end
+
+       # Write an array header for `len` items, max of 0xFFFF items
+       #
+       # After writing the header, clients should write the array items.
+       #
+       # Require: `len <= 0xFFFF`
+       fun write_msgpack_array16(len: Int)
+       do
+               assert len <= 0xFFFF
+               write_byte 0xDCu8
+               write_bytes len.to_bytes(n_bytes=2)
+       end
+
+       # Write an array header for `len` items, max of 0xFFFF_FFFF items
+       #
+       # After writing the header, clients should write the array items.
+       #
+       # Require: `len <= 0xFFFF_FFFF`
+       fun write_msgpack_array32(len: Int)
+       do
+               assert len <= 0xFFFF_FFFF
+               write_byte 0xDDu8
+               write_bytes len.to_bytes(n_bytes=4)
+       end
+
+       # ---
+       # Map
+
+       # Write a map header for `len` keys/value pairs in the shortest possible MessagePack _map_ format
+       #
+       # After writing the header, clients should write the map data, alternating
+       # between keys and values.
+       #
+       # Require: `len <= 0xFFFF_FFFF`
+       fun write_msgpack_map(len: Int)
+       do
+               if len <= 0x0F then
+                       write_msgpack_fixmap len
+               else if len <= 0xFFFF then
+                       write_msgpack_map16 len
+               else if len <= 0xFFFF_FFFF then
+                       write_msgpack_map32 len
+               else
+                       abort
+               end
+       end
+
+       # Write a map header for `len` key/value pairs, max of 0x0F pairs
+       #
+       # After writing the header, clients should write the map data, alternating
+       # between keys and values.
+       #
+       # Require: `len <= 0x0F`
+       fun write_msgpack_fixmap(len: Int)
+       do
+               assert len <= 0x0F
+               write_byte 0b1000_0000u8 | len.to_b
+       end
+
+       # Write a map header for `len` key/value pairs, max of 0xFFFF pairs
+       #
+       # After writing the header, clients should write the map data, alternating
+       # between keys and values.
+       #
+       # Require: `len <= 0xFFFF`
+       fun write_msgpack_map16(len: Int)
+       do
+               assert len <= 0xFFFF
+               write_byte 0xDEu8
+               write_bytes len.to_bytes(n_bytes=2)
+       end
+
+       # Write a map header for `len` key/value pairs, max of 0xFFFF_FFFF pairs
+       #
+       # After writing the header, clients should write the map data, alternating
+       # between keys and values.
+       #
+       # Require: `len <= 0xFFFF_FFFF`
+       fun write_msgpack_map32(len: Int)
+       do
+               assert len <= 0xFFFF_FFFF
+               write_byte 0xDFu8
+               write_bytes len.to_bytes(n_bytes=4)
+       end
+
+       # ---
+       # Ext
+
+       # Write an application-specific extension for `typ` and `bytes` in the shortest possible MessagePack _ext_ format
+       #
+       # Require: `bytes.length <= 0xFFFF_FFFF`
+       #
+       # ~~~
+       # var writer = new BytesWriter
+       # writer.write_msgpack_ext(0x0Au8, b"\x0B\x0C\x0D")
+       # assert writer.bytes == b"\xC7\x03\x0A\x0B\x0C\x0D"
+       # ~~~
+       fun write_msgpack_ext(typ: Byte, bytes: Bytes)
+       do
+               var len = bytes.length
+               if len == 1 then
+                       write_msgpack_fixext1 typ
+                       write_byte bytes.first
+               else if len == 2 then
+                       write_msgpack_fixext2 typ
+                       write_bytes bytes
+               else if len == 4 then
+                       write_msgpack_fixext4 typ
+                       write_bytes bytes
+               else if len == 8 then
+                       write_msgpack_fixext8 typ
+                       write_bytes bytes
+               else if len == 16 then
+                       write_msgpack_fixext16 typ
+                       write_bytes bytes
+               else if len <= 0xFF then
+                       write_msgpack_ext8(typ, len)
+                       write_bytes bytes
+               else if len <= 0xFFFF then
+                       write_msgpack_ext16(typ, len)
+                       write_bytes bytes
+               else if len <= 0xFFFF_FFFF then
+                       write_msgpack_ext32(typ, len)
+                       write_bytes bytes
+               else
+                       abort
+               end
+       end
+
+       # Write the header for an application-specific extension of one data byte
+       #
+       # After writing the header, clients should write the data byte.
+       fun write_msgpack_fixext1(typ: Byte)
+       do
+               write_byte 0xD4u8
+               write_byte typ
+       end
+
+       # Write the header for an application-specific extension of two data bytes
+       #
+       # After writing the header, clients should write the two data bytes.
+       fun write_msgpack_fixext2(typ: Byte)
+       do
+               write_byte 0xD5u8
+               write_byte typ
+       end
+
+       # Write the header for an application-specific extension of 4 data bytes
+       #
+       # After writing the header, clients should write the 4 data bytes.
+       fun write_msgpack_fixext4(typ: Byte)
+       do
+               write_byte 0xD6u8
+               write_byte typ
+       end
+
+       # Write the header for an application-specific extension of 8 data bytes
+       #
+       # After writing the header, clients should write the 8 data bytes.
+       fun write_msgpack_fixext8(typ: Byte)
+       do
+               write_byte 0xD7u8
+               write_byte typ
+       end
+
+       # Write the header for an application-specific extension of 16 data bytes
+       #
+       # After writing the header, clients should write the 16 data bytes.
+       fun write_msgpack_fixext16(typ: Byte)
+       do
+               write_byte 0xD8u8
+               write_byte typ
+       end
+
+       # Write the header for an application-specific extension of `len` data bytes
+       #
+       # After writing the header, clients should write the data bytes.
+       #
+       # Require: `len >= 0 and <= 0xFF`
+       fun write_msgpack_ext8(typ: Byte, len: Int)
+       do
+               assert len >= 0 and len <= 0xFF
+               write_byte 0xC7u8
+               write_byte len.to_b
+               write_byte typ
+       end
+
+       # Write the header for an application-specific extension of `len` data bytes
+       #
+       # After writing the header, clients should write the data bytes.
+       #
+       # Require: `len >= 0 and <= 0xFFFF`
+       fun write_msgpack_ext16(typ: Byte, len: Int)
+       do
+               assert len >= 0 and len <= 0xFFFF
+               write_byte 0xC8u8
+               write_bytes len.to_bytes(n_bytes=2)
+               write_byte typ
+       end
+
+       # Write the header for an application-specific extension of `len` data bytes
+       #
+       # After writing the header, clients should write the data bytes.
+       #
+       # Require: `len >= 0 and <= 0xFFFF_FFFF`
+       fun write_msgpack_ext32(typ: Byte, len: Int)
+       do
+               assert len >= 0 and len <= 0xFFFF_FFFF
+               write_byte 0xC9u8
+               write_bytes len.to_bytes(n_bytes=4)
+               write_byte typ
+       end
+
+       # TODO timestamps
+end
index 7c6fa1d..7ca4802 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_example_angular is test_suite
+module test_example_angular is test
 
 import pop_tests
 import example_angular
 
 class TestExampleAngular
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}/counter"
@@ -29,7 +30,7 @@ class TestExampleAngular
                system "curl -s {host}:{port}/not_found" # handled by angular controller
        end
 
-       fun test_example_angular do
+       fun test_example_angular is test do
                var app = new App
                app.use("/counter", new CounterAPI)
                app.use("/*", new StaticHandler(test_path / "../www/", "index.html"))
index a1cbb13..a0dc10f 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_example_post_handler is test_suite
+module test_example_post_handler is test
 
 import pop_tests
 import example_post_handler
 
 class TestExamplePostHandler
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}/ -X POST"
@@ -31,7 +32,7 @@ class TestExamplePostHandler
                system "curl -s {host}:{port}/"
        end
 
-       fun test_example_post_handler do
+       fun test_example_post_handler is test do
                var app = new App
                app.use("/", new PostHandler)
                run_test(app)
index db326ea..8909f91 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_example_query_string is test_suite
+module test_example_query_string is test
 
 import pop_tests
 import example_query_string
 
 class TestExampleQueryString
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}/"
@@ -30,7 +31,7 @@ class TestExampleQueryString
                system "curl -s {host}:{port}/?items=10\\&order=asc"
        end
 
-       fun test_example_query_string do
+       fun test_example_query_string is test do
                var app = new App
                app.use("/", new QueryStringHandler)
                run_test(app)
index 163e624..e206456 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_example_hello is test_suite
+module test_example_hello is test
 
 import pop_tests
 import example_hello
 
 class TestExampleHello
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}"
@@ -30,7 +31,7 @@ class TestExampleHello
                system "curl -s {host}:{port}/not_found/not_found"
        end
 
-       fun test_example_hello do
+       fun test_example_hello is test do
                var app = new App
                app.use("/", new HelloHandler)
                run_test(app)
index f870136..adfe426 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_example_advanced_logger is test_suite
+module test_example_advanced_logger is test
 
 import pop_tests
 import example_advanced_logger
 
 class TestExampleAdvancedLogger
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}/"
                system "curl -s {host}:{port}/about"
        end
 
-       fun test_example_advanced_logger do
+       fun test_example_advanced_logger is test do
                var app = new App
                app.use_before("/*", new RequestTimeHandler)
                app.use("/", new AnotherHandler)
index 93912ca..5322bbc 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_example_html_error_handler is test_suite
+module test_example_html_error_handler is test
 
 import pop_tests
 import example_html_error_handler
 
 class TestExampleHtmlErrorHandler
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}/"
                system "curl -s {host}:{port}/about"
        end
 
-       fun test_example_html_error_handler do
+       fun test_example_html_error_handler is test do
                var app = new App
                app.use("/*", new HtmlErrorHandler)
                run_test(app)
index e45acad..7016f1a 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_example_simple_error_handler is test_suite
+module test_example_simple_error_handler is test
 
 import pop_tests
 import example_simple_error_handler
 
 class TestExampleSimpleErrorHandler
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}/"
                system "curl -s {host}:{port}/about"
        end
 
-       fun test_example_simple_error_handler do
+       fun test_example_simple_error_handler is test do
                var app = new App
                app.use("/", new SomeHandler)
                app.use("/*", new SimpleErrorHandler)
index b0c41f8..25db274 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_example_simple_logger is test_suite
+module test_example_simple_logger is test
 
 import pop_tests
 import example_simple_logger
 
 class TestExampleSimpleLogger
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}/"
                system "curl -s {host}:{port}/about"
        end
 
-       fun test_example_simple_logger do
+       fun test_example_simple_logger is test do
                var app = new App
                app.use_before("/*", new SimpleLoggerHandler)
                app.use("/", new MyOtherHandler)
index c258260..e44be2a 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_example_glob_route is test_suite
+module test_example_glob_route is test
 
 import pop_tests
 import example_glob_route
 
 class TestExampleGlobRoute
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}/user/Morriar/item/10"
@@ -32,7 +33,7 @@ class TestExampleGlobRoute
                system "curl -s {host}:{port}/not_found/not_found"
        end
 
-       fun test_example_glob_route do
+       fun test_example_glob_route is test do
                var app = new App
                app.use("/user/:user/item/:item/*", new UserItem)
                run_test(app)
index 52f818a..813eda4 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_example_param_route is test_suite
+module test_example_param_route is test
 
 import pop_tests
 import example_param_route
 
 class TestExampleParamRoute
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}/Morriar"
@@ -30,7 +31,7 @@ class TestExampleParamRoute
                system "curl -s {host}:{port}/not_found/not_found"
        end
 
-       fun test_example_param_route do
+       fun test_example_param_route is test do
                var app = new App
                app.use("/:user", new UserHome)
                run_test(app)
index 55d5ed9..e0c3082 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_example_router is test_suite
+module test_example_router is test
 
 import pop_tests
 import example_router
 
 class TestExampleRouter
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}"
@@ -33,7 +34,7 @@ class TestExampleRouter
                system "curl -s {host}:{port}/products/not_found"
        end
 
-       fun test_example_router do
+       fun test_example_router is test do
                var user_router = new Router
                user_router.use("/*", new UserLogger)
                user_router.use("/", new UserHomepage)
index 6310171..91a6e8f 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_example_session is test_suite
+module test_example_session is test
 
 import pop_tests
 import example_session
 
 class TestExampleSession
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}/"
@@ -30,7 +31,7 @@ class TestExampleSession
                system "curl -s {host}:{port}/products/not_found"
        end
 
-       fun test_example_session do
+       fun test_example_session is test do
                var app = new App
                app.use("/*", new SessionInit)
                app.use("/", new AppLogin)
index a1c26a9..b65bcd6 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_example_static is test_suite
+module test_example_static is test
 
 import pop_tests
 import example_static
 
 class TestExampleStatic
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}/css/style.css"
@@ -32,7 +33,7 @@ class TestExampleStatic
                system "curl -s {host}:{port}/not_found.nit"
        end
 
-       fun test_example_static do
+       fun test_example_static is test do
                var app = new App
                app.use("/", new StaticHandler(test_path / "../public/"))
                run_test(app)
index 30ef93e..3f9ce22 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_example_static_default is test_suite
+module test_example_static_default is test
 
 import pop_tests
 import example_static_default
 
 class TestExampleStaticDefault
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}/css/style.css"
@@ -32,7 +33,7 @@ class TestExampleStaticDefault
                system "curl -s {host}:{port}/not_found.nit"
        end
 
-       fun test_example_static_default do
+       fun test_example_static_default is test do
                var app = new App
                app.use("/", new StaticHandler(test_path / "../public/", "default.html"))
                run_test(app)
index 42fc7df..3d4d2d9 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_example_static_multiple is test_suite
+module test_example_static_multiple is test
 
 import pop_tests
 import example_static_multiple
 
 class TestExampleStaticMultiple
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}/css/style.css"
@@ -36,7 +37,7 @@ class TestExampleStaticMultiple
                system "curl -s {host}:{port}/not_found.nit"
        end
 
-       fun test_example_static_multiple do
+       fun test_example_static_multiple is test do
                var app = new App
                app.use("/", new StaticHandler(test_path / "../public/"))
                app.use("/", new StaticHandler(test_path / "../files/"))
index 3334320..fb88bf1 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_example_templates is test_suite
+module test_example_templates is test
 
 import pop_tests
 intrude import example_templates
 
 class TestExampleTemplate
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}/"
        end
 
-       fun test_example_template do
+       fun test_example_template is test do
                var app = new App
                app.use("/", new MyTemplateHandler)
                run_test(app)
@@ -35,12 +36,13 @@ end
 
 class TestExampleTemplateString
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}/"
        end
 
-       fun test_example_template_string do
+       fun test_example_template_string is test do
                var app = new App
                app.use("/", new MyTemplateStringHandler)
                run_test(app)
@@ -49,12 +51,13 @@ end
 
 class TestExampleTemplateFile
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}/"
        end
 
-       fun test_example_template_file do
+       fun test_example_template_file is test do
                var app = new App
                var handler = new MyTemplateFileHandler
                handler.tpl_file = test_path / "../example_template.tpl"
@@ -65,12 +68,13 @@ end
 
 class TestExampleTemplatePug
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}/"
        end
 
-       fun test_example_template_pug do
+       fun test_example_template_pug is test do
                var app = new App
                app.use("/", new MyTemplatePugHandler)
                run_test(app)
@@ -79,12 +83,13 @@ end
 
 class TestExampleTemplatePugFile
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}/"
        end
 
-       fun test_example_template_pug_file do
+       fun test_example_template_pug_file is test do
                var app = new App
                var handler = new MyTemplatePugFileHandler
                handler.pug_file = test_path / "../example_template.pug"
index 6c5985d..5d89e59 100644 (file)
 # Here we use `curl` to access some URI on the app.
 #
 # ~~~nitish
-# module test_example_hello is test_suite
+# module test_example_hello is test
 #
 # import pop_tests
 # import example_hello
 #
 # class TestExampleHello
 #      super TestPopcorn
+#      test
 #
-#      fun test_example_hello do
+#      fun example_hello is test do
 #              var app = new App
 #              app.use("/", new HelloHandler)
 #              run_test(app)
@@ -68,7 +69,6 @@
 # See `examples/hello_world` for the complete example.
 module pop_tests
 
-import test_suite
 import popcorn
 import pthreads
 
@@ -123,7 +123,6 @@ end
 
 # TestSuite for Popcorn blackbox testing.
 class TestPopcorn
-       super TestSuite
 
        # Host used to run App.
        var host: String = test_host
index 9ad37a5..9c81cce 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_pop_routes is test_suite
+module test_pop_routes is test
 
 import pop_routes
-import test_suite
 
 class TestAppRoute
-       super TestSuite
+       test
 
-       fun test_root_match_only_one_uri do
+       fun test_root_match_only_one_uri is test do
                var r = new AppRoute("/")
                assert r.match("")
                assert r.match("/")
                assert not r.match("/user")
        end
 
-       fun test_strict_route_match_only_one_uri do
+       fun test_strict_route_match_only_one_uri is test do
                var r = new AppRoute("/user")
                assert not r.match("/")
                assert r.match("/user")
@@ -40,9 +39,9 @@ class TestAppRoute
 end
 
 class TestAppParamRoute
-       super TestSuite
+       test
 
-       fun test_param_route_match_good_uri_params_1 do
+       fun test_param_route_match_good_uri_params_1 is test do
                var r = new AppParamRoute("/:id")
                assert not r.match("/")
                assert r.match("/user")
@@ -50,7 +49,7 @@ class TestAppParamRoute
                assert not r.match("/user/10")
        end
 
-       fun test_param_route_match_good_uri_params_2 do
+       fun test_param_route_match_good_uri_params_2 is test do
                var r = new AppParamRoute("/user/:id")
                assert not r.match("/")
                assert not r.match("/user")
@@ -60,7 +59,7 @@ class TestAppParamRoute
                assert not r.match("/user/10/profile")
        end
 
-       fun test_param_route_match_good_uri_params_3 do
+       fun test_param_route_match_good_uri_params_3 is test do
                var r = new AppParamRoute("/user/:id/profile")
                assert not r.match("/")
                assert not r.match("/user")
@@ -73,7 +72,7 @@ class TestAppParamRoute
                assert not r.match("/user/10/foo")
        end
 
-       fun test_param_route_match_good_uri_params_4 do
+       fun test_param_route_match_good_uri_params_4 is test do
                var r = new AppParamRoute("/:id/:foo")
                assert not r.match("/")
                assert not r.match("/user")
@@ -83,7 +82,7 @@ class TestAppParamRoute
                assert not r.match("/user/10/10")
        end
 
-       fun test_param_route_match_good_uri_params_5 do
+       fun test_param_route_match_good_uri_params_5 is test do
                var r = new AppParamRoute("/user/:id/:foo")
                assert not r.match("/")
                assert not r.match("/user")
@@ -94,7 +93,7 @@ class TestAppParamRoute
                assert not r.match("/user/10/10/profile")
        end
 
-       fun test_param_route_match_good_uri_params_6 do
+       fun test_param_route_match_good_uri_params_6 is test do
                var r = new AppParamRoute("/user/:id/settings/:foo")
                assert not r.match("/")
                assert not r.match("/user")
@@ -110,30 +109,30 @@ class TestAppParamRoute
 end
 
 class TestRouteMatching
-       super TestSuite
+       test
 
-       fun test_glob_route_match_good_uri_prefix1 do
+       fun test_glob_route_match_good_uri_prefix1 is test do
                var r = new AppGlobRoute("/*")
                assert r.match("/")
                assert r.match("/user")
                assert r.match("/user/10")
        end
 
-       fun test_glob_route_match_good_uri_prefix2 do
+       fun test_glob_route_match_good_uri_prefix2 is test do
                var r = new AppGlobRoute("/user/*")
                assert not r.match("/")
                assert r.match("/user")
                assert r.match("/user/10")
        end
 
-       fun test_glob_route_match_good_uri_prefix3 do
+       fun test_glob_route_match_good_uri_prefix3 is test do
                var r = new AppGlobRoute("/user*")
                assert not r.match("/")
                assert r.match("/user")
                assert r.match("/user/10")
        end
 
-       fun test_glob_route_work_with_parameters_1 do
+       fun test_glob_route_work_with_parameters_1 is test do
                var r = new AppGlobRoute("/:id/*")
                assert not r.match("/")
                assert r.match("/user")
@@ -141,14 +140,14 @@ class TestRouteMatching
                assert r.match("/user/10/profile")
        end
 
-       fun test_glob_route_work_with_parameters_2 do
+       fun test_glob_route_work_with_parameters_2 is test do
                var r = new AppGlobRoute("/:id*")
                assert not r.match("/")
                assert r.match("/user")
                assert r.match("/user/10")
        end
 
-       fun test_glob_route_work_with_parameters_3 do
+       fun test_glob_route_work_with_parameters_3 is test do
                var r = new AppGlobRoute("/user/:id/*")
                assert not r.match("/")
                assert not r.match("/user")
@@ -156,7 +155,7 @@ class TestRouteMatching
                assert r.match("/user/10/profile")
        end
 
-       fun test_glob_route_work_with_parameters_4 do
+       fun test_glob_route_work_with_parameters_4 is test do
                var r = new AppGlobRoute("/user/:id*")
                assert not r.match("/")
                assert not r.match("/user")
index 108eb7a..d2daed4 100644 (file)
@@ -14,7 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_popcorn is test_suite
+module test_popcorn is test
 
 import pop_tests
 import popcorn
@@ -29,6 +29,7 @@ end
 
 class TestPopcornRouter
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}"
@@ -44,7 +45,7 @@ class TestPopcornRouter
                system "curl -s {host}:{port}/products/not_found"
        end
 
-       fun test_router do
+       fun test_router is test do
                var app = new App
                app.use("/", new TestHandler("/"))
                app.use("/about", new TestHandler("/about"))
@@ -65,6 +66,7 @@ end
 
 class TestPopcornRoutes
        super TestPopcorn
+       test
 
        redef fun client_test do
                system "curl -s {host}:{port}"
@@ -84,7 +86,7 @@ class TestPopcornRoutes
                system "curl -s {host}:{port}/user/id/not_found"
        end
 
-       fun test_routes do
+       fun test_routes is test do
                var app = new App
                app.use("/", new TestHandler("/"))
                app.use("/user", new TestHandler("/user"))
index 767b27d..3cd64db 100644 (file)
@@ -17,7 +17,7 @@
 # Pre order sets and partial order set (ie hierarchies)
 module poset
 
-import serialization
+import serialization::serialization_core
 
 # Pre-order set graph.
 # This class models an incremental pre-order graph where new nodes and edges can be added (but not removed).
index 27c98c4..12c7b57 100644 (file)
@@ -9,13 +9,12 @@
 # another product.
 
 # Test suite for `attributes_impl`.
-module test_attributes_impl is test_suite
+module test_attributes_impl is test
 
-import test_suite
 import sax::helpers::attributes_impl
 
 class TestAttributesImpl
-       super TestSuite
+       test
 
        private fun sample: AttributesImpl do
                var subject = new AttributesImpl
@@ -32,7 +31,7 @@ class TestAttributesImpl
                return subject
        end
 
-       fun test_length do
+       fun test_length is test do
                var subject = new AttributesImpl
 
                assert 0 == subject.length
@@ -47,7 +46,7 @@ class TestAttributesImpl
                assert 0 == subject.length
        end
 
-       fun test_uri do
+       fun test_uri is test do
                var subject = sample
 
                assert "http://example.com/" == subject.uri(0)
@@ -60,7 +59,7 @@ class TestAttributesImpl
                assert subject.uri(0) == null
        end
 
-       fun test_local_name do
+       fun test_local_name is test do
                var subject = sample
 
                assert "bar" == subject.local_name(0)
@@ -73,7 +72,7 @@ class TestAttributesImpl
                assert subject.local_name(0) == null
        end
 
-       fun test_qname do
+       fun test_qname is test do
                var subject = sample
 
                assert "foo:bar" == subject.qname(0)
@@ -86,7 +85,7 @@ class TestAttributesImpl
                assert subject.qname(0) == null
        end
 
-       fun test_type_of do
+       fun test_type_of is test do
                var subject = sample
 
                assert "CDATA" == subject.type_of(0)
@@ -99,7 +98,7 @@ class TestAttributesImpl
                assert subject.type_of(0) == null
        end
 
-       fun test_type_of_qname do
+       fun test_type_of_qname is test do
                var subject = sample
 
                assert "CDATA" == subject.type_of("foo:bar")
@@ -111,7 +110,7 @@ class TestAttributesImpl
                assert subject.type_of("xml:lang") == null
        end
 
-       fun test_value_of do
+       fun test_value_of is test do
                var subject = sample
 
                assert "baz" == subject.value_of(0)
@@ -124,7 +123,7 @@ class TestAttributesImpl
                assert subject.value_of(0) == null
        end
 
-       fun test_value_of_qname do
+       fun test_value_of_qname is test do
                var subject = sample
 
                assert "baz" == subject.value_of("foo:bar")
@@ -136,7 +135,7 @@ class TestAttributesImpl
                assert subject.value_of("xml:lang") == null
        end
 
-       fun test_index_ns do
+       fun test_index_ns is test do
                var subject = sample
 
                assert 0 == subject.index_ns("http://example.com/", "bar")
@@ -148,7 +147,7 @@ class TestAttributesImpl
                assert -1 == subject.index_ns("http://example.com/", "bar")
        end
 
-       fun test_index_of do
+       fun test_index_of is test do
                var subject = sample
 
                assert 0 == subject.index_of("foo:bar")
@@ -160,7 +159,7 @@ class TestAttributesImpl
                assert -1 == subject.index_of("foo:bar")
        end
 
-       fun test_type_ns do
+       fun test_type_ns is test do
                var subject = sample
 
                assert "CDATA" == subject.type_ns("http://example.com/", "bar")
@@ -172,7 +171,7 @@ class TestAttributesImpl
                assert subject.type_ns("http://example.com/", "bar") == null
        end
 
-       fun test_value_ns do
+       fun test_value_ns is test do
                var subject = sample
 
                assert "baz" == subject.value_ns("http://example.com/", "bar")
@@ -184,7 +183,7 @@ class TestAttributesImpl
                assert subject.value_ns("http://example.com/", "bar") == null
        end
 
-       fun test_attributes_set do
+       fun test_attributes_set is test do
                var subject = sample
                var subject2 = new AttributesImpl
 
@@ -195,7 +194,7 @@ class TestAttributesImpl
                assert subject.length == 4
        end
 
-       fun test_set do
+       fun test_set is test do
                var subject = sample
 
                subject.set(1, "urn:is:not:often:used", "i-am_ME", "i-am_ME", "ID",
@@ -205,7 +204,7 @@ class TestAttributesImpl
                assert "NMTOKENS" == subject.type_of(0)
        end
 
-       fun test_remove_at do
+       fun test_remove_at is test do
                var subject = sample
 
                subject.remove_at(1)
@@ -213,7 +212,7 @@ class TestAttributesImpl
                assert "xml:lang" == subject.qname(1)
        end
 
-       fun test_uri_set do
+       fun test_uri_set is test do
                var subject = sample
 
                subject.uri(0) = "https://example.org/serious"
@@ -222,7 +221,7 @@ class TestAttributesImpl
                assert "https://example.org/serious" == subject.uri(0)
        end
 
-       fun test_local_name_set do
+       fun test_local_name_set is test do
                var subject = sample
 
                subject.local_name(0) = "trololol"
@@ -231,7 +230,7 @@ class TestAttributesImpl
                assert "ImYou42" == subject.local_name(1)
        end
 
-       fun test_qname_set do
+       fun test_qname_set is test do
                var subject = sample
 
                subject.qname(0) = "go-to:bar"
@@ -240,7 +239,7 @@ class TestAttributesImpl
                assert "yo:i-am_ME" == subject.qname(1)
        end
 
-       fun test_type_of_set do
+       fun test_type_of_set is test do
                var subject = sample
 
                subject.type_of(0) = "NMTOKENS"
@@ -249,7 +248,7 @@ class TestAttributesImpl
                assert "ENTITY" == subject.type_of(1)
        end
 
-       fun test_value_of_set do
+       fun test_value_of_set is test do
                var subject = sample
 
                subject.value_of(0) = "buz"
index 3892e12..ed25c75 100644 (file)
@@ -9,13 +9,12 @@
 # another product.
 
 # Test suite for `namespace_support`.
-module test_namespace_support is test_suite
+module test_namespace_support is test
 
-import test_suite
 import sax::helpers::namespace_support
 
 class TestNamespaceSupport
-       super TestSuite
+       test
 
        private fun sample: NamespaceSupport do
                var subject = new NamespaceSupport
@@ -26,7 +25,7 @@ class TestNamespaceSupport
                return subject
        end
 
-       fun test_reset do
+       fun test_reset is test do
                var subject = sample
 
                subject.reset
@@ -37,7 +36,7 @@ class TestNamespaceSupport
                assert 2 == subject.prefixes.length
        end
 
-       fun test_push_context_override_default do
+       fun test_push_context_override_default is test do
                var subject = sample
 
                subject.push_context
@@ -51,7 +50,7 @@ class TestNamespaceSupport
                assert 3 == subject.prefixes.length
        end
 
-       fun test_push_context_override_dc do
+       fun test_push_context_override_dc is test do
                var subject = sample
 
                subject.push_context
@@ -64,7 +63,7 @@ class TestNamespaceSupport
                assert 3 == subject.prefixes.length
        end
 
-       fun test_push_context_undeclare do
+       fun test_push_context_undeclare is test do
                var subject = sample
 
                subject.push_context
@@ -74,7 +73,7 @@ class TestNamespaceSupport
                assert 2 == subject.prefixes.length
        end
 
-       fun test_pop_context do
+       fun test_pop_context is test do
                var subject = sample
 
                subject.pop_context
@@ -86,7 +85,7 @@ class TestNamespaceSupport
 
        #fun test_declare_prefix # SEE: test_push_context_*
 
-       fun test_process_name do
+       fun test_process_name is test do
                var subject = sample
                var parts = new Array[String]
 
@@ -104,7 +103,7 @@ class TestNamespaceSupport
                assert ["", "p", "p"] == subject.process_name("p", parts, true)
        end
 
-       fun test_process_name_xmlns do
+       fun test_process_name_xmlns is test do
                var subject = sample
                var parts = new Array[String].with_capacity(3)
 
@@ -112,7 +111,7 @@ class TestNamespaceSupport
                assert ["http://www.w3.org/1999/xhtml", "xmlns", "xmlns"] == subject.process_name("xmlns", parts, false)
        end
 
-       fun test_declare_prefix_illegal do
+       fun test_declare_prefix_illegal is test do
                var subject = sample
 
                assert not subject.declare_prefix("xml", "http://example.org")
@@ -122,7 +121,7 @@ class TestNamespaceSupport
                assert 2 == subject.declared_prefixes.length
        end
 
-       fun test_uri do
+       fun test_uri is test do
                var subject = sample
 
                assert "http://www.w3.org/1999/xhtml" == subject.uri("")
@@ -131,7 +130,7 @@ class TestNamespaceSupport
                assert subject.uri("foo") == null
        end
 
-       fun test_prefixes do
+       fun test_prefixes is test do
                var res = sample.prefixes
 
                assert 3 == res.length else
@@ -142,7 +141,7 @@ class TestNamespaceSupport
                assert res.has("xmlns")
        end
 
-       fun test_prefix do
+       fun test_prefix is test do
                var subject = sample
 
                assert subject.prefix("http://www.w3.org/1999/xhtml") == null
@@ -151,7 +150,7 @@ class TestNamespaceSupport
                assert subject.prefix("https://example.org/serious") == null
        end
 
-       fun test_prefixes_of do
+       fun test_prefixes_of is test do
                var subject = sample
                var res: Collection[String]
 
@@ -169,7 +168,7 @@ class TestNamespaceSupport
                assert res.has_all(["dc", "dc2"])
        end
 
-       fun test_declared_prefixes do
+       fun test_declared_prefixes is test do
                var res = sample.declared_prefixes
 
                assert 2 == res.length else
index c4afbbf..98ddc26 100644 (file)
@@ -9,7 +9,7 @@
 # another product.
 
 # Tests for SAXophoNit
-module test_saxophonit is test_suite
+module test_saxophonit is test
 
 import sax::helpers::sax_locator_impl
 import sax::helpers::attributes_impl
@@ -18,10 +18,11 @@ import saxophonit::testing
 
 class TestSaxophonit
        super SAXTestSuite
+       test
 
        redef fun create_reader do return new XophonReader
 
-       fun test_empty do
+       fun test_empty is test do
                before_test
                parse_string("<foo />")
                expected.document_locator = new SAXLocatorImpl
@@ -32,7 +33,7 @@ class TestSaxophonit
                assert_equals
        end
 
-       fun test_simple_element do
+       fun test_simple_element is test do
                before_test
                parse_string("<foo>bar</foo>")
                expected.document_locator = new SAXLocatorImpl
@@ -44,7 +45,7 @@ class TestSaxophonit
                assert_equals
        end
 
-       fun test_type_mismatch do
+       fun test_type_mismatch is test do
                before_test
                parse_string("<a></b>")
                expected.document_locator = new SAXLocatorImpl
@@ -57,7 +58,7 @@ class TestSaxophonit
                assert_equals
        end
 
-       fun test_attributes do
+       fun test_attributes is test do
                var atts = new AttributesImpl
 
                before_test
@@ -72,7 +73,7 @@ class TestSaxophonit
                assert_equals
        end
 
-       fun test_nested do
+       fun test_nested is test do
                var atts = new AttributesImpl
 
                before_test
@@ -90,7 +91,7 @@ class TestSaxophonit
                assert_equals
        end
 
-       fun test_xmldecl do
+       fun test_xmldecl is test do
                before_test
                parse_string("<?xml version='1.0'?><foo />")
                expected.document_locator = new SAXLocatorImpl
@@ -101,7 +102,7 @@ class TestSaxophonit
                assert_equals
        end
 
-       fun test_xmldecl_encoding do
+       fun test_xmldecl_encoding is test do
                before_test
                parse_string("<?xml version=\"1.0\" encoding='utf-8'?><foo />")
                expected.document_locator = new SAXLocatorImpl
@@ -112,7 +113,7 @@ class TestSaxophonit
                assert_equals
        end
 
-       fun test_xmldecl_standalone do
+       fun test_xmldecl_standalone is test do
                before_test
                parse_string("<?xml version='1.0' standalone=\"yes\"?><foo />")
                expected.document_locator = new SAXLocatorImpl
@@ -123,7 +124,7 @@ class TestSaxophonit
                assert_equals
        end
 
-       fun test_xmldecl_both do
+       fun test_xmldecl_both is test do
                before_test
                parse_string("<?xml version='1.0' encoding='utf-8' standalone=\"yes\"?><foo />")
                expected.document_locator = new SAXLocatorImpl
@@ -134,7 +135,7 @@ class TestSaxophonit
                assert_equals
        end
 
-       fun test_reference_builtin do
+       fun test_reference_builtin is test do
                before_test
                parse_string("<foo>&amp;&quot;&apos;&lt;&gt;&#48;&#x30;&#x03A;</foo>")
                expected.document_locator = new SAXLocatorImpl
@@ -153,7 +154,7 @@ class TestSaxophonit
                assert_equals
        end
 
-       fun test_comments do
+       fun test_comments is test do
                # TODO For the moment, comments are simply ignored.
                before_test
                parse_string("<!-- I--><foo>bar<!--l-i-k-e--></foo><!--comments    -->")
@@ -166,7 +167,7 @@ class TestSaxophonit
                assert_equals
        end
 
-       fun test_ns_simple do
+       fun test_ns_simple is test do
                before_test
                parse_string("<foo:bar xmlns:foo='https://s.exemple.org' />")
                expected.document_locator = new SAXLocatorImpl
@@ -178,7 +179,7 @@ class TestSaxophonit
                assert_equals
        end
 
-       fun test_ns_prefix do
+       fun test_ns_prefix is test do
                var atts = new AttributesImpl
 
                before_test
@@ -194,7 +195,7 @@ class TestSaxophonit
                assert_equals
        end
 
-       fun test_mixed do
+       fun test_mixed is test do
                var atts = new AttributesImpl
 
                # TODO For the moment, ignorable white space is not detected.
index c2ace75..c9c7fc1 100644 (file)
@@ -9,13 +9,12 @@
 # another product.
 
 # Test suite for `testing`.
-module test_testing is test_suite
+module test_testing is test
 
 import saxophonit::testing
-import test_suite
 
 class TestSaxEventLogger
-       super TestSuite
+       test
 
        # Constants for diff formatting.
 
@@ -40,8 +39,7 @@ class TestSaxEventLogger
 
        private var init_done: Bool = false
 
-       redef fun before_test do
-               super
+       fun before_test is before do
                if not init_done then
                        default = a.term_default
                        ins = a.term_insertion
@@ -64,19 +62,19 @@ class TestSaxEventLogger
                end
        end
 
-       fun test_diff_empty do
+       fun test_diff_empty is test do
                assert "" == a.diff(b).to_s
                assert "" == b.diff(a).to_s
        end
 
-       fun test_diff_equal1 do
+       fun test_diff_equal1 is test do
                b.start_document
                a.start_document
                assert "" == a.diff(b).to_s
                assert "" == b.diff(a).to_s
        end
 
-       fun test_diff_equal2 do
+       fun test_diff_equal2 is test do
                b.start_document
                b.end_document
                a.start_document
@@ -85,7 +83,7 @@ class TestSaxEventLogger
                assert "" == b.diff(a).to_s
        end
 
-       fun test_diff_insertion do
+       fun test_diff_insertion is test do
                var exp: String
                var test: String
 
@@ -115,7 +113,7 @@ class TestSaxEventLogger
                assert_equals(2, exp, test)
        end
 
-       fun test_diff_edition do
+       fun test_diff_edition is test do
                var exp: String
                var test: String
 
index cc69c83..a5af113 100644 (file)
@@ -17,7 +17,6 @@ import sax::helpers::xml_filter_impl
 import sax::ext::decl_handler
 import sax::ext::lexical_handler
 import console
-import test_suite
 
 # A filter that internally log events it recieves.
 #
@@ -532,7 +531,6 @@ end
 
 # Base class for test suites on a SAX reader.
 abstract class SAXTestSuite
-       super TestSuite
 
        # Logger of the expected event sequence.
        var expected = new SAXEventLogger
@@ -545,8 +543,7 @@ abstract class SAXTestSuite
 
        private var init_done: Bool = false
 
-       redef fun before_test do
-               super
+       fun before_test is before do
                if not init_done then
                        reader = create_reader
                        actual.parent = reader
index 3554d22..e1b9a16 100644 (file)
@@ -15,7 +15,7 @@
 # Services for caching serialization engines
 module caching
 
-import serialization
+import serialization_core
 private import engine_tools
 
 # A `Serializer` with a `cache`
index d43da6d..bd4b417 100644 (file)
@@ -15,7 +15,7 @@
 # Advanced services for serialization engines
 module engine_tools
 
-import serialization
+import serialization_core
 intrude import core::collection::hash_collection
 
 # Maps instances to a value, uses `is_same_serialized` and `serialization_hash`.
@@ -59,6 +59,11 @@ redef interface Object
        fun serialization_hash: Int do return object_id
 end
 
+redef class String
+       redef fun serialization_hash do return hash
+       redef fun is_same_serialized(o) do return self == o
+end
+
 redef class Text
 
        # Strip the `nullable` prefix from the type name `self`
diff --git a/lib/serialization/inspect.nit b/lib/serialization/inspect.nit
new file mode 100644 (file)
index 0000000..ec5d466
--- /dev/null
@@ -0,0 +1,294 @@
+# 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.
+
+# Refine `Serializable::inspect` to show more useful information
+module inspect
+
+import serialization_core
+private import caching
+
+private fun inspect_testing: Bool do return "NIT_TESTING".environ == "true"
+
+# Serialization engine writing the object attributes to strings
+private class InspectSerializer
+       super CachingSerializer
+
+       # Target writing stream
+       var stream: Writer
+
+       redef var current_object = null
+
+       var first_object: nullable Object = null
+
+       redef fun serialize(object)
+       do
+               if object == null then
+                       stream.write "null"
+               else
+                       if current_object == null then
+                               first_object = object
+                       end
+
+                       var last_object = current_object
+                       current_object = object
+                       object.accept_inspect_serializer self
+                       current_object = last_object
+               end
+       end
+
+       var first_attribute_serialized = false
+
+       redef fun serialize_attribute(name, value)
+       do
+               if first_attribute_serialized then
+                       stream.write ", "
+               else
+                       stream.write " "
+                       first_attribute_serialized = true
+               end
+
+               stream.write name
+               stream.write ":"
+
+               super
+       end
+
+       redef fun serialize_reference(object)
+       do
+               if cache.has_object(object) then
+                       # Cycle
+                       var id = object.object_id
+                       if inspect_testing then id = cache.id_for(object)
+
+                       stream.write "<"
+                       stream.write object.class_name
+                       stream.write "#"
+                       stream.write id.to_s
+                       stream.write ">"
+               else if object != first_object and (not object isa DirectSerializable) then
+                       # Another object, print class and id only
+                       var id = object.object_id
+                       if inspect_testing then id = cache.new_id_for(object)
+
+                       stream.write "<"
+                       stream.write object.class_name
+                       stream.write "#"
+                       stream.write id.to_s
+                       stream.write ">"
+               else
+                       # Main object
+                       serialize object
+               end
+       end
+end
+
+redef class Serializable
+
+       # Improve the default inspection reading serializable attributes
+       #
+       # Simple immutable data are inspected as they would be written in Nit code.
+       #
+       # ~~~
+       # assert 123.inspect == "123"
+       # assert 1.5.inspect == "1.5"
+       # assert 0xa1u8.inspect == "0xa1u8"
+       # assert 'c'.inspect == "'c'"
+       # assert "asdf\n".inspect == "\"asdf\\n\""
+       # ~~~
+       #
+       # Inspections of mutable serializable objects show their dynamic type,
+       # their `object_id` and their first level attributes. When testing,
+       # the `object_id` is replaced by an id unique to each call to `inspect`.
+       #
+       # ~~~
+       # class MyClass
+       #     serialize
+       #
+       #     var i: Int
+       #     var o: nullable Object
+       # end
+       #
+       # var class_with_null = new MyClass(123)
+       # assert class_with_null.to_s == class_with_null.inspect
+       # assert class_with_null.to_s == "<MyClass#0 i:123, o:null>"
+       #
+       # var class_with_other = new MyClass(456, class_with_null)
+       # assert class_with_other.to_s == "<MyClass#0 i:456, o:<MyClass#1>>"
+       #
+       # var class_with_cycle = new MyClass(789)
+       # class_with_cycle.o = class_with_cycle
+       # assert class_with_cycle.to_s == "<MyClass#0 i:789, o:<MyClass#0>>"
+       # ~~~
+       #
+       # Items of collections are flattened and appended to the output.
+       #
+       # ~~~
+       # assert [1, 2, 3].inspect == "<Array[Int]#0 [1, 2, 3]>"
+       #
+       # var set = new HashSet[Object].from([1, 1.5, "two": Object])
+       # assert set.inspect == """<HashSet[Object]#0 [1, 1.5, "two"]>"""
+       #
+       # var map = new Map[Int, String]
+       # map[1] = "one"
+       # map[2] = "two"
+       # assert map.inspect == """<HashMap[Int, String]#0 {1:"one", 2:"two"}>"""
+       # ~~~
+       #
+       # Inspections producing over 80 characters are cut short.
+       #
+       # ~~~
+       # var long_class = new MyClass(123456789, "Some " + "very "*8 + "long string")
+       # assert long_class.to_s == "<MyClass#0 i:123456789, o:\"Some very very very very very very very very long s…>"
+       # ~~~
+       redef fun inspect
+       do
+               var stream = new StringWriter
+               var serializer = new InspectSerializer(stream)
+               serializer.serialize self
+               stream.close
+               var str = stream.to_s
+
+               # Cut long inspects
+               var max_length = 80
+               if str.length > max_length then
+                       str = str.substring(0, max_length-2) + "…>"
+               end
+
+               return str
+       end
+
+       private fun accept_inspect_serializer(v: InspectSerializer)
+       do
+               v.stream.write "<"
+
+               v.stream.write class_name
+               v.stream.write "#"
+
+               var id = object_id
+               if inspect_testing then id = v.cache.new_id_for(self)
+               v.stream.write id.to_s
+
+               accept_inspect_serializer_core v
+
+               v.stream.write ">"
+       end
+
+       private fun accept_inspect_serializer_core(v: InspectSerializer)
+       do v.serialize_core(self)
+end
+
+redef class Int
+       redef fun accept_inspect_serializer(v) do v.stream.write to_s
+end
+
+redef class Float
+       redef fun accept_inspect_serializer(v) do v.stream.write to_s
+end
+
+redef class Bool
+       redef fun accept_inspect_serializer(v) do v.stream.write to_s
+end
+
+redef class Char
+       redef fun accept_inspect_serializer(v)
+       do
+               v.stream.write "'"
+               v.stream.write to_s.escape_to_nit
+               v.stream.write "'"
+       end
+end
+
+redef class Byte
+       redef fun accept_inspect_serializer(v)
+       do
+               v.stream.write to_s
+               v.stream.write "u8"
+       end
+end
+
+redef class CString
+       redef fun accept_inspect_serializer_core(v)
+       do
+               v.stream.write " \""
+               v.stream.write to_s.escape_to_nit
+               v.stream.write_char '"'
+       end
+end
+
+redef class Text
+
+       redef fun accept_inspect_serializer(v)
+       do
+               v.stream.write "\""
+               v.stream.write escape_to_nit
+               v.stream.write "\""
+       end
+end
+
+redef class Collection[E]
+       private fun serialize_as_inspect(v: InspectSerializer)
+       do
+               v.stream.write "["
+               var is_first = true
+               for e in self do
+                       if is_first then
+                               is_first = false
+                       else
+                               v.stream.write ", "
+                       end
+
+                       if not v.try_to_serialize(e) then
+                               assert e != null
+                               v.stream.write e.inspect
+                       end
+               end
+               v.stream.write "]"
+       end
+end
+
+redef class SimpleCollection[E]
+       redef fun accept_inspect_serializer_core(v)
+       do
+               v.stream.write " "
+               serialize_as_inspect v
+       end
+end
+
+redef class Map[K, V]
+       redef fun accept_inspect_serializer_core(v)
+       do
+               v.stream.write " \{"
+
+               var first = true
+               for key, val in self do
+                       if not first then
+                               v.stream.write ", "
+                       else first = false
+
+                       if not v.try_to_serialize(key) then
+                               assert key != null
+                               v.stream.write key.inspect
+                       end
+
+                       v.stream.write ":"
+
+                       if not v.try_to_serialize(val) then
+                               assert val != null
+                               v.stream.write val.inspect
+                       end
+               end
+
+               v.stream.write "\}"
+       end
+end
index 3feb4ee..cdbaed4 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Abstract services to serialize Nit objects to different formats
-#
-# This module declares the `serialize` annotation to mark Nit classes as serializable.
-# For an introduction to this service, refer to the documentation of the `serialization` group.
-# This documentation provides more technical information on interesting entitie of this module.
-#
-# Interesting entities for end users of serializable classes:
-#
-# * Serialize an instance subclass of `Serializable` with either
-#   `Serializer::serializable` and `Serializable::serialize`.
-# * Deserialize an object using `Deserializer::deserialize`.
-#   The object type must the be checked with an `assert` or otherwise.
-#
-# Interesting entities to create custom serializable classes:
-#
-# * Subclass `Serializable` to declare a class as serializable and to customize
-#   the serialization and deserialization behavior.
-# * Redefine `Serializable::core_serialize_to` to customize the serialization
-#   of the receiver class.
-# * Redefine `Deserializer::deserialize_class` to customize the deserialization
-#   of a specific class by name.
-#
-# Interesting entities for serialization format:
-#
-# * Subclass `Serializer` and `Deserializer` with custom serices.
-# * In `Serializer`, `serialize` and `serialize_reference` must be redefined.
-# * In `Deserializer`; `deserialize`, `deserialize_attribute and
-#   `notify_of_creation` must be redefined.
-module serialization is
-       new_annotation auto_serializable
-       new_annotation serialize
-       new_annotation noserialize
-       new_annotation serialize_as
-end
-
-intrude import core::queue
-import meta
-
-# Abstract serialization service to be sub-classed by specialized services.
-interface Serializer
-       # Entry point method of this service, serialize the `object`
-       #
-       # This method, and refinements, should handle `null` and probably
-       # use double dispatch to customize the bahavior per serializable objects.
-       fun serialize(object: nullable Serializable) is abstract
-
-       # The object currently serialized by `serialized`
-       #
-       # Can be used by a custom serializer to add domain-specific serialization behavior.
-       protected fun current_object: nullable Object is abstract
-
-       # Serialize an object, with full serialization or a simple reference
-       protected fun serialize_reference(object: Serializable) is abstract
-
-       # Serialize an attribute to compose a serializable object
-       #
-       # This method should be called from `Serializable::core_serialize_to`.
-       fun serialize_attribute(name: String, value: nullable Object)
-       do
-               if not try_to_serialize(value) then
-                       assert value != null # null would have been serialized
-                       warn("argument {name} of type {value.class_name} is not serializable.")
-               end
-       end
-
-       # Serialize `value` is possible, i.e. it is `Serializable` or `null`
-       fun try_to_serialize(value: nullable Object): Bool
-       do
-               if value isa Serializable then
-                       value.serialize_to_or_delay(self)
-               else if value == null then
-                       serialize value
-               else return false
-               return true
-       end
-
-       # The method is called when a standard `value` is serialized
-       #
-       # The default behavior is to call `value.core_serialize_to(self)` but it
-       # can be redefined by a custom serializer to add domain-specific serialization behavior.
-       fun serialize_core(value: Serializable)
-       do
-               value.core_serialize_to(self)
-       end
-
-       # Warn of problems and potential errors (such as if an attribute
-       # is not serializable)
-       fun warn(msg: String) do print "Serialization warning: {msg}"
-end
-
-# Abstract deserialization service
-#
-# The main service is `deserialize`.
-abstract class Deserializer
-       # Deserialize and return an object, storing errors in the attribute `errors`
-       #
-       # If a `static_type` is given, only subtypes of the `static_type` are accepted.
-       #
-       # This method behavior varies according to the implementation engines.
-       fun deserialize(static_type: nullable String): nullable Object is abstract
-
-       # Deserialize the attribute with `name` from the object open for deserialization
-       #
-       # The `static_type` restricts what kind of object can be deserialized.
-       #
-       # Return the deserialized value or null on error, and set
-       # `deserialize_attribute_missing` to whether the attribute was missing.
-       #
-       # Internal method to be implemented by the engines.
-       fun deserialize_attribute(name: String, static_type: nullable String): nullable Object is abstract
-
-       # Was the attribute queried by the last call to `deserialize_attribute` missing?
-       var deserialize_attribute_missing = false
-
-       # Register a newly allocated object (even if not completely built)
-       #
-       # Internal method called by objects in creation, to be implemented by the engines.
-       fun notify_of_creation(new_object: Object) is abstract
-
-       # Deserialize the next available object as an instance of `class_name`
-       #
-       # Return the deserialized object on success and
-       # record in `errors` if `class_name` is unknown.
-       #
-       # This method should be redefined for each custom subclass of `Serializable`.
-       # All refinement should look for a precise `class_name` and call super
-       # on unsupported classes.
-       protected fun deserialize_class(class_name: Text): nullable Object do
-               if class_name == "Error" then return new Error.from_deserializer(self)
-               return deserialize_class_intern(class_name)
-       end
-
-       # Generated service to deserialize the next available object as an instance of `class_name`
-       #
-       # Refinements to this method will be generated by the serialization phase.
-       # To avoid conflicts, there should not be any other refinements to this method.
-       # You can instead use `deserialize_class`.
-       protected fun deserialize_class_intern(class_name: Text): nullable Object do
-               errors.add new Error("Deserialization Error: Doesn't know how to deserialize class \"{class_name}\"")
-               return null
-       end
-
-       # Should `self` keep trying to deserialize an object after an error?
-       #
-       # This behavior takes effect after each attribute deserialization with
-       # errors such as a missing attribute or the value is of the wrong type.
-       # If `keep_going`, the attribute will be skipped but the engine will
-       # deserialize the next attribute.
-       # If `not keep_going`, the engine stops deserializing right away.
-       #
-       # When at `true`, this may cause the accumulation of a lot of entries in `errors`.
-       #
-       # Default at `true`.
-       var keep_going: nullable Bool = null is writable
-
-       # Errors encountered in the last call to `deserialize`
-       var errors = new Array[Error]
-end
-
-# Deserialization error related to an attribute of `receiver`
-abstract class AttributeError
-       super Error
-
-       # Parent object of the problematic attribute
-       var receiver: Object
-
-       # Name of the problematic attribute in `receiver`
-       var attribute_name: String
-end
-
-# Invalid dynamic type for a deserialized attribute
-class AttributeTypeError
-       super AttributeError
-
-       autoinit receiver, attribute_name, attribute, expected_type
-
-       # Deserialized object that isn't of the `expected_type`
-       var attribute: nullable Object
-
-       # Name of the type expected for `attribute`
-       var expected_type: String
-
-       redef var message is lazy do
-               var attribute = attribute
-               var found_type = if attribute != null then attribute.class_name else "null"
-
-               return "Deserialization Error: {
-               }Wrong type on `{receiver.class_name}::{attribute_name}` expected `{expected_type}`, got `{found_type}`"
-       end
-end
-
-# Missing attribute at deserialization
-class AttributeMissingError
-       super AttributeError
-
-       autoinit receiver, attribute_name
-
-       redef var message is lazy do
-               return "Deserialization Error: Missing attribute `{receiver.class_name}::{attribute_name}`"
-       end
-end
-
-# Instances of this class can be passed to `Serializer::serialize`
-interface Serializable
-       # Serialize `self` to `serializer`
-       #
-       # This is a shortcut to `Serializer::serialize`.
-       fun serialize_to(serializer: Serializer) do serializer.serialize(self)
-
-       # Actual serialization of `self` to `serializer`
-       #
-       # This writes the full data of `self` to `serializer`.
-       #
-       # This method can be redefined in sub classes and refinements.
-       # It should use `Serializer::serialize_attribute` to to register real or
-       # logical attributes.
-       #
-       # Any refinement should have its equivalent refinement of
-       # `Deserializer::deserialize_class` to support this custom deserialization.
-       fun core_serialize_to(serializer: Serializer) do end
-
-       # Accept references or force direct serialization (using `serialize_to`)
-       #
-       # The subclass change the default behavior, which will accept references,
-       # to force to always serialize copies of `self`.
-       private fun serialize_to_or_delay(v: Serializer) do v.serialize_reference(self)
-
-       # Create an instance of this class from the `deserializer`
-       #
-       # This constructor is refined by subclasses to correctly build their instances.
-       init from_deserializer(deserializer: Deserializer) is nosuper do end
-end
-
-# Instances of this class are not delayed and instead serialized immediately
-# This applies mainly to `universal` types
-interface DirectSerializable
-       super Serializable
-
-       redef fun serialize_to_or_delay(v) do serialize_to(v)
-end
-
-redef class Bool super DirectSerializable end
-redef class Char super DirectSerializable end
-redef class Byte super DirectSerializable end
-redef class Int super DirectSerializable end
-redef class Float super DirectSerializable end
-redef class CString super DirectSerializable end
-redef class Text super DirectSerializable end
-redef class SimpleCollection[E] super Serializable end
-redef class Map[K, V] super Serializable end
-
-redef class Couple[F, S]
-       super Serializable
-
-       redef init from_deserializer(v)
-       do
-               v.notify_of_creation self
-               var first = v.deserialize_attribute("first")
-               var second = v.deserialize_attribute("second")
-               init(first, second)
-       end
-
-       redef fun core_serialize_to(v)
-       do
-               v.serialize_attribute("first", first)
-               v.serialize_attribute("second", second)
-       end
-end
-
-redef class Ref[E]
-       super Serializable
-
-       redef init from_deserializer(v)
-       do
-               v.notify_of_creation self
-               var item = v.deserialize_attribute("item")
-               init item
-       end
-
-       redef fun core_serialize_to(v)
-       do
-               v.serialize_attribute("item", first)
-       end
-end
-
-redef class Error
-       super Serializable
-
-       redef init from_deserializer(v)
-       do
-               v.notify_of_creation self
-
-               var message = v.deserialize_attribute("message")
-               if not message isa String then message = ""
-               init message
-
-               var cause = v.deserialize_attribute("cause")
-               if cause isa nullable Error then self.cause = cause
-       end
-
-       redef fun core_serialize_to(v)
-       do
-               v.serialize_attribute("message", message)
-               v.serialize_attribute("cause", cause)
-       end
-end
-
-# ---
-# core::queue classes
-
-redef abstract class ProxyQueue[E]
-
-       redef init from_deserializer(v)
-       do
-               v.notify_of_creation self
-
-               var seq = v.deserialize_attribute("seq", (new GetName[Sequence[E]]).to_s)
-               if not seq isa Sequence[E] then seq = new Array[E]
-               if v.deserialize_attribute_missing then
-                       v.errors.add new AttributeMissingError(self, "seq")
-               end
-
-               init seq
-       end
-
-       redef fun core_serialize_to(v) do v.serialize_attribute("seq", seq)
-end
-
-redef class RandQueue[E]
-
-       redef init from_deserializer(v)
-       do
-               v.notify_of_creation self
-
-               var seq = v.deserialize_attribute("seq", (new GetName[SimpleCollection[E]]).to_s)
-               if not seq isa SimpleCollection[E] then seq = new Array[E]
-               if v.deserialize_attribute_missing then
-                       v.errors.add new AttributeMissingError(self, "seq")
-               end
-
-               init seq
-       end
-
-       redef fun core_serialize_to(v) do v.serialize_attribute("seq", seq)
-end
-
-redef class MinHeap[E]
-
-       redef init from_deserializer(v)
-       do
-               v.notify_of_creation self
-
-               var items = v.deserialize_attribute("items", (new GetName[SimpleCollection[E]]).to_s)
-               if not items isa Array[E] then items = new Array[E]
-               if v.deserialize_attribute_missing then
-                       v.errors.add new AttributeMissingError(self, "items")
-               end
-
-               var comparator = v.deserialize_attribute("comparator", "Comparator")
-               if not comparator isa Comparator then comparator = default_comparator
-               if v.deserialize_attribute_missing then
-                       v.errors.add new AttributeMissingError(self, "comparator")
-               end
-
-               init comparator
-               self.items.add_all items
-       end
+# General serialization services
+module serialization
 
-       redef fun core_serialize_to(v)
-       do
-               v.serialize_attribute("items", items)
-               v.serialize_attribute("comparator", comparator)
-       end
-end
+import serialization_core
+import inspect
diff --git a/lib/serialization/serialization_core.nit b/lib/serialization/serialization_core.nit
new file mode 100644 (file)
index 0000000..a19cfca
--- /dev/null
@@ -0,0 +1,390 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# 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.
+
+# Abstract services to serialize Nit objects to different formats
+#
+# This module declares the `serialize` annotation to mark Nit classes as serializable.
+# For an introduction to this service, refer to the documentation of the `serialization` group.
+# This documentation provides more technical information on interesting entitie of this module.
+#
+# Interesting entities for end users of serializable classes:
+#
+# * Serialize an instance subclass of `Serializable` with either
+#   `Serializer::serializable` and `Serializable::serialize`.
+# * Deserialize an object using `Deserializer::deserialize`.
+#   The object type must the be checked with an `assert` or otherwise.
+#
+# Interesting entities to create custom serializable classes:
+#
+# * Subclass `Serializable` to declare a class as serializable and to customize
+#   the serialization and deserialization behavior.
+# * Redefine `Serializable::core_serialize_to` to customize the serialization
+#   of the receiver class.
+# * Redefine `Deserializer::deserialize_class` to customize the deserialization
+#   of a specific class by name.
+#
+# Interesting entities for serialization format:
+#
+# * Subclass `Serializer` and `Deserializer` with custom serices.
+# * In `Serializer`, `serialize` and `serialize_reference` must be redefined.
+# * In `Deserializer`; `deserialize`, `deserialize_attribute and
+#   `notify_of_creation` must be redefined.
+module serialization_core is
+       new_annotation auto_serializable
+       new_annotation serialize
+       new_annotation noserialize
+       new_annotation serialize_as
+end
+
+intrude import core::queue
+import meta
+
+# Abstract serialization service to be sub-classed by specialized services.
+interface Serializer
+       # Entry point method of this service, serialize the `object`
+       #
+       # This method, and refinements, should handle `null` and probably
+       # use double dispatch to customize the bahavior per serializable objects.
+       fun serialize(object: nullable Serializable) is abstract
+
+       # The object currently serialized by `serialized`
+       #
+       # Can be used by a custom serializer to add domain-specific serialization behavior.
+       protected fun current_object: nullable Object is abstract
+
+       # Serialize an object, with full serialization or a simple reference
+       protected fun serialize_reference(object: Serializable) is abstract
+
+       # Serialize an attribute to compose a serializable object
+       #
+       # This method should be called from `Serializable::core_serialize_to`.
+       fun serialize_attribute(name: String, value: nullable Object)
+       do
+               if not try_to_serialize(value) then
+                       assert value != null # null would have been serialized
+                       warn("argument {name} of type {value.class_name} is not serializable.")
+               end
+       end
+
+       # Serialize `value` is possible, i.e. it is `Serializable` or `null`
+       fun try_to_serialize(value: nullable Object): Bool
+       do
+               if value isa Serializable then
+                       value.serialize_to_or_delay(self)
+               else if value == null then
+                       serialize value
+               else return false
+               return true
+       end
+
+       # The method is called when a standard `value` is serialized
+       #
+       # The default behavior is to call `value.core_serialize_to(self)` but it
+       # can be redefined by a custom serializer to add domain-specific serialization behavior.
+       fun serialize_core(value: Serializable)
+       do
+               value.core_serialize_to(self)
+       end
+
+       # Warn of problems and potential errors (such as if an attribute
+       # is not serializable)
+       fun warn(msg: String) do print "Serialization warning: {msg}"
+end
+
+# Abstract deserialization service
+#
+# The main service is `deserialize`.
+abstract class Deserializer
+       # Deserialize and return an object, storing errors in the attribute `errors`
+       #
+       # If a `static_type` is given, only subtypes of the `static_type` are accepted.
+       #
+       # This method behavior varies according to the implementation engines.
+       fun deserialize(static_type: nullable String): nullable Object is abstract
+
+       # Deserialize the attribute with `name` from the object open for deserialization
+       #
+       # The `static_type` restricts what kind of object can be deserialized.
+       #
+       # Return the deserialized value or null on error, and set
+       # `deserialize_attribute_missing` to whether the attribute was missing.
+       #
+       # Internal method to be implemented by the engines.
+       fun deserialize_attribute(name: String, static_type: nullable String): nullable Object is abstract
+
+       # Was the attribute queried by the last call to `deserialize_attribute` missing?
+       var deserialize_attribute_missing = false
+
+       # Register a newly allocated object (even if not completely built)
+       #
+       # Internal method called by objects in creation, to be implemented by the engines.
+       fun notify_of_creation(new_object: Object) is abstract
+
+       # Deserialize the next available object as an instance of `class_name`
+       #
+       # Return the deserialized object on success and
+       # record in `errors` if `class_name` is unknown.
+       #
+       # This method should be redefined for each custom subclass of `Serializable`.
+       # All refinement should look for a precise `class_name` and call super
+       # on unsupported classes.
+       protected fun deserialize_class(class_name: Text): nullable Object do
+               if class_name == "Error" then return new Error.from_deserializer(self)
+               return deserialize_class_intern(class_name)
+       end
+
+       # Generated service to deserialize the next available object as an instance of `class_name`
+       #
+       # Refinements to this method will be generated by the serialization phase.
+       # To avoid conflicts, there should not be any other refinements to this method.
+       # You can instead use `deserialize_class`.
+       protected fun deserialize_class_intern(class_name: Text): nullable Object do
+               errors.add new Error("Deserialization Error: Doesn't know how to deserialize class \"{class_name}\"")
+               return null
+       end
+
+       # Should `self` keep trying to deserialize an object after an error?
+       #
+       # This behavior takes effect after each attribute deserialization with
+       # errors such as a missing attribute or the value is of the wrong type.
+       # If `keep_going`, the attribute will be skipped but the engine will
+       # deserialize the next attribute.
+       # If `not keep_going`, the engine stops deserializing right away.
+       #
+       # When at `true`, this may cause the accumulation of a lot of entries in `errors`.
+       #
+       # Default at `true`.
+       var keep_going: nullable Bool = null is writable
+
+       # Errors encountered in the last call to `deserialize`
+       var errors = new Array[Error]
+end
+
+# Deserialization error related to an attribute of `receiver`
+abstract class AttributeError
+       super Error
+
+       # Parent object of the problematic attribute
+       var receiver: Object
+
+       # Name of the problematic attribute in `receiver`
+       var attribute_name: String
+end
+
+# Invalid dynamic type for a deserialized attribute
+class AttributeTypeError
+       super AttributeError
+
+       autoinit receiver, attribute_name, attribute, expected_type
+
+       # Deserialized object that isn't of the `expected_type`
+       var attribute: nullable Object
+
+       # Name of the type expected for `attribute`
+       var expected_type: String
+
+       redef var message is lazy do
+               var attribute = attribute
+               var found_type = if attribute != null then attribute.class_name else "null"
+
+               return "Deserialization Error: {
+               }Wrong type on `{receiver.class_name}::{attribute_name}` expected `{expected_type}`, got `{found_type}`"
+       end
+end
+
+# Missing attribute at deserialization
+class AttributeMissingError
+       super AttributeError
+
+       autoinit receiver, attribute_name
+
+       redef var message is lazy do
+               return "Deserialization Error: Missing attribute `{receiver.class_name}::{attribute_name}`"
+       end
+end
+
+# Instances of this class can be passed to `Serializer::serialize`
+interface Serializable
+       # Serialize `self` to `serializer`
+       #
+       # This is a shortcut to `Serializer::serialize`.
+       fun serialize_to(serializer: Serializer) do serializer.serialize(self)
+
+       # Actual serialization of `self` to `serializer`
+       #
+       # This writes the full data of `self` to `serializer`.
+       #
+       # This method can be redefined in sub classes and refinements.
+       # It should use `Serializer::serialize_attribute` to to register real or
+       # logical attributes.
+       #
+       # Any refinement should have its equivalent refinement of
+       # `Deserializer::deserialize_class` to support this custom deserialization.
+       fun core_serialize_to(serializer: Serializer) do end
+
+       # Accept references or force direct serialization (using `serialize_to`)
+       #
+       # The subclass change the default behavior, which will accept references,
+       # to force to always serialize copies of `self`.
+       private fun serialize_to_or_delay(v: Serializer) do v.serialize_reference(self)
+
+       # Create an instance of this class from the `deserializer`
+       #
+       # This constructor is refined by subclasses to correctly build their instances.
+       init from_deserializer(deserializer: Deserializer) is nosuper do end
+end
+
+# Instances of this class are not delayed and instead serialized immediately
+# This applies mainly to `universal` types
+interface DirectSerializable
+       super Serializable
+
+       redef fun serialize_to_or_delay(v) do serialize_to(v)
+end
+
+redef class Bool super DirectSerializable end
+redef class Char super DirectSerializable end
+redef class Byte super DirectSerializable end
+redef class Int super DirectSerializable end
+redef class Float super DirectSerializable end
+redef class CString super DirectSerializable end
+redef class Text super DirectSerializable end
+redef class SimpleCollection[E] super Serializable end
+redef class Map[K, V] super Serializable end
+
+redef class Couple[F, S]
+       super Serializable
+
+       redef init from_deserializer(v)
+       do
+               v.notify_of_creation self
+               var first = v.deserialize_attribute("first")
+               var second = v.deserialize_attribute("second")
+               init(first, second)
+       end
+
+       redef fun core_serialize_to(v)
+       do
+               v.serialize_attribute("first", first)
+               v.serialize_attribute("second", second)
+       end
+end
+
+redef class Ref[E]
+       super Serializable
+
+       redef init from_deserializer(v)
+       do
+               v.notify_of_creation self
+               var item = v.deserialize_attribute("item")
+               init item
+       end
+
+       redef fun core_serialize_to(v)
+       do
+               v.serialize_attribute("item", first)
+       end
+end
+
+redef class Error
+       super Serializable
+
+       redef init from_deserializer(v)
+       do
+               v.notify_of_creation self
+
+               var message = v.deserialize_attribute("message")
+               if not message isa String then message = ""
+               init message
+
+               var cause = v.deserialize_attribute("cause")
+               if cause isa nullable Error then self.cause = cause
+       end
+
+       redef fun core_serialize_to(v)
+       do
+               v.serialize_attribute("message", message)
+               v.serialize_attribute("cause", cause)
+       end
+end
+
+# ---
+# core::queue classes
+
+redef abstract class ProxyQueue[E]
+
+       redef init from_deserializer(v)
+       do
+               v.notify_of_creation self
+
+               var seq = v.deserialize_attribute("seq", (new GetName[Sequence[E]]).to_s)
+               if not seq isa Sequence[E] then seq = new Array[E]
+               if v.deserialize_attribute_missing then
+                       v.errors.add new AttributeMissingError(self, "seq")
+               end
+
+               init seq
+       end
+
+       redef fun core_serialize_to(v) do v.serialize_attribute("seq", seq)
+end
+
+redef class RandQueue[E]
+
+       redef init from_deserializer(v)
+       do
+               v.notify_of_creation self
+
+               var seq = v.deserialize_attribute("seq", (new GetName[SimpleCollection[E]]).to_s)
+               if not seq isa SimpleCollection[E] then seq = new Array[E]
+               if v.deserialize_attribute_missing then
+                       v.errors.add new AttributeMissingError(self, "seq")
+               end
+
+               init seq
+       end
+
+       redef fun core_serialize_to(v) do v.serialize_attribute("seq", seq)
+end
+
+redef class MinHeap[E]
+
+       redef init from_deserializer(v)
+       do
+               v.notify_of_creation self
+
+               var items = v.deserialize_attribute("items", (new GetName[SimpleCollection[E]]).to_s)
+               if not items isa Array[E] then items = new Array[E]
+               if v.deserialize_attribute_missing then
+                       v.errors.add new AttributeMissingError(self, "items")
+               end
+
+               var comparator = v.deserialize_attribute("comparator", "Comparator")
+               if not comparator isa Comparator then comparator = default_comparator
+               if v.deserialize_attribute_missing then
+                       v.errors.add new AttributeMissingError(self, "comparator")
+               end
+
+               init comparator
+               self.items.add_all items
+       end
+
+       redef fun core_serialize_to(v)
+       do
+               v.serialize_attribute("items", items)
+               v.serialize_attribute("comparator", comparator)
+       end
+end
index b2854e6..c6cd512 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_macro is test_suite
+module test_macro is test
 
-import test_suite
 import macro
 
 class TestTemplateString
-       super TestSuite
+       test
 
-       fun test_tpl_parse_1 do
+       fun test_tpl_parse_1 is test do
                var tpl = """
 <!DOCTYPE html>
 <html lang="en">
@@ -37,7 +36,7 @@ class TestTemplateString
                assert res == tpl
        end
 
-       fun test_tpl_parse_2 do
+       fun test_tpl_parse_2 is test do
                var tpl = """
 <!DOCTYPE html>
 <html lang="en">
@@ -54,7 +53,7 @@ class TestTemplateString
                assert res == tpl
        end
 
-       fun test_tpl_parse_3 do
+       fun test_tpl_parse_3 is test do
                var tpl = """
 <!DOCTYPE html>
 <html lang="en">
@@ -67,7 +66,7 @@ class TestTemplateString
                assert res == tpl
        end
 
-       fun test_tpl_parse_4 do
+       fun test_tpl_parse_4 is test do
                var tpl = """
 <!DOCTYPE html>
 <html lang="en">
@@ -83,7 +82,7 @@ class TestTemplateString
                assert res == tpl
        end
 
-       fun test_tpl_parse_5 do
+       fun test_tpl_parse_5 is test do
                var tpl = """
 <!DOCTYPE html>
 <html lang="en">
@@ -100,7 +99,7 @@ class TestTemplateString
                assert res == tpl
        end
 
-       fun test_tpl_parse_6 do
+       fun test_tpl_parse_6 is test do
                var tpl = """
 <!DOCTYPE html>
 <html lang="en">
@@ -116,7 +115,7 @@ class TestTemplateString
                assert res == tpl
        end
 
-       fun test_tpl_replace_1 do
+       fun test_tpl_replace_1 is test do
                var tpl = """
 <!DOCTYPE html>
 <html lang="en">
@@ -145,7 +144,7 @@ class TestTemplateString
                assert res == exp
        end
 
-       fun test_tpl_replace_2 do
+       fun test_tpl_replace_2 is test do
                var tpl = """
 <!DOCTYPE html>
 <html lang="en">
@@ -175,7 +174,7 @@ class TestTemplateString
                assert res == exp
        end
 
-       fun test_tpl_replace_3 do
+       fun test_tpl_replace_3 is test do
                var tpl = """
 <!DOCTYPE html>
 <html lang="en">
diff --git a/lib/test_suite.ini b/lib/test_suite.ini
deleted file mode 100644 (file)
index a32a29c..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-[package]
-name=test_suite
-tags=devel,lib
-maintainer=Alexandre Terrasa <alexandre@moz-code.org>
-license=Apache-2.0
-[upstream]
-browse=https://github.com/nitlang/nit/tree/master/lib/test_suite.nit
-git=https://github.com/nitlang/nit.git
-git.directory=lib/test_suite.nit
-homepage=http://nitlanguage.org
-issues=https://github.com/nitlang/nit/issues
diff --git a/lib/test_suite.nit b/lib/test_suite.nit
deleted file mode 100644 (file)
index e7b6f3a..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-# 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.
-
-# Common interface for `nitunit` test-suites.
-module test_suite is
-       # Annotation used by test-suite modules.
-       new_annotation test_suite
-end
-
-# A test-suite that can be executed by `nitunit`.
-#
-# All test-suite classes must implement `TestSuite`.
-class TestSuite
-       # Internal empty init.
-       private init nitunit do end
-
-       # Method called before each test-case.
-       #
-       # Redefine this method to factorize code that have to be
-       # executed before every test.
-       fun before_test do end
-
-       # Method called after each test-case.
-       #
-       # Redefine this method to factorize code that have to be
-       # executed after every test.
-       fun after_test do end
-end
-
-redef class Sys
-       # Internal empty init.
-       private init nitunit do end
-
-       # No before test for the module
-       private fun before_test do end
-
-       # No after test for the module
-       private fun after_test do end
-
-       # Method called before each test-suite.
-       #
-       # Redefine this method to factorize code that have to be
-       # executed before every test suite.
-       fun before_module do end
-
-       # Method called after each test-suite.
-       #
-       # Redefine this method to factorize code that have to be
-       # executed after every test suite.
-       fun after_module do end
-end
index 0ab13a5..2263bd3 100644 (file)
@@ -131,25 +131,24 @@ This flag can be used by libraries and program to prevent (or limit) the executi
 
 TestSuites are Nit modules that define a set of TestCases.
 
-A test suite is a module that uses the annotation `is test_suite`.
+A test suite is a module that uses the annotation `is test`.
 
 It is common that a test suite focuses on testing a single module.
-In this case, the name of the test_suite is often `test_foo.nit` where `foo.nit` is the tested module.
+In this case, the name of the test suite is often `test_foo.nit` where `foo.nit` is the tested module.
 
 The structure of a test suite is the following:
 
 ~~~~
 # test suite for module `foo`
-module test_foo is test_suite
+module test_foo is test
 
-import test_suite
 import foo # can be intrude to test private things
 
 class TestFoo
-       super TestSuite
+    test
 
     # test case for `foo::Foo::baz`
-    fun test_baz do
+    fun baz is test do
         var subject = new Foo
         assert subject.baz(1, 2) == 3
     end
@@ -160,18 +159,18 @@ Test suite can be executed using the same `nitunit` command:
 
     $ nitunit foo.nit
 
-`nitunit` will execute a test for each method named `test_*` in a class
-subclassing `TestSuite` so multiple tests can be executed for a single method:
+`nitunit` will execute a test for each method with the `test` annotation in a class
+also annotated with `test` so multiple tests can be executed for a single method:
 
 ~~~~
 class TestFoo
-       super TestSuite
+    test
 
-    fun test_baz_1 do
+    fun baz_1 is test do
         var subject = new Foo
         assert subject.baz(1, 2) == 3
     end
-    fun test_baz_2 do
+    fun baz_2 is test do
         var subject = new Foo
         assert subject.baz(1, -2) == -1
     end
@@ -204,12 +203,12 @@ The `diff(1)` command is used to perform the comparison.
 The test is failed if non-zero is returned by `diff`.
 
 ~~~
-module test_mod is test_suite
+module test_mod is test
 
 class TestFoo
-       super TestSuite
+       test
 
-       fun test_bar do
+       fun bar is test do
                print "Hello!"
        end
 end
@@ -228,25 +227,27 @@ To helps the management of the expected results, the option `--autosav` can be u
 
 ## Configuring TestSuites
 
-`TestSuite`s also provide methods to configure the test run:
-
-`before_test` and `after_test`: methods called before/after each test case.
+`TestSuite`s also provide annotations to configure the test run:
+`before` and `after` annotations can be applied to methods that must be called before/after each test case.
 They can be used to factorize repetitive tasks:
 
 ~~~~
 class TestFoo
-       super TestSuite
+    test
+
     var subject: Foo
+
     # Mandatory empty init
     init do end
+
     # Method executed before each test
-    fun before_test do
+    fun set_up is before do
         subject = new Foo
     end
-    fun test_baz_1 do
+    fun baz_1 is test do
         assert subject.baz(1, 2) == 3
     end
-    fun test_baz_2 do
+    fun baz_2 is test do
         assert subject.baz(1, -2) == -1
     end
 end
@@ -254,7 +255,7 @@ end
 
 When using custom test attributes, an empty `init` must be declared to allow automatic test running.
 
-`before_module` and `after_module`: methods called before/after each test suite.
+`before_all` and `after_all` annotations can be applied to methods that must be called before/after each test suite.
 They have to be declared at top level:
 
 ~~~~
@@ -279,11 +280,11 @@ end
 The `NIT_TESTING_PATH` environment variable contains the current test suite
 file path.
 Nitunit define this variable before the execution of each test suite.
-It can be used to access files based on the current test_suite location:
+It can be used to access files based on the current test suite location:
 
 ~~~
 class TestWithPath
-       super TestSuite
+    test
 
     fun test_suite_path do
         assert "NIT_TESTING_PATH".environ != ""
diff --git a/share/nitweb/directives/contributor-list.html b/share/nitweb/directives/contributor-list.html
deleted file mode 100644 (file)
index a46cce9..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<div ng-if='listContributors.length > 0'>
-       <h3 id={{listId}}>
-               <span>{{listTitle}}</span>
-       </h3>
-       <ul class='list-unstyled user-list'>
-               <li ng-repeat='contributor in listContributors'>
-                       <img class='avatar' src="https://secure.gravatar.com/avatar/{{contributor.hash}}?size=20&amp;default=retro">
-                       {{contributor.name}}
-               </li>
-       </ul>
-</div>
index 0b48c22..18675a0 100644 (file)
@@ -1,35 +1,35 @@
 <div class='card'>
-       <div class='card-left text-center' ng-if='!noSynopsis'>
+       <div class='card-left text-center'>
                <entity-tag mentity='mentity' />
        </div>
-       <div class='card-body'>
-               <h5 class='card-heading' ng-if='!noSynopsis'>
-                       <entity-signature mentity='mentity'/>
+       <div class='card-body' ng-if='mentity.class_name == "MPackage"' style='width: 75%'>
+               <h5 class='card-heading'>
+                       <entity-signature mentity='mentity' />
+                       <small ng-if='mentity.class_name == "MPackage"'>
+                               <span ng-repeat='tag in mentity.metadata.tags'>
+                                       <a ui-sref='tag({id: tag})' class='text-muted'>{{tag}}</a>
+                                       <span ng-if='!$last'>,</span>
+                               </span>
+                       </small>
                </h5>
-               <div class='tab-content'>
-                       <div id='{{mentity.html_id}}-signature' class='tab-pane' ng-if='!noSynopsis'
-                         ng-class='currentTab == "signature" ? "active" : ""'>
-                               <span class='synopsis' ng-bind-html='mentity.mdoc.html_synopsis' />
-                       </div>
-                       <div id='{{mentity.html_id}}-grade' class='tab-pane'
-                         ng-class='currentTab == "grade" ? "active" : ""'>
-                               <entity-rating mentity='mentity' ratings='ratings'>
-                       </div>
-               </div>
+               <span class='synopsis' ng-bind-html='mentity.mdoc.html_synopsis' />
        </div>
-       <div class='card-right'>
-               <div class='dropdown'>
-                       <button class='btn btn-link dropdown-toggle' type='button' data-toggle='dropdown'>
-                               <span class='glyphicon glyphicon-chevron-down'></span>
-                       </button>
-                       <ul class='dropdown-menu dropdown-menu-right'>
-                               <li ng-class='currentTab == "signature" ? "active" : ""' ng-if='!noSynopsis'>
-                                       <a ng-click='currentTab = "signature"'>Signature</a>
-                               </li>
-                               <li ng-class='currentTab == "grade" ? "active" : ""'>
-                                       <a ng-click='loadEntityStars(); currentTab = "grade"'>Grade</a>
-                               </li>
-                       </ul>
-               </div>
+       <div class='card-body' ng-if='mentity.class_name != "MPackage"'>
+               <h5 class='card-heading'>
+                       <entity-signature mentity='mentity' />
+               </h5>
+               <span class='synopsis' ng-bind-html='mentity.mdoc.html_synopsis' />
+       </div>
+       <div class='card-right' ng-if='mentity.class_name == "MPackage"' style='width: 25%'>
+               <span ng-repeat='maintainer in mentity.metadata.maintainers'>
+                       <img class='avatar' src='https://secure.gravatar.com/avatar/{{maintainer.gravatar}}?size=14&amp;default=retro' />
+                       <a ui-sref='person({id: maintainer.name})'>{{maintainer.name}}</a>
+               </span>
+               <br>
+               <span ng-if='mentity.metadata.license'>
+                       <span class='text-muted'>
+                               <a href='http://opensource.org/licenses/{{mentity.license}}' class='text-muted'>{{mentity.metadata.license}}</a>
+                       </span>
+               </span>
        </div>
 </div>
diff --git a/share/nitweb/directives/search/card.html b/share/nitweb/directives/search/card.html
deleted file mode 100644 (file)
index 7052c60..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<div class='card card-search'>
-       <div class='card-left text-center'>
-               <entity-tag mentity='mentity' />
-       </div>
-       <div class='card-body'>
-               <h5 class='card-heading'>
-                       <entity-signature mentity='mentity'/>
-                       <br>
-                       <small><entity-namespace namespace='mentity.namespace' /></small>
-               </h5>
-               <span class='synopsis' ng-bind-html='mentity.mdoc.html_synopsis' />
-       </div>
-</div>
diff --git a/share/nitweb/directives/search/field.html b/share/nitweb/directives/search/field.html
deleted file mode 100644 (file)
index d9ee5f1..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<form>
-       <div class='form-group has-icon'>
-               <input placeholder='Search...' type='text' class='form-control search-input'
-                       ng-model-options='{ debounce: 300 }' ng-model='query'
-                       ng-keydown='update($event)' ng-change='search()'>
-               <span class='glyphicon glyphicon-search form-control-icon text-muted'></span>
-       </div>
-       <div ng-if='results.length > 0' class='search-results'>
-               <div class='card-list'>
-                       <search-card ng-click='selectEnter()' ng-class='{active: activeItem == $index}' ng-mouseover='setActive($index)' mentity='mentity' ng-repeat='mentity in results' />
-               </div>
-       </div>
-</form>
diff --git a/share/nitweb/directives/ui/pagination.html b/share/nitweb/directives/ui/pagination.html
new file mode 100644 (file)
index 0000000..4af87d2
--- /dev/null
@@ -0,0 +1,14 @@
+<nav>
+       <ul class='pagination' ng-if='pagination.pagination.max > 1'>
+               <li ng-class='{disabled: pagination.pagination.page <= 1}'>
+                       <a ng-click='pagination.changePage(pagination.pagination.page - 1, pagination.pagination.limit)'><span>&laquo;</span></a>
+               </li>
+               <li ng-repeat='page in pagination.pages'
+                       ng-class='{disabled: pagination.pagination.page == page}'>
+                       <a ng-click='pagination.changePage(page, pagination.pagination.limit)'>{{page}}</a>
+               </li>
+               <li ng-class='{disabled: pagination.pagination.page >= pagination.pagination.max}'>
+                       <a ng-click='pagination.changePage(pagination.pagination.page + 1, pagination.pagination.limit)'><span>&raquo;</span></a>
+               </li>
+       </ul>
+</nav>
index e0bd4fd..a4d06a6 100644 (file)
@@ -2,7 +2,15 @@
        <div class='form-group has-icon'>
                <input placeholder='Search...' type='text' class='form-control search-input'
                        ng-model-options='{ debounce: 300 }' ng-model='vm.query'
-                       ng-keydown='update($event)' ng-change='vm.search()'>
+                       ng-keydown='vm.update($event)' ng-change='vm.search()'>
                <span class='glyphicon glyphicon-search form-control-icon text-muted'></span>
        </div>
+       <div ng-if='vm.results.results.length > 0' class='card-list search-results'>
+               <entity-card ng-click='vm.selectEnter()' ng-class='{active: vm.activeItem == $index}' ng-mouseover='vm.setActive($index)' mentity='mentity' ng-repeat='mentity in vm.results.results' />
+               <div class='card' ng-click='vm.selectEnter()' ng-mouseover='vm.setActive(vm.results.results.length)' ng-class='{active: vm.activeItem == vm.results.results.length}'>
+                       <div class='card-body'>
+                               Show all {{vm.results.total}} results for <a>"{{vm.query}}"</a>
+                       </div>
+               </div>
+       </div>
 </form>
index 2812b99..92eb64e 100644 (file)
@@ -47,7 +47,7 @@
                                        </div>
                                </div>
                                <div class='col-xs-7'>
-                                       <search-field />
+                                       <ui-search-field />
                                </div>
                                <div class='col-xs-2'>
                                        <user-menu />
@@ -73,7 +73,7 @@
                <script src='/javascripts/nitweb.js'></script>
                <script src='/javascripts/entities.js'></script>
                <script src='/javascripts/ui.js'></script>
-               <script src='/javascripts/index.js'></script>
+               <script src='/javascripts/catalog.js'></script>
                <script src='/javascripts/docdown.js'></script>
                <script src='/javascripts/metrics.js'></script>
                <script src='/javascripts/users.js'></script>
diff --git a/share/nitweb/javascripts/catalog.js b/share/nitweb/javascripts/catalog.js
new file mode 100644 (file)
index 0000000..2667db3
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * 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.
+ */
+
+(function() {
+       angular.module('catalog', [])
+
+       /* Router */
+
+       .config(function($stateProvider, $locationProvider) {
+               $stateProvider
+                       .state('catalog', {
+                               url: '/?p&n',
+                               controller: 'CatalogCtrl',
+                               controllerAs: 'vm',
+                               templateUrl: 'views/catalog/index.html',
+                               resolve: {
+                                       packages: function(Catalog, $q, $stateParams, $state) {
+                                               var d = $q.defer();
+                                               var page = $stateParams.p ? $stateParams.p : 1;
+                                               var limit = $stateParams.n ? $stateParams.n : 10;
+                                               Catalog.packages(page, limit, d.resolve,
+                                                       function(err) {
+                                                               $state.go('404', null, { location: false })
+                                                       });
+                                               return d.promise;
+                                       },
+                                       tags: function(Catalog, $q, $state) {
+                                               var d = $q.defer();
+                                               Catalog.tags(d.resolve,
+                                                       function(err) {
+                                                               $state.go('404', null, { location: false })
+                                                       });
+                                               return d.promise;
+                                       },
+                                       stats: function(Catalog, $q, $state) {
+                                               var d = $q.defer();
+                                               Catalog.stats(d.resolve,
+                                                       function(err) {
+                                                               $state.go('404', null, { location: false })
+                                                       });
+                                               return d.promise;
+
+                                       }
+                               }
+                       })
+                       .state('person', {
+                               url: '/person/:id?p1&n1&p2&n2',
+                               controller: 'PersonCtrl',
+                               controllerAs: 'vm',
+                               templateUrl: 'views/catalog/person.html',
+                               resolve: {
+                                       person: function(Catalog, $q, $stateParams, $state) {
+                                               var d = $q.defer();
+                                               Catalog.person($stateParams.id, d.resolve,
+                                               function(err) {
+                                                       $state.go('404', null, { location: false })
+                                               });
+                                               return d.promise;
+                                       },
+                                       maintaining: function(Catalog, $q, $stateParams, $state) {
+                                               var d = $q.defer();
+                                               var p1 = $stateParams.p2 ? $stateParams.p1 : 1;
+                                               var n1 = $stateParams.n2 ? $stateParams.n1 : 10;
+                                               Catalog.personMaintaining($stateParams.id, p1, n1, d.resolve,
+                                                       function(err) {
+                                                               $state.go('404', null, { location: false })
+                                                       });
+                                               return d.promise;
+                                       },
+                                       contributing: function(Catalog, $q, $stateParams, $state) {
+                                               var d = $q.defer();
+                                               var p2 = $stateParams.p2 ? $stateParams.p2 : 1;
+                                               var n2 = $stateParams.n2 ? $stateParams.n2 : 10;
+                                               Catalog.personContributing($stateParams.id, p2, n2, d.resolve,
+                                                       function(err) {
+                                                               $state.go('404', null, { location: false })
+                                                       });
+                                               return d.promise;
+
+                                       }
+                               }
+                       })
+                       .state('tag', {
+                               url: '/tag/:id?p&n',
+                               controller: 'TagCtrl',
+                               controllerAs: 'vm',
+                               templateUrl: 'views/catalog/tag.html',
+                               resolve: {
+                                       tag: function(Catalog, $q, $stateParams, $state) {
+                                               var d = $q.defer();
+                                               var page = $stateParams.p ? $stateParams.p : 1;
+                                               var limit = $stateParams.l ? $stateParams.l : 10;
+                                               Catalog.tag($stateParams.id, page, limit, d.resolve,
+                                                       function() {
+                                                               $state.go('404', null, { location: false })
+                                                       });
+                                               return d.promise;
+                                       }
+                               }
+                       })
+       })
+
+       /* Factories */
+
+       .factory('Catalog', [ '$http', function($http) {
+               return {
+                       stats: function(cb, cbErr) {
+                               $http.get('/api/catalog/stats')
+                                       .success(cb)
+                                       .error(cbErr);
+                       },
+                       packages: function(p, n, cb, cbErr) {
+                               $http.get('/api/catalog/packages?p=' + p + '&n=' + n)
+                                       .success(cb)
+                                       .error(cbErr);
+                       },
+                       tags: function(cb, cbErr) {
+                               $http.get('/api/catalog/tags')
+                                       .success(cb)
+                                       .error(cbErr);
+                       },
+                       person: function(id, cb, cbErr) {
+                               $http.get('/api/catalog/person/' + id)
+                                       .success(cb)
+                                       .error(cbErr);
+                       },
+                       personMaintaining: function(id, p, n, cb, cbErr) {
+                               $http.get('/api/catalog/person/' + id + '/maintaining?p=' + p + '&n=' + n)
+                                       .success(cb)
+                                       .error(cbErr);
+                       },
+                       personContributing: function(id, p, n, cb, cbErr) {
+                               $http.get('/api/catalog/person/' + id + '/contributing?p=' + p + '&n=' + n)
+                                       .success(cb)
+                                       .error(cbErr);
+                       },
+                       tag: function(id, p, n, cb, cbErr) {
+                               $http.get('/api/catalog/tag/' + id + '?p=' + p + '&n=' + n)
+                                       .success(cb)
+                                       .error(cbErr);
+                       }
+               };
+       }])
+
+       /* Controllers */
+
+       .controller('CatalogCtrl', function($scope, $state, packages, tags, stats) {
+               var vm = this;
+               vm.packages = packages;
+               vm.tags = tags;
+               vm.stats = stats;
+
+               $scope.$on('change-page', function(e, page, limit) {
+                       $state.go('catalog', {p: page, l: limit});
+               })
+       })
+
+       .controller('PersonCtrl', function($scope, $state, $stateParams, person, maintaining, contributing) {
+               var vm = this;
+               vm.person = person;
+               vm.maintaining = maintaining;
+               vm.contributing = contributing;
+
+               var p1 = $stateParams.p1 ? $stateParams.p1 : 1;
+               var n1 = $stateParams.n1 ? $stateParams.n1 : 10;
+               var p2 = $stateParams.p2 ? $stateParams.p2 : 1;
+               var n2 = $stateParams.n2 ? $stateParams.n2 : 10;
+
+               $scope.$on('change-page1', function(e, page, limit) {
+                       $state.go('person', {id: $stateParams.id, p1: page, n1: limit, p2: p2, n2: n2});
+               })
+
+               $scope.$on('change-page2', function(e, page, limit) {
+                       $state.go('person', {id: $stateParams.id, p1: p1, n1: n1, p2: page, n2: limit});
+               })
+       })
+
+       .controller('TagCtrl', function($state, $scope, tag) {
+               var vm = this;
+               vm.tag = tag;
+
+               $scope.$on('change-page', function(e, page, limit) {
+                       $state.go('tag', {id: vm.tag.tag, p: page, l: limit});
+               })
+       })
+})();
index c5deb0a..545e487 100644 (file)
                                        controller: function(mentity, doc) {
                                                this.mentity = mentity;
                                                this.doc = doc;
+
+                                               this.date = function(date) {
+                                                       return new Date(date);
+                                               }
                                        },
                                        controllerAs: 'vm',
                                })
                                                .success(cb)
                                                .error(cbErr);
                                },
-
-                               search: function(q, n, cb, cbErr) {
-                                       $http.get('/api/search?q=' + q + '&n=' + n)
+                               search: function(q, p, n, cb, cbErr) {
+                                       $http.get('/api/search?q=' + q + '&p=' + p + '&n=' + n)
                                                .success(cb)
                                                .error(cbErr);
                                }
                        return {
                                restrict: 'E',
                                scope: {
-                                       mentity: '=',
-                                       defaultTab: '@',
-                                       noSynopsis: '='
+                                       mentity: '='
                                },
                                replace: true,
-                               templateUrl: '/directives/entity/card.html',
-                               link: function ($scope, element, attrs) {
-                                       $scope.currentTab = $scope.defaultTab ? $scope.defaultTab : 'signature';
-
-                                       $scope.loadEntityStars = function() {
-                                               Feedback.loadEntityStars($scope.mentity.full_name,
-                                                       function(data) {
-                                                               $scope.ratings = data;
-                                                       }, function(message, status) {
-                                                               $scope.error = {message: message, status: status};
-                                                       });
-                                       };
-                               }
+                               templateUrl: '/directives/entity/card.html'
                        };
                }])
 
index 72eadbc..96ede05 100644 (file)
 
                .config(function($stateProvider, $locationProvider) {
                        $stateProvider
+                               .state('doc.entity.grades', {
+                                       url: '/grades',
+                                       templateUrl: 'views/doc/grades.html',
+                                       resolve: {
+                                               metrics: function(Feedback, $q, $stateParams, $state) {
+                                                       var d = $q.defer();
+                                                       Feedback.loadEntityStars($stateParams.id, d.resolve,
+                                                               function() {
+                                                                       $state.go('404', null, { location: false })
+                                                               });
+                                                       return d.promise;
+                                               }
+                                       },
+                                       controller: function(mentity, metrics) {
+                                               this.mentity = mentity;
+                                               this.metrics = metrics;
+                                       },
+                                       controllerAs: 'vm',
+                               })
                                .state('grades', {
                                        url: '/grades',
                                        templateUrl: 'views/grades.html',
diff --git a/share/nitweb/javascripts/index.js b/share/nitweb/javascripts/index.js
deleted file mode 100644 (file)
index 104d627..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * 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.
- */
-
-(function() {
-       angular
-               .module('index', [])
-
-               .config(function($stateProvider, $locationProvider) {
-                       $stateProvider
-                               .state('catalog', {
-                                       url: '/',
-                                       templateUrl: 'views/catalog/index.html',
-                                       controller: 'CatalogCtrl',
-                                       controllerAs: 'vm',
-                                       abstract: true
-                               })
-                               .state('catalog.highlighted', {
-                                       url: '',
-                                       templateUrl: 'views/catalog/highlighted.html',
-                                       controller: 'CatalogHighlightedCtrl',
-                                       controllerAs: 'vm'
-                               })
-                               .state('catalog.required', {
-                                       url: 'required',
-                                       templateUrl: 'views/catalog/most_required.html',
-                                       controller: 'CatalogRequiredCtrl',
-                                       controllerAs: 'vm'
-                               })
-                               .state('catalog.tags', {
-                                       url: 'tags',
-                                       templateUrl: 'views/catalog/by_tags.html',
-                                       controller: 'CatalogTagsCtrl',
-                                       controllerAs: 'vm'
-                               })
-               })
-
-               .factory('Catalog', [ '$http', function($http) {
-                       return {
-                               loadHightlighted: function(cb, cbErr) {
-                                       $http.get('/api/catalog/highlighted')
-                                               .success(cb)
-                                               .error(cbErr);
-                               },
-
-                               loadMostRequired: function(cb, cbErr) {
-                                       $http.get('/api/catalog/required')
-                                               .success(cb)
-                                               .error(cbErr);
-                               },
-
-                               loadByTags: function(cb, cbErr) {
-                                       $http.get('/api/catalog/bytags')
-                                               .success(cb)
-                                               .error(cbErr);
-                               },
-
-                               loadStats: function(cb, cbErr) {
-                                       $http.get('/api/catalog/stats')
-                                               .success(cb)
-                                               .error(cbErr);
-                               },
-
-                               loadContributors: function(cb, cbErr) {
-                                       $http.get('/api/catalog/contributors')
-                                               .success(cb)
-                                               .error(cbErr);
-                               },
-                       }
-               }])
-
-               .controller('CatalogCtrl', function(Catalog) {
-                       var vm = this;
-
-                       Catalog.loadContributors(
-                               function(data) {
-                                       vm.contributors = data;
-                               }, function(err) {
-                                       vm.error = err;
-                               });
-
-                       Catalog.loadStats(
-                               function(data) {
-                                       vm.stats = data;
-                               }, function(err) {
-                                       vm.error = err;
-                               });
-               })
-
-               .controller('CatalogHighlightedCtrl', function(Catalog) {
-                       var vm = this;
-
-                       Catalog.loadHightlighted(
-                               function(data) {
-                                       vm.highlighted = data;
-                               }, function(err) {
-                                       vm.error = err;
-                               });
-               })
-
-               .controller('CatalogRequiredCtrl', function(Catalog) {
-                       var vm = this;
-
-                       Catalog.loadMostRequired(
-                               function(data) {
-                                       vm.required = data;
-                               }, function(err) {
-                                       vm.error = err;
-                               });
-               })
-
-               .controller('CatalogTagsCtrl', function(Catalog, $anchorScroll, $location) {
-                       var vm = this;
-
-                       Catalog.loadByTags(
-                               function(data) {
-                                       vm.bytags = data;
-                               }, function(err) {
-                                       vm.error = err;
-                               });
-
-
-                       vm.scrollTo = function(hash) {
-                               $location.hash(hash);
-                               $anchorScroll();
-                       }
-               })
-
-               .directive('contributorList', function(Model) {
-                       return {
-                               restrict: 'E',
-                               scope: {
-                                       listId: '@',
-                                       listTitle: '@',
-                                       listContributors: '='
-                               },
-                               templateUrl: '/directives/contributor-list.html'
-                       };
-               })
-})();
index 181da8e..1de3f69 100644 (file)
  */
 
 (function() {
-       angular.module('nitweb', ['ui.router', 'ngSanitize', 'angular-loading-bar', 'index', 'entities', 'docdown', 'metrics', 'users', 'grades'])
+       angular.module('nitweb', ['ui.router', 'ngSanitize', 'angular-loading-bar', 'catalog', 'entities', 'docdown', 'metrics', 'users', 'grades'])
 
        .config(['cfpLoadingBarProvider', function(cfpLoadingBarProvider) {
                cfpLoadingBarProvider.includeSpinner = false;
        }])
 
-       .run(['$anchorScroll', function($anchorScroll) {
+       .run(function($rootScope, $anchorScroll) {
                $anchorScroll.yOffset = 80;
-       }])
+               $rootScope.$on('$stateChangeSuccess', function() {
+                 $anchorScroll();
+               });
+       })
 
        .config(function($stateProvider, $locationProvider) {
                $stateProvider
index 7fc3f8a..5327782 100644 (file)
        angular
                .module('ui', [])
 
-               .controller('SearchCtrl', function(Model, $scope, $location, $document) {
-
-                       $scope.query = '';
-
-                       $scope.reset = function() {
-                               $scope.activeItem = 0;
-                               $scope.results = [];
-                       }
-
-                       $scope.update = function(e) {
-                               if(e.keyCode == 38) {
-                                       $scope.selectUp();
-                               } else if(e.keyCode == 40) {
-                                       $scope.selectDown();
-                               } else if(e.keyCode == 27) {
-                                       $scope.selectEscape();
-                               } else if(e.keyCode == 13) {
-                                       $scope.selectEnter();
-                               }
-                       }
-
-                       $scope.selectUp = function() {
-                               if($scope.activeItem > 0) {
-                                       $scope.activeItem -= 1;
-                               }
-                       }
-
-                       $scope.selectDown = function() {
-                               if($scope.activeItem < $scope.results.length - 1) {
-                                       $scope.activeItem += 1;
-                               }
-                       }
-
-                       $scope.selectEnter = function(e) {
-                               $location.url($scope.results[$scope.activeItem].web_url);
-                               $scope.reset();
-                       }
+               /* Search */
 
-                       $scope.selectEscape = function() {
-                               $scope.reset();
-                       }
-
-                       $scope.setActive = function(index) {
-                               $scope.activeItem = index;
-                       }
+               .config(function($stateProvider, $locationProvider) {
+                       $stateProvider
+                               .state('search', {
+                                       url: '/search?q&p&n',
+                                       controller: 'SearchCtrl',
+                                       controllerAs: 'vm',
+                                       templateUrl: 'views/search.html',
+                                       resolve: {
+                                               entities: function(Model, $q, $stateParams, $state) {
+                                                       var d = $q.defer();
+                                                       var query = $stateParams.q;
+                                                       var page = $stateParams.p ? $stateParams.p : 1;
+                                                       var limit = $stateParams.n ? $stateParams.n : 10;
+                                                       Model.search(query, page, limit, d.resolve,
+                                                               function() {
+                                                                       $state.go('404', null, { location: false })
+                                                               });
+                                                       return d.promise;
+                                               }
+                                       }
+                               })
+               })
 
-                       $scope.search = function() {
-                               if(!$scope.query) {
-                                       $scope.reset();
-                                       return;
-                               }
-                               Model.search($scope.query, 10,
-                                       function(data) {
-                                               $scope.reset();
-                                               $scope.results = data;
-                                       }, function(err) {
-                                               $scope.reset();
-                                               $scope.error = err;
-                                       });
-                       }
+               .controller('SearchCtrl', function($scope, $state, $stateParams, entities) {
+                       var vm = this;
+                       vm.entities = entities;
+                       vm.query = $stateParams.q;
 
-                       $scope.reset();
+                       $scope.$on('change-page', function(e, page, limit) {
+                               $state.go('search', {q: vm.query, p: page, l: limit});
+                       })
                })
 
-               .directive('searchField', function($document) {
+               .directive('uiSearchField', function($document) {
                        return {
                                restrict: 'E',
                                replace: true,
-                               controller: 'SearchCtrl',
-                               controllerAs: 'searchCtrl',
-                               templateUrl: '/directives/search/field.html',
-                               link: function ($scope, element, attrs) {
+                               controller: function($scope, $state, $stateParams, $location, Model) {
+                                       var vm = this;
+                                       vm.search = function() {
+                                               if(!vm.query) {
+                                                       vm.reset();
+                                                       return;
+                                               }
+                                               Model.search(vm.query, 1, 8,
+                                                       function(data) {
+                                                               vm.reset();
+                                                               vm.results = data;
+                                                       }, function(err) {
+                                                               vm.reset();
+                                                               vm.error = err;
+                                                       });
+                                       }
+
+                                       vm.reset = function() {
+                                               vm.activeItem = -1;
+                                               vm.results = {
+                                                       results: []
+                                               };
+                                       }
+
+                                       vm.update = function(e) {
+                                               if(e.keyCode == 38) {
+                                                       vm.selectUp();
+                                               } else if(e.keyCode == 40) {
+                                                       vm.selectDown();
+                                               } else if(e.keyCode == 27) {
+                                                       vm.selectEscape();
+                                               } else if(e.keyCode == 13) {
+                                                       vm.selectEnter();
+                                               }
+                                       }
+
+                                       vm.selectUp = function() {
+                                               if(vm.activeItem >= 0) {
+                                                       vm.activeItem -= 1;
+                                               }
+                                       }
+
+                                       vm.selectDown = function() {
+                                               if(vm.activeItem < vm.results.results.length) {
+                                                       vm.activeItem += 1;
+                                               }
+                                       }
+
+                                       vm.selectEnter = function(e) {
+                                               if(vm.activeItem >= 0 && vm.activeItem < vm.results.results.length) {
+                                                       $location.url(vm.results.results[vm.activeItem].web_url);
+                                               } else {
+                                                       $state.go('search', {q: vm.query, p: 1});
+                                               }
+                                               vm.reset();
+                                       }
+
+                                       vm.selectEscape = function() {
+                                               vm.reset();
+                                       }
+
+                                       vm.setActive = function(index) {
+                                               vm.activeItem = index;
+                                       }
+
+                                       vm.reset();
+
+                                       $scope.$watch(function() {
+                                               return $stateParams.q;
+                                       }, function(q) {
+                                               if(q) vm.query = q;
+                                       });
+                               },
+                               controllerAs: 'vm',
+                               templateUrl: 'directives/ui/search-field.html',
+                               link: function ($scope, element, attrs, ctrl) {
                                        $document.bind('click', function (event) {
                                                var isChild = $(element).has(event.target).length > 0;
                                                var isSelf = element[0] == event.target;
                                                var isInside = isChild || isSelf;
                                                if (!isInside) {
-                                                       $scope.reset();
+                                                       ctrl.reset();
                                                        $scope.$apply();
                                                }
                                        });
                        };
                })
 
-               .directive('searchCard', function() {
-                       return {
-                               restrict: 'E',
-                               scope: {
-                                       mentity: '='
-                               },
-                               replace: true,
-                               templateUrl: '/directives/search/card.html'
-                       };
-               })
+               /* Filters */
 
                .directive('uiFilters', function() {
                        return {
                                }
                        };
                })
+
+               /* Pagination */
+
+               .directive('uiPagination', function() {
+                       return {
+                               restrict: 'E',
+                               replace: true,
+                               bindToController: {
+                                       pagination: '=',
+                                       suffix: '=?'
+                               },
+                               controller: function($scope) {
+                                       var vm = this;
+
+                                       $scope.$watch('pagination.pagination', function(pagination) {
+                                               if(!pagination) return;
+                                               vm.computePages(pagination);
+                                       })
+
+                                       vm.computePages = function(pagination) {
+                                               vm.pages = [];
+                                               var len = 11;
+                                               var page = pagination.page;
+                                               var start = page - Math.floor(len / 2);
+                                               var end = page + Math.floor(len / 2);
+
+                                               if(start < 1) {
+                                                       end = Math.min(pagination.max, end + Math.abs(start) + 1)
+                                                       start = 1
+                                               } else if(end > pagination.max) {
+                                                       start = Math.max(1, start - Math.abs(end - pagination.max))
+                                                       end = pagination.max;
+                                               }
+
+                                               for(var i = start; i <= end; i++) {
+                                                       vm.pages.push(i);
+                                               }
+                                       }
+
+                                       vm.changePage = function(page, limit) {
+                                               if(page <= 0 || page > vm.pagination.max) return;
+                                               var suffix = vm.suffix ? vm.suffix : '';
+                                               $scope.$emit('change-page' + suffix, page, limit);
+                                       }
+                               },
+                               controllerAs: 'pagination',
+                               templateUrl: 'directives/ui/pagination.html'
+                       };
+               })
 })();
index 0d39859..de16f88 100644 (file)
@@ -29,9 +29,9 @@
        display: table;
        width: 100%;
        background: #fff;
-       border: 1px solid #ccc;
+       border: 1px solid #eee;
        margin-top: 10px;
-       box-shadow: 0 -1px 0 #e5e5e5,0 0 2px rgba(0,0,0,.12),0 2px 4px rgba(0,0,0,.24);
+       box-shadow: -1px -1px 3px rgba(0,0,0,.06), 1px 1px 3px rgba(0,0,0,.12);
 }
 
 .card-body, .card-left, .card-right {
@@ -57,7 +57,7 @@
 }
 
 .card-list > .card:first-child {
-       border-top: 1px solid #ccc;
+       border-top: 1px solid #ddd;
 }
 
 .card-list > .card {
index 864c296..ccfcdd0 100644 (file)
        background: #FF8100;
 }
 
+[ng-click] {
+       cursor: pointer;
+}
+
 /* Body */
 
 body {
@@ -127,28 +131,6 @@ entity-list:hover .btn-filter {
     pointer-events: none;
 }
 
-/* search */
-
-.search-input {
-       width: 100%;
-}
-
-.search-results {
-       position: absolute;
-       margin-top: 2px;
-       right: 15px;
-       left: 15px;
-}
-
-.search-results .card.active {
-       background: #eee;
-       border-color: #eee;
-}
-
-.card-search {
-       cursor: pointer;
-}
-
 /* navs */
 
 .nav-tabs li { cursor: pointer; }
index 2f6e2db..68516dc 100644 (file)
 .navbar-fixed-top *:-moz-placeholder { color: #fff; }
 .navbar-fixed-top *::-moz-placeholder { color: #fff; }
 .navbar-fixed-top *:-ms-input-placeholder { color: #fff; }
+
+.search-input {
+       width: 100%;
+}
+
+.card-list.search-results {
+       position: absolute;
+       margin-top: 2px;
+       right: 15px;
+       left: 15px;
+       box-shadow: 1px 1px 3px rgba(0,0,0,0.12), -1px -1px 3px rgba(0,0,0,.12);
+}
+
+.search-results .card.active {
+       background: #eee;
+       border-color: #eee;
+}
diff --git a/share/nitweb/views/catalog/by_tags.html b/share/nitweb/views/catalog/by_tags.html
deleted file mode 100644 (file)
index 31ca905..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<div>
-       <h3>Tags</h3>
-       <div class='container-fluid'>
-               <div class='col-xs-3' ng-repeat='(tag, packages) in vm.bytags'>
-                       <span class='badge'>{{packages.length}}</span>
-                       <a ng-click='vm.scrollTo(tag)'>{{tag}}</a>
-               </div>
-       </div>
-       <div ng-repeat='(tag, packages) in vm.bytags'>
-               <entity-list list-id='{{tag}}' list-title='{{tag}}'
-                       list-entities='packages'
-                       list-object-filter='{}' />
-       </div>
-</div>
diff --git a/share/nitweb/views/catalog/highlighted.html b/share/nitweb/views/catalog/highlighted.html
deleted file mode 100644 (file)
index 5607bd5..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<div>
-       <entity-list list-title='Highlighted packages'
-               list-entities='vm.highlighted'
-               list-object-filter='{}' />
-</div>
index 877db4a..2333a0a 100644 (file)
@@ -1,47 +1,35 @@
-<div class='container-fluid'>
+<div class='container'>
        <div class='page-header'>
                <h2>Welcome to NitWeb!</h2>
                <p class='text-muted'>The Nit knowledge base.</p>
        </div>
 
-       <ul class='nav nav-tabs' role='tablist'>
-               <li role='presentation' ui-sref-active='active'>
-                       <a ui-sref='catalog.highlighted'>
-                               <span class='glyphicon glyphicon-book'/> Highlighed
-                       </a>
-               </li>
-               <li role='presentation' ui-sref-active='active'>
-                       <a ui-sref='catalog.required'>
-                               <span class='glyphicon glyphicon-book'/> Most required
-                       </a>
-               </li>
-               <li role='presentation' ui-sref-active='active'>
-                       <a ui-sref='catalog.tags'>
-                               <span class='glyphicon glyphicon-book'/> By tags
-                       </a>
-               </li>
-       </ul>
-       <table class='table'>
-               <tr>
-                       <td ng-repeat='(key, value) in vm.stats'>
-                               <h5><strong>{{value}}</strong>&nbsp;<span>{{key}}</span></h5>
-                       </td>
-               </tr>
-       </table>
+       <div ng-if='vm.stats' class='container-fluid no-padding'>
+               <span ng-repeat='(key, value) in vm.stats' class='text-muted small'>
+                       <strong>{{value}}</strong>&nbsp;<span>{{key}}</span>
+                       &nbsp;
+               </span>
+       </div>
+       <hr/>
 
-       <div class='container-fluid'>
-               <div class='col-xs-9'>
-                       <div class='tab-content'>
-                               <div role='tabpanel' class='tab-pane fade in active'>
-                                       <ui-view />
-                               </div>
+       <div class='col-md-3 col-md-push-9 no-padding' ng-if='vm.tags'>
+               <h2>Tags</h2>
+               <div class='container-fluid no-padding'>
+                       <div class='col-xs-3 col-md-12' ng-repeat='(tag, packages) in vm.tags'>
+                               <span class='badge'>{{packages}}</span>
+                               <a ui-sref='tag({id: tag})'>{{tag}}</a>
                        </div>
                </div>
-               <div class='col-xs-3'>
-                       <contributor-list list-title='Maintainers'
-                                       list-contributors='vm.contributors.maintainers' />
-                       <contributor-list list-title='Contributors'
-                                       list-contributors='vm.contributors.contributors' />
+               <hr/>
+       </div>
+
+       <div class='col-md-9 col-md-pull-3 no-padding'>
+               <h2>Packages</h2>
+               <div class='card-list'>
+                       <entity-card mentity='package' ng-repeat='package in vm.packages.results' />
+               </div>
+               <div class='container text-center' ng-if='vm.packages'>
+                       <ui-pagination pagination='vm.packages'/>
                </div>
        </div>
 </div>
diff --git a/share/nitweb/views/catalog/most_required.html b/share/nitweb/views/catalog/most_required.html
deleted file mode 100644 (file)
index 93bf2e1..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<div>
-       <entity-list list-title='Most required'
-               list-entities='vm.required'
-               list-object-filter='{}' />
-</div>
diff --git a/share/nitweb/views/catalog/person.html b/share/nitweb/views/catalog/person.html
new file mode 100644 (file)
index 0000000..433b73d
--- /dev/null
@@ -0,0 +1,33 @@
+<div class='container'>
+       <div class='col-xs-2 no-padding'>
+               <img class='avatar' style='width:100%; max-width:100px' src='https://secure.gravatar.com/avatar/{{vm.person.gravatar}}?size=100&amp;default=retro' />
+       </div>
+       <div class='col-xs-10'>
+               <h1>
+                       {{vm.person.name}}<br>
+                       <small>{{vm.person.email}}</small>
+               </h1>
+       </div>
+       <hr/>
+</div>
+<br><br>
+<div class='container'>
+       <div ng-if='vm.maintaining.results.length > 0'>
+               <h3 id='maintaining'>{{vm.maintaining.total}} maintained projects</h3>
+               <div class='card-list'>
+                       <entity-card mentity='package' ng-repeat='package in vm.maintaining.results' />
+               </div>
+               <div class='container text-center' ng-if='vm.maintaining'>
+                       <ui-pagination pagination='vm.maintaining' suffix='1'/>
+               </div>
+       </div>
+       <div ng-if='vm.contributing.results.length > 0'>
+               <h3 id='contributing'>{{vm.contributing.total}} contributed projects</h3>
+               <div class='card-list'>
+                       <entity-card mentity='package' ng-repeat='package in vm.contributing.results' />
+               </div>
+               <div class='container text-center' ng-if='vm.contributing'>
+                       <ui-pagination pagination='vm.contributing' suffix='2' />
+               </div>
+       </div>
+</div>
diff --git a/share/nitweb/views/catalog/tag.html b/share/nitweb/views/catalog/tag.html
new file mode 100644 (file)
index 0000000..6c0a37e
--- /dev/null
@@ -0,0 +1,16 @@
+<div class='container'>
+       <h1>
+               {{vm.tag.tag}}
+               <small>{{vm.tag.packages.total}} packages</small>
+       </h1>
+       <hr/>
+
+       <div class='container-fluid no-padding' ng-if='vm.tag.packages.results.length > 0'>
+               <div class='card-list'>
+                       <entity-card mentity='package' ng-repeat='package in vm.tag.packages.results' />
+               </div>
+               <div class='container text-center' ng-if='vm.tag.packages'>
+                       <ui-pagination pagination='vm.tag.packages'/>
+               </div>
+       </div>
+</div>
index 1e60694..eadcf37 100644 (file)
@@ -1,8 +1,10 @@
 <div>
-       <div class='col-xs-3'>
+       <div class='col-lg-2 col-sm-3 col-xs-12'>
                <ui-summary target='#summary-content' />
        </div>
-       <div class='col-xs-9' id='summary-content'>
+       <div class='col-lg-10 col-sm-9 col-xs-12' id='summary-content' ng-class='{
+               "col-lg-8 col-sm-6 col-xs-12": vm.mentity.class_name == "MPackage"
+       }'>
                <div class='card'>
                        <div class='card-body'>
                                <div ng-if='vm.doc'>
                        list-object-filter='{is_init: "!true"}' />
 
        </div>
+
+       <div class='col-lg-2 col-sm-3 col-xs-12' ng-class='{
+               "hidden": vm.mentity.class_name != "MPackage"
+       }'>
+               <br>
+               <p ng-repeat='maintainer in vm.mentity.metadata.maintainers' class='lead'>
+                       <img class='avatar' src='https://secure.gravatar.com/avatar/{{maintainer.gravatar}}?size=20&default=retro' />
+                       <span>
+                               <a ui-sref='person({id: maintainer.name})'>{{maintainer.name}}</a>
+                       </span>
+                       <br>
+               </p>
+               <span ng-if='vm.mentity.metadata.license'>
+                       <span class='text-muted'>
+                               <a href='http://opensource.org/licenses/{{vm.mentity.metadata.license}}'>{{vm.mentity.metadata.license}}</a>
+                               license
+                       </span>
+                       <br>
+               </span>
+
+               <div ng-if='vm.mentity.metadata.homepage || vm.mentity.metadata.issues'>
+                       <h3>Links</h3>
+                       <ul class='list-unstyled'>
+                               <li ng-if='vm.mentity.metadata.homepage'>
+                                       <a href='{{vm.mentity.metadata.homepage}}'>Homepage</a>
+                               </li>
+                               <li ng-if='vm.mentity.metadata.browse'>
+                                       <a href='{{vm.mentity.metadata.browse}}'>Source Code</a>
+                               </li>
+                               <li ng-if='vm.mentity.metadata.issues'>
+                                       <a href='{{vm.mentity.metadata.issues}}'>Issues</a>
+                               </li>
+                       </ul>
+               </div>
+
+               <div ng-if='vm.mentity.metadata.git || vm.mentity.stats.commits'>
+                       <h3>Git</h3>
+                       <ul class='list-unstyled' style='white-space: nowrap; overflow: hidden; text-overflow: ellipsis;'>
+                               <li ng-if='vm.mentity.metadata.git'>
+                                       <a href='{{vm.mentity.metadata.git}}'>{{vm.mentity.metadata.git}}</a>
+                               </li>
+                               <li ng-if='vm.mentity.stats.commits' class='text-muted'>
+                                       <br><b>{{vm.mentity.stats.commits}} commits</b>
+                               </li>
+                               <li ng-if='vm.mentity.metadata.last_date'><b class='text-muted'>Last:</b> {{vm.date(vm.mentity.metadata.last_date) | date: 'medium'}}</li>
+                               <li ng-if='vm.mentity.metadata.first_date'><b class='text-muted'>First: </b>{{vm.date(vm.mentity.metadata.first_date) | date: 'medium'}}</li>
+                       </ul>
+               </div>
+
+               <div ng-if='vm.mentity.stats'>
+                       <h3>Quality</h3>
+                       <ul class='list-unstyled'>
+                               <li ng-if='vm.mentity.stats.documentation_score'>
+                                       {{vm.mentity.stats.documentation_score}}% documented
+                               </li>
+                               <li ng-if='vm.mentity.stats.errors' class='text-danger'>
+                                       {{vm.mentity.stats.errors}} errors
+                               </li>
+                               <li ng-if='vm.mentity.stats.warnings' class='text-warning'>
+                                       {{vm.mentity.stats.warnings}} warnings
+                                       ({{vm.mentity.stats.warnings_per_kloc}} / kloc)
+                               </li>
+                       </ul>
+               </div>
+
+               <div ng-if='vm.mentity.metadata.tags.length > 0'>
+                       <h3>Tags</h3>
+                       <span ng-repeat='tag in vm.mentity.metadata.tags'>
+                               <a ui-sref='tag({id: tag})'>{{tag}}</a><span ng-if='!$last'>,</span>
+                       </span>
+               </div>
+
+               <div ng-if='vm.mentity.dependencies.length > 0'>
+                       <h3>Requirements</h3>
+                       <span ng-repeat='parent in vm.mentity.dependencies'>
+                               <a ui-sref='mentity({id: parent.name})' title='{{parent.synopsis}}'>{{parent.name}}</a><span ng-if='!$last'>,</span>
+                       </span>
+               </div>
+
+               <div ng-if='vm.mentity.clients.length > 0'>
+                       <h3>Clients</h3>
+                       <span ng-repeat='client in vm.mentity.clients'>
+                               <a ui-sref='mentity({id: client.name})' title='{{client.synopsis}}'>{{client.name}}</a><span ng-if='!$last'>,</span>
+                       </span>
+               </div>
+
+               <div ng-if='vm.mentity.metadata.contributors.length > 1'>
+                       <h3>Contributors</h3>
+                       <ul class='list-unstyled'>
+                               <li ng-repeat='contributor in vm.mentity.metadata.contributors'>
+                                       <img class='avatar' src='https://secure.gravatar.com/avatar/{{contributor.gravatar}}?size=20&default=retro' />
+                                       <a ui-sref='person({id: contributor.name})'>
+                                       {{contributor.name}}</a>
+                               </li>
+                       </ul>
+               </div>
+
+               <div ng-if='vm.mentity.stats'>
+                       <h3>Stats</h3>
+                       <ul class='list-unstyled'>
+                               <li>{{vm.mentity.stats.mmodules}} modules</li>
+                               <li>{{vm.mentity.stats.mclasses}} classes</li>
+                               <li>{{vm.mentity.stats.mmethods}} methods</li>
+                               <li>{{vm.mentity.stats.loc}} loc</li>
+                       </ul>
+               </div>
+       </div>
 </div>
index 3543b9f..e6b559f 100644 (file)
                                <span class='glyphicon glyphicon-stats'/> Metrics
                        </a>
                </li>
+
+               <!-- grades -->
+               <li role='presentation' ui-sref-active='active'>
+                       <a ui-sref='.grades'>
+                               <span class='glyphicon glyphicon-star'/> Grades
+                       </a>
+               </li>
        </ul>
        <br>
        <ui-view />
diff --git a/share/nitweb/views/doc/grades.html b/share/nitweb/views/doc/grades.html
new file mode 100644 (file)
index 0000000..89fc74c
--- /dev/null
@@ -0,0 +1,5 @@
+<div class='card'>
+       <div class='card-body'>
+               <entity-rating mentity='vm.mentity' ratings='vm.ratings'>
+       </div>
+</div>
diff --git a/share/nitweb/views/search.html b/share/nitweb/views/search.html
new file mode 100644 (file)
index 0000000..6614e9c
--- /dev/null
@@ -0,0 +1,12 @@
+<div class='container'>
+       <h2>
+               {{vm.entities.total}} matches for
+               <a ui-sref='search({q: vm.query})'>{{vm.query}}</a>
+       </h2>
+       <div class='card-list'>
+               <entity-card mentity='mentity' ng-repeat='mentity in vm.entities.results' />
+       </div>
+       <div class='container text-center' ng-if='vm.entities'>
+               <ui-pagination pagination='vm.entities'/>
+       </div>
+</div>
index 92510bc..dd891ac 100644 (file)
@@ -53,18 +53,41 @@ import counter # For statistics
 import modelize # To process and count classes and methods
 
 redef class MPackage
+
+       # Metadata related to this package
+       var metadata = new MPackageMetadata(self)
+end
+
+# The metadata extracted from a MPackage
+class MPackageMetadata
+
+       # The mpacakge this metadata belongs to
+       var mpackage: MPackage
+
        # Return the associated metadata from the `ini`, if any
-       fun metadata(key: String): nullable String
-       do
-               var ini = self.ini
+       fun metadata(key: String): nullable String do
+               var ini = mpackage.ini
                if ini == null then return null
                return ini[key]
        end
 
        # The consolidated list of tags
-       var tags = new Array[String]
+       var tags: Array[String] is lazy do
+               var tags = new Array[String]
+               var string = metadata("package.tags")
+               if string == null then return tags
+               for tag in string.split(",") do
+                       tag = tag.trim
+                       if tag.is_empty then continue
+                       tags.add tag
+               end
+               if tryit != null then tags.add "tryit"
+               if apk != null then tags.add "apk"
+               if tags.is_empty then tags.add "none"
+               return tags
+       end
 
-       # The list of maintainers
+       # The list of all maintainers
        var maintainers = new Array[Person]
 
        # The list of contributors
@@ -75,6 +98,43 @@ redef class MPackage
 
        # The date of the oldest commit
        var first_date: nullable String = null
+
+       # Key: package.maintainer`
+       var maintainer: nullable String is lazy do return metadata("package.maintainer")
+
+       # Key: `package.more_contributors`
+       var more_contributors: Array[String] is lazy do
+               var res = new Array[String]
+               var string = metadata("package.more_contributors")
+               if string == null then return res
+               for c in string.split(",") do
+                       c = c.trim
+                       if c.is_empty then continue
+                       res.add c
+               end
+               return res
+       end
+
+       # Key: `package.license`
+       var license: nullable String is lazy do return metadata("package.license")
+
+       # Key: `upstream.tryit`
+       var tryit: nullable String is lazy do return metadata("upstream.tryit")
+
+       # Key: `upstream.apk`
+       var apk: nullable String is lazy do return metadata("upstream.apk")
+
+       # Key: `upstream.homepage`
+       var homepage: nullable String is lazy do return metadata("upstream.homepage")
+
+       # Key: `upstream.browse`
+       var browse: nullable String is lazy do return metadata("upstream.browse")
+
+       # Package git clone address
+       var git: nullable String is lazy do return metadata("upstream.git")
+
+       # Package issue tracker
+       var issues: nullable String is lazy do return metadata("upstream.issues")
 end
 
 redef class Int
@@ -98,6 +158,13 @@ class Person
        # Some homepage. Eg "http://example.com/~jdoe"
        var page: nullable String is writable
 
+       # Gravatar id
+       var gravatar: nullable String is lazy do
+               var email = self.email
+               if email == null then return null
+               return email.md5.to_lower
+       end
+
        # Return a full-featured link to a person
        fun to_html: String
        do
@@ -107,10 +174,9 @@ class Person
                if page != null then
                        res += "<a href=\"{page.html_escape}\">"
                end
-               var email = self.email
-               if email != null then
-                       var md5 = email.md5.to_lower
-                       res += "<img src=\"https://secure.gravatar.com/avatar/{md5}?size=20&amp;default=retro\">&nbsp;"
+               var gravatar = self.gravatar
+               if gravatar != null then
+                       res += "<img src=\"https://secure.gravatar.com/avatar/{gravatar}?size=20&amp;default=retro\">&nbsp;"
                end
                res += e
                if page != null then res += "</a>"
@@ -202,6 +268,9 @@ class Catalog
        # used to access the files and count source lines of code
        var modelbuilder: ModelBuilder
 
+       # List of all packages by their names
+       var mpackages = new HashMap[String, MPackage]
+
        # Packages by tag
        var tag2proj = new MultiHashMap[String, MPackage]
 
@@ -249,9 +318,15 @@ class Catalog
        # The score is loosely computed using other metrics
        var score = new Counter[MPackage]
 
-       # List of known people
+       # List of known people by their git string
        var persons = new HashMap[String, Person]
 
+       # Map person short names to person objects
+       var name2person = new HashMap[String, Person]
+
+       # Package statistics cache
+       var mpackages_stats = new HashMap[MPackage, MPackageStats]
+
        # Scan, register and add a contributor to a package
        fun register_contrib(person: String, mpackage: MPackage): Person
        do
@@ -268,14 +343,17 @@ class Catalog
                var projs = contrib2proj[p]
                if not projs.has(mpackage) then
                        projs.add mpackage
-                       mpackage.contributors.add p
+                       mpackage.metadata.contributors.add p
                end
+               name2person[p.name] = p
                return p
        end
 
        # Compute information for a package
        fun package_page(mpackage: MPackage)
        do
+               mpackages[mpackage.full_name] = mpackage
+
                var score = score[mpackage].to_f
 
                var mdoc = mpackage.mdoc_or_fallback
@@ -283,58 +361,45 @@ class Catalog
                        score += 100.0
                        score += mdoc.content.length.score
                end
+               var metadata = mpackage.metadata
 
-
-               var tryit = mpackage.metadata("upstream.tryit")
+               var tryit = metadata.tryit
                if tryit != null then
                        score += 1.0
                end
-               var apk = mpackage.metadata("upstream.apk")
+               var apk = metadata.apk
                if apk != null then
                        score += 1.0
                end
-
-               var homepage = mpackage.metadata("upstream.homepage")
+               var homepage = metadata.homepage
                if homepage != null then
                        score += 5.0
                end
-               var maintainer = mpackage.metadata("package.maintainer")
+               var maintainer = metadata.maintainer
                if maintainer != null then
                        score += 5.0
                        var person = register_contrib(maintainer, mpackage)
-                       mpackage.maintainers.add person
+                       mpackage.metadata.maintainers.add person
                        var projs = maint2proj[person]
                        if not projs.has(mpackage) then projs.add mpackage
                end
-               var license = mpackage.metadata("package.license")
+               var license = metadata.license
                if license != null then
                        score += 5.0
                end
-
-               var browse = mpackage.metadata("upstream.browse")
+               var browse = metadata.browse
                if browse != null then
                        score += 5.0
                end
-
-               var tags = mpackage.metadata("package.tags")
-               var ts = mpackage.tags
-               if tags != null then
-                       for t in tags.split(",") do
-                               t = t.trim
-                               if t == "" then continue
-                               ts.add t
-                       end
+               var tags = metadata.tags
+               for tag in tags do
+                       tag2proj[tag].add mpackage
                end
-               if ts.is_empty then ts.add "none"
-               if tryit != null then ts.add "tryit"
-               if apk != null then ts.add "apk"
-               for t in ts do
-                       tag2proj[t].add mpackage
+               if tags.not_empty then
+                       var cat = tags.first
+                       cat2proj[cat].add mpackage
+                       score += tags.length.score
                end
-               var cat = ts.first
-               cat2proj[cat].add mpackage
-               score += ts.length.score
-
                if deps.has(mpackage) then
                        score += deps[mpackage].greaters.length.score
                        score += deps[mpackage].direct_greaters.length.score
@@ -342,15 +407,12 @@ class Catalog
                        score += deps[mpackage].direct_smallers.length.score
                end
 
-               var contributors = mpackage.contributors
-               var more_contributors = mpackage.metadata("package.more_contributors")
-               if more_contributors != null then
-                       for c in more_contributors.split(",") do
-                               register_contrib(c.trim, mpackage)
-                       end
+               var contributors = mpackage.metadata.contributors
+               var more_contributors = metadata.more_contributors
+               for c in more_contributors do
+                       register_contrib(c, mpackage)
                end
                score += contributors.length.to_f
-
                var mmodules = 0
                var mclasses = 0
                var mmethods = 0
@@ -384,7 +446,7 @@ class Catalog
                                        end
                                end
                                var ms = gs
-                               if m.is_test_suite then ms /= 100.0
+                               if m.is_test then ms /= 100.0
                                entity_score += ms
                                if m.mdoc != null then doc_score += ms else ms /= 10.0
                                for cd in m.mclassdefs do
@@ -417,7 +479,6 @@ class Catalog
                end
                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
@@ -433,12 +494,15 @@ class Catalog
                var ini = mpackage.ini
                if ini == null then return
 
+               var root = mpackage.root
+               if root == null then return
+
                # TODO use real git info
                #var repo = ini.get_or_null("upstream.git")
                #var branch = ini.get_or_null("upstream.git.branch")
                #var directory = ini.get_or_null("upstream.git.directory")
 
-               var dirpath = mpackage.root.filepath
+               var dirpath = root.filepath
                if dirpath == null then return
 
                # Collect commits info
@@ -452,8 +516,8 @@ class Catalog
                        if s.length != 2 or s.last == "" then continue
 
                        # Collect date of last and first commit
-                       if mpackage.last_date == null then mpackage.last_date = s.first
-                       mpackage.first_date = s.first
+                       if mpackage.metadata.last_date == null then mpackage.metadata.last_date = s.first
+                       mpackage.metadata.first_date = s.first
 
                        # Count contributors
                        contributors.inc(s.last)
@@ -461,10 +525,132 @@ class Catalog
                for c in contributors.sort.reverse_iterator do
                        register_contrib(c, mpackage)
                end
+       end
+
+       # Compose package stats
+       fun mpackage_stats(mpackage: MPackage): MPackageStats do
+               var stats = new MPackageStats
+               stats.mmodules = mmodules[mpackage]
+               stats.mclasses = mclasses[mpackage]
+               stats.mmethods = mmethods[mpackage]
+               stats.loc = loc[mpackage]
+               stats.errors = errors[mpackage]
+               stats.warnings = warnings[mpackage]
+               stats.warnings_per_kloc = warnings_per_kloc[mpackage]
+               stats.documentation_score = documentation_score[mpackage]
+               stats.commits = commits[mpackage]
+               stats.score = score[mpackage]
+
+               mpackages_stats[mpackage] = stats
+               return stats
+       end
 
+       # Compose catalog stats
+       var catalog_stats: CatalogStats is lazy do
+               var stats = new CatalogStats
+               stats.packages = mpackages.length
+               stats.maintainers = maint2proj.length
+               stats.contributors = contrib2proj.length
+               stats.tags = tag2proj.length
+               stats.modules = mmodules.sum
+               stats.classes = mclasses.sum
+               stats.methods = mmethods.sum
+               stats.loc = loc.sum
+               return stats
        end
 end
 
+# Catalog statistics
+class CatalogStats
+
+       # Number of packages
+       var packages = 0
+
+       # Number of maintainers
+       var maintainers = 0
+
+       # Number of contributors
+       var contributors = 0
+
+       # Number of tags
+       var tags = 0
+
+       # Number of modules
+       var modules = 0
+
+       # Number of classes
+       var classes = 0
+
+       # Number of methods
+       var methods = 0
+
+       # Number of line of codes
+       var loc = 0
+end
+
+# MPackage statistics for the catalog
+class MPackageStats
+
+       # Number of modules
+       var mmodules = 0
+
+       # Number of classes
+       var mclasses = 0
+
+       # Number of methods
+       var mmethods = 0
+
+       # Number of lines of code
+       var loc = 0
+
+       # Number of errors
+       var errors = 0
+
+       # Number of warnings and advices
+       var warnings = 0
+
+       # Number of warnings per 1000 lines of code (w/kloc)
+       var warnings_per_kloc = 0
+
+       # Documentation score (between 0 and 100)
+       var documentation_score = 0
+
+       # Number of commits by package
+       var commits = 0
+
+       # Score by package
+       #
+       # The score is loosely computed using other metrics
+       var score = 0
+end
+
+# Sort the mpackages by their score
+class CatalogScoreSorter
+       super Comparator
+
+       # Catalog used to access scores
+       var catalog: Catalog
+
+       redef type COMPARED: MPackage
+
+       redef fun compare(a, b) do
+               if not catalog.mpackages_stats.has_key(a) then return 1
+               if not catalog.mpackages_stats.has_key(b) then return -1
+               var astats = catalog.mpackages_stats[a]
+               var bstats = catalog.mpackages_stats[b]
+               return bstats.score <=> astats.score
+       end
+end
+
+# Sort tabs alphabetically
+class CatalogTagsSorter
+       super Comparator
+
+       redef type COMPARED: String
+
+       redef fun compare(a, b) do return a <=> b
+end
+
 # Execute a git command and return the result
 fun git_run(command: String...): String
 do
index 867afcf..0fc7008 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_coloring is test_suite
+module test_coloring is test
 
-import test_suite
 import coloring
 
 class TestPOSetColorer
-       super TestSuite
+       test
 
-       fun test_ids_strictly_positive do
+       fun test_ids_strictly_positive is test do
                var poset = new POSet[String]
                poset.add_node "A"
 
index 97efb63..54d9a01 100644 (file)
@@ -48,8 +48,8 @@ private class NoWarningPhase
 
                var modelbuilder = toolcontext.modelbuilder
 
-               # Disable `missing-doc` for `test_suite`
-               if source != null and not nmoduledecl.get_annotations("test_suite").is_empty then
+               # Disable `missing-doc` for `test`
+               if source != null and not nmoduledecl.get_annotations("test").is_empty then
                        toolcontext.warning_blacklist[source].add("missing-doc")
                end
 
index b2a9687..409a994 100644 (file)
@@ -106,12 +106,26 @@ end
 
 redef class MModule
        super AnnotatedMEntity
+
+       redef var is_test is lazy do return has_annotation("test")
 end
 
 redef class MClassDef
        super AnnotatedMEntity
+
+       redef var is_test is lazy do return has_annotation("test")
 end
 
 redef class MPropDef
        super AnnotatedMEntity
+
+       redef var is_test is lazy do return has_annotation("test")
+
+       redef var is_before is lazy do return has_annotation("before")
+
+       redef var is_before_all is lazy do return has_annotation("before_all")
+
+       redef var is_after is lazy do return has_annotation("after")
+
+       redef var is_after_all is lazy do return has_annotation("after_all")
 end
index a269ee4..7e9a506 100644 (file)
@@ -225,7 +225,7 @@ private class SerializationPhasePreModel
                end
 
                var code = """
-redef init from_deserializer(v: Deserializer) do abort"""
+redef init from_deserializer(v) do abort"""
 
                var npropdef = toolcontext.parse_propdef(code).as(AMethPropdef)
                npropdefs.add npropdef
index 615aba9..125866a 100644 (file)
@@ -812,8 +812,6 @@ redef class ModelBuilder
                                mmodule.mdoc = mdoc
                                mdoc.original_mentity = mmodule
                        end
-                       # Is the module a test suite?
-                       mmodule.is_test_suite = not decl.get_annotations("test_suite").is_empty
                        # Is the module generated?
                        mmodule.is_generated = not decl.get_annotations("generated").is_empty
                end
index 9d1fb9c..a410236 100644 (file)
@@ -248,9 +248,6 @@ class MModule
                end
        end
 
-       # Is `self` a unit test module used by `nitunit`?
-       var is_test_suite: Bool = false is writable
-
        # Is `self` a module generated by a tool?
        #
        # This flag has no effect on the semantic.
index 935b201..47900c5 100644 (file)
@@ -588,6 +588,8 @@ class MClass
        # Is `self` and abstract class?
        var is_abstract: Bool is lazy do return kind == abstract_kind
 
+       redef var is_test is lazy do return intro.is_test
+
        redef fun mdoc_or_fallback
        do
                # Don’t use `intro.mdoc_or_fallback` because it would create an infinite
@@ -2327,6 +2329,20 @@ abstract class MProperty
        end
 
        private var lookup_all_definitions_cache = new HashMap2[MModule, MType, Array[MPROPDEF]]
+
+       redef var is_test is lazy do return intro.is_test
+
+       # Does self have the `before` annotation?
+       var is_before: Bool is lazy do return intro.is_before
+
+       # Does self have the `before_all` annotation?
+       var is_before_all: Bool is lazy do return intro.is_before_all
+
+       # Does self have the `after` annotation?
+       var is_after: Bool is lazy do return intro.is_after
+
+       # Does self have the `after_all` annotation?
+       var is_after_all: Bool is lazy do return intro.is_after_all
 end
 
 # A global method
@@ -2516,6 +2532,18 @@ abstract class MPropDef
        end
 
        redef fun mdoc_or_fallback do return mdoc or else mproperty.mdoc_or_fallback
+
+       # Does self have the `before` annotation?
+       var is_before = false is writable
+
+       # Does self have the `before_all` annotation?
+       var is_before_all = false is writable
+
+       # Does self have the `after` annotation?
+       var is_after = false is writable
+
+       # Does self have the `after_all` annotation?
+       var is_after_all = false is writable
 end
 
 # A local definition of a method
index 0c136b8..0a0d8b0 100644 (file)
@@ -103,6 +103,11 @@ abstract class MEntity
        # Fictive entities are used internally but they should not be
        # exposed to the final user.
        var is_fictive: Bool = false is writable
+
+       # Is `self` created for unit testing purpose?
+       #
+       # See `nitunit`.
+       var is_test: Bool = false is writable
 end
 
 # Something that represents a concern
index 2f0eed5..8b0f122 100644 (file)
@@ -122,7 +122,7 @@ class ModelView
                v.include_fictive = self.include_fictive
                v.include_empty_doc = self.include_empty_doc
                v.include_attribute = self.include_attribute
-               v.include_test_suite = self.include_test_suite
+               v.include_test = self.include_test
        end
 
        # Searches the MEntity that matches `full_name`.
index 59d3e61..a2807de 100644 (file)
@@ -104,13 +104,13 @@ abstract class ModelVisitor
        # Should we accept nitunit test suites?
        #
        # Default is `false`.
-       var include_test_suite = false is writable
+       var include_test = false is writable
 
        # Can we accept this `mentity` regarding its test suite status?
-       fun accept_test_suite(mentity: MEntity): Bool do
-               if include_test_suite then return true
+       fun accept_test(mentity: MEntity): Bool do
+               if include_test then return true
                if not mentity isa MModule then return true
-               return not mentity.is_test_suite
+               return not mentity.is_test
        end
 
        # Should we accept `MAttribute` instances?
@@ -131,7 +131,7 @@ abstract class ModelVisitor
                if not accept_visibility(mentity) then return false
                if not accept_fictive(mentity) then return false
                if not accept_empty_doc(mentity) then return false
-               if not accept_test_suite(mentity) then return false
+               if not accept_test(mentity) then return false
                if not accept_attribute(mentity) then return false
                return true
        end
index 897dfd5..c544446 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_model_json is test_suite
+module test_model_json is test
 
-import test_suite
 import model_json
 import frontend
 
 class TestModelSerialization
-       super TestSuite
+       test
 
        var suite_path: String = "NIT_TESTING_PATH".environ
        var lib_path: String = "{suite_path.dirname}/../../tests/test_prog"
@@ -35,7 +34,7 @@ class TestModelSerialization
                return model
        end
 
-       fun test_refs_to_full_json do
+       fun test_refs_to_full_json is test do
                var mentities = new Array[MEntity]
                mentities.add model.mpackages.first
                mentities.add model.mmodules.first
@@ -45,13 +44,13 @@ class TestModelSerialization
                end
        end
 
-       fun test_packages_to_full_json do
+       fun test_packages_to_full_json is test do
                for mentity in model.mpackages do
                        print mentity.to_pretty_full_json
                end
        end
 
-       fun test_groups_to_full_json do
+       fun test_groups_to_full_json is test do
                for mpackage in model.mpackages do
                        for mentity in mpackage.mgroups do
                                print mentity.to_pretty_full_json
@@ -59,19 +58,19 @@ class TestModelSerialization
                end
        end
 
-       fun test_modules_to_full_json do
+       fun test_modules_to_full_json is test do
                for mentity in model.mmodules do
                        print mentity.to_pretty_full_json
                end
        end
 
-       fun test_classes_to_full_json do
+       fun test_classes_to_full_json is test do
                for mentity in model.mclasses do
                        print mentity.to_pretty_full_json
                end
        end
 
-       fun test_classdefs_to_full_json do
+       fun test_classdefs_to_full_json is test do
                for mclass in model.mclasses do
                        for mentity in mclass.mclassdefs do
                                print mentity.to_pretty_full_json
@@ -79,13 +78,13 @@ class TestModelSerialization
                end
        end
 
-       fun test_props_to_full_json do
+       fun test_props_to_full_json is test do
                for mentity in model.mproperties do
                        print mentity.to_pretty_full_json
                end
        end
 
-       fun test_propdefs_to_full_json do
+       fun test_propdefs_to_full_json is test do
                for mprop in model.mproperties do
                        for mentity in mprop.mpropdefs do
                                print mentity.to_pretty_full_json
index 982e206..c116234 100644 (file)
@@ -267,12 +267,12 @@ redef class Catalog
 <div class="sidebar">
 <ul class="box">
 """
-               var tryit = mpackage.metadata("upstream.tryit")
+               var tryit = mpackage.metadata.metadata("upstream.tryit")
                if tryit != null then
                        var e = tryit.html_escape
                        res.add "<li><a href=\"{e}\">Try<span style=\"color:white\">n</span>it!</a></li>\n"
                end
-               var apk = mpackage.metadata("upstream.apk")
+               var apk = mpackage.metadata.metadata("upstream.apk")
                if apk != null then
                        var e = apk.html_escape
                        res.add "<li><a href=\"{e}\">Android apk</a></li>\n"
@@ -280,15 +280,15 @@ redef class Catalog
 
                res.add """</ul>\n<ul class="box">\n"""
 
-               var homepage = mpackage.metadata("upstream.homepage")
+               var homepage = mpackage.metadata.metadata("upstream.homepage")
                if homepage != null then
                        var e = homepage.html_escape
                        res.add "<li><a href=\"{e}\">{e}</a></li>\n"
                end
-               for maintainer in mpackage.maintainers do
+               for maintainer in mpackage.metadata.maintainers do
                        res.add "<li>{maintainer.to_html}</li>"
                end
-               var license = mpackage.metadata("package.license")
+               var license = mpackage.metadata.metadata("package.license")
                if license != null then
                        var e = license.html_escape
                        res.add "<li><a href=\"http://opensource.org/licenses/{e}\">{e}</a> license</li>\n"
@@ -296,22 +296,22 @@ redef class Catalog
                res.add "</ul>\n"
 
                res.add "<h3>Source Code</h3>\n<ul class=\"box\">\n"
-               var browse = mpackage.metadata("upstream.browse")
+               var browse = mpackage.metadata.metadata("upstream.browse")
                if browse != null then
                        var e = browse.html_escape
                        res.add "<li><a href=\"{e}\">{e}</a></li>\n"
                end
-               var git = mpackage.metadata("upstream.git")
+               var git = mpackage.metadata.metadata("upstream.git")
                if git != null then
                        var e = git.html_escape
                        res.add "<li><tt>{e}</tt></li>\n"
                end
-               var last_date = mpackage.last_date
+               var last_date = mpackage.metadata.last_date
                if last_date != null then
                        var e = last_date.html_escape
                        res.add "<li>most recent commit: {e}</li>\n"
                end
-               var first_date = mpackage.first_date
+               var first_date = mpackage.metadata.first_date
                if first_date != null then
                        var e = first_date.html_escape
                        res.add "<li>oldest commit: {e}</li>\n"
@@ -333,7 +333,7 @@ redef class Catalog
 
                res.add "<h3>Tags</h3>\n"
                var ts2 = new Array[String]
-               for t in mpackage.tags do
+               for t in mpackage.metadata.tags do
                        t = t.html_escape
                        ts2.add "<a href=\"../index.html#tag_{t}\">{t}</a>"
                end
@@ -381,7 +381,7 @@ redef class Catalog
                        end
                end
 
-               var contributors = mpackage.contributors
+               var contributors = mpackage.metadata.contributors
                if not contributors.is_empty then
                        res.add "<h3>Contributors</h3>\n<ul class=\"box\">"
                        for c in contributors do
@@ -499,9 +499,9 @@ redef class Catalog
                        res.add "<tr>"
                        res.add "<td><a href=\"p/{p.name}.html\">{p.name}</a></td>"
                        var maint = "?"
-                       if p.maintainers.not_empty then maint = p.maintainers.first.name.html_escape
+                       if p.metadata.maintainers.not_empty then maint = p.metadata.maintainers.first.name.html_escape
                        res.add "<td>{maint}</td>"
-                       res.add "<td>{p.contributors.length}</td>"
+                       res.add "<td>{p.metadata.contributors.length}</td>"
                        if deps.not_empty then
                                res.add "<td>{deps[p].greaters.length-1}</td>"
                                res.add "<td>{deps[p].direct_greaters.length}</td>"
index 772ec38..a0addf1 100644 (file)
@@ -49,20 +49,19 @@ To test a method you have to instanciate its class:
 
 TestSuites are Nit files that define a set of TestCase for a particular module.
 
-The test suite module must be declared using the `test_suite` annotation.
+The test suite module must be declared using the `test` annotation.
 The structure of a test suite is the following:
 
        # test suite for module `foo`
-       module test_foo is test_suite
+       module test_foo is test
 
-       import test_suite # import the `TestSuite` class and the `test_suite` annotation
        import foo # can be intrude to test private things
 
        class TestFoo
-               super TestSuite
+               test
 
                # test case for `foo::Foo::baz`
-               fun test_baz do
+               fun baz is test do
                        var subject = new Foo
                        assert subject.baz(1, 2) == 3
                end
@@ -79,18 +78,18 @@ Otherwise, you can use the `-t` option to specify the test suite module name:
 
        $ nitunit foo.nit -t my_test_suite.nit
 
-`nitunit` will execute a test for each method named `test_*` in a class named `Test*`
+`nitunit` will execute a test for each method annotated with `test` in a class also annotated with `test`
 so multiple tests can be executed for a single method:
 
        class TestFoo
-               super TestSuite
+               test
 
-               fun test_baz_1 do
+               fun baz_1 is test do
                        var subject = new Foo
                        assert subject.baz(1, 2) == 3
                end
 
-               fun test_baz_2 do
+               fun baz_2 is test do
                        var subject = new Foo
                        assert subject.baz(1, -2) == -1
                end
@@ -98,51 +97,50 @@ so multiple tests can be executed for a single method:
 
 `TestSuites` also provide methods to configure the test run:
 
-`before_test` and `after_test`: methods called before/after each test case.
+`before` and `after` annotations can be added to methods that must be called before/after each test case.
 They can be used to factorize repetitive tasks:
 
        class TestFoo
-               super TestSuite
+               test
 
                var subject: Foo is noinit
 
                # Method executed before each test
-               redef fun before_test do
+               redef fun set_up is before do
                        subject = new Foo
                end
 
-               fun test_baz_1 do
+               fun baz_1 is test do
                        assert subject.baz(1, 2) == 3
                end
 
-               fun test_baz_2 do
+               fun baz_2 is test do
                        assert subject.baz(1, -2) == -1
                end
        end
 
 When using custom test attributes, a empty init must be declared to allow automatic test running.
 
-`before_module` and `after_module`: methods called before/after each test suite.
+`before_all` and `after_all` annotations can be set on methods that must be called before/after each test suite.
 They have to be declared at top level:
 
        module test_bdd_connector
 
-       import test_suite
        import bdd_connector
 
        # Testing the bdd_connector
        class TestConnector
-               super TestSuite
+               test
                # test cases using a server
        end
 
        # Method executed before testing the module
-       redef fun before_module do
+       fun before_module is before_all do
                # start server before all test cases
        end
 
        # Method executed after testing the module
-       redef fun after_module do
+       fun after_module is after_all do
                # stop server after all test cases
        end
 
index 2564c5e..a444bee 100644 (file)
@@ -27,9 +27,8 @@ class NitUnitGenerator
        fun gen_unit(mmodule: MModule, test_file: String): Template do
                var with_private = toolcontext.opt_gen_private.value
                var tpl = new Template
-               tpl.addn "module test_{mmodule.name} is test_suite"
+               tpl.addn "module test_{mmodule.name} is test"
                tpl.addn ""
-               tpl.addn "import test_suite"
                if with_private then
                        tpl.addn "intrude import {mmodule.name}"
                else
@@ -39,7 +38,7 @@ class NitUnitGenerator
                        if mclassdef.mclass.kind != concrete_kind then continue
                        tpl.addn ""
                        tpl.addn "class Test{mclassdef.name}"
-                       tpl.addn "\tsuper TestSuite"
+                       tpl.addn "\ttest"
                        for mpropdef in mclassdef.mpropdefs do
                                if not mpropdef isa MMethodDef then continue
                                var mproperty = mpropdef.mproperty
@@ -48,7 +47,7 @@ class NitUnitGenerator
                                if not with_private and mproperty.visibility <= protected_visibility then continue
                                var case_name = case_name(mpropdef)
                                tpl.addn ""
-                               tpl.addn "\tfun {case_name} do"
+                               tpl.addn "\tfun {case_name} is test do"
                                tpl.addn "\t\tassert not_implemented: false # TODO remove once implemented"
                                tpl.addn ""
                                tpl.addn gen_init(mclassdef)
index 295598b..43ac8f5 100644 (file)
@@ -17,7 +17,7 @@ module testing_suite
 
 import testing_base
 import html
-private import annotation
+private import parse_annotations
 private import realtime
 
 redef class ToolContext
@@ -38,29 +38,33 @@ class NitUnitTester
                var toolcontext = mbuilder.toolcontext
                var suite = new TestSuite(mmodule, toolcontext)
                # method to execute before all tests in the module
-               var before_module = mmodule.before_test
-               if before_module != null then
+               for mmethod in mmodule.before_all do
                        toolcontext.modelbuilder.total_tests += 1
-                       suite.before_module = new TestCase(suite, before_module, toolcontext)
+                       suite.before_all.add new TestCase(suite, mmethod, toolcontext)
                end
                # generate all test cases
                for mclassdef in mmodule.mclassdefs do
                        if not mclassdef.is_test then continue
                        if not suite_match_pattern(mclassdef) then continue
                        toolcontext.modelbuilder.total_classes += 1
+
+                       var before = mclassdef.before
+                       var after = mclassdef.after
+
                        for mpropdef in mclassdef.mpropdefs do
                                if not mpropdef isa MMethodDef or not mpropdef.is_test then continue
                                if not case_match_pattern(mpropdef) then continue
                                toolcontext.modelbuilder.total_tests += 1
                                var test = new TestCase(suite, mpropdef, toolcontext)
-                               suite.add_test test
+                               test.before = before
+                               test.after = after
+                               suite.test_cases.add test
                        end
                end
                # method to execute after all tests in the module
-               var after_module = mmodule.after_test
-               if after_module != null then
+               for mmethod in mmodule.after_all do
                        toolcontext.modelbuilder.total_tests += 1
-                       suite.after_module = new TestCase(suite, after_module, toolcontext)
+                       suite.after_all.add new TestCase(suite, mmethod, toolcontext)
                end
                suite.run
                return suite
@@ -113,20 +117,17 @@ class TestSuite
        # List of `TestCase` to be executed in this suite.
        var test_cases = new Array[TestCase]
 
-       # Add a `TestCase` to the suite.
-       fun add_test(case: TestCase) do test_cases.add case
-
-       # Test to be executed before the whole test suite.
-       var before_module: nullable TestCase = null
+       # Tests to be executed before the whole test suite.
+       var before_all = new Array[TestCase]
 
-       # Test to be executed after the whole test suite.
-       var after_module: nullable TestCase = null
+       # Tests to be executed after the whole test suite.
+       var after_all = new Array[TestCase]
 
        # Display test suite status in std-out.
        fun show_status do
                var test_cases = self.test_cases.to_a
-               if before_module != null then test_cases.add before_module.as(not null)
-               if after_module != null then test_cases.add after_module.as(not null)
+               test_cases.add_all before_all
+               test_cases.add_all after_all
                toolcontext.show_unit_status("Test-suite of module " + mmodule.full_name, test_cases)
        end
 
@@ -152,10 +153,7 @@ class TestSuite
                end
                toolcontext.info("Execute test-suite {mmodule.name}", 1)
 
-               var before_module = self.before_module
-               var after_module = self.after_module
-
-               if before_module != null then
+               for before_module in before_all do
                        before_module.run
                        toolcontext.clear_progress_bar
                        toolcontext.show_unit(before_module)
@@ -165,7 +163,7 @@ class TestSuite
                                        toolcontext.clear_progress_bar
                                        toolcontext.show_unit(case)
                                end
-                               if after_module != null then
+                               for after_module in after_all do
                                        after_module.fail "Nitunit Error: before_module test failed"
                                        toolcontext.clear_progress_bar
                                        toolcontext.show_unit(after_module)
@@ -183,7 +181,7 @@ class TestSuite
                        show_status
                end
 
-               if not after_module == null then
+               for after_module in after_all do
                        after_module.run
                        toolcontext.clear_progress_bar
                        toolcontext.show_unit(after_module)
@@ -197,18 +195,16 @@ class TestSuite
        # Write the test unit for `self` in a nit compilable file.
        fun write_to_nit do
                var file = new Template
-               file.addn "intrude import test_suite"
+               file.addn "intrude import core"
                file.addn "import {mmodule.name}\n"
                file.addn "var name = args.first"
-               var before_module = self.before_module
-               if before_module != null then
+               for before_module in before_all do
                        before_module.write_to_nit(file)
                end
                for case in test_cases do
                        case.write_to_nit(file)
                end
-               var after_module = self.after_module
-               if after_module != null then
+               for after_module in after_all do
                        after_module.write_to_nit(file)
                end
                file.write_to_file("{test_file}.nit")
@@ -272,6 +268,12 @@ class TestCase
        # Test method to be compiled and tested.
        var test_method: MMethodDef
 
+       # Cases to execute before this one
+       var before = new Array[MMethodDef]
+
+       # Cases to execute after this one
+       var after = new Array[MMethodDef]
+
        redef fun full_name do return test_method.full_name
 
        redef fun location do return test_method.location
@@ -286,10 +288,14 @@ class TestCase
                if test_method.mproperty.is_toplevel then
                        file.addn "\t{name}"
                else
-                       file.addn "\tvar subject = new {test_method.mclassdef.name}.nitunit"
-                       file.addn "\tsubject.before_test"
+                       file.addn "\tvar subject = new {test_method.mclassdef.name}.intern"
+                       for mmethod in before do
+                               file.addn "\tsubject.{mmethod.name}"
+                       end
                        file.addn "\tsubject.{name}"
-                       file.addn "\tsubject.after_test"
+                       for mmethod in after do
+                               file.addn "\tsubject.{mmethod.name}"
+                       end
                end
                file.addn "end"
        end
@@ -374,54 +380,87 @@ class TestCase
        end
 end
 
-redef class MMethodDef
-       # TODO use annotations?
-
-       # Is the method a test_method?
-       # i.e. begins with "test_"
-       private fun is_test: Bool do return name.has_prefix("test_")
+redef class MClassDef
+       # Methods tagged with `before` in this class definition
+       private fun before: Array[MMethodDef] do
+               var res = new Array[MMethodDef]
+               for mpropdef in mpropdefs do
+                       if mpropdef isa MMethodDef and mpropdef.is_before then
+                               res.add mpropdef
+                       end
+               end
+               var in_hierarchy = self.in_hierarchy
+               if in_hierarchy == null then return res
+               for mclassdef in in_hierarchy.direct_greaters do
+                       res.add_all mclassdef.before
+               end
+               return res
+       end
 
-       # Is the method a "before_module"?
-       private fun is_before_module: Bool do return name == "before_module"
+       # Methods tagged with `before_all` in this class definition
+       private fun before_all: Array[MMethodDef] do
+               var res = new Array[MMethodDef]
+               for mpropdef in mpropdefs do
+                       if mpropdef isa MMethodDef and mpropdef.is_before_all then
+                               res.add mpropdef
+                       end
+               end
+               var in_hierarchy = self.in_hierarchy
+               if in_hierarchy == null then return res
+               for mclassdef in in_hierarchy.direct_greaters do
+                       res.add_all mclassdef.before_all
+               end
+               return res
+       end
 
-       # Is the method a "after_module"?
-       private fun is_after_module: Bool do return name == "after_module"
-end
+       # Methods tagged with `after` in this class definition
+       private fun after: Array[MMethodDef] do
+               var res = new Array[MMethodDef]
+               for mpropdef in mpropdefs do
+                       if mpropdef isa MMethodDef and mpropdef.is_after then
+                               res.add mpropdef
+                       end
+               end
+               var in_hierarchy = self.in_hierarchy
+               if in_hierarchy == null then return res
+               for mclassdef in in_hierarchy.direct_greaters do
+                       res.add_all mclassdef.after
+               end
+               return res
+       end
 
-redef class MClassDef
-       # Is the class a TestClass?
-       # i.e. is a subclass of `TestSuite`
-       private fun is_test: Bool do
+       # Methods tagged with `after_all` in this class definition
+       private fun after_all: Array[MMethodDef] do
+               var res = new Array[MMethodDef]
+               for mpropdef in mpropdefs do
+                       if mpropdef isa MMethodDef and mpropdef.is_after_all then
+                               res.add mpropdef
+                       end
+               end
                var in_hierarchy = self.in_hierarchy
-               if in_hierarchy == null then return false
-               for sup in in_hierarchy.greaters do
-                       if sup.name == "TestSuite" then return true
+               if in_hierarchy == null then return res
+               for mclassdef in in_hierarchy.direct_greaters do
+                       res.add_all mclassdef.after_all
                end
-               return false
+               return res
        end
 end
 
 redef class MModule
-       # "before_module" method for this module.
-       private fun before_test: nullable MMethodDef do
+       # Methods tagged with `before_all` at the module level (in `Sys`)
+       private fun before_all: Array[MMethodDef] do
                for mclassdef in mclassdefs do
-                       if not mclassdef.name == "Sys" then continue
-                       for mpropdef in mclassdef.mpropdefs do
-                               if mpropdef isa MMethodDef and mpropdef.is_before_module then return mpropdef
-                       end
+                       if mclassdef.name == "Sys" then return mclassdef.before_all
                end
-               return null
+               return new Array[MMethodDef]
        end
 
-       # "after_module" method for this module.
-       private fun after_test: nullable MMethodDef do
+       # Methods tagged with `after_all` at the module level (in `Sys`)
+       private fun after_all: Array[MMethodDef] do
                for mclassdef in mclassdefs do
-                       if not mclassdef.name == "Sys" then continue
-                       for mpropdef in mclassdef.mpropdefs do
-                               if mpropdef isa MMethodDef and mpropdef.is_after_module then return mpropdef
-                       end
+                       if mclassdef.name == "Sys" then return mclassdef.after_all
                end
-               return null
+               return new Array[MMethodDef]
        end
 end
 
@@ -438,7 +477,7 @@ redef class ModelBuilder
        # Run NitUnit test suite for `mmodule` (if it is one).
        fun test_unit(mmodule: MModule): nullable HTMLTag do
                # is the module a test_suite?
-               if get_mmodule_annotation("test_suite", mmodule) == null then return null
+               if not mmodule.is_test then return null
                toolcontext.info("nitunit: test-suite {mmodule}", 2)
 
                var tester = new NitUnitTester(self)
index 678f514..4d4eac9 100644 (file)
@@ -14,7 +14,7 @@
 
 module api_catalog
 
-import web_base
+import api_model
 import catalog
 
 redef class NitwebConfig
@@ -26,126 +26,400 @@ redef class NitwebConfig
        #
        # This method should be called at nitweb startup.
        fun build_catalog do
-               var catalog = new Catalog(modelbuilder)
-               for mpackage in model.mpackages do
-                       catalog.deps.add_node(mpackage)
-                       for mgroup in mpackage.mgroups do
-                               for mmodule in mgroup.mmodules do
-                                       for imported in mmodule.in_importation.direct_greaters do
-                                               var ip = imported.mpackage
-                                               if ip == null or ip == mpackage then continue
-                                               catalog.deps.add_edge(mpackage, ip)
-                                       end
-                               end
-                       end
-                       catalog.git_info(mpackage)
-                       catalog.package_page(mpackage)
-               end
-               self.catalog = catalog
+               self.catalog = new Catalog(modelbuilder)
+               self.catalog.build_catalog(model.mpackages)
        end
 end
 
 redef class APIRouter
        redef init do
                super
-               use("/catalog/highlighted", new APICatalogHighLighted(config))
-               use("/catalog/required", new APICatalogMostRequired(config))
-               use("/catalog/bytags", new APICatalogByTags(config))
-               use("/catalog/contributors", new APICatalogContributors(config))
+               use("/catalog/packages/", new APICatalogPackages(config))
                use("/catalog/stats", new APICatalogStats(config))
+
+               use("/catalog/tags", new APICatalogTags(config))
+               use("/catalog/tag/:tid", new APICatalogTag(config))
+
+               use("/catalog/person/:pid", new APICatalogPerson(config))
+               use("/catalog/person/:pid/maintaining", new APICatalogMaintaining(config))
+               use("/catalog/person/:pid/contributing", new APICatalogContributing(config))
        end
 end
 
 abstract class APICatalogHandler
        super APIHandler
 
-       # List the 10 best packages from `cpt`
-       fun list_best(cpt: Counter[MPackage]): JsonArray do
-               var res = new JsonArray
-               var best = cpt.sort
-               for i in [1..10] do
-                       if i > best.length then break
-                       res.add best[best.length-i]
-               end
-               return res
-       end
+       # Sorter used to sort packages
+       #
+       # Sorting is based on mpackage score.
+       var mpackages_sorter = new CatalogScoreSorter(config.catalog) is lazy
+end
 
-       # List packages by group.
-       fun list_by(map: MultiHashMap[Object, MPackage]): JsonObject do
-               var res = new JsonObject
-               var keys = map.keys.to_a
-               alpha_comparator.sort(keys)
-               for k in keys do
-                       var projs = map[k].to_a
-                       alpha_comparator.sort(projs)
-                       res[k.to_s.html_escape] = new JsonArray.from(projs)
-               end
-               return res
+# Get all the packages from the catalog using pagination
+#
+# `GET /packages?p=1&n=10`: get the list of catalog by page
+class APICatalogPackages
+       super APICatalogHandler
+
+       redef fun get(req, res) do
+               var page = req.int_arg("p")
+               var limit = req.int_arg("n")
+               var mpackages = config.catalog.mpackages.values.to_a
+               mpackages_sorter.sort(mpackages)
+               var response = new JsonArray.from(mpackages)
+               res.json paginate(response, response.length, page, limit)
        end
 end
 
+# Get the catalog statistics
+#
+# `GET /stats`: return the catalog statistics
 class APICatalogStats
        super APICatalogHandler
 
        redef fun get(req, res) do
-               var obj = new JsonObject
-               obj["packages"] = config.model.mpackages.length
-               obj["maintainers"] = config.catalog.maint2proj.length
-               obj["contributors"] = config.catalog.contrib2proj.length
-               obj["modules"] = config.catalog.mmodules.sum
-               obj["classes"] = config.catalog.mclasses.sum
-               obj["methods"] = config.catalog.mmethods.sum
-               obj["loc"] = config.catalog.loc.sum
-               res.json obj
+               res.json config.catalog.catalog_stats
        end
 end
 
-class APICatalogHighLighted
+# Get all the tags from the catalog
+#
+# `GET /tags`: the list of tags associated with their number of packages
+class APICatalogTags
        super APICatalogHandler
 
-       redef fun get(req, res) do res.json list_best(config.catalog.score)
+       # Sorter to sort tags alphabetically
+       var tags_sorter = new CatalogTagsSorter
+
+       redef fun get(req, res) do
+               var obj = new JsonObject
+
+               var tags = config.catalog.tag2proj.keys.to_a
+               tags_sorter.sort(tags)
+
+               for tag in tags do
+                       if not config.catalog.tag2proj.has_key(tag) then continue
+                       obj[tag] = config.catalog.tag2proj[tag].length
+               end
+               res.json obj
+       end
 end
 
-class APICatalogMostRequired
+# Get the packages related to a tag
+#
+# `GET /tag/:tid?p=1&n=10`: return a paginated list of packages
+class APICatalogTag
        super APICatalogHandler
 
        redef fun get(req, res) do
-               if config.catalog.deps.not_empty then
-                       var reqs = new Counter[MPackage]
-                       for p in config.model.mpackages do
-                               reqs[p] = config.catalog.deps[p].smallers.length - 1
-                       end
-                       res.json list_best(reqs)
+               var page = req.int_arg("p")
+               var limit = req.int_arg("n")
+               var id = req.param("tid")
+               if id == null then
+                       res.api_error(400, "Missing tag")
+                       return
+               end
+               id = id.from_percent_encoding
+               if not config.catalog.tag2proj.has_key(id) then
+                       res.api_error(404, "Tag not found")
                        return
                end
-               res.json new JsonArray
+               var obj = new JsonObject
+               obj["tag"] = id
+               var mpackages = config.catalog.tag2proj[id]
+               mpackages_sorter.sort(mpackages)
+               var response = new JsonArray.from(mpackages)
+               obj["packages"] = paginate(response, response.length, page, limit)
+               res.json obj
        end
 end
 
-class APICatalogByTags
+# Get a person existing in the catalog
+#
+# `GET /person/:pid`: get the person with `pid`
+class APICatalogPerson
        super APICatalogHandler
 
-       redef fun get(req, res) do res.json list_by(config.catalog.tag2proj)
+       # Get the person with `:pid` or throw a 404 error
+       fun get_person(req: HttpRequest, res: HttpResponse): nullable Person do
+               var id = req.param("pid")
+               if id == null then
+                       res.api_error(400, "Missing package full_name")
+                       return null
+               end
+               id = id.from_percent_encoding
+               if not config.catalog.name2person.has_key(id) then
+                       res.api_error(404, "Person not found")
+                       return null
+               end
+               return config.catalog.name2person[id]
+       end
+
+       redef fun get(req, res) do
+               var person = get_person(req, res)
+               if person == null then return
+               res.json person
+       end
 end
 
-class APICatalogContributors
-       super APICatalogHandler
+# Get the list of mpackages maintained by a person
+#
+# `GET /person/:pid/maintaining?p=1&n=10`: return a paginated list of packages
+class APICatalogMaintaining
+       super APICatalogPerson
 
        redef fun get(req, res) do
-               var obj = new JsonObject
-               obj["maintainers"] = new JsonArray.from(config.catalog.maint2proj.keys)
-               obj["contributors"] = new JsonArray.from(config.catalog.contrib2proj.keys)
-               res.json obj
+               var person = get_person(req, res)
+               if person == null then return
+
+               var page = req.int_arg("p")
+               var limit = req.int_arg("n")
+               var array = new Array[MPackage]
+               if config.catalog.maint2proj.has_key(person) then
+                       array = config.catalog.maint2proj[person].to_a
+               end
+               mpackages_sorter.sort(array)
+               var response = new JsonArray.from(array)
+               res.json paginate(response, response.length, page, limit)
+       end
+end
+
+# Get the list of mpackages contributed by a person
+#
+# `GET /person/:pid/contributing?p=1&n=10`: return a paginated list of packages
+class APICatalogContributing
+       super APICatalogPerson
+
+       redef fun get(req, res) do
+               var person = get_person(req, res)
+               if person == null then return
+
+               var page = req.int_arg("p")
+               var limit = req.int_arg("n")
+               var array = new Array[MPackage]
+               if config.catalog.contrib2proj.has_key(person) then
+                       array = config.catalog.contrib2proj[person].to_a
+               end
+               mpackages_sorter.sort(array)
+               var response = new JsonArray.from(array)
+               res.json paginate(response, response.length, page, limit)
+       end
+end
+
+redef class APIEntity
+       redef fun get(req, res) do
+               var mentity = mentity_from_uri(req, res)
+               if mentity == null then return
+
+               # Special case for packages (catalog view)
+               if mentity isa MPackage then
+                       res.raw_json mentity.to_full_catalog_json(plain=true, config.catalog)
+               else
+                       res.raw_json mentity.to_full_json
+               end
+       end
+end
+
+redef class APISearch
+       super APICatalogHandler
+
+       redef fun search(query, limit) do
+               var index = config.view.index
+
+               # lookup by name prefix
+               var matches = index.find_by_name_prefix(query).uniq.
+                       sort(lname_sorter, name_sorter, kind_sorter)
+               matches = matches.rerank.sort(vis_sorter, score_sorter)
+
+               # lookup by tags
+               var malus = matches.length
+               if config.catalog.tag2proj.has_key(query) then
+                       for mpackage in config.catalog.tag2proj[query] do
+                               matches.add new IndexMatch(mpackage, malus)
+                               malus += 1
+                       end
+                       matches = matches.uniq.rerank.sort(vis_sorter, score_sorter)
+               end
+
+               # lookup by full_name prefix
+               malus = matches.length
+               var full_matches = new IndexMatches
+               for match in index.find_by_full_name_prefix(query).
+                       sort(lfname_sorter, fname_sorter) do
+                       match.score += 1
+                       full_matches.add match
+               end
+               matches = matches.uniq
+
+               # lookup by similarity
+               malus = matches.length
+               var sim_matches = new IndexMatches
+               for match in index.find_by_similarity(query).sort(score_sorter, lname_sorter, name_sorter) do
+                       if match.score > query.length then break
+                       match.score += 1
+                       sim_matches.add match
+               end
+               matches.add_all sim_matches
+               matches = matches.uniq
+               return matches.rerank.sort(vis_sorter, score_sorter).mentities
+       end
+
+       private var score_sorter = new ScoreComparator
+       private var vis_sorter = new VisibilityComparator
+       private var name_sorter = new NameComparator
+       private var lname_sorter = new NameLengthComparator
+       private var fname_sorter = new FullNameComparator
+       private var lfname_sorter = new FullNameLengthComparator
+       private var kind_sorter = new MEntityComparator
+end
+
+redef class Catalog
+
+       # Build the catalog from `mpackages`
+       fun build_catalog(mpackages: Array[MPackage]) do
+               # Compute the poset
+               for p in mpackages do
+                       var g = p.root
+                       assert g != null
+                       modelbuilder.scan_group(g)
+
+                       deps.add_node(p)
+                       for gg in p.mgroups do for m in gg.mmodules do
+                               for im in m.in_importation.direct_greaters do
+                                       var ip = im.mpackage
+                                       if ip == null or ip == p then continue
+                                       deps.add_edge(p, ip)
+                               end
+                       end
+               end
+               # Build the catalog
+               for mpackage in mpackages do
+                       package_page(mpackage)
+                       git_info(mpackage)
+                       mpackage_stats(mpackage)
+               end
+       end
+end
+
+redef class MPackageMetadata
+       serialize
+
+       redef fun core_serialize_to(v) do
+               super
+               v.serialize_attribute("license", license)
+               v.serialize_attribute("maintainers", maintainers)
+               v.serialize_attribute("contributors", contributors)
+               v.serialize_attribute("tags", tags)
+               v.serialize_attribute("tryit", tryit)
+               v.serialize_attribute("apk", apk)
+               v.serialize_attribute("homepage", homepage)
+               v.serialize_attribute("browse", browse)
+               v.serialize_attribute("git", git)
+               v.serialize_attribute("issues", issues)
+               v.serialize_attribute("first_date", first_date)
+               v.serialize_attribute("last_date", last_date)
+       end
+end
+
+# Catalog statistics
+redef class CatalogStats
+       serialize
+
+       redef fun core_serialize_to(v) do
+               super
+               v.serialize_attribute("packages", packages)
+               v.serialize_attribute("maintainers", maintainers)
+               v.serialize_attribute("contributors", contributors)
+               v.serialize_attribute("tags", tags)
+               v.serialize_attribute("modules", modules)
+               v.serialize_attribute("classes", classes)
+               v.serialize_attribute("methods", methods)
+               v.serialize_attribute("loc", loc)
+       end
+end
+
+# MPackage statistics for the catalog
+redef class MPackageStats
+       serialize
+
+       redef fun core_serialize_to(v) do
+               super
+               v.serialize_attribute("mmodules", mmodules)
+               v.serialize_attribute("mclasses", mclasses)
+               v.serialize_attribute("mmethods", mmethods)
+               v.serialize_attribute("loc", loc)
+               v.serialize_attribute("errors", errors)
+               v.serialize_attribute("warnings", warnings)
+               v.serialize_attribute("warnings_per_kloc", warnings_per_kloc)
+               v.serialize_attribute("documentation_score", documentation_score)
+               v.serialize_attribute("commits", commits)
+               v.serialize_attribute("score", score)
        end
 end
 
 redef class Person
-       super Serializable
+       serialize
 
        redef fun core_serialize_to(v) do
+               super
                v.serialize_attribute("name", name)
                v.serialize_attribute("email", email)
-               v.serialize_attribute("page", page)
-               v.serialize_attribute("hash", (email or else "").md5.to_lower)
+               v.serialize_attribute("gravatar", gravatar)
+       end
+end
+
+redef class MPackage
+       # Serialize the full catalog version of `self` to JSON
+       #
+       # See: `FullCatalogSerializer`
+       fun to_full_catalog_json(catalog: Catalog, plain, pretty: nullable Bool): String do
+               var stream = new StringWriter
+               var serializer = new FullCatalogSerializer(stream, catalog)
+               serializer.plain_json = plain or else false
+               serializer.pretty_json = pretty or else false
+               serializer.serialize self
+               stream.close
+               return stream.to_s
+       end
+
+       redef fun core_serialize_to(v) do
+               super
+               v.serialize_attribute("metadata", metadata)
+               if v isa FullCatalogSerializer then
+                       v.serialize_attribute("stats", v.catalog.mpackages_stats[self])
+
+                       var parents = v.catalog.deps[self].direct_greaters.to_a
+                       v.serialize_attribute("dependencies", v.deps_to_json(parents))
+                       var children = v.catalog.deps[self].direct_smallers.to_a
+                       v.serialize_attribute("clients", v.deps_to_json(children))
+               end
+       end
+end
+
+# CatalogSerializer decorate the Package JSON with full catalog metadata
+#
+# See MEntity::to_full_catalog_json.
+class FullCatalogSerializer
+       super FullJsonSerializer
+
+       # Catalog used to decorate the MPackages
+       var catalog: Catalog
+
+       private fun deps_to_json(mpackages: Array[MPackage]): JsonArray do
+               var res = new JsonArray
+               for mpackage in mpackages do
+                       res.add dep_to_json(mpackage)
+               end
+               return res
+       end
+
+       private fun dep_to_json(mpackage: MPackage): JsonObject do
+               var obj = new JsonObject
+               obj["name"] = mpackage.name
+               var mdoc = mpackage.mdoc_or_fallback
+               if mdoc != null then
+                       obj["synopsis"] = mdoc.synopsis.write_to_string
+               end
+               return obj
        end
 end
index 4f1a148..328eff9 100644 (file)
@@ -104,13 +104,19 @@ class APISearch
        super APIList
 
        redef fun get(req, res) do
-               var q = req.string_arg("q")
-               if q == null then
-                       res.json new JsonArray
+               var query = req.string_arg("q")
+               if query == null then
+                       res.api_error(400, "Missing search string")
                        return
                end
-               var n = req.int_arg("n")
-               res.json new JsonArray.from(config.view.find(q, n))
+               var page = req.int_arg("p")
+               var limit = req.int_arg("n")
+               var response = new JsonArray.from(search(query, limit))
+               res.json paginate(response, response.length, page, limit)
+       end
+
+       fun search(query: String, limit: nullable Int): Array[MEntity] do
+               return config.view.find(query)
        end
 end
 
index c863061..ac63f53 100644 (file)
@@ -47,7 +47,7 @@ class NitwebConfig
                view.include_fictive = true
                view.include_empty_doc = true
                view.include_attribute = true
-               view.include_test_suite = true
+               view.include_test = true
                return view
        end
 end
@@ -82,6 +82,46 @@ abstract class APIHandler
                end
                return mentity
        end
+
+       # Paginate a json array
+       #
+       # Returns only a subset of `results` depending on the current `page` and the
+       # number of elements to return set by `limit`.
+       #
+       # Transforms the json array into an object:
+       # ~~~json
+       # {
+       #       "page": 2,
+       #       "limit": 10,
+       #       "results: [ ... ],
+       #       "max": 5,
+       #       "total": 49
+       # }
+       # ~~~
+       fun paginate(results: JsonArray, count: Int, page, limit: nullable Int): JsonObject do
+               if page == null or page <= 0 then page = 1
+               if limit == null or limit <= 0 then limit = 20
+
+               var max = count / limit
+               if max == 0 then
+                       page = 1
+                       max = 1
+               else if page > max then
+                       page = max
+               end
+
+               var lstart = (page - 1) * limit
+               var lend = limit
+               if lstart + lend > count then lend = count - lstart
+
+               var res = new JsonObject
+               res["page"] = page
+               res["limit"] = limit
+               res["results"] = new JsonArray.from(results.subarray(lstart, lend))
+               res["max"] = max
+               res["total"] = count
+               return res
+       end
 end
 
 # A Rooter dedicated to APIHandlers.
diff --git a/tests/sav/nitce/test_inspect_serialization.res b/tests/sav/nitce/test_inspect_serialization.res
new file mode 100644 (file)
index 0000000..2ee51e5
--- /dev/null
@@ -0,0 +1,49 @@
+# Custom:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Inspect:
+<A#0 b:true, c:'a', f:0.123, i:1234, serialization_specific_name:"asdf", n:null>
+
+# Custom:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Inspect:
+<B#0 b:false, c:'b', f:123.123, i:2345, serialization_specific_name:"hjkl", n:…>
+
+# Custom:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# Inspect:
+<C#0 a:<A#1>, b:<B#2>, aa:<A#1>>
+
+# Custom:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# Inspect:
+<D#0 b:false, c:'b', f:123.123, i:2345, serialization_specific_name:"new line â€¦>
+
+# Custom:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Inspect:
+<E#0 a:<Array#1>, b:<Array#2>>
+
+# Custom:
+<F: 2222>
+
+# Inspect:
+<F#0 n:2222>
+
+# Custom:
+<F: 33.33>
+
+# Inspect:
+<F#0 n:33.33>
+
+# Custom:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# Inspect:
+<G#0 hs:<HashSet#1>, s:<ArraySet#2>, hm:<HashMap#3>, am:<ArrayMap#4>>
+
diff --git a/tests/sav/nitce/test_msgpack_deserialization_alt2.res b/tests/sav/nitce/test_msgpack_deserialization_alt2.res
new file mode 100644 (file)
index 0000000..21e30b8
--- /dev/null
@@ -0,0 +1,170 @@
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+       "b": true,
+       "c": "a",
+       "f": 0.123,
+       "i": 1234,
+       "serialization_specific_name": "asdf",
+       "n": null
+}
+
+# 4. Back in Nit (no metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "hjkl",
+       "n": 12,
+       "ii": 1111,
+       "ss": "qwer"
+}
+
+# 4. Back in Nit (no metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x83\xA1\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+       "a": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       },
+       "b": {
+               "b": false,
+               "c": "b",
+               "f": 123.123,
+               "i": 2345,
+               "serialization_specific_name": "hjkl",
+               "n": 12,
+               "ii": 1111,
+               "ss": "qwer"
+       },
+       "aa": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       }
+}
+
+# 4. Back in Nit (no metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 2. MsgPack:
+\x89\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xC0
+
+# 3. JSON:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "new line ->\n<-",
+       "n": null,
+       "ii": 1111,
+       "ss": "\tf\"\r\\/",
+       "d": null
+}
+
+# 4. Back in Nit (no metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> false>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x82\xA1\x61\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\xA1\x62\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+{
+       "a": ["hello", 1234, 123.4],
+       "b": ["hella", 2345, 234.5]
+}
+
+# 4. Back in Nit (no metadata):
+<E: a: ; b: >
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x81\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+{
+       "n": 2222
+}
+
+# 4. Back in Nit (no metadata):
+null
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x81\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+{
+       "n": 33.33
+}
+
+# 4. Back in Nit (no metadata):
+null
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x84\xA2\x68\x73\x92\xFF\x00\xA1\x73\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\xA2\x68\x6D\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\xA2\x61\x6D\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+{
+       "hs": [-1, 0],
+       "s": ["one", "two"],
+       "hm": {
+               "one": 1,
+               "two": 2
+       },
+       "am": {
+               "three": "3",
+               "four": "4"
+       }
+}
+
+# 4. Back in Nit (no metadata):
+<G: hs: ; s: ; hm: one. 1, two. 2; am: three. 3, four. 4>
+
diff --git a/tests/sav/nitce/test_msgpack_deserialization_alt3.res b/tests/sav/nitce/test_msgpack_deserialization_alt3.res
new file mode 100644 (file)
index 0000000..8cd614f
--- /dev/null
@@ -0,0 +1,231 @@
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x41\x86\xA1\x62\xC3\xA1\x63\xD4\x7C\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "A", {
+       "b": true,
+       "c": {
+               "typ": 124,
+               "data": [97]
+       },
+       "f": 0.123,
+       "i": 1234,
+       "serialization_specific_name": "asdf",
+       "n": null
+}]
+
+# 4. Back in Nit (with metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x42\x88\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "B", {
+       "b": false,
+       "c": {
+               "typ": 124,
+               "data": [98]
+       },
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "hjkl",
+       "n": 12,
+       "ii": 1111,
+       "ss": "qwer"
+}]
+
+# 4. Back in Nit (with metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x43\x83\xA1\x61\x93\xD4\x7B\x01\xA1\x41\x86\xA1\x62\xC3\xA1\x63\xD4\x7C\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x93\xD4\x7B\x02\xA1\x42\x88\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\xD4\x7D\x01
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "C", {
+       "a": [{
+               "typ": 123,
+               "data": [1]
+       }, "A", {
+               "b": true,
+               "c": {
+                       "typ": 124,
+                       "data": [97]
+               },
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       }],
+       "b": [{
+               "typ": 123,
+               "data": [2]
+       }, "B", {
+               "b": false,
+               "c": {
+                       "typ": 124,
+                       "data": [98]
+               },
+               "f": 123.123,
+               "i": 2345,
+               "serialization_specific_name": "hjkl",
+               "n": 12,
+               "ii": 1111,
+               "ss": "qwer"
+       }],
+       "aa": {
+               "typ": 125,
+               "data": [1]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x44\x89\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xD4\x7D\x00
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "D", {
+       "b": false,
+       "c": {
+               "typ": 124,
+               "data": [98]
+       },
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "new line ->\n<-",
+       "n": null,
+       "ii": 1111,
+       "ss": "\tf\"\r\\/",
+       "d": {
+               "typ": 125,
+               "data": [0]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x45\x82\xA1\x61\x93\xD4\x7B\x01\xA5\x41\x72\x72\x61\x79\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\xA1\x62\x93\xD4\x7B\x02\xA5\x41\x72\x72\x61\x79\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "E", {
+       "a": [{
+               "typ": 123,
+               "data": [1]
+       }, "Array", ["hello", 1234, 123.4]],
+       "b": [{
+               "typ": 123,
+               "data": [2]
+       }, "Array", ["hella", 2345, 234.5]]
+}]
+
+# 4. Back in Nit (with metadata):
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x46\x81\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "F", {
+       "n": 2222
+}]
+
+# 4. Back in Nit (with metadata):
+null
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x46\x81\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "F", {
+       "n": 33.33
+}]
+
+# 4. Back in Nit (with metadata):
+null
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x47\x84\xA2\x68\x73\x93\xD4\x7B\x01\xA7\x48\x61\x73\x68\x53\x65\x74\x92\xFF\x00\xA1\x73\x93\xD4\x7B\x02\xA8\x41\x72\x72\x61\x79\x53\x65\x74\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\xA2\x68\x6D\x93\xD4\x7B\x03\xA7\x48\x61\x73\x68\x4D\x61\x70\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\xA2\x61\x6D\x93\xD4\x7B\x04\xA8\x41\x72\x72\x61\x79\x4D\x61\x70\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "G", {
+       "hs": [{
+               "typ": 123,
+               "data": [1]
+       }, "HashSet", [-1, 0]],
+       "s": [{
+               "typ": 123,
+               "data": [2]
+       }, "ArraySet", ["one", "two"]],
+       "hm": [{
+               "typ": 123,
+               "data": [3]
+       }, "HashMap", {
+               "one": 1,
+               "two": 2
+       }],
+       "am": [{
+               "typ": 123,
+               "data": [4]
+       }, "ArrayMap", {
+               "three": "3",
+               "four": "4"
+       }]
+}]
+
+# 4. Back in Nit (with metadata):
+<G: hs: ; s: ; hm: ; am: >
+
diff --git a/tests/sav/nitce/test_msgpack_deserialization_alt4.res b/tests/sav/nitce/test_msgpack_deserialization_alt4.res
new file mode 100644 (file)
index 0000000..a05f017
--- /dev/null
@@ -0,0 +1,279 @@
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x41\x86\x92\xD4\x7B\x02\xA1\x62\xC3\x92\xD4\x7B\x03\xA1\x63\xD4\x7C\x61\x92\xD4\x7B\x04\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\x92\xD4\x7B\x05\xA1\x69\xCD\x04\xD2\x92\xD4\x7B\x06\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\x92\xD4\x7B\x07\xA1\x6E\xC0
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "A"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,b]": true,
+       "[<MsgPackExt typ: 0x7b, data: \\x03>,c]": {
+               "typ": 124,
+               "data": [97]
+       },
+       "[<MsgPackExt typ: 0x7b, data: \\x04>,f]": 0.123,
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,i]": 1234,
+       "[<MsgPackExt typ: 0x7b, data: \\x06>,serialization_specific_name]": "asdf",
+       "[<MsgPackExt typ: 0x7b, data: \\x07>,n]": null
+}]
+
+# 4. Back in Nit (with metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x42\x88\x92\xD4\x7B\x02\xA1\x62\xC2\x92\xD4\x7B\x03\xA1\x63\xD4\x7C\x62\x92\xD4\x7B\x04\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\x92\xD4\x7B\x05\xA1\x69\xCD\x09\x29\x92\xD4\x7B\x06\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\x92\xD4\x7B\x07\xA1\x6E\x0C\x92\xD4\x7B\x08\xA2\x69\x69\xCD\x04\x57\x92\xD4\x7B\x09\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "B"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,b]": false,
+       "[<MsgPackExt typ: 0x7b, data: \\x03>,c]": {
+               "typ": 124,
+               "data": [98]
+       },
+       "[<MsgPackExt typ: 0x7b, data: \\x04>,f]": 123.123,
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,i]": 2345,
+       "[<MsgPackExt typ: 0x7b, data: \\x06>,serialization_specific_name]": "hjkl",
+       "[<MsgPackExt typ: 0x7b, data: \\x07>,n]": 12,
+       "[<MsgPackExt typ: 0x7b, data: \\x08>,ii]": 1111,
+       "[<MsgPackExt typ: 0x7b, data: \\x09>,ss]": "qwer"
+}]
+
+# 4. Back in Nit (with metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x43\x83\x92\xD4\x7B\x02\xA1\x61\x93\xD4\x7B\x03\x92\xD4\x7B\x04\xA1\x41\x86\x92\xD4\x7B\x05\xA1\x62\xC3\x92\xD4\x7B\x06\xA1\x63\xD4\x7C\x61\x92\xD4\x7B\x07\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\x92\xD4\x7B\x08\xA1\x69\xCD\x04\xD2\x92\xD4\x7B\x09\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\x92\xD4\x7B\x0A\xA1\x6E\xC0\xD4\x7D\x05\x93\xD4\x7B\x0B\x92\xD4\x7B\x0C\xA1\x42\x88\xD4\x7D\x05\xC2\xD4\x7D\x06\xD4\x7C\x62\xD4\x7D\x07\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xD4\x7D\x08\xCD\x09\x29\xD4\x7D\x09\xA4\x68\x6A\x6B\x6C\xD4\x7D\x0A\x0C\x92\xD4\x7B\x0D\xA2\x69\x69\xCD\x04\x57\x92\xD4\x7B\x0E\xA2\x73\x73\xA4\x71\x77\x65\x72\x92\xD4\x7B\x0F\xA2\x61\x61\xD4\x7D\x03
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "C"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,a]": [{
+               "typ": 123,
+               "data": [3]
+       }, [{
+               "typ": 123,
+               "data": [4]
+       }, "A"], {
+               "[<MsgPackExt typ: 0x7b, data: \\x05>,b]": true,
+               "[<MsgPackExt typ: 0x7b, data: \\x06>,c]": {
+                       "typ": 124,
+                       "data": [97]
+               },
+               "[<MsgPackExt typ: 0x7b, data: \\x07>,f]": 0.123,
+               "[<MsgPackExt typ: 0x7b, data: \\x08>,i]": 1234,
+               "[<MsgPackExt typ: 0x7b, data: \\x09>,serialization_specific_name]": "asdf",
+               "[<MsgPackExt typ: 0x7b, data: \\x0A>,n]": null
+       }],
+       "<MsgPackExt typ: 0x7d, data: \\x05>": [{
+               "typ": 123,
+               "data": [11]
+       }, [{
+               "typ": 123,
+               "data": [12]
+       }, "B"], {
+               "<MsgPackExt typ: 0x7d, data: \\x05>": false,
+               "<MsgPackExt typ: 0x7d, data: \\x06>": {
+                       "typ": 124,
+                       "data": [98]
+               },
+               "<MsgPackExt typ: 0x7d, data: \\x07>": 123.123,
+               "<MsgPackExt typ: 0x7d, data: \\x08>": 2345,
+               "<MsgPackExt typ: 0x7d, data: \\x09>": "hjkl",
+               "<MsgPackExt typ: 0x7d, data: \\x0A>": 12,
+               "[<MsgPackExt typ: 0x7b, data: \\x0D>,ii]": 1111,
+               "[<MsgPackExt typ: 0x7b, data: \\x0E>,ss]": "qwer"
+       }],
+       "[<MsgPackExt typ: 0x7b, data: \\x0F>,aa]": {
+               "typ": 125,
+               "data": [3]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x44\x89\x92\xD4\x7B\x02\xA1\x62\xC2\x92\xD4\x7B\x03\xA1\x63\xD4\x7C\x62\x92\xD4\x7B\x04\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\x92\xD4\x7B\x05\xA1\x69\xCD\x09\x29\x92\xD4\x7B\x06\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\x92\xD4\x7B\x07\xA1\x6E\xC0\x92\xD4\x7B\x08\xA2\x69\x69\xCD\x04\x57\x92\xD4\x7B\x09\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\x92\xD4\x7B\x0A\xA1\x64\xD4\x7D\x00
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "D"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,b]": false,
+       "[<MsgPackExt typ: 0x7b, data: \\x03>,c]": {
+               "typ": 124,
+               "data": [98]
+       },
+       "[<MsgPackExt typ: 0x7b, data: \\x04>,f]": 123.123,
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,i]": 2345,
+       "[<MsgPackExt typ: 0x7b, data: \\x06>,serialization_specific_name]": "new line ->\n<-",
+       "[<MsgPackExt typ: 0x7b, data: \\x07>,n]": null,
+       "[<MsgPackExt typ: 0x7b, data: \\x08>,ii]": 1111,
+       "[<MsgPackExt typ: 0x7b, data: \\x09>,ss]": "\tf\"\r\\/",
+       "[<MsgPackExt typ: 0x7b, data: \\x0A>,d]": {
+               "typ": 125,
+               "data": [0]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x45\x82\x92\xD4\x7B\x02\xA1\x61\x93\xD4\x7B\x03\x92\xD4\x7B\x04\xA5\x41\x72\x72\x61\x79\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\x92\xD4\x7B\x05\xA1\x62\x93\xD4\x7B\x06\xD4\x7D\x04\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "E"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,a]": [{
+               "typ": 123,
+               "data": [3]
+       }, [{
+               "typ": 123,
+               "data": [4]
+       }, "Array"], ["hello", 1234, 123.4]],
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,b]": [{
+               "typ": 123,
+               "data": [6]
+       }, {
+               "typ": 125,
+               "data": [4]
+       }, ["hella", 2345, 234.5]]
+}]
+
+# 4. Back in Nit (with metadata):
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x46\x81\x92\xD4\x7B\x02\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "F"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,n]": 2222
+}]
+
+# 4. Back in Nit (with metadata):
+null
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x46\x81\x92\xD4\x7B\x02\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "F"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,n]": 33.33
+}]
+
+# 4. Back in Nit (with metadata):
+null
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x47\x84\x92\xD4\x7B\x02\xA2\x68\x73\x93\xD4\x7B\x03\x92\xD4\x7B\x04\xA7\x48\x61\x73\x68\x53\x65\x74\x92\xFF\x00\x92\xD4\x7B\x05\xA1\x73\x93\xD4\x7B\x06\x92\xD4\x7B\x07\xA8\x41\x72\x72\x61\x79\x53\x65\x74\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\x92\xD4\x7B\x08\xA2\x68\x6D\x93\xD4\x7B\x09\x92\xD4\x7B\x0A\xA7\x48\x61\x73\x68\x4D\x61\x70\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\x92\xD4\x7B\x0B\xA2\x61\x6D\x93\xD4\x7B\x0C\x92\xD4\x7B\x0D\xA8\x41\x72\x72\x61\x79\x4D\x61\x70\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "G"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,hs]": [{
+               "typ": 123,
+               "data": [3]
+       }, [{
+               "typ": 123,
+               "data": [4]
+       }, "HashSet"], [-1, 0]],
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,s]": [{
+               "typ": 123,
+               "data": [6]
+       }, [{
+               "typ": 123,
+               "data": [7]
+       }, "ArraySet"], ["one", "two"]],
+       "[<MsgPackExt typ: 0x7b, data: \\x08>,hm]": [{
+               "typ": 123,
+               "data": [9]
+       }, [{
+               "typ": 123,
+               "data": [10]
+       }, "HashMap"], {
+               "one": 1,
+               "two": 2
+       }],
+       "[<MsgPackExt typ: 0x7b, data: \\x0B>,am]": [{
+               "typ": 123,
+               "data": [12]
+       }, [{
+               "typ": 123,
+               "data": [13]
+       }, "ArrayMap"], {
+               "three": "3",
+               "four": "4"
+       }]
+}]
+
+# 4. Back in Nit (with metadata):
+<G: hs: ; s: ; hm: ; am: >
+
diff --git a/tests/sav/niti/fixme/test_msgpack_deserialization_alt2.res b/tests/sav/niti/fixme/test_msgpack_deserialization_alt2.res
new file mode 100644 (file)
index 0000000..a1c58f3
--- /dev/null
@@ -0,0 +1,170 @@
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+       "b": true,
+       "c": "a",
+       "f": 0.123,
+       "i": 1234,
+       "serialization_specific_name": "asdf",
+       "n": null
+}
+
+# 4. Back in Nit (no metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "hjkl",
+       "n": 12,
+       "ii": 1111,
+       "ss": "qwer"
+}
+
+# 4. Back in Nit (no metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x83\xA1\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+       "a": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       },
+       "b": {
+               "b": false,
+               "c": "b",
+               "f": 123.123,
+               "i": 2345,
+               "serialization_specific_name": "hjkl",
+               "n": 12,
+               "ii": 1111,
+               "ss": "qwer"
+       },
+       "aa": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       }
+}
+
+# 4. Back in Nit (no metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 2. MsgPack:
+\x89\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xC0
+
+# 3. JSON:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "new line ->\n<-",
+       "n": null,
+       "ii": 1111,
+       "ss": "\tf\"\r\\/",
+       "d": null
+}
+
+# 4. Back in Nit (no metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> false>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x82\xA1\x61\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\xA1\x62\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+{
+       "a": ["hello", 1234, 123.4],
+       "b": ["hella", 2345, 234.5]
+}
+
+# 4. Back in Nit (no metadata):
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x81\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+{
+       "n": 2222
+}
+
+# 4. Back in Nit (no metadata):
+<F: 2222>
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x81\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+{
+       "n": 33.33
+}
+
+# 4. Back in Nit (no metadata):
+<F: 33.33>
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x84\xA2\x68\x73\x92\xFF\x00\xA1\x73\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\xA2\x68\x6D\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\xA2\x61\x6D\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+{
+       "hs": [-1, 0],
+       "s": ["one", "two"],
+       "hm": {
+               "one": 1,
+               "two": 2
+       },
+       "am": {
+               "three": "3",
+               "four": "4"
+       }
+}
+
+# 4. Back in Nit (no metadata):
+<G: hs: ; s: ; hm: one. 1, two. 2; am: three. 3, four. 4>
+
index f8172b0..6658182 100644 (file)
@@ -19,6 +19,7 @@ redef class Deserializer
                if name == "Array[Error]" then return new Array[Error].from_deserializer(self)
                if name == "StrictHashMap[Int, Object]" then return new StrictHashMap[Int, Object].from_deserializer(self)
                if name == "POSet[String]" then return new POSet[String].from_deserializer(self)
+               if name == "StrictHashMap[Serializable, Int]" then return new StrictHashMap[Serializable, Int].from_deserializer(self)
                if name == "Array[Int]" then return new Array[Int].from_deserializer(self)
                if name == "Array[nullable Object]" then return new Array[nullable Object].from_deserializer(self)
                if name == "HashSet[String]" then return new HashSet[String].from_deserializer(self)
@@ -29,7 +30,6 @@ redef class Deserializer
                if name == "Array[Float]" then return new Array[Float].from_deserializer(self)
                if name == "Array[Object]" then return new Array[Object].from_deserializer(self)
                if name == "Array[Serializable]" then return new Array[Serializable].from_deserializer(self)
-               if name == "StrictHashMap[Serializable, Int]" then return new StrictHashMap[Serializable, Int].from_deserializer(self)
                if name == "POSetElement[String]" then return new POSetElement[String].from_deserializer(self)
                if name == "HashMap[String, POSetElement[String]]" then return new HashMap[String, POSetElement[String]].from_deserializer(self)
                if name == "Array[Match]" then return new Array[Match].from_deserializer(self)
index a71e35b..dcddc6a 100644 (file)
@@ -16,9 +16,9 @@
 ==== Test-suite of module test_test_nitunit::test_test_nitunit | tests: 3
 [OK] test_test_nitunit$TestX$test_foo
 [KO] test_test_nitunit$TestX$test_foo1
-     test_test_nitunit.nit:36,2--40,4: Runtime Error in file nitunit.out/gen_test_test_nitunit.nit
+     test_test_nitunit.nit:35,2--39,4: Runtime Error in file nitunit.out/gen_test_test_nitunit.nit
      Output
-       Runtime error: Assert failed (test_test_nitunit.nit:39)
+       Runtime error: Assert failed (test_test_nitunit.nit:38)
 
 [OK] test_test_nitunit$TestX$test_foo2
 
@@ -32,5 +32,5 @@ Test suites: Classes: 1; Test Cases: 3; Failures: 1
 </system-out></testcase><testcase classname="nitunit.test_nitunit.X" name="foo" time="0.0"><failure message="Compilation error in nitunit.out&#47;test_nitunit-3.nit">nitunit.out&#47;test_nitunit-3.nit:5,8--27: Error: method or variable `undefined_identifier` unknown in `Sys`.
 </failure><system-out>assert undefined_identifier
 </system-out></testcase><testcase classname="nitunit.test_nitunit.X" name="foo1" time="0.0"><failure message="Syntax Error: unexpected operator &#39;!&#39;."></failure><system-out>assert !@#$%^&amp;*()
-</system-out></testcase></testsuite><testsuite package="test_test_nitunit::test_test_nitunit"></testsuite><testsuite package="test_test_nitunit"><testcase classname="nitunit.test_test_nitunit.TestX" name="test_foo" time="0.0"><system-err></system-err></testcase><testcase classname="nitunit.test_test_nitunit.TestX" name="test_foo1" time="0.0"><error message="Runtime Error in file nitunit.out&#47;gen_test_test_nitunit.nit">Runtime error: Assert failed (test_test_nitunit.nit:39)
+</system-out></testcase></testsuite><testsuite package="test_test_nitunit::test_test_nitunit"></testsuite><testsuite package="test_test_nitunit"><testcase classname="nitunit.test_test_nitunit.TestX" name="test_foo" time="0.0"><system-err></system-err></testcase><testcase classname="nitunit.test_test_nitunit.TestX" name="test_foo1" time="0.0"><error message="Runtime Error in file nitunit.out&#47;gen_test_test_nitunit.nit">Runtime error: Assert failed (test_test_nitunit.nit:38)
 </error></testcase><testcase classname="nitunit.test_test_nitunit.TestX" name="test_foo2" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
index 0a8b785..42ee33e 100644 (file)
@@ -1,13 +1,13 @@
 ==== Test-suite of module test_nitunit6::test_nitunit6 | tests: 3
 [KO] test_nitunit6::test_nitunit6$core::Sys$before_module
-     test_nitunit6.nit:27,1--29,3: Runtime Error in file nitunit.out/gen_test_nitunit6.nit
+     test_nitunit6.nit:25,1--27,3: Runtime Error in file nitunit.out/gen_test_nitunit6.nit
      Output
-       Runtime error: Assert failed (test_nitunit6.nit:28)
+       Runtime error: Assert failed (test_nitunit6.nit:26)
 
 [KO] test_nitunit6$TestNitunit6$test_foo
-     test_nitunit6.nit:22,2--24,4: Nitunit Error: before_module test failed
+     test_nitunit6.nit:20,2--22,4: Nitunit Error: before_module test failed
 [KO] test_nitunit6::test_nitunit6$core::Sys$after_module
-     test_nitunit6.nit:31,1--33,3: Nitunit Error: before_module test failed
+     test_nitunit6.nit:29,1--31,3: Nitunit Error: before_module test failed
 
 Docunits: Entities: 5; Documented ones: 0; With nitunits: 0
 Test suites: Classes: 1; Test Cases: 3; Failures: 3
index aa79730..9015b4a 100644 (file)
@@ -2,9 +2,9 @@
 [OK] test_nitunit7::test_nitunit7$core::Sys$before_module
 [OK] test_nitunit7$TestNitunit7$test_foo
 [KO] test_nitunit7::test_nitunit7$core::Sys$after_module
-     test_nitunit7.nit:31,1--33,3: Runtime Error in file nitunit.out/gen_test_nitunit7.nit
+     test_nitunit7.nit:29,1--31,3: Runtime Error in file nitunit.out/gen_test_nitunit7.nit
      Output
-       Runtime error: Assert failed (test_nitunit7.nit:32)
+       Runtime error: Assert failed (test_nitunit7.nit:30)
 
 
 Docunits: Entities: 5; Documented ones: 0; With nitunits: 0
index bc00752..06ef822 100644 (file)
@@ -1,19 +1,18 @@
-module test_test_nitunit is test_suite
+module test_test_nitunit is test
 
-import test_suite
 import test_nitunit
 
 class TestX
-       super TestSuite
+       test
 
-       fun test_foo do
+       fun test_foo is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: X
                subject.foo
        end
 
-       fun test_foo1 do
+       fun test_foo1 is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: X
@@ -22,7 +21,7 @@ class TestX
                subject.foo1(a, b)
        end
 
-       fun test_foo3 do
+       fun test_foo3 is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: X
@@ -33,9 +32,9 @@ class TestX
 end
 
 class TestY
-       super TestSuite
+       test
 
-       fun test_bra do
+       fun test_bra is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -45,7 +44,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_bra_assign do
+       fun test_bra_assign is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -54,7 +53,7 @@ class TestY
                subject[e] = i
        end
 
-       fun test_plus do
+       fun test_plus is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -64,7 +63,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_minus do
+       fun test_minus is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -74,7 +73,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_star do
+       fun test_star is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -84,7 +83,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_slash do
+       fun test_slash is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -94,7 +93,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_percent do
+       fun test_percent is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -104,7 +103,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_unary_minus do
+       fun test_unary_minus is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -113,7 +112,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_equals do
+       fun test_equals is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -123,7 +122,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_not_equals do
+       fun test_not_equals is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -133,7 +132,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_lt do
+       fun test_lt is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -143,7 +142,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_le do
+       fun test_le is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -153,7 +152,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_compare do
+       fun test_compare is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -163,7 +162,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_ge do
+       fun test_ge is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -173,7 +172,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_gt do
+       fun test_gt is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -185,9 +184,9 @@ class TestY
 end
 
 class TestZ
-       super TestSuite
+       test
 
-       fun test_bra do
+       fun test_bra is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Z
@@ -198,7 +197,7 @@ class TestZ
                assert exp == res
        end
 
-       fun test_bra_assign do
+       fun test_bra_assign is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Z
@@ -208,7 +207,7 @@ class TestZ
                subject[i, j] = k
        end
 
-       fun test_foo= do
+       fun test_foo= is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Z
@@ -217,7 +216,7 @@ class TestZ
                subject.foo(i) = j
        end
 
-       fun test_bar= do
+       fun test_bar= is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Z
index e93c6ca..586af7d 100644 (file)
@@ -1,19 +1,18 @@
-module test_test_nitunit is test_suite
+module test_test_nitunit is test
 
-import test_suite
 intrude import test_nitunit
 
 class TestX
-       super TestSuite
+       test
 
-       fun test_foo do
+       fun test_foo is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: X
                subject.foo
        end
 
-       fun test_foo1 do
+       fun test_foo1 is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: X
@@ -22,7 +21,7 @@ class TestX
                subject.foo1(a, b)
        end
 
-       fun test_foo2 do
+       fun test_foo2 is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: X
@@ -31,7 +30,7 @@ class TestX
                assert exp == res
        end
 
-       fun test_foo3 do
+       fun test_foo3 is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: X
@@ -40,7 +39,7 @@ class TestX
                assert exp == res
        end
 
-       fun test_foo3= do
+       fun test_foo3= is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: X
@@ -50,9 +49,9 @@ class TestX
 end
 
 class TestY
-       super TestSuite
+       test
 
-       fun test_bra do
+       fun test_bra is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -62,7 +61,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_bra_assign do
+       fun test_bra_assign is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -71,7 +70,7 @@ class TestY
                subject[e] = i
        end
 
-       fun test_plus do
+       fun test_plus is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -81,7 +80,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_minus do
+       fun test_minus is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -91,7 +90,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_star do
+       fun test_star is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -101,7 +100,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_slash do
+       fun test_slash is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -111,7 +110,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_percent do
+       fun test_percent is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -121,7 +120,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_unary_minus do
+       fun test_unary_minus is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -130,7 +129,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_equals do
+       fun test_equals is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -140,7 +139,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_not_equals do
+       fun test_not_equals is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -150,7 +149,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_lt do
+       fun test_lt is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -160,7 +159,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_le do
+       fun test_le is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -170,7 +169,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_compare do
+       fun test_compare is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -180,7 +179,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_ge do
+       fun test_ge is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -190,7 +189,7 @@ class TestY
                assert exp == res
        end
 
-       fun test_gt do
+       fun test_gt is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Y[X]
@@ -202,9 +201,9 @@ class TestY
 end
 
 class TestZ
-       super TestSuite
+       test
 
-       fun test_bra do
+       fun test_bra is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Z
@@ -215,7 +214,7 @@ class TestZ
                assert exp == res
        end
 
-       fun test_bra_assign do
+       fun test_bra_assign is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Z
@@ -225,7 +224,7 @@ class TestZ
                subject[i, j] = k
        end
 
-       fun test_foo= do
+       fun test_foo= is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Z
@@ -234,7 +233,7 @@ class TestZ
                subject.foo(i) = j
        end
 
-       fun test_bar= do
+       fun test_bar= is test do
                assert not_implemented: false # TODO remove once implemented
 
                var subject: Z
index bb114f6..18de7be 100644 (file)
@@ -1,41 +1,41 @@
-test_nitunit4/test_bad_comp.nit:27,10--19: Error: method or variable `bad_method` unknown in `TestSuiteBadComp`.
-test_nitunit4/test_bad_comp2.nit:19,7--22: Error: a class named `test_nitunit4::TestSuiteBadComp` is already defined in module `test_bad_comp` at test_nitunit4/test_bad_comp.nit:19,1--29,3.
+test_nitunit4/test_bad_comp.nit:25,10--19: Error: method or variable `bad_method` unknown in `TestSuiteBadComp`.
+test_nitunit4/test_bad_comp2.nit:17,7--22: Error: a class named `test_nitunit4::TestSuiteBadComp` is already defined in module `test_bad_comp` at test_nitunit4/test_bad_comp.nit:17,1--27,3.
 ==== Test-suite of module test_nitunit4::test_bad_comp | tests: 2
 [KO] test_nitunit4$TestSuiteBadComp$test_good
-     test_nitunit4/test_bad_comp.nit:22,2--24,4: Compilation Error
+     test_nitunit4/test_bad_comp.nit:20,2--22,4: Compilation Error
      Output
-       test_nitunit4/test_bad_comp.nit:27,10--19: Error: method or variable `bad_method` unknown in `TestSuiteBadComp`.
+       test_nitunit4/test_bad_comp.nit:25,10--19: Error: method or variable `bad_method` unknown in `TestSuiteBadComp`.
 
 [KO] test_nitunit4$TestSuiteBadComp$test_bad
-     test_nitunit4/test_bad_comp.nit:26,2--28,4: Compilation Error
+     test_nitunit4/test_bad_comp.nit:24,2--26,4: Compilation Error
      Output
-       test_nitunit4/test_bad_comp.nit:27,10--19: Error: method or variable `bad_method` unknown in `TestSuiteBadComp`.
+       test_nitunit4/test_bad_comp.nit:25,10--19: Error: method or variable `bad_method` unknown in `TestSuiteBadComp`.
 
 
 ==== Test-suite of module test_nitunit4::test_bad_comp2 | tests: 2
 [KO] test_nitunit4$TestSuiteBadComp$test_good
-     test_nitunit4/test_bad_comp2.nit:22,2--24,4: Compilation Error
+     test_nitunit4/test_bad_comp2.nit:20,2--22,4: Compilation Error
      Output
-       nitunit.out/gen_test_bad_comp2.nit:14,10--17: Error: expected 1 argument(s) for `test_bad(param: Bool)`; got 0. See introduction at `test_nitunit4::TestSuiteBadComp::test_bad`.
+       nitunit.out/gen_test_bad_comp2.nit:11,10--17: Error: expected 1 argument(s) for `test_bad(param: Bool)`; got 0. See introduction at `test_nitunit4::TestSuiteBadComp::test_bad`.
 
 [KO] test_nitunit4$TestSuiteBadComp$test_bad
-     test_nitunit4/test_bad_comp2.nit:26,2--28,4: Compilation Error
+     test_nitunit4/test_bad_comp2.nit:24,2--26,4: Compilation Error
      Output
-       nitunit.out/gen_test_bad_comp2.nit:14,10--17: Error: expected 1 argument(s) for `test_bad(param: Bool)`; got 0. See introduction at `test_nitunit4::TestSuiteBadComp::test_bad`.
+       nitunit.out/gen_test_bad_comp2.nit:11,10--17: Error: expected 1 argument(s) for `test_bad(param: Bool)`; got 0. See introduction at `test_nitunit4::TestSuiteBadComp::test_bad`.
 
 
 ==== Test-suite of module test_nitunit4::test_nitunit4 | tests: 4
 [KO] test_nitunit4$TestTestSuite$test_foo
-     test_nitunit4/test_nitunit4.nit:22,2--26,4: Runtime Error in file nitunit.out/gen_test_nitunit4.nit
+     test_nitunit4/test_nitunit4.nit:23,2--27,4: Runtime Error in file nitunit.out/gen_test_nitunit4.nit
      Output
        Before Test
        Tested method
        After Test
-       Runtime error: Assert failed (test_nitunit4/test_nitunit4_base.nit:31)
+       Runtime error: Assert failed (test_nitunit4/test_nitunit4_base.nit:28)
 
 [OK] test_nitunit4$TestTestSuite$test_bar
 [KO] test_nitunit4$TestTestSuite$test_baz
-     test_nitunit4/test_nitunit4.nit:32,2--34,4: Difference with expected output: diff -u test_nitunit4/test_baz.res nitunit.out/gen_test_nitunit4_test_baz.out1
+     test_nitunit4/test_nitunit4.nit:33,2--35,4: Difference with expected output: diff -u test_nitunit4/test_baz.res nitunit.out/gen_test_nitunit4_test_baz.out1
      Output
        Diff
        --- expected:test_nitunit4/test_baz.res
@@ -47,28 +47,25 @@ test_nitunit4/test_bad_comp2.nit:19,7--22: Error: a class named `test_nitunit4::
        +After Test
 
 [KO] test_nitunit4$TestTestSuite$test_sav_conflict
-     test_nitunit4/test_nitunit4.nit:36,2--38,4: Conflicting expected output: test_nitunit4/test_nitunit4.sav/test_sav_conflict.res, test_nitunit4/sav/test_sav_conflict.res and test_nitunit4/test_sav_conflict.res all exist
+     test_nitunit4/test_nitunit4.nit:37,2--39,4: Conflicting expected output: test_nitunit4/test_nitunit4.sav/test_sav_conflict.res, test_nitunit4/sav/test_sav_conflict.res and test_nitunit4/test_sav_conflict.res all exist
      Output
        Before Test
        Tested method
        After Test
 
 
-==== Test-suite of module test_nitunit4::test_nitunit4_base | tests: 0
-==== Test-suite of module test_nitunit4::test_nitunit4_base | tests: 0
-
 Docunits: Entities: 21; Documented ones: 0; With nitunits: 0
-Test suites: Classes: 4; Test Cases: 8; Failures: 7
+Test suites: Classes: 3; Test Cases: 8; Failures: 7
 [FAILURE] 7/8 tests failed.
 `nitunit.out` is not removed for investigation.
-<testsuites><testsuite package="test_nitunit4&gt;"></testsuite><testsuite package="test_nitunit4::nitunit4"></testsuite><testsuite package="test_nitunit4::test_bad_comp"></testsuite><testsuite package="test_bad_comp"><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_good" time="0.0"><failure message="Compilation Error">test_nitunit4&#47;test_bad_comp.nit:27,10--19: Error: method or variable `bad_method` unknown in `TestSuiteBadComp`.
-</failure></testcase><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_bad" time="0.0"><failure message="Compilation Error">test_nitunit4&#47;test_bad_comp.nit:27,10--19: Error: method or variable `bad_method` unknown in `TestSuiteBadComp`.
-</failure></testcase></testsuite><testsuite package="test_nitunit4::test_bad_comp2"></testsuite><testsuite package="test_bad_comp2"><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_good" time="0.0"><failure message="Compilation Error">nitunit.out&#47;gen_test_bad_comp2.nit:14,10--17: Error: expected 1 argument(s) for `test_bad(param: Bool)`; got 0. See introduction at `test_nitunit4::TestSuiteBadComp::test_bad`.
-</failure></testcase><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_bad" time="0.0"><failure message="Compilation Error">nitunit.out&#47;gen_test_bad_comp2.nit:14,10--17: Error: expected 1 argument(s) for `test_bad(param: Bool)`; got 0. See introduction at `test_nitunit4::TestSuiteBadComp::test_bad`.
+<testsuites><testsuite package="test_nitunit4&gt;"></testsuite><testsuite package="test_nitunit4::nitunit4"></testsuite><testsuite package="test_nitunit4::test_bad_comp"></testsuite><testsuite package="test_bad_comp"><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_good" time="0.0"><failure message="Compilation Error">test_nitunit4&#47;test_bad_comp.nit:25,10--19: Error: method or variable `bad_method` unknown in `TestSuiteBadComp`.
+</failure></testcase><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_bad" time="0.0"><failure message="Compilation Error">test_nitunit4&#47;test_bad_comp.nit:25,10--19: Error: method or variable `bad_method` unknown in `TestSuiteBadComp`.
+</failure></testcase></testsuite><testsuite package="test_nitunit4::test_bad_comp2"></testsuite><testsuite package="test_bad_comp2"><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_good" time="0.0"><failure message="Compilation Error">nitunit.out&#47;gen_test_bad_comp2.nit:11,10--17: Error: expected 1 argument(s) for `test_bad(param: Bool)`; got 0. See introduction at `test_nitunit4::TestSuiteBadComp::test_bad`.
+</failure></testcase><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_bad" time="0.0"><failure message="Compilation Error">nitunit.out&#47;gen_test_bad_comp2.nit:11,10--17: Error: expected 1 argument(s) for `test_bad(param: Bool)`; got 0. See introduction at `test_nitunit4::TestSuiteBadComp::test_bad`.
 </failure></testcase></testsuite><testsuite package="test_nitunit4::test_nitunit4"></testsuite><testsuite package="test_nitunit4"><testcase classname="nitunit.test_nitunit4.TestTestSuite" name="test_foo" time="0.0"><error message="Runtime Error in file nitunit.out&#47;gen_test_nitunit4.nit">Before Test
 Tested method
 After Test
-Runtime error: Assert failed (test_nitunit4&#47;test_nitunit4_base.nit:31)
+Runtime error: Assert failed (test_nitunit4&#47;test_nitunit4_base.nit:28)
 </error></testcase><testcase classname="nitunit.test_nitunit4.TestTestSuite" name="test_bar" time="0.0"><system-err>Before Test
 Tested method
 After Test
@@ -83,4 +80,4 @@ After Test
 </error></testcase><testcase classname="nitunit.test_nitunit4.TestTestSuite" name="test_sav_conflict" time="0.0"><error message="Conflicting expected output: test_nitunit4&#47;test_nitunit4.sav&#47;test_sav_conflict.res, test_nitunit4&#47;sav&#47;test_sav_conflict.res and test_nitunit4&#47;test_sav_conflict.res all exist">Before Test
 Tested method
 After Test
-</error></testcase></testsuite><testsuite package="test_nitunit4::test_nitunit4_base"></testsuite><testsuite package="test_nitunit4_base"></testsuite></testsuites>
\ No newline at end of file
+</error></testcase></testsuite><testsuite package="test_nitunit4::test_nitunit4_base"></testsuite></testsuites>
\ No newline at end of file
index 26fb54f..52e9594 100644 (file)
@@ -1,10 +1,8 @@
 syntax_annotations3.nit:16,2--20: Warning: unknown annotation `invariant`.
 syntax_annotations3.nit:19,3--12: Warning: unknown annotation `pre`.
 syntax_annotations3.nit:20,3--22: Warning: unknown annotation `post`.
-syntax_annotations3.nit:21,3--19: Warning: unknown annotation `test`.
 syntax_annotations3.nit:28,3--7: Warning: unknown annotation `inter`.
 syntax_annotations3.nit:33,16--18: Warning: unknown annotation `u32`.
-syntax_annotations3.nit:34,19--36: Warning: unknown annotation `after`.
 syntax_annotations3.nit:34,12--36: Warning: unknown annotation `daemon`.
 syntax_annotations3.nit:34,3--37: Warning: unknown annotation `ondebug`.
 syntax_annotations3.nit:35,3--7: Warning: unknown annotation `final`.
diff --git a/tests/sav/test_inspect_serialization.res b/tests/sav/test_inspect_serialization.res
new file mode 100644 (file)
index 0000000..313b090
--- /dev/null
@@ -0,0 +1,49 @@
+# Custom:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Inspect:
+<A#0 b:true, c:'a', f:0.123, i:1234, serialization_specific_name:"asdf", n:null>
+
+# Custom:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Inspect:
+<B#0 b:false, c:'b', f:123.123, i:2345, serialization_specific_name:"hjkl", n:…>
+
+# Custom:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# Inspect:
+<C#0 a:<A#1>, b:<B#2>, aa:<A#1>>
+
+# Custom:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# Inspect:
+<D#0 b:false, c:'b', f:123.123, i:2345, serialization_specific_name:"new line â€¦>
+
+# Custom:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Inspect:
+<E#0 a:<Array[Object]#1>, b:<Array[nullable Serializable]#2>>
+
+# Custom:
+<F: 2222>
+
+# Inspect:
+<F[Int]#0 n:2222>
+
+# Custom:
+<F: 33.33>
+
+# Inspect:
+<F[Float]#0 n:33.33>
+
+# Custom:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# Inspect:
+<G#0 hs:<HashSet[Int]#1>, s:<ArraySet[String]#2>, hm:<HashMap[String, Int]#3>,…>
+
diff --git a/tests/sav/test_msgpack_deserialization.res b/tests/sav/test_msgpack_deserialization.res
new file mode 100644 (file)
index 0000000..8ae5166
--- /dev/null
@@ -0,0 +1,104 @@
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+       "b": true,
+       "c": "a",
+       "f": 0.123,
+       "i": 1234,
+       "serialization_specific_name": "asdf",
+       "n": null
+}
+
+# 4. Back in Nit (no metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "hjkl",
+       "n": 12,
+       "ii": 1111,
+       "ss": "qwer"
+}
+
+# 4. Back in Nit (no metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x83\xA1\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+       "a": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       },
+       "b": {
+               "b": false,
+               "c": "b",
+               "f": 123.123,
+               "i": 2345,
+               "serialization_specific_name": "hjkl",
+               "n": 12,
+               "ii": 1111,
+               "ss": "qwer"
+       },
+       "aa": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       }
+}
+
+# 4. Back in Nit (no metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 2. MsgPack:
+\x89\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xC0
+
+# 3. JSON:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "new line ->\n<-",
+       "n": null,
+       "ii": 1111,
+       "ss": "\tf\"\r\\/",
+       "d": null
+}
+
+# 4. Back in Nit (no metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> false>
+
diff --git a/tests/sav/test_msgpack_deserialization_alt1.res b/tests/sav/test_msgpack_deserialization_alt1.res
new file mode 100644 (file)
index 0000000..a454a78
--- /dev/null
@@ -0,0 +1,135 @@
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x41\x86\xA1\x62\xC3\xA1\x63\xD4\x7C\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "A", {
+       "b": true,
+       "c": {
+               "typ": 124,
+               "data": [97]
+       },
+       "f": 0.123,
+       "i": 1234,
+       "serialization_specific_name": "asdf",
+       "n": null
+}]
+
+# 4. Back in Nit (with metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x42\x88\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "B", {
+       "b": false,
+       "c": {
+               "typ": 124,
+               "data": [98]
+       },
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "hjkl",
+       "n": 12,
+       "ii": 1111,
+       "ss": "qwer"
+}]
+
+# 4. Back in Nit (with metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x43\x83\xA1\x61\x93\xD4\x7B\x01\xA1\x41\x86\xA1\x62\xC3\xA1\x63\xD4\x7C\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x93\xD4\x7B\x02\xA1\x42\x88\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\xD4\x7D\x01
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "C", {
+       "a": [{
+               "typ": 123,
+               "data": [1]
+       }, "A", {
+               "b": true,
+               "c": {
+                       "typ": 124,
+                       "data": [97]
+               },
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       }],
+       "b": [{
+               "typ": 123,
+               "data": [2]
+       }, "B", {
+               "b": false,
+               "c": {
+                       "typ": 124,
+                       "data": [98]
+               },
+               "f": 123.123,
+               "i": 2345,
+               "serialization_specific_name": "hjkl",
+               "n": 12,
+               "ii": 1111,
+               "ss": "qwer"
+       }],
+       "aa": {
+               "typ": 125,
+               "data": [1]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x44\x89\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xD4\x7D\x00
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "D", {
+       "b": false,
+       "c": {
+               "typ": 124,
+               "data": [98]
+       },
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "new line ->\n<-",
+       "n": null,
+       "ii": 1111,
+       "ss": "\tf\"\r\\/",
+       "d": {
+               "typ": 125,
+               "data": [0]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
diff --git a/tests/sav/test_msgpack_deserialization_alt2.res b/tests/sav/test_msgpack_deserialization_alt2.res
new file mode 100644 (file)
index 0000000..2a5d0ad
--- /dev/null
@@ -0,0 +1,170 @@
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+       "b": true,
+       "c": "a",
+       "f": 0.123,
+       "i": 1234,
+       "serialization_specific_name": "asdf",
+       "n": null
+}
+
+# 4. Back in Nit (no metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "hjkl",
+       "n": 12,
+       "ii": 1111,
+       "ss": "qwer"
+}
+
+# 4. Back in Nit (no metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x83\xA1\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+       "a": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       },
+       "b": {
+               "b": false,
+               "c": "b",
+               "f": 123.123,
+               "i": 2345,
+               "serialization_specific_name": "hjkl",
+               "n": 12,
+               "ii": 1111,
+               "ss": "qwer"
+       },
+       "aa": {
+               "b": true,
+               "c": "a",
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       }
+}
+
+# 4. Back in Nit (no metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 2. MsgPack:
+\x89\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xC0
+
+# 3. JSON:
+{
+       "b": false,
+       "c": "b",
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "new line ->\n<-",
+       "n": null,
+       "ii": 1111,
+       "ss": "\tf\"\r\\/",
+       "d": null
+}
+
+# 4. Back in Nit (no metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> false>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x82\xA1\x61\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\xA1\x62\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+{
+       "a": ["hello", 1234, 123.4],
+       "b": ["hella", 2345, 234.5]
+}
+
+# 4. Back in Nit (no metadata):
+<E: a: ; b: >
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x81\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+{
+       "n": 2222
+}
+
+# 4. Back in Nit (no metadata):
+<F: 2222>
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x81\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+{
+       "n": 33.33
+}
+
+# 4. Back in Nit (no metadata):
+<F: 33.33>
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x84\xA2\x68\x73\x92\xFF\x00\xA1\x73\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\xA2\x68\x6D\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\xA2\x61\x6D\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+{
+       "hs": [-1, 0],
+       "s": ["one", "two"],
+       "hm": {
+               "one": 1,
+               "two": 2
+       },
+       "am": {
+               "three": "3",
+               "four": "4"
+       }
+}
+
+# 4. Back in Nit (no metadata):
+<G: hs: ; s: ; hm: one. 1, two. 2; am: three. 3, four. 4>
+
diff --git a/tests/sav/test_msgpack_deserialization_alt3.res b/tests/sav/test_msgpack_deserialization_alt3.res
new file mode 100644 (file)
index 0000000..6aa484f
--- /dev/null
@@ -0,0 +1,231 @@
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x41\x86\xA1\x62\xC3\xA1\x63\xD4\x7C\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "A", {
+       "b": true,
+       "c": {
+               "typ": 124,
+               "data": [97]
+       },
+       "f": 0.123,
+       "i": 1234,
+       "serialization_specific_name": "asdf",
+       "n": null
+}]
+
+# 4. Back in Nit (with metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x42\x88\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "B", {
+       "b": false,
+       "c": {
+               "typ": 124,
+               "data": [98]
+       },
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "hjkl",
+       "n": 12,
+       "ii": 1111,
+       "ss": "qwer"
+}]
+
+# 4. Back in Nit (with metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x43\x83\xA1\x61\x93\xD4\x7B\x01\xA1\x41\x86\xA1\x62\xC3\xA1\x63\xD4\x7C\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x93\xD4\x7B\x02\xA1\x42\x88\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\xD4\x7D\x01
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "C", {
+       "a": [{
+               "typ": 123,
+               "data": [1]
+       }, "A", {
+               "b": true,
+               "c": {
+                       "typ": 124,
+                       "data": [97]
+               },
+               "f": 0.123,
+               "i": 1234,
+               "serialization_specific_name": "asdf",
+               "n": null
+       }],
+       "b": [{
+               "typ": 123,
+               "data": [2]
+       }, "B", {
+               "b": false,
+               "c": {
+                       "typ": 124,
+                       "data": [98]
+               },
+               "f": 123.123,
+               "i": 2345,
+               "serialization_specific_name": "hjkl",
+               "n": 12,
+               "ii": 1111,
+               "ss": "qwer"
+       }],
+       "aa": {
+               "typ": 125,
+               "data": [1]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x44\x89\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xD4\x7D\x00
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "D", {
+       "b": false,
+       "c": {
+               "typ": 124,
+               "data": [98]
+       },
+       "f": 123.123,
+       "i": 2345,
+       "serialization_specific_name": "new line ->\n<-",
+       "n": null,
+       "ii": 1111,
+       "ss": "\tf\"\r\\/",
+       "d": {
+               "typ": 125,
+               "data": [0]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x45\x82\xA1\x61\x93\xD4\x7B\x01\xAD\x41\x72\x72\x61\x79\x5B\x4F\x62\x6A\x65\x63\x74\x5D\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\xA1\x62\x93\xD4\x7B\x02\xBC\x41\x72\x72\x61\x79\x5B\x6E\x75\x6C\x6C\x61\x62\x6C\x65\x20\x53\x65\x72\x69\x61\x6C\x69\x7A\x61\x62\x6C\x65\x5D\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "E", {
+       "a": [{
+               "typ": 123,
+               "data": [1]
+       }, "Array[Object]", ["hello", 1234, 123.4]],
+       "b": [{
+               "typ": 123,
+               "data": [2]
+       }, "Array[nullable Serializable]", ["hella", 2345, 234.5]]
+}]
+
+# 4. Back in Nit (with metadata):
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA6\x46\x5B\x49\x6E\x74\x5D\x81\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "F[Int]", {
+       "n": 2222
+}]
+
+# 4. Back in Nit (with metadata):
+<F: 2222>
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA8\x46\x5B\x46\x6C\x6F\x61\x74\x5D\x81\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "F[Float]", {
+       "n": 33.33
+}]
+
+# 4. Back in Nit (with metadata):
+<F: 33.33>
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x47\x84\xA2\x68\x73\x93\xD4\x7B\x01\xAC\x48\x61\x73\x68\x53\x65\x74\x5B\x49\x6E\x74\x5D\x92\xFF\x00\xA1\x73\x93\xD4\x7B\x02\xB0\x41\x72\x72\x61\x79\x53\x65\x74\x5B\x53\x74\x72\x69\x6E\x67\x5D\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\xA2\x68\x6D\x93\xD4\x7B\x03\xB4\x48\x61\x73\x68\x4D\x61\x70\x5B\x53\x74\x72\x69\x6E\x67\x2C\x20\x49\x6E\x74\x5D\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\xA2\x61\x6D\x93\xD4\x7B\x04\xB8\x41\x72\x72\x61\x79\x4D\x61\x70\x5B\x53\x74\x72\x69\x6E\x67\x2C\x20\x53\x74\x72\x69\x6E\x67\x5D\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, "G", {
+       "hs": [{
+               "typ": 123,
+               "data": [1]
+       }, "HashSet[Int]", [-1, 0]],
+       "s": [{
+               "typ": 123,
+               "data": [2]
+       }, "ArraySet[String]", ["one", "two"]],
+       "hm": [{
+               "typ": 123,
+               "data": [3]
+       }, "HashMap[String, Int]", {
+               "one": 1,
+               "two": 2
+       }],
+       "am": [{
+               "typ": 123,
+               "data": [4]
+       }, "ArrayMap[String, String]", {
+               "three": "3",
+               "four": "4"
+       }]
+}]
+
+# 4. Back in Nit (with metadata):
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
diff --git a/tests/sav/test_msgpack_deserialization_alt4.res b/tests/sav/test_msgpack_deserialization_alt4.res
new file mode 100644 (file)
index 0000000..e6affaa
--- /dev/null
@@ -0,0 +1,279 @@
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x41\x86\x92\xD4\x7B\x02\xA1\x62\xC3\x92\xD4\x7B\x03\xA1\x63\xD4\x7C\x61\x92\xD4\x7B\x04\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\x92\xD4\x7B\x05\xA1\x69\xCD\x04\xD2\x92\xD4\x7B\x06\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\x92\xD4\x7B\x07\xA1\x6E\xC0
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "A"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,b]": true,
+       "[<MsgPackExt typ: 0x7b, data: \\x03>,c]": {
+               "typ": 124,
+               "data": [97]
+       },
+       "[<MsgPackExt typ: 0x7b, data: \\x04>,f]": 0.123,
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,i]": 1234,
+       "[<MsgPackExt typ: 0x7b, data: \\x06>,serialization_specific_name]": "asdf",
+       "[<MsgPackExt typ: 0x7b, data: \\x07>,n]": null
+}]
+
+# 4. Back in Nit (with metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x42\x88\x92\xD4\x7B\x02\xA1\x62\xC2\x92\xD4\x7B\x03\xA1\x63\xD4\x7C\x62\x92\xD4\x7B\x04\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\x92\xD4\x7B\x05\xA1\x69\xCD\x09\x29\x92\xD4\x7B\x06\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\x92\xD4\x7B\x07\xA1\x6E\x0C\x92\xD4\x7B\x08\xA2\x69\x69\xCD\x04\x57\x92\xD4\x7B\x09\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "B"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,b]": false,
+       "[<MsgPackExt typ: 0x7b, data: \\x03>,c]": {
+               "typ": 124,
+               "data": [98]
+       },
+       "[<MsgPackExt typ: 0x7b, data: \\x04>,f]": 123.123,
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,i]": 2345,
+       "[<MsgPackExt typ: 0x7b, data: \\x06>,serialization_specific_name]": "hjkl",
+       "[<MsgPackExt typ: 0x7b, data: \\x07>,n]": 12,
+       "[<MsgPackExt typ: 0x7b, data: \\x08>,ii]": 1111,
+       "[<MsgPackExt typ: 0x7b, data: \\x09>,ss]": "qwer"
+}]
+
+# 4. Back in Nit (with metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x43\x83\x92\xD4\x7B\x02\xA1\x61\x93\xD4\x7B\x03\x92\xD4\x7B\x04\xA1\x41\x86\x92\xD4\x7B\x05\xA1\x62\xC3\x92\xD4\x7B\x06\xA1\x63\xD4\x7C\x61\x92\xD4\x7B\x07\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\x92\xD4\x7B\x08\xA1\x69\xCD\x04\xD2\x92\xD4\x7B\x09\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\x92\xD4\x7B\x0A\xA1\x6E\xC0\xD4\x7D\x05\x93\xD4\x7B\x0B\x92\xD4\x7B\x0C\xA1\x42\x88\xD4\x7D\x05\xC2\xD4\x7D\x06\xD4\x7C\x62\xD4\x7D\x07\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xD4\x7D\x08\xCD\x09\x29\xD4\x7D\x09\xA4\x68\x6A\x6B\x6C\xD4\x7D\x0A\x0C\x92\xD4\x7B\x0D\xA2\x69\x69\xCD\x04\x57\x92\xD4\x7B\x0E\xA2\x73\x73\xA4\x71\x77\x65\x72\x92\xD4\x7B\x0F\xA2\x61\x61\xD4\x7D\x03
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "C"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,a]": [{
+               "typ": 123,
+               "data": [3]
+       }, [{
+               "typ": 123,
+               "data": [4]
+       }, "A"], {
+               "[<MsgPackExt typ: 0x7b, data: \\x05>,b]": true,
+               "[<MsgPackExt typ: 0x7b, data: \\x06>,c]": {
+                       "typ": 124,
+                       "data": [97]
+               },
+               "[<MsgPackExt typ: 0x7b, data: \\x07>,f]": 0.123,
+               "[<MsgPackExt typ: 0x7b, data: \\x08>,i]": 1234,
+               "[<MsgPackExt typ: 0x7b, data: \\x09>,serialization_specific_name]": "asdf",
+               "[<MsgPackExt typ: 0x7b, data: \\x0A>,n]": null
+       }],
+       "<MsgPackExt typ: 0x7d, data: \\x05>": [{
+               "typ": 123,
+               "data": [11]
+       }, [{
+               "typ": 123,
+               "data": [12]
+       }, "B"], {
+               "<MsgPackExt typ: 0x7d, data: \\x05>": false,
+               "<MsgPackExt typ: 0x7d, data: \\x06>": {
+                       "typ": 124,
+                       "data": [98]
+               },
+               "<MsgPackExt typ: 0x7d, data: \\x07>": 123.123,
+               "<MsgPackExt typ: 0x7d, data: \\x08>": 2345,
+               "<MsgPackExt typ: 0x7d, data: \\x09>": "hjkl",
+               "<MsgPackExt typ: 0x7d, data: \\x0A>": 12,
+               "[<MsgPackExt typ: 0x7b, data: \\x0D>,ii]": 1111,
+               "[<MsgPackExt typ: 0x7b, data: \\x0E>,ss]": "qwer"
+       }],
+       "[<MsgPackExt typ: 0x7b, data: \\x0F>,aa]": {
+               "typ": 125,
+               "data": [3]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x44\x89\x92\xD4\x7B\x02\xA1\x62\xC2\x92\xD4\x7B\x03\xA1\x63\xD4\x7C\x62\x92\xD4\x7B\x04\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\x92\xD4\x7B\x05\xA1\x69\xCD\x09\x29\x92\xD4\x7B\x06\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\x92\xD4\x7B\x07\xA1\x6E\xC0\x92\xD4\x7B\x08\xA2\x69\x69\xCD\x04\x57\x92\xD4\x7B\x09\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\x92\xD4\x7B\x0A\xA1\x64\xD4\x7D\x00
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "D"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,b]": false,
+       "[<MsgPackExt typ: 0x7b, data: \\x03>,c]": {
+               "typ": 124,
+               "data": [98]
+       },
+       "[<MsgPackExt typ: 0x7b, data: \\x04>,f]": 123.123,
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,i]": 2345,
+       "[<MsgPackExt typ: 0x7b, data: \\x06>,serialization_specific_name]": "new line ->\n<-",
+       "[<MsgPackExt typ: 0x7b, data: \\x07>,n]": null,
+       "[<MsgPackExt typ: 0x7b, data: \\x08>,ii]": 1111,
+       "[<MsgPackExt typ: 0x7b, data: \\x09>,ss]": "\tf\"\r\\/",
+       "[<MsgPackExt typ: 0x7b, data: \\x0A>,d]": {
+               "typ": 125,
+               "data": [0]
+       }
+}]
+
+# 4. Back in Nit (with metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x45\x82\x92\xD4\x7B\x02\xA1\x61\x93\xD4\x7B\x03\x92\xD4\x7B\x04\xAD\x41\x72\x72\x61\x79\x5B\x4F\x62\x6A\x65\x63\x74\x5D\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\x92\xD4\x7B\x05\xA1\x62\x93\xD4\x7B\x06\x92\xD4\x7B\x07\xBC\x41\x72\x72\x61\x79\x5B\x6E\x75\x6C\x6C\x61\x62\x6C\x65\x20\x53\x65\x72\x69\x61\x6C\x69\x7A\x61\x62\x6C\x65\x5D\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "E"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,a]": [{
+               "typ": 123,
+               "data": [3]
+       }, [{
+               "typ": 123,
+               "data": [4]
+       }, "Array[Object]"], ["hello", 1234, 123.4]],
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,b]": [{
+               "typ": 123,
+               "data": [6]
+       }, [{
+               "typ": 123,
+               "data": [7]
+       }, "Array[nullable Serializable]"], ["hella", 2345, 234.5]]
+}]
+
+# 4. Back in Nit (with metadata):
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA6\x46\x5B\x49\x6E\x74\x5D\x81\x92\xD4\x7B\x02\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "F[Int]"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,n]": 2222
+}]
+
+# 4. Back in Nit (with metadata):
+<F: 2222>
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA8\x46\x5B\x46\x6C\x6F\x61\x74\x5D\x81\x92\xD4\x7B\x02\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "F[Float]"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,n]": 33.33
+}]
+
+# 4. Back in Nit (with metadata):
+<F: 33.33>
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x47\x84\x92\xD4\x7B\x02\xA2\x68\x73\x93\xD4\x7B\x03\x92\xD4\x7B\x04\xAC\x48\x61\x73\x68\x53\x65\x74\x5B\x49\x6E\x74\x5D\x92\xFF\x00\x92\xD4\x7B\x05\xA1\x73\x93\xD4\x7B\x06\x92\xD4\x7B\x07\xB0\x41\x72\x72\x61\x79\x53\x65\x74\x5B\x53\x74\x72\x69\x6E\x67\x5D\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\x92\xD4\x7B\x08\xA2\x68\x6D\x93\xD4\x7B\x09\x92\xD4\x7B\x0A\xB4\x48\x61\x73\x68\x4D\x61\x70\x5B\x53\x74\x72\x69\x6E\x67\x2C\x20\x49\x6E\x74\x5D\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\x92\xD4\x7B\x0B\xA2\x61\x6D\x93\xD4\x7B\x0C\x92\xD4\x7B\x0D\xB8\x41\x72\x72\x61\x79\x4D\x61\x70\x5B\x53\x74\x72\x69\x6E\x67\x2C\x20\x53\x74\x72\x69\x6E\x67\x5D\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+[{
+       "typ": 123,
+       "data": [0]
+}, [{
+       "typ": 123,
+       "data": [1]
+}, "G"], {
+       "[<MsgPackExt typ: 0x7b, data: \\x02>,hs]": [{
+               "typ": 123,
+               "data": [3]
+       }, [{
+               "typ": 123,
+               "data": [4]
+       }, "HashSet[Int]"], [-1, 0]],
+       "[<MsgPackExt typ: 0x7b, data: \\x05>,s]": [{
+               "typ": 123,
+               "data": [6]
+       }, [{
+               "typ": 123,
+               "data": [7]
+       }, "ArraySet[String]"], ["one", "two"]],
+       "[<MsgPackExt typ: 0x7b, data: \\x08>,hm]": [{
+               "typ": 123,
+               "data": [9]
+       }, [{
+               "typ": 123,
+               "data": [10]
+       }, "HashMap[String, Int]"], {
+               "one": 1,
+               "two": 2
+       }],
+       "[<MsgPackExt typ: 0x7b, data: \\x0B>,am]": [{
+               "typ": 123,
+               "data": [12]
+       }, [{
+               "typ": 123,
+               "data": [13]
+       }, "ArrayMap[String, String]"], {
+               "three": "3",
+               "four": "4"
+       }]
+}]
+
+# 4. Back in Nit (with metadata):
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
diff --git a/tests/test_inspect_serialization.nit b/tests/test_inspect_serialization.nit
new file mode 100644 (file)
index 0000000..42fd089
--- /dev/null
@@ -0,0 +1,23 @@
+# 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.
+
+import test_deserialization
+
+var entities = new TestEntities
+var tests = entities.with_generics
+
+for o in tests do
+       print "# Custom:\n{o}\n"
+       print "# Inspect:\n{o.inspect}\n"
+end
diff --git a/tests/test_msgpack_deserialization.nit b/tests/test_msgpack_deserialization.nit
new file mode 100644 (file)
index 0000000..d64bd44
--- /dev/null
@@ -0,0 +1,74 @@
+# 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.
+
+# alt0 non-generics plain
+# alt1 non-generics metadata
+# alt2     generics plain
+# alt3     generics metadata
+# alt4     generics metadata cache_metadata_strings
+
+import test_deserialization
+import msgpack
+import msgpack::read
+import json
+#alt2# import test_deserialization_serial
+#alt3# import test_deserialization_serial
+#alt4# import test_deserialization_serial
+
+var entities = new TestEntities
+
+var tests = entities.without_generics
+#alt2#tests = entities.with_generics
+#alt3#tests = entities.with_generics
+#alt4#tests = entities.with_generics
+
+for o in tests do
+       var plain = true
+       #alt1#plain = false
+       #alt3#plain = false
+       #alt4#plain = false
+
+       var writer = new BytesWriter
+       var serializer = new MsgPackSerializer(writer)
+       serializer.plain_msgpack = plain
+       #alt4#serializer.cache_metadata_strings = true
+       serializer.serialize o
+       var bytes = writer.bytes
+
+       # Nit source
+       print "# 1. Nit source:\n{o}\n"
+
+       # Generated MessagePack
+       print "# 2. MsgPack:\n{bytes.chexdigest}\n"
+
+       # Python deserialization line to ensure compliance and otherwise debug
+       #print "msgpack.unpackb(b'{bytes.chexdigest}')"
+
+       # Show readable structure of the generated MessagePack as JSON
+       var msg = (new BytesReader(bytes)).read_msgpack
+       var json = if msg != null then
+                       msg.serialize_to_json(plain=true, pretty=true)
+               else "null"
+       print "# 3. JSON:\n{json}\n"
+
+       # Deserialize
+       var deserializer = new MsgPackDeserializer(new BytesReader(bytes))
+       if serializer.plain_msgpack then
+               var deserialized = deserializer.deserialize(o.class_name)
+               print "# 4. Back in Nit (no metadata):\n{deserialized or else "null"}\n"
+       else
+               var deserialized = deserializer.deserialize
+               print "# 4. Back in Nit (with metadata):\n{deserialized or else "null"}\n"
+       end
+end
index f8d9b87..04354d0 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_bad_comp is test_suite
-
-import test_suite
+module test_bad_comp is test
 
 class TestSuiteBadComp
-       super TestSuite
+       test
 
-       fun test_good do
+       fun test_good is test do
                assert true
        end
 
-       fun test_bad do
+       fun test_bad is test do
                assert bad_method
        end
 end
index aac1a09..c274df6 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_bad_comp2 is test_suite
-
-import test_suite
+module test_bad_comp2 is test
 
 class TestSuiteBadComp
-       super TestSuite
+       test
 
-       fun test_good do
+       fun test_good is test do
                assert true
        end
 
-       fun test_bad(param: Bool) do
+       fun test_bad(param: Bool) is test do
                assert param
        end
 end
index 8864263..dfb5e32 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_nitunit4 is test_suite
+module test_nitunit4 is test
 
 import test_nitunit4_base
 
 class TestTestSuite
        super SuperTestSuite
+       test
 
-       fun test_foo do
+       fun test_foo is test do
                print "Tested method"
                assert before
                before = false
        end
 
-       fun test_bar do
+       fun test_bar is test do
                print "Tested method"
        end
 
-       fun test_baz do
+       fun test_baz is test do
                print "Tested method"
        end
 
-       fun test_sav_conflict do
+       fun test_sav_conflict is test do
                print "Tested method"
        end
 end
index af21e73..3fe9e16 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_nitunit4_base is test_suite
-
-import test_suite
+module test_nitunit4_base
 
 class SuperTestSuite
-       super TestSuite
 
        var before = false
 
-       redef fun before_test do
+       fun before_test is before do
                print "Before Test"
                before = true
        end
 
-       redef fun after_test do
+       fun after_test is after do
                print "After Test"
                assert before
        end
index 602648b..fc327aa 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_nitunit5 is test_suite
-
-import test_suite
+module test_nitunit5 is test
 
 class TestNitunit5
-       super TestSuite
+       test
 
-       fun test_path_is_set do
+       fun test_path_is_set is test do
                assert "NIT_TESTING_PATH".environ != ""
        end
 
-       fun test_path_is_suite_path do
+       fun test_path_is_suite_path is test do
                assert "NIT_TESTING_PATH".environ.basename == "test_nitunit5.nit"
        end
 end
index 97eca0a..f5f4e67 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_nitunit6 is test_suite
-
-import test_suite
+module test_nitunit6 is test
 
 class TestNitunit6
-       super TestSuite
+       test
 
-       fun test_foo do
+       fun test_foo is test do
                assert true
        end
 end
 
-redef fun before_module do
+fun before_module is before_all do
        assert false
 end
 
-redef fun after_module do
+fun after_module is after_all do
        assert false
 end
index 63f47ea..3c79772 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-module test_nitunit7 is test_suite
-
-import test_suite
+module test_nitunit7 is test
 
 class TestNitunit7
-       super TestSuite
+       test
 
-       fun test_foo do
+       fun test_foo is test do
                assert true
        end
 end
 
-redef fun before_module do
+fun before_module is before_all do
        assert true
 end
 
-redef fun after_module do
+fun after_module is after_all do
        assert false
 end
index 71ce971..b04e567 100644 (file)
 # limitations under the License.
 
 # NitUnit file for test_nitunit module.
-module test_test_nitunit is test_suite
+module test_test_nitunit is test
 
-import test_suite
 intrude import test_nitunit
 
 class TestX
-       super TestSuite
+       test
 
        var subject: X is noinit
 
-       redef fun before_test do
+       fun before_test is before do
                subject = new X
        end
 
-       fun test_foo do
+       fun test_foo is test do
                subject.foo
        end
 
        # will fail
-       fun test_foo1 do
+       fun test_foo1 is test do
                subject.foo1(10, 20)
                assert false
        end
 
-       fun test_foo2 do
+       fun test_foo2 is test do
                assert subject.foo2
        end
 end