Merge: nitweb: add READMEs summaries
authorJean Privat <jean@pryen.org>
Thu, 8 Dec 2016 21:58:25 +0000 (16:58 -0500)
committerJean Privat <jean@pryen.org>
Thu, 8 Dec 2016 21:58:25 +0000 (16:58 -0500)
Automatically add a summary extracted from the README / doc contents

![image](https://cloud.githubusercontent.com/assets/583144/20892633/fef2fd4a-badc-11e6-89f5-5de007981914.png)

Also add anchors to the content of the README, so you can make links like:

http://nitweb.moz-code.org/doc/core#Methods-Implicitly-Defined-in-Sys

Pull-Request: #2342

34 files changed:
contrib/nitrpg/README.md
lib/config.ini [new file with mode: 0644]
lib/config.nit [new file with mode: 0644]
lib/core/bytes.nit
lib/core/text/abstract_text.nit
lib/github/api.nit
lib/opts.nit
lib/popcorn/pop_config.nit
lib/popcorn/pop_repos.nit
share/nitweb/directives/entity/link.html
share/nitweb/directives/entity/location.html
share/nitweb/directives/entity/rating.html [new file with mode: 0644]
share/nitweb/directives/entity/stars.html
share/nitweb/index.html
share/nitweb/javascripts/entities.js
share/nitweb/javascripts/grades.js [new file with mode: 0644]
share/nitweb/javascripts/model.js
share/nitweb/javascripts/nitweb.js
share/nitweb/javascripts/users.js
share/nitweb/stylesheets/nitweb.css
share/nitweb/views/classdef.html
share/nitweb/views/grades.html [new file with mode: 0644]
share/nitweb/views/propdef.html
share/nitweb/views/user.html
src/model/model_json.nit
src/model/test_model_json.sav/test_classdefs_to_full_json.res
src/model/test_model_json.sav/test_classes_to_full_json.res
src/model/test_model_json.sav/test_modules_to_full_json.res
src/model/test_model_json.sav/test_packages_to_full_json.res
src/model/test_model_json.sav/test_props_to_full_json.res
src/nitweb.nit
src/web/api_feedback.nit
src/web/api_model.nit
src/web/web_base.nit

index a83264e..5f6a02d 100644 (file)
@@ -42,7 +42,9 @@ It should alwaysd be up if you want your game to be kept up-to-date.
 
 To run the listener:
 
+~~~raw
        ./listener <host> <port>
+~~~
 
 The arguments `host` and `port` must correspond to what you entered in your
 GitHub hook settings.
@@ -53,7 +55,9 @@ The `web` program act as a [nitcorn](http://nitlanguage.org/doc/stdlib/module_ni
 
 To run the webserver:
 
+~~~raw
        ./web <host> <port> <root>
+~~~
 
 The arguments `host` and `port` must correspond to what you entered in your
 GitHub hook settings.
@@ -62,11 +66,15 @@ NitRPG root.
 
 For example, if NitRPG is installed in `yourdomain.com/nitrpg`:
 
+~~~raw
        ./web localhost 3000 "/nitrpg"
+~~~
 
 Leave it empty if NitRPG is installed at the root of the domain:
 
+~~~raw
        ./web localhost 3000 ""
+~~~
 
 The webserver can then be accessed at `http://yourdomain.com:3000/nitrpg/`.
 
diff --git a/lib/config.ini b/lib/config.ini
new file mode 100644 (file)
index 0000000..38dbaf5
--- /dev/null
@@ -0,0 +1,11 @@
+[package]
+name=config
+tags=config,options,ini,lib
+maintainer=Alexandre Terrasa <alexandre@moz-code.org>
+license=Apache-2.0
+[upstream]
+browse=https://github.com/nitlang/nit/tree/master/lib/config.nit
+git=https://github.com/nitlang/nit.git
+git.directory=lib/config.nit
+homepage=http://nitlanguage.org
+issues=https://github.com/nitlang/nit/issues
diff --git a/lib/config.nit b/lib/config.nit
new file mode 100644 (file)
index 0000000..83566a6
--- /dev/null
@@ -0,0 +1,279 @@
+# 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.
+
+# Configuration options for nit tools and apps
+#
+# This module provides basic services for options handling in your Nit programs.
+#
+# ## Basic configuration holder
+#
+# The `Config` class can be used as a simple option holder and processor:
+#
+# ~~~
+# import config
+#
+# # Create a new config option
+# var opt_my = new OptionString("My option", "--my")
+#
+# # Create the config and add the option
+# var config = new Config
+# config.add_option(opt_my)
+#
+# # Parse the program arguments, usually `args`
+# config.parse_options(["--my", "myOption", "arg1", "arg2"])
+#
+# # Access the options and args
+# assert opt_my.value == "myOption"
+# assert config.args == ["arg1", "arg2"]
+# ~~~
+#
+# ## Custom configuration class
+#
+# Instead of using basic `Config` instances, it is better to define new sublcasses
+# to store options and define custom services.
+#
+# ~~~
+# import config
+#
+# class MyConfig
+#      super Config
+#
+#      var opt_my = new OptionString("My option", "--my")
+#
+#      init do
+#              super
+#              tool_description = "Usage: MyExample [OPTION]... [ARGS]..."
+#              add_option(opt_my)
+#      end
+#
+#      fun my: String do return opt_my.value or else "Default value"
+# end
+#
+# var config = new MyConfig
+# config.parse_options(["--my", "myOption", "arg1", "arg2"])
+#
+# assert config.my == "myOption"
+# assert config.args == ["arg1", "arg2"]
+# ~~~
+#
+# We define the `my` method to provide an elegant shortcut to `opt_my.value`
+# and define the default value if the option was not set by the user.
+#
+# The `tool_description` attribute is used to set the `usage` header printed when
+# the user request the `help` message.
+#
+# ~~~
+# config.parse_options(["-h"])
+# if config.help then
+#      config.usage
+#      exit 0
+# end
+# ~~~
+#
+# This will display the tool usage like this:
+#
+# ~~~raw
+# Usage: MyExample [OPTION]... [ARGS]...
+#  -h, --help   Show this help message
+#  --my         My option
+# ~~~
+#
+# ## Configuration with `ini` file
+#
+# The `IniConfig` class extends `Config` to add an easy way to link your
+# configuration to an ini file.
+
+# ~~~
+# class MyIniConfig
+#      super IniConfig
+#
+#      var opt_my = new OptionString("My option", "--my")
+#
+#      init do
+#              super
+#              tool_description = "Usage: MyExample [OPTION]... [ARGS]..."
+#              opts.add_option(opt_my)
+#      end
+#
+#      fun my: String do return opt_my.value or else ini["my"] or else "Default"
+# end
+# ~~~
+#
+# This time, we define the `my` method to return the option value or the ini
+# if no option was passed. Finally, if no ini value can be found, we return the
+# default value.
+#
+# By default, `IniConfig` looks at a `config.ini` file in the execution directory.
+# This can be overrided in multiple ways.
+#
+# First by the app user by setting the `--config` option:
+#
+# ~~~
+# var config = new MyIniConfig
+# config.parse_options(["--config", "my_config.ini"])
+#
+# assert config.config_file == "my_config.ini"
+# ~~~
+#
+# Default config file can also be changed by the library client through the
+# `default_config_file` attribute:
+#
+# ~~~
+# config = new MyIniConfig
+# config.default_config_file = "my_config.ini"
+# config.parse_options(["arg"])
+#
+# assert config.config_file == "my_config.ini"
+# ~~~
+#
+# Or by the library developper in the custom config class:
+#
+# ~~~
+# class MyCustomIniConfig
+#      super IniConfig
+#
+#      redef var default_config_file = "my_config.ini"
+# end
+#
+# var config = new MyCustomIniConfig
+# config.parse_options(["arg"])
+#
+# assert config.config_file == "my_config.ini"
+# ~~~
+module config
+
+import ini
+import opts
+
+# Basic configuration class
+#
+# ~~~
+# import config
+#
+# class MyConfig
+#      super Config
+#
+#      var opt_my = new OptionString("My option", "--my")
+#
+#      init do
+#              super
+#              tool_description = "Usage: MyExample [OPTION]... [ARGS]..."
+#              opts.add_option(opt_my)
+#      end
+#
+#      fun my: String do return opt_my.value or else "Default value"
+# end
+#
+# var config = new MyConfig
+# config.parse_options(["--my", "hello", "arg1", "arg2"])
+# assert config.my == "hello"
+# assert config.args == ["arg1", "arg2"]
+# ~~~
+class Config
+
+       # Context used to store and parse options
+       var opts = new OptionContext
+
+       # Help option
+       var opt_help = new OptionBool("Show this help message", "-h", "--help")
+
+       # Redefine this init to add your options
+       init do
+               add_option(opt_help)
+       end
+
+       # Add an option to `self`
+       #
+       # Shortcut to `opts.add_option`.
+       fun add_option(opt: Option...) do opts.add_option(opt...)
+
+       # Initialize `self` options from `args`
+       fun parse_options(args: Collection[String]) do
+               opts.parse(args)
+       end
+
+       # Return the remaining args once options are parsed by `from_args`
+       fun args: Array[String] do return opts.rest
+
+       # Name, usage and synopsis of the tool.
+       # It is mainly used in `usage`.
+       # Should be correctly set by the client before calling `usage`
+       # A multi-line string is recommended.
+       #
+       # eg. `"Usage: tool [OPTION]... [FILE]...\nDo some things."`
+       var tool_description: String = "Usage: [OPTION]... [ARG]..." is writable
+
+       # Was the `--help` option requested?
+       fun help: Bool do return opt_help.value
+
+       # Display `tool_description` and options usage in console
+       fun usage do
+               print tool_description
+               opts.usage
+       end
+end
+
+# Configuration class based on a INI file.
+#
+# ~~~
+# class MyIniConfig
+#      super IniConfig
+#
+#      var opt_my = new OptionString("My option", "--my")
+#
+#      init do
+#              super
+#              tool_description = "Usage: MyExample [OPTION]... [ARGS]..."
+#              opts.add_option(opt_my)
+#      end
+#
+#      fun my: String do return opt_my.value or else ini["my"] or else "Default"
+# end
+#
+# var config = new MyIniConfig
+# config.default_config_file = "my_config.ini"
+# config.parse_options(args)
+#
+# if config.help then
+#      config.usage
+#      exit 0
+# end
+#
+# assert config.my == "Default"
+# ~~~
+class IniConfig
+       super Config
+
+       # Config tree used to store config options
+       var ini: ConfigTree is noinit
+
+       # Path to app config file
+       var opt_config = new OptionString("Path to config file", "--config")
+
+       init do
+               super
+               opts.add_option(opt_config)
+       end
+
+       redef fun parse_options(args) do
+               super
+               ini = new ConfigTree(config_file)
+       end
+
+       # Default config file path
+       var default_config_file = "config.ini" is writable
+
+       # Return the config file path from options or the default
+       fun config_file: String do return opt_config.value or else default_config_file
+end
index 85b457f..9e7064a 100644 (file)
@@ -572,15 +572,16 @@ class Bytes
 
        # Decode `self` from percent (or URL) encoding to a clear string
        #
-       # Replace invalid use of '%' with '?'.
+       # Invalid '%' are not decoded.
        #
        #     assert "aBc09-._~".to_bytes.from_percent_encoding == "aBc09-._~".to_bytes
        #     assert "%25%28%29%3c%20%3e".to_bytes.from_percent_encoding == "%()< >".to_bytes
        #     assert ".com%2fpost%3fe%3dasdf%26f%3d123".to_bytes.from_percent_encoding == ".com/post?e=asdf&f=123".to_bytes
        #     assert "%25%28%29%3C%20%3E".to_bytes.from_percent_encoding == "%()< >".to_bytes
-       #     assert "incomplete %".to_bytes.from_percent_encoding == "incomplete ?".to_bytes
-       #     assert "invalid % usage".to_bytes.from_percent_encoding == "invalid ? usage".to_bytes
+       #     assert "incomplete %".to_bytes.from_percent_encoding == "incomplete %".to_bytes
+       #     assert "invalid % usage".to_bytes.from_percent_encoding == "invalid % usage".to_bytes
        #     assert "%c3%a9%e3%81%82%e3%81%84%e3%81%86".to_bytes.from_percent_encoding == "éあいう".to_bytes
+       #     assert "%1 %A %C3%A9A9".to_bytes.from_percent_encoding == "%1 %A éA9".to_bytes
        fun from_percent_encoding: Bytes do
                var tmp = new Bytes.with_capacity(length)
                var pos = 0
@@ -592,14 +593,14 @@ class Bytes
                                continue
                        end
                        if length - pos < 2 then
-                               tmp.add '?'.ascii
+                               tmp.add '%'.ascii
                                pos += 1
                                continue
                        end
                        var bn = self[pos + 1]
                        var bnn = self[pos + 2]
                        if not bn.is_valid_hexdigit or not bnn.is_valid_hexdigit then
-                               tmp.add '?'.ascii
+                               tmp.add '%'.ascii
                                pos += 1
                                continue
                        end
index f95377e..50f0aff 100644 (file)
@@ -843,15 +843,16 @@ abstract class Text
 
        # Decode `self` from percent (or URL) encoding to a clear string
        #
-       # Replace invalid use of '%' with '?'.
+       # Invalid '%' are not decoded.
        #
        #     assert "aBc09-._~".from_percent_encoding == "aBc09-._~"
        #     assert "%25%28%29%3c%20%3e".from_percent_encoding == "%()< >"
        #     assert ".com%2fpost%3fe%3dasdf%26f%3d123".from_percent_encoding == ".com/post?e=asdf&f=123"
        #     assert "%25%28%29%3C%20%3E".from_percent_encoding == "%()< >"
-       #     assert "incomplete %".from_percent_encoding == "incomplete ?"
-       #     assert "invalid % usage".from_percent_encoding == "invalid ? usage"
+       #     assert "incomplete %".from_percent_encoding == "incomplete %"
+       #     assert "invalid % usage".from_percent_encoding == "invalid % usage"
        #     assert "%c3%a9%e3%81%82%e3%81%84%e3%81%86".from_percent_encoding == "éあいう"
+       #     assert "%1 %A %C3%A9A9".from_percent_encoding == "%1 %A éA9"
        fun from_percent_encoding: String
        do
                var len = byte_length
@@ -874,7 +875,7 @@ abstract class Text
                        if c == '%' then
                                if i + 2 >= length then
                                        # What follows % has been cut off
-                                       buf[l] = '?'.ascii
+                                       buf[l] = '%'.ascii
                                else
                                        i += 1
                                        var hex_s = substring(i, 2)
@@ -884,7 +885,7 @@ abstract class Text
                                                i += 1
                                        else
                                                # What follows a % is not Hex
-                                               buf[l] = '?'.ascii
+                                               buf[l] = '%'.ascii
                                                i -= 1
                                        end
                                end
index 1a9ed1a..d236427 100644 (file)
@@ -757,6 +757,9 @@ class PullRequest
 
        # Changed files count.
        var changed_files: Int is writable
+
+       # URL to patch file
+       var patch_url: nullable String is writable
 end
 
 # A pull request reference (used for head and base).
index 30ea286..c93d39a 100644 (file)
@@ -292,9 +292,7 @@ class OptionContext
        private var optmap = new HashMap[String, Option]
 
        # Add one or more options to the context
-       fun add_option(opts: Option...) do
-                       options.add_all(opts)
-       end
+       fun add_option(opts: Option...) do options.add_all(opts)
 
        # Display all the options available
        fun usage
index 1883b52..d92e9d9 100644 (file)
 # import popcorn
 # import popcorn::pop_config
 #
-# # Parse app options
-# var opts = new AppOptions.from_args(args)
-#
 # # Build config from options
-# var config = new AppConfig.from_options(opts)
+# var config = new AppConfig
+# config.parse_options(args)
 #
 # # Use options
 # var app = new App
 #      super AppConfig
 #
 #      # My secret code I don't want to share in my source repository
-#      var secret: String = value_or_default("secret", "my-secret")
-#
-#      redef init from_options(options) do
-#              super
-#              if options isa MyOptions then
-#                      var secret = options.opt_secret.value
-#                      if secret != null then self["secret"] = secret
-#              end
-#      end
-# end
-#
-# class MyOptions
-#      super AppOptions
+#      fun secret: String do return opt_secret.value or else ini["secret"] or else "my-secret"
 #
+#      # opt --secret
 #      var opt_secret = new OptionString("My secret string", "--secret")
 #
 #      redef init do
@@ -81,8 +68,8 @@
 #      end
 # end
 #
-# var opts = new MyOptions.from_args(args)
-# var config = new MyConfig.from_options(opts)
+# var config = new MyConfig
+# config.parse_options(args)
 #
 # var app = new App
 # app.use("/secret", new SecretHandler(config))
@@ -90,8 +77,7 @@
 # ~~~
 module pop_config
 
-import ini
-import opts
+import config
 
 # Configuration file for Popcorn apps
 #
@@ -100,92 +86,47 @@ import opts
 # import popcorn::pop_config
 #
 # # Build config from default values
-# var config = new AppConfig("app.ini")
+# var config = new AppConfig
+# config.parse_options(args)
 #
 # # Change config values
-# config["app.port"] = 3001.to_s
+# config.ini["app.port"] = 3001.to_s
 #
 # # Use options
 # var app = new App
 # app.listen(config.app_host, config.app_port)
 # ~~~
 class AppConfig
-       super ConfigTree
+       super IniConfig
 
-       # Kind of options used by this config
-       type OPTIONS: AppOptions
+       redef var default_config_file: String = "app.ini"
 
-       # Default configuration file path
-       var default_config_file: String = "app.ini"
+       # Host name to bind on (will overwrite the config one).
+       var opt_host = new OptionString("Host to bind the server on", "--host")
 
        # Web app host name
        #
        # * key: `app.host`
        # * default: `localhost`
-       var app_host: String is lazy do return value_or_default("app.host", "localhost")
+       fun app_host: String do return opt_host.value or else ini["app.host"] or else "localhost"
+
+       # Port number to bind on (will overwrite the config one).
+       var opt_port = new OptionInt("Port number to use", -1, "--port")
 
        # Web app port
        #
        # * key: `app.port`
        # * default: `3000`
-       var app_port: Int is lazy do return value_or_default("app.port", "3000").to_i
-
-       # Init `self` from a `AppOptions` option values
-       init from_options(opts: OPTIONS) do
-               init(opts.opt_config.value or else default_config_file)
-               var opt_host = opts.opt_host.value
-               if opt_host != null then self["app.host"] = opt_host
-               var opt_port = opts.opt_port.value
-               if opt_port > 0 then self["app.port"] = opt_port.to_s
+       fun app_port: Int do
+               var opt = opt_port.value
+               if opt > -1 then return opt
+               var val = ini["app.port"]
+               if val != null then return val.to_i
+               return 3000
        end
 
-       # Return the registered value for `key` or `default`
-       protected fun value_or_default(key: String, default: String): String do
-               return self[key] or else default
-       end
-end
-
-# Options configuration for Popcorn apps
-#
-# Use the `AppOptions` class in your app to parse command line args:
-# ~~~
-# import popcorn
-# import popcorn::pop_config
-#
-# # Parse app options
-# var opts = new AppOptions.from_args(args)
-#
-# # Build config from options
-# var config = new AppConfig.from_options(opts)
-#
-# # Use options
-# var app = new App
-# app.listen(config.app_host, config.app_port)
-# ~~~
-class AppOptions
-       super OptionContext
-
-       # Help option.
-       var opt_help = new OptionBool("Show this help message", "-h", "--help")
-
-       # Path to app config file.
-       var opt_config = new OptionString("Path to app config file", "--config")
-
-       # Host name to bind on (will overwrite the config one).
-       var opt_host = new OptionString("Host to bind the server on", "--host")
-
-       # Port number to bind on (will overwrite the config one).
-       var opt_port = new OptionInt("Port number to use", -1, "--port")
-
-       # You should redefined this method to add your options
        init do
                super
-               add_option(opt_help, opt_config, opt_host, opt_port)
-       end
-
-       # Initialize `self` and parse `args`
-       init from_args(args: Collection[String]) do
-               init
-               parse(args)
+               add_option(opt_host, opt_port)
        end
 end
index 7b02c35..1f12853 100644 (file)
@@ -131,39 +131,28 @@ redef class AppConfig
        # Default database hostname
        var default_db_name = "popcorn"
 
-       # MongoDB server used for data persistence
-       var db_host: String is lazy do return value_or_default("db.host", default_db_host)
-
-       # MongoDB DB used for data persistence
-       var db_name: String is lazy do return value_or_default("db.name", default_db_name)
-
-       # Mongo db client
-       var client = new MongoClient(db_host) is lazy
-
-       # Mongo db instance
-       var db: MongoDb = client.database(db_name) is lazy
-
-       redef init from_options(opts) do
-               super
-               var db_host = opts.opt_db_host.value
-               if db_host != null then self["db.host"] = db_host
-               var db_name = opts.opt_db_name.value
-               if db_name != null then self["db.name"] = db_name
-       end
-end
-
-redef class AppOptions
-
        # MongoDb host name
        var opt_db_host = new OptionString("MongoDb host", "--db-host")
 
        # MongoDb database name
        var opt_db_name = new OptionString("MongoDb database name", "--db-name")
 
+       # MongoDB server used for data persistence
+       fun db_host: String do return opt_db_host.value or else ini["db.host"] or else default_db_host
+
+       # MongoDB DB used for data persistence
+       fun db_name: String do return opt_db_name.value or else ini["db.name"] or else default_db_name
+
        init do
                super
                add_option(opt_db_host, opt_db_name)
        end
+
+       # Mongo db client
+       var client = new MongoClient(db_host) is lazy
+
+       # Mongo db instance
+       var db: MongoDb = client.database(db_name) is lazy
 end
 
 # A Repository is an object that can store serialized instances.
index 740ea8b..ae57696 100644 (file)
@@ -1,3 +1,3 @@
 <span>
-       <a ng-href='{{mentity.web_url}}'>{{mentity.name}}</a>
+       <a ng-href='{{mentity.web_url | encodeURI}}'>{{mentity.name}}</a>
 </span>
index 7e817c6..05f8ccb 100644 (file)
@@ -1,5 +1,4 @@
-<span ng-if='mentity.location'>
-       <a ng-href="{{mentity.web_url}}">{{mentity.location.file}}
-               <span ng-if='mentity.location.line_start'>:{{mentity.location.line_start}}</span>
-       </a>
+<span ng-if='mentity.location' class='text-muted'>
+       {{mentity.location.file}}
+       <span ng-if='mentity.location.line_start'>:{{mentity.location.line_start}}</span>
 </span>
diff --git a/share/nitweb/directives/entity/rating.html b/share/nitweb/directives/entity/rating.html
new file mode 100644 (file)
index 0000000..25d58ef
--- /dev/null
@@ -0,0 +1,54 @@
+<h4>Grade this entity</h4>
+
+<div class='container-fluid'>
+       <div class='col-xs-8'>
+               <dl class='dl-horizontal'>
+                       <dt>Feature</dt>
+                       <dd>
+                               <entity-stars mentity='mentity' dimension='feature' mean='ratings.feature.mean' list='ratings.feature.ratings' user='ratings.feature.user' ratings='ratings' />
+                               <p class='text-muted'><i>This entity is useful, usable and has a good API. It helps me getting things done.</i></p>
+                       </dd>
+                       <dt>Documentation</dt>
+                       <dd>
+                               <entity-stars mentity='mentity' dimension='doc' mean='ratings.doc.mean' list='ratings.doc.ratings' user='ratings.doc.user' ratings='ratings' />
+                               <p class='text-muted'><i>This entity is well documented, the explanations are clear and useful. They help me understand the feature and how to use it.</i></p>
+                       </dd>
+                       <dt>Examples</dt>
+                       <dd>
+                               <entity-stars mentity='mentity' dimension='examples' mean='ratings.examples.mean' list='ratings.examples.ratings' user='ratings.examples.user' ratings='ratings' />
+                               <p class='text-muted'><i>This entity is provided with examples, they are runnable and free of bugs. They help me understand the feature behavior and how to use it.</i></p>
+                       </dd>
+                       <dt>Code</dt>
+                       <dd>
+                               <entity-stars mentity='mentity' dimension='code' mean='ratings.code.mean' list='ratings.code.ratings' user='ratings.code.user' ratings='ratings' />
+                               <p class='text-muted'><i>This entity is well coded and respects the Nit standards.</i></p>
+                       </dd>
+               </dl>
+       </div>
+       <div class='col-xs-4'>
+               <h1 class='text-center'>
+                       <span class='text-danger'>{{ratings.feature.mean + ratings.doc.mean + ratings.examples.mean + ratings.code.mean | number: 1}} / 20</span><br><small>{{ratings.ratings.length}} votes</small>
+               </h1>
+       </div>
+</div>
+
+<h4>History</h4>
+
+<table class='table'>
+       <tr ng-repeat='rating in ratings.ratings | orderBy: "-timestamp"'>
+               <td>{{rating.timestamp * 1000 | date: 'yy/MM/dd hh:mm a'}}</td>
+               <td>
+                       <i>{{rating.user ? rating.user : "anon"}}<i>
+               </td>
+               <td>
+                       rated the <b>{{rating.dimension}}</b>
+               </td>
+               <td>
+                       <span class='stars' ng-repeat='star in [1, 2, 3, 4, 5]'>
+                               <span
+                                       class='star glyphicon'
+                                       ng-class='star <= rating.rating? "glyphicon-star": "glyphicon-star-empty"' />
+                       </span>
+               </td>
+       </tr>
+</table>
index 796bc3b..8af294f 100644 (file)
@@ -1,6 +1,6 @@
-<span class='stars' ng-repeat='star in [1, 2, 3, 4, 5]' ng-if='ratings' title='mean: {{ratings.mean}} ({{ratings.ratings.length}} stars)'>
+<span class='stars editable' ng-repeat='star in [1, 2, 3, 4, 5]' title='mean: {{mean ? mean : 0 | number: 1}} ({{ratings ? ratings.length : 0}} stars)'>
        <span
                class='star glyphicon'
-               ng-class='star <= ratings.mean? "glyphicon-star": "glyphicon-star-empty"'
-               ng-click='postStar(star)' />
+               ng-class='[star <= mean? "glyphicon-star": "glyphicon-star-empty", user.rating == star? "active": ""]'
+               ng-click='starsCtrl.postStar(star)' />
 </span>
index c7657bc..930931d 100644 (file)
@@ -35,6 +35,7 @@
                                                        </a>
                                                        <ul class='dropdown-menu'>
                                                                <li><a href='/docdown'>DocDown</a></li>
+                                                               <li><a href='/grades'>Grades</a></li>
                                                        </ul>
                                                </li>
                                        </ul>
@@ -88,5 +89,6 @@
                <script src='/javascripts/docdown.js'></script>
                <script src='/javascripts/metrics.js'></script>
                <script src='/javascripts/users.js'></script>
+               <script src='/javascripts/grades.js'></script>
        </body>
 </html>
index 98903e7..e48b40a 100644 (file)
@@ -18,7 +18,7 @@
        angular
                .module('entities', ['ngSanitize', 'ui', 'model'])
 
-               .controller('EntityCtrl', ['Model', 'Metrics', '$routeParams', '$scope', '$sce', function(Model, Metrics, $routeParams, $scope, $sce) {
+               .controller('EntityCtrl', ['Model', 'Metrics', 'Feedback', '$routeParams', '$scope', '$sce', function(Model, Metrics, Feedback, $routeParams, $scope, $sce) {
                        $scope.entityId = $routeParams.id;
 
                        this.loadEntityLinearization = function() {
                        };
                })
 
-               .directive('entityCard', function() {
+               .directive('entityCard', ['Feedback', function(Feedback) {
                        return {
                                restrict: 'E',
                                scope: {
                                templateUrl: '/directives/entity/card.html',
                                link: function ($scope, element, attrs) {
                                        $scope.currentTab = $scope.defaultTab ? $scope.defaultTab : 'signature';
+
+                                       $scope.loadEntityStars = function() {
+                                               Feedback.loadEntityStars($scope.mentity.full_name,
+                                                       function(data) {
+                                                               $scope.ratings = data;
+                                                       }, function(message, status) {
+                                                               $scope.error = {message: message, status: status};
+                                                       });
+                                       };
                                }
                        };
-               })
+               }])
 
                .directive('entityList', function() {
                        return {
                        };
                }])
 
-               .directive('entityRating', ['Feedback', function(Feedback, Code) {
+               .controller('StarsCtrl', ['Feedback', '$scope', function(Feedback, $scope) {
+                       $ctrl = this;
+
+                       this.postStar = function(rating) {
+                               Feedback.postEntityStarDimension($scope.mentity.full_name,
+                               $scope.dimension, rating,
+                               function(data) {
+                                       $scope.mean = data.mean;
+                                       $scope.list = data.ratings;
+                                       $scope.user = data.user;
+                                       $ctrl.loadEntityStars($scope);
+                               }, function(err) {
+                                       $scope.err = err;
+                               });
+                       }
+
+                       this.loadEntityStars = function($scope) {
+                               Feedback.loadEntityStars($scope.mentity.full_name,
+                                       function(data) {
+                                               $scope.ratings = data;
+                                       }, function(message, status) {
+                                               $scope.error = {message: message, status: status};
+                                       });
+                       };
+               }])
+
+               .directive('entityRating', ['Feedback', function(Feedback) {
                        return {
                                restrict: 'E',
                                scope: {
-                                       mentity: '='
+                                       mentity: '=',
+                                       ratings: '='
                                },
-                               templateUrl: '/directives/entity/stars.html',
-                               link: function ($scope, element, attrs) {
-                                       $scope.postStar = function(rating) {
-                                               Feedback.postEntityStar($scope.mentity.full_name, rating,
-                                               function(data) {
-                                                       $scope.ratings = data;
-                                               }, function(err) {
-                                                       $scope.err = err;
-                                               });
-                                       }
+                               controller: 'StarsCtrl',
+                               controllerAs: 'ratingsCtrl',
+                               templateUrl: '/directives/entity/rating.html'
+                       };
+               }])
 
-                                       Feedback.loadEntityStars($scope.mentity.full_name,
-                                               function(data) {
-                                                       $scope.ratings = data;
-                                               }, function(err) {
-                                                       $scope.err = err;
-                                               });
-                               }
+               .directive('entityStars', ['Feedback', function(Feedback) {
+                       return {
+                               restrict: 'E',
+                               scope: {
+                                       mentity: '=',
+                                       dimension: '@',
+                                       mean: '=',
+                                       list: '=',
+                                       user: '=',
+                                       refresh: '=',
+                                       ratings: '='
+                               },
+                               controller: 'StarsCtrl',
+                               controllerAs: 'starsCtrl',
+                               templateUrl: '/directives/entity/stars.html'
                        };
                }])
 })();
diff --git a/share/nitweb/javascripts/grades.js b/share/nitweb/javascripts/grades.js
new file mode 100644 (file)
index 0000000..41d6647
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016 Alexandre Terrasa <alexandre@moz-code.org>.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+       angular
+               .module('grades', ['ngSanitize', 'model'])
+
+               .controller('GradesCtrl', ['Feedback', '$scope', function(Feedback, $scope) {
+
+                       this.loadMostRated = function() {
+                               Feedback.loadMostRated(
+                                       function(data) {
+                                               $scope.most = data;
+                                       }, function(err) {
+                                               $scope.error = err;
+                                       });
+                       };
+
+                       this.loadBestRated = function() {
+                               Feedback.loadBestRated(
+                                       function(data) {
+                                               $scope.best = data;
+                                       }, function(err) {
+                                               $scope.error = err;
+                                       });
+                       };
+                       this.loadWorstRated = function() {
+                               Feedback.loadWorstRated(
+                                       function(data) {
+                                               $scope.worst = data;
+                                       }, function(err) {
+                                               $scope.error = err;
+                                       });
+                       };
+                       this.loadUsersRatings = function() {
+                               Feedback.loadUsersRatings(
+                                       function(data) {
+                                               $scope.ratings = data;
+                                       }, function(err) {
+                                               $scope.error = err;
+                                       });
+                       };
+
+                       this.loadMostRated();
+                       this.loadBestRated();
+                       this.loadWorstRated();
+                       this.loadUsersRatings();
+               }])
+})();
index 8ec516c..fb2c008 100644 (file)
                                                .success(cb)
                                                .error(cbErr);
                                },
-                               postEntityStar: function(id, rating, cb, cbErr) {
-                                       $http.post(apiUrl + '/feedback/stars/' + id, {rating: rating})
+                               loadEntityStarsDimension: function(id, dimension, cb, cbErr) {
+                                       $http.get(apiUrl + '/feedback/stars/' + id + '/dimension/' + dimension)
                                                .success(cb)
                                                .error(cbErr);
-                               }
+                               },
+                               postEntityStarDimension: function(id, dimension, rating, cb, cbErr) {
+                                       $http.post(apiUrl + '/feedback/stars/' + id + '/dimension/' + dimension,
+                                               {rating: rating})
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+                               loadMostRated: function(cb, cbErr) {
+                                       $http.get(apiUrl + '/feedback/grades/most')
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+                               loadBestRated: function(cb, cbErr) {
+                                       $http.get(apiUrl + '/feedback/grades/best')
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+                               loadWorstRated: function(cb, cbErr) {
+                                       $http.get(apiUrl + '/feedback/grades/worst')
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+                               loadUsersRatings: function(cb, cbErr) {
+                                       $http.get(apiUrl + '/feedback/grades/users')
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
                        }
                }])
 
                                        $http.get(apiUrl + '/user')
                                                .success(cb)
                                                .error(cbErr);
-                               }
+                               },
+                               loadUserStars: function(cb, cbErr) {
+                                       $http.get(apiUrl + '/feedback/user/stars')
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
                        }
                }])
 })();
