Merge: lib/geometry: intro more services on points and for angles
authorJean Privat <jean@pryen.org>
Wed, 8 Jul 2015 18:02:23 +0000 (14:02 -0400)
committerJean Privat <jean@pryen.org>
Wed, 8 Jul 2015 18:02:23 +0000 (14:02 -0400)
Some useful services for games and other geometry heavy applications.

I must say that I'm getting tired of the points API. Dealing with both the numeric complexity and the 2/3D is unpleasant. I will probably remove/separate the 3D part in the near future.

Pull-Request: #1557
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>

45 files changed:
VERSION
contrib/inkscape_tools/Makefile
contrib/inkscape_tools/src/svg_to_png_and_nit.nit
contrib/nitiwiki/src/wiki_links.nit
lib/binary/serialization.nit [new file with mode: 0644]
lib/github/README.md [new file with mode: 0644]
lib/github/api.nit
lib/github/github.nit
lib/github/github_curl.nit
lib/markdown/markdown.nit
lib/markdown/test_markdown.nit
lib/markdown/test_wikilinks.nit [new file with mode: 0644]
lib/markdown/wikilinks.nit
lib/mnit/opengles1.nit
lib/mnit_input.nit
lib/more_collections.nit
lib/performance_analysis.nit [new file with mode: 0644]
lib/pthreads/pthreads.nit
lib/realtime.nit
lib/serialization/serialization.nit
src/doc/doc_base.nit
src/doc/doc_commands.nit [new file with mode: 0644]
src/doc/doc_down.nit
src/doc/doc_phases/doc_console.nit
src/doc/doc_phases/doc_html.nit
src/doc/doc_phases/doc_pages.nit
src/doc/doc_phases/doc_readme.nit [new file with mode: 0644]
src/doc/html_templates/html_model.nit
src/doc/html_templates/html_templates.nit
src/frontend/serialization_phase.nit
src/loader.nit
src/location.nit
src/nitdoc.nit
tests/sav/nitce/test_binary_deserialization_alt1.res [new file with mode: 0644]
tests/sav/nitce/test_json_deserialization_alt1.res
tests/sav/nitdoc_args1.res
tests/sav/nitdoc_args2.res
tests/sav/nitdoc_args3.res
tests/sav/nitdoc_args4.res
tests/sav/nitunit_args6.res
tests/sav/nitunit_args7.res
tests/sav/test_binary_deserialization.res [new file with mode: 0644]
tests/sav/test_binary_deserialization_alt1.res [new file with mode: 0644]
tests/sav/test_binary_deserialization_alt2.res [new file with mode: 0644]
tests/test_binary_deserialization.nit [new file with mode: 0644]

diff --git a/VERSION b/VERSION
index aed5a7d..378c127 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-v0.7.5
+v0.7.6
index bfadf98..d94e33e 100644 (file)
@@ -1,11 +1,8 @@
-all: bins tests
-
-bins:
+all:
        mkdir -p bin
        ../../bin/nitc --dir bin src/svg_to_png_and_nit.nit src/svg_to_icons.nit
 
-check: tests
-tests: test-dino test-app
+check: test-dino test-app
 
 test-app: bin/svg_to_png_and_nit
        make -C tests/app
index f9e690b..abd0912 100644 (file)
@@ -329,8 +329,10 @@ for drawing in drawings do
        end
        svg_file.close
 
-       assert page_width != -1
-       assert page_height != -1
+       if page_width == -1 or page_height == -1 then
+               stderr.write "Source drawing file '{drawing}' doesn't look like an SVG file\n"
+               exit 1
+       end
 
        # Query Inkscape
        var prog = "inkscape"
index bdb27da..6af6d48 100644 (file)
@@ -227,38 +227,39 @@ private class NitiwikiDecorator
        # Article used to contextualize links.
        var context: WikiEntry
 
-       redef fun add_wikilink(v, link, name, comment) do
+       redef fun add_wikilink(v, token) do
+               var wiki = v.processor.as(NitiwikiMdProcessor).wiki
+               var target: nullable WikiEntry = null
                var anchor: nullable String = null
-               v.add "<a "
-               if not link.has_prefix("http://") and not link.has_prefix("https://") then
-                       var wiki = v.processor.as(NitiwikiMdProcessor).wiki
-                       var target: nullable WikiEntry = null
-                       if link.has("#") then
-                               var parts = link.split_with("#")
-                               link = parts.first
-                               anchor = parts.subarray(1, parts.length - 1).join("#")
-                       end
-                       if link.has("/") then
-                               target = wiki.lookup_entry_by_path(context, link.to_s)
-                       else
-                               target = wiki.lookup_entry_by_name(context, link.to_s)
-                               if target == null then
-                                       target = wiki.lookup_entry_by_title(context, link.to_s)
-                               end
-                       end
-                       if target != null then
-                               if name == null then name = target.title
-                               link = target.href_from(context)
-                       else
-                               var loc = context.src_path or else context.name
-                               wiki.message("Warning: unknown wikilink `{link}` (in {loc})", 0)
-                               v.add "class=\"broken\" "
+               var link = token.link
+               if link == null then return
+               if link.has("#") then
+                       var parts = link.split_with("#")
+                       link = parts.first
+                       anchor = parts.subarray(1, parts.length - 1).join("#")
+               end
+               if link.has("/") then
+                       target = wiki.lookup_entry_by_path(context, link.to_s)
+               else
+                       target = wiki.lookup_entry_by_name(context, link.to_s)
+                       if target == null then
+                               target = wiki.lookup_entry_by_title(context, link.to_s)
                        end
                end
+               v.add "<a "
+               var name = token.name
+               if target != null then
+                       if name == null then name = target.title
+                       link = target.url
+               else
+                       wiki.message("Warning: unknown wikilink `{link}` (in {context.src_path.as(not null)})", 0)
+                       v.add "class=\"broken\" "
+               end
                v.add "href=\""
                append_value(v, link)
                if anchor != null then append_value(v, "#{anchor}")
                v.add "\""
+               var comment = token.comment
                if comment != null and not comment.is_empty then
                        v.add " title=\""
                        append_value(v, comment)
diff --git a/lib/binary/serialization.nit b/lib/binary/serialization.nit
new file mode 100644 (file)
index 0000000..ba39c3b
--- /dev/null
@@ -0,0 +1,495 @@
+# 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 and deserialize Nit objects to binary streams
+#
+# The serialized data format uses a dictionary structure similar to BSON:
+#
+# ~~~raw
+# object = 0x01                   # null
+#        | 0x02 id attributes     # New object
+#        | 0x03 id                # Ref to object
+#        | 0x04 int64             # Int
+#        | 0x05 int8              # Bool (int8 != 0)
+#        | 0x06 int8              # Char
+#        | 0x07 double(64 bits)   # Float
+#        | 0x08 block             # String
+#        | 0x09 block             # NativeString
+#        | 0x0A flat_array;       # Array[nullable Object]
+#
+# block = int64 int8*;
+# cstring = int8* 0x00;
+# id = int64;
+#
+# attributes = attribute* 0x00;
+# attribute = cstring object;
+# ~~~
+module serialization
+
+import ::serialization::caching
+private import ::serialization::engine_tools
+import binary
+import more_collections
+
+# ---
+# Special bytes, marking the kind of objects in the stream and the end on an object
+
+private fun kind_null: Int do return 0x01
+private fun kind_object_new: Int do return 0x02
+private fun kind_object_ref: Int do return 0x03
+private fun kind_int: Int do return 0x04
+private fun kind_bool: Int do return 0x05
+private fun kind_char: Int do return 0x06
+private fun kind_float: Int do return 0x07
+private fun kind_string: Int do return 0x08
+private fun kind_native_string: Int do return 0x09
+private fun kind_flat_array: Int do return 0x0A
+
+private fun new_object_end: Int do return 0x00
+
+#---
+# Engines
+
+# Writes Nit objects to the binary `stream`
+#
+# The output can be deserialized with `BinaryDeserializer`.
+class BinarySerializer
+       super CachingSerializer
+
+       # Target writing stream
+       var stream: Writer is writable
+
+       redef fun serialize(object)
+       do
+               if object == null then
+                       stream.write_byte kind_null
+               else serialize_reference(object)
+       end
+
+       redef fun serialize_attribute(name, value)
+       do
+               stream.write_string name
+               super
+       end
+
+       redef fun serialize_reference(object)
+       do
+               if cache.has_object(object) then
+                       # if already serialized, add local reference
+                       var id = cache.id_for(object)
+                       stream.write_byte kind_object_ref
+                       stream.write_int64 id
+               else
+                       # serialize here
+                       object.serialize_to_binary self
+               end
+       end
+
+       # Write `collection` as a simple list of objects
+       private fun serialize_flat_array(collection: Collection[nullable Object])
+       do
+               stream.write_byte kind_flat_array
+               stream.write_int64 collection.length
+               for e in collection do
+                       if not try_to_serialize(e) then
+                               assert e != null
+                               warn "Element of {collection} is not serializable, it is a {e}"
+                               serialize null
+                       end
+               end
+       end
+end
+
+# Deserialize Nit objects from a binary `stream`
+#
+# Used with `BinarySerializer`.
+class BinaryDeserializer
+       super CachingDeserializer
+
+       # Source `Reader` stream
+       var stream: Reader
+
+       # Last encountered object reference id.
+       #
+       # See `cache.received`.
+       private var just_opened_id: nullable Int = null
+
+       # Tree of attributes, deserialized but not yet claimed
+       private var unclaimed_attributes = new UnrolledList[HashMap[String, nullable Object]]
+
+       # Read and deserialize the next attribute name and value
+       #
+       # A `peeked_char` can suffix the next attribute name.
+       #
+       # Returns `null` on error.
+       private fun deserialize_next_attribute(peeked_char: nullable Char):
+               nullable Couple[String, nullable Object]
+       do
+               # Try the next attribute
+               var next_attribute_name = stream.read_string
+               var next_object = deserialize_next_object
+
+               if stream.last_error != null then return null
+
+               if peeked_char != null then
+                       # Replace a char peeked to find an object end
+                       next_attribute_name = "{peeked_char}{next_attribute_name}"
+               end
+
+               return new Couple[String, nullable Object](next_attribute_name, next_object)
+       end
+
+       redef fun deserialize_attribute(name)
+       do
+               if unclaimed_attributes.last.keys.has(name) then
+                       # Pick in already deserialized attributes
+                       var value = unclaimed_attributes.last[name]
+                       unclaimed_attributes.last.keys.remove(name)
+                       return value
+               end
+
+               # Read attributes until we find the wanted one named `name`
+               loop
+                       var next = deserialize_next_attribute
+                       if next == null then
+                               # Error was already logged
+                               return null
+                       end
+
+                       var next_attribute_name = next.first
+                       var next_object = next.second
+
+                       # Got the wanted object
+                       if next_attribute_name == name then return next_object
+
+                       # An invalid attribute name is an heuristic for invalid data.
+                       # Hitting an object end marker will result in an empty string.
+                       assert next_attribute_name.is_valid_id else
+
+                               var error
+                               if next_attribute_name.is_empty then
+                                       # Reached the end of the object
+                                       error = new Error("Deserialization Error: Attributes '{name}' not in stream.")
+                               else
+                                       error = new Error("Deserialization Error: Got an invalid attribute name '{next_attribute_name}', expected '{name}'")
+                                       # TODO this is invalid data, break even on keep_going
+                               end
+                               errors.add error
+                               return null
+                       end
+
+                       # It's not the next attribute, put it aside
+                       unclaimed_attributes.last[next_attribute_name] = next_object
+               end
+       end
+
+       redef fun notify_of_creation(new_object)
+       do
+               var id = just_opened_id
+               if id == null then return
+               cache[id] = new_object
+       end
+
+       # Convert from simple Json object to Nit object
+       private fun deserialize_next_object: nullable Object
+       do
+               var kind = stream.read_byte
+               assert kind isa Int else
+                       # TODO break even on keep_going
+                       return null
+               end
+
+               # After this point, all stream reading errors are caught later
+
+               if kind == kind_null then return null
+               if kind == kind_int then return stream.read_int64
+               if kind == kind_bool then return stream.read_bool
+               if kind == kind_float then return stream.read_double
+               if kind == kind_char then return (stream.read_byte or else 0).ascii
+               if kind == kind_string then return stream.read_block
+               if kind == kind_native_string then return stream.read_block.to_cstring
+
+               if kind == kind_flat_array then
+                       # An array
+                       var length = stream.read_int64
+                       var array = new Array[nullable Object]
+                       for i in length.times do
+                               array.add deserialize_next_object
+                       end
+                       return array
+               end
+
+               if kind == kind_object_ref then
+                       # A reference
+                       var id = stream.read_int64
+                       if stream.last_error != null then return null
+
+                       if not cache.has_id(id) then
+                               errors.add new Error("Deserialization Error: Unknown reference to id #{id}")
+                               return null
+                       end
+                       return cache.object_for(id)
+               end
+
+               if kind == kind_object_new then
+                       # A new object
+                       var id = stream.read_int64
+                       if stream.last_error != null then return null
+
+                       if cache.has_id(id) then
+                               errors.add new Error("Deserialization Error: Duplicated use of reference #{id}")
+                               return null
+                       end
+
+                       var class_name = stream.read_string
+
+                       if stream.last_error != null then return null
+
+                       # Use the validity of the `class_name` as heuristic to detect invalid data
+                       if not class_name.is_valid_id then
+                               errors.add new Error("Deserialization Error: got an invalid class name '{class_name}'")
+                               return null
+                       end
+
+                       # Prepare opening a new object
+                       just_opened_id = id
+                       unclaimed_attributes.push new HashMap[String, nullable Object]
+
+                       var value = deserialize_class(class_name)
+
+                       # Check for the attributes end marker
+                       loop
+                               var next_byte = stream.read_byte
+                               if next_byte == new_object_end then break
+
+                               # Fetch an additional attribute, even if it isn't expected
+                               deserialize_next_attribute((next_byte or else 0).ascii)
+                       end
+
+                       # Close object
+                       unclaimed_attributes.pop
+                       just_opened_id = null
+
+                       return value
+               end
+
+               errors.add new Error("Deserialization Error: Unknown binary object kind `{kind}`")
+               # TODO fatal error and break even on keep_going
+               return null
+       end
+
+       redef fun deserialize
+       do
+               errors.clear
+
+               var value = deserialize_next_object
+
+               var error = stream.last_error
+               if error != null then
+                       errors.add error
+                       return true
+               end
+
+               return value
+       end
+end
+
+# ---
+# Services
+
+redef class Text
+       # Is `self` a valid identifier for a Nit class or property?
+       private fun is_valid_id: Bool
+       do
+               if trim.is_empty then return false
+
+               for c in chars do
+                       if not (c.is_letter or c.is_numeric or c == '[' or c == ']' or
+                               c == ' ' or c == ',' or c == '_') then return false
+               end
+
+               return true
+       end
+end
+
+# ---
+# Per class serialization behavior
+
+redef class Serializable
+       # Write the binary serialization header
+       #
+       # The header for a normal object is:
+       # 1. The kind of object on 8 bits, `0x01` for a new object.
+       # 2. The id of this object so it is not serialized more than once.
+       # 3. The name of the object type as a null terminated string.
+       private fun serialize_header_to_binary(v: BinarySerializer)
+       do
+               var id = v.cache.new_id_for(self)
+               v.stream.write_byte kind_object_new # is object intro
+               v.stream.write_int64 id
+               v.stream.write_string class_name
+       end
+
+       # Write a normal object to binary
+       private fun serialize_to_binary(v: BinarySerializer)
+       do
+               serialize_header_to_binary v
+               core_serialize_to v
+               v.stream.write_byte new_object_end
+       end
+end
+
+redef class Int
+       redef fun serialize_to_binary(v)
+       do
+               v.stream.write_byte kind_int
+               v.stream.write_int64 self
+       end
+end
+
+redef class Float
+       redef fun serialize_to_binary(v)
+       do
+               v.stream.write_byte kind_float
+               v.stream.write_double self
+       end
+end
+
+redef class Bool
+       redef fun serialize_to_binary(v)
+       do
+               v.stream.write_byte kind_bool
+               v.stream.write_bool self
+       end
+end
+
+redef class Char
+       redef fun serialize_to_binary(v)
+       do
+               v.stream.write_byte kind_char
+               v.stream.write_byte self.ascii
+       end
+end
+
+redef class String
+       redef fun serialize_to_binary(v)
+       do
+               v.stream.write_byte kind_string
+               v.stream.write_block self
+       end
+end
+
+redef class NativeString
+       redef fun serialize_to_binary(v)
+       do
+               v.stream.write_byte kind_native_string
+               v.stream.write_block to_s
+       end
+end
+
+redef class SimpleCollection[E]
+
+       redef fun serialize_to_binary(v)
+       do
+               serialize_header_to_binary v
+
+               v.stream.write_string "items"
+               v.serialize_flat_array self
+
+               v.stream.write_byte new_object_end
+       end
+
+       redef init from_deserializer(v)
+       do
+               # Give a chance to other engines, and defs
+               super
+
+               if v isa BinaryDeserializer then
+                       v.notify_of_creation self
+                       init
+
+                       var items = v.deserialize_attribute("items")
+                       assert items isa Array[nullable Object]
+                       for item in items do
+                               assert item isa E else
+                                       var item_type = "null"
+                                       if item != null then item_type = item.class_name
+
+                                       v.errors.add new Error("Deserialization Error: invalid type '{item_type}' for the collection '{class_name}'")
+                                       continue
+                               end
+
+                               add item
+                       end
+               end
+       end
+end
+
+redef class Map[K, V]
+       redef fun serialize_to_binary(v)
+       do
+               serialize_header_to_binary v
+
+               core_serialize_to v
+
+               v.stream.write_string "keys"
+               v.serialize_flat_array keys
+
+               v.stream.write_string "values"
+               v.serialize_flat_array values
+
+               v.stream.write_byte new_object_end
+       end
+
+       # Instantiate a new `Array` from its serialized representation.
+       redef init from_deserializer(v)
+       do
+               # Give a chance to other engines, and defs
+               super
+
+               if v isa BinaryDeserializer then
+                       v.notify_of_creation self
+
+                       init
+
+                       var keys = v.deserialize_attribute("keys")
+                       var values = v.deserialize_attribute("values")
+                       assert keys isa Array[nullable Object]
+                       assert values isa Array[nullable Object]
+
+                       for i in keys.length.times do
+                               var key = keys[i]
+                               var value = values[i]
+
+                               if not key isa K then
+                                       var item_type = "null"
+                                       if key != null then item_type = key.class_name
+
+                                       v.errors.add new Error("Deserialization Error: Invalid key type '{item_type}' for '{class_name}'")
+                                       continue
+                               end
+
+                               if not value isa V then
+                                       var item_type = "null"
+                                       if value != null then item_type = value.class_name
+
+                                       v.errors.add new Error("Deserialization Error: Invalid value type '{item_type}' for '{class_name}'")
+                                       continue
+                               end
+
+                               self[key] = value
+                       end
+               end
+       end
+end
diff --git a/lib/github/README.md b/lib/github/README.md
new file mode 100644 (file)
index 0000000..64a7e98
--- /dev/null
@@ -0,0 +1,71 @@
+# Nit wrapper for Github API
+
+This module provides a Nit object oriented interface to access the Github api.
+
+## Accessing the API
+
+[[doc: GithubAPI]]
+
+### Authentification
+
+[[doc: GithubAPI::auth]]
+
+Token can also be recovered from user config with `get_github_oauth`.
+
+[[doc: get_github_oauth]]
+
+### Retrieving user data
+
+[[doc: load_user]]
+[[doc: User]]
+[[list: User]]
+
+### Retrieving repo data
+
+[[doc: load_repo]]
+[[doc: Repo]]
+[[list: Repo]]
+
+### Other data
+
+[[list: api]]
+
+### Advanced uses
+
+#### Caching
+
+[[doc: cache]]
+
+#### Custom requests
+
+[[doc: GithubAPI::get]]
+
+#### Change the user agent
+
+[[doc: GithubAPI::user_agent]]
+
+#### Debugging
+
+[[doc: verbose_lvl]]
+
+#### Using with GitLab
+
+If URL scheme of GitLab API follows the one of Github API, it may be possible to
+configure this wrapper to use a custom URL.
+
+[[doc: api_url]]
+
+## Creating hooks
+
+Using this API you can create Github hooks able to respond to actions performed
+on a repository.
+
+[[doc: hooks]]
+
+## Dealing with events
+
+GithubAPI can trigger different events depending on the hook configuration.
+
+[[doc: GithubEvent]]
+
+[[list: github::events]]
index 6f7c7d9..0d5a67a 100644 (file)
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Nit object oriented interface to Github api.
+# Nit object oriented interface to [Github api](https://developer.github.com/v3/).
 #
 # This modules reifies Github API elements as Nit classes.
 #
@@ -21,11 +21,9 @@ module api
 
 import github_curl
 
-# Interface to Github REST API.
+# Client to Github API
 #
-# Used by all `GithubEntity` to perform requests.
-#
-# Usage:
+# To access the API you need an instance of a `GithubAPI` client.
 #
 # ~~~
 # # Get Github authentification token.
@@ -36,7 +34,7 @@ import github_curl
 # var api = new GithubAPI(token)
 # ~~~
 #
-# The API client allows to get Github API entities:
+# The API client allows you to get Github API entities.
 #
 # ~~~
 # var repo = api.load_repo("privat/nit")
@@ -49,9 +47,16 @@ import github_curl
 # ~~~
 class GithubAPI
 
-       # Github API OAuth token.
+       # Github API OAuth token
+       #
+       # To access your private ressources, you must
+       # [authenticate](https://developer.github.com/guides/basics-of-authentication/).
+       #
+       # For client applications, Github recommands to use the
+       # [OAuth tokens](https://developer.github.com/v3/oauth/) authentification method.
+       #
+       #
        #
-       # This token is used to authenticate the application on Github API.
        # Be aware that there is [rate limits](https://developer.github.com/v3/rate_limit/)
        # associated to the key.
        var auth: String
@@ -140,9 +145,9 @@ class GithubAPI
                return res.as(JsonObject)
        end
 
-       # Get the Github user with `login`.
+       # Get the Github user with `login`
        #
-       # Returns `null` if the user cannot be found.
+       # Loads the `User` from the API or returns `null` if the user cannot be found.
        #
        #     var api = new GithubAPI(get_github_oauth)
        #     var user = api.load_user("Morriar")
@@ -154,7 +159,7 @@ class GithubAPI
 
        # Get the Github repo with `full_name`.
        #
-       # Returns `null` if the repo cannot be found.
+       # Loads the `Repo` from the API or returns `null` if the repo cannot be found.
        #
        #     var api = new GithubAPI(get_github_oauth)
        #     var repo = api.load_repo("privat/nit")
@@ -349,11 +354,10 @@ abstract class GithubEntity
        fun html_url: String do return json["html_url"].to_s
 end
 
-# A Github user.
+# A Github user
 #
+# Provides access to [Github user data](https://developer.github.com/v3/users/).
 # Should be accessed from `GithubAPI::load_user`.
-#
-# See <https://developer.github.com/v3/users/>.
 class User
        super GithubEntity
 
@@ -374,9 +378,8 @@ end
 
 # A Github repository.
 #
+# Provides access to [Github repo data](https://developer.github.com/v3/repos/).
 # Should be accessed from `GithubAPI::load_repo`.