index f3ba26d..78f0f71 100644 (file)
@@ -15,7 +15,7 @@
  */
 
 (function() {
-       angular.module('nitweb', ['ngRoute', 'ngSanitize', 'angular-loading-bar', 'entities', 'docdown', 'index', 'metrics', 'users'])
+       angular.module('nitweb', ['ngRoute', 'ngSanitize', 'angular-loading-bar', 'entities', 'docdown', 'index', 'metrics', 'users', 'grades'])
        .config(['cfpLoadingBarProvider', function(cfpLoadingBarProvider) {
                cfpLoadingBarProvider.includeSpinner = false;
        }])
                                controller: 'DocdownCtrl',
                                controllerAs: 'docdownCtrl'
                        })
+                       .when('/grades', {
+                               templateUrl: 'views/grades.html',
+                               controller: 'GradesCtrl',
+                               controllerAs: 'gradesCtrl'
+                       })
                        .when('/login', {
                                controller : function(){
                                        window.location.replace('/login');
@@ -58,5 +63,9 @@
                                templateUrl: 'views/error.html'
                        });
                $locationProvider.html5Mode(true);
+       })
+
+       .filter('encodeURI', function() {
+               return encodeURIComponent;
        });
 })();
index 42e7d6f..b9c077a 100644 (file)
                                                $scope.error = err;
                                        });
                        };