-#
-# See <https://developer.github.com/v3/repos/>.
 class Repo
        super GithubEntity
 
index 9bc63a8..d4d5904 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Github API related features.
+# Nit wrapper for Github API
+#
+# This module provides a Nit object oriented interface to access the
+# [Github api](https://developer.github.com/v3/).
 module github
 
 import cache
index 62776e6..dea87e4 100644 (file)
@@ -131,8 +131,10 @@ class GithubError
        redef fun to_s do return "[{name}] {super}"
 end
 
+# Gets the Github token from `git` configuration
+#
 # Return the value of `git config --get github.oauthtoken`
-# return "" if no such a key
+# or `""` if no key exists.
 fun get_github_oauth: String
 do
        var p = new ProcessReader("git", "config", "--get", "github.oauthtoken")
index eac11c2..854eed5 100644 (file)
@@ -396,7 +396,11 @@ class MarkdownProcessor
                        c2 = ' '
                end
 
-               var loc = text.pos_to_loc(pos)
+               var loc = new MDLocation(
+                       current_loc.line_start,
+                       current_loc.column_start + pos,
+                       current_loc.line_start,
+                       current_loc.column_start + pos)
 
                if c == '*' then
                        if c1 == '*' then
@@ -476,6 +480,12 @@ class MarkdownProcessor
                end
                return -1
        end
+
+       # Location used for next parsed token.
+       #
+       # This location can be changed by the emitter to adjust with `\n` found
+       # in the input.
+       private fun current_loc: MDLocation do return emitter.current_loc
 end
 
 # Emit output corresponding to blocks content.
@@ -519,15 +529,19 @@ class MarkdownEmitter
        # Transform and emit mardown text
        fun emit_text(text: Text) do emit_text_until(text, 0, null)
 
-       # Transform and emit mardown text starting at `from` and
+       # Transform and emit mardown text starting at `start` and
        # until a token with the same type as `token` is found.
-       # Go until the end of text if `token` is null.
+       # Go until the end of `text` if `token` is null.
        fun emit_text_until(text: Text, start: Int, token: nullable Token): Int do
                var old_text = current_text
                var old_pos = current_pos
                current_text = text
                current_pos = start
                while current_pos < text.length do
+                       if text[current_pos] == '\n' then
+                               current_loc.line_start += 1
+                               current_loc.column_start = -current_pos
+                       end
                        var mt = processor.token_at(text, current_pos)
                        if (token != null and not token isa TokenNone) and
                        (mt.is_same_type(token) or
@@ -570,6 +584,21 @@ class MarkdownEmitter
                return buffer_stack.last
        end
 
+       # Stacked locations.
+       private var loc_stack = new List[MDLocation]
+
+       # Push a new MDLocation on the stack.
+       private fun push_loc(location: MDLocation) do loc_stack.add location
+
+       # Pop the last buffer.
+       private fun pop_loc: MDLocation do return loc_stack.pop
+
+       # Current output buffer.
+       private fun current_loc: MDLocation do
+               assert not loc_stack.is_empty
+               return loc_stack.last
+       end
+
        # Append `e` to current buffer.
        fun add(e: Writable) do
                if e isa Text then
@@ -900,6 +929,11 @@ class MDLocation
        var column_end: Int
 
        redef fun to_s do return "{line_start},{column_start}--{line_end},{column_end}"
+
+       # Return a copy of `self`.
+       fun copy: MDLocation do
+               return new MDLocation(line_start, column_start, line_end, column_end)
+       end
 end
 
 # A block of markdown lines.
@@ -1128,7 +1162,9 @@ abstract class Block
        fun emit_blocks(v: MarkdownEmitter) do
                var block = self.block.first_block
                while block != null do
+                       v.push_loc(block.location)
                        block.kind.emit(v)
+                       v.pop_loc
                        block = block.next
                end
        end
@@ -1209,7 +1245,15 @@ end
 class BlockHeadline
        super Block
 
-       redef fun emit(v) do v.decorator.add_headline(v, self)
+       redef fun emit(v) do
+               var loc = block.location.copy
+               loc.column_start += start
+               v.push_loc(loc)
+               v.decorator.add_headline(v, self)
+               v.pop_loc
+       end
+
+       private var start = 0
 
        # Depth of the headline used to determine the headline level.
        var depth = 0
@@ -1238,6 +1282,7 @@ class BlockHeadline
                        line.leading = 0
                        line.trailing = 0
                end
+               self.start = start
                depth = level.min(6)
        end
 end
@@ -2495,24 +2540,6 @@ redef class Text
                return null
        end
 
-       # Init a `MDLocation` instance at `pos` in `self`.
-       private fun pos_to_loc(pos: Int): MDLocation do
-               assert pos <= length
-               var line = 1
-               var col = 0
-               var i = 0
-               while i <= pos do
-                       col += 1
-                       var c = self[i]
-                       if c == '\n' then
-                               line +=1
-                               col = 0
-                       end
-                       i +=1
-               end
-               return new MDLocation(line, col, line, col)
-       end
-
        # Is `self` an unsafe HTML element?
        private fun is_html_unsafe: Bool do return html_unsafe_tags.has(self.write_to_string)
 
index c6ea559..53c355f 100644 (file)
@@ -2800,6 +2800,26 @@ class TestTokenLocation
                        "TokenLink at 4,1--4,1"]
                (new TestTokenProcessor(stack)).process(string)
        end
+
+       fun test_token_location4 do
+               var string = "**Hello**\n\n`World`"
+               var stack =  [
+                       "TokenStrongStar at 1,1--1,1",
+                       "TokenStrongStar at 1,8--1,8",
+                       "TokenCodeSingle at 3,1--3,1",
+                       "TokenCodeSingle at 3,7--3,7"]
+               (new TestTokenProcessor(stack)).process(string)
+       end
+
+       fun test_token_location5 do
+               var string = "# *Title1*\n\n# *Title2*"
+               var stack =  [
+                       "TokenEmStar at 1,3--1,3",
+                       "TokenEmStar at 1,10--1,10",
+                       "TokenEmStar at 3,3--3,3",
+                       "TokenEmStar at 3,10--3,10"]
+               (new TestTokenProcessor(stack)).process(string)
+       end
 end
 
 class TestTokenProcessor
@@ -2811,8 +2831,10 @@ class TestTokenProcessor
                var token = super
                if token isa TokenNone then return token
                var res = "{token.class_name} at {token.location}"
-               print res
                var exp = test_stack.shift
+               print ""
+               print "EXP {exp}"
+               print "RES {res}"
                assert exp == res
                return token
        end
@@ -2852,6 +2874,15 @@ some code
                proc.emitter.decorator = new TestBlockDecorator(stack)
                proc.process(string)
        end
+
+       fun test_block_location3 do
+               var stack = [
+                       "BlockHeadline: 1,1--1,8",
+                       "BlockHeadline: 3,1--3,10"]
+               var string ="""# Title\n\n## Title 2"""
+               proc.emitter.decorator = new TestBlockDecorator(stack)
+               proc.process(string)
+       end
 end
 
 class TestBlockDecorator
diff --git a/lib/markdown/test_wikilinks.nit b/lib/markdown/test_wikilinks.nit
new file mode 100644 (file)
index 0000000..5c4fb7e
--- /dev/null
@@ -0,0 +1,73 @@
+# 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.
+
+# Test suites for module `markdown`
+module test_wikilinks is test_suite
+
+import test_markdown
+import wikilinks
+
+class TestTokenWikilink
+       super TestSuite
+
+       fun test_token_location1 do
+               var string = "[[wikilink]]"
+               var stack =  ["TokenWikiLink at 1,1--1,1"]
+               (new TestTokenProcessor(stack)).process(string)
+       end
+
+       fun test_token_location2 do
+               var string = "Hello [[World]]"
+               var stack =  ["TokenWikiLink at 1,7--1,7"]
+               (new TestTokenProcessor(stack)).process(string)
+       end
+
+       fun test_token_location3 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
+               var string = "[[link1]]\n\n[[link2]]"
+               var stack =  [
+                       "TokenWikiLink at 1,1--1,1",
+                       "TokenWikiLink at 3,1--3,1"]
+               (new TestTokenProcessor(stack)).process(string)
+       end
+
+       fun test_token_location5 do
+               var string = "[[link1]]\n[[link2]]"
+               var stack =  [
+                       "TokenWikiLink at 1,1--1,1",
+                       "TokenWikiLink at 2,1--2,1"]
+               (new TestTokenProcessor(stack)).process(string)
+       end
+
+       fun test_token_location6 do
+               var string = """
+[[doc: github]]
+
+[[loollll]]
+
+## Accessing the API
+
+[[doc: GithubAPI]]"""
+               var stack =  [
+                       "TokenWikiLink at 1,1--1,1",
+                       "TokenWikiLink at 3,1--3,1",
+                       "TokenWikiLink at 7,1--7,1"]
+               (new TestTokenProcessor(stack)).process(string)
+       end
+end
index 28fd1e0..9f3097c 100644 (file)
@@ -41,11 +41,11 @@ end
 redef class Decorator
 
        # Renders a `[[wikilink]]` item.
-       fun add_wikilink(v: EMITTER, link: Text, name, comment: nullable Text) do
-               if name != null then
-                       v.add "[[{name}|{link}]]"
+       fun add_wikilink(v: EMITTER, token: TokenWikiLink) do
+               if token.name != null then
+                       v.add "[[{token.name.to_s}|{token.link.to_s}]]"
                else
-                       v.add "[[{link}]]"
+                       v.add "[[{token.link.to_s}]]"
                end
        end
 end
@@ -67,7 +67,7 @@ class TokenWikiLink
        super TokenLink
 
        redef fun emit_hyper(v) do
-               v.decorator.add_wikilink(v, link.as(not null), name, comment)
+               v.decorator.add_wikilink(v, self)
        end
 
        redef fun check_link(v, out, start, token) do
index faa370c..3ee92fe 100644 (file)
@@ -152,7 +152,7 @@ class Opengles1Display
                                EGL_RED_SIZE, 8,
                                EGL_NONE
                };
-               EGLint w, h, dummy, format;
+               EGLint w, h, format;
                EGLint numConfigs;
                EGLConfig config;
                EGLSurface surface;
index 58864e4..ea035b3 100644 (file)
@@ -66,7 +66,7 @@ interface KeyEvent
        fun is_down: Bool is abstract
 
        # Key is currently up?
-       fun is_up: Bool is abstract
+       fun is_up: Bool do return not is_down
 
        # Key is the up arrow key?
        fun is_arrow_up: Bool is abstract
index 019dc67..9fb05bb 100644 (file)
@@ -97,6 +97,13 @@ class HashMap2[K1, K2, V]
                level2.keys.remove(k2)
        end
 
+       # Is there a value at `k1, k2`?
+       fun has(k1: K1, k2: K2): Bool
+       do
+               if not level1.keys.has(k1) then return false
+               return level1[k1].keys.has(k2)
+       end
+
        # Remove all items
        fun clear do level1.clear
 end
@@ -145,6 +152,13 @@ class HashMap3[K1, K2, K3, V]
                level2.remove_at(k2, k3)
        end
 
+       # Is there a value at `k1, k2, k3`?
+       fun has(k1: K1, k2: K2, k3: K3): Bool
+       do
+               if not level1.keys.has(k1) then return false
+               return level1[k1].has(k2, k3)
+       end
+
        # Remove all items
        fun clear do level1.clear
 end
diff --git a/lib/performance_analysis.nit b/lib/performance_analysis.nit
new file mode 100644 (file)
index 0000000..3ae01d9
--- /dev/null
@@ -0,0 +1,98 @@
+# 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.
+
+# Services to gather information on the performance of events by categories
+#
+# Provides `PerfMap` to manage all the categories and
+# `PerfEntry` for per-category statistics.
+#
+# ~~~
+# for i in 100.times do
+#     var clock = new Clock
+#
+#     # Do some "work" here
+#     nanosleep(0, 1000000)
+#
+#     # Register the perf
+#     sys.perfs["sleep 1ms"].add clock.lapse
+#
+#     # Do some other "work" here
+#     nanosleep(0, 5000000)
+#
+#     # Register the perf
+#     sys.perfs["sleep 5ms"].add clock.lapse
+# end
+#
+# assert sys.perfs["sleep 1ms"].count == 100
+# assert sys.perfs["sleep 1ms"].avg.is_approx(0.001, 0.0001)
+# assert sys.perfs["sleep 5ms"].avg.is_approx(0.005, 0.0005)
+# ~~~
+module performance_analysis
+
+import realtime
+
+redef class Sys
+       # Main `PerfMap` available by default
+       var perfs = new PerfMap
+end
+
+# Collection of statistics on many events
+class PerfMap
+       super HashMap[String, PerfEntry]
+
+       redef fun provide_default_value(key)
+       do
+               if not key isa String then return super
+
+               var ts = new PerfEntry(key)
+               self[key] = ts
+               return ts
+       end
+
+       redef fun to_s do return "* " + join(": ", "\n* ")
+end
+
+# Statistics on wall clock execution time of a category of events by `name`
+class PerfEntry
+
+       # Name of the category
+       var name: String
+
+       # Shortest execution time of registered events
+       var min = 0.0
+
+       # Longest execution time of registered events
+       var max = 0.0
+
+       # Average execution time of registered events
+       var avg = 0.0
+
+       # Number of registered events
+       var count = 0
+
+       # Register a new event execution time with a `Timespec`
+       fun add(lapse: Timespec) do add_float lapse.to_f
+
+       # Register a new event execution time in seconds using a `Float`
+       fun add_float(time: Float)
+       do
+               if time.to_f < min.to_f or count == 0 then min = time
+               if time.to_f > max.to_f then max = time
+
+               avg = (avg * count.to_f + time) / (count+1).to_f
+               count += 1
+       end
+
+       redef fun to_s do return "min {min}, max {max}, avg {avg}, count {count}"
+end
index 900063f..bbff9fc 100644 (file)
@@ -155,7 +155,11 @@ end
 private extern class NativePthreadMutex in "C" `{ pthread_mutex_t * `}
        new (attr: NativePthreadMutexAttr) `{
                pthread_mutex_t *mutex = malloc(sizeof(pthread_mutex_t));
-               int res = pthread_mutex_init(mutex, attr);
+               int r = pthread_mutex_init(mutex, attr);
+               if (r != 0) {
+                       free(mutex);
+                       return NULL;
+               }
                return mutex;
        `}
 
@@ -169,7 +173,11 @@ end
 private extern class NativePthreadMutexAttr in "C" `{ pthread_mutexattr_t * `}
        new `{
                pthread_mutexattr_t *attr = malloc(sizeof(pthread_mutexattr_t));
-               int res = pthread_mutexattr_init(attr);
+               int r = pthread_mutexattr_init(attr);
+               if (r != 0) {
+                       free(attr);
+                       return NULL;
+               }
                return attr;
        `}
 
@@ -188,7 +196,11 @@ end
 private extern class NativePthreadKey in "C" `{ pthread_key_t * `}
        new `{
                pthread_key_t *key = malloc(sizeof(pthread_key_t));
-               int res = pthread_key_create(key, NULL);
+               int r = pthread_key_create(key, NULL);
+               if (r != 0) {
+                       free(key);
+                       return NULL;
+               }
                return key;
        `}
 