-
+                       this.loadGrades = function() {
+                               User.loadUserStars(
+                                       function(data) {
+                                               $scope.ratings = data;
+                                       }, function(err) {
+                                               $scope.error = err;
+                                       });
+                       };
                        this.loadUser();
+                       this.loadGrades();
                }])
 
                .directive('userMenu', ['User', function(User) {
index 3ca9e18..b78f244 100644 (file)
@@ -245,20 +245,15 @@ entity-list:hover .btn-filter {
  * Ratings
  */
 
-.card .stars {
-       visibility: hidden
-}
-
-.card:hover .stars {
-       visibility: visible
-}
-
 .star {
        color: grey;
+}
+
+.editable .star {
        cursor: pointer;
 }
 
-.star:hover, .star.active:hover {
+.editable .star:hover, .editable .star.active:hover {
        color: #FF8100
 }
 
index 04dfa46..1e1365b 100644 (file)
@@ -9,11 +9,6 @@
                        <span class='glyphicon glyphicon-arrow-down'/> Linearization
                </a>
        </li>
-       <li role='presentation'>
-               <a data-toggle='tab' data-target='#code' ng-click="entityCtrl.loadEntityCode()">
-                       <span class='glyphicon glyphicon-console'/> Code
-               </a>
-       </li>
 </ul>
 
 <div class='tab-content'>
                        list-entities='linearization'
                        list-focus='mentity' />
        </div>
-       <div role='tabpanel' class='tab-pane fade' id='code'>
-               <div class='card'>
-                       <div class='card-body'>
-                               <pre ng-bind-html='code' />
-                               <entity-location mentity='mentity' />
-                       </div>
-               </div>
-       </div>
 </div>
diff --git a/share/nitweb/views/grades.html b/share/nitweb/views/grades.html
new file mode 100644 (file)
index 0000000..3a928f5
--- /dev/null
@@ -0,0 +1,93 @@
+<div class='container-fluid'>
+       <div class='page-header'>
+               <h2>Grades</h2>
+
+               <div class='container-fluid'>
+                       <div class='col-xs-6'>
+                               <div class='card'>
+                                       <div class='card-body'>
+                                               <h4 class='card-title'>Most rated entities</h4>
+                                               <p ng-if='!most || most.length == 0' class='text-muted'>
+                                                       <i>No grades yet</i>
+                                               </p>
+                                               <table class='table' ng-if='most.length > 0'>
+                                                       <tr>
+                                                               <th width='60%'>Entity</th>
+                                                               <th>Total grades</th>
+                                                       </tr>
+                                                       <tr ng-repeat='rating in most'>
+                                                               <th><a href='/doc/{{rating._id}}'>{{rating._id}}</a></th>
+                                                               <td>{{rating.count}}</td>
+                                                       </tr>
+                                               </table>
+                                       </div>
+                               </div>
+                       </div>
+                       <div class='col-xs-6'>
+                               <div class='card'>
+                                       <div class='card-body'>
+                                               <h4 class='card-title'>Best rated entities</h4>
+                                               <p ng-if='!best || best.length == 0' class='text-muted'>
+                                                       <i>No grades yet</i>
+                                               </p>
+                                               <table class='table' ng-if='best.length > 0'>
+                                                       <tr>
+                                                               <th width='60%'>Entity</th>
+                                                               <th>Avg. grade</th>
+                                                       </tr>
+                                                       <tr ng-repeat='rating in best'>
+                                                               <th><a href='/doc/{{rating._id}}'>{{rating._id}}</a></th>
+                                                               <td>{{rating.avg}}</td>
+                                                       </tr>
+                                               </table>
+                                       </div>
+                               </div>
+                       </div>
+                       <div class='col-xs-6'>
+                               <div class='card'>
+                                       <div class='card-body'>
+                                               <h4 class='card-title'>Most active users</h4>
+                                               <p ng-if='!ratings || ratings.length == 0' class='text-muted'>
+                                                       <i>No grades yet</i>
+                                               </p>
+                                               <table class='table' ng-if='ratings.length > 0'>
+                                                       <tr>
+                                                               <th width='60%'>User</th>
+                                                               <th>Total grades</th>
+                                                       </tr>
+                                                       <tr ng-repeat='rating in ratings'>
+                                                               <th ng-if='rating._id'>
+                                                                       <b>{{rating._id}}</b>
+                                                               </th>
+                                                               <th ng-if='!rating._id'>
+                                                                       <em>anon.</em>
+                                                               </th>
+                                                               <td>{{rating.count}}</td>
+                                                       </tr>
+                                               </table>
+                                       </div>
+                               </div>
+                       </div>
+                       <div class='col-xs-6'>
+                               <div class='card'>
+                                       <div class='card-body'>
+                                               <h4 class='card-title'>Worst rated entities</h4>
+                                               <p ng-if='!worst || worst.length == 0' class='text-muted'>
+                                                       <i>No grades yet</i>
+                                               </p>
+                                               <table class='table' ng-if='worst.length > 0'>
+                                                       <tr>
+                                                               <th width='60%'>Entity</th>
+                                                               <th>Total grades</th>
+                                                       </tr>
+                                                       <tr ng-repeat='rating in worst'>
+                                                               <th><a href='/doc/{{rating._id}}'>{{rating._id}}</a></th>
+                                                               <td>{{rating.avg}}</td>
+                                                       </tr>
+                                               </table>
+                                       </div>
+                               </div>
+                       </div>
+               </div>
+       </div>
+</div>
index 825a782..975cfb5 100644 (file)
@@ -9,11 +9,6 @@
                        <span class='glyphicon glyphicon-arrow-down'/> Linearization
                </a>
        </li>
-       <li role='presentation'>
-               <a data-toggle='tab' data-target='#code' ng-click="entityCtrl.loadEntityCode()">
-                       <span class='glyphicon glyphicon-console'/> Code
-               </a>
-       </li>
 </ul>
 
 <div class='tab-content'>
                        list-entities='linearization'
                        list-focus='mentity' />
        </div>
-       <div role='tabpanel' class='tab-pane fade' id='code'>
-               <div class='card'>
-                       <div class='card-body'>
-                               <pre ng-bind-html='code' />
-                               <entity-location mentity='mentity' />
-                       </div>
-               </div>
-       </div>
 </div>
index b044c9c..c9f10f5 100644 (file)
                </ul>
        </div>
        <div class='col-xs-8'>
-               Nothing to display yet.
+               <h4>Your grades</h4>
+
+               <table class='table'>
+                       <tr ng-repeat='rating in ratings | orderBy: "-timestamp"'>
+                               <td>{{rating.timestamp * 1000 | date: 'yy/MM/dd hh:mm a'}}</td>
+                               <td>
+                                       <b><a href='/doc/{{rating.mentity}}'>{{rating.mentity}}</a></b>
+                               </td>
+                               <td>
+                                       <b>{{rating.dimension}}</b>
+                               </td>
+                               <td>
+                                       <span class='stars' ng-repeat='star in [1, 2, 3, 4, 5]'>
+                                               <span class='star glyphicon'
+                                                 ng-class='star <= rating.rating? "glyphicon-star": "glyphicon-star-empty"' />
+                                       </span>
+                               </td>
+                       </tr>
+               </table>
        </div>
 <div>
index ddd7023..8fe8754 100644 (file)
@@ -77,6 +77,14 @@ redef class MEntity
 
        # Same as `to_full_json` but with pretty json.
        fun to_pretty_full_json: String do return serialize_to_full_json(plain=true, pretty=true)
+
+       # Sort mentities by name
+       private fun sort_entities(mentities: Collection[MEntity]): Array[MEntity] do
+               var sorter = new MEntityNameSorter
+               var sorted = mentities.to_a
+               sorter.sort(sorted)
+               return sorted
+       end
 end
 
 redef class MDoc
@@ -111,7 +119,7 @@ redef class MPackage
                super
                if v isa FullJsonSerializer then
                        v.serialize_attribute("root", to_mentity_ref(root))
-                       v.serialize_attribute("mgroups", to_mentity_refs(mgroups))
+                       v.serialize_attribute("mgroups", to_mentity_refs(sort_entities(mgroups)))
                        var ini = self.ini
                        if ini != null then v.serialize_attribute("ini", ini.to_map)
                end
@@ -126,8 +134,8 @@ redef class MGroup
                        v.serialize_attribute("mpackage", to_mentity_ref(mpackage))
                        v.serialize_attribute("default_mmodule", to_mentity_ref(default_mmodule))
                        v.serialize_attribute("parent", to_mentity_ref(parent))
-                       v.serialize_attribute("mmodules", to_mentity_refs(mmodules))
-                       v.serialize_attribute("mgroups", to_mentity_refs(in_nesting.direct_smallers))
+                       v.serialize_attribute("mmodules", to_mentity_refs(sort_entities(mmodules)))
+                       v.serialize_attribute("mgroups", to_mentity_refs(sort_entities(in_nesting.direct_smallers)))
                end
        end
 end
@@ -139,10 +147,10 @@ redef class MModule
                        var view = private_view
                        v.serialize_attribute("mpackage", to_mentity_ref(mpackage))
                        v.serialize_attribute("mgroup", to_mentity_ref(mgroup))
-                       v.serialize_attribute("intro_mclasses", to_mentity_refs(intro_mclasses))
-                       v.serialize_attribute("mclassdefs", to_mentity_refs(mclassdefs))
-                       v.serialize_attribute("intro_mclassdefs", to_mentity_refs(collect_intro_mclassdefs(view)))
-                       v.serialize_attribute("redef_mclassdefs", to_mentity_refs(collect_redef_mclassdefs(view)))
+                       v.serialize_attribute("intro_mclasses", to_mentity_refs(sort_entities(intro_mclasses)))
+                       v.serialize_attribute("mclassdefs", to_mentity_refs(sort_entities(mclassdefs)))
+                       v.serialize_attribute("intro_mclassdefs", to_mentity_refs(sort_entities(collect_intro_mclassdefs(view))))
+                       v.serialize_attribute("redef_mclassdefs", to_mentity_refs(sort_entities(collect_redef_mclassdefs(view))))
                        v.serialize_attribute("imports", to_mentity_refs(in_importation.direct_greaters))
                end
        end
@@ -158,10 +166,10 @@ redef class MClass
                        v.serialize_attribute("intro_mmodule", to_mentity_ref(intro_mmodule))
                        v.serialize_attribute("mpackage", to_mentity_ref(intro_mmodule.mpackage))
                        v.serialize_attribute("mclassdefs", to_mentity_refs(mclassdefs))
-                       v.serialize_attribute("all_mproperties", to_mentity_refs(collect_accessible_mproperties(view)))
-                       v.serialize_attribute("intro_mproperties", to_mentity_refs(collect_intro_mproperties(view)))
-                       v.serialize_attribute("redef_mproperties", to_mentity_refs(collect_redef_mproperties(view)))
-                       v.serialize_attribute("parents", to_mentity_refs(collect_parents(view)))
+                       v.serialize_attribute("all_mproperties", to_mentity_refs(sort_entities(collect_accessible_mproperties(view))))
+                       v.serialize_attribute("intro_mproperties", to_mentity_refs(sort_entities(collect_intro_mproperties(view))))
+                       v.serialize_attribute("redef_mproperties", to_mentity_refs(sort_entities(collect_redef_mproperties(view))))
+                       v.serialize_attribute("parents", to_mentity_refs(sort_entities(collect_parents(view))))
                end
        end
 end
@@ -175,12 +183,12 @@ redef class MClassDef
                        var view = private_view
                        v.serialize_attribute("mmodule", to_mentity_ref(mmodule))
                        v.serialize_attribute("mclass", to_mentity_ref(mclass))
-                       v.serialize_attribute("mpropdefs", to_mentity_refs(mpropdefs))
-                       v.serialize_attribute("intro_mproperties", to_mentity_refs(intro_mproperties))
+                       v.serialize_attribute("mpropdefs", to_mentity_refs(sort_entities(mpropdefs)))
+                       v.serialize_attribute("intro_mproperties", to_mentity_refs(sort_entities(intro_mproperties)))
                        v.serialize_attribute("intro", to_mentity_ref(mclass.intro))
                        v.serialize_attribute("mpackage", to_mentity_ref(mmodule.mpackage))
-                       v.serialize_attribute("intro_mpropdefs", to_mentity_refs(collect_intro_mpropdefs(view)))
-                       v.serialize_attribute("redef_mpropdefs", to_mentity_refs(collect_redef_mpropdefs(view)))
+                       v.serialize_attribute("intro_mpropdefs", to_mentity_refs(sort_entities(collect_intro_mpropdefs(view))))
+                       v.serialize_attribute("redef_mpropdefs", to_mentity_refs(sort_entities(collect_redef_mpropdefs(view))))
                end
        end
 end
@@ -191,7 +199,7 @@ redef class MProperty
                if v isa FullJsonSerializer then
                        v.serialize_attribute("intro", to_mentity_ref(intro))
                        v.serialize_attribute("intro_mclassdef", to_mentity_ref(intro_mclassdef))
-                       v.serialize_attribute("mpropdefs", to_mentity_refs(mpropdefs))
+                       v.serialize_attribute("mpropdefs", to_mentity_refs(sort_entities(mpropdefs)))
                        v.serialize_attribute("intro_mclass", to_mentity_ref(intro_mclassdef.mclass))
                        v.serialize_attribute("mpackage", to_mentity_ref(intro_mclassdef.mmodule.mpackage))
                end
index 2dd320b..404065b 100644 (file)
                "full_name": "test_prog::Object"
        },
        "mpropdefs": [{
-               "full_name": "test_prog$Object$OTHER"
+               "full_name": "test_prog$Object$!="
        }, {
                "full_name": "test_prog$Object$=="
        }, {
-               "full_name": "test_prog$Object$!="
+               "full_name": "test_prog$Object$OTHER"
        }, {
                "full_name": "test_prog$Object$init"
        }],
        "intro_mproperties": [{
-               "full_name": "test_prog::Object::OTHER"
+               "full_name": "test_prog::Object::!="
        }, {
                "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Object::OTHER"
        }, {
                "full_name": "test_prog::Object::init"
        }],
                "full_name": "test_prog"
        },
        "intro_mpropdefs": [{
-               "full_name": "test_prog$Object$OTHER"
+               "full_name": "test_prog$Object$!="
        }, {
                "full_name": "test_prog$Object$=="
        }, {
-               "full_name": "test_prog$Object$!="
+               "full_name": "test_prog$Object$OTHER"
        }, {
                "full_name": "test_prog$Object$init"
        }],
                "full_name": "test_prog::Int"
        },
        "mpropdefs": [{
-               "full_name": "test_prog$Int$unary -"
+               "full_name": "test_prog$Int$*"
        }, {
                "full_name": "test_prog$Int$+"
        }, {
                "full_name": "test_prog$Int$-"
        }, {
-               "full_name": "test_prog$Int$*"
-       }, {
                "full_name": "test_prog$Int$/"
        }, {
                "full_name": "test_prog$Int$>"
        }, {
                "full_name": "test_prog$Int$to_f"
+       }, {
+               "full_name": "test_prog$Int$unary -"
        }],
        "intro_mproperties": [{
-               "full_name": "test_prog::Int::unary -"
+               "full_name": "test_prog::Int::*"
        }, {
                "full_name": "test_prog::Int::+"
        }, {
                "full_name": "test_prog::Int::-"
        }, {
-               "full_name": "test_prog::Int::*"
-       }, {
                "full_name": "test_prog::Int::/"
        }, {
                "full_name": "test_prog::Int::>"
        }, {
                "full_name": "test_prog::Int::to_f"
+       }, {
+               "full_name": "test_prog::Int::unary -"
        }],
        "intro": {
                "full_name": "test_prog$Int"
                "full_name": "test_prog"
        },
        "intro_mpropdefs": [{
-               "full_name": "test_prog$Int$unary -"
+               "full_name": "test_prog$Int$*"
        }, {
                "full_name": "test_prog$Int$+"
        }, {
                "full_name": "test_prog$Int$-"
        }, {
-               "full_name": "test_prog$Int$*"
-       }, {
                "full_name": "test_prog$Int$/"
        }, {
                "full_name": "test_prog$Int$>"
        }, {
                "full_name": "test_prog$Int$to_f"
+       }, {
+               "full_name": "test_prog$Int$unary -"
        }],
        "redef_mpropdefs": []
 }
                "full_name": "test_prog::Float"
        },
        "mpropdefs": [{
+               "full_name": "test_prog$Float$*"
+       }, {
                "full_name": "test_prog$Float$+"
        }, {
                "full_name": "test_prog$Float$-"
        }, {
-               "full_name": "test_prog$Float$*"
-       }, {
                "full_name": "test_prog$Float$/"
        }, {
                "full_name": "test_prog$Float$>"
        }],
        "intro_mproperties": [{
+               "full_name": "test_prog::Float::*"
+       }, {
                "full_name": "test_prog::Float::+"
        }, {
                "full_name": "test_prog::Float::-"
        }, {
-               "full_name": "test_prog::Float::*"
-       }, {
                "full_name": "test_prog::Float::/"
        }, {
                "full_name": "test_prog::Float::>"
                "full_name": "test_prog"
        },
        "intro_mpropdefs": [{
+               "full_name": "test_prog$Float$*"
+       }, {
                "full_name": "test_prog$Float$+"
        }, {
                "full_name": "test_prog$Float$-"
        }, {
-               "full_name": "test_prog$Float$*"
-       }, {
                "full_name": "test_prog$Float$/"
        }, {
                "full_name": "test_prog$Float$>"
                "full_name": "test_prog::Career"
        },
        "mpropdefs": [{
-               "full_name": "test_prog$Career$_strength_bonus"
-       }, {
-               "full_name": "test_prog$Career$strength_bonus"
+               "full_name": "test_prog$Career$_endurance_bonus"
        }, {
-               "full_name": "test_prog$Career$strength_bonus="
+               "full_name": "test_prog$Career$_intelligence_bonus"
        }, {
-               "full_name": "test_prog$Career$_endurance_bonus"
+               "full_name": "test_prog$Career$_strength_bonus"
        }, {
                "full_name": "test_prog$Career$endurance_bonus"
        }, {
                "full_name": "test_prog$Career$endurance_bonus="
        }, {
-               "full_name": "test_prog$Career$_intelligence_bonus"
+               "full_name": "test_prog$Career$Object::init"
        }, {
                "full_name": "test_prog$Career$intelligence_bonus"
        }, {
                "full_name": "test_prog$Career$intelligence_bonus="
        }, {
-               "full_name": "test_prog$Career$Object::init"
+               "full_name": "test_prog$Career$strength_bonus"
+       }, {
+               "full_name": "test_prog$Career$strength_bonus="
        }],
        "intro_mproperties": [{
-               "full_name": "test_prog::careers::Career::_strength_bonus"
-       }, {
-               "full_name": "test_prog::Career::strength_bonus"
+               "full_name": "test_prog::careers::Career::_endurance_bonus"
        }, {
-               "full_name": "test_prog::Career::strength_bonus="
+               "full_name": "test_prog::careers::Career::_intelligence_bonus"
        }, {
-               "full_name": "test_prog::careers::Career::_endurance_bonus"
+               "full_name": "test_prog::careers::Career::_strength_bonus"
        }, {
                "full_name": "test_prog::Career::endurance_bonus"
        }, {
                "full_name": "test_prog::Career::endurance_bonus="
        }, {
-               "full_name": "test_prog::careers::Career::_intelligence_bonus"
-       }, {
                "full_name": "test_prog::Career::intelligence_bonus"
        }, {
                "full_name": "test_prog::Career::intelligence_bonus="
+       }, {
+               "full_name": "test_prog::Career::strength_bonus"
+       }, {
+               "full_name": "test_prog::Career::strength_bonus="
        }],
        "intro": {
                "full_name": "test_prog$Career"
                "full_name": "test_prog"
        },
        "intro_mpropdefs": [{
-               "full_name": "test_prog$Career$_strength_bonus"
-       }, {
-               "full_name": "test_prog$Career$strength_bonus"
+               "full_name": "test_prog$Career$_endurance_bonus"
        }, {
-               "full_name": "test_prog$Career$strength_bonus="
+               "full_name": "test_prog$Career$_intelligence_bonus"
        }, {
-               "full_name": "test_prog$Career$_endurance_bonus"
+               "full_name": "test_prog$Career$_strength_bonus"
        }, {
                "full_name": "test_prog$Career$endurance_bonus"
        }, {
                "full_name": "test_prog$Career$endurance_bonus="
        }, {
-               "full_name": "test_prog$Career$_intelligence_bonus"
-       }, {
                "full_name": "test_prog$Career$intelligence_bonus"
        }, {
                "full_name": "test_prog$Career$intelligence_bonus="
+       }, {
+               "full_name": "test_prog$Career$strength_bonus"
+       }, {
+               "full_name": "test_prog$Career$strength_bonus="
        }],
        "redef_mpropdefs": [{
                "full_name": "test_prog$Career$Object::init"
                "full_name": "test_prog::Race"
        },
        "mpropdefs": [{
-               "full_name": "test_prog$Race$_base_strength"
-       }, {
-               "full_name": "test_prog$Race$base_strength"
+               "full_name": "test_prog$Race$_base_endurance"
        }, {
-               "full_name": "test_prog$Race$base_strength="
+               "full_name": "test_prog$Race$_base_intelligence"
        }, {
-               "full_name": "test_prog$Race$_base_endurance"
+               "full_name": "test_prog$Race$_base_strength"
        }, {
                "full_name": "test_prog$Race$base_endurance"
        }, {
                "full_name": "test_prog$Race$base_endurance="
        }, {
-               "full_name": "test_prog$Race$_base_intelligence"
-       }, {
                "full_name": "test_prog$Race$base_intelligence"
        }, {
                "full_name": "test_prog$Race$base_intelligence="
        }, {
+               "full_name": "test_prog$Race$base_strength"
+       }, {
+               "full_name": "test_prog$Race$base_strength="
+       }, {
                "full_name": "test_prog$Race$Object::init"
        }],
        "intro_mproperties": [{
-               "full_name": "test_prog::races::Race::_base_strength"
-       }, {
-               "full_name": "test_prog::Race::base_strength"
+               "full_name": "test_prog::races::Race::_base_endurance"
        }, {
-               "full_name": "test_prog::Race::base_strength="
+               "full_name": "test_prog::races::Race::_base_intelligence"
        }, {
-               "full_name": "test_prog::races::Race::_base_endurance"
+               "full_name": "test_prog::races::Race::_base_strength"
        }, {
                "full_name": "test_prog::Race::base_endurance"
        }, {
                "full_name": "test_prog::Race::base_endurance="
        }, {
-               "full_name": "test_prog::races::Race::_base_intelligence"
-       }, {
                "full_name": "test_prog::Race::base_intelligence"
        }, {
                "full_name": "test_prog::Race::base_intelligence="
+       }, {
+               "full_name": "test_prog::Race::base_strength"
+       }, {
+               "full_name": "test_prog::Race::base_strength="
        }],
        "intro": {
                "full_name": "test_prog$Race"
                "full_name": "test_prog"
        },
        "intro_mpropdefs": [{
-               "full_name": "test_prog$Race$_base_strength"
-       }, {
-               "full_name": "test_prog$Race$base_strength"
+               "full_name": "test_prog$Race$_base_endurance"
        }, {
-               "full_name": "test_prog$Race$base_strength="
+               "full_name": "test_prog$Race$_base_intelligence"
        }, {
-               "full_name": "test_prog$Race$_base_endurance"
+               "full_name": "test_prog$Race$_base_strength"
        }, {
                "full_name": "test_prog$Race$base_endurance"
        }, {
                "full_name": "test_prog$Race$base_endurance="
        }, {
-               "full_name": "test_prog$Race$_base_intelligence"
-       }, {
                "full_name": "test_prog$Race$base_intelligence"
        }, {
                "full_name": "test_prog$Race$base_intelligence="
+       }, {
+               "full_name": "test_prog$Race$base_strength"
+       }, {
+               "full_name": "test_prog$Race$base_strength="
        }],
        "redef_mpropdefs": [{
                "full_name": "test_prog$Race$Object::init"
                "full_name": "test_prog::Character"
        },
        "mpropdefs": [{
+               "full_name": "test_prog$Character$_age"
+       }, {
+               "full_name": "test_prog$Character$_career"
+       }, {
+               "full_name": "test_prog$Character$_health"
+       }, {
+               "full_name": "test_prog$Character$_name"
+       }, {
                "full_name": "test_prog$Character$_race"
        }, {
-               "full_name": "test_prog$Character$race"
+               "full_name": "test_prog$Character$_sex"
        }, {
-               "full_name": "test_prog$Character$race="
+               "full_name": "test_prog$Character$age"
        }, {
-               "full_name": "test_prog$Character$_career"
+               "full_name": "test_prog$Character$age="
        }, {
                "full_name": "test_prog$Character$career"
        }, {
                "full_name": "test_prog$Character$career="
        }, {
-               "full_name": "test_prog$Character$quit"
+               "full_name": "test_prog$Character$health"
        }, {
-               "full_name": "test_prog$Character$_name"
+               "full_name": "test_prog$Character$health="
+       }, {
+               "full_name": "test_prog$Character$Object::init"
+       }, {
+               "full_name": "test_prog$Character$max_health"
        }, {
                "full_name": "test_prog$Character$name"
        }, {
                "full_name": "test_prog$Character$name="
        }, {
-               "full_name": "test_prog$Character$_age"
-       }, {
-               "full_name": "test_prog$Character$age"
+               "full_name": "test_prog$Character$quit"
        }, {
-               "full_name": "test_prog$Character$age="
+               "full_name": "test_prog$Character$race"
        }, {
-               "full_name": "test_prog$Character$_sex"
+               "full_name": "test_prog$Character$race="
        }, {
                "full_name": "test_prog$Character$sex"
        }, {
                "full_name": "test_prog$Character$sex="
        }, {
-               "full_name": "test_prog$Character$total_strengh"
-       }, {
                "full_name": "test_prog$Character$total_endurance"
        }, {
                "full_name": "test_prog$Character$total_intelligence"
        }, {
-               "full_name": "test_prog$Character$max_health"
+               "full_name": "test_prog$Character$total_strengh"
+       }],
+       "intro_mproperties": [{
+               "full_name": "test_prog::character::Character::_age"
        }, {
-               "full_name": "test_prog$Character$_health"
+               "full_name": "test_prog::character::Character::_career"
        }, {
-               "full_name": "test_prog$Character$health"
+               "full_name": "test_prog::character::Character::_health"
        }, {
-               "full_name": "test_prog$Character$health="
+               "full_name": "test_prog::character::Character::_name"
        }, {
-               "full_name": "test_prog$Character$Object::init"
-       }],
-       "intro_mproperties": [{
                "full_name": "test_prog::character::Character::_race"
        }, {
-               "full_name": "test_prog::Character::race"
+               "full_name": "test_prog::character::Character::_sex"
        }, {
-               "full_name": "test_prog::Character::race="
+               "full_name": "test_prog::Character::age"
        }, {
-               "full_name": "test_prog::character::Character::_career"
+               "full_name": "test_prog::Character::age="
        }, {
                "full_name": "test_prog::Character::career"
        }, {
                "full_name": "test_prog::Character::career="
        }, {
-               "full_name": "test_prog::Character::quit"
+               "full_name": "test_prog::Character::health"
        }, {
-               "full_name": "test_prog::character::Character::_name"
+               "full_name": "test_prog::Character::health="
+       }, {
+               "full_name": "test_prog::Character::max_health"
        }, {
                "full_name": "test_prog::Character::name"
        }, {
                "full_name": "test_prog::Character::name="
        }, {
-               "full_name": "test_prog::character::Character::_age"
-       }, {
-               "full_name": "test_prog::Character::age"
+               "full_name": "test_prog::Character::quit"
        }, {
-               "full_name": "test_prog::Character::age="
+               "full_name": "test_prog::Character::race"
        }, {
-               "full_name": "test_prog::character::Character::_sex"
+               "full_name": "test_prog::Character::race="
        }, {
                "full_name": "test_prog::Character::sex"
        }, {
                "full_name": "test_prog::Character::sex="
        }, {
-               "full_name": "test_prog::Character::total_strengh"
-       }, {
                "full_name": "test_prog::Character::total_endurance"
        }, {
                "full_name": "test_prog::Character::total_intelligence"
        }, {
-               "full_name": "test_prog::Character::max_health"
-       }, {
-               "full_name": "test_prog::character::Character::_health"
-       }, {
-               "full_name": "test_prog::Character::health"
-       }, {
-               "full_name": "test_prog::Character::health="
+               "full_name": "test_prog::Character::total_strengh"
        }],
        "intro": {
                "full_name": "test_prog$Character"
                "full_name": "test_prog"
        },
        "intro_mpropdefs": [{
+               "full_name": "test_prog$Character$_age"
+       }, {
+               "full_name": "test_prog$Character$_career"
+       }, {
+               "full_name": "test_prog$Character$_health"
+       }, {
+               "full_name": "test_prog$Character$_name"
+       }, {
                "full_name": "test_prog$Character$_race"
        }, {
-               "full_name": "test_prog$Character$race"
+               "full_name": "test_prog$Character$_sex"
        }, {
-               "full_name": "test_prog$Character$race="
+               "full_name": "test_prog$Character$age"
        }, {
-               "full_name": "test_prog$Character$_career"
+               "full_name": "test_prog$Character$age="
        }, {
                "full_name": "test_prog$Character$career"
        }, {
                "full_name": "test_prog$Character$career="
        }, {
-               "full_name": "test_prog$Character$quit"
+               "full_name": "test_prog$Character$health"
        }, {
-               "full_name": "test_prog$Character$_name"
+               "full_name": "test_prog$Character$health="
+       }, {
+               "full_name": "test_prog$Character$max_health"
        }, {
                "full_name": "test_prog$Character$name"
        }, {
                "full_name": "test_prog$Character$name="
        }, {
-               "full_name": "test_prog$Character$_age"
-       }, {
-               "full_name": "test_prog$Character$age"
+               "full_name": "test_prog$Character$quit"
        }, {
-               "full_name": "test_prog$Character$age="
+               "full_name": "test_prog$Character$race"
        }, {
-               "full_name": "test_prog$Character$_sex"
+               "full_name": "test_prog$Character$race="
        }, {
                "full_name": "test_prog$Character$sex"
        }, {
                "full_name": "test_prog$Character$sex="
        }, {
-               "full_name": "test_prog$Character$total_strengh"
-       }, {
                "full_name": "test_prog$Character$total_endurance"
        }, {
                "full_name": "test_prog$Character$total_intelligence"
        }, {
-               "full_name": "test_prog$Character$max_health"
-       }, {
-               "full_name": "test_prog$Character$_health"
-       }, {
-               "full_name": "test_prog$Character$health"
-       }, {
-               "full_name": "test_prog$Character$health="
+               "full_name": "test_prog$Character$total_strengh"
        }],
        "redef_mpropdefs": [{
                "full_name": "test_prog$Character$Object::init"
                "full_name": "test_prog::Combatable"
        },
        "mpropdefs": [{
-               "full_name": "test_prog$Combatable$hit_points"
-       }, {
                "full_name": "test_prog$Combatable$attack"
        }, {
+               "full_name": "test_prog$Combatable$defend"
+       }, {
                "full_name": "test_prog$Combatable$direct_attack"
        }, {
-               "full_name": "test_prog$Combatable$defend"
+               "full_name": "test_prog$Combatable$hit_points"
        }, {
                "full_name": "test_prog$Combatable$is_dead"
        }],
        "intro_mproperties": [{
-               "full_name": "test_prog::Combatable::hit_points"
-       }, {
                "full_name": "test_prog::Combatable::attack"
        }, {
+               "full_name": "test_prog::Combatable::defend"
+       }, {
                "full_name": "test_prog::Combatable::direct_attack"
        }, {
-               "full_name": "test_prog::Combatable::defend"
+               "full_name": "test_prog::Combatable::hit_points"
        }, {
                "full_name": "test_prog::Combatable::is_dead"
        }],
                "full_name": "test_prog"
        },
        "intro_mpropdefs": [{
-               "full_name": "test_prog$Combatable$hit_points"
-       }, {
                "full_name": "test_prog$Combatable$attack"
        }, {
+               "full_name": "test_prog$Combatable$defend"
+       }, {
                "full_name": "test_prog$Combatable$direct_attack"
        }, {
-               "full_name": "test_prog$Combatable$defend"
+               "full_name": "test_prog$Combatable$hit_points"
        }, {
                "full_name": "test_prog$Combatable$is_dead"
        }],
                "full_name": "test_prog::Game"
        },
        "mpropdefs": [{
-               "full_name": "test_prog$Game$player_characters"
-       }, {
                "full_name": "test_prog$Game$computer_characters"
        }, {
-               "full_name": "test_prog$Game$start_game"
-       }, {
                "full_name": "test_prog$Game$pause_game"
        }, {
+               "full_name": "test_prog$Game$player_characters"
+       }, {
+               "full_name": "test_prog$Game$start_game"
+       }, {
                "full_name": "test_prog$Game$stop_game"
        }],
        "intro_mproperties": [{
-               "full_name": "test_prog::Game::player_characters"
-       }, {
                "full_name": "test_prog::Game::computer_characters"
        }, {
-               "full_name": "test_prog::Game::start_game"
-       }, {
                "full_name": "test_prog::Game::pause_game"
        }, {
+               "full_name": "test_prog::Game::player_characters"
+       }, {
+               "full_name": "test_prog::Game::start_game"
+       }, {
                "full_name": "test_prog::Game::stop_game"
        }],
        "intro": {
                "full_name": "test_prog"
        },
        "intro_mpropdefs": [{
-               "full_name": "test_prog$Game$player_characters"
-       }, {
                "full_name": "test_prog$Game$computer_characters"
        }, {
-               "full_name": "test_prog$Game$start_game"
-       }, {
                "full_name": "test_prog$Game$pause_game"
        }, {
+               "full_name": "test_prog$Game$player_characters"
+       }, {
+               "full_name": "test_prog$Game$start_game"
+       }, {
                "full_name": "test_prog$Game$stop_game"
        }],
        "redef_mpropdefs": []
index 2fc7104..d5eb364 100644 (file)
                "full_name": "test_prog$Object"
        }],
        "all_mproperties": [{
-               "full_name": "test_prog::Object::OTHER"
+               "full_name": "test_prog::Object::!="
        }, {
                "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Object::OTHER"
        }, {
                "full_name": "test_prog::Object::init"
        }],
        "intro_mproperties": [{
-               "full_name": "test_prog::Object::OTHER"
+               "full_name": "test_prog::Object::!="
        }, {
                "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Object::OTHER"
        }, {
                "full_name": "test_prog::Object::init"
        }],
                "full_name": "test_prog$Int"
        }],
        "all_mproperties": [{
-               "full_name": "test_prog::Int::unary -"
+               "full_name": "test_prog::Object::!="
+       }, {
+               "full_name": "test_prog::Int::*"
        }, {
                "full_name": "test_prog::Int::+"
        }, {
                "full_name": "test_prog::Int::-"
        }, {
-               "full_name": "test_prog::Int::*"
-       }, {
                "full_name": "test_prog::Int::/"
        }, {
-               "full_name": "test_prog::Int::>"
+               "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::Int::to_f"
+               "full_name": "test_prog::Int::>"
        }, {
                "full_name": "test_prog::Object::OTHER"
        }, {
-               "full_name": "test_prog::Object::=="
+               "full_name": "test_prog::Object::init"
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Int::to_f"
        }, {
-               "full_name": "test_prog::Object::init"
+               "full_name": "test_prog::Int::unary -"
        }],
        "intro_mproperties": [{
-               "full_name": "test_prog::Int::unary -"
+               "full_name": "test_prog::Int::*"
        }, {
                "full_name": "test_prog::Int::+"
        }, {
                "full_name": "test_prog::Int::-"
        }, {
-               "full_name": "test_prog::Int::*"
-       }, {
                "full_name": "test_prog::Int::/"
        }, {
                "full_name": "test_prog::Int::>"
        }, {
                "full_name": "test_prog::Int::to_f"
+       }, {
+               "full_name": "test_prog::Int::unary -"
        }],
        "redef_mproperties": [],
        "parents": [{
                "full_name": "test_prog$Float"
        }],
        "all_mproperties": [{
+               "full_name": "test_prog::Object::!="
+       }, {
+               "full_name": "test_prog::Float::*"
+       }, {
                "full_name": "test_prog::Float::+"
        }, {
                "full_name": "test_prog::Float::-"
        }, {
-               "full_name": "test_prog::Float::*"
-       }, {
                "full_name": "test_prog::Float::/"
        }, {
+               "full_name": "test_prog::Object::=="
+       }, {
                "full_name": "test_prog::Float::>"
        }, {
                "full_name": "test_prog::Object::OTHER"
        }, {
-               "full_name": "test_prog::Object::=="
-       }, {
-               "full_name": "test_prog::Object::!="
-       }, {
                "full_name": "test_prog::Object::init"
        }],
        "intro_mproperties": [{
+               "full_name": "test_prog::Float::*"
+       }, {
                "full_name": "test_prog::Float::+"
        }, {
                "full_name": "test_prog::Float::-"
        }, {
-               "full_name": "test_prog::Float::*"
-       }, {
                "full_name": "test_prog::Float::/"
        }, {
                "full_name": "test_prog::Float::>"
                "full_name": "test_prog$Bool"
        }],
        "all_mproperties": [{
-               "full_name": "test_prog::Object::OTHER"
+               "full_name": "test_prog::Object::!="
        }, {
                "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Object::OTHER"
        }, {
                "full_name": "test_prog::Object::init"
        }],
                "full_name": "test_prog$String"
        }],
        "all_mproperties": [{
-               "full_name": "test_prog::Object::OTHER"
+               "full_name": "test_prog::Object::!="
        }, {
                "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Object::OTHER"
        }, {
                "full_name": "test_prog::Object::init"
        }],
                "full_name": "test_prog$List"
        }],
        "all_mproperties": [{
-               "full_name": "test_prog::Object::OTHER"
+               "full_name": "test_prog::Object::!="
        }, {
                "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Object::OTHER"
        }, {
                "full_name": "test_prog::Object::init"
        }],
                "full_name": "test_prog$Career"
        }],
        "all_mproperties": [{
-               "full_name": "test_prog::careers::Career::_strength_bonus"
+               "full_name": "test_prog::Object::!="
        }, {
-               "full_name": "test_prog::Career::strength_bonus"
+               "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::Career::strength_bonus="
+               "full_name": "test_prog::Object::OTHER"
        }, {
                "full_name": "test_prog::careers::Career::_endurance_bonus"
        }, {
+               "full_name": "test_prog::careers::Career::_intelligence_bonus"
+       }, {
+               "full_name": "test_prog::careers::Career::_strength_bonus"
+       }, {
                "full_name": "test_prog::Career::endurance_bonus"
        }, {
                "full_name": "test_prog::Career::endurance_bonus="
        }, {
-               "full_name": "test_prog::careers::Career::_intelligence_bonus"
+               "full_name": "test_prog::Object::init"
        }, {
                "full_name": "test_prog::Career::intelligence_bonus"
        }, {
                "full_name": "test_prog::Career::intelligence_bonus="
        }, {
-               "full_name": "test_prog::Object::init"
-       }, {
-               "full_name": "test_prog::Object::OTHER"
-       }, {
-               "full_name": "test_prog::Object::=="
+               "full_name": "test_prog::Career::strength_bonus"
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Career::strength_bonus="
        }],
        "intro_mproperties": [{
-               "full_name": "test_prog::careers::Career::_strength_bonus"
-       }, {
-               "full_name": "test_prog::Career::strength_bonus"
+               "full_name": "test_prog::careers::Career::_endurance_bonus"
        }, {
-               "full_name": "test_prog::Career::strength_bonus="
+               "full_name": "test_prog::careers::Career::_intelligence_bonus"
        }, {
-               "full_name": "test_prog::careers::Career::_endurance_bonus"
+               "full_name": "test_prog::careers::Career::_strength_bonus"
        }, {
                "full_name": "test_prog::Career::endurance_bonus"
        }, {
                "full_name": "test_prog::Career::endurance_bonus="
        }, {
-               "full_name": "test_prog::careers::Career::_intelligence_bonus"
-       }, {
                "full_name": "test_prog::Career::intelligence_bonus"
        }, {
                "full_name": "test_prog::Career::intelligence_bonus="
+       }, {
+               "full_name": "test_prog::Career::strength_bonus"
+       }, {
+               "full_name": "test_prog::Career::strength_bonus="
        }],
        "redef_mproperties": [{
                "full_name": "test_prog::Object::init"
                "full_name": "test_prog$Warrior"
        }],
        "all_mproperties": [{
-               "full_name": "test_prog::Object::init"
-       }, {
-               "full_name": "test_prog::careers::Career::_strength_bonus"
+               "full_name": "test_prog::Object::!="
        }, {
-               "full_name": "test_prog::Career::strength_bonus"
+               "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::Career::strength_bonus="
+               "full_name": "test_prog::Object::OTHER"
        }, {
                "full_name": "test_prog::careers::Career::_endurance_bonus"
        }, {
+               "full_name": "test_prog::careers::Career::_intelligence_bonus"
+       }, {
+               "full_name": "test_prog::careers::Career::_strength_bonus"
+       }, {
                "full_name": "test_prog::Career::endurance_bonus"
        }, {
                "full_name": "test_prog::Career::endurance_bonus="
        }, {
-               "full_name": "test_prog::careers::Career::_intelligence_bonus"
+               "full_name": "test_prog::Object::init"
        }, {
                "full_name": "test_prog::Career::intelligence_bonus"
        }, {
                "full_name": "test_prog::Career::intelligence_bonus="
        }, {
-               "full_name": "test_prog::Object::OTHER"
-       }, {
-               "full_name": "test_prog::Object::=="
+               "full_name": "test_prog::Career::strength_bonus"
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Career::strength_bonus="
        }],
        "intro_mproperties": [],
        "redef_mproperties": [{
                "full_name": "test_prog$Magician"
        }],
        "all_mproperties": [{
-               "full_name": "test_prog::Object::init"
-       }, {
-               "full_name": "test_prog::careers::Career::_strength_bonus"
+               "full_name": "test_prog::Object::!="
        }, {
-               "full_name": "test_prog::Career::strength_bonus"
+               "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::Career::strength_bonus="
+               "full_name": "test_prog::Object::OTHER"
        }, {
                "full_name": "test_prog::careers::Career::_endurance_bonus"
        }, {
+               "full_name": "test_prog::careers::Career::_intelligence_bonus"
+       }, {
+               "full_name": "test_prog::careers::Career::_strength_bonus"
+       }, {
                "full_name": "test_prog::Career::endurance_bonus"
        }, {
                "full_name": "test_prog::Career::endurance_bonus="
        }, {
-               "full_name": "test_prog::careers::Career::_intelligence_bonus"
+               "full_name": "test_prog::Object::init"
        }, {
                "full_name": "test_prog::Career::intelligence_bonus"
        }, {
                "full_name": "test_prog::Career::intelligence_bonus="
        }, {
-               "full_name": "test_prog::Object::OTHER"
-       }, {
-               "full_name": "test_prog::Object::=="
+               "full_name": "test_prog::Career::strength_bonus"
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Career::strength_bonus="
        }],
        "intro_mproperties": [],
        "redef_mproperties": [{
                "full_name": "test_prog$Alcoholic"
        }],
        "all_mproperties": [{
-               "full_name": "test_prog::Object::init"
-       }, {
-               "full_name": "test_prog::careers::Career::_strength_bonus"
+               "full_name": "test_prog::Object::!="
        }, {
-               "full_name": "test_prog::Career::strength_bonus"
+               "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::Career::strength_bonus="
+               "full_name": "test_prog::Object::OTHER"
        }, {
                "full_name": "test_prog::careers::Career::_endurance_bonus"
        }, {
+               "full_name": "test_prog::careers::Career::_intelligence_bonus"
+       }, {
+               "full_name": "test_prog::careers::Career::_strength_bonus"
+       }, {
                "full_name": "test_prog::Career::endurance_bonus"
        }, {
                "full_name": "test_prog::Career::endurance_bonus="
        }, {
-               "full_name": "test_prog::careers::Career::_intelligence_bonus"
+               "full_name": "test_prog::Object::init"
        }, {
                "full_name": "test_prog::Career::intelligence_bonus"
        }, {
                "full_name": "test_prog::Career::intelligence_bonus="
        }, {
-               "full_name": "test_prog::Object::OTHER"
-       }, {
-               "full_name": "test_prog::Object::=="
+               "full_name": "test_prog::Career::strength_bonus"
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Career::strength_bonus="
        }],
        "intro_mproperties": [],
        "redef_mproperties": [{
                "full_name": "test_prog$Race"
        }],
        "all_mproperties": [{
-               "full_name": "test_prog::races::Race::_base_strength"
+               "full_name": "test_prog::Object::!="
        }, {
-               "full_name": "test_prog::Race::base_strength"
+               "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::Race::base_strength="
+               "full_name": "test_prog::Object::OTHER"
        }, {
                "full_name": "test_prog::races::Race::_base_endurance"
        }, {
+               "full_name": "test_prog::races::Race::_base_intelligence"
+       }, {
+               "full_name": "test_prog::races::Race::_base_strength"
+       }, {
                "full_name": "test_prog::Race::base_endurance"
        }, {
                "full_name": "test_prog::Race::base_endurance="
        }, {
-               "full_name": "test_prog::races::Race::_base_intelligence"
-       }, {
                "full_name": "test_prog::Race::base_intelligence"
        }, {
                "full_name": "test_prog::Race::base_intelligence="
        }, {
-               "full_name": "test_prog::Object::init"
-       }, {
-               "full_name": "test_prog::Object::OTHER"
+               "full_name": "test_prog::Race::base_strength"
        }, {
-               "full_name": "test_prog::Object::=="
+               "full_name": "test_prog::Race::base_strength="
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Object::init"
        }],
        "intro_mproperties": [{
-               "full_name": "test_prog::races::Race::_base_strength"
-       }, {
-               "full_name": "test_prog::Race::base_strength"
+               "full_name": "test_prog::races::Race::_base_endurance"
        }, {
-               "full_name": "test_prog::Race::base_strength="
+               "full_name": "test_prog::races::Race::_base_intelligence"
        }, {
-               "full_name": "test_prog::races::Race::_base_endurance"
+               "full_name": "test_prog::races::Race::_base_strength"
        }, {
                "full_name": "test_prog::Race::base_endurance"
        }, {
                "full_name": "test_prog::Race::base_endurance="
        }, {
-               "full_name": "test_prog::races::Race::_base_intelligence"
-       }, {
                "full_name": "test_prog::Race::base_intelligence"
        }, {
                "full_name": "test_prog::Race::base_intelligence="
+       }, {
+               "full_name": "test_prog::Race::base_strength"
+       }, {
+               "full_name": "test_prog::Race::base_strength="
        }],
        "redef_mproperties": [{
                "full_name": "test_prog::Object::init"
                "full_name": "test_prog$Human"
        }],
        "all_mproperties": [{
-               "full_name": "test_prog::Object::init"
-       }, {
-               "full_name": "test_prog::races::Race::_base_strength"
+               "full_name": "test_prog::Object::!="
        }, {
-               "full_name": "test_prog::Race::base_strength"
+               "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::Race::base_strength="
+               "full_name": "test_prog::Object::OTHER"
        }, {
                "full_name": "test_prog::races::Race::_base_endurance"
        }, {
+               "full_name": "test_prog::races::Race::_base_intelligence"
+       }, {
+               "full_name": "test_prog::races::Race::_base_strength"
+       }, {
                "full_name": "test_prog::Race::base_endurance"
        }, {
                "full_name": "test_prog::Race::base_endurance="
        }, {
-               "full_name": "test_prog::races::Race::_base_intelligence"
-       }, {
                "full_name": "test_prog::Race::base_intelligence"
        }, {
                "full_name": "test_prog::Race::base_intelligence="
        }, {
-               "full_name": "test_prog::Object::OTHER"
+               "full_name": "test_prog::Race::base_strength"
        }, {
-               "full_name": "test_prog::Object::=="
+               "full_name": "test_prog::Race::base_strength="
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Object::init"
        }],
        "intro_mproperties": [],
        "redef_mproperties": [{
                "full_name": "test_prog::combat$Dwarf"
        }],
        "all_mproperties": [{
-               "full_name": "test_prog::Object::init"
+               "full_name": "test_prog::Object::!="
        }, {
-               "full_name": "test_prog::Weapon::dps"
+               "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::races::Race::_base_strength"
+               "full_name": "test_prog::Object::OTHER"
        }, {
-               "full_name": "test_prog::Race::base_strength"
+               "full_name": "test_prog::races::Race::_base_endurance"
        }, {
-               "full_name": "test_prog::Race::base_strength="
+               "full_name": "test_prog::races::Race::_base_intelligence"
        }, {
-               "full_name": "test_prog::races::Race::_base_endurance"
+               "full_name": "test_prog::races::Race::_base_strength"
        }, {
                "full_name": "test_prog::Race::base_endurance"
        }, {
                "full_name": "test_prog::Race::base_endurance="
        }, {
-               "full_name": "test_prog::races::Race::_base_intelligence"
-       }, {
                "full_name": "test_prog::Race::base_intelligence"
        }, {
                "full_name": "test_prog::Race::base_intelligence="
        }, {
-               "full_name": "test_prog::Object::OTHER"
+               "full_name": "test_prog::Race::base_strength"
        }, {
-               "full_name": "test_prog::Object::=="
+               "full_name": "test_prog::Race::base_strength="
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Weapon::dps"
+       }, {
+               "full_name": "test_prog::Object::init"
        }],
        "intro_mproperties": [],
        "redef_mproperties": [{
-               "full_name": "test_prog::Object::init"
-       }, {
                "full_name": "test_prog::Weapon::dps"
+       }, {
+               "full_name": "test_prog::Object::init"
        }],
        "parents": [{
                "full_name": "test_prog::Race"
                "full_name": "test_prog$Elf"
        }],
        "all_mproperties": [{
-               "full_name": "test_prog::Object::init"
-       }, {
-               "full_name": "test_prog::races::Race::_base_strength"
+               "full_name": "test_prog::Object::!="
        }, {
-               "full_name": "test_prog::Race::base_strength"
+               "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::Race::base_strength="
+               "full_name": "test_prog::Object::OTHER"
        }, {
                "full_name": "test_prog::races::Race::_base_endurance"
        }, {
+               "full_name": "test_prog::races::Race::_base_intelligence"
+       }, {
+               "full_name": "test_prog::races::Race::_base_strength"
+       }, {
                "full_name": "test_prog::Race::base_endurance"
        }, {
                "full_name": "test_prog::Race::base_endurance="
        }, {
-               "full_name": "test_prog::races::Race::_base_intelligence"
-       }, {
                "full_name": "test_prog::Race::base_intelligence"
        }, {
                "full_name": "test_prog::Race::base_intelligence="
        }, {
-               "full_name": "test_prog::Object::OTHER"
+               "full_name": "test_prog::Race::base_strength"
        }, {
-               "full_name": "test_prog::Object::=="
+               "full_name": "test_prog::Race::base_strength="
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Object::init"
        }],
        "intro_mproperties": [],
        "redef_mproperties": [{
                "full_name": "test_prog::combat$Character"
        }],
        "all_mproperties": [{
-               "full_name": "test_prog::character::Character::_race"
-       }, {
-               "full_name": "test_prog::Character::race"
+               "full_name": "test_prog::Object::!="
        }, {
-               "full_name": "test_prog::Character::race="
+               "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::character::Character::_career"
+               "full_name": "test_prog::Object::OTHER"
        }, {
-               "full_name": "test_prog::Character::career"
+               "full_name": "test_prog::character::Character::_age"
        }, {
-               "full_name": "test_prog::Character::career="
+               "full_name": "test_prog::character::Character::_career"
        }, {
-               "full_name": "test_prog::Character::quit"
+               "full_name": "test_prog::character::Character::_health"
        }, {
                "full_name": "test_prog::character::Character::_name"
        }, {
-               "full_name": "test_prog::Character::name"
-       }, {
-               "full_name": "test_prog::Character::name="
+               "full_name": "test_prog::character::Character::_race"
        }, {
-               "full_name": "test_prog::character::Character::_age"
+               "full_name": "test_prog::character::Character::_sex"
        }, {
                "full_name": "test_prog::Character::age"
        }, {
                "full_name": "test_prog::Character::age="
        }, {
-               "full_name": "test_prog::character::Character::_sex"
-       }, {
-               "full_name": "test_prog::Character::sex"
-       }, {
-               "full_name": "test_prog::Character::sex="
-       }, {
-               "full_name": "test_prog::Character::total_strengh"
+               "full_name": "test_prog::Combatable::attack"
        }, {
-               "full_name": "test_prog::Character::total_endurance"
+               "full_name": "test_prog::Character::career"
        }, {
-               "full_name": "test_prog::Character::total_intelligence"
+               "full_name": "test_prog::Character::career="
        }, {
-               "full_name": "test_prog::Character::max_health"
+               "full_name": "test_prog::Combatable::defend"
        }, {
-               "full_name": "test_prog::character::Character::_health"
+               "full_name": "test_prog::Combatable::direct_attack"
        }, {
                "full_name": "test_prog::Character::health"
        }, {
                "full_name": "test_prog::Character::health="
        }, {
+               "full_name": "test_prog::Combatable::hit_points"
+       }, {
                "full_name": "test_prog::Object::init"
        }, {
-               "full_name": "test_prog::Combatable::hit_points"
+               "full_name": "test_prog::Combatable::is_dead"
        }, {
-               "full_name": "test_prog::Object::OTHER"
+               "full_name": "test_prog::Character::max_health"
        }, {
-               "full_name": "test_prog::Object::=="
+               "full_name": "test_prog::Character::name"
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Character::name="
        }, {
-               "full_name": "test_prog::Combatable::attack"
+               "full_name": "test_prog::Character::quit"
        }, {
-               "full_name": "test_prog::Combatable::direct_attack"
+               "full_name": "test_prog::Character::race"
        }, {
-               "full_name": "test_prog::Combatable::defend"
+               "full_name": "test_prog::Character::race="
        }, {
-               "full_name": "test_prog::Combatable::is_dead"
+               "full_name": "test_prog::Character::sex"
+       }, {
+               "full_name": "test_prog::Character::sex="
+       }, {
+               "full_name": "test_prog::Character::total_endurance"
+       }, {
+               "full_name": "test_prog::Character::total_intelligence"
+       }, {
+               "full_name": "test_prog::Character::total_strengh"
        }],
        "intro_mproperties": [{
+               "full_name": "test_prog::character::Character::_age"
+       }, {
+               "full_name": "test_prog::character::Character::_career"
+       }, {
+               "full_name": "test_prog::character::Character::_health"
+       }, {
+               "full_name": "test_prog::character::Character::_name"
+       }, {
                "full_name": "test_prog::character::Character::_race"
        }, {
-               "full_name": "test_prog::Character::race"
+               "full_name": "test_prog::character::Character::_sex"
        }, {
-               "full_name": "test_prog::Character::race="
+               "full_name": "test_prog::Character::age"
        }, {
-               "full_name": "test_prog::character::Character::_career"
+               "full_name": "test_prog::Character::age="
        }, {
                "full_name": "test_prog::Character::career"
        }, {
                "full_name": "test_prog::Character::career="
        }, {
-               "full_name": "test_prog::Character::quit"
+               "full_name": "test_prog::Character::health"
        }, {
-               "full_name": "test_prog::character::Character::_name"
+               "full_name": "test_prog::Character::health="
+       }, {
+               "full_name": "test_prog::Character::max_health"
        }, {
                "full_name": "test_prog::Character::name"
        }, {
                "full_name": "test_prog::Character::name="
        }, {
-               "full_name": "test_prog::character::Character::_age"
-       }, {
-               "full_name": "test_prog::Character::age"
+               "full_name": "test_prog::Character::quit"
        }, {
-               "full_name": "test_prog::Character::age="
+               "full_name": "test_prog::Character::race"
        }, {
-               "full_name": "test_prog::character::Character::_sex"
+               "full_name": "test_prog::Character::race="
        }, {
                "full_name": "test_prog::Character::sex"
        }, {
                "full_name": "test_prog::Character::sex="
        }, {
-               "full_name": "test_prog::Character::total_strengh"
-       }, {
                "full_name": "test_prog::Character::total_endurance"
        }, {
                "full_name": "test_prog::Character::total_intelligence"
        }, {
-               "full_name": "test_prog::Character::max_health"
-       }, {
-               "full_name": "test_prog::character::Character::_health"
-       }, {
-               "full_name": "test_prog::Character::health"
-       }, {
-               "full_name": "test_prog::Character::health="
+               "full_name": "test_prog::Character::total_strengh"
        }],
        "redef_mproperties": [{
-               "full_name": "test_prog::Object::init"
-       }, {
                "full_name": "test_prog::Combatable::hit_points"
+       }, {
+               "full_name": "test_prog::Object::init"
        }],
        "parents": [{
-               "full_name": "test_prog::Object"
-       }, {
                "full_name": "test_prog::Combatable"
+       }, {
+               "full_name": "test_prog::Object"
        }]
 }
 {
                "full_name": "test_prog$Weapon"
        }],
        "all_mproperties": [{
-               "full_name": "test_prog::Weapon::dps"
-       }, {
-               "full_name": "test_prog::Object::OTHER"
+               "full_name": "test_prog::Object::!="
        }, {
                "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Object::OTHER"
+       }, {
+               "full_name": "test_prog::Weapon::dps"
        }, {
                "full_name": "test_prog::Object::init"
        }],
                "full_name": "test_prog$Combatable"
        }],
        "all_mproperties": [{
-               "full_name": "test_prog::Combatable::hit_points"
-       }, {
-               "full_name": "test_prog::Combatable::attack"
+               "full_name": "test_prog::Object::!="
        }, {
-               "full_name": "test_prog::Combatable::direct_attack"
+               "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::Combatable::defend"
+               "full_name": "test_prog::Object::OTHER"
        }, {
-               "full_name": "test_prog::Combatable::is_dead"
+               "full_name": "test_prog::Combatable::attack"
        }, {
-               "full_name": "test_prog::Object::OTHER"
+               "full_name": "test_prog::Combatable::defend"
        }, {
-               "full_name": "test_prog::Object::=="
+               "full_name": "test_prog::Combatable::direct_attack"
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Combatable::hit_points"
        }, {
                "full_name": "test_prog::Object::init"
+       }, {
+               "full_name": "test_prog::Combatable::is_dead"
        }],
        "intro_mproperties": [{
-               "full_name": "test_prog::Combatable::hit_points"
-       }, {
                "full_name": "test_prog::Combatable::attack"
        }, {
+               "full_name": "test_prog::Combatable::defend"
+       }, {
                "full_name": "test_prog::Combatable::direct_attack"
        }, {
-               "full_name": "test_prog::Combatable::defend"
+               "full_name": "test_prog::Combatable::hit_points"
        }, {
                "full_name": "test_prog::Combatable::is_dead"
        }],
                "full_name": "test_prog$Game"
        }],
        "all_mproperties": [{
-               "full_name": "test_prog::Game::player_characters"
+               "full_name": "test_prog::Object::!="
        }, {
-               "full_name": "test_prog::Game::computer_characters"
+               "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::Game::start_game"
+               "full_name": "test_prog::Object::OTHER"
        }, {
-               "full_name": "test_prog::Game::pause_game"
+               "full_name": "test_prog::Game::computer_characters"
        }, {
-               "full_name": "test_prog::Game::stop_game"
+               "full_name": "test_prog::Object::init"
        }, {
-               "full_name": "test_prog::Object::OTHER"
+               "full_name": "test_prog::Game::pause_game"
        }, {
-               "full_name": "test_prog::Object::=="
+               "full_name": "test_prog::Game::player_characters"
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Game::start_game"
        }, {
-               "full_name": "test_prog::Object::init"
+               "full_name": "test_prog::Game::stop_game"
        }],
        "intro_mproperties": [{
-               "full_name": "test_prog::Game::player_characters"
-       }, {
                "full_name": "test_prog::Game::computer_characters"
        }, {
-               "full_name": "test_prog::Game::start_game"
-       }, {
                "full_name": "test_prog::Game::pause_game"
        }, {
+               "full_name": "test_prog::Game::player_characters"
+       }, {
+               "full_name": "test_prog::Game::start_game"
+       }, {
                "full_name": "test_prog::Game::stop_game"
        }],
        "redef_mproperties": [],
                "full_name": "test_prog$Starter"
        }],
        "all_mproperties": [{
-               "full_name": "test_prog::Starter::start"
-       }, {
-               "full_name": "test_prog::Object::OTHER"
+               "full_name": "test_prog::Object::!="
        }, {
                "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Object::OTHER"
        }, {
                "full_name": "test_prog::Object::init"
+       }, {
+               "full_name": "test_prog::Starter::start"
        }],
        "intro_mproperties": [{
                "full_name": "test_prog::Starter::start"
                "full_name": "test_prog$Sys"
        }],
        "all_mproperties": [{
-               "full_name": "test_prog::Sys::main"
-       }, {
-               "full_name": "test_prog::Object::OTHER"
+               "full_name": "test_prog::Object::!="
        }, {
                "full_name": "test_prog::Object::=="
        }, {
-               "full_name": "test_prog::Object::!="
+               "full_name": "test_prog::Object::OTHER"
        }, {
                "full_name": "test_prog::Object::init"
+       }, {
+               "full_name": "test_prog::Sys::main"
        }],
        "intro_mproperties": [{
                "full_name": "test_prog::Sys::main"
index 92690d1..e0c653a 100644 (file)
                "full_name": "test_prog>platform>"
        },
        "intro_mclasses": [{
-               "full_name": "test_prog::Object"
+               "full_name": "test_prog::Bool"
+       }, {
+               "full_name": "test_prog::Float"
        }, {
                "full_name": "test_prog::Int"
        }, {
-               "full_name": "test_prog::Float"
+               "full_name": "test_prog::List"
        }, {
-               "full_name": "test_prog::Bool"
+               "full_name": "test_prog::Object"
        }, {
                "full_name": "test_prog::String"
-       }, {
-               "full_name": "test_prog::List"
        }],
        "mclassdefs": [{
-               "full_name": "test_prog$Object"
+               "full_name": "test_prog$Bool"
+       }, {
+               "full_name": "test_prog$Float"
        }, {
                "full_name": "test_prog$Int"
        }, {
-               "full_name": "test_prog$Float"
+               "full_name": "test_prog$List"
        }, {
-               "full_name": "test_prog$Bool"
+               "full_name": "test_prog$Object"
        }, {
                "full_name": "test_prog$String"
-       }, {
-               "full_name": "test_prog$List"
        }],
        "intro_mclassdefs": [{
-               "full_name": "test_prog$Object"
+               "full_name": "test_prog$Bool"
+       }, {
+               "full_name": "test_prog$Float"
        }, {
                "full_name": "test_prog$Int"
        }, {
-               "full_name": "test_prog$Float"
+               "full_name": "test_prog$List"
        }, {
-               "full_name": "test_prog$Bool"
+               "full_name": "test_prog$Object"
        }, {
                "full_name": "test_prog$String"
-       }, {
-               "full_name": "test_prog$List"
        }],
        "redef_mclassdefs": [],
        "imports": []
                "full_name": "test_prog>rpg>"
        },
        "intro_mclasses": [{
-               "full_name": "test_prog::Career"
+               "full_name": "test_prog::Alcoholic"
        }, {
-               "full_name": "test_prog::Warrior"
+               "full_name": "test_prog::Career"
        }, {
                "full_name": "test_prog::Magician"
        }, {
-               "full_name": "test_prog::Alcoholic"
+               "full_name": "test_prog::Warrior"
        }],
        "mclassdefs": [{
-               "full_name": "test_prog$Career"
+               "full_name": "test_prog$Alcoholic"
        }, {
-               "full_name": "test_prog$Warrior"
+               "full_name": "test_prog$Career"
        }, {
                "full_name": "test_prog$Magician"
        }, {
-               "full_name": "test_prog$Alcoholic"
+               "full_name": "test_prog$Warrior"
        }],
        "intro_mclassdefs": [{
-               "full_name": "test_prog$Career"
+               "full_name": "test_prog$Alcoholic"
        }, {
-               "full_name": "test_prog$Warrior"
+               "full_name": "test_prog$Career"
        }, {
                "full_name": "test_prog$Magician"
        }, {
-               "full_name": "test_prog$Alcoholic"
+               "full_name": "test_prog$Warrior"
        }],
        "redef_mclassdefs": [],
        "imports": [{
                "full_name": "test_prog>rpg>"
        },
        "intro_mclasses": [{
-               "full_name": "test_prog::Weapon"
-       }, {
                "full_name": "test_prog::Combatable"
+       }, {
+               "full_name": "test_prog::Weapon"
        }],
        "mclassdefs": [{
-               "full_name": "test_prog$Weapon"
+               "full_name": "test_prog::combat$Character"
        }, {
                "full_name": "test_prog$Combatable"
        }, {
-               "full_name": "test_prog::combat$Character"
-       }, {
                "full_name": "test_prog::combat$Dwarf"
+       }, {
+               "full_name": "test_prog$Weapon"
        }],
        "intro_mclassdefs": [{
-               "full_name": "test_prog$Weapon"
-       }, {
                "full_name": "test_prog$Combatable"
+       }, {
+               "full_name": "test_prog$Weapon"
        }],
        "redef_mclassdefs": [{
                "full_name": "test_prog::combat$Character"
                "full_name": "test_prog>rpg>"
        },
        "intro_mclasses": [{
-               "full_name": "test_prog::Race"
-       }, {
-               "full_name": "test_prog::Human"
-       }, {
                "full_name": "test_prog::Dwarf"
        }, {
                "full_name": "test_prog::Elf"
-       }],
-       "mclassdefs": [{
-               "full_name": "test_prog$Race"
        }, {
-               "full_name": "test_prog$Human"
+               "full_name": "test_prog::Human"
        }, {
+               "full_name": "test_prog::Race"
+       }],
+       "mclassdefs": [{
                "full_name": "test_prog$Dwarf"
        }, {
                "full_name": "test_prog$Elf"
-       }],
-       "intro_mclassdefs": [{
-               "full_name": "test_prog$Race"
        }, {
                "full_name": "test_prog$Human"
        }, {
+               "full_name": "test_prog$Race"
+       }],
+       "intro_mclassdefs": [{
                "full_name": "test_prog$Dwarf"
        }, {
                "full_name": "test_prog$Elf"
+       }, {
+               "full_name": "test_prog$Human"
+       }, {
+               "full_name": "test_prog$Race"
        }],
        "redef_mclassdefs": [],
        "imports": [{
index 81a0cae..ef7ced3 100644 (file)
                "full_name": "test_prog>"
        },
        "mgroups": [{
-               "full_name": "test_prog>"
-       }, {
                "full_name": "test_prog>game>"
        }, {
                "full_name": "test_prog>platform>"
        }, {
                "full_name": "test_prog>rpg>"
+       }, {
+               "full_name": "test_prog>"
        }],
        "ini": {
                "upstream.issues": "https://github.com/nitlang/nit/issues",
index d61dd81..f3c8146 100644 (file)
                "full_name": "test_prog$Object"
        },
        "mpropdefs": [{
-               "full_name": "test_prog$Object$init"
+               "full_name": "test_prog$Elf$Object::init"
        }, {
                "full_name": "test_prog$Career$Object::init"
        }, {
        }, {
                "full_name": "test_prog$Dwarf$Object::init"
        }, {
-               "full_name": "test_prog$Elf$Object::init"
-       }, {
                "full_name": "test_prog$Character$Object::init"
+       }, {
+               "full_name": "test_prog$Object$init"
        }],
        "intro_mclass": {
                "full_name": "test_prog::Object"
index 419f257..95b1671 100644 (file)
@@ -15,7 +15,6 @@
 # Runs a webserver based on nitcorn that render things from model.
 module nitweb
 
-import popcorn::pop_config
 import popcorn::pop_auth
 import frontend
 import web
@@ -27,15 +26,13 @@ redef class NitwebConfig
        #
        # * key: `github.client_id`
        # * default: ``
-       var github_client_id: String is lazy do return value_or_default("github.client.id", "")
+       fun github_client_id: String do return ini["github.client.id"] or else ""
 
        # Github client secret used for Github OAuth login.
        #
        # * key: `github.client_secret`
        # * default: ``
-       var github_client_secret: String is lazy do
-               return value_or_default("github.client.secret", "")
-       end
+       fun github_client_secret: String do return ini["github.client.secret"] or else ""
 end
 
 redef class ToolContext
@@ -64,17 +61,17 @@ private class NitwebPhase
 
        # Build the nitweb config from `toolcontext` options.
        fun build_config(toolcontext: ToolContext, mainmodule: MModule): NitwebConfig do
-               var config_file = toolcontext.opt_config.value
-               if config_file == null then config_file = "nitweb.ini"
                var config = new NitwebConfig(
-                       config_file,
                        toolcontext.modelbuilder.model,
                        mainmodule,
                        toolcontext.modelbuilder)
+               var config_file = toolcontext.opt_config.value
+               if config_file == null then config.default_config_file = "nitweb.ini"
+               config.parse_options(args)
                var opt_host = toolcontext.opt_host.value
-               if opt_host != null then config["app.host"] = opt_host
+               if opt_host != null then config.ini["app.host"] = opt_host
                var opt_port = toolcontext.opt_port.value
-               if opt_port >= 0 then config["app.port"] = opt_port.to_s
+               if opt_port >= 0 then config.ini["app.port"] = opt_port.to_s
                return config
        end
 
index a5cacff..6a356c3 100644 (file)
 module api_feedback
 
 import web_base
+import popcorn::pop_auth
 
 redef class NitwebConfig
 
        # MongoDB collection used to store stars.
-       var stars: MongoCollection is lazy do return db.collection("stars")
+       var stars = new StarRatingRepo(db.collection("stars")) is lazy
 end
 
 redef class APIRouter
+
        redef init do
                super
+
+               use("/feedback/grades/most", new APIStarsMost(config))
+               use("/feedback/grades/best", new APIStarsBest(config))
+               use("/feedback/grades/worst", new APIStarsWorst(config))
+               use("/feedback/grades/users", new APIStarsUsers(config))
+
+               use("/feedback/user/stars", new APIUserStars(config))
+
                use("/feedback/stars/:id", new APIStars(config))
+               use("/feedback/stars/:id/dimension/:dimension", new APIStarsDimension(config))
+       end
+end
+
+# Base handler for feedback features.
+abstract class APIFeedBack
+       super APIHandler
+
+       # Get the user logged in or null if no session
+       fun get_session_user(req: HttpRequest): nullable User do
+               var session = req.session
+               if session == null then return null
+               return session.user
+       end
+
+       # Get the login of the session user or null if no session
+       fun get_session_login(req: HttpRequest): nullable String do
+               var user = get_session_user(req)
+               if user == null then return null
+               return user.login
+       end
+end
+
+# Most rated entities
+class APIStarsMost
+       super APIFeedBack
+
+       redef fun get(req, res) do
+               res.json new JsonArray.from(config.stars.most_rated)
+       end
+end
+
+# Best rated entities
+class APIStarsBest
+       super APIFeedBack
+
+       redef fun get(req, res) do
+               res.json new JsonArray.from(config.stars.best_rated)
+       end
+end
+
+# Best rated entities
+class APIStarsWorst
+       super APIFeedBack
+
+       redef fun get(req, res) do
+               res.json new JsonArray.from(config.stars.worst_rated)
+       end
+end
+
+# Best rated entities
+class APIStarsUsers
+       super APIFeedBack
+
+       redef fun get(req, res) do
+               res.json new JsonArray.from(config.stars.users_ratings)
+       end
+end
+
+# Stars attributed to mentities by user
+class APIUserStars
+       super APIFeedBack
+
+       redef fun get(req, res) do
+               var user = get_session_user(req)
+               if user == null then return
+               res.json new JsonArray.from(user.ratings(config))
        end
 end
 
 # Stars attributed to mentities
 class APIStars
-       super APIHandler
+       super APIFeedBack
 
        redef fun get(req, res) do
+               var login = get_session_login(req)
                var mentity = mentity_from_uri(req, res)
                if mentity == null then return
-               res.json mentity_ratings(mentity)
+               res.json mentity.ratings(config, login)
+       end
+end
+
+# Stars attributed to mentities by dimension
+class APIStarsDimension
+       super APIFeedBack
+
+       redef fun get(req, res) do
+               var login = get_session_login(req)
+               var mentity = mentity_from_uri(req, res)
+               if mentity == null then return
+               var dimension = req.param("dimension")
+               if dimension == null then return
+               res.json mentity.ratings_by_dimension(config, dimension, login)
        end
 
        redef fun post(req, res) do
+               var user = get_session_user(req)
+               var login = null
+               if user != null then login = user.login
+
                var mentity = mentity_from_uri(req, res)
                if mentity == null then return
+               var dimension = req.param("dimension")
+               if dimension == null then return
+
+               # Retrieve user previous rating
+               var previous = null
+               if user != null then
+                       previous = user.find_previous_rating(config, mentity, dimension)
+               end
+
                var obj = req.body.parse_json
                if not obj isa JsonObject then
                        res.api_error(400, "Expected a JSON object")
@@ -54,81 +159,129 @@ class APIStars
                        return
                end
 
-               var val = new MEntityRating(mentity.full_name, rating, get_time)
-               config.stars.insert(val.json)
-
-               res.json mentity_ratings(mentity)
+               if previous != null then
+                       previous.rating = rating
+                       previous.timestamp = get_time
+                       config.stars.save previous
+               else
+                       config.stars.save new StarRating(login, mentity.full_name, dimension, rating)
+               end
+               res.json mentity.ratings_by_dimension(config, dimension, login)
        end
+end
 
-       # Get the ratings of a `mentity`
-       fun mentity_ratings(mentity: MEntity): MEntityRatings do
-               var ratings = new MEntityRatings(mentity)
+# Star ratings allow users to rate mentities with a 5-stars system.
+#
+# Each rating can consider only one `dimension` of the mentity.
+# Dimensions are arbitrary strings used to group ratings.
+class StarRating
+       super RepoObject
+       serialize
 
-               var req = new JsonObject
-               req["mentity"] = mentity.full_name
-               var rs = config.stars.find_all(req)
-               for r in rs do ratings.ratings.add new MEntityRating.from_json(r)
-               return ratings
-       end
-end
+       # The user login that made that rating (or null if anon)
+       var user: nullable String
 
-# Ratings representation for a mentity
-class MEntityRatings
-       super Jsonable
+       # Rated `MEntity::full_name`
+       var mentity: String
 
-       # MEntity rated
-       var mentity: MEntity
+       # The dimension rated (arbritrary key)
+       var dimension: nullable String
 
-       # List of ratings
-       var ratings = new Array[MEntityRating]
+       # The rating (traditionally a score between 0 and 5)
+       var rating: Int is writable
 
-       # Mean of all ratings or 0
-       fun mean: Float do
-               if ratings.is_empty then return 0.0
-               var sum = 0.0
-               for r in ratings do sum += r.rating.to_f
-               var res = sum / ratings.length.to_f
-               return res
+       # Timestamp when this rating was created
+       var timestamp = 0 is writable
+end
+
+redef class User
+
+       # Find a previous rating of `self` for `mentity` and `dimension`
+       fun find_previous_rating(config: NitwebConfig, mentity: MEntity, dimension: nullable String): nullable StarRating do
+               var match = new MongoMatch
+               match.eq("mentity", mentity.full_name)
+               match.eq("dimension", dimension)
+               match.eq("user", login)
+               return config.stars.find(match)
        end
 
-       # Json representation of `self`
-       fun json: JsonObject do
+       # Find all ratings by `self`
+       fun ratings(config: NitwebConfig): Array[StarRating] do
+               return config.stars.find_all((new MongoMatch).eq("user", login))
+       end
+end
+
+redef class MEntity
+
+       # Get the ratings of a `dimension`
+       fun ratings_by_dimension(config: NitwebConfig, dimension: String, user: nullable String): JsonObject do
+               var match = (new MongoMatch).eq("mentity", full_name).eq("dimension", dimension)
+               var pipeline = new MongoPipeline
+               pipeline.match(match)
+               pipeline.group((new MongoGroup("mean_group")).avg("mean", "$rating"))
+
+               var res = config.stars.collection.aggregate(pipeline)
                var obj = new JsonObject
-               obj["mentity"] = mentity.full_name
-               obj["ratings"] = new JsonArray.from(ratings)
-               obj["mean"] = mean
+               obj["mean"] = if res.is_empty then 0.0 else res.first["mean"]
+               obj["ratings"] = new JsonArray.from(config.stars.find_all(match))
+
+               if user != null then
+                       match["user"] = user
+                       obj["user"] = config.stars.find(match)
+               end
                return obj
        end
 
-       redef fun serialize_to(v) do json.serialize_to(v)
+       # Get the ratings of a `mentity`
+       fun ratings(config: NitwebConfig, user: nullable String): JsonObject do
+               var match = new JsonObject
+               match["mentity"] = full_name
+               match["ratings"] = new JsonArray.from(config.stars.find_all(match))
+               match["feature"] = ratings_by_dimension(config, "feature", user)
+               match["doc"] = ratings_by_dimension(config, "doc", user)
+               match["examples"] = ratings_by_dimension(config, "examples", user)
+               match["code"] = ratings_by_dimension(config, "code", user)
+               return match
+       end
 end
 
-# Rating value of a MEntity
-class MEntityRating
-       super Jsonable
-
-       # MEntity this rating is about
-       var mentity: String
-
-       # Rating value (between 1 and 5)
-       var rating: Int
+# StarRating Mongo Repository
+class StarRatingRepo
+       super MongoRepository[StarRating]
 
-       # Timestamp of this rating
-       var timestamp: Int
+       # Find most rated mentities
+       fun most_rated: Array[JsonObject] do
+               var pipeline = new MongoPipeline
+               pipeline.group((new MongoGroup("$mentity")).sum("count", 1))
+               pipeline.sort((new MongoMatch).eq("count", -1))
+               pipeline.limit(10)
+               return collection.aggregate(pipeline)
+       end
 
-       # Init this rating value from a JsonObject
-       init from_json(obj: JsonObject) do
-               init(obj["mentity"].as(String), obj["rating"].as(Int), obj["timestamp"].as(Int))
+       # Find best rated mentities
+       fun best_rated: Array[JsonObject] do
+               var pipeline = new MongoPipeline
+               pipeline.group((new MongoGroup("$mentity")).avg("avg", "$rating"))
+               pipeline.sort((new MongoMatch).eq("avg", -1))
+               pipeline.limit(10)
+               return collection.aggregate(pipeline)
        end
 
-       # Translate this rating value to a JsonObject
-       fun json: JsonObject do
-               var obj = new JsonObject
-               obj["mentity"] = mentity
-               obj["rating"] = rating
-               obj["timestamp"] = timestamp
-               return obj
+       # Find worst rated mentities
+       fun worst_rated: Array[JsonObject] do
+               var pipeline = new MongoPipeline
+               pipeline.group((new MongoGroup("$mentity")).avg("avg", "$rating"))
+               pipeline.sort((new MongoMatch).eq("avg", 1))
+               pipeline.limit(10)
+               return collection.aggregate(pipeline)
        end
 
-       redef fun serialize_to(v) do json.serialize_to(v)
+       # Find worst rated mentities
+       fun users_ratings: Array[JsonObject] do
+               var pipeline = new MongoPipeline
+               pipeline.group((new MongoGroup("$user")).sum("count", 1))
+               pipeline.sort((new MongoMatch).eq("count", -1))
+               pipeline.limit(10)
+               return collection.aggregate(pipeline)
+       end
 end
index dbf93ab..31077c4 100644 (file)
@@ -69,6 +69,16 @@ class APIList
                return mentities
        end
 
+       # Sort mentities by lexicographic order
+       #
+       # TODO choose order from request
+       fun sort_mentities(req: HttpRequest, mentities: Array[MEntity]) : Array[MEntity] do
+               var sorted = mentities.to_a
+               var sorter = new MEntityNameSorter
+               sorter.sort(sorted)
+               return sorted
+       end
+
        # Limit mentities depending on the `n` parameter.
        fun limit_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
                var n = req.int_arg("n")
@@ -80,6 +90,7 @@ class APIList
 
        redef fun get(req, res) do
                var mentities = list_mentities(req)
+               mentities = sort_mentities(req, mentities)
                mentities = limit_mentities(req, mentities)
                res.json new JsonArray.from(mentities)
        end
@@ -173,25 +184,27 @@ end
 #
 # Example: `GET /defs/core::Array`
 class APIEntityDefs
-       super APIHandler
+       super APIList
 
        redef fun get(req, res) do
                var mentity = mentity_from_uri(req, res)
                if mentity == null then return
-               var arr = new JsonArray
+               var mentities: Array[MEntity]
                if mentity isa MModule then
-                       for mclassdef in mentity.mclassdefs do arr.add mclassdef
+                       mentities = mentity.mclassdefs
                else if mentity isa MClass then
-                       for mclassdef in mentity.mclassdefs do arr.add mclassdef
+                       mentities = mentity.mclassdefs
                else if mentity isa MClassDef then
-                       for mpropdef in mentity.mpropdefs do arr.add mpropdef
+                       mentities = mentity.mpropdefs
                else if mentity isa MProperty then
-                       for mpropdef in mentity.mpropdefs do arr.add mpropdef
+                       mentities = mentity.mpropdefs
                else
                        res.api_error(404, "No definition list for mentity `{mentity.full_name}`")
                        return
                end
-               res.json arr
+               mentities = sort_mentities(req, mentities)
+               mentities = limit_mentities(req, mentities)
+               res.json new JsonArray.from(mentities)
        end
 end
 
index 587be65..351d7a6 100644 (file)
@@ -26,7 +26,7 @@ import popcorn::pop_repos
 class NitwebConfig
        super AppConfig
 
-       redef var default_db_name = "nitweb"
+       redef fun default_db_name do return "nitweb"
 
        # Model to use.
        var model: Model