index 960a3cb..dc24c99 100644 (file)
@@ -72,8 +72,23 @@ extern class Timespec `{struct timespec*`}
                return self->tv_nsec;
        `}
 
-       # Seconds in Float
-       # Loss of precision but great to print
+       # Elapsed time in microseconds, with both whole seconds and the rest
+       #
+       # May cause an `Int` overflow, use only with a low number of seconds.
+       fun microsec: Int `{
+               return self->tv_sec*1000000 + self->tv_nsec/1000;
+       `}
+
+       # Elapsed time in milliseconds, with both whole seconds and the rest
+       #
+       # May cause an `Int` overflow, use only with a low number of seconds.
+       fun millisec: Int `{
+               return self->tv_sec*1000 + self->tv_nsec/1000000;
+       `}
+
+       # Number of seconds as a `Float`
+       #
+       # Incurs a loss of precision, but the result is pretty to print.
        fun to_f: Float do return sec.to_f + nanosec.to_f / 1000000000.0
 
        redef fun to_s do return "{to_f}s"
index b0dc728..9408af0 100644 (file)
@@ -106,7 +106,7 @@ abstract class Deserializer
        # 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: String): Object do
+       protected fun deserialize_class(class_name: String): nullable Object do
                return deserialize_class_intern(class_name)
        end
 
@@ -115,9 +115,9 @@ abstract class Deserializer
        # 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: String): Object do
-               print "Error: doesn't know how to deserialize class \"{class_name}\""
-               abort
+       protected fun deserialize_class_intern(class_name: String): 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?
index c866351..29efa62 100644 (file)
@@ -111,7 +111,7 @@ abstract class DocComposite
        var id: String is writable
 
        # Item title if any.
-       var title: nullable String
+       var title: nullable String is writable
 
        # Does `self` have a `parent`?
        fun is_root: Bool do return parent == null
diff --git a/src/doc/doc_commands.nit b/src/doc/doc_commands.nit
new file mode 100644 (file)
index 0000000..e69bb38
--- /dev/null
@@ -0,0 +1,155 @@
+# 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.
+
+# Parsing of commands understood by documentation tools.
+#
+# This can be through:
+# * `nitx` commands like `code: MEntity::name`
+# * `nitdoc` wikilinks like `[[doc: MEntity::name]]`
+module doc_commands
+
+import doc_base
+
+# A command aimed at a documentation tool like `nitdoc` or `nitx`.
+#
+# `DocCommand` are generally of the form `command: args`.
+interface DocCommand
+
+       # Original command string.
+       fun string: String is abstract
+
+       # Command name.
+       fun name: String is abstract
+
+       # Command arguments.
+       #
+       # FIXME: define a syntax
+       fun args: Array[String] is abstract
+
+       # Command factory.
+       #
+       # Returns a concrete instance of `DocCommand` depending on the string.
+       new(command_string: String) do
+               if command_string.has_prefix("doc:") then
+                       return new ArticleCommand(command_string)
+               else if command_string.has_prefix("comment:") then
+                       return new CommentCommand(command_string)
+               else if command_string.has_prefix("list:") then
+                       return new ListCommand(command_string)
+               else if command_string.has_prefix("param:") then
+                       return new ParamCommand(command_string)
+               else if command_string.has_prefix("return:") then
+                       return new ReturnCommand(command_string)
+               else if command_string.has_prefix("new:") then
+                       return new NewCommand(command_string)
+               else if command_string.has_prefix("call:") then
+                       return new CallCommand(command_string)
+               else if command_string.has_prefix("code:") then
+                       return new CodeCommand(command_string)
+               end
+               return new UnknownCommand(command_string)
+       end
+
+       redef fun to_s do return string
+end
+
+# Used to factorize initialization of DocCommands.
+abstract class AbstractDocCommand
+       super DocCommand
+
+       redef var string
+       redef var name is noinit
+       redef var args = new Array[String]
+
+       init do
+               # parse command
+               var str = new FlatBuffer
+               var i = 0
+               while i < string.length do
+                       var c = string[i]
+                       i += 1
+                       if c == ':' then break
+                       str.add c
+               end
+               name = str.write_to_string
+               # parse args
+               args.add string.substring_from(i).trim
+       end
+end
+
+# A `DocCommand` not recognized by documentation tools.
+#
+# Used to provide warnings or any other behavior for unexisting commands.
+class UnknownCommand
+       super AbstractDocCommand
+end
+
+# A `DocCommand` that includes the documentation article of a `MEntity`.
+#
+# Syntax: `doc: MEntity::name`.
+class ArticleCommand
+       super AbstractDocCommand
+end
+
+# A `DocCommand` that includes the MDoc of a `MEntity`.
+#
+# Syntax: `comment: MEntity::name`.
+class CommentCommand
+       super AbstractDocCommand
+end
+
+# A `DocCommand` that includes a list of something.
+#
+# Syntax: `list:kind: <arg>`.
+class ListCommand
+       super AbstractDocCommand
+end
+
+# A `DocCommand` that includes the list of methods tanking a `MType` as parameter.
+#
+# Syntax: `param: MType`.
+class ParamCommand
+       super AbstractDocCommand
+end
+
+# A `DocCommand` that includes the list of methods returning a `MType` as parameter.
+#
+# Syntax: `param: MType`.
+class ReturnCommand
+       super AbstractDocCommand
+end
+
+# A `DocCommand` that includes the list of methods creating new instances of a specific `MType`
+#
+# Syntax: `new: MType`.
+class NewCommand
+       super AbstractDocCommand
+end
+
+# A `DocCommand` that includes the list of methods calling a specific `MProperty`.
+#
+# Syntax: `call: MEntity::name`.
+class CallCommand
+       super AbstractDocCommand
+end
+
+# A `DocCommand` that includes the source code of a `MEntity`.
+#
+# Syntax:
+# * `code: MEntity::name`
+# * `./src/file.nit` to include source code from a file.
+# * `./src/file.nit:1,2--3,4` to select code between positions.
+class CodeCommand
+       super AbstractDocCommand
+end
index e1a5348..5284dc1 100644 (file)
@@ -20,11 +20,19 @@ import highlight
 private import parser_util
 
 redef class MDoc
-       # Comment synopsys HTML escaped
-       var short_comment: String is lazy do return content.first.html_escape
 
-       # Full comment HTML escaped
-       var full_comment: String is lazy do return content.join("\n").html_escape
+       # Synopsis HTML escaped.
+       var synopsis: String is lazy do return content.first.html_escape
+
+       # Comment without synopsis HTML escaped
+       var comment: String is lazy do
+               var lines = content.to_a
+               if not lines.is_empty then lines.shift
+               return content.join("\n").html_escape
+       end
+
+       # Full comment HTML escaped.
+       var documentation: String is lazy do return content.join("\n").html_escape
 
        private var markdown_proc: MarkdownProcessor is lazy do
                return original_mentity.model.nitdoc_md_processor
@@ -34,8 +42,8 @@ redef class MDoc
                return original_mentity.model.nitdoc_inline_processor
        end
 
-       # Synopsys in a template
-       var tpl_short_comment: Writable is lazy do
+       # Renders the synopsis as a HTML comment block.
+       var html_synopsis: Writable is lazy do
                var res = new Template
                var syn = inline_proc.process(content.first)
                res.add "<span class=\"synopsys nitdoc\">{syn}</span>"
@@ -43,17 +51,28 @@ redef class MDoc
 
        end
 
-       # Full comment in a template
-       var tpl_comment: Writable is lazy do
-               var res = new Template
+       # Renders the comment without the synopsis as a HTML comment block.
+       var html_comment: Writable is lazy do
                var lines = content.to_a
+               if not lines.is_empty then lines.shift
+               return lines_to_html(lines)
+       end
+
+       # Renders the synopsis and the comment as a HTML comment block.
+       var html_documentation: Writable is lazy do return lines_to_html(content.to_a)
+
+       # Renders markdown line as a HTML comment block.
+       private fun lines_to_html(lines: Array[String]): Writable do
+               var res = new Template
                res.add "<div class=\"nitdoc\">"
                # do not use DocUnit as synopsys
-               if not content.first.has_prefix("    ") and
-                  not content.first.has_prefix("\t") then
-                       # parse synopsys
-                       var syn = inline_proc.process(lines.shift)
-                       res.add "<p class=\"synopsys\">{syn}</p>"
+               if not lines.is_empty then
+                       if not lines.first.has_prefix("    ") and
+                          not lines.first.has_prefix("\t") then
+                               # parse synopsys
+                               var syn = inline_proc.process(lines.shift)
+                               res.add "<p class=\"synopsys\">{syn}</p>"
+                       end
                end
                # check for annotations
                for i in [0 .. lines.length[ do
@@ -70,6 +89,7 @@ redef class MDoc
                res.add markdown_proc.process(lines.join("\n"))
                res.add "</div>"
                return res
+
        end
 end
 
@@ -185,7 +205,7 @@ end
 
 redef class Model
        # Get a markdown processor for Nitdoc comments.
-       private var nitdoc_md_processor: MarkdownProcessor is lazy do
+       var nitdoc_md_processor: MarkdownProcessor is lazy do
                var proc = new MarkdownProcessor
                proc.emitter.decorator = new NitdocDecorator
                return proc
@@ -194,7 +214,7 @@ redef class Model
        # Get a markdown inline processor for Nitdoc comments.
        #
        # This processor is specificaly designed to inlinable doc elements like synopsys.
-       private var nitdoc_inline_processor: MarkdownProcessor is lazy do
+       var nitdoc_inline_processor: MarkdownProcessor is lazy do
                var proc = new MarkdownProcessor
                proc.emitter.decorator = new InlineDecorator
                return proc
index b6d08dc..9734ed3 100644 (file)
@@ -19,6 +19,7 @@
 module doc_console
 
 import semantize
+import doc_commands
 import doc_extract
 import doc_poset
 import doc::console_templates
@@ -89,7 +90,7 @@ class Nitx
 
        # Processes the query string and performs it.
        fun do_query(str: String) do
-               var query = new NitxQuery(str)
+               var query = new DocCommand(str)
                if query isa NitxCommand then
                        query.execute(self)
                        return
@@ -100,49 +101,19 @@ class Nitx
        end
 end
 
-# A query performed on Nitx.
-#
-# Queries are responsible to collect matching results and render them as a
-# DocPage.
-#
-# Used as a factory to concrete instances.
-interface NitxQuery
-
-       # Original query string.
-       fun query_string: String is abstract
+redef interface DocCommand
 
-       # Query factory.
-       #
-       # Will return a concrete instance of NitxQuery.
-       new(query_string: String) do
+       redef new(query_string) do
                if query_string == ":q" then
                        return new NitxQuit
                else if query_string == ":h" then
                        return new NitxHelp
-               else if query_string.has_prefix("comment:") then
-                       return new CommentQuery(query_string)
-               else if query_string.has_prefix("doc:") then
-                       return new DocQuery(query_string)
-               else if query_string.has_prefix("param:") then
-                       return new ParamQuery(query_string)
-               else if query_string.has_prefix("return:") then
-                       return new ReturnQuery(query_string)
-               else if query_string.has_prefix("new:") then
-                       return new NewQuery(query_string)
-               else if query_string.has_prefix("call:") then
-                       return new CallQuery(query_string)
-               else if query_string.has_prefix("code:") then
-                       return new CodeQuery(query_string)
-               else if query_string.has_prefix("parents:") then
-                       return new ParentsQuery(query_string)
-               else if query_string.has_prefix("ancestors:") then
-                       return new AncestorsQuery(query_string)
-               else if query_string.has_prefix("children:") then
-                       return new ChildrenQuery(query_string)
-               else if query_string.has_prefix("descendants:") then
-                       return new DescendantsQuery(query_string)
                end
-               return new CommentQuery("comment: {query_string}")
+               var cmd = super(query_string)
+               if cmd isa UnknownCommand then
+                       return new CommentCommand("comment: {query_string}")
+               end
+               return cmd
        end
 
        # Looks up the `doc` model and returns possible matches.
@@ -151,54 +122,22 @@ interface NitxQuery
        # Pretty prints the results for the console.
        fun make_results(nitx: Nitx, results: Array[NitxMatch]): DocPage do
                var page = new DocPage("results", "Results")
-               page.root.add_child(new QueryResultArticle("results.article", "Results", self, results))
+               page.root.add_child(new QueryResultArticle("results", "Results", self, results))
                return page
        end
-
-       redef fun to_s do return query_string
 end
 
-# Something that matches a `NitxQuery`.
+# Something that matches a `DocCommand`.
 abstract class NitxMatch
 
        # Query matched by `self`.
-       var query: NitxQuery
+       var query: DocCommand
 
        # Pretty prints `self` for console.
        fun make_list_item: String is abstract
 end
 
-# A query that contains a meta command.
-#
-# In Nitx, commands are written such as `command: args...`.
-abstract class MetaQuery
-       super NitxQuery
-
-       redef var query_string
-
-       # Meta command used.
-       var command: String is noinit
-
-       # Arguments passed to the `command`.
-       var args = new Array[String]
-
-       init do
-               # parse command
-               var str = new FlatBuffer
-               var i = 0
-               while i < query_string.length do
-                       var c = query_string[i]
-                       i += 1
-                       if c == ':' then break
-                       str.add c
-               end
-               command = str.write_to_string
-               # parse args
-               args.add query_string.substring_from(i).trim
-       end
-end
-
-# A match between a `NitxQuery` and a `MEntity`.
+# A match between a `DocCommand` and a `MEntity`.
 class MEntityMatch
        super NitxMatch
 
@@ -208,10 +147,7 @@ class MEntityMatch
        redef fun make_list_item do return mentity.cs_list_item
 end
 
-# A query to search a `MEntity` comment by its name or namespace.
-class CommentQuery
-       super MetaQuery
-
+redef class CommentCommand
        redef fun perform(nitx, doc) do
                var name = args.first
                var res = new Array[NitxMatch]
@@ -226,8 +162,8 @@ class CommentQuery
                if len == 1 then
                        var res = results.first.as(MEntityMatch)
                        var mentity = res.mentity
-                       var page = new DocPage("results", "Results")
-                       var article = new DefinitionArticle("results.article", "Results", mentity)
+                       var page = new DocPage("resultats", "Results")
+                       var article = new DefinitionArticle("results", "Results", mentity)
                        article.cs_title = mentity.name
                        article.cs_subtitle = mentity.cs_declaration
                        page.root.add_child article
@@ -239,9 +175,7 @@ class CommentQuery
 end
 
 # A query to search signatures using a specific `MType` as parameter.
-class ParamQuery
-       super MetaQuery
-
+redef class ParamCommand
        redef fun perform(nitx, doc) do
                var res = new Array[NitxMatch]
                var mtype_name = args.first
@@ -261,9 +195,7 @@ class ParamQuery
 end
 
 # A query to search signatures using a specific `MType` as return.
-class ReturnQuery
-       super MetaQuery
-
+redef class ReturnCommand
        redef fun perform(nitx, doc) do
                var res = new Array[NitxMatch]
                var mtype_name = args.first
@@ -282,9 +214,7 @@ class ReturnQuery
 end
 
 # A query to search methods creating new instances of a specific `MType`.
-class NewQuery
-       super MetaQuery
-
+redef class NewCommand
        redef fun perform(nitx, doc) do
                var res = new Array[NitxMatch]
                var mtype_name = args.first
@@ -302,9 +232,7 @@ class NewQuery
 end
 
 # A query to search methods calling a specific `MProperty`.
-class CallQuery
-       super MetaQuery
-
+redef class CallCommand
        redef fun perform(nitx, doc) do
                var res = new Array[NitxMatch]
                var mprop_name = args.first
@@ -323,9 +251,7 @@ class CallQuery
 end
 
 # A query to search a Nitdoc documentation page by its name.
-class DocQuery
-       super MetaQuery
-
+redef class ArticleCommand
        redef fun perform(nitx, doc) do
                var res = new Array[NitxMatch]
                var name = args.first
@@ -377,7 +303,7 @@ end
 # It actually searches for pages about the mentity and extracts the
 # pre-calculated hierarchies by the `doc_post` phase.
 abstract class HierarchiesQuery
-       super DocQuery
+       super DocCommand
 
        redef fun make_results(nitx, results) do
                var page = new DocPage("hierarchy", "Hierarchy")
@@ -443,9 +369,7 @@ class DescendantsQuery
 end
 
 # A query to search source code from a file name.
-class CodeQuery
-       super MetaQuery
-
+redef class CodeCommand
        # FIXME refactor this!
        redef fun perform(nitx, doc) do
                var res = new Array[NitxMatch]
@@ -470,7 +394,7 @@ class CodeQuery
        redef fun make_results(nitx, results) do
                var page = new DocPage("results", "Code Results")
                for res in results do
-                       page.add new CodeQueryArticle("results.article", "Results", self, res.as(CodeMatch))
+                       page.add new CodeQueryArticle("results", "Results", self, res.as(CodeMatch))
                end
                return page
        end
@@ -495,7 +419,7 @@ end
 # These commands are prefixed with `:` and are used to control the execution of
 # `nitx` like displaying the help or quiting.
 interface NitxCommand
-       super NitxQuery
+       super DocCommand
 
        # Executes the command.
        fun execute(nitx: Nitx) is abstract
@@ -548,7 +472,7 @@ private class MPropertyCallVisitor
        do
                node.visit_all(self)
                if not node isa ASendExpr then return
-               calls.add node.callsite.mproperty
+               calls.add node.callsite.as(not null).mproperty
        end
 end
 
@@ -559,7 +483,7 @@ private class QueryResultArticle
        super DocArticle
 
        # Query linked to the results to display.
-       var query: NitxQuery
+       var query: DocCommand
 
        # Results to display.
        var results: Array[NitxMatch]
@@ -567,9 +491,9 @@ private class QueryResultArticle
        redef fun render_title do
                var len = results.length
                if len == 0 then
-                       add "No result found for '{query.query_string}'..."
+                       add "No result found for '{query.string}'..."
                else
-                       add "# {len} result(s) for '{query.query_string}'".green.bold
+                       add "# {len} result(s) for '{query.string}'".green.bold
                end
        end
 
@@ -587,7 +511,7 @@ private class CodeQueryArticle
        super DocArticle
 
        # The query linked to the result to display.
-       var query: NitxQuery
+       var query: DocCommand
 
        # The result to display.
        var result: CodeMatch
index afeba88..722724b 100644 (file)
@@ -238,7 +238,13 @@ redef class SearchPage
 end
 
 redef class MEntityPage
-       redef var html_url is lazy do return mentity.nitdoc_url
+       redef var html_url is lazy do
+               if mentity isa MGroup and mentity.mdoc != null then
+                       return "api_{mentity.nitdoc_url}"
+               end
+               return mentity.nitdoc_url
+       end
+
        redef fun init_title(v, doc) do title = mentity.html_name
 end
 
@@ -246,6 +252,26 @@ end
 # doc phases. This is to preserve the compatibility with the current
 # `doc_templates` module.
 
+redef class ReadmePage
+       redef var html_url is lazy do return mentity.nitdoc_url
+
+       redef fun init_topmenu(v, doc) do
+               super
+               var mproject = mentity.mproject
+               if not mentity.is_root then
+                       topmenu.add_li new ListItem(new Link(mproject.nitdoc_url, mproject.html_name))
+               end
+               topmenu.add_li new ListItem(new Link(html_url, mproject.html_name))
+               topmenu.active_item = topmenu.items.last
+       end
+
+       redef fun init_sidebar(v, doc) do
+               super
+               var api_lnk = """<a href="api_{{{mentity.nitdoc_url}}}">Go to API</a>"""
+               sidebar.boxes.unshift new DocSideBox(api_lnk, "")
+       end
+end
+
 redef class MGroupPage
        redef fun init_topmenu(v, doc) do
                super
@@ -259,6 +285,12 @@ redef class MGroupPage
 
        redef fun init_sidebar(v, doc) do
                super
+               # README link
+               if mentity.mdoc != null then
+                       var doc_lnk = """<a href="{{{mentity.nitdoc_url}}}">Go to README</a>"""
+                       sidebar.boxes.unshift new DocSideBox(doc_lnk, "")
+               end
+               # MClasses list
                var mclasses = new HashSet[MClass]
                mclasses.add_all intros
                mclasses.add_all redefs
@@ -375,7 +407,7 @@ redef class MClassPage
                        var def_url = "{cls_url}#{mprop.nitdoc_id}.definition"
                        var lnk = new Link(def_url, mprop.html_name)
                        var mdoc = mprop.intro.mdoc_or_fallback
-                       if mdoc != null then lnk.title = mdoc.short_comment
+                       if mdoc != null then lnk.title = mdoc.synopsis
                        var item = new Template
                        item.add new DocHTMLLabel.with_classes(classes)
                        item.add lnk
index 3f83001..e08a385 100644 (file)
@@ -26,6 +26,7 @@ class MakePagePhase
                doc.add_page new OverviewPage("overview", "Overview")
                doc.add_page new SearchPage("search", "Index")
                for mgroup in doc.mgroups do
+                       doc.add_page new ReadmePage(mgroup)
                        doc.add_page new MGroupPage(mgroup)
                end
                for mmodule in doc.mmodules do
@@ -65,6 +66,14 @@ class MEntityPage
        redef var title is lazy do return mentity.nitdoc_name
 end
 
+# A page that displays a `MGroup` README.
+class ReadmePage
+       super MEntityPage
+
+       redef type MENTITY: MGroup
+       redef var id is lazy do return "readme_{mentity.nitdoc_id}"
+end
+
 # A documentation page about a MGroup.
 class MGroupPage
        super MEntityPage
diff --git a/src/doc/doc_phases/doc_readme.nit b/src/doc/doc_phases/doc_readme.nit
new file mode 100644 (file)
index 0000000..0a43edb
--- /dev/null
@@ -0,0 +1,314 @@
+# 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.
+
+# This phase parses README files.
+module doc_readme
+
+import markdown::decorators
+intrude import markdown::wikilinks
+import doc_commands
+import doc_down
+import doc_intros_redefs
+
+# Generate content of `ReadmePage`.
+#
+# This phase extracts the structure of a `ReadmePage` from the markdown content
+# of the README file.
+# It also resolves Wikilinks and commands.
+class ReadmePhase
+       super DocPhase
+
+       redef fun apply do
+               for page in doc.pages.values do page.build_content(self, doc)
+       end
+
+       # Display a warning about something wrong in the readme file.
+       fun warning(location: nullable MDLocation, page: ReadmePage, message: String) do
+               var loc = null
+               if location != null then
+                       loc = location.to_location(page.mentity.mdoc.location.file)
+               end
+               ctx.warning(loc, "readme-warning", message)
+       end
+end
+
+redef class DocPage
+       # Build content of `ReadmePage` based on the content of the readme file.
+       private fun build_content(v: ReadmePhase, doc: DocModel) do end
+end
+
+redef class ReadmePage
+       redef fun build_content(v, doc) do
+               if mentity.mdoc == null then
+                       v.warning(null, self, "Empty README for group `{mentity}`")
+                       return
+               end
+               var proc = new MarkdownProcessor
+               proc.emitter = new ReadmeMdEmitter(proc, self, v)
+               proc.emitter.decorator = new ReadmeDecorator
+               var md = mentity.mdoc.content.join("\n")
+               proc.process(md)
+       end
+end
+
+# Markdown emitter used to produce the `ReadmeArticle`.
+class ReadmeMdEmitter
+       super MarkdownEmitter
+
+       # Readme page being decorated.
+       var page: ReadmePage
+
+       # Phase used to access doc model and toolcontext.
+       var phase: ReadmePhase
+
+       init do open_article
+
+       # Push the article template on top of the buffer stack.
+       #
+       # Subsequent markdown writting will be done in the article template.
+       #
+       # See `ReadmeArticle::md`.
+       private fun push_article(article: ReadmeArticle) do
+               buffer_stack.add article.md
+       end
+
+       private var context = new Array[DocComposite]
+
+       # Creates a new ReadmeSection in `self.toc.page`.
+       #
+       # Called from `add_headline`.
+       private fun open_section(lvl: Int, title: String) do
+               var section = new ReadmeSection(title.escape_to_c, title, lvl, processor)
+               if current_section == null then
+                       page.root.add_child(section)
+               else
+                       current_section.add_child(section)
+               end
+               current_section = section
+               context.add section
+       end
+       private var current_section: nullable ReadmeSection is noinit
+
+       # Close the current section.
+       #
+       # Ensure `context.last isa ReadmeSection`.
+       private fun close_section do
+               assert context.last isa ReadmeSection
+               context.pop
+               if context.is_empty then
+                       current_section = null
+               else
+                       current_section = context.last.as(ReadmeSection)
+               end
+       end
+
+       # Add an article at current location.
+       #
+       # This closes the current article, inserts `article` then opens a new article.
+       private fun add_article(article: DocArticle) do
+               close_article
+               if current_section == null then
+                       page.root.add_child(article)
+               else
+                       current_section.add_child(article)
+               end
+               open_article
+       end
+
+       # Creates a new ReadmeArticle in `self.toc.page`.
+       #
+       # Called from `add_headline`.
+       private fun open_article do
+               var section: DocComposite = page.root
+               if current_section != null then section = current_section.as(not null)
+               var article = new ReadmeArticle("mdarticle-{section.children.length}", null, processor)
+               section.add_child(article)
+               context.add article
+               push_article article
+       end
+
+       # Close the current article.
+       #
+       # Ensure `context.last isa ReadmeArticle`.
+       fun close_article do
+               assert context.last isa ReadmeArticle
+               context.pop
+               pop_buffer
+       end
+end
+
+# MarkdownDecorator used to decorated the Readme file with links between doc entities.
+class ReadmeDecorator
+       super MdDecorator
+
+       redef type EMITTER: ReadmeMdEmitter
+
+       redef fun add_headline(v, block) do
+               var txt = block.block.first_line.value
+               var lvl = block.depth
+               if not v.context.is_empty then
+                       v.close_article
+                       while v.current_section != null do
+                               if v.current_section.depth < lvl then break
+                               v.close_section
+                       end
+               end
+               v.open_section(lvl, txt)
+               v.open_article
+       end
+
+       redef fun add_wikilink(v, token) do
+               var link = token.link.to_s
+               var cmd = new DocCommand(link)
+               if cmd isa UnknownCommand then
+                       # search MEntities by name
+                       var res = v.phase.doc.mentities_by_name(link.to_s)
+                       # no match, print warning and display wikilink as is
+                       if res.is_empty then
+                               v.phase.warning(token.location, v.page, "Link to unknown entity `{link}`")
+                               super
+                       else
+                               add_mentity_link(v, res.first, token.name, token.comment)
+                       end
+                       return
+               end
+               cmd.render(v, token)
+       end
+
+       # Renders a link to a mentity.
+       private fun add_mentity_link(v: EMITTER, mentity: MEntity, name, comment: nullable Text) do
+               # TODO real link
+               var link = mentity.full_name
+               if name == null then name = mentity.name
+               if comment == null and mentity.mdoc != null then
+                       comment = mentity.mdoc.synopsis
+               end
+               add_link(v, link, name, comment)
+       end
+end
+
+redef interface DocCommand
+
+       # Render the content of the doc command.
+       fun render(v: ReadmeMdEmitter, token: TokenWikiLink) is abstract
+
+       # Search `doc` model for mentities match `string`.
+       fun search_model(doc: DocModel, string: String): Array[MEntity] do
+               var res
+               if string.has("::") then
+                       res = doc.mentities_by_namespace(string).to_a
+               else
+                       res = doc.mentities_by_name(string).to_a
+               end
+               return res
+       end
+end
+
+redef class ArticleCommand
+       redef fun render(v, token) do
+               var string = args.first
+               var res = search_model(v.phase.doc, string)
+               res = filter_results(res)
+               if res.is_empty then
+                       v.phase.warning(
+                               token.location, v.page,
+                               "Try to include documentation of unknown entity `{args.first}`")
+                       return
+               end
+               if res.length > 1 then
+                       v.phase.warning(token.location, v.page, "conflicting article for `{args.first}` (choices : {res.join(", ")})")
+               end
+               v.add_article new DocumentationArticle("readme", "Readme", res.first)
+       end
+
+       private fun filter_results(res: Array[MEntity]): Array[MEntity] do
+               var out = new Array[MEntity]
+               for e in res do
+                       if e isa MProject then continue
+                       if e isa MGroup then continue
+                       out.add e
+               end
+               return out
+       end
+end
+
+redef class ListCommand
+       redef fun render(v, token) do
+               var string = args.first
+               var res = search_model(v.phase.doc, string)
+               if res.is_empty then
+                       v.phase.warning(token.location, v.page, "include article for unknown entity `{args.first}`")
+                       return
+               end
+               if res.length > 1 then
+                       v.phase.warning(token.location, v.page, "conflicting article for `{args.first}` (choices : {res.join(", ")})")
+               end
+               var mentity = res.first
+               if mentity isa MModule then
+                       v.add_article new MEntitiesListArticle("Classes", mentity.mclassdefs)
+               else if mentity isa MClass then
+                       var mprops = mentity.collect_intro_mproperties(public_visibility)
+                       v.add_article new MEntitiesListArticle("Methods", mprops.to_a)
+               else if mentity isa MClassDef then
+                       v.add_article new MEntitiesListArticle("Methods", mentity.mpropdefs)
+               end
+       end
+end
+
+
+# A section found in a README.
+#
+# Produced by markdown headlines like `## Section 1.1`.
+class ReadmeSection
+       super DocSection
+
+       # The depth is based on the markdown headline depth.
+       redef var depth
+
+       # Markdown processor used to process the section title.
+       var markdown_processor: MarkdownProcessor
+
+       redef var is_hidden = false
+end
+
+# An article found in a README file.
+#
+# Basically, everything found in a README that is not a headline.
+class ReadmeArticle
+       super DocArticle
+
+       # Markdown processor used to process the article content.
+       var markdown_processor: MarkdownProcessor
+
+       # Markdown content of this article extracted from the README file.
+       var md = new FlatBuffer
+
+       redef fun is_hidden do return super and md.trim.is_empty
+end
+
+# Documentation Article to introduce from the directive `doc: Something`.
+#
+# TODO merge with DefinitionArticle once the html is simplified
+class DocumentationArticle
+       super MEntityArticle
+
+       redef var is_hidden = false
+end
+
+redef class MDLocation
+       # Translate a Markdown location in Nit location.
+       private fun to_location(file: nullable SourceFile): Location do
+               return new Location(file, line_start, line_end, column_start, column_end)
+       end
+end
index b5ccd96..ed895ee 100644 (file)
@@ -44,7 +44,7 @@ redef class MEntity
                var tpl = new Link(nitdoc_url, html_name)
                var mdoc = mdoc_or_fallback
                if mdoc != null then
-                       tpl.title = mdoc.short_comment
+                       tpl.title = mdoc.synopsis
                end
                return tpl
        end
@@ -56,7 +56,7 @@ redef class MEntity
                var tpl = new Link("#{nitdoc_id}", html_name)
                var mdoc = mdoc_or_fallback
                if mdoc != null then
-                       tpl.title = mdoc.short_comment
+                       tpl.title = mdoc.synopsis
                end
                return tpl
        end
@@ -94,18 +94,25 @@ redef class MEntity
        # * MPropdef: `mclassdef:mpropdef`
        fun html_namespace: Template is abstract
 
-       # Returns the comment of this MEntity formatted as HTML.
-       var html_comment: nullable Writable is lazy do
+       # Returns the synopsis and the comment of this MEntity formatted as HTML.
+       var html_documentation: nullable Writable is lazy do
+               var mdoc = mdoc_or_fallback
+               if mdoc == null then return null
+               return mdoc.html_documentation
+       end
+
+       # Returns the synopsis of this MEntity formatted as HTML.
+       var html_synopsis: nullable Writable is lazy do
                var mdoc = mdoc_or_fallback
                if mdoc == null then return null
-               return mdoc.tpl_comment
+               return mdoc.html_synopsis
        end
 
-       # Returns the comment of this MEntity formatted as HTML.
-       var html_short_comment: nullable Writable is lazy do
+       # Returns the the comment without the synopsis formatted as HTML.
+       var html_comment: nullable Writable is lazy do
                var mdoc = mdoc_or_fallback
                if mdoc == null then return null
-               return mdoc.tpl_short_comment
+               return mdoc.html_comment
        end
 
        # Icon that will be displayed before the title
@@ -125,7 +132,7 @@ redef class MEntity
                var tpl = new Template
                tpl.add new DocHTMLLabel.with_classes(css_classes)
                tpl.add html_link
-               var comment = html_short_comment
+               var comment = html_synopsis
                if comment != null then
                        tpl.add ": "
                        tpl.add comment
@@ -688,7 +695,7 @@ redef class MConcern
                var lnk = html_link
                var tpl = new Template
                tpl.add new Link.with_title("#{nitdoc_id}.concern", lnk.text, lnk.title)
-               var comment = html_short_comment
+               var comment = html_synopsis
                if comment != null then
                        tpl.add ": "
                        tpl.add comment
index bf14d90..a497ad3 100644 (file)
@@ -22,6 +22,8 @@ import doc_phases::doc_hierarchies
 import doc_phases::doc_graphs
 import doc_phases::doc_intros_redefs
 import doc_phases::doc_lin
+import doc_phases::doc_readme
+intrude import doc_down
 
 # Renders the page as HTML.
 redef class DocPage
@@ -265,7 +267,6 @@ redef class DocComposite
                if html_title != null then
                var header = new Header(hlvl, html_title.write_to_string)
                header.css_classes.add "signature"
-               if hlvl == 2 then header.css_classes.add "well well-sm"
                addn header
                end
                if html_subtitle != null then
@@ -447,7 +448,10 @@ redef class IntroArticle
 
        redef fun render_body do
                var tabs = new DocTabs("{html_id}.tabs", "")
-               var comment = mentity.html_comment
+               var comment = mentity.html_documentation
+               if mentity isa MProject then
+                       comment = mentity.html_synopsis
+               end
                if comment != null then
                        tabs.add_panel new DocTabPanel("{html_tab_id}-comment", "Comment", comment)
                end
@@ -502,10 +506,10 @@ redef class DefinitionArticle
                var tabs = new DocTabs("{html_id}.tabs", "")
                if not is_no_body then
                        var comment
-                       if is_short_comment then
-                               comment = mentity.html_short_comment
+                       if is_short_comment or mentity isa MProject then
+                               comment = mentity.html_synopsis
                        else
-                               comment = mentity.html_comment
+                               comment = mentity.html_documentation
                        end
                        if comment != null then
                                tabs.add_panel new DocTabPanel("{html_tab_id}-comment", "Comment", comment)
@@ -543,7 +547,7 @@ redef class DefinitionLinArticle
                        if not mentity isa MPropDef then continue # TODO handle all mentities
                        var tpl = new Template
                        tpl.add mentity.mclassdef.html_namespace
-                       var comment = mentity.mclassdef.html_short_comment
+                       var comment = mentity.mclassdef.html_synopsis
                        if comment != null then
                                tpl.add ": "
                                tpl.add comment
@@ -571,3 +575,49 @@ redef class GraphArticle
                addn "</div>"
        end
 end
+
+redef class ReadmeSection
+       redef var html_id is lazy do
+               return markdown_processor.emitter.decorator.strip_id(html_title.as(not null).to_s)
+       end
+
+       redef var html_title is lazy do
+               return markdown_processor.process(title.as(not null))
+       end
+end
+
+redef class ReadmeArticle
+       redef var html_id = ""
+       redef var html_title = null
+       redef var is_toc_hidden = true
+
+       redef fun render_body do
+               add markdown_processor.process(md.trim.write_to_string)
+       end
+end
+
+redef class DocumentationArticle
+       redef var html_title is lazy do
+               var synopsis = mentity.html_synopsis
+               if synopsis == null then return mentity.html_link
+               return "{mentity.html_link.write_to_string} &ndash; {synopsis.write_to_string}"
+       end
+
+       redef var html_subtitle is lazy do return null
+       redef var html_toc_title is lazy do return mentity.html_name
+       redef var is_toc_hidden is lazy do return depth > 3
+
+       redef fun render_body do
+               var tabs = new DocTabs("{html_id}.tabs", "")
+               var comment = mentity.html_comment
+               if comment != null then
+                       tabs.add_panel new DocTabPanel("{html_tab_id}-comment", "Comment", comment)
+               end
+               for child in children do
+                       if child.is_hidden then continue
+                       var title = child.html_toc_title or else child.toc_title or else ""
+                       tabs.add_panel new DocTabPanel(child.html_tab_id, title, child)
+               end
+               addn tabs
+       end
+end
index 87b7433..3a70e66 100644 (file)
@@ -116,33 +116,17 @@ private class SerializationPhasePreModel
        do
                if not nclassdef isa AStdClassdef then return
 
-               # Is there a declaration on the classdef or the module?
-               var serialize = nclassdef.is_serialize
-
-               if not serialize and not nclassdef.is_noserialize then
-                       # Is the module marked serialize?
-                       serialize = nclassdef.parent.as(AModule).is_serialize
-               end
+               var serialize_by_default = nclassdef.how_serialize
 
-               var per_attribute = false
-               if not serialize then
-                       # Is there an attribute marked serialize?
-                       for npropdef in nclassdef.n_propdefs do
-                               if npropdef.is_serialize then
-                                       serialize = true
-                                       per_attribute = true
-                                       break
-                               end
-                       end
-               end
+               if serialize_by_default != null then
 
-               if serialize then
                        # Add `super Serializable`
                        var sc = toolcontext.parse_superclass("Serializable")
                        sc.location = nclassdef.location
                        nclassdef.n_propdefs.add sc
 
                        # Add services
+                       var per_attribute = not serialize_by_default
                        generate_serialization_method(nclassdef, per_attribute)
                        generate_deserialization_init(nclassdef, per_attribute)
                end
@@ -156,7 +140,7 @@ private class SerializationPhasePreModel
                # collect all classes
                var auto_serializable_nclassdefs = new Array[AStdClassdef]
                for nclassdef in nmodule.n_classdefs do
-                       if nclassdef isa AStdClassdef and nclassdef.is_serialize then
+                       if nclassdef isa AStdClassdef and nclassdef.how_serialize != null then
                                auto_serializable_nclassdefs.add nclassdef
                        end
                end
@@ -269,7 +253,7 @@ do
                for nclassdef in nclassdefs do
                        var name = nclassdef.n_id.text
                        if nclassdef.n_formaldefs.is_empty and
-                               not nclassdef.n_classkind isa AAbstractClasskind then
+                          nclassdef.n_classkind isa AConcreteClasskind then
 
                                code.add "              if name == \"{name}\" then return new {name}.from_deserializer(self)"
                        end
@@ -380,4 +364,34 @@ redef class AStdClassdef
 
                return null
        end
+
+       # Is this classed marked `serialize`? in part or fully?
+       #
+       # This method returns 3 possible values:
+       # * `null`, this class is not to be serialized.
+       # * `true`, the attributes of this class are to be serialized by default.
+       # * `false`, the attributes of this class are to be serialized on demand only.
+       fun how_serialize: nullable Bool
+       do
+               # Is there a declaration on the classdef or the module?
+               var serialize = is_serialize
+
+               if not serialize and not is_noserialize then
+                       # Is the module marked serialize?
+                       serialize = parent.as(AModule).is_serialize
+               end
+
+               if serialize then return true
+
+               if not serialize then
+                       # Is there an attribute marked serialize?
+                       for npropdef in n_propdefs do
+                               if npropdef.is_serialize then
+                                       return false
+                               end
+                       end
+               end
+
+               return null
+       end
 end
index d78ac71..5d20c9c 100644 (file)
@@ -479,11 +479,21 @@ redef class ModelBuilder
        # Load a markdown file as a documentation object
        fun load_markdown(filepath: String): MDoc
        do
-               var mdoc = new MDoc(new Location(new SourceFile.from_string(filepath, ""),0,0,0,0))
                var s = new FileReader.open(filepath)
+               var lines = new Array[String]
+               var line_starts = new Array[Int]
+               var len = 1
                while not s.eof do
-                       mdoc.content.add(s.read_line)
-               end
+                       var line = s.read_line
+                       lines.add(line)
+                       line_starts.add(len)
+                       len += line.length + 1
+               end
+               s.close
+               var source = new SourceFile.from_string(filepath, lines.join("\n"))
+               source.line_starts.add_all line_starts
+               var mdoc = new MDoc(new Location(source, 1, lines.length, 0, 0))
+               mdoc.content.add_all(lines)
                return mdoc
        end
 
index 1a27697..108cf28 100644 (file)
@@ -42,7 +42,9 @@ class SourceFile
                line_starts[0] = 0
        end
 
-       # Position of each line start
+       # Offset of each line start in the content `string`.
+       #
+       # Used for fast access to each line when rendering parts of the `string`.
        var line_starts = new Array[Int]
 end
 
index d9e17f3..bda79e7 100644 (file)
@@ -51,7 +51,8 @@ private class Nitdoc
                        new InheritanceListsPhase(toolcontext, doc),
                        new IntroRedefListPhase(toolcontext, doc),
                        new LinListPhase(toolcontext, doc),
-                       new GraphPhase(toolcontext, doc): DocPhase]
+                       new GraphPhase(toolcontext, doc),
+                       new ReadmePhase(toolcontext, doc): DocPhase]
 
                if not toolcontext.opt_test.value then
                        phases.add new RenderHTMLPhase(toolcontext, doc)
diff --git a/tests/sav/nitce/test_binary_deserialization_alt1.res b/tests/sav/nitce/test_binary_deserialization_alt1.res
new file mode 100644 (file)
index 0000000..4d4a3a0
--- /dev/null
@@ -0,0 +1,36 @@
+# Src:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+# Dst:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Src:
+<B: <A: false b 123.123 2345 hjkl false p4ssw0rd> 1111 qwer>
+# Dst:
+<B: <A: false b 123.123 2345 hjkl false p4ssw0rd> 1111 qwer>
+
+# Src:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl false p4ssw0rd> 1111 qwer>>
+# Dst:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl false p4ssw0rd> 1111 qwer>>
+
+# Src:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+# Dst:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+Deserialization Error: Doesn't know how to deserialize class "Array", Deserialization Error: Wrong type on `E::a` expected `PlaceHolderTypeWhichShouldNotExist`, got `null`, Deserialization Error: Doesn't know how to deserialize class "Array", Deserialization Error: Wrong type on `E::b` expected `PlaceHolderTypeWhichShouldNotExist`, got `null`
+# Src:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+# Dst:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+Deserialization Error: Doesn't know how to deserialize class "F"
+Deserialization Error: Doesn't know how to deserialize class "F"
+Deserialization Error: Doesn't know how to deserialize class "HashSet", Deserialization Error: Wrong type on `G::hs` expected `PlaceHolderTypeWhichShouldNotExist`, got `null`, Deserialization Error: Doesn't know how to deserialize class "ArraySet", Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`, Deserialization Error: Doesn't know how to deserialize class "HashMap", Deserialization Error: Wrong type on `G::hm` expected `PlaceHolderTypeWhichShouldNotExist`, got `null`, Deserialization Error: Doesn't know how to deserialize class "ArrayMap", Deserialization Error: Wrong type on `G::am` expected `PlaceHolderTypeWhichShouldNotExist`, got `null`
+# Src:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+# Dst:
+<G: hs: ; s: ; hm: ; am: >
+
index c53bde5..7681dab 100644 (file)
@@ -1,4 +1,3 @@
-Runtime error: Aborted (../lib/serialization/serialization.nit:120)
 # Nit:
 <A: true a 0.123 1234 asdf false p4ssw0rd>
 
@@ -37,4 +36,39 @@ Runtime error: Aborted (../lib/serialization/serialization.nit:120)
 <D: <B: <A: false b 123.123 2345 new line ->
 <- false p4ssw0rd> 1111        f"\r\/> true>
 
-Error: doesn't know how to deserialize class "Array"
+# Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Json:
+{"__kind": "obj", "__id": 0, "__class": "E", "a": {"__kind": "obj", "__id": 1, "__class": "Array", "__items": ["hello", 1234, 123.4]}, "b": {"__kind": "obj", "__id": 2, "__class": "Array", "__items": ["hella", 2345, 234.5]}}
+
+# Back in Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Nit:
+<E: 2222>
+
+# Json:
+{"__kind": "obj", "__id": 0, "__class": "F", "n": 2222}
+
+# Back in Nit:
+null
+
+# Nit:
+<E: 33.33>
+
+# Json:
+{"__kind": "obj", "__id": 0, "__class": "F", "n": 33.33}
+
+# Back in Nit:
+null
+
+# Nit:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# Json:
+{"__kind": "obj", "__id": 0, "__class": "G", "hs": {"__kind": "obj", "__id": 1, "__class": "HashSet", "__items": [-1, 0]}, "s": {"__kind": "obj", "__id": 2, "__class": "ArraySet", "__items": ["one", "two"]}, "hm": {"__kind": "obj", "__id": 3, "__class": "HashMap", "__length": 2, "__keys": ["one", "two"], "__values": [1, 2]}, "am": {"__kind": "obj", "__id": 4, "__class": "ArrayMap", "__length": 2, "__keys": ["three", "four"], "__values": ["3", "4"]}}
+
+# Back in Nit:
+<G: hs: ; s: ; hm: ; am: >
+
index 6f669b2..5fea655 100644 (file)
@@ -1,3 +1,6 @@
+Empty README for group `module_1` (readme-warning)
+Empty README for group `module_0` (readme-warning)
+Errors: 0. Warnings: 2.
 class_module_95d0-__Int.html
 class_module_95d0-__Object.html
 class_module_95d0-__Sys.html
index dbc5101..6033620 100644 (file)
@@ -1,3 +1,5 @@
+Empty README for group `base_attr_nullable` (readme-warning)
+Errors: 0. Warnings: 1.
 class_base_attr_nullable-__Bar.html
 class_base_attr_nullable-__Bool.html
 class_base_attr_nullable-__Foo.html
index 6d471ad..4d76b95 100644 (file)
@@ -1,3 +1,5 @@
+Empty README for group `base_attr_nullable` (readme-warning)
+Errors: 0. Warnings: 1.
 class_base_attr_nullable-__Bar.html
 class_base_attr_nullable-__Bool.html
 class_base_attr_nullable-__Foo.html
index 1f773c6..56b146d 100644 (file)
@@ -3,6 +3,18 @@ OverviewPage Overview
                ## projects.section
                        ### test_prog.definition
 
+ReadmePage test_prog
+       # mdarticle-0
+
+ReadmePage game
+       # mdarticle-0
+
+ReadmePage platform
+       # mdarticle-0
+
+ReadmePage rpg
+       # mdarticle-0
+
 SearchPage Index
        # index.article
 
@@ -940,14 +952,15 @@ MModulePage rpg
                                #### test_prog__rpg__rpg.imports
                                #### test_prog__rpg__rpg.clients
 
-Generated 81 pages
+Generated 85 pages
  list:
-  MPropertyPage: 47 (58.02%)
-  MClassPage: 20 (24.69%)
-  MModulePage: 8 (9.87%)
-  MGroupPage: 4 (4.93%)
-  SearchPage: 1 (1.23%)
-  OverviewPage: 1 (1.23%)
+  MPropertyPage: 47 (55.29%)
+  MClassPage: 20 (23.52%)
+  MModulePage: 8 (9.41%)
+  MGroupPage: 4 (4.70%)
+  ReadmePage: 4 (4.70%)
+  SearchPage: 1 (1.17%)
+  OverviewPage: 1 (1.17%)
 Found 160 mentities
  list:
   MMethodDef: 57 (35.62%)
index ac5aebd..8971f9e 100644 (file)
@@ -1,5 +1,5 @@
-test_nitunit3/README.md: Error: there is a block of invalid Nit code, thus not considered a nitunit. To suppress this warning, enclose the block with a fence tagged `nitish` or `raw` (see `man nitdoc`). At 1,2--4: Syntax Error: unexpected malformed character '\]..
-test_nitunit3/README.md: ERROR: nitunit.test_nitunit3.<group> (in .nitunit/test_nitunit3-0.nit): Runtime error: Assert failed (.nitunit/test_nitunit3-0.nit:7)
+test_nitunit3/README.md:1,0--13,0: Error: there is a block of invalid Nit code, thus not considered a nitunit. To suppress this warning, enclose the block with a fence tagged `nitish` or `raw` (see `man nitdoc`). At 1,2--4: Syntax Error: unexpected malformed character '\]..
+test_nitunit3/README.md:1,0--13,0: ERROR: nitunit.test_nitunit3.<group> (in .nitunit/test_nitunit3-0.nit): Runtime error: Assert failed (.nitunit/test_nitunit3-0.nit:7)
 
 DocUnits:
 Entities: 2; Documented ones: 2; With nitunits: 3; Failures: 2
@@ -7,7 +7,7 @@ Entities: 2; Documented ones: 2; With nitunits: 3; Failures: 2
 TestSuites:
 No test cases found
 Class suites: 0; Test Cases: 0; Failures: 0
-<testsuites><testsuite package="test_nitunit3"><testcase classname="nitunit.test_nitunit3" name="&lt;group&gt;"><failure message="test_nitunit3&#47;README.md: Invalid block of code. At 1,2--4: Syntax Error: unexpected malformed character &#39;\].."></failure><system-err></system-err><system-out>assert false
+<testsuites><testsuite package="test_nitunit3"><testcase classname="nitunit.test_nitunit3" name="&lt;group&gt;"><failure message="test_nitunit3&#47;README.md:1,0--13,0: Invalid block of code. At 1,2--4: Syntax Error: unexpected malformed character &#39;\].."></failure><system-err></system-err><system-out>assert false
 assert true
 </system-out><error message="Runtime error: Assert failed (.nitunit&#47;test_nitunit3-0.nit:7)
 "></error></testcase></testsuite><testsuite package="test_nitunit3"><testcase classname="nitunit.test_nitunit3.&lt;module&gt;" name="&lt;module&gt;"><system-err></system-err><system-out>assert true
index ea6bab2..fc20a76 100644 (file)
@@ -1,4 +1,4 @@
-test_nitunit_md.md: ERROR: nitunit.<file>.test_nitunit_md.md (in .nitunit/file-0.nit): Runtime error: Assert failed (.nitunit/file-0.nit:8)
+test_nitunit_md.md:1,0--15,0: ERROR: nitunit.<file>.test_nitunit_md.md:1,0--15,0 (in .nitunit/file-0.nit): Runtime error: Assert failed (.nitunit/file-0.nit:8)
 
 DocUnits:
 Entities: 1; Documented ones: 1; With nitunits: 1; Failures: 1
@@ -6,7 +6,7 @@ Entities: 1; Documented ones: 1; With nitunits: 1; Failures: 1
 TestSuites:
 No test cases found
 Class suites: 0; Test Cases: 0; Failures: 0
-<testsuites><testsuite package="test_nitunit_md.md"><testcase classname="nitunit.&lt;file&gt;" name="test_nitunit_md.md"><system-err></system-err><system-out>var a = 1
+<testsuites><testsuite package="test_nitunit_md.md:1,0--15,0"><testcase classname="nitunit.&lt;file&gt;" name="test_nitunit_md.md:1,0--15,0"><system-err></system-err><system-out>var a = 1
 assert 1 == 1
 assert false
 </system-out><error message="Runtime error: Assert failed (.nitunit&#47;file-0.nit:8)
diff --git a/tests/sav/test_binary_deserialization.res b/tests/sav/test_binary_deserialization.res
new file mode 100644 (file)
index 0000000..61991a0
--- /dev/null
@@ -0,0 +1,22 @@
+# Src:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+# Dst:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Src:
+<B: <A: false b 123.123 2345 hjkl false p4ssw0rd> 1111 qwer>
+# Dst:
+<B: <A: false b 123.123 2345 hjkl false p4ssw0rd> 1111 qwer>
+
+# Src:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl false p4ssw0rd> 1111 qwer>>
+# Dst:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl false p4ssw0rd> 1111 qwer>>
+
+# Src:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+# Dst:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
diff --git a/tests/sav/test_binary_deserialization_alt1.res b/tests/sav/test_binary_deserialization_alt1.res
new file mode 100644 (file)
index 0000000..9683ba6
--- /dev/null
@@ -0,0 +1,42 @@
+# Src:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+# Dst:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Src:
+<B: <A: false b 123.123 2345 hjkl false p4ssw0rd> 1111 qwer>
+# Dst:
+<B: <A: false b 123.123 2345 hjkl false p4ssw0rd> 1111 qwer>
+
+# Src:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl false p4ssw0rd> 1111 qwer>>
+# Dst:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl false p4ssw0rd> 1111 qwer>>
+
+# Src:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+# Dst:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111        f"\r\/> true>
+
+# Src:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+# Dst:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Src:
+<E: 2222>
+# Dst:
+<E: 2222>
+
+# Src:
+<E: 33.33>
+# Dst:
+<E: 33.33>
+
+# Src:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+# Dst:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
diff --git a/tests/sav/test_binary_deserialization_alt2.res b/tests/sav/test_binary_deserialization_alt2.res
new file mode 100644 (file)
index 0000000..474fef8
--- /dev/null
@@ -0,0 +1 @@
+Deserialization Error: Doesn't know how to deserialize class "NoSerializeClass"
diff --git a/tests/test_binary_deserialization.nit b/tests/test_binary_deserialization.nit
new file mode 100644 (file)
index 0000000..c6bcd0a
--- /dev/null
@@ -0,0 +1,61 @@
+# 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
+import binary::serialization
+#alt1# import test_deserialization_serial
+
+class NoSerializeClass
+       super Serializable
+
+       var some_attribute: String
+end
+
+var entities = new TestEntities
+
+var tests = entities.without_generics#alt1##alt2#
+#alt1#var tests = entities.with_generics
+#alt2#var tests = [new NoSerializeClass("will not deserialize")]
+
+var dir = "out/test_binary_deserialization"
+if not dir.file_exists then dir.mkdir
+
+var path = dir / "alt0"#alt1##alt2#
+#alt1#var path = dir / "alt1"
+#alt2#var path = dir / "alt2"
+
+var writer = new FileWriter.open(path)
+var serializer = new BinarySerializer(writer)
+for o in tests do
+       serializer.serialize o
+end
+writer.close
+
+var reader = new FileReader.open(path)
+var deserializer = new BinaryDeserializer(reader)
+for o in tests do
+       var dst = deserializer.deserialize
+
+       if deserializer.errors.not_empty then
+               print deserializer.errors.join(", ")
+       end
+
+       if dst != null then
+               assert o.is_same_type(dst)
+
+               print "# Src:\n{o}"
+               print "# Dst:\n{dst}\n"
+       end
+end
+reader.close