Merge: Base64: Revamped base64_decode and added a strict mode
authorJean Privat <jean@pryen.org>
Fri, 10 Jun 2016 01:55:20 +0000 (21:55 -0400)
committerJean Privat <jean@pryen.org>
Fri, 10 Jun 2016 01:55:20 +0000 (21:55 -0400)
As talked with @ppepos, base64_decode now supports either `strict` or `non-strict`
modes when decoding a base64 string.

The `strict` mode is inspired from the `-i` option from the Unix `base64` command, which ignores non-compliant characters and non-padded `base64` strings.

This is more or less what we do now, `strict` being the default.

Since only one allocation and copy is done now, performance should be better.
There is however one pitfall of this implementation due to the `nullable Object` contract from `Collection[K]`, causing multiple boxings, some further optimization could be done on that front.

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

177 files changed:
.gitattributes
NOTICE
README.md
contrib/benitlux/src/client/base.nit
contrib/benitlux/src/client/features/translations.nit
contrib/benitlux/src/client/views/user_views.nit
contrib/benitlux/src/server/benitlux_social.nit
contrib/nitiwiki/src/wiki_base.nit
contrib/nitiwiki/src/wiki_html.nit
contrib/nitiwiki/src/wiki_links.nit
contrib/nitrpg/src/test_helper.nit
contrib/pep8analysis/package.ini
contrib/refund/src/refund_json.nit
contrib/simplan/simplan.nit
examples/fibonacci.nit
lib/app/README.md
lib/app/http_request.nit
lib/app/package.ini
lib/core/collection/abstract_collection.nit
lib/core/collection/array.nit
lib/core/collection/list.nit
lib/core/error.nit
lib/core/exec.nit
lib/core/file.nit
lib/core/math.nit
lib/core/re.nit
lib/core/text/abstract_text.nit
lib/core/text/flat.nit
lib/core/text/ropes.nit
lib/core/text/test_abstract_text.nit [new file with mode: 0644]
lib/nitcorn/examples/src/htcpcp_server.nit
lib/nitcorn/http_request.nit
lib/nitcorn/http_response.nit
lib/nitcorn/media_types.nit
lib/popcorn/Makefile
lib/popcorn/examples/angular/test_example_angular.nit [moved from lib/popcorn/tests/test_example_angular.nit with 68% similarity]
lib/popcorn/examples/angular/test_example_angular.sav/test_example_angular.res [moved from lib/popcorn/tests/res/test_example_angular.res with 100% similarity]
lib/popcorn/examples/handlers/test_example_post_handler.nit [moved from lib/popcorn/tests/test_example_post.nit with 75% similarity]
lib/popcorn/examples/handlers/test_example_post_handler.sav/test_example_post_handler.res [moved from lib/popcorn/tests/res/test_example_post.res with 100% similarity]
lib/popcorn/examples/handlers/test_example_query_string.nit [moved from lib/popcorn/tests/test_example_query_string.nit with 73% similarity]
lib/popcorn/examples/handlers/test_example_query_string.sav/test_example_query_string.res [moved from lib/popcorn/tests/res/test_example_query_string.res with 100% similarity]
lib/popcorn/examples/hello_world/test_example_hello.nit [moved from lib/popcorn/tests/test_example_hello.nit with 73% similarity]
lib/popcorn/examples/hello_world/test_example_hello.sav/test_example_hello.res [moved from lib/popcorn/tests/res/test_example_hello.res with 98% similarity]
lib/popcorn/examples/middlewares/test_example_advanced_logger.nit [moved from lib/popcorn/tests/test_example_advanced_logger.nit with 65% similarity]
lib/popcorn/examples/middlewares/test_example_advanced_logger.sav/test_example_advanced_logger.res [moved from lib/popcorn/tests/res/test_example_advanced_logger.res with 100% similarity]
lib/popcorn/examples/middlewares/test_example_html_error_handler.nit [moved from lib/popcorn/tests/test_example_html_error_handler.nit with 70% similarity]
lib/popcorn/examples/middlewares/test_example_html_error_handler.sav/test_example_html_error_handler.res [moved from lib/popcorn/tests/res/test_example_html_error_handler.res with 100% similarity]
lib/popcorn/examples/middlewares/test_example_simple_error_handler.nit [moved from lib/popcorn/tests/test_example_simple_error_handler.nit with 68% similarity]
lib/popcorn/examples/middlewares/test_example_simple_error_handler.sav/test_example_simple_error_handler.res [moved from lib/popcorn/tests/res/test_example_simple_error_handler.res with 100% similarity]
lib/popcorn/examples/middlewares/test_example_simple_logger.nit [moved from lib/popcorn/tests/test_example_simple_logger.nit with 68% similarity]
lib/popcorn/examples/middlewares/test_example_simple_logger.sav/test_example_simple_logger.res [moved from lib/popcorn/tests/res/test_example_simple_logger.res with 100% similarity]
lib/popcorn/examples/routing/test_example_glob_route.nit [moved from lib/popcorn/tests/test_example_glob_route.nit with 75% similarity]
lib/popcorn/examples/routing/test_example_glob_route.sav/test_example_glob_route.res [moved from lib/popcorn/tests/res/test_example_glob_route.res with 98% similarity]
lib/popcorn/examples/routing/test_example_param_route.nit [moved from lib/popcorn/tests/test_example_param_route.nit with 73% similarity]
lib/popcorn/examples/routing/test_example_param_route.sav/test_example_param_route.res [moved from lib/popcorn/tests/res/test_example_param_route.res with 98% similarity]
lib/popcorn/examples/routing/test_example_router.nit [moved from lib/popcorn/tests/test_example_router.nit with 67% similarity]
lib/popcorn/examples/routing/test_example_router.sav/test_example_router.res [moved from lib/popcorn/tests/res/test_example_router.res with 98% similarity]
lib/popcorn/examples/sessions/test_example_session.nit [moved from lib/popcorn/tests/test_example_session.nit with 71% similarity]
lib/popcorn/examples/sessions/test_example_session.sav/test_example_session.res [moved from lib/popcorn/tests/res/test_example_session.res with 100% similarity]
lib/popcorn/examples/static_files/test_example_static.nit [moved from lib/popcorn/tests/test_example_static.nit with 73% similarity]
lib/popcorn/examples/static_files/test_example_static.sav/test_example_static.res [moved from lib/popcorn/tests/res/test_example_static.res with 100% similarity]
lib/popcorn/examples/static_files/test_example_static_default.nit [moved from lib/popcorn/tests/test_example_static_default.nit with 72% similarity]
lib/popcorn/examples/static_files/test_example_static_default.sav/test_example_static_default.res [moved from lib/popcorn/tests/res/test_example_static_default.res with 100% similarity]
lib/popcorn/examples/static_files/test_example_static_multiple.nit [moved from lib/popcorn/tests/test_example_static_multiple.nit with 67% similarity]
lib/popcorn/examples/static_files/test_example_static_multiple.sav/test_example_static_multiple.res [moved from lib/popcorn/tests/res/test_example_static_multiple.res with 100% similarity]
lib/popcorn/pop_tests.nit [new file with mode: 0644]
lib/popcorn/test_popcorn.nit [new file with mode: 0644]
lib/popcorn/test_popcorn.sav/test_router.res [moved from lib/popcorn/tests/res/test_router.res with 98% similarity]
lib/popcorn/test_popcorn.sav/test_routes.res [moved from lib/popcorn/tests/res/test_routes.res with 99% similarity]
lib/popcorn/tests/base_tests.nit [deleted file]
lib/popcorn/tests/test_router.nit [deleted file]
lib/popcorn/tests/test_routes.nit [deleted file]
lib/popcorn/tests/tests.sh [deleted file]
lib/readline.ini [new file with mode: 0644]
lib/readline.nit [new file with mode: 0644]
share/man/nit.md
share/man/nitc.md
share/man/nitunit.md
share/nitweb/directives/contributor-list.html [new file with mode: 0644]
share/nitweb/directives/entity/card.html [new file with mode: 0644]
share/nitweb/directives/entity/defcard.html [new file with mode: 0644]
share/nitweb/directives/entity/doc.html [new file with mode: 0644]
share/nitweb/directives/entity/linearization.html [new file with mode: 0644]
share/nitweb/directives/entity/link.html [new file with mode: 0644]
share/nitweb/directives/entity/list.html [new file with mode: 0644]
share/nitweb/directives/entity/location.html [new file with mode: 0644]
share/nitweb/directives/entity/signature.html [new file with mode: 0644]
share/nitweb/directives/entity/tag.html [new file with mode: 0644]
share/nitweb/directives/group-block.html [new file with mode: 0644]
share/nitweb/directives/ui-filter-button-vis.html [new file with mode: 0644]
share/nitweb/directives/ui-filter-field.html [new file with mode: 0644]
share/nitweb/directives/ui-filter-form.html [new file with mode: 0644]
share/nitweb/directives/ui-filter-group-vis.html [new file with mode: 0644]
share/nitweb/index.html [new file with mode: 0644]
share/nitweb/javascripts/entities.js [new file with mode: 0644]
share/nitweb/javascripts/index.js [new file with mode: 0644]
share/nitweb/javascripts/model.js [new file with mode: 0644]
share/nitweb/javascripts/nitweb.js [new file with mode: 0644]
share/nitweb/javascripts/ui.js [new file with mode: 0644]
share/nitweb/stylesheets/nitweb.css [new file with mode: 0644]
share/nitweb/stylesheets/nitweb_bootstrap.css [new file with mode: 0644]
share/nitweb/views/class.html [new file with mode: 0644]
share/nitweb/views/classdef.html [new file with mode: 0644]
share/nitweb/views/group.html [new file with mode: 0644]
share/nitweb/views/index.html [new file with mode: 0644]
share/nitweb/views/module.html [new file with mode: 0644]
share/nitweb/views/package.html [new file with mode: 0644]
share/nitweb/views/propdef.html [new file with mode: 0644]
share/nitweb/views/property.html [new file with mode: 0644]
src/catalog.nit
src/loader.nit
src/model/model_collect.nit
src/modelbuilder_base.nit
src/nit.nit
src/nitcatalog.nit
src/nitls.nit
src/nitunit.nit
src/nitweb.nit
src/package.ini
src/rapid_type_analysis.nit
src/testing/testing_base.nit
src/testing/testing_doc.nit
src/testing/testing_suite.nit
src/web/api_catalog.nit [new file with mode: 0644]
src/web/model_api.nit [new file with mode: 0644]
src/web/web.nit
src/web/web_actions.nit
src/web/web_base.nit
src/web/web_views.nit
tests/Darwin.skip
tests/base_gen_infinite2.nit [new file with mode: 0644]
tests/nit.args
tests/nit_args8.inputs [new file with mode: 0644]
tests/niti.skip
tests/nitls.args
tests/nitpick.args
tests/nitunit.args
tests/nitvm.skip
tests/project1/subdir/subdir2/subdir3/submodule.nit [new file with mode: 0644]
tests/project1/uselessdir/uselessdir2/uselessfile [new file with mode: 0644]
tests/sav/base_gen_infinite2.res [new file with mode: 0644]
tests/sav/nit_args8.res [new file with mode: 0644]
tests/sav/nitcatalog_args1.res
tests/sav/nitce/base_gen_infinite2.res [new file with mode: 0644]
tests/sav/nitcg/fixme/base_gen_infinite2.res [new file with mode: 0644]
tests/sav/nitcs/fixme/base_gen_infinite2.res [new file with mode: 0644]
tests/sav/nitcsg/fixme/base_gen_infinite2.res [new file with mode: 0644]
tests/sav/nitdoc_args4.res
tests/sav/nitls_args1.res
tests/sav/nitls_args2.res
tests/sav/nitls_args3.res
tests/sav/nitls_args4.res
tests/sav/nitls_args6.res
tests/sav/nitls_args7.res [new file with mode: 0644]
tests/sav/nitls_args8.res [new file with mode: 0644]
tests/sav/nitunit_args1.res
tests/sav/nitunit_args4.res
tests/sav/nitunit_args5.res
tests/sav/nitunit_args6.res
tests/sav/nitunit_args7.res
tests/sav/nitunit_args8.res
tests/sav/nitunit_args9.res
tests/sav/test_basename_perf_args1.res
tests/sav/test_neo_args1.res
tests/sav/test_new_native_alt1.res
tests/sav/test_readline.res [new file with mode: 0644]
tests/sav/test_sort_perf_args1.res
tests/test_nitunit4/sav/test_sav_conflict.res [new file with mode: 0644]
tests/test_nitunit4/test_baz.res [moved from tests/test_nitunit4/test_nitunit4.sav/test_baz.res with 100% similarity]
tests/test_nitunit4/test_nitunit4.nit
tests/test_nitunit4/test_nitunit4.sav/test_sav_conflict.res [new file with mode: 0644]
tests/test_nitunit4/test_sav_conflict.res [new file with mode: 0644]
tests/test_prog/game/excluded.nit [new file with mode: 0644]
tests/test_prog/game/excluded_dir/more.nit [new file with mode: 0644]
tests/test_prog/package.ini
tests/test_readline.inputs [new file with mode: 0644]
tests/test_readline.nit [moved from lib/popcorn/tests/Makefile with 77% similarity]

index 02c0677..e326f39 100644 (file)
@@ -6,5 +6,5 @@ tables_nit.c            -diff
 c_src/**               -diff
 
 tests/sav/**/*.res     -whitespace
-lib/popcorn/tests/res/*.res -whitespace
+*.res                  -whitespace
 *.patch                        -whitespace
diff --git a/NOTICE b/NOTICE
index 33a5cff..514ed50 100644 (file)
--- a/NOTICE
+++ b/NOTICE
@@ -30,6 +30,7 @@ Copyright: 2004-2016 Jean Privat <jean@pryen.org>
            2015      Jean-Philippe Caissy <jean-philippe.caissy@shopify.com>
            2015      Alexandre Blondin Massé <alexandre.blondin.masse@gmail.com>
            2015-2016 Guilherme Mansur <guilhermerpmansur@gmail.com>
+           2016      Tony Gaetani <tony.gaetani@gmail.com>
 License: Apache 2.0 (see LICENSE)
 Comment: The main license of the work, except for the following exceptions
 
index d84731a..1a5dd3c 100644 (file)
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ Requirements:
  * gcc         http://gcc.gnu.org/ (or a compatible C compiler)
  * pkg-config  http://www.freedesktop.org/wiki/Software/pkg-config/
  * ccache      http://ccache.samba.org/        to improve recompilation
- * libgc-dev   http://www.hpl.hp.com/personal/Hans_Boehm/gc/
+ * libgc-dev   http://hboehm.info/gc/
  * graphviz    http://www.graphviz.org/        to enable graphs with the nitdoc tool
  * libunwind   http://nongnu.org/libunwind
 
index bcc1efd..438a125 100644 (file)
@@ -117,14 +117,17 @@ class BenitluxHttpRequest
                        app.user = null
                        return true
                else if res isa BenitluxError then
-                       app.feedback((res.user_message or else res.message).t)
+                       feedback((res.user_message or else res.message).t)
                        return true
                else if res isa Error then
-                       app.feedback res.message.t
+                       feedback res.message.t
                        return true
                end
                return false
        end
+
+       # Show feedback pertinent to the user, defaults to a platform specific popup
+       fun feedback(text: String) do app.feedback text
 end
 
 # Async request with services to act on the windows of the app
index 02deb61..be42f57 100644 (file)
@@ -63,13 +63,23 @@ do
        map["Welcome %0!"] = "Bienvenue %0!"
        map["Logged in as %0"] = "Connecté en tant que %0"
        map["Username"] = "Nom d'utilisateur"
-       map["Invalid name"] = "Nom d'utilisateur invalide"
        map["Password"] = "Mot de passe"
-       map["Passwords must be composed of at least 6 characters."] = "Le mot de passe doit avoir au moins 6 charactères."
+       map["Repeat password"] = "Répéter le mot de passe"
        map["Email"] = "Courriel"
        map["Login"] = "Se connecter"
+       map["Loging in..."] = "Authentification..."
        map["Logout"] = "Se déconnecter"
        map["Signup"] = "Créer un compte"
+       map["Signing up..."] = "Création du compte..."
+
+       map["Passwords must be composed of at least 6 characters."] = "Le mot de passe doit avoir au moins 6 charactères."
+       map["Fill the following fields to sign up."] = "Remplissez les champs suivants pour créer un compte."
+
+       map["Passwords do not match."] = "Les mots de passe ne correspondent pas."
+       map["Invalid username."] = "Nom d'utilisateur invalide."
+       map["Invalid password."] = "Mot de passe invalide."
+       map["Username already in use."] = "Le nom d'utilisateur est déjà réservé."
+       map["Invalid username and password combination."] = "La combinaison de nom et mot de passe n'est pas reconnue."
 
        # Social views
        map["Follow"] = "Suivre"
index e45fcb5..b69b1db 100644 (file)
@@ -86,38 +86,50 @@ end
 class SignupWindow
        super Window
 
-       # Main window layout
-       var layout = new ListLayout(parent=self)
+       private var list = new ListLayout(parent=self)
+       private var lbl_feedback = new Label(parent=list, text="Welcome")
 
-       private var lbl_welcome = new Label(parent=layout, text="Welcome")
+       private var layout_login = new VerticalLayout(parent=list)
+
+       # ---
+       # First the login options
 
        # Name
-       private var name_line = new HorizontalLayout(parent=layout)
+       private var name_line = new HorizontalLayout(parent=layout_login)
        private var lbl_name = new Label(parent=name_line, text="Username".t)
        private var txt_name = new TextInput(parent=name_line, text=app.user)
 
-       # Pass
-       private var pass_line = new HorizontalLayout(parent=layout)
+       # Password
+       private var pass_line = new HorizontalLayout(parent=layout_login)
        private var lbl_pass = new Label(parent=pass_line, text="Password".t)
        private var txt_pass = new TextInput(parent=pass_line, is_password=true)
-       private var lbl_pass_desc = new Label(parent=layout,
+       private var lbl_pass_desc = new Label(parent=layout_login, size = 0.5,
                text="Passwords must be composed of at least 6 characters.".t)
 
-       private var but_login = new Button(parent=layout, text="Login".t)
+       private var but_login = new Button(parent=layout_login, text="Login".t)
+
+       # ---
+       # Then, the signup options
+
+       private var layout_register = new VerticalLayout(parent=list)
+
+       private var lbl_signup_desc = new Label(parent=layout_register, size = 0.5,
+               text="Fill the following fields to sign up.".t)
+
+       # Repeat password
+       private var pass_line2 = new HorizontalLayout(parent=layout_register)
+       private var lbl_pass2 = new Label(parent=pass_line2, text="Repeat password".t)
+       private var txt_pass2 = new TextInput(parent=pass_line2, is_password=true)
 
        # Email
-       private var email_line = new HorizontalLayout(parent=layout)
+       private var email_line = new HorizontalLayout(parent=layout_register)
        private var lbl_email = new Label(parent=email_line, text="Email".t)
        private var txt_email = new TextInput(parent=email_line)
 
-       private var but_signup = new Button(parent=layout, text="Signup".t)
-
-       private var lbl_feedback = new Label(parent=layout, text="")
+       private var but_signup = new Button(parent=layout_register, text="Signup".t)
 
        init
        do
-               lbl_pass_desc.size = 0.5
-
                for c in [but_login, but_signup] do
                        c.observers.add self
                end
@@ -133,25 +145,32 @@ class SignupWindow
 
                                var name = txt_name.text
                                if name == null or not name.name_is_ok then
-                                       feedback "Invalid name".t
+                                       feedback "Invalid username.".t
                                        return
                                end
 
                                var pass = txt_pass.text
                                if pass == null or not pass.pass_is_ok then
-                                       feedback "Invalid password".t
+                                       feedback "Invalid password.".t
                                        return
                                end
 
                                if sender == but_login then
+                                       feedback "Logging in...".t
                                        (new LoginOrSignupAction(self, "rest/login?name={name}&pass={pass.pass_hash}")).start
                                else if sender == but_signup then
+                                       if pass != txt_pass2.text then
+                                               feedback "Passwords do not match.".t
+                                               return
+                                       end
+
                                        var email = txt_email.text
                                        if email == null or email.is_empty then
                                                feedback "Invalid email".t
                                                return
                                        end
 
+                                       feedback "Signing up...".t
                                        (new LoginOrSignupAction(self, "rest/signup?name={name}&pass={pass.pass_hash}&email={email}")).start
                                end
                        end
@@ -189,27 +208,6 @@ class LoginOrSignupAction
 
                app.on_log_in
        end
-end
-
-# Async request for signing up
-class SignupAction
-       super WindowHttpRequest
 
-       redef type W: SignupWindow
-
-       init do affected_views.add_all([window.but_signup])
-
-       redef fun on_load(res)
-       do
-               if intercept_error(res) then return
-
-               if not res isa LoginResult then
-                       on_fail new Error("Server sent unexpected data {res or else "null"}")
-                       return
-               end
-
-               app.token = res.token
-               app.user = res.user.name
-               app.on_log_in
-       end
+       redef fun feedback(text) do window.feedback text
 end
index bdd83b4..7764c75 100644 (file)
@@ -182,7 +182,7 @@ GROUP BY beer0, beer1""") else
                # Check if already in user
                var stmt = select("ROWID FROM users WHERE lower({user.to_sql_string}) = lower(name)")
                assert stmt != null else print_error "Select 'sign_up' failed with: {error or else "?"}"
-               if not stmt.iterator.to_a.is_empty then return "Username already in use"
+               if not stmt.iterator.to_a.is_empty then return "Username already in use."
 
                # Check email use
                stmt = select("ROWID FROM users WHERE lower({email.to_sql_string}) = lower(email)")
index 70cf35f..d3adc26 100644 (file)
@@ -94,7 +94,7 @@ class Nitiwiki
                                var entry = entries[path]
                                if not entry.is_dirty then continue
                                var name = entry.name
-                               if entry.has_source then name = entry.src_path.to_s
+                               if entry.has_source then name = entry.src_path.as(not null)
                                if entry.is_new then
                                        print " + {name}"
                                else
@@ -141,7 +141,7 @@ class Nitiwiki
        fun need_render(src, target: String): Bool do
                if force_render then return true
                if not target.file_exists then return true
-               return src.file_stat.mtime >= target.file_stat.mtime
+               return src.file_stat.as(not null).mtime >= target.file_stat.as(not null).mtime
        end
 
        # Create a new `WikiSection`.
@@ -244,7 +244,7 @@ class Nitiwiki
        # Used to translate ids in beautiful page names.
        fun pretty_name(name: String): String do
                name = name.replace("_", " ")
-               name = name.capitalized
+               name = name.capitalized(keep_upper=true)
                return name
        end
 end
@@ -283,7 +283,7 @@ abstract class WikiEntry
        # Returns `-1` if not `has_source`.
        fun create_time: Int do
                if not has_source then return -1
-               return src_full_path.file_stat.ctime
+               return src_full_path.as(not null).file_stat.as(not null).ctime
        end
 
        # Entry last modification time.
@@ -291,7 +291,7 @@ abstract class WikiEntry
        # Returns `-1` if not `has_source`.
        fun last_edit_time: Int do
                if not has_source then return -1
-               return src_full_path.file_stat.mtime
+               return src_full_path.as(not null).file_stat.as(not null).mtime
        end
 
        # Entry list rendering time.
@@ -299,7 +299,7 @@ abstract class WikiEntry
        # Returns `-1` if `is_new`.
        fun last_render_time: Int do
                if is_new then return -1
-               return out_full_path.file_stat.mtime
+               return out_full_path.file_stat.as(not null).mtime
        end
 
        # Entries hierarchy
@@ -400,7 +400,7 @@ abstract class WikiEntry
        # then returns the main wiki template file.
        fun template_file: String do
                if is_root then return wiki.config.template_file
-               return parent.template_file
+               return parent.as(not null).template_file
        end
 
        # Header template file for `self`.
@@ -408,7 +408,7 @@ abstract class WikiEntry
        # Behave like `template_file`.
        fun header_file: String do
                if is_root then return wiki.config.header_file
-               return parent.header_file
+               return parent.as(not null).header_file
        end
 
        # Footer template file for `self`.
@@ -416,7 +416,7 @@ abstract class WikiEntry
        # Behave like `template_file`.
        fun footer_file: String do
                if is_root then return wiki.config.footer_file
-               return parent.footer_file
+               return parent.as(not null).footer_file
        end
 
        # Menu template file for `self`.
@@ -424,7 +424,7 @@ abstract class WikiEntry
        # Behave like `template_file`.
        fun menu_file: String do
                if is_root then return wiki.config.menu_file
-               return parent.menu_file
+               return parent.as(not null).menu_file
        end
 
        # Display the entry `name`.
@@ -442,7 +442,7 @@ class WikiSection
 
        redef fun title do
                if has_config then
-                       var title = config.title
+                       var title = config.as(not null).title
                        if title != null then return title
                end
                return super
@@ -452,7 +452,7 @@ class WikiSection
        #
        # Hidden section are rendered but not linked in menus.
        fun is_hidden: Bool do
-               if has_config then return config.is_hidden
+               if has_config then return config.as(not null).is_hidden
                return false
        end
 
@@ -461,7 +461,7 @@ class WikiSection
                if parent == null then
                        return wiki.config.source_dir
                else
-                       return wiki.expand_path(parent.src_path, name)
+                       return wiki.expand_path(parent.as(not null).src_path, name)
                end
        end
 
@@ -486,41 +486,41 @@ class WikiSection
        # Also check custom config.
        redef fun template_file do
                if has_config then
-                       var tpl = config.template_file
+                       var tpl = config.as(not null).template_file
                        if tpl != null then return tpl
                end
                if is_root then return wiki.config.template_file
-               return parent.template_file
+               return parent.as(not null).template_file
        end
 
        # Also check custom config.
        redef fun header_file do
                if has_config then
-                       var tpl = config.header_file
+                       var tpl = config.as(not null).header_file
                        if tpl != null then return tpl
                end
                if is_root then return wiki.config.header_file
-               return parent.header_file
+               return parent.as(not null).header_file
        end
 
        # Also check custom config.
        redef fun footer_file do
                if has_config then
-                       var tpl = config.footer_file
+                       var tpl = config.as(not null).footer_file
                        if tpl != null then return tpl
                end
                if is_root then return wiki.config.footer_file
-               return parent.footer_file
+               return parent.as(not null).footer_file
        end
 
        # Also check custom config.
        redef fun menu_file do
                if has_config then
-                       var tpl = config.menu_file
+                       var tpl = config.as(not null).menu_file
                        if tpl != null then return tpl
                end
                if is_root then return wiki.config.menu_file
-               return parent.menu_file
+               return parent.as(not null).menu_file
        end
 end
 
@@ -534,7 +534,8 @@ class WikiArticle
        # Articles can only have `WikiSection` as parents.
        redef type PARENT: WikiSection
 
-       redef fun title: String do
+       redef fun title do
+               var parent = self.parent
                if name == "index" and parent != null then return parent.title
                return super
        end
@@ -551,7 +552,7 @@ class WikiArticle
                content = md
        end
 
-       redef var src_full_path: nullable String = null
+       redef var src_full_path = null
 
        redef fun src_path do
                var src_full_path = self.src_full_path
@@ -567,7 +568,7 @@ class WikiArticle
        # REQUIRE: `has_source`.
        var md: nullable String is lazy do
                if not has_source then return null
-               var file = new FileReader.open(src_full_path.to_s)
+               var file = new FileReader.open(src_full_path.as(not null))
                var md = file.read_all
                file.close
                return md
@@ -578,7 +579,7 @@ class WikiArticle
        redef fun is_dirty do
                if super then return true
                if has_source then
-                       return wiki.need_render(src_full_path.to_s, out_full_path)
+                       return wiki.need_render(src_full_path.as(not null), out_full_path)
                end
                return false
        end
@@ -779,7 +780,7 @@ class WikiConfig
        var sidebar_blocks: Array[String] is lazy do
                var res = new Array[String]
                if not has_key("wiki.sidebar.blocks") then return res
-               for val in at("wiki.sidebar.blocks").values do
+               for val in at("wiki.sidebar.blocks").as(not null).values do
                        res.add val
                end
                return res
index ab79f09..15ae4d7 100644 (file)
@@ -70,7 +70,8 @@ end
 redef class WikiSection
 
        # Output directory (where to ouput the HTML pages for this section).
-       redef fun out_path: String do
+       redef fun out_path do
+               var parent = self.parent
                if parent == null then
                        return wiki.config.out_dir
                else
@@ -104,7 +105,7 @@ redef class WikiSection
        # Copy attached files from `src_path` to `out_path`.
        private fun copy_files do
                assert has_source
-               var dir = src_full_path.to_s
+               var dir = src_full_path.as(not null).to_s
                for name in dir.files do
                        if name == wiki.config_filename then continue
                        if name.has_suffix(".md") then continue
@@ -167,7 +168,8 @@ end
 
 redef class WikiArticle
 
-       redef fun out_path: String do
+       redef fun out_path do
+               var parent = self.parent
                if parent == null then
                        return wiki.expand_path(wiki.config.out_dir, "{name}.html")
                else
index ac3e171..33dc61f 100644 (file)
@@ -31,7 +31,7 @@ redef class Nitiwiki
        # Returns `null` if no article can be found.
        fun lookup_entry_by_name(context: WikiEntry, name: String): nullable WikiEntry do
                var section: nullable WikiEntry = context.parent or else context
-               var res = section.lookup_entry_by_name(name)
+               var res = section.as(not null).lookup_entry_by_name(name)
                if res != null then return res
                while section != null do
                        if section.name == name then return section
@@ -52,7 +52,7 @@ redef class Nitiwiki
        # Returns `null` if no article can be found.
        fun lookup_entry_by_title(context: WikiEntry, title: String): nullable WikiEntry do
                var section: nullable WikiEntry = context.parent or else context
-               var res = section.lookup_entry_by_title(title)
+               var res = section.as(not null).lookup_entry_by_title(title)
                if res != null then return res
                while section != null do
                        if section.title.to_lower == title.to_lower then return section
@@ -200,6 +200,7 @@ redef class WikiArticle
        fun is_index: Bool do return name == "index"
 
        redef fun href do
+               var parent = self.parent
                if parent == null then
                        return "{name}.html"
                else
index 904862c..ecdc358 100644 (file)
@@ -15,7 +15,7 @@
 # limitations under the License.
 
 # Test tools for NitRPG.
-module test_helper is test_suite
+module test_helper
 
 import test_suite
 import game
index 43d5e4a..bbf3802 100644 (file)
@@ -3,6 +3,8 @@ name=pep8analysis
 tags=educ,web,cli
 maintainer=Alexis Laferrière <alexis.laf@xymus.net>
 license=Apache-2.0
+[source]
+exclude=src/parser/parser_abs.nit
 [upstream]
 browse=https://github.com/nitlang/nit/tree/master/contrib/pep8analysis/
 git=https://github.com/nitlang/nit.git
index 33032b3..4da71f5 100644 (file)
@@ -72,6 +72,9 @@ redef class RefundProcessor
                if json isa JsonParseError then
                        die("Wrong input file ({json.message})")
                        abort
+               else if json == null then
+                       die("Unable to parse input file as json (got null)")
+                       abort
                else if not json isa JsonObject then
                        die("Wrong input type (expected JsonObject got {json.class_name})")
                        abort
@@ -130,7 +133,7 @@ redef class RefundProcessor
                return new RefundStats.from_json(content)
        end
 
-       redef fun save_stats(stats: RefundStats) do
+       redef fun save_stats(stats) do
                write_output(stats.to_json.to_pretty_json, stats_file)
        end
 end
@@ -165,13 +168,19 @@ redef class ReclamationSheet
                proc.check_key(json, "reclamations")
                var res = new Array[Reclamation]
                var recls = json["reclamations"]
-               if not recls isa JsonArray then
+               if recls == null then
+                       proc.die("Wrong type for `number` (expected JsonArray got null)")
+                       abort
+               else if not recls isa JsonArray then
                        proc.die("Wrong type for `number` (expected JsonArray got {recls.class_name})")
                        abort
                end
                var i = 0
                for obj in recls do
-                       if not obj isa JsonObject then
+                       if obj == null then
+                               proc.die("Wrong type for `reclamations#{i}` (expected JsonObject got null)")
+                               abort
+                       else if not obj isa JsonObject then
                                proc.die("Wrong type for `reclamations#{i}` " +
                                        "(expected JsonObject got {obj.class_name})")
                                abort
@@ -197,7 +206,10 @@ redef class ReclFile
        init from_json(proc: RefundProcessor, json: JsonObject) do
                proc.check_key(json, "dossier")
                var id = json["dossier"]
-               if not id isa String then
+               if id == null then
+                       proc.die("Wrong type for `dossier` (expected String got null)")
+                       abort
+               else if not id isa String then
                        proc.die("Wrong type for `dossier` (expected String got {id.class_name})")
                        abort
                end
@@ -232,7 +244,10 @@ redef class ReclMonth
        init from_json(proc: RefundProcessor, json: JsonObject) do
                proc.check_key(json, "mois")
                var month = json["mois"]
-               if not month isa String then
+               if month == null then
+                       proc.die("Wrong type for `mois` (expected String got null)")
+                       return
+               else if not month isa String then
                        proc.die("Wrong type for `mois` (expected String got {month.class_name})")
                        return
                end
@@ -264,7 +279,10 @@ redef class ReclDate
        init from_json(proc: RefundProcessor, json: JsonObject) do
                proc.check_key(json, "date")
                var date = json["date"]
-               if not date isa String then
+               if date == null then
+                       proc.die("Wrong type for `date` (expected String got null)")
+                       abort
+               else if not date isa String then
                        proc.die("Wrong type for `date` (expected String got {date.class_name})")
                        abort
                end
@@ -302,7 +320,10 @@ redef class Reclamation
        private fun parse_care_id(proc: RefundProcessor, json: JsonObject): Int do
                proc.check_key(json, "soin")
                var id = json["soin"]
-               if not id isa Int then
+               if id == null then
+                       proc.die("Wrong type for `soin` (expected Int got null)")
+                       abort
+               else if not id isa Int then
                        proc.die("Wrong type for `soin` (expected Int got {id.class_name})")
                        abort
                end
@@ -313,7 +334,10 @@ redef class Reclamation
        private fun parse_fees(proc: RefundProcessor, json: JsonObject): Dollar do
                proc.check_key(json, "montant")
                var fees = json["montant"]
-               if not fees isa String then
+               if fees == null then
+                       proc.die("Wrong type for `fees` (expected String got null)")
+                       abort
+               else if not fees isa String then
                        proc.die("Wrong type for `fees` (expected String got {fees.class_name})")
                        abort
                end
index ba32497..301c564 100644 (file)
@@ -80,7 +80,7 @@ class PlanProblem
                        print n.message
                        exit 1
                end
-               n = n.children.first.children.first.as(not null)
+               n = n.children.first.as(not null).children.first.as(not null)
                if n isa Nplan then
                        print "Error: expected a problem, got a plan."
                        exit 1
@@ -88,7 +88,7 @@ class PlanProblem
                assert n isa Nproblem
 
                # Load all locations
-               for n2 in n.n_locations.n_list.children do
+               for n2 in n.n_locations.n_list.as(not null).children do
                        var e = new Location(locations.length, n2.n_name.text, n2.n_x.text.to_f, n2.n_y.text.to_f)
                        assert not locations.has_key(e.name)
                        locations[e.name] = e
@@ -97,7 +97,7 @@ class PlanProblem
 
                # Load all roads
                var nbr = 0
-               for n2 in n.n_roads.n_list.children do
+               for n2 in n.n_roads.n_list.as(not null).children do
                        var o = locations.get_or_null(n2.n_orig.text)
                        var d = locations.get_or_null(n2.n_dest.text)
                        assert o != null and d != null
@@ -132,7 +132,7 @@ class PlanProblem
 
                # Load the robot
                var robot = null
-               for n2 in n.n_robots.n_list.children do
+               for n2 in n.n_robots.n_list.as(not null).children do
                        var name = n2.n_name.text
                        robot = locations.get_or_null(n2.n_emplacement.text)
                        assert name == robot_name and robot != null
@@ -142,7 +142,7 @@ class PlanProblem
 
                # Load the parcels
                var parcel_locations = new Array[nullable Location]
-               for n2 in n.n_parcels.n_list.children do
+               for n2 in n.n_parcels.n_list.as(not null).children do
                        var name = n2.n_name.text
                        var e = locations.get_or_null(n2.n_emplacement.text)
                        assert e != null
@@ -155,7 +155,7 @@ class PlanProblem
                print "# {parcels.length} parcels"
 
                # Load the goal of parcels
-               for n2 in n.n_goal.n_list.children do
+               for n2 in n.n_goal.n_list.as(not null).children do
                        var parcel = parcel_by_name.get_or_null(n2.n_name.text)
                        var e = locations.get_or_null(n2.n_emplacement.text)
                        assert parcel != null and e != null
@@ -179,7 +179,7 @@ class PlanProblem
                        print n.message
                        exit 1
                end
-               n = n.children.first.children.first.as(not null)
+               n = n.children.first.as(not null).children.first.as(not null)
                if n isa Nproblem then
                        print "Error: expected a plan, got a problem."
                        exit 1
@@ -189,7 +189,7 @@ class PlanProblem
                var res = new Plan(self)
                var e = initial_state
                var cost = 0.0
-               for n2 in n.n_actions.children do
+               for n2 in n.n_actions.as(not null).children do
                        if n2 isa Naction_load then
                                var parcel = parcel_by_name.get_or_null(n2.n_parcel.text)
                                assert parcel != null
index d4330ba..66cbfb0 100644 (file)
@@ -21,12 +21,9 @@ redef class Int
        # Calculate the self-th element of the fibonacci sequence.
        fun fibonacci: Int
        do
-               if self < 2 then
-                       return 1
-               else
-                       return (self-2).fibonacci + (self-1).fibonacci
-               end
-       end 
+               if self < 2 then return self
+               return (self-2).fibonacci + (self-1).fibonacci
+       end
 end
 
 # Print usage and exit.
index c5caa4b..0d2d24c 100644 (file)
@@ -5,13 +5,14 @@ The framework provides services to manage common needs of modern mobile applicat
 * Life-cycle
 * User interface
 * Persistence
+* Async HTTP requests
 * Package metadata
 * Compilation and packaging
 
 The features offered by _app.nit_ are common to all platforms, but
 may not be available on all devices.
 
-## Application Life-Cycle
+# Application Life-Cycle
 
 The _app.nit_ application life-cycle is compatible with all target platforms.
 It relies on the following sequence of events, represented here by their callback method name:
@@ -44,7 +45,7 @@ The `App` instance is the first to be notified of these events.
 Other UI elements, from the `ui` submodule, are notified of the same events using a simple depth first visit.
 So all UI elements can react separately to live-cycle events.
 
-## User Interface
+# User Interface
 
 The `app::ui` module defines an abstract API to build a portable graphical application.
 The API is composed of interactive `Control`s, visible `View`s and an active `Window`.
@@ -65,21 +66,20 @@ So there is two ways  to customize the behavior on a given event:
 
 * Add an observer to a `Button` instance, and implement `on_event` in the observer.
 
-### Usage Example
+## Usage Example
 
 The calculator example (at `../../examples/calculator/src/calculator.nit`) is a concrete,
 simple and complete use of the _app.nit_ portable UI.
 
-### Platform-specific UI
+## Platform-specific UI
 
 You can go beyond the portable UI API of _app.nit_ by using the natives services of a platform.
 
 The suggested approach is to use platform specific modules to customize the application on a precise platform.
-This module redefine `Window::on_start` to call the native language of the platform and setup a native UI.
+See the calculator example for an adaptation of the UI on Android,
+the interesting module is in this repository at ../../examples/calculator/src/android_calculator.nit
 
-_TODO complete description and add concrete examples_
-
-## Persistent State with data\_store
+# Persistent State with data\_store
 
 _app.nit_ offers the submodule `app::data_store` to easily save the application state and user preferences.
 The service is accessible by the method `App::data_store`. The `DataStore` itself defines 2 methods:
@@ -90,7 +90,7 @@ Pass `null` to clear the value associated to a key.
 * `DataStore::[]` returns the object associated to a `String` key.
 It returns `null` if nothing is associated to the key.
 
-### Usage Example
+## Usage Example
 
 ~~~
 import app::data_store
@@ -123,7 +123,14 @@ redef class App
 end
 ~~~
 
-## Metadata annotations
+# Async HTTP request
+
+The module `app::http_request` provides services to execute asynchronous HTTP request.
+The class `AsyncHttpRequest` hides the complex parallel logic and
+lets the user implement methods acting only on the UI thread.
+See the documentation of `AsyncHttpRequest` for more information.
+
+# Metadata annotations
 
 The _app.nit_ framework defines three annotations to customize the application package.
 
@@ -142,7 +149,7 @@ The _app.nit_ framework defines three annotations to customize the application p
   The special function `git_revision` will use the prefix of the hash of the latest git commit.
   By default, the version is 0.1.
 
-### Usage Example
+## Usage Example
 
 ~~~
 module my_module is
@@ -152,33 +159,38 @@ module my_module is
 end
 ~~~
 
-## Compiling and Packaging an Application
+# Compiling and Packaging an Application
 
 The Nit compiler detects the target platform from the importations and generates the appropriate application format and package.
 
 Applications using only the portable services of _app.nit_ require some special care at compilation.
 Such an application, let's say `calculator.nit`, does not depend on a specific platform and use the portable UI.
-The target platform must be specifed to the compiler for it to produce the correct application package.
+The target platform must be specified to the compiler for it to produce the correct application package.
 There is two main ways to achieve this goal:
 
-* The the mixin option (`-m path`) loads an additionnal module before compiling.
+* The mixin option (`-m module`) imports an additional module before compiling.
   It can be used to load platform specific implementations of the _app.nit_ portable UI.
 
   ~~~
   # GNU/Linux version, using GTK
-  nitc calculator.nit -m NIT_DIR/lib/linux/ui.nit
+  nitc calculator.nit -m linux
 
   # Android version
-  nitc calculator.nit -m NIT_DIR/lib/android/ui/
+  nitc calculator.nit -m android
+
+  # iOS version
+  nitc calculator.nit -m ios
   ~~~
 
 * A common alternative for larger projects is to use platform specific modules.
-  Continuing with the `calculator.nit` example, it can be accompagnied by the module `calculator_linux.nit`.
-  This module imports both `calculator` and `linux::ui`, and can also use other GNU/Linux specific code.
+  Continuing with the calculator example, it is adapted for Android by the module `android_calculator.nit`.
+  This module imports both `calculator` and `android`, it can then use Android specific code.
 
   ~~~
-  module calculator_linux
+  module android_calculator
 
   import calculator
-  import linux::ui
+  import android
+
+  # ...
   ~~~
index 9a5feca..694ba33 100644 (file)
@@ -28,14 +28,21 @@ redef class App
        fun run_on_ui_thread(task: Task) is abstract
 end
 
-# Thread executing an HTTP request and deserializing JSON asynchronously
+# Thread executing an HTTP request asynchronously
 #
-# This class defines four methods acting on the main/UI thread,
-# they should be implemented as needed:
-# * before
-# * on_load
-# * on_fail
-# * after
+# The request is sent to `rest_server_uri / rest_action`.
+#
+# If `deserialize_json`, the default behavior, the response is deserialized from JSON
+#
+# If `delay > 0.0`, sending the reqest is delayed by the given `delay` in seconds.
+# It can be used to delay resending a request on error.
+#
+# Four callback methods act on the main/UI thread,
+# they should be implemented as needed in subclasses:
+# * `before`
+# * `on_load`
+# * `on_fail`
+# * `after`
 class AsyncHttpRequest
        super Thread
 
index d5c48c1..88a75fb 100644 (file)
@@ -1,6 +1,6 @@
 [package]
 name=app
-tags=lib
+tags=lib,mobile
 maintainer=Alexis Laferrière <alexis.laf@xymus.net>
 license=Apache-2.0
 [upstream]
index 5cffbec..5ed911f 100644 (file)
@@ -457,7 +457,9 @@ interface Set[E]
                var res = 23 + length
                # Note: the order of the elements must not change the hash value.
                # So, unlike usual hash functions, the accumulator is not combined with itself.
-               for e in self do res += e.hash
+               for e in self do
+                       if e != null then res += e.hash
+               end
                return res
        end
 
index 79e7a70..f01bbb5 100644 (file)
@@ -321,7 +321,7 @@ class Array[E]
        redef fun [](index)
        do
                assert index: index >= 0 and index < _length
-               return _items[index]
+               return _items.as(not null)[index]
        end
 
        redef fun []=(index, item)
@@ -333,7 +333,7 @@ class Array[E]
                if _length <= index then
                        _length = index + 1
                end
-               _items[index] = item
+               _items.as(not null)[index] = item
        end
 
        redef fun add(item)
@@ -343,7 +343,7 @@ class Array[E]
                        enlarge(l + 1)
                end
                _length = l + 1
-               _items[l] = item
+               _items.as(not null)[l] = item
        end
 
        # Slight optimization for arrays
@@ -358,13 +358,13 @@ class Array[E]
                if items isa Array[E] then
                        var k = 0
                        while l < nl do
-                               _items[l] = items._items[k]
+                               _items.as(not null)[l] = items._items.as(not null)[k]
                                l += 1
                                k += 1
                        end
                else
                        for item in items do
-                               _items[l] = item
+                               _items.as(not null)[l] = item
                                l += 1
                        end
                end
@@ -404,7 +404,7 @@ class Array[E]
                if cap <= c then return
                while c <= cap do c = c * 2 + 2
                var a = new NativeArray[E](c)
-               if _capacity > 0 then _items.copy_to(a, _length)
+               if _capacity > 0 then _items.as(not null).copy_to(a, _length)
                _items = a
                _capacity = c
        end
@@ -474,9 +474,10 @@ class Array[E]
                # Efficient implementation
                var l = length
                if l != o.length then return false
+               if l == 0 then return true
                var i = 0
-               var it = _items
-               var oit = o._items
+               var it = _items.as(not null)
+               var oit = o._items.as(not null)
                while i < l do
                        if it[i] != oit[i] then return false
                        i += 1
@@ -921,10 +922,11 @@ class ArrayCmp[E: nullable Comparable]
 
        redef fun <=>(o)
        do
-               var it = _items
-               var oit = o._items
                var i = 0
                var l = length
+               if l == 0 then return 0
+               var it = _items.as(not null)
+               var oit = o._items.as(not null)
                var ol = o.length
                var len
                if l < ol then len = l else len = ol
index ab81a37..241238b 100644 (file)
@@ -21,21 +21,21 @@ class List[E]
 
 # Access
 
-       redef fun [](index) do return get_node(index).item
+       redef fun [](index) do return get_node(index).as(not null).item
 
-       redef fun []=(index, item) do get_node(index).item = item
+       redef fun []=(index, item) do get_node(index).as(not null).item = item
 
        # O(1)
-       redef fun first do return _head.item
+       redef fun first do return _head.as(not null).item
 
        # O(1)
-       redef fun first=(e) do _head.item = e
+       redef fun first=(e) do _head.as(not null).item = e
 
        # O(1)
-       redef fun last do return _tail.item
+       redef fun last do return _tail.as(not null).item
 
        # O(1)
-       redef fun last=(e) do _tail.item = e
+       redef fun last=(e) do _tail.as(not null).item = e
 
 # Queries
 
@@ -87,11 +87,12 @@ class List[E]
        redef fun push(e)
        do
                var node = new ListNode[E](e)
-               if _tail == null then
+               var tail = _tail
+               if tail == null then
                        _head = node
                else
-                       _tail.next = node
-                       node.prev = _tail
+                       tail.next = node
+                       node.prev = tail
                end
                _tail = node
                length += 1
@@ -101,11 +102,12 @@ class List[E]
        redef fun unshift(e)
        do
                var node = new ListNode[E](e)
-               if _head == null then
+               var head = _head
+               if head == null then
                        _tail = node
                else
-                       node.next = _head
-                       _head.prev = node
+                       node.next = head
+                       head.prev = node
                end
                _head = node
                length += 1
@@ -127,11 +129,12 @@ class List[E]
        # O(1)
        fun link(l: List[E])
        do
-               if _tail == null then
+               var tail = _tail
+               if tail == null then
                        _head = l._head
                else if l._head != null then
-                       _tail.next = l._head
-                       _tail.next.prev = _tail
+                       tail.next = l._head
+                       tail.next.as(not null).prev = tail
                end
                _tail = l._tail
                length += l.length
@@ -143,13 +146,13 @@ class List[E]
        # O(1)
        redef fun pop
        do
-               var node = _tail
+               var node = _tail.as(not null)
                _tail = node.prev
                node.prev = null
                if _tail == null then
                        _head = null
                else
-                       _tail.next = null
+                       _tail.as(not null).next = null
                end
                length -= 1
                return node.item
@@ -158,13 +161,13 @@ class List[E]
        # O(1)
        redef fun shift
        do
-               var node = _head
+               var node = _head.as(not null)
                _head = node.next
                node.next = null
                if _head == null then
                        _tail = null
                else
-                       _head.prev = null
+                       _head.as(not null).prev = null
                end
                length -= 1
                return node.item
@@ -236,14 +239,14 @@ class List[E]
                        if node.next == null then
                                _tail = null
                        else
-                               node.next.prev = null
+                               node.next.as(not null).prev = null
                        end
                else if node.next == null then
                        _tail = node.prev
-                       node.prev.next = null
+                       node.prev.as(not null).next = null
                else
-                       node.prev.next = node.next
-                       node.next.prev = node.prev
+                       node.prev.as(not null).next = node.next
+                       node.next.as(not null).prev = node.prev
                end
        end
 
@@ -266,16 +269,16 @@ end
 # This is the iterator class of List
 class ListIterator[E]
        super IndexedIterator[E]
-       redef fun item do return _node.item
+       redef fun item do return _node.as(not null).item
 
        # Set item `e` at self `index`.
-       fun item=(e: E) do _node.item = e
+       fun item=(e: E) do _node.as(not null).item = e
 
        redef fun is_ok do return not _node == null
 
        redef fun next
        do
-               _node = _node.next
+               _node = _node.as(not null).next
                _index += 1
        end
 
@@ -312,7 +315,7 @@ private class ListReverseIterator[E]
 
        redef fun next
        do
-               _node = _node.prev
+               _node = _node.as(not null).prev
                _index -= 1
        end
 
index ac13301..6a58480 100644 (file)
@@ -86,6 +86,6 @@ class MaybeError[V, E: Error]
        redef fun to_s do
                var e = maybe_error
                if e != null then return e.to_s
-               return value.to_s
+               return value.as(not null).to_s
        end
 end
index 76d9d63..6c03c7f 100644 (file)
@@ -98,6 +98,7 @@ class Process
                var args = new FlatBuffer
                var l = 1 # Number of elements in args
                args.append(command)
+               var arguments = self.arguments
                if arguments != null then
                        for a in arguments do
                                args.add('\0')
index 7f1d481..8bf8078 100644 (file)
@@ -49,23 +49,24 @@ abstract class FileStream
        # Return null in case of error
        fun file_stat: nullable FileStat
        do
-               var stat = _file.file_stat
+               var stat = _file.as(not null).file_stat
                if stat.address_is_null then return null
                return new FileStat(stat)
        end
 
        # File descriptor of this file
-       fun fd: Int do return _file.fileno
+       fun fd: Int do return _file.as(not null).fileno
 
        redef fun close
        do
-               if _file == null then return
-               if _file.address_is_null then
+               var file = _file
+               if file == null then return
+               if file.address_is_null then
                        if last_error != null then return
                        last_error = new IOError("Cannot close unopened file")
                        return
                end
-               var i = _file.io_close
+               var i = file.io_close
                if i != 0 then
                        last_error = new IOError("Close failed due to error {sys.errno.strerror}")
                end
@@ -83,7 +84,7 @@ abstract class FileStream
        # * `buffer_mode_none`
        fun set_buffering_mode(buf_size, mode: Int) do
                if buf_size <= 0 then buf_size = 512
-               if _file.set_buffering_type(buf_size, mode) != 0 then
+               if _file.as(not null).set_buffering_type(buf_size, mode) != 0 then
                        last_error = new IOError("Error while changing buffering type for FileStream, returned error {sys.errno.strerror}")
                end
        end
@@ -105,10 +106,10 @@ class FileReader
        #     assert l == f.read_line
        fun reopen
        do
-               if not eof and not _file.address_is_null then close
+               if not eof and not _file.as(not null).address_is_null then close
                last_error = null
-               _file = new NativeFile.io_open_read(path.to_cstring)
-               if _file.address_is_null then
+               _file = new NativeFile.io_open_read(path.as(not null).to_cstring)
+               if _file.as(not null).address_is_null then
                        last_error = new IOError("Cannot open `{path.as(not null)}`: {sys.errno.strerror}")
                        end_reached = true
                        return
@@ -126,8 +127,8 @@ class FileReader
 
        redef fun fill_buffer
        do
-               var nb = _file.io_read(_buffer, _buffer_capacity)
-               if last_error == null and _file.ferror then
+               var nb = _file.as(not null).io_read(_buffer, _buffer_capacity)
+               if last_error == null and _file.as(not null).ferror then
                        last_error = new IOError("Cannot read `{path.as(not null)}`: {sys.errno.strerror}")
                        end_reached = true
                end
@@ -158,7 +159,7 @@ class FileReader
                self.path = path
                prepare_buffer(100)
                _file = new NativeFile.io_open_read(path.to_cstring)
-               if _file.address_is_null then
+               if _file.as(not null).address_is_null then
                        last_error = new IOError("Cannot open `{path}`: {sys.errno.strerror}")
                        end_reached = true
                end
@@ -171,7 +172,7 @@ class FileReader
                self.path = ""
                prepare_buffer(1)
                _file = fd.fd_to_stream(read_only)
-               if _file.address_is_null then
+               if _file.as(not null).address_is_null then
                        last_error = new IOError("Error: Converting fd {fd} to stream failed with '{sys.errno.strerror}'")
                        end_reached = true
                end
@@ -223,13 +224,13 @@ class FileWriter
                        last_error = new IOError("Cannot write to non-writable stream")
                        return
                end
-               if _file.address_is_null then
+               if _file.as(not null).address_is_null then
                        last_error = new IOError("Writing on a null stream")
                        _is_writable = false
                        return
                end
 
-               var err = _file.write_byte(value)
+               var err = _file.as(not null).write_byte(value)
                if err != 1 then
                        # Big problem
                        last_error = new IOError("Problem writing a byte: {err}")
@@ -251,12 +252,12 @@ class FileWriter
                        last_error = new IOError("Cannot write to non-writable stream")
                        return
                end
-               if _file.address_is_null then
+               if _file.as(not null).address_is_null then
                        last_error = new IOError("Writing on a null stream")
                        _is_writable = false
                        return
                end
-               var err = _file.io_write(native, from, len)
+               var err = _file.as(not null).io_write(native, from, len)
                if err != len then
                        # Big problem
                        last_error = new IOError("Problem in writing : {err} {len} \n")
@@ -269,7 +270,7 @@ class FileWriter
                _file = new NativeFile.io_open_write(path.to_cstring)
                self.path = path
                _is_writable = true
-               if _file.address_is_null then
+               if _file.as(not null).address_is_null then
                        last_error = new IOError("Cannot open `{path}`: {sys.errno.strerror}")
                        is_writable = false
                end
@@ -280,7 +281,7 @@ class FileWriter
                self.path = ""
                _file = fd.fd_to_stream(wipe_write)
                _is_writable = true
-                if _file.address_is_null then
+                if _file.as(not null).address_is_null then
                         last_error = new IOError("Error: Opening stream from file descriptor {fd} failed with '{sys.errno.strerror}'")
                        _is_writable = false
                end
index 0f8325f..7b51e2a 100644 (file)
@@ -169,6 +169,16 @@ redef class Int
                end
                return res
        end
+
+       # Is `self` a power of two ?
+       #
+       # ~~~nit
+       # assert not 3.is_pow2
+       # assert 2.is_pow2
+       # assert 1.is_pow2
+       # assert not 0.is_pow2
+       # ~~~
+       fun is_pow2: Bool do return self != 0 and (self & self - 1) == 0
 end
 
 redef class Byte
index 917ec4f..0ac109c 100644 (file)
@@ -183,7 +183,7 @@ class Regex
        # Cache of a single `regmatch_t` to prevent many calls to `malloc`
        private var native_match: NativeMatchArray is lazy do
                native_match_is_init = true
-               return new NativeMatchArray.malloc(native.re_nsub+1)
+               return new NativeMatchArray.malloc(native.as(not null).re_nsub+1)
        end
 
        private var native_match_is_init = false
index d7dfa48..e07e0d4 100644 (file)
@@ -590,10 +590,13 @@ abstract class Text
                return res.to_s
        end
 
-       # Escape " \ ' and non printable characters using the rules of literal C strings and characters
+       # Escape `"` `\` `'`, trigraphs and non printable characters using the rules of literal C strings and characters
        #
-       #     assert "abAB12<>&".escape_to_c         == "abAB12<>&"
+       #     assert "abAB12<>&".escape_to_c       == "abAB12<>&"
        #     assert "\n\"'\\".escape_to_c         == "\\n\\\"\\'\\\\"
+       #     assert "allo???!".escape_to_c        == "allo??\\?!"
+       #     assert "??=??/??'??(??)".escape_to_c == "?\\?=?\\?/??\\'?\\?(?\\?)"
+       #     assert "??!??<??>??-".escape_to_c    == "?\\?!?\\?<?\\?>?\\?-"
        #
        # Most non-printable characters (bellow ASCII 32) are escaped to an octal form `\nnn`.
        # Three digits are always used to avoid following digits to be interpreted as an element
@@ -617,6 +620,24 @@ abstract class Text
                                b.append("\\\'")
                        else if c == '\\' then
                                b.append("\\\\")
+                       else if c == '?' then
+                               # Escape if it is the last question mark of a ANSI C trigraph.
+                               var j = i + 1
+                               if j < length then
+                                       var next = chars[j]
+                                       # We ignore `??'` because it will be escaped as `??\'`.
+                                       if
+                                               next == '!' or
+                                               next == '(' or
+                                               next == ')' or
+                                               next == '-' or
+                                               next == '/' or
+                                               next == '<' or
+                                               next == '=' or
+                                               next == '>'
+                                       then b.add('\\')
+                               end
+                               b.add('?')
                        else if c.code_point < 32 then
                                b.add('\\')
                                var oct = c.code_point.to_base(8)
@@ -640,6 +661,7 @@ abstract class Text
        # The result might no be legal in C but be used in other languages
        #
        #     assert "ab|\{\}".escape_more_to_c("|\{\}") == "ab\\|\\\{\\\}"
+       #     assert "allo???!".escape_more_to_c("")     == "allo??\\?!"
        fun escape_more_to_c(chars: String): String
        do
                var b = new Buffer
@@ -1360,30 +1382,19 @@ abstract class String
        # Letters that follow a letter are lowercased
        # Letters that follow a non-letter are upcased.
        #
+       # If `keep_upper = true`, already uppercase letters are not lowercased.
+       #
        # SEE : `Char::is_letter` for the definition of letter.
        #
        #     assert "jAVASCRIPT".capitalized == "Javascript"
        #     assert "i am root".capitalized == "I Am Root"
        #     assert "ab_c -ab0c ab\nc".capitalized == "Ab_C -Ab0C Ab\nC"
-       fun capitalized: SELFTYPE do
+       #     assert "preserve my ACRONYMS".capitalized(keep_upper=true) == "Preserve My ACRONYMS"
+       fun capitalized(keep_upper: nullable Bool): SELFTYPE do
                if length == 0 then return self
 
                var buf = new Buffer.with_cap(length)
-
-               var curr = chars[0].to_upper
-               var prev = curr
-               buf[0] = curr
-
-               for i in [1 .. length[ do
-                       prev = curr
-                       curr = self[i]
-                       if prev.is_letter then
-                               buf[i] = curr.to_lower
-                       else
-                               buf[i] = curr.to_upper
-                       end
-               end
-
+               buf.capitalize(keep_upper=keep_upper, src=self)
                return buf.to_s
        end
 end
@@ -1478,6 +1489,13 @@ abstract class Buffer
        # Letters that follow a letter are lowercased
        # Letters that follow a non-letter are upcased.
        #
+       # If `keep_upper = true`, uppercase letters are not lowercased.
+       #
+       # When `src` is specified, this method reads from `src` instead of `self`
+       # but it still writes the result to the beginning of `self`.
+       # This requires `self` to have the capacity to receive all of the
+       # capitalized content of `src`.
+       #
        # SEE: `Char::is_letter` for the definition of a letter.
        #
        #     var b = new FlatBuffer.from("jAVAsCriPt")
@@ -1489,16 +1507,32 @@ abstract class Buffer
        #     b = new FlatBuffer.from("ab_c -ab0c ab\nc")
        #     b.capitalize
        #     assert b == "Ab_C -Ab0C Ab\nC"
-       fun capitalize do
+       #
+       #     b = new FlatBuffer.from("12345")
+       #     b.capitalize(src="foo")
+       #     assert b == "Foo45"
+       #
+       #     b = new FlatBuffer.from("preserve my ACRONYMS")
+       #     b.capitalize(keep_upper=true)
+       #     assert b == "Preserve My ACRONYMS"
+       fun capitalize(keep_upper: nullable Bool, src: nullable Text) do
+               src = src or else self
+               var length = src.length
                if length == 0 then return
-               var c = self[0].to_upper
+               keep_upper = keep_upper or else false
+
+               var c = src[0].to_upper
                self[0] = c
                var prev = c
                for i in [1 .. length[ do
                        prev = c
-                       c = self[i]
+                       c = src[i]
                        if prev.is_letter then
-                               self[i] = c.to_lower
+                               if keep_upper then
+                                       self[i] = c
+                               else
+                                       self[i] = c.to_lower
+                               end
                        else
                                self[i] = c.to_upper
                        end
@@ -2102,7 +2136,12 @@ end
 # see `alpha_comparator`
 private class AlphaComparator
        super Comparator
-       redef fun compare(a, b) do return a.to_s <=> b.to_s
+       redef fun compare(a, b) do
+               if a == b then return 0
+               if a == null then return -1
+               if b == null then return 1
+               return a.to_s <=> b.to_s
+       end
 end
 
 # Stateless comparator that naively use `to_s` to compare things.
index 9e70321..6273609 100644 (file)
@@ -225,6 +225,22 @@ redef class FlatText
                                req_esc += 1
                        else if c == 0x5Cu8 then
                                req_esc += 1
+                       else if c == 0x3Fu8 then
+                               var j = pos + 1
+                               if j < length then
+                                       var next = its[j]
+                                       # We ignore `??'` because it will be escaped as `??\'`.
+                                       if
+                                               next == 0x21u8 or
+                                               next == 0x28u8 or
+                                               next == 0x29u8 or
+                                               next == 0x2Du8 or
+                                               next == 0x2Fu8 or
+                                               next == 0x3Cu8 or
+                                               next == 0x3Du8 or
+                                               next == 0x3Eu8
+                                       then req_esc += 1
+                               end
                        else if c < 32u8 then
                                req_esc += 3
                        end
@@ -280,6 +296,27 @@ redef class FlatText
                                nns[opos] = 0x5Cu8
                                nns[opos + 1] = 0x5Cu8
                                opos += 2
+                       else if c == 0x3Fu8 then
+                               var j = pos + 1
+                               if j < length then
+                                       var next = its[j]
+                                       # We ignore `??'` because it will be escaped as `??\'`.
+                                       if
+                                               next == 0x21u8 or
+                                               next == 0x28u8 or
+                                               next == 0x29u8 or
+                                               next == 0x2Du8 or
+                                               next == 0x2Fu8 or
+                                               next == 0x3Cu8 or
+                                               next == 0x3Du8 or
+                                               next == 0x3Eu8
+                                       then
+                                               nns[opos] = 0x5Cu8
+                                               opos += 1
+                                       end
+                               end
+                               nns[opos] = 0x3Fu8
+                               opos += 1
                        else if c < 32u8 then
                                nns[opos] = 0x5Cu8
                                nns[opos + 1] = 0x30u8
index 0bf97b8..f57b33d 100644 (file)
@@ -821,7 +821,7 @@ private class ReverseRopeSubstrings
        redef fun next do
                if pos < 0 then return
                var curr = iter.prev
-               var currit = curr.node
+               var currit = curr.as(not null).node
                while curr != null do
                        currit = curr.node
                        if not currit isa Concat then
@@ -928,14 +928,14 @@ private class RopeSubstrings
        redef fun next do
                pos += str.length
                if pos > max then return
-               var it = iter.prev
+               var it = iter.prev.as(not null)
                var rnod = it.node
                loop
                        if not rnod isa Concat then
                                it.ldone = true
                                it.rdone = true
                                str = rnod.as(FlatString)
-                               iter = it.as(not null)
+                               iter = it
                                break
                        end
                        if not it.ldone then
@@ -947,7 +947,7 @@ private class RopeSubstrings
                                rnod = rnod._right
                                it = new RopeCharIteratorPiece(rnod, false, false, it)
                        else
-                               it = it.prev
+                               it = it.prev.as(not null)
                                rnod = it.node
                                continue
                        end
diff --git a/lib/core/text/test_abstract_text.nit b/lib/core/text/test_abstract_text.nit
new file mode 100644 (file)
index 0000000..c67a991
--- /dev/null
@@ -0,0 +1,61 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT.  This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without  even  the implied warranty of  MERCHANTABILITY or  FITNESS FOR A
+# PARTICULAR PURPOSE.  You can modify it is you want,  provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You  are  allowed  to  redistribute it and sell it, alone or is a part of
+# another product.
+
+module test_abstract_text is test_suite
+
+import test_suite
+import text
+intrude import ropes
+
+class TestText
+       super TestSuite
+
+       private var factories: Collection[TextFactory] = [
+               new ConcatFactory,
+               new RopeBufferFactory,
+               new FlatBufferFactory
+       : TextFactory]
+
+       fun test_escape_to_c do
+               for f in factories do
+                       assert f.create("abAB12<>&").escape_to_c       == "abAB12<>&"
+                       assert f.create("\n\"'\\").escape_to_c         == "\\n\\\"\\'\\\\"
+                       assert f.create("allo???!").escape_to_c        == "allo??\\?!"
+                       assert f.create("??=??/??'??(??)").escape_to_c == "?\\?=?\\?/??\\'?\\?(?\\?)"
+                       assert f.create("??!??<??>??-").escape_to_c    == "?\\?!?\\?<?\\?>?\\?-"
+               end
+       end
+end
+
+# A factory that creates instances of a particular implementation of `Text`
+interface TextFactory
+
+       # Create a `Text` instance from the specified string
+       fun create(s: String): Text is abstract
+end
+
+
+class ConcatFactory
+       super TextFactory
+
+       redef fun create(s) do return new Concat("", s)
+end
+
+class RopeBufferFactory
+       super TextFactory
+
+       redef fun create(s) do return new RopeBuffer.from(s)
+end
+
+class FlatBufferFactory
+       super TextFactory
+
+       redef fun create(s) do return new FlatBuffer.from(s)
+end
index 37fa87e..315d76f 100644 (file)
@@ -21,15 +21,19 @@ module htcpcp_server
 
 import nitcorn
 
+# Nitcorn Action used to answer requests.
 class HTCPCPAction
        super Action
+
+       # Brewing status.
        var brewing = false
-  var is_teapot = false
+
+       # Teapot status.
+       var is_teapot = false
 
        redef fun answer(http_request, turi) do
                var message: String
                var method = http_request.method
-               var headers = http_request.header
                var response: HttpResponse
 
                if is_teapot == true then
@@ -77,10 +81,13 @@ class HTCPCPAction
        end
 end
 
-
+# Nitcorn server.
 class HTCPCServer
+
+       # Port to listen to.
        var port: Int
 
+       # Start listening.
        fun run do
                var vh = new VirtualHost("localhost:{port}")
                vh.routes.add new Route("/", new HTCPCPAction)
index 9e94493..1833fca 100644 (file)
@@ -24,7 +24,7 @@ import core
 
 # A request received over HTTP, is build by `HttpRequestParser`
 class HttpRequest
-       private init do end
+       private init is old_style_init do end
 
        # HTTP protocol version
        var http_version: String
@@ -114,6 +114,7 @@ class HttpRequestParser
        # Words of the first line
        private var first_line = new Array[String]
 
+       # Parse the `first_line`, `header_fields` and `body` of `full_request`.
        fun parse_http_request(full_request: String): nullable HttpRequest
        do
                clear_data
index 3d0fd33..08d2968 100644 (file)
@@ -97,7 +97,8 @@ class HttpStatusCodes
        # All know code and their message
        var codes = new HashMap[Int, String]
 
-       protected init do insert_status_codes
+       # Init the status `codes` list.
+       protected init is old_style_init do insert_status_codes
 
        # Get the message associated to the status `code`, return `null` in unknown
        fun [](code: Int): nullable String
index 37dd9db..305f314 100644 (file)
@@ -20,6 +20,8 @@ module media_types
 
 # Map of known MIME types
 class MediaTypes
+
+       # MIME types by extensions.
        protected var types = new HashMap[String, String]
 
        # Get the type/subtype associated to a file extension `ext`
@@ -106,4 +108,5 @@ class MediaTypes
        end
 end
 
+# MIME types list.
 fun media_types: MediaTypes do return once new MediaTypes
index 12db3df..bc59692 100644 (file)
@@ -17,8 +17,4 @@
 NITUNIT=../../bin/nitunit
 
 check:
-       $(NITUNIT) README.md
-       $(NITUNIT) pop_routes.nit
-       $(NITUNIT) pop_handlers.nit
-       $(NITUNIT) popcorn.nit
-       cd tests; make check
+       $(NITUNIT) .
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+module test_example_angular is test_suite
+
+import pop_tests
 import example_angular
-import base_tests
 
-class TestClient
-       super ClientThread
+class TestExampleAngular
+       super TestPopcorn
 
-       redef fun main do
+       redef fun client_test do
                system "curl -s {host}:{port}/counter"
                system "curl -s {host}:{port}/counter -X POST"
                system "curl -s {host}:{port}/counter"
                system "curl -s {host}:{port}/not_found" # handled by angular controller
-               return null
        end
-end
-
-var app = new App
-app.use("/counter", new CounterAPI)
-app.use("/*", new StaticHandler("../examples/angular/www/", "index.html"))
-
-var host = test_host
-var port = test_port
 
-var server = new AppThread(host, port, app)
-server.start
-0.1.sleep
-
-var client = new TestClient(host, port)
-client.start
-client.join
-0.1.sleep
-
-exit 0
+       fun test_example_param_route do
+               var app = new App
+               app.use("/counter", new CounterAPI)
+               app.use("/*", new StaticHandler("../examples/angular/www/", "index.html"))
+               run_test(app)
+       end
+end
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+module test_example_post_handler is test_suite
+
+import pop_tests
 import example_post_handler
-import base_tests
 
-class TestClient
-       super ClientThread
+class TestExampleQueryString
+       super TestPopcorn
 
-       redef fun main do
+       redef fun client_test do
                system "curl -s {host}:{port}/ -X POST"
                system "curl -s {host}:{port}/ --data 'user'"
                system "curl -s {host}:{port}/ --data 'user=Morriar'"
                system "curl -s {host}:{port}/ --data 'user=\&order=desc'"
                system "curl -s {host}:{port}/ --data 'user=Morriar\&order=desc'"
-
                system "curl -s {host}:{port}/"
-               return null
        end
-end
-
-var app = new App
-app.use("/", new PostHandler)
-
-var host = test_host
-var port = test_port
 
-var server = new AppThread(host, port, app)
-server.start
-0.1.sleep
-
-var client = new TestClient(host, port)
-client.start
-client.join
-0.1.sleep
-
-exit 0
+       fun test_example_glob_route do
+               var app = new App
+               app.use("/", new PostHandler)
+               run_test(app)
+       end
+end
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+module test_example_query_string is test_suite
+
+import pop_tests
 import example_query_string
-import base_tests
 
-class TestClient
-       super ClientThread
+class TestExampleQueryString
+       super TestPopcorn
 
-       redef fun main do
+       redef fun client_test do
                system "curl -s {host}:{port}/"
                system "curl -s {host}:{port}/?user=Morriar"
                system "curl -s {host}:{port}/?reload"
                system "curl -s {host}:{port}/?foo\\&bar=baz"
                system "curl -s {host}:{port}/?items=10\\&order=asc"
-               return null
        end
-end
-
-var app = new App
-app.use("/", new QueryStringHandler)
-
-var host = test_host
-var port = test_port
 
-var server = new AppThread(host, port, app)
-server.start
-0.1.sleep
-
-var client = new TestClient(host, port)
-client.start
-client.join
-0.1.sleep
-
-exit 0
+       fun test_example_glob_route do
+               var app = new App
+               app.use("/", new QueryStringHandler)
+               run_test(app)
+       end
+end
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+module test_example_hello is test_suite
+
+import pop_tests
 import example_hello
-import base_tests
 
-class TestClient
-       super ClientThread
+class TestExampleHello
+       super TestPopcorn
 
-       redef fun main do
+       redef fun client_test do
                system "curl -s {host}:{port}"
                system "curl -s {host}:{port}/"
                system "curl -s {host}:{port}///////////"
                system "curl -s {host}:{port}/not_found"
                system "curl -s {host}:{port}/not_found/not_found"
-               return null
        end
-end
-
-var app = new App
-app.use("/", new HelloHandler)
-
-var host = test_host
-var port = test_port
 
-var server = new AppThread(host, port, app)
-server.start
-0.1.sleep
-
-var client = new TestClient(host, port)
-client.start
-client.join
-0.1.sleep
-
-exit 0
+       fun test_example_hello do
+               var app = new App
+               app.use("/", new HelloHandler)
+               run_test(app)
+       end
+end
@@ -26,4 +26,4 @@
                <body>
                <h1>404 Not Found</h1>
                </body>
-               </html>
\ No newline at end of file
+               </html>
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+module test_example_advanced_logger is test_suite
+
+import pop_tests
 import example_advanced_logger
-import base_tests
 
-class TestClient
-       super ClientThread
+class TestExampleAdvancedLogger
+       super TestPopcorn
 
-       redef fun main do
+       redef fun client_test do
                system "curl -s {host}:{port}/"
                system "curl -s {host}:{port}/about"
-               return null
        end
-end
-
-var app = new App
-app.use_before("/*", new RequestTimeHandler)
-app.use("/", new HelloHandler)
-app.use_after("/*", new LogHandler)
-
-var host = test_host
-var port = test_port
 
-var server = new AppThread(host, port, app)
-server.start
-0.1.sleep
-
-var client = new TestClient(host, port)
-client.start
-client.join
-0.1.sleep
-
-exit 0
+       fun test_example_param_route do
+               var app = new App
+               app.use_before("/*", new RequestTimeHandler)
+               app.use("/", new HelloHandler)
+               app.use_after("/*", new LogHandler)
+               run_test(app)
+       end
+end
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+module test_example_html_error_handler is test_suite
+
+import pop_tests
 import example_html_error_handler
-import base_tests
 
-class TestClient
-       super ClientThread
+class TestExampleHtmlErrorHandler
+       super TestPopcorn
 
-       redef fun main do
+       redef fun client_test do
                system "curl -s {host}:{port}/"
                system "curl -s {host}:{port}/about"
-               return null
        end
-end
-
-var app = new App
-app.use("/*", new HtmlErrorHandler)
-
-var host = test_host
-var port = test_port
 
-var server = new AppThread(host, port, app)
-server.start
-0.1.sleep
-
-var client = new TestClient(host, port)
-client.start
-client.join
-0.1.sleep
-
-exit 0
+       fun test_example_param_route do
+               var app = new App
+               app.use("/*", new HtmlErrorHandler)
+               run_test(app)
+       end
+end
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+module test_example_simple_error_handler is test_suite
+
+import pop_tests
 import example_simple_error_handler
-import base_tests
 
-class TestClient
-       super ClientThread
+class TestExampleSimpleErrorHandler
+       super TestPopcorn
 
-       redef fun main do
+       redef fun client_test do
                system "curl -s {host}:{port}/"
                system "curl -s {host}:{port}/about"
-               return null
        end
-end
-
-var app = new App
-app.use("/", new HelloHandler)
-app.use("/*", new SimpleErrorHandler)
-
-var host = test_host
-var port = test_port
 
-var server = new AppThread(host, port, app)
-server.start
-0.1.sleep
-
-var client = new TestClient(host, port)
-client.start
-client.join
-0.1.sleep
-
-exit 0
+       fun test_example_param_route do
+               var app = new App
+               app.use("/", new HelloHandler)
+               app.use("/*", new SimpleErrorHandler)
+               run_test(app)
+       end
+end
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+module test_example_simple_logger is test_suite
+
+import pop_tests
 import example_simple_logger
-import base_tests
 
-class TestClient
-       super ClientThread
+class TestExampleSimpleLogger
+       super TestPopcorn
 
-       redef fun main do
+       redef fun client_test do
                system "curl -s {host}:{port}/"
                system "curl -s {host}:{port}/about"
-               return null
        end
-end
-
-var app = new App
-app.use_before("/*", new LogHandler)
-app.use("/", new HelloHandler)
-
-var host = test_host
-var port = test_port
 
-var server = new AppThread(host, port, app)
-server.start
-0.1.sleep
-
-var client = new TestClient(host, port)
-client.start
-client.join
-0.1.sleep
-
-exit 0
+       fun test_example_param_route do
+               var app = new App
+               app.use_before("/*", new LogHandler)
+               app.use("/", new HelloHandler)
+               run_test(app)
+       end
+end
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+module test_example_glob_route is test_suite
+
+import pop_tests
 import example_glob_route
-import base_tests
 
-class TestClient
-       super ClientThread
+class TestExampleGlobRoute
+       super TestPopcorn
 
-       redef fun main do
+       redef fun client_test do
                system "curl -s {host}:{port}/user/Morriar/item/10"
                system "curl -s {host}:{port}/user/Morriar/item/10/"
                system "curl -s {host}:{port}/user/Morriar/item/10/profile"
                system "curl -s {host}:{port}/user/Morriar/item/10/profile/settings"
-
                system "curl -s {host}:{port}/"
                system "curl -s {host}:{port}/not_found"
                system "curl -s {host}:{port}/not_found/not_found"
-               return null
        end
-end
-
-var app = new App
-app.use("/user/:user/item/:item/*", new UserItem)
-
-var host = test_host
-var port = test_port
 
-var server = new AppThread(host, port, app)
-server.start
-0.1.sleep
-
-var client = new TestClient(host, port)
-client.start
-client.join
-0.1.sleep
-
-exit 0
+       fun test_example_glob_route do
+               var app = new App
+               app.use("/user/:user/item/:item/*", new UserItem)
+               run_test(app)
+       end
+end
@@ -39,4 +39,4 @@ Here the item 10 of the use Morriar.
                <body>
                <h1>404 Not Found</h1>
                </body>
-               </html>
\ No newline at end of file
+               </html>
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+module test_example_param_route is test_suite
+
+import pop_tests
 import example_param_route
-import base_tests
 
-class TestClient
-       super ClientThread
+class TestExampleParamRoute
+       super TestPopcorn
 
-       redef fun main do
+       redef fun client_test do
                system "curl -s {host}:{port}/Morriar"
                system "curl -s {host}:{port}//"
-
                system "curl -s {host}:{port}/"
                system "curl -s {host}:{port}/not_found"
                system "curl -s {host}:{port}/not_found/not_found"
-               return null
        end
-end
-
-var app = new App
-app.use("/:user", new UserHome)
-
-var host = test_host
-var port = test_port
 
-var server = new AppThread(host, port, app)
-server.start
-0.1.sleep
-
-var client = new TestClient(host, port)
-client.start
-client.join
-0.1.sleep
-
-exit 0
+       fun test_example_param_route do
+               var app = new App
+               app.use("/:user", new UserHome)
+               run_test(app)
+       end
+end
@@ -35,4 +35,4 @@ Hello not_found
                <body>
                <h1>404 Not Found</h1>
                </body>
-               </html>
\ No newline at end of file
+               </html>
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+module test_example_router is test_suite
+
+import pop_tests
 import example_router
-import base_tests
 
-class HelloClient
-       super ClientThread
+class TestExampleRouter
+       super TestPopcorn
 
-       redef fun main do
+       redef fun client_test do
                system "curl -s {host}:{port}"
                system "curl -s {host}:{port}/"
                system "curl -s {host}:{port}/user"
                system "curl -s {host}:{port}/user/"
                system "curl -s {host}:{port}/user/profile"
-
                system "curl -s {host}:{port}/not_found"
                system "curl -s {host}:{port}/user/not_found"
                system "curl -s {host}:{port}/products/not_found"
-               return null
        end
-end
-
-var user_router = new Router
-user_router.use("/*", new UserLogger)
-user_router.use("/", new UserHome)
-user_router.use("/profile", new UserProfile)
-
-var app = new App
-app.use("/", new AppHome)
-app.use("/user", user_router)
 
-var host = test_host
-var port = test_port
-
-var server = new AppThread(host, port, app)
-server.start
-0.1.sleep
-
-var client = new HelloClient(host, port)
-client.start
-client.join
-0.1.sleep
-
-exit 0
+       fun test_example_router do
+               var user_router = new Router
+               user_router.use("/*", new UserLogger)
+               user_router.use("/", new UserHome)
+               user_router.use("/profile", new UserProfile)
+               var app = new App
+               app.use("/", new AppHome)
+               app.use("/user", user_router)
+               run_test(app)
+       end
+end
@@ -45,4 +45,4 @@ User logged
                <body>
                <h1>404 Not Found</h1>
                </body>
-               </html>
\ No newline at end of file
+               </html>
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+module test_example_session is test_suite
+
+import pop_tests
 import example_session
-import base_tests
 
-class HelloClient
-       super ClientThread
+class TestExampleSession
+       super TestPopcorn
 
-       redef fun main do
+       redef fun client_test do
                system "curl -s {host}:{port}/"
                system "curl -s {host}:{port}/ -X POST"
-
                system "curl -s {host}:{port}/not_found"
                system "curl -s {host}:{port}/user/not_found"
                system "curl -s {host}:{port}/products/not_found"
-               return null
        end
-end
-
-var app = new App
-app.use("/*", new SessionInit)
-app.use("/", new AppLogin)
-
-var host = test_host
-var port = test_port
 
-var server = new AppThread(host, port, app)
-server.start
-0.1.sleep
-
-var client = new HelloClient(host, port)
-client.start
-client.join
-0.1.sleep
-
-exit 0
+       fun test_example_param_route do
+               var app = new App
+               app.use("/*", new SessionInit)
+               app.use("/", new AppLogin)
+               run_test(app)
+       end
+end
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import base_tests
+module test_example_static is test_suite
+
+import pop_tests
 import example_static
 
-class TestClient
-       super ClientThread
+class TestExampleStatic
+       super TestPopcorn
 
-       redef fun main do
+       redef fun client_test do
                system "curl -s {host}:{port}/css/style.css"
                system "curl -s {host}:{port}/js/app.js"
                system "curl -s {host}:{port}/hello.html"
                system "curl -s {host}:{port}/"
-
                system "curl -s {host}:{port}/css/not_found.nit"
                system "curl -s {host}:{port}/static/css/not_found.nit"
                system "curl -s {host}:{port}/not_found.nit"
+       end
 
-               return null
+       fun test_example_param_route do
+               var app = new App
+               app.use("/", new StaticHandler("../examples/static_files/public/"))
+               run_test(app)
        end
 end
-
-var app = new App
-app.use("/", new StaticHandler("../examples/static_files/public/"))
-
-var host = test_host
-var port = test_port
-
-var server = new AppThread(host, port, app)
-server.start
-0.1.sleep
-
-var client = new TestClient(host, port)
-client.start
-client.join
-0.1.sleep
-
-exit 0
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import base_tests
+module test_example_static_default is test_suite
+
+import pop_tests
 import example_static_default
 
-class TestClient
-       super ClientThread
+class TestExampleStaticDefault
+       super TestPopcorn
 
-       redef fun main do
+       redef fun client_test do
                system "curl -s {host}:{port}/css/style.css"
                system "curl -s {host}:{port}/js/app.js"
                system "curl -s {host}:{port}/hello.html"
                system "curl -s {host}:{port}/"
-
                system "curl -s {host}:{port}/css/not_found.nit"
                system "curl -s {host}:{port}/static/css/not_found.nit"
                system "curl -s {host}:{port}/not_found.nit"
+       end
 
-               return null
+       fun test_example_param_route do
+               var app = new App
+               app.use("/", new StaticHandler("../examples/static_files/public/", "default.html"))
+               run_test(app)
        end
 end
-
-var app = new App
-app.use("/", new StaticHandler("../examples/static_files/public/", "default.html"))
-
-var host = test_host
-var port = test_port
-
-var server = new AppThread(host, port, app)
-server.start
-0.1.sleep
-
-var client = new TestClient(host, port)
-client.start
-client.join
-0.1.sleep
-
-exit 0
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import base_tests
+module test_example_static_multiple is test_suite
+
+import pop_tests
 import example_static_multiple
 
-class TestClient
-       super ClientThread
+class TestExampleStaticMultiple
+       super TestPopcorn
 
-       redef fun main do
+       redef fun client_test do
                system "curl -s {host}:{port}/css/style.css"
                system "curl -s {host}:{port}/js/app.js"
                system "curl -s {host}:{port}/hello.html"
                system "curl -s {host}:{port}/"
-
                system "curl -s {host}:{port}/static/css/style.css"
                system "curl -s {host}:{port}/static/js/app.js"
                system "curl -s {host}:{port}/static/hello.html"
                system "curl -s {host}:{port}/static/"
-
                system "curl -s {host}:{port}/css/not_found.nit"
                system "curl -s {host}:{port}/static/css/not_found.nit"
                system "curl -s {host}:{port}/not_found.nit"
+       end
 
-               return null
+       fun test_example_param_route do
+               var app = new App
+               app.use("/", new StaticHandler("../examples/static_files/public/"))
+               app.use("/", new StaticHandler("../examples/static_files/files/"))
+               app.use("/static", new StaticHandler("../examples/static_files/public/"))
+               app.use("/static", new StaticHandler("../examples/static_files/files/"))
+               run_test(app)
        end
 end
-
-var app = new App
-app.use("/", new StaticHandler("../examples/static_files/public/"))
-app.use("/", new StaticHandler("../examples/static_files/files/"))
-app.use("/static", new StaticHandler("../examples/static_files/public/"))
-app.use("/static", new StaticHandler("../examples/static_files/files/"))
-
-var host = test_host
-var port = test_port
-
-var server = new AppThread(host, port, app)
-server.start
-0.1.sleep
-
-var client = new TestClient(host, port)
-client.start
-client.join
-0.1.sleep
-
-exit 0
diff --git a/lib/popcorn/pop_tests.nit b/lib/popcorn/pop_tests.nit
new file mode 100644 (file)
index 0000000..1deaeab
--- /dev/null
@@ -0,0 +1,162 @@
+# 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.
+
+# Popcorn testing services
+#
+# ## Blackbox testing
+#
+# Popcorn allows you to test your apps using nitunit blackbox testing.
+#
+# With blackbox testing you compare the output of your program with a result file.
+#
+# To get started with blackbox testing, create a nitunit test suite and imports
+# the `pop_tests` module.
+#
+# You then need to build the app that will be tested by nitunit as shown in the
+# `test_example_hello` method.
+# Calling `run_test` will automatically set the `host` and `port` used for testing.
+#
+# Redefine the `client_test` method to write your scenario.
+# Here we use `curl` to access some URI on the app.
+#
+# ~~~nitish
+# module test_example_hello is test_suite
+#
+# import pop_tests
+# import example_hello
+#
+# class TestExampleHello
+#      super TestPopcorn
+#
+#      fun test_example_hello do
+#              var app = new App
+#              app.use("/", new HelloHandler)
+#              run_test(app)
+#      end
+#
+#      redef fun client_test do
+#              system "curl -s {host}:{port}"
+#              system "curl -s {host}:{port}/"
+#              system "curl -s {host}:{port}///////////"
+#              system "curl -s {host}:{port}/not_found"
+#              system "curl -s {host}:{port}/not_found/not_found"
+#      end
+# end
+# ~~~
+#
+# The blackbox testing needs a reference result file against wich the test output
+# will be compared.
+# Create your expected result file in `test_example_hello.sav/test_example_hello.res`.
+#
+# Test your app by running nitunit:
+#
+# ~~~bash
+# nitunit ./example_hello.nit
+# ~~~
+#
+# See `examples/hello_world` for the complete example.
+module pop_tests
+
+import test_suite
+import popcorn
+import pthreads
+
+redef class Sys
+
+       # Use localhost for testing
+       var test_host = "localhost"
+
+       # Return a new port for each instance
+       fun test_port: Int do
+               srand
+               return 10000+20000.rand
+       end
+end
+
+# Thread running the App to test.
+class AppThread
+       super Thread
+
+       # Host used by tested App.
+       var host: String
+
+       # Port used by tested App.
+       var port: Int
+
+       # App to test.
+       var app: App
+
+       redef fun main
+       do
+               # Hide testing concept to force nitcorn to actually run
+               "NIT_TESTING".setenv("false")
+               app.quiet = true
+               app.listen(host, port)
+               return null
+       end
+end
+
+# Thread running the test client.
+class ClientThread
+       super Thread
+
+       # Test suite to execute.
+       var test_suite: TestPopcorn
+
+       redef fun main do
+               test_suite.client_test
+               print ""
+               return null
+       end
+end
+
+# TestSuite for Popcorn blackbox testing.
+class TestPopcorn
+       super TestSuite
+
+       # Host used to run App.
+       var host: String = test_host
+
+       # Port used to run App.
+       var port: Int = test_port
+
+       # Run the test suite on the App.
+       fun run_test(app: App) do
+               var server = new AppThread(host, port, app)
+               server.start
+               0.1.sleep
+
+               var client = new ClientThread(self)
+               client.start
+               client.join
+               0.1.sleep
+
+               exit 0
+       end
+
+       # Redefine this method to implement your test scenario.
+       fun client_test do end
+
+       # Regex to catch and hide the port from the output to get consistent results
+       var host_re: Regex = "localhost:\[0-9\]+".to_re
+
+       # Execute a System function.
+       fun system(cmd: String, title: nullable String)
+       do
+               title = title or else cmd
+               title = title.replace(host_re, "localhost:*****")
+               print "\n[Client] {title}"
+               sys.system cmd
+       end
+end
diff --git a/lib/popcorn/test_popcorn.nit b/lib/popcorn/test_popcorn.nit
new file mode 100644 (file)
index 0000000..108eb7a
--- /dev/null
@@ -0,0 +1,97 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2016 Alexandre Terrasa <alexandre@moz-code.org>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module test_popcorn is test_suite
+
+import pop_tests
+import popcorn
+
+class TestHandler
+       super Handler
+
+       var marker: String
+
+       redef fun get(req, res) do res.send marker
+end
+
+class TestPopcornRouter
+       super TestPopcorn
+
+       redef fun client_test do
+               system "curl -s {host}:{port}"
+               system "curl -s {host}:{port}/"
+               system "curl -s {host}:{port}/user"
+               system "curl -s {host}:{port}/user/"
+               system "curl -s {host}:{port}/user/settings"
+               system "curl -s {host}:{port}/products"
+               system "curl -s {host}:{port}/products/"
+               system "curl -s {host}:{port}/products/list"
+               system "curl -s {host}:{port}/not_found"
+               system "curl -s {host}:{port}/user/not_found"
+               system "curl -s {host}:{port}/products/not_found"
+       end
+
+       fun test_router do
+               var app = new App
+               app.use("/", new TestHandler("/"))
+               app.use("/about", new TestHandler("/about"))
+
+               var router1 = new App
+               router1.use("/", new TestHandler("/user"))
+               router1.use("/settings", new TestHandler("/user/settings"))
+               app.use("/user", router1)
+
+               var router2 = new App
+               router2.use("/", new TestHandler("/products"))
+               router2.use("/list", new TestHandler("/products/list"))
+               app.use("/products", router2)
+
+               run_test(app)
+       end
+end
+
+class TestPopcornRoutes
+       super TestPopcorn
+
+       redef fun client_test do
+               system "curl -s {host}:{port}"
+               system "curl -s {host}:{port}/"
+               system "curl -s {host}:{port}/misc"
+               system "curl -s {host}:{port}/misc/foo"
+               system "curl -s {host}:{port}/misc/foo/bar"
+               system "curl -s {host}:{port}/misc/foo/baz"
+               system "curl -s {host}:{port}/user"
+               system "curl -s {host}:{port}/user/"
+               system "curl -s {host}:{port}/user/id"
+               system "curl -s {host}:{port}/user/id/profile"
+               system "curl -s {host}:{port}/user/id/misc/foo"
+               system "curl -s {host}:{port}/user/id/misc/foo/bar"
+               system "curl -s {host}:{port}/user/id/misc/foo/bar/baz"
+               system "curl -s {host}:{port}/not_found"
+               system "curl -s {host}:{port}/user/id/not_found"
+       end
+
+       fun test_routes do
+               var app = new App
+               app.use("/", new TestHandler("/"))
+               app.use("/user", new TestHandler("/user"))
+               app.use("/misc/*", new TestHandler("/misc/everything"))
+               app.use("/user/:id", new TestHandler("/user/id"))
+               app.use("/user/:id/profile", new TestHandler("/user/id/profile"))
+               app.use("/user/:id/misc/*", new TestHandler("/user/id/misc/everything"))
+               run_test(app)
+       end
+end
similarity index 98%
rename from lib/popcorn/tests/res/test_router.res
rename to lib/popcorn/test_popcorn.sav/test_router.res
index 37be3a6..1022411 100644 (file)
@@ -47,4 +47,4 @@
                <body>
                <h1>404 Not Found</h1>
                </body>
-               </html>
\ No newline at end of file
+               </html>
similarity index 99%
rename from lib/popcorn/tests/res/test_routes.res
rename to lib/popcorn/test_popcorn.sav/test_routes.res
index 0c1abfa..58e47d0 100644 (file)
@@ -46,4 +46,4 @@
                <body>
                <h1>404 Not Found</h1>
                </body>
-               </html>
\ No newline at end of file
+               </html>
diff --git a/lib/popcorn/tests/base_tests.nit b/lib/popcorn/tests/base_tests.nit
deleted file mode 100644 (file)
index a56a33d..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import popcorn
-import pthreads
-
-redef class Sys
-       var test_host = "localhost"
-
-       # Return a new port for each instance
-       fun test_port: Int do
-               srand
-               return 10000+20000.rand
-       end
-end
-
-class AppThread
-       super Thread
-
-       var host: String
-       var port: Int
-       var app: App
-
-       redef fun main
-       do
-               # Hide testing concept to force nitcorn to actually run
-               "NIT_TESTING".setenv("false")
-               app.quiet = true
-               app.listen(host, port)
-               return null
-       end
-end
-
-class ClientThread
-       super Thread
-
-       var host: String
-       var port: Int
-
-       redef fun main do return null
-
-       # Regex to catch and hide the port from the output to get consistent results
-       var host_re: Regex = "localhost:\[0-9\]+".to_re
-
-       fun system(cmd: String, title: nullable String)
-       do
-               title = title or else cmd
-               title = title.replace(host_re, "localhost:*****")
-               print "\n[Client] {title}"
-               sys.system cmd
-       end
-end
diff --git a/lib/popcorn/tests/test_router.nit b/lib/popcorn/tests/test_router.nit
deleted file mode 100644 (file)
index 915ce4a..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# Copyright 2016 Alexandre Terrasa <alexandre@moz-code.org>
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import base_tests
-
-class TestHandler
-       super Handler
-
-       var marker: String
-
-       redef fun get(req, res) do res.send marker
-end
-
-class HelloClient
-       super ClientThread
-
-       redef fun main do
-               system "curl -s {host}:{port}"
-               system "curl -s {host}:{port}/"
-               system "curl -s {host}:{port}/user"
-               system "curl -s {host}:{port}/user/"
-               system "curl -s {host}:{port}/user/settings"
-               system "curl -s {host}:{port}/products"
-               system "curl -s {host}:{port}/products/"
-               system "curl -s {host}:{port}/products/list"
-
-               system "curl -s {host}:{port}/not_found"
-               system "curl -s {host}:{port}/user/not_found"
-               system "curl -s {host}:{port}/products/not_found"
-               return null
-       end
-end
-
-var app = new App
-app.use("/", new TestHandler("/"))
-app.use("/about", new TestHandler("/about"))
-
-var router1 = new App
-router1.use("/", new TestHandler("/user"))
-router1.use("/settings", new TestHandler("/user/settings"))
-app.use("/user", router1)
-
-var router2 = new App
-router2.use("/", new TestHandler("/products"))
-router2.use("/list", new TestHandler("/products/list"))
-app.use("/products", router2)
-
-var host = test_host
-var port = test_port
-
-var server = new AppThread(host, port, app)
-server.start
-0.1.sleep
-
-var client = new HelloClient(host, port)
-client.start
-client.join
-0.1.sleep
-
-exit 0
diff --git a/lib/popcorn/tests/test_routes.nit b/lib/popcorn/tests/test_routes.nit
deleted file mode 100644 (file)
index 2db660a..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# Copyright 2016 Alexandre Terrasa <alexandre@moz-code.org>
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import base_tests
-
-class TestHandler
-       super Handler
-
-       var marker: String
-
-       redef fun get(req, res) do res.send marker
-end
-
-class HelloClient
-       super ClientThread
-
-       redef fun main do
-               system "curl -s {host}:{port}"
-               system "curl -s {host}:{port}/"
-
-               system "curl -s {host}:{port}/misc"
-               system "curl -s {host}:{port}/misc/foo"
-               system "curl -s {host}:{port}/misc/foo/bar"
-               system "curl -s {host}:{port}/misc/foo/baz"
-
-               system "curl -s {host}:{port}/user"
-               system "curl -s {host}:{port}/user/"
-               system "curl -s {host}:{port}/user/id"
-               system "curl -s {host}:{port}/user/id/profile"
-               system "curl -s {host}:{port}/user/id/misc/foo"
-               system "curl -s {host}:{port}/user/id/misc/foo/bar"
-               system "curl -s {host}:{port}/user/id/misc/foo/bar/baz"
-
-               system "curl -s {host}:{port}/not_found"
-               system "curl -s {host}:{port}/user/id/not_found"
-               return null
-       end
-end
-
-var app = new App
-app.use("/", new TestHandler("/"))
-app.use("/user", new TestHandler("/user"))
-app.use("/misc/*", new TestHandler("/misc/everything"))
-app.use("/user/:id", new TestHandler("/user/id"))
-app.use("/user/:id/profile", new TestHandler("/user/id/profile"))
-app.use("/user/:id/misc/*", new TestHandler("/user/id/misc/everything"))
-
-var host = test_host
-var port = test_port
-
-# First, launch a server in the background
-var server = new AppThread(host, port, app)
-server.start
-0.1.sleep
-
-# Then, launch a client running test requests
-var client = new HelloClient(host, port)
-client.start
-client.join
-0.1.sleep
-
-# Force quit the server
-exit 0
diff --git a/lib/popcorn/tests/tests.sh b/lib/popcorn/tests/tests.sh
deleted file mode 100755 (executable)
index 4406058..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-#!/bin/bash
-
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# Copyright 2016 Alexandre Terrasa <alexandre@moz-code.org>.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-BIN=../bin
-OUT=./out
-RES=./res
-
-NITC=../../../bin/nitc
-
-compile() {
-       local test="$1"
-       $NITC $test.nit -o $OUT/$test.bin 1>&2 2> $OUT/$test.cmp_err
-}
-
-test_prog()
-{
-       local test="$1"
-
-       chmod +x $OUT/$test.bin 2> $OUT/$test.err
-       $OUT/$test.bin > $OUT/$test.res 2> $OUT/$test.err
-
-       diff $OUT/$test.res $RES/$test.res > $OUT/$test.diff 2> /dev/null
-}
-
-# return
-#  0 if the sav not exists
-#  1 if the file does match
-#  2 if the file does not match
-check_result() {
-       local test="$1"
-
-       if [ -s "$OUT/$test.cmp_err" ]; then
-               return 0
-       elif [ -s "$OUT/$test.err" ]; then
-               return 1
-       elif [ ! -r "$RES/$test.res" ]; then
-               return 2
-       elif [ -s "$OUT/$test.diff" ]; then
-               return 3
-       else
-               return 4
-       fi
-}
-
-echo "Testing..."
-echo ""
-
-rm -rf $OUT 2>/dev/null
-mkdir $OUT 2>/dev/null
-
-all=0
-ok=0
-ko=0
-sk=0
-
-for file in `ls test_*.nit`; do
-       ((all++))
-       test="${file%.*}"
-       echo -n "* $test: "
-
-       compile $test
-       test_prog $test
-       check_result $test
-
-       case "$?" in
-               0)
-                       echo "compile error (cat $OUT/$test.cmp_err)"
-                       ((ko++))
-                       ;;
-               1)
-                       echo "error (cat $OUT/$test.cmp_err)"
-                       ((ko++))
-                       ;;
-               2)
-                       echo "skip ($test.res not found)"
-                       ((sk++))
-                       continue;;
-               3)
-                       echo "error (diff $OUT/$test.res $RES/$test.res)"
-                       ((ko++))
-                       ;;
-               4)
-                       echo "success"
-                       ((ok++))
-                       ;;
-
-       esac
-done
-echo ""
-echo "==> success $ok/$all ($ko tests failed, $sk skipped)"
-
-# return result
-test "$ok" == "$all"
diff --git a/lib/readline.ini b/lib/readline.ini
new file mode 100644 (file)
index 0000000..c7e2ee9
--- /dev/null
@@ -0,0 +1,11 @@
+[package]
+name=readline
+tags=lib
+maintainer=Frédéric Vachon <fredvac@gmail.com>
+license=Apache-2.0
+[upstream]
+browse=https://github.com/nitlang/nit/tree/master/lib/readline.nit
+git=https://github.com/nitlang/nit.git
+git.directory=lib/readline.nit
+homepage=http://nitlanguage.org
+issues=https://github.com/nitlang/nit/issues
diff --git a/lib/readline.nit b/lib/readline.nit
new file mode 100644 (file)
index 0000000..783e4e4
--- /dev/null
@@ -0,0 +1,58 @@
+# This file is part of NIT (http://www.nitlanguage.org).
+#
+# Copyright 2016 Frédéric Vachon <fredvac@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# GNU readline library wrapper
+module readline is ldflags "-lreadline"
+
+in "C" `{
+       #include <readline/readline.h>
+       #include <readline/history.h>
+`}
+
+private fun native_readline(prompt: NativeString): NativeString `{
+       return readline(prompt);
+`}
+
+private fun native_add_history(data: NativeString) `{
+       if (data == NULL) return;
+       add_history(data);
+`}
+
+# Set emacs keybindings mode
+fun set_vi_mode `{ rl_editing_mode = 0; `}
+
+# Set emacs keybindings mode
+fun set_emacs_mode `{ rl_editing_mode = 1; `}
+
+# Use the GNU Library readline function
+# Returns `null` if EOF is read
+# If `with_history` is true, it will save all commands in the history except
+# empty strings and white characters strings
+fun readline(message: String, with_history: nullable Bool): nullable String do
+       var line = native_readline(message.to_cstring)
+       if line.address_is_null then return null
+
+       var nit_str = line.to_s
+
+       if with_history != null and with_history then
+               if nit_str.trim != "" then native_add_history(line)
+       end
+
+       return nit_str
+end
+
+# Adds the data String to the history no matter what it contains
+fun add_history(data: String) do native_add_history data.to_cstring
index 9503883..fcdec6c 100644 (file)
@@ -6,6 +6,8 @@ nit - interprets and debugs Nit programs.
 
 nit [*options*] FILE [ARG]...
 
+nit [*options*] - [ARG]...
+
 nit [*options*] -e COMMAND [ARG]...
 
 # DESCRIPTION
@@ -16,6 +18,9 @@ It takes the main module of a program as the first argument then the options and
     $ nit examples/hello_world.nit
     hello world
 
+If `-` is used instead of a module, then the program is read from the standard input.
+The whole program is read before its interpretation starts.
+
 The Nit interpreter is usable and valid as a *shebang* interpreted directive.
 It is however recommended to use with `/usr/bin/env` because the location of the executable is not standardized.
 
index 2927a24..a316782 100644 (file)
@@ -386,7 +386,7 @@ Put primitive attributes in a box instead of an union.
 ### `--no-shortcut-equal`
 Always call == in a polymorphic way.
 
-### `--no-tag-primitive`
+### `--no-tag-primitives`
 Use only boxes for primitive types.
 
 The separate compiler uses tagged values to encode common primitive types like Int, Bool and Char.
@@ -410,9 +410,6 @@ Use an indirection when calling.
 
 Just add the trampolines of `--substitute-monomorph` without doing any additionnal optimizations.
 
-### `--no-tag-primitives`
-Use only boxes for primitive types.
-
 ## INTERNAL OPTIONS
 
 These options can be used to control the fine behavior of the tool.
index e80e80e..2bec157 100644 (file)
@@ -129,18 +129,25 @@ This flag can be used by libraries and program to prevent (or limit) the executi
 
 ## Working with `TestSuites`
 
-TestSuites are Nit files that define a set of TestCases for a particular module.
+TestSuites are Nit modules that define a set of TestCases.
 
-The test suite must be called `test_` followed by the name of the module to test.
-So for the module `foo.nit` the test suite will be called `test_foo.nit`.
+A test suite is a module that uses the annotation `is test_suite`.
+
+It is common that a test suite focuses on testing a single module.
+In this case, the name of the test_suite is often `test_foo.nit` where `foo.nit` is the tested module.
 
 The structure of a test suite is the following:
 
 ~~~~
 # test suite for module `foo`
-module test_foo
+module test_foo is test_suite
+
+import test_suite
 import foo # can be intrude to test private things
+
 class TestFoo
+       super TestSuite
+
     # test case for `foo::Foo::baz`
     fun test_baz do
         var subject = new Foo
@@ -153,11 +160,13 @@ Test suite can be executed using the same `nitunit` command:
 
     $ nitunit foo.nit
 
-`nitunit` will execute a test for each method named `test_*` in a class named `Test*`
-so multiple tests can be executed for a single method:
+`nitunit` will execute a test for each method named `test_*` in a class
+subclassing `TestSuite` so multiple tests can be executed for a single method:
 
 ~~~~
 class TestFoo
+       super TestSuite
+
     fun test_baz_1 do
         var subject = new Foo
         assert subject.baz(1, 2) == 3
@@ -173,14 +182,33 @@ end
 
 Sometimes, it is easier to validate a `TestCase` by comparing its output with a text file containing the expected result.
 
-For each TestCase `test_bar` of a TestSuite `test_mod.nit`, if the corresponding file `test_mod.sav/test_bar.res` exists, then the output of the test is compared with the file.
+For each TestCase `test_bar` of a TestSuite `test_mod.nit`, a corresponding file with the expected output is looked for:
+
+* "test_mod.sav/test_bar.res". I.e. test-cases grouped by test-suites.
+
+       This is the default and is useful if there is a lot of test-suites and test-cases in a directory
+
+* "sav/test_bar.res". I.e. all test-cases grouped in a common sub-directory.
+
+       Useful if there is a lot of test-suites OR test-cases in a directory.
+
+* "test_bar.res" raw in the directory.
+
+       Useful is there is a few test-suites and test-cases in a directory.
+
+All 3 are exclusive. If more than one exists, the test-case is failed.
+
+If a corresponding file then the output of the test-case is compared with the file.
 
 The `diff(1)` command is used to perform the comparison.
 The test is failed if non-zero is returned by `diff`.
 
 ~~~
 module test_mod is test_suite
+
 class TestFoo
+       super TestSuite
+
        fun test_bar do
                print "Hello!"
        end
@@ -195,15 +223,19 @@ Hello!
 
 If no corresponding `.res` file exists, then the output of the TestCase is ignored.
 
+To helps the management of the expected results, the option `--autosav` can be used to automatically create and update them.
+
+
 ## Configuring TestSuites
 
-`TestSuites` also provide methods to configure the test run:
+`TestSuite`s also provide methods to configure the test run:
 
 `before_test` and `after_test`: methods called before/after each test case.
 They can be used to factorize repetitive tasks:
 
 ~~~~
 class TestFoo
+       super TestSuite
     var subject: Foo
     # Mandatory empty init
     init do end
@@ -268,10 +300,13 @@ Output name (default is 'nitunit.xml').
 `nitunit` produces a XML file compatible with JUnit.
 
 ### `--dir`
-Working directory (default is '.nitunit').
+Working directory (default is 'nitunit.out').
 
 In order to execute the tests, nit files are generated then compiled and executed in the giver working directory.
 
+In case of success, the directory is removed.
+In case of failure, it is kept as is so files can be investigated.
+
 ### `--nitc`
 nitc compiler to use.
 
@@ -286,8 +321,15 @@ Only run test case with name that match pattern.
 
 Examples: `TestFoo`, `TestFoo*`, `TestFoo::test_foo`, `TestFoo::test_foo*`, `test_foo`, `test_foo*`
 
-### `-t`, `--target-file`
-Specify test suite location.
+### `--autosav`
+Automatically create/update .res files for black box testing.
+
+If a black block test fails because a difference between the expected result and the current result then the expected result file is updated (and the test is passed).
+
+If a test-case of a test-suite passes but that some output is generated, then an expected result file is created.
+
+It is expected that the created/updated files are checked since the tests are considered passed.
+A VCS like `git` is often a good tool to check the creation and modification of those files.
 
 ## SUITE GENERATION
 
diff --git a/share/nitweb/directives/contributor-list.html b/share/nitweb/directives/contributor-list.html
new file mode 100644 (file)
index 0000000..a46cce9
--- /dev/null
@@ -0,0 +1,11 @@
+<div ng-if='listContributors.length > 0'>
+       <h3 id={{listId}}>
+               <span>{{listTitle}}</span>
+       </h3>
+       <ul class='list-unstyled user-list'>
+               <li ng-repeat='contributor in listContributors'>
+                       <img class='avatar' src="https://secure.gravatar.com/avatar/{{contributor.hash}}?size=20&amp;default=retro">
+                       {{contributor.name}}
+               </li>
+       </ul>
+</div>
diff --git a/share/nitweb/directives/entity/card.html b/share/nitweb/directives/entity/card.html
new file mode 100644 (file)
index 0000000..aa9ccb3
--- /dev/null
@@ -0,0 +1,11 @@
+<div class='card'>
+       <div class='card-left text-center'>
+               <entity-tag mentity='mentity' />
+       </div>
+       <div class='card-body'>
+               <h5 class='card-heading'>
+                       <entity-signature mentity='mentity'/>
+               </h5>
+               <span class='synopsis' ng-bind-html='mentity.mdoc.html_synopsis' />
+       </div>
+</div>
diff --git a/share/nitweb/directives/entity/defcard.html b/share/nitweb/directives/entity/defcard.html
new file mode 100644 (file)
index 0000000..5a024fe
--- /dev/null
@@ -0,0 +1,37 @@
+<div class='card card-xl' ng-class='{active: focus.full_name == definition.full_name}'>
+       <div class='card-body'>
+               <h5 class='text-muted'>
+                       <span ng-if='definition.is_intro'>
+                               <span class='glyphicon glyphicon-plus' /> Introduction</span>
+                       </span>
+                       <span ng-if='!definition.is_intro'>
+                               <span class='glyphicon glyphicon-asterisk' /> Redefinition</span>
+                       </span>
+                       <span ng-if='definition.mclass'>
+                               of <entity-link mentity='definition.mclass' />
+                       </span>
+                       <span ng-if='definition.mproperty'>
+                               of <entity-link mentity='definition.mproperty' />
+                       </span>
+                       <span ng-if='definition.mclassdef'>
+                               in <entity-link mentity='definition.mmodule' />
+                               :: <entity-link mentity='definition.mclassdef' />
+                       </span>
+                       <span ng-if='!definition.mclassdef'>
+                               in <entity-link mentity='definition.mmodule' />
+                       </span>
+                       <div class='btn-bar'>
+                               <button class='btn btn-link' aria-expanded='false'
+                                       data-target='#{{codeId}}' ng-click='loadCardCode()'
+                                       aria-controls='{{codeId}}'>
+                                       <span class='glyphicon glyphicon-console'
+                                       title='Show code' />
+                               </button>
+                       </div>
+               </h5>
+               <div id='{{codeId}}' class='collapse'>
+                       <pre ng-bind-html='code' />
+               </div>
+               <entity-location mentity='definition' />
+       </div>
+</div>
diff --git a/share/nitweb/directives/entity/doc.html b/share/nitweb/directives/entity/doc.html
new file mode 100644 (file)
index 0000000..9d0ffb0
--- /dev/null
@@ -0,0 +1,5 @@
+<div class='card' ng-if='mentity.mdoc'>
+       <div class='card-body'>
+               <div ng-bind-html='mentity.mdoc.html_documentation'></div>
+       </div>
+</div>
diff --git a/share/nitweb/directives/entity/linearization.html b/share/nitweb/directives/entity/linearization.html
new file mode 100644 (file)
index 0000000..afdde43
--- /dev/null
@@ -0,0 +1,11 @@
+<div class='entity-list' ng-if='listEntities.length'>
+       <h3>{{listTitle}}</h3>
+       <div class='card-list'>
+               <div ng-repeat='def in listEntities'>
+                       <entity-def definition='def' focus='listFocus' />
+                       <h4 ng-if='!$last' class='text-muted text-center'>
+                               <span class='glyphicon glyphicon-chevron-up'></span>
+                       </h4>
+               </div>
+       </div>
+</div>
diff --git a/share/nitweb/directives/entity/link.html b/share/nitweb/directives/entity/link.html
new file mode 100644 (file)
index 0000000..740ea8b
--- /dev/null
@@ -0,0 +1,3 @@
+<span>
+       <a ng-href='{{mentity.web_url}}'>{{mentity.name}}</a>
+</span>
diff --git a/share/nitweb/directives/entity/list.html b/share/nitweb/directives/entity/list.html
new file mode 100644 (file)
index 0000000..cc026a4
--- /dev/null
@@ -0,0 +1,19 @@
+<div class='entity-list'
+       ng-if='(listEntities | filter:listObjectFilter).length > 0'>
+       <h3 id={{listId}}>
+               <span>{{listTitle}}</span>
+               <button class='btn btn-link btn-xs pull-right btn-filter' ng-click='toggleFilters()'>
+                       <span class='glyphicon glyphicon-filter text-muted' />
+               </button>
+       </h3>
+               <div ng-if='showFilters'>
+                       <ui-filter-form
+                               search-filter='listObjectFilter'
+                               visibility-filter='visibilityFilter'>
+               </div>
+               <div class='card-list'>
+                       <entity-card mentity='mentity'
+                               ng-repeat='mentity in listEntities | filter:listObjectFilter | visibility:visibilityFilter' />
+               </div>
+       </div>
+</div>
diff --git a/share/nitweb/directives/entity/location.html b/share/nitweb/directives/entity/location.html
new file mode 100644 (file)
index 0000000..7e817c6
--- /dev/null
@@ -0,0 +1,5 @@
+<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>
diff --git a/share/nitweb/directives/entity/signature.html b/share/nitweb/directives/entity/signature.html
new file mode 100644 (file)
index 0000000..f3f2dc6
--- /dev/null
@@ -0,0 +1,51 @@
+<span class='signature'>
+       <span ng-repeat='modifier in mentity.modifiers'>
+               <span ng-if='modifier != "public"' class='modifier'>{{modifier}}</span>
+       </span>
+       <span class='name'>
+               <entity-link mentity='mentity' />
+       </span>
+       <span ng-if='mentity.mparameters'>
+               <span ng-if='mentity.mparameters.length > 0'>
+                       <span>[</span>
+                       <span ng-repeat='mparam in mentity.mparameters'>
+                               <span>
+                                       <span>{{mparam.name}}</span>
+                                       <span>: </span>
+                                       <entity-signature mentity='mparam.mtype' />
+                               </span>
+                               <span ng-if='$middle'>, </span>
+                       </span>
+                       <span>]</span>
+               </span>
+       </span>
+       <span ng-if='mentity.msignature'>
+               <span ng-if='mentity.msignature.arity > 0'>
+                       <span>(</span>
+                       <span ng-repeat='mparam in mentity.msignature.mparams'>
+                               <span>
+                                       <span>{{mparam.name}}</span>
+                                       <span ng-if='mentity.is_intro !== false'>
+                                               <span>: </span>
+                                               <entity-signature mentity='mparam.mtype' />
+                                       </span>
+                                       <span ng-if='mparam.is_vararg'>...</span>
+                               </span>
+                               <span ng-if='!first && !$last'>, </span>
+                       </span>
+                       <span>)</span>
+               </span>
+               <span ng-if='mentity.is_intro !== false && mentity.msignature.return_mtype'>
+                       <span>: </span>
+                       <entity-signature mentity='mentity.msignature.return_mtype' />
+               </span>
+       </span>
+       <span ng-if='mentity.is_intro !== false && mentity.static_mtype'>
+               <span>: </span>
+               <entity-signature mentity='mentity.static_mtype' />
+       </span>
+       <span ng-if='mentity.bound'>
+               <span>: </span>
+               <entity-signature mentity='mentity.bound' />
+       </span>
+</span>
diff --git a/share/nitweb/directives/entity/tag.html b/share/nitweb/directives/entity/tag.html
new file mode 100644 (file)
index 0000000..7c69f1e
--- /dev/null
@@ -0,0 +1,5 @@
+<span class="glyphicon glyphicon-tag" ng-class='{
+               "text-success": mentity.visibility == "public",
+               "text-warning": mentity.visibility == "protected",
+               "text-danger": mentity.visibility == "private",
+}' />
diff --git a/share/nitweb/directives/group-block.html b/share/nitweb/directives/group-block.html
new file mode 100644 (file)
index 0000000..ad7f00e
--- /dev/null
@@ -0,0 +1,21 @@
+<div class='media'>
+       <div class='media-left text-center' ng-if='mentity.visibility' ng-class='{
+                       "text-success": mentity.visibility == "public",
+                       "text-warning": mentity.visibility == "protected",
+                       "text-danger": mentity.visibility == "private",
+       }'>
+               <span class="glyphicon glyphicon-tag"></span>
+       </div>
+       <div class='media-body'>
+               <h5 class='media-heading'>
+                       <entity-signature mentity='mentity'/>
+               </h5>
+               <span class='synopsis'>{{mentity.mdoc.synopsis}}</span>
+               <div ng-if='recursive && mentity.mgroups'>
+                       <group-block mentity-id='mgroup' ng-repeat='mgroup in mentity.mgroups' />
+               </div>
+               <div ng-if='recursive && mentity.mmodules'>
+                       <module-block mentity-id='mmodule' ng-repeat='mmodule in mentity.mmodules' />
+               </div>
+       </div>
+</div>
diff --git a/share/nitweb/directives/ui-filter-button-vis.html b/share/nitweb/directives/ui-filter-button-vis.html
new file mode 100644 (file)
index 0000000..8d4f8d4
--- /dev/null
@@ -0,0 +1,6 @@
+<button
+       class='btn btn-link btn-xs'
+       ng-click='toggle()'>
+       <span ng-if='property' ng-class='classesOn'/>
+       <span ng-if='!property' ng-class='classesOff'/>
+</button>
diff --git a/share/nitweb/directives/ui-filter-field.html b/share/nitweb/directives/ui-filter-field.html
new file mode 100644 (file)
index 0000000..05c51e4
--- /dev/null
@@ -0,0 +1,4 @@
+<div class='form-group has-icon'>
+       <input type='text' class='form-control' ng-model='property' placeholder='Filter...'>
+       <span class='glyphicon glyphicon-search form-control-icon text-muted'></span>
+</div>
diff --git a/share/nitweb/directives/ui-filter-form.html b/share/nitweb/directives/ui-filter-form.html
new file mode 100644 (file)
index 0000000..9782af8
--- /dev/null
@@ -0,0 +1,6 @@
+<form class='form-inline'>
+       <ui-filter-field property='searchFilter.$' />
+       <div class='pull-right'>
+               <ui-filter-group-vis property='visibilityFilter' />
+       </div>
+</form>
diff --git a/share/nitweb/directives/ui-filter-group-vis.html b/share/nitweb/directives/ui-filter-group-vis.html
new file mode 100644 (file)
index 0000000..7fcd475
--- /dev/null
@@ -0,0 +1,14 @@
+<div class='form-group'>
+       <ui-filter-button-vis property='property.public'
+                       classes-on='"glyphicon glyphicon-eye-open text-success"'
+                       classes-off='"glyphicon glyphicon-eye-close text-success"'
+                       title='Toggle public' />
+       <ui-filter-button-vis property='property.protected'
+                       classes-on='"glyphicon glyphicon-eye-open text-warning"'
+                       classes-off='"glyphicon glyphicon-eye-close text-warning"'
+                       title='Toggle protected' />
+       <ui-filter-button-vis property='property.private'
+                       classes-on='"glyphicon glyphicon-eye-open text-danger"'
+                       classes-off='"glyphicon glyphicon-eye-close text-danger"'
+                       title='Toggle private' />
+</div>
diff --git a/share/nitweb/index.html b/share/nitweb/index.html
new file mode 100644 (file)
index 0000000..1b99a81
--- /dev/null
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html lang='en' ng-app='nitweb'>
+       <head>
+               <base href='/'>
+               <meta charset='utf-8'>
+               <meta http-equiv='X-UA-Compatible' content='IE=edge'>
+               <meta name='viewport' content='width=device-width, initial-scale=1'>
+               <title>ng-doc</title>
+
+               <link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css'
+                       integrity='sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7'
+                       crossorigin='anonymous' rel='stylesheet'>
+
+               <link href='/stylesheets/nitweb_bootstrap.css' rel='stylesheet'>
+               <link href='/stylesheets/nitweb.css' rel='stylesheet'>
+       </head>
+       <body>
+               <nav class='navbar navbar-default navbar-fixed-top'>
+                       <div class='container-fluid'>
+                               <div class='col-xs-3 navbar-header'>
+                                       <a class='navbar-brand' ng-href='/'>Nitdoc</a>
+                               </div>
+                               <div class='col-xs-7'>
+                                       <form ng-controller='SearchCtrl as searchCtrl' >
+                                               <div class='form-group has-icon'>
+                                                       <input placeholder='Search...' type='text' class='form-control search-input'
+                                                               ng-model-options='{ debounce: 150 }' ng-model='query'
+                                                               ng-keydown='update($event)' ng-change='search()'>
+                                                       <span class='glyphicon glyphicon-search form-control-icon text-muted'></span>
+                                               </div>
+                                               <div ng-if='results.length > 0' class='search-results'>
+                                                       <div class='card-list'>
+                                                               <entity-card ng-click='reset()' ng-class='{active: activeItem == $index}' mentity='mentity' ng-repeat='mentity in results' />
+                                                       </div>
+                                               </div>
+                                       </form>
+                               </div>
+                       </div>
+               </nav>
+               <div ng-view></div>
+
+               <script src='https://code.jquery.com/jquery-1.12.4.min.js'
+                       integrity='sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ='
+                       crossorigin='anonymous''></script>
+               <script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js'
+                       integrity='sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS'
+                       crossorigin='anonymous'></script>
+               <script src='https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.5/angular.min.js'>
+               </script>
+               <script src='https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.5/angular-route.js'>
+               </script>
+               <script src='https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.5/angular-sanitize.js'>
+               </script>
+
+               <script src='/javascripts/nitweb.js'></script>
+               <script src='/javascripts/model.js'></script>
+               <script src='/javascripts/entities.js'></script>
+               <script src='/javascripts/ui.js'></script>
+               <script src='/javascripts/index.js'></script>
+       </body>
+</html>
diff --git a/share/nitweb/javascripts/entities.js b/share/nitweb/javascripts/entities.js
new file mode 100644 (file)
index 0000000..9eefc9f
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+ * 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('entities', ['ui', 'model'])
+
+               .controller('EntityCtrl', ['Model', '$routeParams', '$scope', function(Model, $routeParams, $scope) {
+                       this.loadEntityLinearization = function() {
+                               Model.loadEntityLinearization($routeParams.id,
+                                       function(data) {
+                                               $scope.linearization = data;
+                                       }, function(err) {
+                                               $scope.error = err;
+                                       });
+                       };
+
+                       this.loadEntityDefs = function() {
+                               Model.loadEntityDefs($routeParams.id,
+                                       function(data) {
+                                               $scope.defs = data;
+                                       }, function(err) {
+                                               $scope.error = err;
+                                       });
+                       };
+
+                       this.loadEntityCode = function() {
+                               Model.loadEntityCode($routeParams.id,
+                                       function(data) {
+                                               $scope.code = data;
+                                       }, function(err) {
+                                               $scope.code = err;
+                                       });
+                       };
+
+                       Model.loadEntity($routeParams.id,
+                               function(data) {
+                                       $scope.mentity = data;
+                               }, function(err) {
+                                       $scope.error = err;
+                               });
+               }])
+
+               .directive('entityLink', function() {
+                       return {
+                               restrict: 'E',
+                               scope: {
+                                       mentity: '='
+                               },
+                               templateUrl: '/directives/entity/link.html'
+                       };
+               })
+
+               .directive('entityDoc', function() {
+                       return {
+                               restrict: 'E',
+                               scope: {
+                                       mentity: '='
+                               },
+                               templateUrl: '/directives/entity/doc.html'
+                       };
+               })
+
+               .directive('entitySignature', function() {
+                       return {
+                               restrict: 'E',
+                               scope: {
+                                       mentity: '='
+                               },
+                               templateUrl: '/directives/entity/signature.html'
+                       };
+               })
+
+               .directive('entityTag', function() {
+                       return {
+                               restrict: 'E',
+                               scope: {
+                                       mentity: '='
+                               },
+                               replace: true,
+                               templateUrl: '/directives/entity/tag.html'
+                       };
+               })
+
+               .directive('entityLocation', function() {
+                       return {
+                               restrict: 'E',
+                               scope: {
+                                       mentity: '='
+                               },
+                               templateUrl: '/directives/entity/location.html'
+                       };
+               })
+
+               .directive('entityCard', function() {
+                       return {
+                               restrict: 'E',
+                               scope: {
+                                       mentity: '='
+                               },
+                               replace: true,
+                               templateUrl: '/directives/entity/card.html'
+                       };
+               })
+
+               .directive('entityList', function() {
+                       return {
+                               restrict: 'E',
+                               scope: {
+                                       listEntities: '=',
+                                       listId: '@',
+                                       listTitle: '@',
+                                       listObjectFilter: '=',
+                               },
+                               templateUrl: '/directives/entity/list.html',
+                               link: function ($scope, element, attrs) {
+                                       $scope.showFilters = false;
+                                       if(!$scope.listObjectFilter) {
+                                               $scope.listObjectFilter = {};
+                                       }
+                                       if(!$scope.visibilityFilter) {
+                                               $scope.visibilityFilter = {
+                                                       public: true,
+                                                       protected: true,
+                                                       private: false
+                                               };
+                                       }
+                                       $scope.toggleFilters = function() {
+                                               $scope.showFilters = !$scope.showFilters;
+                                       };
+                               }
+                       };
+               })
+
+               .directive('entityLinearization', function() {
+                       return {
+                               restrict: 'E',
+                               scope: {
+                                       listEntities: '=',
+                                       listTitle: '@',
+                                       listFocus: '='
+                               },
+                               templateUrl: '/directives/entity/linearization.html'
+                       };
+               })
+
+               .directive('entityDef', ['Model', function(Model, Code) {
+                       return {
+                               restrict: 'E',
+                               scope: {
+                                       definition: '=',
+                                       focus: '='
+                               },
+                               templateUrl: '/directives/entity/defcard.html',
+                               link: function ($scope, element, attrs) {
+                                       $scope.codeId = 'code_' + $scope.definition.full_name.replace(/[^a-zA-Z0-9]/g, '_');
+                                       $scope.loadCardCode = function() {
+                                               if(!$scope.code) {
+                                                       Model.loadEntityCode($scope.definition.full_name,
+                                                               function(data) {
+                                                                       $scope.code = data;
+                                                                       setTimeout(function() { // smooth collapse
+                                                                               $('#' + $scope.codeId).collapse('show')
+                                                                       }, 1);
+                                                               }, function(err) {
+                                                                       $scope.code = err;
+                                                               });
+                                               } else {
+                                                       if($('#' + $scope.codeId).hasClass('in')) {
+                                                               $('#' + $scope.codeId).collapse('hide');
+                                                       } else {
+                                                               $('#' + $scope.codeId).collapse('show');
+                                                       }
+                                               }
+                                       };
+                               }
+                       };
+               }])
+})();
diff --git a/share/nitweb/javascripts/index.js b/share/nitweb/javascripts/index.js
new file mode 100644 (file)
index 0000000..534f462
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2016 Alexandre Terrasa <alexandre@moz-code.org>.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+       angular
+               .module('index', ['model', 'ngSanitize'])
+
+               .run(['$anchorScroll', function($anchorScroll) {
+                       $anchorScroll.yOffset = 80;
+               }])
+
+               .controller('IndexCtrl', ['Catalog', '$routeParams', '$sce', '$scope', '$location', '$anchorScroll', function(Catalog, $routeParams, $sce, $scope, $location, $anchorScroll) {
+                       this.loadHighlighted = function() {
+                               Catalog.loadHightlighted(
+                                       function(data) {
+                                               $scope.highlighted = data;
+                                       }, function(err) {
+                                               $scope.error = err;
+                                       });
+                       };
+
+                       this.loadMostRequired = function() {
+                               Catalog.loadMostRequired(
+                                       function(data) {
+                                               $scope.required = data;
+                                       }, function(err) {
+                                               $scope.error = err;
+                                       });
+                       };
+
+                       this.loadByTags = function() {
+                               Catalog.loadByTags(
+                                       function(data) {
+                                               $scope.bytags = data;
+                                       }, function(err) {
+                                               $scope.error = err;
+                                       });
+                       };
+
+                       this.loadStats = function() {
+                               Catalog.loadStats(
+                                       function(data) {
+                                               $scope.stats = data;
+                                       }, function(err) {
+                                               $scope.error = err;
+                                       });
+                       };
+
+                       this.loadContributors = function() {
+                               Catalog.loadContributors(
+                                       function(data) {
+                                               $scope.contributors = data;
+                                       }, function(err) {
+                                               $scope.error = err;
+                                       });
+                       };
+
+
+                       this.scrollTo = function(hash) {
+                               $anchorScroll(hash);
+                       }
+
+                       this.loadHighlighted();
+                       this.loadStats();
+                       this.loadContributors();
+               }])
+
+               .directive('contributorList', ['Model', function(Model) {
+                       return {
+                               restrict: 'E',
+                               scope: {
+                                       listId: '@',
+                                       listTitle: '@',
+                                       listContributors: '='
+                               },
+                               templateUrl: '/directives/contributor-list.html'
+                       };
+               }])
+})();
diff --git a/share/nitweb/javascripts/model.js b/share/nitweb/javascripts/model.js
new file mode 100644 (file)
index 0000000..2144983
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * 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() {
+       var apiUrl = '/api';
+
+       angular
+               .module('model', [])
+
+               .factory('Model', [ '$http', function($http) {
+                       return {
+
+                               loadEntity: function(id, cb, cbErr) {
+                                       $http.get(apiUrl + '/entity/' + id)
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+
+                               loadEntityLinearization: function(id, cb, cbErr) {
+                                       $http.get(apiUrl + '/linearization/' + id)
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+
+                               loadEntityDefs: function(id, cb, cbErr) {
+                                       $http.get(apiUrl + '/defs/' + id)
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+
+                               loadEntityCode: function(id, cb, cbErr) {
+                                       $http.get(apiUrl + '/code/' + id)
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+
+                               search: function(q, n, cb, cbErr) {
+                                       $http.get(apiUrl + '/search?q=' + q + '&n=' + n)
+                                               .success(cb)
+                                               .error(cbErr);
+                               }
+                       };
+               }])
+
+               .factory('Catalog', [ '$http', function($http) {
+                       return {
+                               loadHightlighted: function(cb, cbErr) {
+                                       $http.get(apiUrl + '/catalog/highlighted')
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+
+                               loadMostRequired: function(cb, cbErr) {
+                                       $http.get(apiUrl + '/catalog/required')
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+
+                               loadByTags: function(cb, cbErr) {
+                                       $http.get(apiUrl + '/catalog/bytags')
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+
+                               loadStats: function(cb, cbErr) {
+                                       $http.get(apiUrl + '/catalog/stats')
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+
+                               loadContributors: function(cb, cbErr) {
+                                       $http.get(apiUrl + '/catalog/contributors')
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+                       }
+               }])
+})();
diff --git a/share/nitweb/javascripts/nitweb.js b/share/nitweb/javascripts/nitweb.js
new file mode 100644 (file)
index 0000000..93b7c3a
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * 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('nitweb', ['ngRoute', 'ngSanitize', 'entities', 'index'])
+
+       .config(function($routeProvider, $locationProvider) {
+               $routeProvider
+                       .when('/', {
+                               templateUrl: 'views/index.html',
+                               controller: 'IndexCtrl',
+                               controllerAs: 'indexCtrl'
+                       })
+                       .when('/package/:id', {
+                               templateUrl: 'views/package.html',
+                               controller: 'EntityCtrl',
+                               controllerAs: 'entityCtrl'
+                       })
+                       .when('/group/:id', {
+                               templateUrl: 'views/group.html',
+                               controller: 'EntityCtrl',
+                               controllerAs: 'entityCtrl'
+                       })
+                       .when('/module/:id', {
+                               templateUrl: 'views/module.html',
+                               controller: 'EntityCtrl',
+                               controllerAs: 'entityCtrl'
+                       })
+                       .when('/class/:id', {
+                               templateUrl: 'views/class.html',
+                               controller: 'EntityCtrl',
+                               controllerAs: 'entityCtrl'
+                       })
+                       .when('/classdef/:id', {
+                               templateUrl: 'views/classdef.html',
+                               controller: 'EntityCtrl',
+                               controllerAs: 'entityCtrl'
+                       })
+                       .when('/property/:id', {
+                               templateUrl: 'views/property.html',
+                               controller: 'EntityCtrl',
+                               controllerAs: 'entityCtrl'
+                       })
+                       .when('/propdef/:id', {
+                               templateUrl: 'views/propdef.html',
+                               controller: 'EntityCtrl',
+                               controllerAs: 'entityCtrl'
+                       })
+                       .otherwise({
+                               redirectTo: '/'
+                       });
+               $locationProvider.html5Mode(true);
+       });
+})();
diff --git a/share/nitweb/javascripts/ui.js b/share/nitweb/javascripts/ui.js
new file mode 100644 (file)
index 0000000..cd30d06
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * 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('ui', [ 'model' ])
+
+               .controller('SearchCtrl', ['Model', '$routeParams', '$scope', '$window', function(Model, $routeParams, $scope, $window) {
+                       $scope.query = '';
+
+                       $scope.reset = function() {
+                               $scope.activeItem = 0;
+                               $scope.results = [];
+                       }
+
+                       $scope.update = function(e) {
+                               if(e.keyCode == 38) {
+                                       $scope.selectUp();
+                               } else if(e.keyCode == 40) {
+                                       $scope.selectDown();
+                               } else if(e.keyCode == 27) {
+                                       $scope.selectEscape();
+                               } else if(e.keyCode == 13) {
+                                       $scope.selectEnter();
+                               }
+                       }
+
+                       $scope.selectUp = function() {
+                               if($scope.activeItem > 0) {
+                                       $scope.activeItem -= 1;
+                               }
+                       }
+
+                       $scope.selectDown = function() {
+                               if($scope.activeItem < $scope.results.length - 1) {
+                                       $scope.activeItem += 1;
+                               }
+                       }
+
+                       $scope.selectEnter = function() {
+                               $window.location.href = $scope.results[$scope.activeItem].web_url;
+                               $scope.reset();
+                       }
+
+                       $scope.selectEscape = function() {
+                               $scope.reset();
+                       }
+
+                       $scope.search = function() {
+                               if(!$scope.query) {
+                                       $scope.reset();
+                                       return;
+                               }
+                               Model.search($scope.query, 10,
+                                       function(data) {
+                                               $scope.reset();
+                                               $scope.results = data;
+                                       }, function(err) {
+                                               $scope.reset();
+                                               $scope.error = err;
+                                       });
+                       }
+
+                       $scope.reset();
+               }])
+
+               .directive('uiFilters', function() {
+                       return {
+                               restrict: 'E',
+                               scope: {
+                                       property: '=',
+                                       classesOn: '=',
+                                       classesOff: '='
+                               },
+                               replace: true,
+                               templateUrl: '/directives/ui-filter-button-vis.html',
+                               link: function ($scope, element, attrs) {
+                                       $scope.toggle = function() {
+                                               $scope.property = !$scope.property;
+                                       }
+                               }
+                       };
+               })
+
+               .filter('visibility', function() {
+                       return function(input, visibilityFilter) {
+                               var res = [];
+                               input.forEach(function(entry) {
+                                       if(visibilityFilter.public == false && entry.visibility == "public") {
+                                               return;
+                                       }
+                                       if(visibilityFilter.protected == false && entry.visibility == "protected") {
+                                               return;
+                                       }
+                                       if(visibilityFilter.private == false && entry.visibility == "private") {
+                                               return;
+                                       }
+                                       res.push(entry);
+                               });
+                               return res;
+                       };
+               })
+
+               .directive('uiFilterForm', function() {
+                       return {
+                               restrict: 'E',
+                               scope: {
+                                       searchFilter: '=',
+                                       visibilityFilter: '='
+                               },
+                               replace: true,
+                               templateUrl: '/directives/ui-filter-form.html'
+                       };
+               })
+
+               .directive('uiFilterField', function() {
+                       return {
+                               restrict: 'E',
+                               scope: {
+                                       property: '='
+                               },
+                               replace: true,
+                               templateUrl: '/directives/ui-filter-field.html'
+                       };
+               })
+
+               .directive('uiFilterGroupVis', function() {
+                       return {
+                               restrict: 'E',
+                               scope: {
+                                       property: '='
+                               },
+                               replace: true,
+                               templateUrl: '/directives/ui-filter-group-vis.html'
+                       };
+               })
+
+               .directive('uiFilterButtonVis', function() {
+                       return {
+                               restrict: 'E',
+                               scope: {
+                                       property: '=',
+                                       classesOn: '=',
+                                       classesOff: '='
+                               },
+                               replace: true,
+                               templateUrl: '/directives/ui-filter-button-vis.html',
+                               link: function ($scope, element, attrs) {
+                                       $scope.toggle = function() {
+                                               $scope.property = !$scope.property;
+                                       }
+                               }
+                       };
+               })
+})();
diff --git a/share/nitweb/stylesheets/nitweb.css b/share/nitweb/stylesheets/nitweb.css
new file mode 100644 (file)
index 0000000..941b5a4
--- /dev/null
@@ -0,0 +1,250 @@
+/* Body */
+
+body {
+       background: #f2f2f2;
+       margin-top: 70px;
+       margin-bottom: 70px;
+}
+
+h1, h2, h3, h4, h5, h6 {
+       color: #666;
+}
+
+a {
+       cursor: pointer;
+}
+
+.nitdoc h1, .nitdoc h2, .nitdoc h3, .nitdoc h4, .nitdoc h5, .nitdoc h6 {
+       color: #333;
+}
+
+.page-header {
+    margin-top: 0;
+    border: none;
+}
+
+/* cards */
+
+.card.active {
+       border: 1px solid #1E9431;
+}
+
+.card, .card-body { overflow: hidden; }
+
+.card-heading {
+    margin-top: 0;
+    margin-bottom: 5px;
+}
+
+.card {
+       background: #fff;
+       border: 1px solid #ccc;
+       margin-top: 10px;
+       box-shadow: 0 -1px 0 #e5e5e5,0 0 2px rgba(0,0,0,.12),0 2px 4px rgba(0,0,0,.24);
+}
+
+.card-body {
+    padding: 15px;
+    width: 10000px;
+}
+
+.card-body, .card-right, .card-left {
+    display: table-cell;
+    vertical-align: top;
+}
+
+.card-left, .card>.pull-left {
+    padding: 15px;
+       padding-right: 0px;
+}
+.card-right, .card>.pull-right {
+    padding: 15px;
+       padding-left: 0px;
+}
+
+.card-list {
+       margin-top: 10px;
+}
+
+.card-list > .card:first-child {
+       border-top: 1px solid #ccc;
+}
+
+.card-list > .card {
+       margin-top: 0;
+       border-top: none;
+}
+
+/* ui */
+
+.btn-bar { margin-top: -5px; float: right }
+.btn-bar .btn { padding: 5px 10px; }
+
+entity-list .btn-filter {
+       visibility: hidden;
+}
+
+entity-list:hover .btn-filter {
+       visibility: visible;
+}
+
+/* doc */
+
+.nitdoc .synopsys {
+       font-size: 2em;
+}
+
+.signature {
+       color: #666;
+       font-family: monospace;
+}
+
+.signature .name {
+       font-weight: bold;
+}
+
+.page-header .signature .name, .signature .signature .name {
+       font-weight: normal;
+}
+
+.signature .signature a {
+       color: #666;
+       font-family: monospace;
+}
+
+/* tabs */
+
+.nav-tabs li { cursor: pointer; }
+
+.nav>li.warning>a {
+    color: #fff;
+    background-color: #f0ad4e;
+}
+
+.nav>li.warning>a:focus, .nav>li.warning>a:hover {
+    background-color: #ff9c0f;
+}
+
+/* forms */
+
+.has-icon {
+    position: relative;
+}
+
+.has-icon .form-control {
+       padding-left: 35px;
+}
+
+.form-control-icon {
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 2;
+    display: block;
+    width: 34px;
+    height: 34px;
+    line-height: 34px;
+    text-align: center;
+    pointer-events: none;
+}
+
+/* search */
+
+.search-input {
+       width: 100%;
+}
+
+.search-results {
+       position: absolute;
+       right: 0;
+}
+
+.search-results .card.active {
+       background: #eee;
+       border-color: #eee;
+}
+
+/* navs */
+
+.nav-tabs li { cursor: pointer; }
+
+.navbar-fixed-top {
+       background-color: #1E9431;
+       box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28);
+}
+
+.navbar-fixed-top .form-control:hover, .navbar-fixed-top .form-control:focus {
+       background: rgba(255, 255, 255, 0.2);
+}
+
+.navbar-fixed-top .form-control {
+       background: rgba(255, 255, 255, 0.1);
+    border: none;
+    color: #fff;
+    box-shadow: none;
+}
+
+.navbar-fixed-top .form-control-icon {
+       color: #fff;
+}
+
+.navbar-fixed-top *::-webkit-input-placeholder {
+    color: #fff;
+}
+.navbar-fixed-top *:-moz-placeholder {
+    /* FF 4-18 */
+    color: #fff;
+}
+.navbar-fixed-top *::-moz-placeholder {
+    /* FF 19+ */
+    color: #fff;
+}
+.navbar-fixed-top *:-ms-input-placeholder {
+    /* IE 10+ */
+    color: #fff;
+}
+
+.navbar-fixed-top .form-group {
+       margin-top: 8px;
+       margin-bottom: 0px;
+}
+/*
+ * Users
+ */
+
+.avatar {
+       border-radius: 2px;
+}
+
+/*
+ * Code Highlighting
+ */
+
+.nitcode a { color: inherit; text-decoration: inherit; } /* hide links */
+.nitcode a:hover { text-decoration: underline; } /* underline links */
+.nitcode span[title]:hover { text-decoration: underline; } /* underline titles */
+/* lexical raw tokens. independent of usage or semantic: */
+.nitcode .nc_c { color: gray; font-style: italic; } /* comment */
+.nitcode .nc_d { color: #3D8127; font-style: italic; } /* documentation comments */
+.nitcode .nc_k { font-weight: bold; } /* keyword */
+.nitcode .nc_o {} /* operator */
+.nitcode .nc_i {} /* standard identifier */
+.nitcode .nc_t { color: #445588; font-weight: bold; } /* type/class identifier */
+.nitcode .nc_a { color: #445588; font-style: italic; } /* old style attribute identifier */
+.nitcode .nc_l { color: #009999; } /* char and number literal */
+.nitcode .nc_s { color: #8F1546; } /* string literal */
+/* syntactic token usage. added because of their position in the AST */
+.nitcode .nc_ast { color: blue; } /* assert label */
+.nitcode .nc_la { color: blue; } /* break/continue label */
+.nitcode .nc_m { color: #445588; } /* module name */
+/* syntactic groups */
+.nitcode .nc_def { font-weight: bold; color: blue; } /* name used in a definition */
+.nitcode .nc_def.nc_a { color: blue; } /* name used in a attribute definition */
+.nitcode .nc_def.nc_t { color: blue; } /* name used in a class or vt definition */
+.nitcode .nc_ss { color: #9E6BEB; } /* superstrings */
+.nitcode .nc_cdef {} /* A whole class definition */
+.nitcode .nc_pdef {} /* A whole property definition */
+/* semantic token usage */
+.nitcode .nc_v { font-style: italic; } /* local variable or parameter */
+.nitcode .nc_vt { font-style: italic; } /* virtual type or formal type */
+.nitcode .nc_error { border: 1px red solid;} /* not used */
diff --git a/share/nitweb/stylesheets/nitweb_bootstrap.css b/share/nitweb/stylesheets/nitweb_bootstrap.css
new file mode 100644 (file)
index 0000000..e82732b
--- /dev/null
@@ -0,0 +1,5769 @@
+/*! normalize.css v3.0.0 | MIT License | git.io/normalize */\r
+html {\r
+  font-family: sans-serif;\r
+  -ms-text-size-adjust: 100%;\r
+  -webkit-text-size-adjust: 100%;\r
+}\r
+body {\r
+  margin: 0;\r
+}\r
+article,\r
+aside,\r
+details,\r
+figcaption,\r
+figure,\r
+footer,\r
+header,\r
+hgroup,\r
+main,\r
+nav,\r
+section,\r
+summary {\r
+  display: block;\r
+}\r
+audio,\r
+canvas,\r
+progress,\r
+video {\r
+  display: inline-block;\r
+  vertical-align: baseline;\r
+}\r
+audio:not([controls]) {\r
+  display: none;\r
+  height: 0;\r
+}\r
+[hidden],\r
+template {\r
+  display: none;\r
+}\r
+a {\r
+  background: transparent;\r
+}\r
+a:active,\r
+a:hover {\r
+  outline: 0;\r
+}\r
+abbr[title] {\r
+  border-bottom: 1px dotted;\r
+}\r
+b,\r
+strong {\r
+  font-weight: bold;\r
+}\r
+dfn {\r
+  font-style: italic;\r
+}\r
+h1 {\r
+  font-size: 2em;\r
+  margin: 0.67em 0;\r
+}\r
+mark {\r
+  background: #ff0;\r
+  color: #000;\r
+}\r
+small {\r
+  font-size: 80%;\r
+}\r
+sub,\r
+sup {\r
+  font-size: 75%;\r
+  line-height: 0;\r
+  position: relative;\r
+  vertical-align: baseline;\r
+}\r
+sup {\r
+  top: -0.5em;\r
+}\r
+sub {\r
+  bottom: -0.25em;\r
+}\r
+img {\r
+  border: 0;\r
+}\r
+svg:not(:root) {\r
+  overflow: hidden;\r
+}\r
+figure {\r
+  margin: 1em 40px;\r
+}\r
+hr {\r
+  -moz-box-sizing: content-box;\r
+  box-sizing: content-box;\r
+  height: 0;\r
+}\r
+pre {\r
+  overflow: auto;\r
+}\r
+code,\r
+kbd,\r
+pre,\r
+samp {\r
+  font-family: monospace, monospace;\r
+  font-size: 1em;\r
+}\r
+button,\r
+input,\r
+optgroup,\r
+select,\r
+textarea {\r
+  color: inherit;\r
+  font: inherit;\r
+  margin: 0;\r
+}\r
+button {\r
+  overflow: visible;\r
+}\r
+button,\r
+select {\r
+  text-transform: none;\r
+}\r
+button,\r
+html input[type="button"],\r
+input[type="reset"],\r
+input[type="submit"] {\r
+  -webkit-appearance: button;\r
+  cursor: pointer;\r
+}\r
+button[disabled],\r
+html input[disabled] {\r
+  cursor: default;\r
+}\r
+button::-moz-focus-inner,\r
+input::-moz-focus-inner {\r
+  border: 0;\r
+  padding: 0;\r
+}\r
+input {\r
+  line-height: normal;\r
+}\r
+input[type="checkbox"],\r
+input[type="radio"] {\r
+  box-sizing: border-box;\r
+  padding: 0;\r
+}\r
+input[type="number"]::-webkit-inner-spin-button,\r
+input[type="number"]::-webkit-outer-spin-button {\r
+  height: auto;\r
+}\r
+input[type="search"] {\r
+  -webkit-appearance: textfield;\r
+  -moz-box-sizing: content-box;\r
+  -webkit-box-sizing: content-box;\r
+  box-sizing: content-box;\r
+}\r
+input[type="search"]::-webkit-search-cancel-button,\r
+input[type="search"]::-webkit-search-decoration {\r
+  -webkit-appearance: none;\r
+}\r
+fieldset {\r
+  border: 1px solid #c0c0c0;\r
+  margin: 0 2px;\r
+  padding: 0.35em 0.625em 0.75em;\r
+}\r
+legend {\r
+  border: 0;\r
+  padding: 0;\r
+}\r
+textarea {\r
+  overflow: auto;\r
+}\r
+optgroup {\r
+  font-weight: bold;\r
+}\r
+table {\r
+  border-collapse: collapse;\r
+  border-spacing: 0;\r
+}\r
+td,\r
+th {\r
+  padding: 0;\r
+}\r
+@media print {\r
+  * {\r
+    text-shadow: none !important;\r
+    color: #000 !important;\r
+    background: transparent !important;\r
+    box-shadow: none !important;\r
+  }\r
+  a,\r
+  a:visited {\r
+    text-decoration: underline;\r
+  }\r
+  a[href]:after {\r
+    content: " (" attr(href) ")";\r
+  }\r
+  abbr[title]:after {\r
+    content: " (" attr(title) ")";\r
+  }\r
+  a[href^="javascript:"]:after,\r
+  a[href^="#"]:after {\r
+    content: "";\r
+  }\r
+  pre,\r
+  blockquote {\r
+    border: 1px solid #999;\r
+    page-break-inside: avoid;\r
+  }\r
+  thead {\r
+    display: table-header-group;\r
+  }\r
+  tr,\r
+  img {\r
+    page-break-inside: avoid;\r
+  }\r
+  img {\r
+    max-width: 100% !important;\r
+  }\r
+  p,\r
+  h2,\r
+  h3 {\r
+    orphans: 3;\r
+    widows: 3;\r
+  }\r
+  h2,\r
+  h3 {\r
+    page-break-after: avoid;\r
+  }\r
+  select {\r
+    background: #fff !important;\r
+  }\r
+  .navbar {\r
+    display: none;\r
+  }\r
+  .table td,\r
+  .table th {\r
+    background-color: #fff !important;\r
+  }\r
+  .btn > .caret,\r
+  .dropup > .btn > .caret {\r
+    border-top-color: #000 !important;\r
+  }\r
+  .label {\r
+    border: 1px solid #000;\r
+  }\r
+  .table {\r
+    border-collapse: collapse !important;\r
+  }\r
+  .table-bordered th,\r
+  .table-bordered td {\r
+    border: 1px solid #ddd !important;\r
+  }\r
+}\r
+* {\r
+  -webkit-box-sizing: border-box;\r
+  -moz-box-sizing: border-box;\r
+  box-sizing: border-box;\r
+}\r
+*:before,\r
+*:after {\r
+  -webkit-box-sizing: border-box;\r
+  -moz-box-sizing: border-box;\r
+  box-sizing: border-box;\r
+}\r
+html {\r
+  font-size: 62.5%;\r
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\r
+}\r
+body {\r
+  font-family: sans-serif;\r
+  font-size: 14px;\r
+  line-height: 1.428571429;\r
+  color: #333333;\r
+  background-color: #f2f2f2;\r
+}\r
+input,\r
+button,\r
+select,\r
+textarea {\r
+  font-family: inherit;\r
+  font-size: inherit;\r
+  line-height: inherit;\r
+}\r
+a {\r
+  color: #0d8921;\r
+  text-decoration: none;\r
+}\r
+a:hover,\r
+a:focus {\r
+  color: #064310;\r
+  text-decoration: underline;\r
+}\r
+a:focus {\r
+  outline: thin dotted;\r
+  outline: 5px auto -webkit-focus-ring-color;\r
+  outline-offset: -2px;\r
+}\r
+figure {\r
+  margin: 0;\r
+}\r
+img {\r
+  vertical-align: middle;\r
+}\r
+.img-responsive,\r
+.thumbnail > img,\r
+.thumbnail a > img,\r
+.carousel-inner > .item > img,\r
+.carousel-inner > .item > a > img {\r
+  display: block;\r
+  max-width: 100%;\r
+  height: auto;\r
+}\r
+.img-rounded {\r
+  border-radius: 0px;\r
+}\r
+.img-thumbnail {\r
+  padding: 4px;\r
+  line-height: 1.428571429;\r
+  background-color: #f2f2f2;\r
+  border: 1px solid #dddddd;\r
+  border-radius: 0px;\r
+  -webkit-transition: all 0.2s ease-in-out;\r
+  transition: all 0.2s ease-in-out;\r
+  display: inline-block;\r
+  max-width: 100%;\r
+  height: auto;\r
+}\r
+.img-circle {\r
+  border-radius: 50%;\r
+}\r
+hr {\r
+  margin-top: 20px;\r
+  margin-bottom: 20px;\r
+  border: 0;\r
+  border-top: 1px solid #eeeeee;\r
+}\r
+.sr-only {\r
+  position: absolute;\r
+  width: 1px;\r
+  height: 1px;\r
+  margin: -1px;\r
+  padding: 0;\r
+  overflow: hidden;\r
+  clip: rect(0, 0, 0, 0);\r
+  border: 0;\r
+}\r
+h1,\r
+h2,\r
+h3,\r
+h4,\r
+h5,\r
+h6,\r
+.h1,\r
+.h2,\r
+.h3,\r
+.h4,\r
+.h5,\r
+.h6 {\r
+  font-family: sans-serif;\r
+  font-weight: 500;\r
+  line-height: 1.1;\r
+  color: inherit;\r
+}\r
+h1 small,\r
+h2 small,\r
+h3 small,\r
+h4 small,\r
+h5 small,\r
+h6 small,\r
+.h1 small,\r
+.h2 small,\r
+.h3 small,\r
+.h4 small,\r
+.h5 small,\r
+.h6 small,\r
+h1 .small,\r
+h2 .small,\r
+h3 .small,\r
+h4 .small,\r
+h5 .small,\r
+h6 .small,\r
+.h1 .small,\r
+.h2 .small,\r
+.h3 .small,\r
+.h4 .small,\r
+.h5 .small,\r
+.h6 .small {\r
+  font-weight: normal;\r
+  line-height: 1;\r
+  color: #999999;\r
+}\r
+h1,\r
+.h1,\r
+h2,\r
+.h2,\r
+h3,\r
+.h3 {\r
+  margin-top: 20px;\r
+  margin-bottom: 10px;\r
+}\r
+h1 small,\r
+.h1 small,\r
+h2 small,\r
+.h2 small,\r
+h3 small,\r
+.h3 small,\r
+h1 .small,\r
+.h1 .small,\r
+h2 .small,\r
+.h2 .small,\r
+h3 .small,\r
+.h3 .small {\r
+  font-size: 65%;\r
+}\r
+h4,\r
+.h4,\r
+h5,\r
+.h5,\r
+h6,\r
+.h6 {\r
+  margin-top: 10px;\r
+  margin-bottom: 10px;\r
+}\r
+h4 small,\r
+.h4 small,\r
+h5 small,\r
+.h5 small,\r
+h6 small,\r
+.h6 small,\r
+h4 .small,\r
+.h4 .small,\r
+h5 .small,\r
+.h5 .small,\r
+h6 .small,\r
+.h6 .small {\r
+  font-size: 75%;\r
+}\r
+h1,\r
+.h1 {\r
+  font-size: 36px;\r
+}\r
+h2,\r
+.h2 {\r
+  font-size: 30px;\r
+}\r
+h3,\r
+.h3 {\r
+  font-size: 23px;\r
+}\r
+h4,\r
+.h4 {\r
+  font-size: 17px;\r
+}\r
+h5,\r
+.h5 {\r
+  font-size: 14px;\r
+}\r
+h6,\r
+.h6 {\r
+  font-size: 11px;\r
+}\r
+p {\r
+  margin: 0 0 10px;\r
+}\r
+.lead {\r
+  margin-bottom: 20px;\r
+  font-size: 16px;\r
+  font-weight: 200;\r
+  line-height: 1.4;\r
+}\r
+@media (min-width: 768px) {\r
+  .lead {\r
+    font-size: 21px;\r
+  }\r
+}\r
+small,\r
+.small {\r
+  font-size: 85%;\r
+}\r
+cite {\r
+  font-style: normal;\r
+}\r
+.text-left {\r
+  text-align: left;\r
+}\r
+.text-right {\r
+  text-align: right;\r
+}\r
+.text-center {\r
+  text-align: center;\r
+}\r
+.text-justify {\r
+  text-align: justify;\r
+}\r
+.text-muted {\r
+  color: #999999;\r
+}\r
+.text-primary {\r
+  color: #0d8921;\r
+}\r
+a.text-primary:hover {\r
+  color: #095a16;\r
+}\r
+.text-success {\r
+  color: #5cb85c;\r
+}\r
+a.text-success:hover {\r
+  color: #449d44;\r
+}\r
+.text-info {\r
+  color: #5bc0de;\r
+}\r
+a.text-info:hover {\r
+  color: #31b0d5;\r
+}\r
+.text-warning {\r
+  color: #f0ad4e;\r
+}\r
+a.text-warning:hover {\r
+  color: #ec971f;\r
+}\r
+.text-danger {\r
+  color: #d9534f;\r
+}\r
+a.text-danger:hover {\r
+  color: #c9302c;\r
+}\r
+.bg-primary {\r
+  color: #fff;\r
+  background-color: #0d8921;\r
+}\r
+a.bg-primary:hover {\r
+  background-color: #095a16;\r
+}\r
+.bg-success {\r
+  background-color: #dff0d8;\r
+}\r
+a.bg-success:hover {\r
+  background-color: #c1e2b3;\r
+}\r
+.bg-info {\r
+  background-color: #d9edf7;\r
+}\r
+a.bg-info:hover {\r
+  background-color: #afd9ee;\r
+}\r
+.bg-warning {\r
+  background-color: #fcf8e3;\r
+}\r
+a.bg-warning:hover {\r
+  background-color: #f7ecb5;\r
+}\r
+.bg-danger {\r
+  background-color: #f2dede;\r
+}\r
+a.bg-danger:hover {\r
+  background-color: #e4b9b9;\r
+}\r
+.page-header {\r
+  padding-bottom: 9px;\r
+  margin: 40px 0 20px;\r
+  border-bottom: 1px solid #eeeeee;\r
+}\r
+ul,\r
+ol {\r
+  margin-top: 0;\r
+  margin-bottom: 10px;\r
+}\r
+ul ul,\r
+ol ul,\r
+ul ol,\r
+ol ol {\r
+  margin-bottom: 0;\r
+}\r
+.list-unstyled {\r
+  padding-left: 0;\r
+  list-style: none;\r
+}\r
+.list-inline {\r
+  padding-left: 0;\r
+  list-style: none;\r
+}\r
+.list-inline > li {\r
+  display: inline-block;\r
+  padding-left: 5px;\r
+  padding-right: 5px;\r
+}\r
+.list-inline > li:first-child {\r
+  padding-left: 0;\r
+}\r
+dl {\r
+  margin-top: 0;\r
+  margin-bottom: 20px;\r
+}\r
+dt,\r
+dd {\r
+  line-height: 1.428571429;\r
+}\r
+dt {\r
+  font-weight: bold;\r
+}\r
+dd {\r
+  margin-left: 0;\r
+}\r
+@media (min-width: 768px) {\r
+  .dl-horizontal dt {\r
+    float: left;\r
+    width: 160px;\r
+    clear: left;\r
+    text-align: right;\r
+    overflow: hidden;\r
+    text-overflow: ellipsis;\r
+    white-space: nowrap;\r
+  }\r
+  .dl-horizontal dd {\r
+    margin-left: 180px;\r
+  }\r
+}\r
+abbr[title],\r
+abbr[data-original-title] {\r
+  cursor: help;\r
+  border-bottom: 1px dotted #999999;\r
+}\r
+.initialism {\r
+  font-size: 90%;\r
+  text-transform: uppercase;\r
+}\r
+blockquote {\r
+  padding: 10px 20px;\r
+  margin: 0 0 20px;\r
+  font-size: 17.5px;\r
+  border-left: 5px solid #eeeeee;\r
+}\r
+blockquote p:last-child,\r
+blockquote ul:last-child,\r
+blockquote ol:last-child {\r
+  margin-bottom: 0;\r
+}\r
+blockquote footer,\r
+blockquote small,\r
+blockquote .small {\r
+  display: block;\r
+  font-size: 80%;\r
+  line-height: 1.428571429;\r
+  color: #999999;\r
+}\r
+blockquote footer:before,\r
+blockquote small:before,\r
+blockquote .small:before {\r
+  content: '\2014 \00A0';\r
+}\r
+.blockquote-reverse,\r
+blockquote.pull-right {\r
+  padding-right: 15px;\r
+  padding-left: 0;\r
+  border-right: 5px solid #eeeeee;\r
+  border-left: 0;\r
+  text-align: right;\r
+}\r
+.blockquote-reverse footer:before,\r
+blockquote.pull-right footer:before,\r
+.blockquote-reverse small:before,\r
+blockquote.pull-right small:before,\r
+.blockquote-reverse .small:before,\r
+blockquote.pull-right .small:before {\r
+  content: '';\r
+}\r
+.blockquote-reverse footer:after,\r
+blockquote.pull-right footer:after,\r
+.blockquote-reverse small:after,\r
+blockquote.pull-right small:after,\r
+.blockquote-reverse .small:after,\r
+blockquote.pull-right .small:after {\r
+  content: '\00A0 \2014';\r
+}\r
+blockquote:before,\r
+blockquote:after {\r
+  content: "";\r
+}\r
+address {\r
+  margin-bottom: 20px;\r
+  font-style: normal;\r
+  line-height: 1.428571429;\r
+}\r
+code,\r
+kbd,\r
+pre,\r
+samp {\r
+  font-family: monospace;\r
+}\r
+code {\r
+  padding: 2px 4px;\r
+  font-size: 90%;\r
+  color: #c7254e;\r
+  background-color: #f9f2f4;\r
+  white-space: nowrap;\r
+  border-radius: 0px;\r
+}\r
+kbd {\r
+  padding: 2px 4px;\r
+  font-size: 90%;\r
+  color: #ffffff;\r
+  background-color: #333333;\r
+  border-radius: 0px;\r
+  box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);\r
+}\r
+pre {\r
+  display: block;\r
+  padding: 9.5px;\r
+  margin: 0 0 10px;\r
+  font-size: 13px;\r
+  line-height: 1.428571429;\r
+  word-break: break-all;\r
+  word-wrap: break-word;\r
+  color: #333333;\r
+  background-color: #f5f5f5;\r
+  border: 1px solid #cccccc;\r
+  border-radius: 0px;\r
+}\r
+pre code {\r
+  padding: 0;\r
+  font-size: inherit;\r
+  color: inherit;\r
+  white-space: pre-wrap;\r
+  background-color: transparent;\r
+  border-radius: 0;\r
+}\r
+.pre-scrollable {\r
+  max-height: 340px;\r
+  overflow-y: scroll;\r
+}\r
+.container {\r
+  margin-right: auto;\r
+  margin-left: auto;\r
+  padding-left: 15px;\r
+  padding-right: 15px;\r
+}\r
+@media (min-width: 768px) {\r
+  .container {\r
+    width: 750px;\r
+  }\r
+}\r
+@media (min-width: 992px) {\r
+  .container {\r
+    width: 970px;\r
+  }\r
+}\r
+@media (min-width: 1200px) {\r
+  .container {\r
+    width: 1170px;\r
+  }\r
+}\r
+.container-fluid {\r
+  margin-right: auto;\r
+  margin-left: auto;\r
+  padding-left: 15px;\r
+  padding-right: 15px;\r
+}\r
+.row {\r
+  margin-left: -15px;\r
+  margin-right: -15px;\r
+}\r
+.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {\r
+  position: relative;\r
+  min-height: 1px;\r
+  padding-left: 15px;\r
+  padding-right: 15px;\r
+}\r
+.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {\r
+  float: left;\r
+}\r
+.col-xs-12 {\r
+  width: 100%;\r
+}\r
+.col-xs-11 {\r
+  width: 91.66666666666666%;\r
+}\r
+.col-xs-10 {\r
+  width: 83.33333333333334%;\r
+}\r
+.col-xs-9 {\r
+  width: 75%;\r
+}\r
+.col-xs-8 {\r
+  width: 66.66666666666666%;\r
+}\r
+.col-xs-7 {\r
+  width: 58.333333333333336%;\r
+}\r
+.col-xs-6 {\r
+  width: 50%;\r
+}\r
+.col-xs-5 {\r
+  width: 41.66666666666667%;\r
+}\r
+.col-xs-4 {\r
+  width: 33.33333333333333%;\r
+}\r
+.col-xs-3 {\r
+  width: 25%;\r
+}\r
+.col-xs-2 {\r
+  width: 16.666666666666664%;\r
+}\r
+.col-xs-1 {\r
+  width: 8.333333333333332%;\r
+}\r
+.col-xs-pull-12 {\r
+  right: 100%;\r
+}\r
+.col-xs-pull-11 {\r
+  right: 91.66666666666666%;\r
+}\r
+.col-xs-pull-10 {\r
+  right: 83.33333333333334%;\r
+}\r
+.col-xs-pull-9 {\r
+  right: 75%;\r
+}\r
+.col-xs-pull-8 {\r
+  right: 66.66666666666666%;\r
+}\r
+.col-xs-pull-7 {\r
+  right: 58.333333333333336%;\r
+}\r
+.col-xs-pull-6 {\r
+  right: 50%;\r
+}\r
+.col-xs-pull-5 {\r
+  right: 41.66666666666667%;\r
+}\r
+.col-xs-pull-4 {\r
+  right: 33.33333333333333%;\r
+}\r
+.col-xs-pull-3 {\r
+  right: 25%;\r
+}\r
+.col-xs-pull-2 {\r
+  right: 16.666666666666664%;\r
+}\r
+.col-xs-pull-1 {\r
+  right: 8.333333333333332%;\r
+}\r
+.col-xs-pull-0 {\r
+  right: 0%;\r
+}\r
+.col-xs-push-12 {\r
+  left: 100%;\r
+}\r
+.col-xs-push-11 {\r
+  left: 91.66666666666666%;\r
+}\r
+.col-xs-push-10 {\r
+  left: 83.33333333333334%;\r
+}\r
+.col-xs-push-9 {\r
+  left: 75%;\r
+}\r
+.col-xs-push-8 {\r
+  left: 66.66666666666666%;\r
+}\r
+.col-xs-push-7 {\r
+  left: 58.333333333333336%;\r
+}\r
+.col-xs-push-6 {\r
+  left: 50%;\r
+}\r
+.col-xs-push-5 {\r
+  left: 41.66666666666667%;\r
+}\r
+.col-xs-push-4 {\r
+  left: 33.33333333333333%;\r
+}\r
+.col-xs-push-3 {\r
+  left: 25%;\r
+}\r
+.col-xs-push-2 {\r
+  left: 16.666666666666664%;\r
+}\r
+.col-xs-push-1 {\r
+  left: 8.333333333333332%;\r
+}\r
+.col-xs-push-0 {\r
+  left: 0%;\r
+}\r
+.col-xs-offset-12 {\r
+  margin-left: 100%;\r
+}\r
+.col-xs-offset-11 {\r
+  margin-left: 91.66666666666666%;\r
+}\r
+.col-xs-offset-10 {\r
+  margin-left: 83.33333333333334%;\r
+}\r
+.col-xs-offset-9 {\r
+  margin-left: 75%;\r
+}\r
+.col-xs-offset-8 {\r
+  margin-left: 66.66666666666666%;\r
+}\r
+.col-xs-offset-7 {\r
+  margin-left: 58.333333333333336%;\r
+}\r
+.col-xs-offset-6 {\r
+  margin-left: 50%;\r
+}\r
+.col-xs-offset-5 {\r
+  margin-left: 41.66666666666667%;\r
+}\r
+.col-xs-offset-4 {\r
+  margin-left: 33.33333333333333%;\r
+}\r
+.col-xs-offset-3 {\r
+  margin-left: 25%;\r
+}\r
+.col-xs-offset-2 {\r
+  margin-left: 16.666666666666664%;\r
+}\r
+.col-xs-offset-1 {\r
+  margin-left: 8.333333333333332%;\r
+}\r
+.col-xs-offset-0 {\r
+  margin-left: 0%;\r
+}\r
+@media (min-width: 768px) {\r
+  .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {\r
+    float: left;\r
+  }\r
+  .col-sm-12 {\r
+    width: 100%;\r
+  }\r
+  .col-sm-11 {\r
+    width: 91.66666666666666%;\r
+  }\r
+  .col-sm-10 {\r
+    width: 83.33333333333334%;\r
+  }\r
+  .col-sm-9 {\r
+    width: 75%;\r
+  }\r
+  .col-sm-8 {\r
+    width: 66.66666666666666%;\r
+  }\r
+  .col-sm-7 {\r
+    width: 58.333333333333336%;\r
+  }\r
+  .col-sm-6 {\r
+    width: 50%;\r
+  }\r
+  .col-sm-5 {\r
+    width: 41.66666666666667%;\r
+  }\r
+  .col-sm-4 {\r
+    width: 33.33333333333333%;\r
+  }\r
+  .col-sm-3 {\r
+    width: 25%;\r
+  }\r
+  .col-sm-2 {\r
+    width: 16.666666666666664%;\r
+  }\r
+  .col-sm-1 {\r
+    width: 8.333333333333332%;\r
+  }\r
+  .col-sm-pull-12 {\r
+    right: 100%;\r
+  }\r
+  .col-sm-pull-11 {\r
+    right: 91.66666666666666%;\r
+  }\r
+  .col-sm-pull-10 {\r
+    right: 83.33333333333334%;\r
+  }\r
+  .col-sm-pull-9 {\r
+    right: 75%;\r
+  }\r
+  .col-sm-pull-8 {\r
+    right: 66.66666666666666%;\r
+  }\r
+  .col-sm-pull-7 {\r
+    right: 58.333333333333336%;\r
+  }\r
+  .col-sm-pull-6 {\r
+    right: 50%;\r
+  }\r
+  .col-sm-pull-5 {\r
+    right: 41.66666666666667%;\r
+  }\r
+  .col-sm-pull-4 {\r
+    right: 33.33333333333333%;\r
+  }\r
+  .col-sm-pull-3 {\r
+    right: 25%;\r
+  }\r
+  .col-sm-pull-2 {\r
+    right: 16.666666666666664%;\r
+  }\r
+  .col-sm-pull-1 {\r
+    right: 8.333333333333332%;\r
+  }\r
+  .col-sm-pull-0 {\r
+    right: 0%;\r
+  }\r
+  .col-sm-push-12 {\r
+    left: 100%;\r
+  }\r
+  .col-sm-push-11 {\r
+    left: 91.66666666666666%;\r
+  }\r
+  .col-sm-push-10 {\r
+    left: 83.33333333333334%;\r
+  }\r
+  .col-sm-push-9 {\r
+    left: 75%;\r
+  }\r
+  .col-sm-push-8 {\r
+    left: 66.66666666666666%;\r
+  }\r
+  .col-sm-push-7 {\r
+    left: 58.333333333333336%;\r
+  }\r
+  .col-sm-push-6 {\r
+    left: 50%;\r
+  }\r
+  .col-sm-push-5 {\r
+    left: 41.66666666666667%;\r
+  }\r
+  .col-sm-push-4 {\r
+    left: 33.33333333333333%;\r
+  }\r
+  .col-sm-push-3 {\r
+    left: 25%;\r
+  }\r
+  .col-sm-push-2 {\r
+    left: 16.666666666666664%;\r
+  }\r
+  .col-sm-push-1 {\r
+    left: 8.333333333333332%;\r
+  }\r
+  .col-sm-push-0 {\r
+    left: 0%;\r
+  }\r
+  .col-sm-offset-12 {\r
+    margin-left: 100%;\r
+  }\r
+  .col-sm-offset-11 {\r
+    margin-left: 91.66666666666666%;\r
+  }\r
+  .col-sm-offset-10 {\r
+    margin-left: 83.33333333333334%;\r
+  }\r
+  .col-sm-offset-9 {\r
+    margin-left: 75%;\r
+  }\r
+  .col-sm-offset-8 {\r
+    margin-left: 66.66666666666666%;\r
+  }\r
+  .col-sm-offset-7 {\r
+    margin-left: 58.333333333333336%;\r
+  }\r
+  .col-sm-offset-6 {\r
+    margin-left: 50%;\r
+  }\r
+  .col-sm-offset-5 {\r
+    margin-left: 41.66666666666667%;\r
+  }\r
+  .col-sm-offset-4 {\r
+    margin-left: 33.33333333333333%;\r
+  }\r
+  .col-sm-offset-3 {\r
+    margin-left: 25%;\r
+  }\r
+  .col-sm-offset-2 {\r
+    margin-left: 16.666666666666664%;\r
+  }\r
+  .col-sm-offset-1 {\r
+    margin-left: 8.333333333333332%;\r
+  }\r
+  .col-sm-offset-0 {\r
+    margin-left: 0%;\r
+  }\r
+}\r
+@media (min-width: 992px) {\r
+  .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {\r
+    float: left;\r
+  }\r
+  .col-md-12 {\r
+    width: 100%;\r
+  }\r
+  .col-md-11 {\r
+    width: 91.66666666666666%;\r
+  }\r
+  .col-md-10 {\r
+    width: 83.33333333333334%;\r
+  }\r
+  .col-md-9 {\r
+    width: 75%;\r
+  }\r
+  .col-md-8 {\r
+    width: 66.66666666666666%;\r
+  }\r
+  .col-md-7 {\r
+    width: 58.333333333333336%;\r
+  }\r
+  .col-md-6 {\r
+    width: 50%;\r
+  }\r
+  .col-md-5 {\r
+    width: 41.66666666666667%;\r
+  }\r
+  .col-md-4 {\r
+    width: 33.33333333333333%;\r
+  }\r
+  .col-md-3 {\r
+    width: 25%;\r
+  }\r
+  .col-md-2 {\r
+    width: 16.666666666666664%;\r
+  }\r
+  .col-md-1 {\r
+    width: 8.333333333333332%;\r
+  }\r
+  .col-md-pull-12 {\r
+    right: 100%;\r
+  }\r
+  .col-md-pull-11 {\r
+    right: 91.66666666666666%;\r
+  }\r
+  .col-md-pull-10 {\r
+    right: 83.33333333333334%;\r
+  }\r
+  .col-md-pull-9 {\r
+    right: 75%;\r
+  }\r
+  .col-md-pull-8 {\r
+    right: 66.66666666666666%;\r
+  }\r
+  .col-md-pull-7 {\r
+    right: 58.333333333333336%;\r
+  }\r
+  .col-md-pull-6 {\r
+    right: 50%;\r
+  }\r
+  .col-md-pull-5 {\r
+    right: 41.66666666666667%;\r
+  }\r
+  .col-md-pull-4 {\r
+    right: 33.33333333333333%;\r
+  }\r
+  .col-md-pull-3 {\r
+    right: 25%;\r
+  }\r
+  .col-md-pull-2 {\r
+    right: 16.666666666666664%;\r
+  }\r
+  .col-md-pull-1 {\r
+    right: 8.333333333333332%;\r
+  }\r
+  .col-md-pull-0 {\r
+    right: 0%;\r
+  }\r
+  .col-md-push-12 {\r
+    left: 100%;\r
+  }\r
+  .col-md-push-11 {\r
+    left: 91.66666666666666%;\r
+  }\r
+  .col-md-push-10 {\r
+    left: 83.33333333333334%;\r
+  }\r
+  .col-md-push-9 {\r
+    left: 75%;\r
+  }\r
+  .col-md-push-8 {\r
+    left: 66.66666666666666%;\r
+  }\r
+  .col-md-push-7 {\r
+    left: 58.333333333333336%;\r
+  }\r
+  .col-md-push-6 {\r
+    left: 50%;\r
+  }\r
+  .col-md-push-5 {\r
+    left: 41.66666666666667%;\r
+  }\r
+  .col-md-push-4 {\r
+    left: 33.33333333333333%;\r
+  }\r
+  .col-md-push-3 {\r
+    left: 25%;\r
+  }\r
+  .col-md-push-2 {\r
+    left: 16.666666666666664%;\r
+  }\r
+  .col-md-push-1 {\r
+    left: 8.333333333333332%;\r
+  }\r
+  .col-md-push-0 {\r
+    left: 0%;\r
+  }\r
+  .col-md-offset-12 {\r
+    margin-left: 100%;\r
+  }\r
+  .col-md-offset-11 {\r
+    margin-left: 91.66666666666666%;\r
+  }\r
+  .col-md-offset-10 {\r
+    margin-left: 83.33333333333334%;\r
+  }\r
+  .col-md-offset-9 {\r
+    margin-left: 75%;\r
+  }\r
+  .col-md-offset-8 {\r
+    margin-left: 66.66666666666666%;\r
+  }\r
+  .col-md-offset-7 {\r
+    margin-left: 58.333333333333336%;\r
+  }\r
+  .col-md-offset-6 {\r
+    margin-left: 50%;\r
+  }\r
+  .col-md-offset-5 {\r
+    margin-left: 41.66666666666667%;\r
+  }\r
+  .col-md-offset-4 {\r
+    margin-left: 33.33333333333333%;\r
+  }\r
+  .col-md-offset-3 {\r
+    margin-left: 25%;\r
+  }\r
+  .col-md-offset-2 {\r
+    margin-left: 16.666666666666664%;\r
+  }\r
+  .col-md-offset-1 {\r
+    margin-left: 8.333333333333332%;\r
+  }\r
+  .col-md-offset-0 {\r
+    margin-left: 0%;\r
+  }\r
+}\r
+@media (min-width: 1200px) {\r
+  .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {\r
+    float: left;\r
+  }\r
+  .col-lg-12 {\r
+    width: 100%;\r
+  }\r
+  .col-lg-11 {\r
+    width: 91.66666666666666%;\r
+  }\r
+  .col-lg-10 {\r
+    width: 83.33333333333334%;\r
+  }\r
+  .col-lg-9 {\r
+    width: 75%;\r
+  }\r
+  .col-lg-8 {\r
+    width: 66.66666666666666%;\r
+  }\r
+  .col-lg-7 {\r
+    width: 58.333333333333336%;\r
+  }\r
+  .col-lg-6 {\r
+    width: 50%;\r
+  }\r
+  .col-lg-5 {\r
+    width: 41.66666666666667%;\r
+  }\r
+  .col-lg-4 {\r
+    width: 33.33333333333333%;\r
+  }\r
+  .col-lg-3 {\r
+    width: 25%;\r
+  }\r
+  .col-lg-2 {\r
+    width: 16.666666666666664%;\r
+  }\r
+  .col-lg-1 {\r
+    width: 8.333333333333332%;\r
+  }\r
+  .col-lg-pull-12 {\r
+    right: 100%;\r
+  }\r
+  .col-lg-pull-11 {\r
+    right: 91.66666666666666%;\r
+  }\r
+  .col-lg-pull-10 {\r
+    right: 83.33333333333334%;\r
+  }\r
+  .col-lg-pull-9 {\r
+    right: 75%;\r
+  }\r
+  .col-lg-pull-8 {\r
+    right: 66.66666666666666%;\r
+  }\r
+  .col-lg-pull-7 {\r
+    right: 58.333333333333336%;\r
+  }\r
+  .col-lg-pull-6 {\r
+    right: 50%;\r
+  }\r
+  .col-lg-pull-5 {\r
+    right: 41.66666666666667%;\r
+  }\r
+  .col-lg-pull-4 {\r
+    right: 33.33333333333333%;\r
+  }\r
+  .col-lg-pull-3 {\r
+    right: 25%;\r
+  }\r
+  .col-lg-pull-2 {\r
+    right: 16.666666666666664%;\r
+  }\r
+  .col-lg-pull-1 {\r
+    right: 8.333333333333332%;\r
+  }\r
+  .col-lg-pull-0 {\r
+    right: 0%;\r
+  }\r
+  .col-lg-push-12 {\r
+    left: 100%;\r
+  }\r
+  .col-lg-push-11 {\r
+    left: 91.66666666666666%;\r
+  }\r
+  .col-lg-push-10 {\r
+    left: 83.33333333333334%;\r
+  }\r
+  .col-lg-push-9 {\r
+    left: 75%;\r
+  }\r
+  .col-lg-push-8 {\r
+    left: 66.66666666666666%;\r
+  }\r
+  .col-lg-push-7 {\r
+    left: 58.333333333333336%;\r
+  }\r
+  .col-lg-push-6 {\r
+    left: 50%;\r
+  }\r
+  .col-lg-push-5 {\r
+    left: 41.66666666666667%;\r
+  }\r
+  .col-lg-push-4 {\r
+    left: 33.33333333333333%;\r
+  }\r
+  .col-lg-push-3 {\r
+    left: 25%;\r
+  }\r
+  .col-lg-push-2 {\r
+    left: 16.666666666666664%;\r
+  }\r
+  .col-lg-push-1 {\r
+    left: 8.333333333333332%;\r
+  }\r
+  .col-lg-push-0 {\r
+    left: 0%;\r
+  }\r
+  .col-lg-offset-12 {\r
+    margin-left: 100%;\r
+  }\r
+  .col-lg-offset-11 {\r
+    margin-left: 91.66666666666666%;\r
+  }\r
+  .col-lg-offset-10 {\r
+    margin-left: 83.33333333333334%;\r
+  }\r
+  .col-lg-offset-9 {\r
+    margin-left: 75%;\r
+  }\r
+  .col-lg-offset-8 {\r
+    margin-left: 66.66666666666666%;\r
+  }\r
+  .col-lg-offset-7 {\r
+    margin-left: 58.333333333333336%;\r
+  }\r
+  .col-lg-offset-6 {\r
+    margin-left: 50%;\r
+  }\r
+  .col-lg-offset-5 {\r
+    margin-left: 41.66666666666667%;\r
+  }\r
+  .col-lg-offset-4 {\r
+    margin-left: 33.33333333333333%;\r
+  }\r
+  .col-lg-offset-3 {\r
+    margin-left: 25%;\r
+  }\r
+  .col-lg-offset-2 {\r
+    margin-left: 16.666666666666664%;\r
+  }\r
+  .col-lg-offset-1 {\r
+    margin-left: 8.333333333333332%;\r
+  }\r
+  .col-lg-offset-0 {\r
+    margin-left: 0%;\r
+  }\r
+}\r
+table {\r
+  max-width: 100%;\r
+  background-color: transparent;\r
+}\r
+th {\r
+  text-align: left;\r
+}\r
+.table {\r
+  width: 100%;\r
+  margin-bottom: 20px;\r
+}\r
+.table > thead > tr > th,\r
+.table > tbody > tr > th,\r
+.table > tfoot > tr > th,\r
+.table > thead > tr > td,\r
+.table > tbody > tr > td,\r
+.table > tfoot > tr > td {\r
+  padding: 8px;\r
+  line-height: 1.428571429;\r
+  vertical-align: top;\r
+  border-top: 1px solid #dddddd;\r
+}\r
+.table > thead > tr > th {\r
+  vertical-align: bottom;\r
+  border-bottom: 2px solid #dddddd;\r
+}\r
+.table > caption + thead > tr:first-child > th,\r
+.table > colgroup + thead > tr:first-child > th,\r
+.table > thead:first-child > tr:first-child > th,\r
+.table > caption + thead > tr:first-child > td,\r
+.table > colgroup + thead > tr:first-child > td,\r
+.table > thead:first-child > tr:first-child > td {\r
+  border-top: 0;\r
+}\r
+.table > tbody + tbody {\r
+  border-top: 2px solid #dddddd;\r
+}\r
+.table .table {\r
+  background-color: #f2f2f2;\r
+}\r
+.table-condensed > thead > tr > th,\r
+.table-condensed > tbody > tr > th,\r
+.table-condensed > tfoot > tr > th,\r
+.table-condensed > thead > tr > td,\r
+.table-condensed > tbody > tr > td,\r
+.table-condensed > tfoot > tr > td {\r
+  padding: 5px;\r
+}\r
+.table-bordered {\r
+  border: 1px solid #dddddd;\r
+}\r
+.table-bordered > thead > tr > th,\r
+.table-bordered > tbody > tr > th,\r
+.table-bordered > tfoot > tr > th,\r
+.table-bordered > thead > tr > td,\r
+.table-bordered > tbody > tr > td,\r
+.table-bordered > tfoot > tr > td {\r
+  border: 1px solid #dddddd;\r
+}\r
+.table-bordered > thead > tr > th,\r
+.table-bordered > thead > tr > td {\r
+  border-bottom-width: 2px;\r
+}\r
+.table-striped > tbody > tr:nth-child(odd) > td,\r
+.table-striped > tbody > tr:nth-child(odd) > th {\r
+  background-color: #f9f9f9;\r
+}\r
+.table-hover > tbody > tr:hover > td,\r
+.table-hover > tbody > tr:hover > th {\r
+  background-color: #dbdbdb;\r
+}\r
+table col[class*="col-"] {\r
+  position: static;\r
+  float: none;\r
+  display: table-column;\r
+}\r
+table td[class*="col-"],\r
+table th[class*="col-"] {\r
+  position: static;\r
+  float: none;\r
+  display: table-cell;\r
+}\r
+.table > thead > tr > td.active,\r
+.table > tbody > tr > td.active,\r
+.table > tfoot > tr > td.active,\r
+.table > thead > tr > th.active,\r
+.table > tbody > tr > th.active,\r
+.table > tfoot > tr > th.active,\r
+.table > thead > tr.active > td,\r
+.table > tbody > tr.active > td,\r
+.table > tfoot > tr.active > td,\r
+.table > thead > tr.active > th,\r
+.table > tbody > tr.active > th,\r
+.table > tfoot > tr.active > th {\r
+  background-color: #dbdbdb;\r
+}\r
+.table-hover > tbody > tr > td.active:hover,\r
+.table-hover > tbody > tr > th.active:hover,\r
+.table-hover > tbody > tr.active:hover > td,\r
+.table-hover > tbody > tr.active:hover > th {\r
+  background-color: #cecece;\r
+}\r
+.table > thead > tr > td.success,\r
+.table > tbody > tr > td.success,\r
+.table > tfoot > tr > td.success,\r
+.table > thead > tr > th.success,\r
+.table > tbody > tr > th.success,\r
+.table > tfoot > tr > th.success,\r
+.table > thead > tr.success > td,\r
+.table > tbody > tr.success > td,\r
+.table > tfoot > tr.success > td,\r
+.table > thead > tr.success > th,\r
+.table > tbody > tr.success > th,\r
+.table > tfoot > tr.success > th {\r
+  background-color: #dff0d8;\r
+}\r
+.table-hover > tbody > tr > td.success:hover,\r
+.table-hover > tbody > tr > th.success:hover,\r
+.table-hover > tbody > tr.success:hover > td,\r
+.table-hover > tbody > tr.success:hover > th {\r
+  background-color: #d0e9c6;\r
+}\r
+.table > thead > tr > td.info,\r
+.table > tbody > tr > td.info,\r
+.table > tfoot > tr > td.info,\r
+.table > thead > tr > th.info,\r
+.table > tbody > tr > th.info,\r
+.table > tfoot > tr > th.info,\r
+.table > thead > tr.info > td,\r
+.table > tbody > tr.info > td,\r
+.table > tfoot > tr.info > td,\r
+.table > thead > tr.info > th,\r
+.table > tbody > tr.info > th,\r
+.table > tfoot > tr.info > th {\r
+  background-color: #d9edf7;\r
+}\r
+.table-hover > tbody > tr > td.info:hover,\r
+.table-hover > tbody > tr > th.info:hover,\r
+.table-hover > tbody > tr.info:hover > td,\r
+.table-hover > tbody > tr.info:hover > th {\r
+  background-color: #c4e3f3;\r
+}\r
+.table > thead > tr > td.warning,\r
+.table > tbody > tr > td.warning,\r
+.table > tfoot > tr > td.warning,\r
+.table > thead > tr > th.warning,\r
+.table > tbody > tr > th.warning,\r
+.table > tfoot > tr > th.warning,\r
+.table > thead > tr.warning > td,\r
+.table > tbody > tr.warning > td,\r
+.table > tfoot > tr.warning > td,\r
+.table > thead > tr.warning > th,\r
+.table > tbody > tr.warning > th,\r
+.table > tfoot > tr.warning > th {\r
+  background-color: #fcf8e3;\r
+}\r
+.table-hover > tbody > tr > td.warning:hover,\r
+.table-hover > tbody > tr > th.warning:hover,\r
+.table-hover > tbody > tr.warning:hover > td,\r
+.table-hover > tbody > tr.warning:hover > th {\r
+  background-color: #faf2cc;\r
+}\r
+.table > thead > tr > td.danger,\r
+.table > tbody > tr > td.danger,\r
+.table > tfoot > tr > td.danger,\r
+.table > thead > tr > th.danger,\r
+.table > tbody > tr > th.danger,\r
+.table > tfoot > tr > th.danger,\r
+.table > thead > tr.danger > td,\r
+.table > tbody > tr.danger > td,\r
+.table > tfoot > tr.danger > td,\r
+.table > thead > tr.danger > th,\r
+.table > tbody > tr.danger > th,\r
+.table > tfoot > tr.danger > th {\r
+  background-color: #f2dede;\r
+}\r
+.table-hover > tbody > tr > td.danger:hover,\r
+.table-hover > tbody > tr > th.danger:hover,\r
+.table-hover > tbody > tr.danger:hover > td,\r
+.table-hover > tbody > tr.danger:hover > th {\r
+  background-color: #ebcccc;\r
+}\r
+@media (max-width: 767px) {\r
+  .table-responsive {\r
+    width: 100%;\r
+    margin-bottom: 15px;\r
+    overflow-y: hidden;\r
+    overflow-x: scroll;\r
+    -ms-overflow-style: -ms-autohiding-scrollbar;\r
+    border: 1px solid #dddddd;\r
+    -webkit-overflow-scrolling: touch;\r
+  }\r
+  .table-responsive > .table {\r
+    margin-bottom: 0;\r
+  }\r
+  .table-responsive > .table > thead > tr > th,\r
+  .table-responsive > .table > tbody > tr > th,\r
+  .table-responsive > .table > tfoot > tr > th,\r
+  .table-responsive > .table > thead > tr > td,\r
+  .table-responsive > .table > tbody > tr > td,\r
+  .table-responsive > .table > tfoot > tr > td {\r
+    white-space: nowrap;\r
+  }\r
+  .table-responsive > .table-bordered {\r
+    border: 0;\r
+  }\r
+  .table-responsive > .table-bordered > thead > tr > th:first-child,\r
+  .table-responsive > .table-bordered > tbody > tr > th:first-child,\r
+  .table-responsive > .table-bordered > tfoot > tr > th:first-child,\r
+  .table-responsive > .table-bordered > thead > tr > td:first-child,\r
+  .table-responsive > .table-bordered > tbody > tr > td:first-child,\r
+  .table-responsive > .table-bordered > tfoot > tr > td:first-child {\r
+    border-left: 0;\r
+  }\r
+  .table-responsive > .table-bordered > thead > tr > th:last-child,\r
+  .table-responsive > .table-bordered > tbody > tr > th:last-child,\r
+  .table-responsive > .table-bordered > tfoot > tr > th:last-child,\r
+  .table-responsive > .table-bordered > thead > tr > td:last-child,\r
+  .table-responsive > .table-bordered > tbody > tr > td:last-child,\r
+  .table-responsive > .table-bordered > tfoot > tr > td:last-child {\r
+    border-right: 0;\r
+  }\r
+  .table-responsive > .table-bordered > tbody > tr:last-child > th,\r
+  .table-responsive > .table-bordered > tfoot > tr:last-child > th,\r
+  .table-responsive > .table-bordered > tbody > tr:last-child > td,\r
+  .table-responsive > .table-bordered > tfoot > tr:last-child > td {\r
+    border-bottom: 0;\r
+  }\r
+}\r
+fieldset {\r
+  padding: 0;\r
+  margin: 0;\r
+  border: 0;\r
+  min-width: 0;\r
+}\r
+legend {\r
+  display: block;\r
+  width: 100%;\r
+  padding: 0;\r
+  margin-bottom: 20px;\r
+  font-size: 21px;\r
+  line-height: inherit;\r
+  color: #333333;\r
+  border: 0;\r
+  border-bottom: 1px solid #e5e5e5;\r
+}\r
+label {\r
+  display: inline-block;\r
+  margin-bottom: 5px;\r
+  font-weight: bold;\r
+}\r
+input[type="search"] {\r
+  -webkit-box-sizing: border-box;\r
+  -moz-box-sizing: border-box;\r
+  box-sizing: border-box;\r
+}\r
+input[type="radio"],\r
+input[type="checkbox"] {\r
+  margin: 4px 0 0;\r
+  margin-top: 1px \9;\r
+  /* IE8-9 */\r
+\r
+  line-height: normal;\r
+}\r
+input[type="file"] {\r
+  display: block;\r
+}\r
+input[type="range"] {\r
+  display: block;\r
+  width: 100%;\r
+}\r
+select[multiple],\r
+select[size] {\r
+  height: auto;\r
+}\r
+input[type="file"]:focus,\r
+input[type="radio"]:focus,\r
+input[type="checkbox"]:focus {\r
+  outline: thin dotted;\r
+  outline: 5px auto -webkit-focus-ring-color;\r
+  outline-offset: -2px;\r
+}\r
+output {\r
+  display: block;\r
+  padding-top: 7px;\r
+  font-size: 14px;\r
+  line-height: 1.428571429;\r
+  color: #555555;\r
+}\r
+.form-control {\r
+  display: block;\r
+  width: 100%;\r
+  height: 34px;\r
+  padding: 6px 12px;\r
+  font-size: 14px;\r
+  line-height: 1.428571429;\r
+  color: #555555;\r
+  background-color: #ffffff;\r
+  background-image: none;\r
+  border: 1px solid #cccccc;\r
+  border-radius: 0px;\r
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\r
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\r
+  -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\r
+  transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\r
+}\r
+.form-control:focus {\r
+  border-color: #66afe9;\r
+  outline: 0;\r
+  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\r
+  box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\r
+}\r
+.form-control::-moz-placeholder {\r
+  color: #999999;\r
+  opacity: 1;\r
+}\r
+.form-control:-ms-input-placeholder {\r
+  color: #999999;\r
+}\r
+.form-control::-webkit-input-placeholder {\r
+  color: #999999;\r
+}\r
+.form-control[disabled],\r
+.form-control[readonly],\r
+fieldset[disabled] .form-control {\r
+  cursor: not-allowed;\r
+  background-color: #eeeeee;\r
+  opacity: 1;\r
+}\r
+textarea.form-control {\r
+  height: auto;\r
+}\r
+input[type="search"] {\r
+  -webkit-appearance: none;\r
+}\r
+input[type="date"] {\r
+  line-height: 34px;\r
+}\r
+.form-group {\r
+  margin-bottom: 15px;\r
+}\r
+.radio,\r
+.checkbox {\r
+  display: block;\r
+  min-height: 20px;\r
+  margin-top: 10px;\r
+  margin-bottom: 10px;\r
+  padding-left: 20px;\r
+}\r
+.radio label,\r
+.checkbox label {\r
+  display: inline;\r
+  font-weight: normal;\r
+  cursor: pointer;\r
+}\r
+.radio input[type="radio"],\r
+.radio-inline input[type="radio"],\r
+.checkbox input[type="checkbox"],\r
+.checkbox-inline input[type="checkbox"] {\r
+  float: left;\r
+  margin-left: -20px;\r
+}\r
+.radio + .radio,\r
+.checkbox + .checkbox {\r
+  margin-top: -5px;\r
+}\r
+.radio-inline,\r
+.checkbox-inline {\r
+  display: inline-block;\r
+  padding-left: 20px;\r
+  margin-bottom: 0;\r
+  vertical-align: middle;\r
+  font-weight: normal;\r
+  cursor: pointer;\r
+}\r
+.radio-inline + .radio-inline,\r
+.checkbox-inline + .checkbox-inline {\r
+  margin-top: 0;\r
+  margin-left: 10px;\r
+}\r
+input[type="radio"][disabled],\r
+input[type="checkbox"][disabled],\r
+.radio[disabled],\r
+.radio-inline[disabled],\r
+.checkbox[disabled],\r
+.checkbox-inline[disabled],\r
+fieldset[disabled] input[type="radio"],\r
+fieldset[disabled] input[type="checkbox"],\r
+fieldset[disabled] .radio,\r
+fieldset[disabled] .radio-inline,\r
+fieldset[disabled] .checkbox,\r
+fieldset[disabled] .checkbox-inline {\r
+  cursor: not-allowed;\r
+}\r
+.input-sm {\r
+  height: 30px;\r
+  padding: 5px 10px;\r
+  font-size: 12px;\r
+  line-height: 1.5;\r
+  border-radius: 0px;\r
+}\r
+select.input-sm {\r
+  height: 30px;\r
+  line-height: 30px;\r
+}\r
+textarea.input-sm,\r
+select[multiple].input-sm {\r
+  height: auto;\r
+}\r
+.input-lg {\r
+  height: 45px;\r
+  padding: 10px 16px;\r
+  font-size: 18px;\r
+  line-height: 1.33;\r
+  border-radius: 0px;\r
+}\r
+select.input-lg {\r
+  height: 45px;\r
+  line-height: 45px;\r
+}\r
+textarea.input-lg,\r
+select[multiple].input-lg {\r
+  height: auto;\r
+}\r
+.has-feedback {\r
+  position: relative;\r
+}\r
+.has-feedback .form-control {\r
+  padding-right: 42.5px;\r
+}\r
+.has-feedback .form-control-feedback {\r
+  position: absolute;\r
+  top: 25px;\r
+  right: 0;\r
+  display: block;\r
+  width: 34px;\r
+  height: 34px;\r
+  line-height: 34px;\r
+  text-align: center;\r
+}\r
+.has-success .help-block,\r
+.has-success .control-label,\r
+.has-success .radio,\r
+.has-success .checkbox,\r
+.has-success .radio-inline,\r
+.has-success .checkbox-inline {\r
+  color: #5cb85c;\r
+}\r
+.has-success .form-control {\r
+  border-color: #5cb85c;\r
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\r
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\r
+}\r
+.has-success .form-control:focus {\r
+  border-color: #449d44;\r
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #a3d7a3;\r
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #a3d7a3;\r
+}\r
+.has-success .input-group-addon {\r
+  color: #5cb85c;\r
+  border-color: #5cb85c;\r
+  background-color: #dff0d8;\r
+}\r
+.has-success .form-control-feedback {\r
+  color: #5cb85c;\r
+}\r
+.has-warning .help-block,\r
+.has-warning .control-label,\r
+.has-warning .radio,\r
+.has-warning .checkbox,\r
+.has-warning .radio-inline,\r
+.has-warning .checkbox-inline {\r
+  color: #f0ad4e;\r
+}\r
+.has-warning .form-control {\r
+  border-color: #f0ad4e;\r
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\r
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\r
+}\r
+.has-warning .form-control:focus {\r
+  border-color: #ec971f;\r
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #f8d9ac;\r
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #f8d9ac;\r
+}\r
+.has-warning .input-group-addon {\r
+  color: #f0ad4e;\r
+  border-color: #f0ad4e;\r
+  background-color: #fcf8e3;\r
+}\r
+.has-warning .form-control-feedback {\r
+  color: #f0ad4e;\r
+}\r
+.has-error .help-block,\r
+.has-error .control-label,\r
+.has-error .radio,\r
+.has-error .checkbox,\r
+.has-error .radio-inline,\r
+.has-error .checkbox-inline {\r
+  color: #d9534f;\r
+}\r
+.has-error .form-control {\r
+  border-color: #d9534f;\r
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\r
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\r
+}\r
+.has-error .form-control:focus {\r
+  border-color: #c9302c;\r
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #eba5a3;\r
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #eba5a3;\r
+}\r
+.has-error .input-group-addon {\r
+  color: #d9534f;\r
+  border-color: #d9534f;\r
+  background-color: #f2dede;\r
+}\r
+.has-error .form-control-feedback {\r
+  color: #d9534f;\r
+}\r
+.form-control-static {\r
+  margin-bottom: 0;\r
+}\r
+.help-block {\r
+  display: block;\r
+  margin-top: 5px;\r
+  margin-bottom: 10px;\r
+  color: #737373;\r
+}\r
+@media (min-width: 768px) {\r
+  .form-inline .form-group {\r
+    display: inline-block;\r
+    margin-bottom: 0;\r
+    vertical-align: middle;\r
+  }\r
+  .form-inline .form-control {\r
+    display: inline-block;\r
+    width: auto;\r
+    vertical-align: middle;\r
+  }\r
+  .form-inline .input-group > .form-control {\r
+    width: 100%;\r
+  }\r
+  .form-inline .control-label {\r
+    margin-bottom: 0;\r
+    vertical-align: middle;\r
+  }\r
+  .form-inline .radio,\r
+  .form-inline .checkbox {\r
+    display: inline-block;\r
+    margin-top: 0;\r
+    margin-bottom: 0;\r
+    padding-left: 0;\r
+    vertical-align: middle;\r
+  }\r
+  .form-inline .radio input[type="radio"],\r
+  .form-inline .checkbox input[type="checkbox"] {\r
+    float: none;\r
+    margin-left: 0;\r
+  }\r
+  .form-inline .has-feedback .form-control-feedback {\r
+    top: 0;\r
+  }\r
+}\r
+.form-horizontal .control-label,\r
+.form-horizontal .radio,\r
+.form-horizontal .checkbox,\r
+.form-horizontal .radio-inline,\r
+.form-horizontal .checkbox-inline {\r
+  margin-top: 0;\r
+  margin-bottom: 0;\r
+  padding-top: 7px;\r
+}\r
+.form-horizontal .radio,\r
+.form-horizontal .checkbox {\r
+  min-height: 27px;\r
+}\r
+.form-horizontal .form-group {\r
+  margin-left: -15px;\r
+  margin-right: -15px;\r
+}\r
+.form-horizontal .form-control-static {\r
+  padding-top: 7px;\r
+}\r
+@media (min-width: 768px) {\r
+  .form-horizontal .control-label {\r
+    text-align: right;\r
+  }\r
+}\r
+.form-horizontal .has-feedback .form-control-feedback {\r
+  top: 0;\r
+  right: 15px;\r
+}\r
+.btn {\r
+  display: inline-block;\r
+  margin-bottom: 0;\r
+  font-weight: normal;\r
+  text-align: center;\r
+  vertical-align: middle;\r
+  cursor: pointer;\r
+  background-image: none;\r
+  border: 1px solid transparent;\r
+  white-space: nowrap;\r
+  padding: 6px 12px;\r
+  font-size: 14px;\r
+  line-height: 1.428571429;\r
+  border-radius: 0px;\r
+  -webkit-user-select: none;\r
+  -moz-user-select: none;\r
+  -ms-user-select: none;\r
+  -o-user-select: none;\r
+  user-select: none;\r
+}\r
+.btn:focus {\r
+  outline: thin dotted;\r
+  outline: 5px auto -webkit-focus-ring-color;\r
+  outline-offset: -2px;\r
+}\r
+.btn:hover,\r
+.btn:focus {\r
+  color: #333333;\r
+  text-decoration: none;\r
+}\r
+.btn:active,\r
+.btn.active {\r
+  outline: 0;\r
+  background-image: none;\r
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\r
+  box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\r
+}\r
+.btn.disabled,\r
+.btn[disabled],\r
+fieldset[disabled] .btn {\r
+  cursor: not-allowed;\r
+  pointer-events: none;\r
+  opacity: 0.65;\r
+  filter: alpha(opacity=65);\r
+  -webkit-box-shadow: none;\r
+  box-shadow: none;\r
+}\r
+.btn-default {\r
+  color: #333333;\r
+  background-color: #ffffff;\r
+  border-color: #cccccc;\r
+}\r
+.btn-default:hover,\r
+.btn-default:focus,\r
+.btn-default:active,\r
+.btn-default.active,\r
+.open .dropdown-toggle.btn-default {\r
+  color: #333333;\r
+  background-color: #ebebeb;\r
+  border-color: #adadad;\r
+}\r
+.btn-default:active,\r
+.btn-default.active,\r
+.open .dropdown-toggle.btn-default {\r
+  background-image: none;\r
+}\r
+.btn-default.disabled,\r
+.btn-default[disabled],\r
+fieldset[disabled] .btn-default,\r
+.btn-default.disabled:hover,\r
+.btn-default[disabled]:hover,\r
+fieldset[disabled] .btn-default:hover,\r
+.btn-default.disabled:focus,\r
+.btn-default[disabled]:focus,\r
+fieldset[disabled] .btn-default:focus,\r
+.btn-default.disabled:active,\r
+.btn-default[disabled]:active,\r
+fieldset[disabled] .btn-default:active,\r
+.btn-default.disabled.active,\r
+.btn-default[disabled].active,\r
+fieldset[disabled] .btn-default.active {\r
+  background-color: #ffffff;\r
+  border-color: #cccccc;\r
+}\r
+.btn-default .badge {\r
+  color: #ffffff;\r
+  background-color: #333333;\r
+}\r
+.btn-primary {\r
+  color: #ffffff;\r
+  background-color: #0d8921;\r
+  border-color: #0b721b;\r
+}\r
+.btn-primary:hover,\r
+.btn-primary:focus,\r
+.btn-primary:active,\r
+.btn-primary.active,\r
+.open .dropdown-toggle.btn-primary {\r
+  color: #ffffff;\r
+  background-color: #096418;\r
+  border-color: #053a0e;\r
+}\r
+.btn-primary:active,\r
+.btn-primary.active,\r
+.open .dropdown-toggle.btn-primary {\r
+  background-image: none;\r
+}\r
+.btn-primary.disabled,\r
+.btn-primary[disabled],\r
+fieldset[disabled] .btn-primary,\r
+.btn-primary.disabled:hover,\r
+.btn-primary[disabled]:hover,\r
+fieldset[disabled] .btn-primary:hover,\r
+.btn-primary.disabled:focus,\r
+.btn-primary[disabled]:focus,\r
+fieldset[disabled] .btn-primary:focus,\r
+.btn-primary.disabled:active,\r
+.btn-primary[disabled]:active,\r
+fieldset[disabled] .btn-primary:active,\r
+.btn-primary.disabled.active,\r
+.btn-primary[disabled].active,\r
+fieldset[disabled] .btn-primary.active {\r
+  background-color: #0d8921;\r
+  border-color: #0b721b;\r
+}\r
+.btn-primary .badge {\r
+  color: #0d8921;\r
+  background-color: #ffffff;\r
+}\r
+.btn-success {\r
+  color: #ffffff;\r
+  background-color: #5cb85c;\r
+  border-color: #4cae4c;\r
+}\r
+.btn-success:hover,\r
+.btn-success:focus,\r
+.btn-success:active,\r
+.btn-success.active,\r
+.open .dropdown-toggle.btn-success {\r
+  color: #ffffff;\r
+  background-color: #47a447;\r
+  border-color: #398439;\r
+}\r
+.btn-success:active,\r
+.btn-success.active,\r
+.open .dropdown-toggle.btn-success {\r
+  background-image: none;\r
+}\r
+.btn-success.disabled,\r
+.btn-success[disabled],\r
+fieldset[disabled] .btn-success,\r
+.btn-success.disabled:hover,\r
+.btn-success[disabled]:hover,\r
+fieldset[disabled] .btn-success:hover,\r
+.btn-success.disabled:focus,\r
+.btn-success[disabled]:focus,\r
+fieldset[disabled] .btn-success:focus,\r
+.btn-success.disabled:active,\r
+.btn-success[disabled]:active,\r
+fieldset[disabled] .btn-success:active,\r
+.btn-success.disabled.active,\r
+.btn-success[disabled].active,\r
+fieldset[disabled] .btn-success.active {\r
+  background-color: #5cb85c;\r
+  border-color: #4cae4c;\r
+}\r
+.btn-success .badge {\r
+  color: #5cb85c;\r
+  background-color: #ffffff;\r
+}\r
+.btn-info {\r
+  color: #ffffff;\r
+  background-color: #5bc0de;\r
+  border-color: #46b8da;\r
+}\r
+.btn-info:hover,\r
+.btn-info:focus,\r
+.btn-info:active,\r
+.btn-info.active,\r
+.open .dropdown-toggle.btn-info {\r
+  color: #ffffff;\r
+  background-color: #39b3d7;\r
+  border-color: #269abc;\r
+}\r
+.btn-info:active,\r
+.btn-info.active,\r
+.open .dropdown-toggle.btn-info {\r
+  background-image: none;\r
+}\r
+.btn-info.disabled,\r
+.btn-info[disabled],\r
+fieldset[disabled] .btn-info,\r
+.btn-info.disabled:hover,\r
+.btn-info[disabled]:hover,\r
+fieldset[disabled] .btn-info:hover,\r
+.btn-info.disabled:focus,\r
+.btn-info[disabled]:focus,\r
+fieldset[disabled] .btn-info:focus,\r
+.btn-info.disabled:active,\r
+.btn-info[disabled]:active,\r
+fieldset[disabled] .btn-info:active,\r
+.btn-info.disabled.active,\r
+.btn-info[disabled].active,\r
+fieldset[disabled] .btn-info.active {\r
+  background-color: #5bc0de;\r
+  border-color: #46b8da;\r
+}\r
+.btn-info .badge {\r
+  color: #5bc0de;\r
+  background-color: #ffffff;\r
+}\r
+.btn-warning {\r
+  color: #ffffff;\r
+  background-color: #f0ad4e;\r
+  border-color: #eea236;\r
+}\r
+.btn-warning:hover,\r
+.btn-warning:focus,\r
+.btn-warning:active,\r
+.btn-warning.active,\r
+.open .dropdown-toggle.btn-warning {\r
+  color: #ffffff;\r
+  background-color: #ed9c28;\r
+  border-color: #d58512;\r
+}\r
+.btn-warning:active,\r
+.btn-warning.active,\r
+.open .dropdown-toggle.btn-warning {\r
+  background-image: none;\r
+}\r
+.btn-warning.disabled,\r
+.btn-warning[disabled],\r
+fieldset[disabled] .btn-warning,\r
+.btn-warning.disabled:hover,\r
+.btn-warning[disabled]:hover,\r
+fieldset[disabled] .btn-warning:hover,\r
+.btn-warning.disabled:focus,\r
+.btn-warning[disabled]:focus,\r
+fieldset[disabled] .btn-warning:focus,\r
+.btn-warning.disabled:active,\r
+.btn-warning[disabled]:active,\r
+fieldset[disabled] .btn-warning:active,\r
+.btn-warning.disabled.active,\r
+.btn-warning[disabled].active,\r
+fieldset[disabled] .btn-warning.active {\r
+  background-color: #f0ad4e;\r
+  border-color: #eea236;\r
+}\r
+.btn-warning .badge {\r
+  color: #f0ad4e;\r
+  background-color: #ffffff;\r
+}\r
+.btn-danger {\r
+  color: #ffffff;\r
+  background-color: #d9534f;\r
+  border-color: #d43f3a;\r
+}\r
+.btn-danger:hover,\r
+.btn-danger:focus,\r
+.btn-danger:active,\r
+.btn-danger.active,\r
+.open .dropdown-toggle.btn-danger {\r
+  color: #ffffff;\r
+  background-color: #d2322d;\r
+  border-color: #ac2925;\r
+}\r
+.btn-danger:active,\r
+.btn-danger.active,\r
+.open .dropdown-toggle.btn-danger {\r
+  background-image: none;\r
+}\r
+.btn-danger.disabled,\r
+.btn-danger[disabled],\r
+fieldset[disabled] .btn-danger,\r
+.btn-danger.disabled:hover,\r
+.btn-danger[disabled]:hover,\r
+fieldset[disabled] .btn-danger:hover,\r
+.btn-danger.disabled:focus,\r
+.btn-danger[disabled]:focus,\r
+fieldset[disabled] .btn-danger:focus,\r
+.btn-danger.disabled:active,\r
+.btn-danger[disabled]:active,\r
+fieldset[disabled] .btn-danger:active,\r
+.btn-danger.disabled.active,\r
+.btn-danger[disabled].active,\r
+fieldset[disabled] .btn-danger.active {\r
+  background-color: #d9534f;\r
+  border-color: #d43f3a;\r
+}\r
+.btn-danger .badge {\r
+  color: #d9534f;\r
+  background-color: #ffffff;\r
+}\r
+.btn-link {\r
+  color: #0d8921;\r
+  font-weight: normal;\r
+  cursor: pointer;\r
+  border-radius: 0;\r
+}\r
+.btn-link,\r
+.btn-link:active,\r
+.btn-link[disabled],\r
+fieldset[disabled] .btn-link {\r
+  background-color: transparent;\r
+  -webkit-box-shadow: none;\r
+  box-shadow: none;\r
+}\r
+.btn-link,\r
+.btn-link:hover,\r
+.btn-link:focus,\r
+.btn-link:active {\r
+  border-color: transparent;\r
+}\r
+.btn-link:hover,\r
+.btn-link:focus {\r
+  color: #064310;\r
+  text-decoration: underline;\r
+  background-color: transparent;\r
+}\r
+.btn-link[disabled]:hover,\r
+fieldset[disabled] .btn-link:hover,\r
+.btn-link[disabled]:focus,\r
+fieldset[disabled] .btn-link:focus {\r
+  color: #999999;\r
+  text-decoration: none;\r
+}\r
+.btn-lg,\r
+.btn-group-lg > .btn {\r
+  padding: 10px 16px;\r
+  font-size: 18px;\r
+  line-height: 1.33;\r
+  border-radius: 0px;\r
+}\r
+.btn-sm,\r
+.btn-group-sm > .btn {\r
+  padding: 5px 10px;\r
+  font-size: 12px;\r
+  line-height: 1.5;\r
+  border-radius: 0px;\r
+}\r
+.btn-xs,\r
+.btn-group-xs > .btn {\r
+  padding: 1px 5px;\r
+  font-size: 12px;\r
+  line-height: 1.5;\r
+  border-radius: 0px;\r
+}\r
+.btn-block {\r
+  display: block;\r
+  width: 100%;\r
+  padding-left: 0;\r
+  padding-right: 0;\r
+}\r
+.btn-block + .btn-block {\r
+  margin-top: 5px;\r
+}\r
+input[type="submit"].btn-block,\r
+input[type="reset"].btn-block,\r
+input[type="button"].btn-block {\r
+  width: 100%;\r
+}\r
+.fade {\r
+  opacity: 0;\r
+  -webkit-transition: opacity 0.15s linear;\r
+  transition: opacity 0.15s linear;\r
+}\r
+.fade.in {\r
+  opacity: 1;\r
+}\r
+.collapse {\r
+  display: none;\r
+}\r
+.collapse.in {\r
+  display: block;\r
+}\r
+.collapsing {\r
+  position: relative;\r
+  height: 0;\r
+  overflow: hidden;\r
+  -webkit-transition: height 0.35s ease;\r
+  transition: height 0.35s ease;\r
+}\r
+@font-face {\r
+  font-family: 'Glyphicons Halflings';\r
+  src: url('https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/fonts/glyphicons-halflings-regular.eot');\r
+  src: url('https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/fonts/glyphicons-halflings-regular.woff') format('woff'), url('https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');\r
+}\r
+.glyphicon {\r
+  position: relative;\r
+  top: 1px;\r
+  display: inline-block;\r
+  font-family: 'Glyphicons Halflings';\r
+  font-style: normal;\r
+  font-weight: normal;\r
+  line-height: 1;\r
+  -webkit-font-smoothing: antialiased;\r
+  -moz-osx-font-smoothing: grayscale;\r
+}\r
+.glyphicon-asterisk:before {\r
+  content: "\2a";\r
+}\r
+.glyphicon-plus:before {\r
+  content: "\2b";\r
+}\r
+.glyphicon-euro:before {\r
+  content: "\20ac";\r
+}\r
+.glyphicon-minus:before {\r
+  content: "\2212";\r
+}\r
+.glyphicon-cloud:before {\r
+  content: "\2601";\r
+}\r
+.glyphicon-envelope:before {\r
+  content: "\2709";\r
+}\r
+.glyphicon-pencil:before {\r
+  content: "\270f";\r
+}\r
+.glyphicon-glass:before {\r
+  content: "\e001";\r
+}\r
+.glyphicon-music:before {\r
+  content: "\e002";\r
+}\r
+.glyphicon-search:before {\r
+  content: "\e003";\r
+}\r
+.glyphicon-heart:before {\r
+  content: "\e005";\r
+}\r
+.glyphicon-star:before {\r
+  content: "\e006";\r
+}\r
+.glyphicon-star-empty:before {\r
+  content: "\e007";\r
+}\r
+.glyphicon-user:before {\r
+  content: "\e008";\r
+}\r
+.glyphicon-film:before {\r
+  content: "\e009";\r
+}\r
+.glyphicon-th-large:before {\r
+  content: "\e010";\r
+}\r
+.glyphicon-th:before {\r
+  content: "\e011";\r
+}\r
+.glyphicon-th-list:before {\r
+  content: "\e012";\r
+}\r
+.glyphicon-ok:before {\r
+  content: "\e013";\r
+}\r
+.glyphicon-remove:before {\r
+  content: "\e014";\r
+}\r
+.glyphicon-zoom-in:before {\r
+  content: "\e015";\r
+}\r
+.glyphicon-zoom-out:before {\r
+  content: "\e016";\r
+}\r
+.glyphicon-off:before {\r
+  content: "\e017";\r
+}\r
+.glyphicon-signal:before {\r
+  content: "\e018";\r
+}\r
+.glyphicon-cog:before {\r
+  content: "\e019";\r
+}\r
+.glyphicon-trash:before {\r
+  content: "\e020";\r
+}\r
+.glyphicon-home:before {\r
+  content: "\e021";\r
+}\r
+.glyphicon-file:before {\r
+  content: "\e022";\r
+}\r
+.glyphicon-time:before {\r
+  content: "\e023";\r
+}\r
+.glyphicon-road:before {\r
+  content: "\e024";\r
+}\r
+.glyphicon-download-alt:before {\r
+  content: "\e025";\r
+}\r
+.glyphicon-download:before {\r
+  content: "\e026";\r
+}\r
+.glyphicon-upload:before {\r
+  content: "\e027";\r
+}\r
+.glyphicon-inbox:before {\r
+  content: "\e028";\r
+}\r
+.glyphicon-play-circle:before {\r
+  content: "\e029";\r
+}\r
+.glyphicon-repeat:before {\r
+  content: "\e030";\r
+}\r
+.glyphicon-refresh:before {\r
+  content: "\e031";\r
+}\r
+.glyphicon-list-alt:before {\r
+  content: "\e032";\r
+}\r
+.glyphicon-lock:before {\r
+  content: "\e033";\r
+}\r
+.glyphicon-flag:before {\r
+  content: "\e034";\r
+}\r
+.glyphicon-headphones:before {\r
+  content: "\e035";\r
+}\r
+.glyphicon-volume-off:before {\r
+  content: "\e036";\r
+}\r
+.glyphicon-volume-down:before {\r
+  content: "\e037";\r
+}\r
+.glyphicon-volume-up:before {\r
+  content: "\e038";\r
+}\r
+.glyphicon-qrcode:before {\r
+  content: "\e039";\r
+}\r
+.glyphicon-barcode:before {\r
+  content: "\e040";\r
+}\r
+.glyphicon-tag:before {\r
+  content: "\e041";\r
+}\r
+.glyphicon-tags:before {\r
+  content: "\e042";\r
+}\r
+.glyphicon-book:before {\r
+  content: "\e043";\r
+}\r
+.glyphicon-bookmark:before {\r
+  content: "\e044";\r
+}\r
+.glyphicon-print:before {\r
+  content: "\e045";\r
+}\r
+.glyphicon-camera:before {\r
+  content: "\e046";\r
+}\r
+.glyphicon-font:before {\r
+  content: "\e047";\r
+}\r
+.glyphicon-bold:before {\r
+  content: "\e048";\r
+}\r
+.glyphicon-italic:before {\r
+  content: "\e049";\r
+}\r
+.glyphicon-text-height:before {\r
+  content: "\e050";\r
+}\r
+.glyphicon-text-width:before {\r
+  content: "\e051";\r
+}\r
+.glyphicon-align-left:before {\r
+  content: "\e052";\r
+}\r
+.glyphicon-align-center:before {\r
+  content: "\e053";\r
+}\r
+.glyphicon-align-right:before {\r
+  content: "\e054";\r
+}\r
+.glyphicon-align-justify:before {\r
+  content: "\e055";\r
+}\r
+.glyphicon-list:before {\r
+  content: "\e056";\r
+}\r
+.glyphicon-indent-left:before {\r
+  content: "\e057";\r
+}\r
+.glyphicon-indent-right:before {\r
+  content: "\e058";\r
+}\r
+.glyphicon-facetime-video:before {\r
+  content: "\e059";\r
+}\r
+.glyphicon-picture:before {\r
+  content: "\e060";\r
+}\r
+.glyphicon-map-marker:before {\r
+  content: "\e062";\r
+}\r
+.glyphicon-adjust:before {\r
+  content: "\e063";\r
+}\r
+.glyphicon-tint:before {\r
+  content: "\e064";\r
+}\r
+.glyphicon-edit:before {\r
+  content: "\e065";\r
+}\r
+.glyphicon-share:before {\r
+  content: "\e066";\r
+}\r
+.glyphicon-check:before {\r
+  content: "\e067";\r
+}\r
+.glyphicon-move:before {\r
+  content: "\e068";\r
+}\r
+.glyphicon-step-backward:before {\r
+  content: "\e069";\r
+}\r
+.glyphicon-fast-backward:before {\r
+  content: "\e070";\r
+}\r
+.glyphicon-backward:before {\r
+  content: "\e071";\r
+}\r
+.glyphicon-play:before {\r
+  content: "\e072";\r
+}\r
+.glyphicon-pause:before {\r
+  content: "\e073";\r
+}\r
+.glyphicon-stop:before {\r
+  content: "\e074";\r
+}\r
+.glyphicon-forward:before {\r
+  content: "\e075";\r
+}\r
+.glyphicon-fast-forward:before {\r
+  content: "\e076";\r
+}\r
+.glyphicon-step-forward:before {\r
+  content: "\e077";\r
+}\r
+.glyphicon-eject:before {\r
+  content: "\e078";\r
+}\r
+.glyphicon-chevron-left:before {\r
+  content: "\e079";\r
+}\r
+.glyphicon-chevron-right:before {\r
+  content: "\e080";\r
+}\r
+.glyphicon-plus-sign:before {\r
+  content: "\e081";\r
+}\r
+.glyphicon-minus-sign:before {\r
+  content: "\e082";\r
+}\r
+.glyphicon-remove-sign:before {\r
+  content: "\e083";\r
+}\r
+.glyphicon-ok-sign:before {\r
+  content: "\e084";\r
+}\r
+.glyphicon-question-sign:before {\r
+  content: "\e085";\r
+}\r
+.glyphicon-info-sign:before {\r
+  content: "\e086";\r
+}\r
+.glyphicon-screenshot:before {\r
+  content: "\e087";\r
+}\r
+.glyphicon-remove-circle:before {\r
+  content: "\e088";\r
+}\r
+.glyphicon-ok-circle:before {\r
+  content: "\e089";\r
+}\r
+.glyphicon-ban-circle:before {\r
+  content: "\e090";\r
+}\r
+.glyphicon-arrow-left:before {\r
+  content: "\e091";\r
+}\r
+.glyphicon-arrow-right:before {\r
+  content: "\e092";\r
+}\r
+.glyphicon-arrow-up:before {\r
+  content: "\e093";\r
+}\r
+.glyphicon-arrow-down:before {\r
+  content: "\e094";\r
+}\r
+.glyphicon-share-alt:before {\r
+  content: "\e095";\r
+}\r
+.glyphicon-resize-full:before {\r
+  content: "\e096";\r
+}\r
+.glyphicon-resize-small:before {\r
+  content: "\e097";\r
+}\r
+.glyphicon-exclamation-sign:before {\r
+  content: "\e101";\r
+}\r
+.glyphicon-gift:before {\r
+  content: "\e102";\r
+}\r
+.glyphicon-leaf:before {\r
+  content: "\e103";\r
+}\r
+.glyphicon-fire:before {\r
+  content: "\e104";\r
+}\r
+.glyphicon-eye-open:before {\r
+  content: "\e105";\r
+}\r
+.glyphicon-eye-close:before {\r
+  content: "\e106";\r
+}\r
+.glyphicon-warning-sign:before {\r
+  content: "\e107";\r
+}\r
+.glyphicon-plane:before {\r
+  content: "\e108";\r
+}\r
+.glyphicon-calendar:before {\r
+  content: "\e109";\r
+}\r
+.glyphicon-random:before {\r
+  content: "\e110";\r
+}\r
+.glyphicon-comment:before {\r
+  content: "\e111";\r
+}\r
+.glyphicon-magnet:before {\r
+  content: "\e112";\r
+}\r
+.glyphicon-chevron-up:before {\r
+  content: "\e113";\r
+}\r
+.glyphicon-chevron-down:before {\r
+  content: "\e114";\r
+}\r
+.glyphicon-retweet:before {\r
+  content: "\e115";\r
+}\r
+.glyphicon-shopping-cart:before {\r
+  content: "\e116";\r
+}\r
+.glyphicon-folder-close:before {\r
+  content: "\e117";\r
+}\r
+.glyphicon-folder-open:before {\r
+  content: "\e118";\r
+}\r
+.glyphicon-resize-vertical:before {\r
+  content: "\e119";\r
+}\r
+.glyphicon-resize-horizontal:before {\r
+  content: "\e120";\r
+}\r
+.glyphicon-hdd:before {\r
+  content: "\e121";\r
+}\r
+.glyphicon-bullhorn:before {\r
+  content: "\e122";\r
+}\r
+.glyphicon-bell:before {\r
+  content: "\e123";\r
+}\r
+.glyphicon-certificate:before {\r
+  content: "\e124";\r
+}\r
+.glyphicon-thumbs-up:before {\r
+  content: "\e125";\r
+}\r
+.glyphicon-thumbs-down:before {\r
+  content: "\e126";\r
+}\r
+.glyphicon-hand-right:before {\r
+  content: "\e127";\r
+}\r
+.glyphicon-hand-left:before {\r
+  content: "\e128";\r
+}\r
+.glyphicon-hand-up:before {\r
+  content: "\e129";\r
+}\r
+.glyphicon-hand-down:before {\r
+  content: "\e130";\r
+}\r
+.glyphicon-circle-arrow-right:before {\r
+  content: "\e131";\r
+}\r
+.glyphicon-circle-arrow-left:before {\r
+  content: "\e132";\r
+}\r
+.glyphicon-circle-arrow-up:before {\r
+  content: "\e133";\r
+}\r
+.glyphicon-circle-arrow-down:before {\r
+  content: "\e134";\r
+}\r
+.glyphicon-globe:before {\r
+  content: "\e135";\r
+}\r
+.glyphicon-wrench:before {\r
+  content: "\e136";\r
+}\r
+.glyphicon-tasks:before {\r
+  content: "\e137";\r
+}\r
+.glyphicon-filter:before {\r
+  content: "\e138";\r
+}\r
+.glyphicon-briefcase:before {\r
+  content: "\e139";\r
+}\r
+.glyphicon-fullscreen:before {\r
+  content: "\e140";\r
+}\r
+.glyphicon-dashboard:before {\r
+  content: "\e141";\r
+}\r
+.glyphicon-paperclip:before {\r
+  content: "\e142";\r
+}\r
+.glyphicon-heart-empty:before {\r
+  content: "\e143";\r
+}\r
+.glyphicon-link:before {\r
+  content: "\e144";\r
+}\r
+.glyphicon-phone:before {\r
+  content: "\e145";\r
+}\r
+.glyphicon-pushpin:before {\r
+  content: "\e146";\r
+}\r
+.glyphicon-usd:before {\r
+  content: "\e148";\r
+}\r
+.glyphicon-gbp:before {\r
+  content: "\e149";\r
+}\r
+.glyphicon-sort:before {\r
+  content: "\e150";\r
+}\r
+.glyphicon-sort-by-alphabet:before {\r
+  content: "\e151";\r
+}\r
+.glyphicon-sort-by-alphabet-alt:before {\r
+  content: "\e152";\r
+}\r
+.glyphicon-sort-by-order:before {\r
+  content: "\e153";\r
+}\r
+.glyphicon-sort-by-order-alt:before {\r
+  content: "\e154";\r
+}\r
+.glyphicon-sort-by-attributes:before {\r
+  content: "\e155";\r
+}\r
+.glyphicon-sort-by-attributes-alt:before {\r
+  content: "\e156";\r
+}\r
+.glyphicon-unchecked:before {\r
+  content: "\e157";\r
+}\r
+.glyphicon-expand:before {\r
+  content: "\e158";\r
+}\r
+.glyphicon-collapse-down:before {\r
+  content: "\e159";\r
+}\r
+.glyphicon-collapse-up:before {\r
+  content: "\e160";\r
+}\r
+.glyphicon-log-in:before {\r
+  content: "\e161";\r
+}\r
+.glyphicon-flash:before {\r
+  content: "\e162";\r
+}\r
+.glyphicon-log-out:before {\r
+  content: "\e163";\r
+}\r
+.glyphicon-new-window:before {\r
+  content: "\e164";\r
+}\r
+.glyphicon-record:before {\r
+  content: "\e165";\r
+}\r
+.glyphicon-save:before {\r
+  content: "\e166";\r
+}\r
+.glyphicon-open:before {\r
+  content: "\e167";\r
+}\r
+.glyphicon-saved:before {\r
+  content: "\e168";\r
+}\r
+.glyphicon-import:before {\r
+  content: "\e169";\r
+}\r
+.glyphicon-export:before {\r
+  content: "\e170";\r
+}\r
+.glyphicon-send:before {\r
+  content: "\e171";\r
+}\r
+.glyphicon-floppy-disk:before {\r
+  content: "\e172";\r
+}\r
+.glyphicon-floppy-saved:before {\r
+  content: "\e173";\r
+}\r
+.glyphicon-floppy-remove:before {\r
+  content: "\e174";\r
+}\r
+.glyphicon-floppy-save:before {\r
+  content: "\e175";\r
+}\r
+.glyphicon-floppy-open:before {\r
+  content: "\e176";\r
+}\r
+.glyphicon-credit-card:before {\r
+  content: "\e177";\r
+}\r
+.glyphicon-transfer:before {\r
+  content: "\e178";\r
+}\r
+.glyphicon-cutlery:before {\r
+  content: "\e179";\r
+}\r
+.glyphicon-header:before {\r
+  content: "\e180";\r
+}\r
+.glyphicon-compressed:before {\r
+  content: "\e181";\r
+}\r
+.glyphicon-earphone:before {\r
+  content: "\e182";\r
+}\r
+.glyphicon-phone-alt:before {\r
+  content: "\e183";\r
+}\r
+.glyphicon-tower:before {\r
+  content: "\e184";\r
+}\r
+.glyphicon-stats:before {\r
+  content: "\e185";\r
+}\r
+.glyphicon-sd-video:before {\r
+  content: "\e186";\r
+}\r
+.glyphicon-hd-video:before {\r
+  content: "\e187";\r
+}\r
+.glyphicon-subtitles:before {\r
+  content: "\e188";\r
+}\r
+.glyphicon-sound-stereo:before {\r
+  content: "\e189";\r
+}\r
+.glyphicon-sound-dolby:before {\r
+  content: "\e190";\r
+}\r
+.glyphicon-sound-5-1:before {\r
+  content: "\e191";\r
+}\r
+.glyphicon-sound-6-1:before {\r
+  content: "\e192";\r
+}\r
+.glyphicon-sound-7-1:before {\r
+  content: "\e193";\r
+}\r
+.glyphicon-copyright-mark:before {\r
+  content: "\e194";\r
+}\r
+.glyphicon-registration-mark:before {\r
+  content: "\e195";\r
+}\r
+.glyphicon-cloud-download:before {\r
+  content: "\e197";\r
+}\r
+.glyphicon-cloud-upload:before {\r
+  content: "\e198";\r
+}\r
+.glyphicon-tree-conifer:before {\r
+  content: "\e199";\r
+}\r
+.glyphicon-tree-deciduous:before {\r
+  content: "\e200";\r
+}\r
+.caret {\r
+  display: inline-block;\r
+  width: 0;\r
+  height: 0;\r
+  margin-left: 2px;\r
+  vertical-align: middle;\r
+  border-top: 4px solid;\r
+  border-right: 4px solid transparent;\r
+  border-left: 4px solid transparent;\r
+}\r
+.dropdown {\r
+  position: relative;\r
+}\r
+.dropdown-toggle:focus {\r
+  outline: 0;\r
+}\r
+.dropdown-menu {\r
+  position: absolute;\r
+  top: 100%;\r
+  left: 0;\r
+  z-index: 1000;\r
+  display: none;\r
+  float: left;\r
+  min-width: 160px;\r
+  padding: 5px 0;\r
+  margin: 2px 0 0;\r
+  list-style: none;\r
+  font-size: 14px;\r
+  background-color: #ffffff;\r
+  border: 1px solid #cccccc;\r
+  border: 1px solid rgba(0, 0, 0, 0.15);\r
+  border-radius: 0px;\r
+  -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\r
+  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\r
+  background-clip: padding-box;\r
+}\r
+.dropdown-menu.pull-right {\r
+  right: 0;\r
+  left: auto;\r
+}\r
+.dropdown-menu .divider {\r
+  height: 1px;\r
+  margin: 9px 0;\r
+  overflow: hidden;\r
+  background-color: #e5e5e5;\r
+}\r
+.dropdown-menu > li > a {\r
+  display: block;\r
+  padding: 3px 20px;\r
+  clear: both;\r
+  font-weight: normal;\r
+  line-height: 1.428571429;\r
+  color: #333333;\r
+  white-space: nowrap;\r
+}\r
+.dropdown-menu > li > a:hover,\r
+.dropdown-menu > li > a:focus {\r
+  text-decoration: none;\r
+  color: #ffffff;\r
+  background-color: #0d8921;\r
+}\r
+.dropdown-menu > .active > a,\r
+.dropdown-menu > .active > a:hover,\r
+.dropdown-menu > .active > a:focus {\r
+  color: #ffffff;\r
+  text-decoration: none;\r
+  outline: 0;\r
+  background-color: #0d8921;\r
+}\r
+.dropdown-menu > .disabled > a,\r
+.dropdown-menu > .disabled > a:hover,\r
+.dropdown-menu > .disabled > a:focus {\r
+  color: #999999;\r
+}\r
+.dropdown-menu > .disabled > a:hover,\r
+.dropdown-menu > .disabled > a:focus {\r
+  text-decoration: none;\r
+  background-color: transparent;\r
+  background-image: none;\r
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\r
+  cursor: not-allowed;\r
+}\r
+.open > .dropdown-menu {\r
+  display: block;\r
+}\r
+.open > a {\r
+  outline: 0;\r
+}\r
+.dropdown-menu-right {\r
+  left: auto;\r
+  right: 0;\r
+}\r
+.dropdown-menu-left {\r
+  left: 0;\r
+  right: auto;\r
+}\r
+.dropdown-header {\r
+  display: block;\r
+  padding: 3px 20px;\r
+  font-size: 12px;\r
+  line-height: 1.428571429;\r
+  color: #999999;\r
+}\r
+.dropdown-backdrop {\r
+  position: fixed;\r
+  left: 0;\r
+  right: 0;\r
+  bottom: 0;\r
+  top: 0;\r
+  z-index: 990;\r
+}\r
+.pull-right > .dropdown-menu {\r
+  right: 0;\r
+  left: auto;\r
+}\r
+.dropup .caret,\r
+.navbar-fixed-bottom .dropdown .caret {\r
+  border-top: 0;\r
+  border-bottom: 4px solid;\r
+  content: "";\r
+}\r
+.dropup .dropdown-menu,\r
+.navbar-fixed-bottom .dropdown .dropdown-menu {\r
+  top: auto;\r
+  bottom: 100%;\r
+  margin-bottom: 1px;\r
+}\r
+@media (min-width: 768px) {\r
+  .navbar-right .dropdown-menu {\r
+    left: auto;\r
+    right: 0;\r
+  }\r
+  .navbar-right .dropdown-menu-left {\r
+    left: 0;\r
+    right: auto;\r
+  }\r
+}\r
+.btn-group,\r
+.btn-group-vertical {\r
+  position: relative;\r
+  display: inline-block;\r
+  vertical-align: middle;\r
+}\r
+.btn-group > .btn,\r
+.btn-group-vertical > .btn {\r
+  position: relative;\r
+  float: left;\r
+}\r
+.btn-group > .btn:hover,\r
+.btn-group-vertical > .btn:hover,\r
+.btn-group > .btn:focus,\r
+.btn-group-vertical > .btn:focus,\r
+.btn-group > .btn:active,\r
+.btn-group-vertical > .btn:active,\r
+.btn-group > .btn.active,\r
+.btn-group-vertical > .btn.active {\r
+  z-index: 2;\r
+}\r
+.btn-group > .btn:focus,\r
+.btn-group-vertical > .btn:focus {\r
+  outline: none;\r
+}\r
+.btn-group .btn + .btn,\r
+.btn-group .btn + .btn-group,\r
+.btn-group .btn-group + .btn,\r
+.btn-group .btn-group + .btn-group {\r
+  margin-left: -1px;\r
+}\r
+.btn-toolbar {\r
+  margin-left: -5px;\r
+}\r
+.btn-toolbar .btn-group,\r
+.btn-toolbar .input-group {\r
+  float: left;\r
+}\r
+.btn-toolbar > .btn,\r
+.btn-toolbar > .btn-group,\r
+.btn-toolbar > .input-group {\r
+  margin-left: 5px;\r
+}\r
+.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\r
+  border-radius: 0;\r
+}\r
+.btn-group > .btn:first-child {\r
+  margin-left: 0;\r
+}\r
+.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {\r
+  border-bottom-right-radius: 0;\r
+  border-top-right-radius: 0;\r
+}\r
+.btn-group > .btn:last-child:not(:first-child),\r
+.btn-group > .dropdown-toggle:not(:first-child) {\r
+  border-bottom-left-radius: 0;\r
+  border-top-left-radius: 0;\r
+}\r
+.btn-group > .btn-group {\r
+  float: left;\r
+}\r
+.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\r
+  border-radius: 0;\r
+}\r
+.btn-group > .btn-group:first-child > .btn:last-child,\r
+.btn-group > .btn-group:first-child > .dropdown-toggle {\r
+  border-bottom-right-radius: 0;\r
+  border-top-right-radius: 0;\r
+}\r
+.btn-group > .btn-group:last-child > .btn:first-child {\r
+  border-bottom-left-radius: 0;\r
+  border-top-left-radius: 0;\r
+}\r
+.btn-group .dropdown-toggle:active,\r
+.btn-group.open .dropdown-toggle {\r
+  outline: 0;\r
+}\r
+.btn-group > .btn + .dropdown-toggle {\r
+  padding-left: 8px;\r
+  padding-right: 8px;\r
+}\r
+.btn-group > .btn-lg + .dropdown-toggle {\r
+  padding-left: 12px;\r
+  padding-right: 12px;\r
+}\r
+.btn-group.open .dropdown-toggle {\r
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\r
+  box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\r
+}\r
+.btn-group.open .dropdown-toggle.btn-link {\r
+  -webkit-box-shadow: none;\r
+  box-shadow: none;\r
+}\r
+.btn .caret {\r
+  margin-left: 0;\r
+}\r
+.btn-lg .caret {\r
+  border-width: 5px 5px 0;\r
+  border-bottom-width: 0;\r
+}\r
+.dropup .btn-lg .caret {\r
+  border-width: 0 5px 5px;\r
+}\r
+.btn-group-vertical > .btn,\r
+.btn-group-vertical > .btn-group,\r
+.btn-group-vertical > .btn-group > .btn {\r
+  display: block;\r
+  float: none;\r
+  width: 100%;\r
+  max-width: 100%;\r
+}\r
+.btn-group-vertical > .btn-group > .btn {\r
+  float: none;\r
+}\r
+.btn-group-vertical > .btn + .btn,\r
+.btn-group-vertical > .btn + .btn-group,\r
+.btn-group-vertical > .btn-group + .btn,\r
+.btn-group-vertical > .btn-group + .btn-group {\r
+  margin-top: -1px;\r
+  margin-left: 0;\r
+}\r
+.btn-group-vertical > .btn:not(:first-child):not(:last-child) {\r
+  border-radius: 0;\r
+}\r
+.btn-group-vertical > .btn:first-child:not(:last-child) {\r
+  border-top-right-radius: 0px;\r
+  border-bottom-right-radius: 0;\r
+  border-bottom-left-radius: 0;\r
+}\r
+.btn-group-vertical > .btn:last-child:not(:first-child) {\r
+  border-bottom-left-radius: 0px;\r
+  border-top-right-radius: 0;\r
+  border-top-left-radius: 0;\r
+}\r
+.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\r
+  border-radius: 0;\r
+}\r
+.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,\r
+.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\r
+  border-bottom-right-radius: 0;\r
+  border-bottom-left-radius: 0;\r
+}\r
+.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\r
+  border-top-right-radius: 0;\r
+  border-top-left-radius: 0;\r
+}\r
+.btn-group-justified {\r
+  display: table;\r
+  width: 100%;\r
+  table-layout: fixed;\r
+  border-collapse: separate;\r
+}\r
+.btn-group-justified > .btn,\r
+.btn-group-justified > .btn-group {\r
+  float: none;\r
+  display: table-cell;\r
+  width: 1%;\r
+}\r
+.btn-group-justified > .btn-group .btn {\r
+  width: 100%;\r
+}\r
+[data-toggle="buttons"] > .btn > input[type="radio"],\r
+[data-toggle="buttons"] > .btn > input[type="checkbox"] {\r
+  display: none;\r
+}\r
+.input-group {\r
+  position: relative;\r
+  display: table;\r
+  border-collapse: separate;\r
+}\r
+.input-group[class*="col-"] {\r
+  float: none;\r
+  padding-left: 0;\r
+  padding-right: 0;\r
+}\r
+.input-group .form-control {\r
+  float: left;\r
+  width: 100%;\r
+  margin-bottom: 0;\r
+}\r
+.input-group-lg > .form-control,\r
+.input-group-lg > .input-group-addon,\r
+.input-group-lg > .input-group-btn > .btn {\r
+  height: 45px;\r
+  padding: 10px 16px;\r
+  font-size: 18px;\r
+  line-height: 1.33;\r
+  border-radius: 0px;\r
+}\r
+select.input-group-lg > .form-control,\r
+select.input-group-lg > .input-group-addon,\r
+select.input-group-lg > .input-group-btn > .btn {\r
+  height: 45px;\r
+  line-height: 45px;\r
+}\r
+textarea.input-group-lg > .form-control,\r
+textarea.input-group-lg > .input-group-addon,\r
+textarea.input-group-lg > .input-group-btn > .btn,\r
+select[multiple].input-group-lg > .form-control,\r
+select[multiple].input-group-lg > .input-group-addon,\r
+select[multiple].input-group-lg > .input-group-btn > .btn {\r
+  height: auto;\r
+}\r
+.input-group-sm > .form-control,\r
+.input-group-sm > .input-group-addon,\r
+.input-group-sm > .input-group-btn > .btn {\r
+  height: 30px;\r
+  padding: 5px 10px;\r
+  font-size: 12px;\r
+  line-height: 1.5;\r
+  border-radius: 0px;\r
+}\r
+select.input-group-sm > .form-control,\r
+select.input-group-sm > .input-group-addon,\r
+select.input-group-sm > .input-group-btn > .btn {\r
+  height: 30px;\r
+  line-height: 30px;\r
+}\r
+textarea.input-group-sm > .form-control,\r
+textarea.input-group-sm > .input-group-addon,\r
+textarea.input-group-sm > .input-group-btn > .btn,\r
+select[multiple].input-group-sm > .form-control,\r
+select[multiple].input-group-sm > .input-group-addon,\r
+select[multiple].input-group-sm > .input-group-btn > .btn {\r
+  height: auto;\r
+}\r
+.input-group-addon,\r
+.input-group-btn,\r
+.input-group .form-control {\r
+  display: table-cell;\r
+}\r
+.input-group-addon:not(:first-child):not(:last-child),\r
+.input-group-btn:not(:first-child):not(:last-child),\r
+.input-group .form-control:not(:first-child):not(:last-child) {\r
+  border-radius: 0;\r
+}\r
+.input-group-addon,\r
+.input-group-btn {\r
+  width: 1%;\r
+  white-space: nowrap;\r
+  vertical-align: middle;\r
+}\r
+.input-group-addon {\r
+  padding: 6px 12px;\r
+  font-size: 14px;\r
+  font-weight: normal;\r
+  line-height: 1;\r
+  color: #555555;\r
+  text-align: center;\r
+  background-color: #eeeeee;\r
+  border: 1px solid #cccccc;\r
+  border-radius: 0px;\r
+}\r
+.input-group-addon.input-sm {\r
+  padding: 5px 10px;\r
+  font-size: 12px;\r
+  border-radius: 0px;\r
+}\r
+.input-group-addon.input-lg {\r
+  padding: 10px 16px;\r
+  font-size: 18px;\r
+  border-radius: 0px;\r
+}\r
+.input-group-addon input[type="radio"],\r
+.input-group-addon input[type="checkbox"] {\r
+  margin-top: 0;\r
+}\r
+.input-group .form-control:first-child,\r
+.input-group-addon:first-child,\r
+.input-group-btn:first-child > .btn,\r
+.input-group-btn:first-child > .btn-group > .btn,\r
+.input-group-btn:first-child > .dropdown-toggle,\r
+.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\r
+.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\r
+  border-bottom-right-radius: 0;\r
+  border-top-right-radius: 0;\r
+}\r
+.input-group-addon:first-child {\r
+  border-right: 0;\r
+}\r
+.input-group .form-control:last-child,\r
+.input-group-addon:last-child,\r
+.input-group-btn:last-child > .btn,\r
+.input-group-btn:last-child > .btn-group > .btn,\r
+.input-group-btn:last-child > .dropdown-toggle,\r
+.input-group-btn:first-child > .btn:not(:first-child),\r
+.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\r
+  border-bottom-left-radius: 0;\r
+  border-top-left-radius: 0;\r
+}\r
+.input-group-addon:last-child {\r
+  border-left: 0;\r
+}\r
+.input-group-btn {\r
+  position: relative;\r
+  font-size: 0;\r
+  white-space: nowrap;\r
+}\r
+.input-group-btn > .btn {\r
+  position: relative;\r
+}\r
+.input-group-btn > .btn + .btn {\r
+  margin-left: -1px;\r
+}\r
+.input-group-btn > .btn:hover,\r
+.input-group-btn > .btn:focus,\r
+.input-group-btn > .btn:active {\r
+  z-index: 2;\r
+}\r
+.input-group-btn:first-child > .btn,\r
+.input-group-btn:first-child > .btn-group {\r
+  margin-right: -1px;\r
+}\r
+.input-group-btn:last-child > .btn,\r
+.input-group-btn:last-child > .btn-group {\r
+  margin-left: -1px;\r
+}\r
+.nav {\r
+  margin-bottom: 0;\r
+  padding-left: 0;\r
+  list-style: none;\r
+}\r
+.nav > li {\r
+  position: relative;\r
+  display: block;\r
+}\r
+.nav > li > a {\r
+  position: relative;\r
+  display: block;\r
+  padding: 10px 15px;\r
+}\r
+.nav > li > a:hover,\r
+.nav > li > a:focus {\r
+  text-decoration: none;\r
+  background-color: #dddddd;\r
+}\r
+.nav > li.disabled > a {\r
+  color: #999999;\r
+}\r
+.nav > li.disabled > a:hover,\r
+.nav > li.disabled > a:focus {\r
+  color: #999999;\r
+  text-decoration: none;\r
+  background-color: transparent;\r
+  cursor: not-allowed;\r
+}\r
+.nav .open > a,\r
+.nav .open > a:hover,\r
+.nav .open > a:focus {\r
+  background-color: #dddddd;\r
+  border-color: #0d8921;\r
+}\r
+.nav .nav-divider {\r
+  height: 1px;\r
+  margin: 9px 0;\r
+  overflow: hidden;\r
+  background-color: #e5e5e5;\r
+}\r
+.nav > li > a > img {\r
+  max-width: none;\r
+}\r
+.nav-tabs {\r
+  border-bottom: 1px solid #dddddd;\r
+}\r
+.nav-tabs > li {\r
+  float: left;\r
+  margin-bottom: -1px;\r
+}\r
+.nav-tabs > li > a {\r
+  margin-right: 2px;\r
+  line-height: 1.428571429;\r
+  border: 1px solid transparent;\r
+  border-radius: 0px 0px 0 0;\r
+}\r
+.nav-tabs > li > a:hover {\r
+  border-color: #eeeeee #eeeeee #dddddd;\r
+}\r
+.nav-tabs > li.active > a,\r
+.nav-tabs > li.active > a:hover,\r
+.nav-tabs > li.active > a:focus {\r
+  color: #555555;\r
+  background-color: #f2f2f2;\r
+  border: 1px solid #dddddd;\r
+  border-bottom-color: transparent;\r
+  cursor: default;\r
+}\r
+.nav-tabs.nav-justified {\r
+  width: 100%;\r
+  border-bottom: 0;\r
+}\r
+.nav-tabs.nav-justified > li {\r
+  float: none;\r
+}\r
+.nav-tabs.nav-justified > li > a {\r
+  text-align: center;\r
+  margin-bottom: 5px;\r
+}\r
+.nav-tabs.nav-justified > .dropdown .dropdown-menu {\r
+  top: auto;\r
+  left: auto;\r
+}\r
+@media (min-width: 768px) {\r
+  .nav-tabs.nav-justified > li {\r
+    display: table-cell;\r
+    width: 1%;\r
+  }\r
+  .nav-tabs.nav-justified > li > a {\r
+    margin-bottom: 0;\r
+  }\r
+}\r
+.nav-tabs.nav-justified > li > a {\r
+  margin-right: 0;\r
+  border-radius: 0px;\r
+}\r
+.nav-tabs.nav-justified > .active > a,\r
+.nav-tabs.nav-justified > .active > a:hover,\r
+.nav-tabs.nav-justified > .active > a:focus {\r
+  border: 1px solid #dddddd;\r
+}\r
+@media (min-width: 768px) {\r
+  .nav-tabs.nav-justified > li > a {\r
+    border-bottom: 1px solid #dddddd;\r
+    border-radius: 0px 0px 0 0;\r
+  }\r
+  .nav-tabs.nav-justified > .active > a,\r
+  .nav-tabs.nav-justified > .active > a:hover,\r
+  .nav-tabs.nav-justified > .active > a:focus {\r
+    border-bottom-color: #f2f2f2;\r
+  }\r
+}\r
+.nav-pills > li {\r
+  float: left;\r
+}\r
+.nav-pills > li > a {\r
+  border-radius: 0px;\r
+}\r
+.nav-pills > li + li {\r
+  margin-left: 2px;\r
+}\r
+.nav-pills > li.active > a,\r
+.nav-pills > li.active > a:hover,\r
+.nav-pills > li.active > a:focus {\r
+  color: #ffffff;\r
+  background-color: #0d8921;\r
+}\r
+.nav-stacked > li {\r
+  float: none;\r
+}\r
+.nav-stacked > li + li {\r
+  margin-top: 2px;\r
+  margin-left: 0;\r
+}\r
+.nav-justified {\r
+  width: 100%;\r
+}\r
+.nav-justified > li {\r
+  float: none;\r
+}\r
+.nav-justified > li > a {\r
+  text-align: center;\r
+  margin-bottom: 5px;\r
+}\r
+.nav-justified > .dropdown .dropdown-menu {\r
+  top: auto;\r
+  left: auto;\r
+}\r
+@media (min-width: 768px) {\r
+  .nav-justified > li {\r
+    display: table-cell;\r
+    width: 1%;\r
+  }\r
+  .nav-justified > li > a {\r
+    margin-bottom: 0;\r
+  }\r
+}\r
+.nav-tabs-justified {\r
+  border-bottom: 0;\r
+}\r
+.nav-tabs-justified > li > a {\r
+  margin-right: 0;\r
+  border-radius: 0px;\r
+}\r
+.nav-tabs-justified > .active > a,\r
+.nav-tabs-justified > .active > a:hover,\r
+.nav-tabs-justified > .active > a:focus {\r
+  border: 1px solid #dddddd;\r
+}\r
+@media (min-width: 768px) {\r
+  .nav-tabs-justified > li > a {\r
+    border-bottom: 1px solid #dddddd;\r
+    border-radius: 0px 0px 0 0;\r
+  }\r
+  .nav-tabs-justified > .active > a,\r
+  .nav-tabs-justified > .active > a:hover,\r
+  .nav-tabs-justified > .active > a:focus {\r
+    border-bottom-color: #f2f2f2;\r
+  }\r
+}\r
+.tab-content > .tab-pane {\r
+  display: none;\r
+}\r
+.tab-content > .active {\r
+  display: block;\r
+}\r
+.nav-tabs .dropdown-menu {\r
+  margin-top: -1px;\r
+  border-top-right-radius: 0;\r
+  border-top-left-radius: 0;\r
+}\r
+.navbar {\r
+  position: relative;\r
+  min-height: 50px;\r
+  margin-bottom: 20px;\r
+  border: 1px solid transparent;\r
+}\r
+@media (min-width: 768px) {\r
+  .navbar {\r
+    border-radius: 0px;\r
+  }\r
+}\r
+@media (min-width: 768px) {\r
+  .navbar-header {\r
+    float: left;\r
+  }\r
+}\r
+.navbar-collapse {\r
+  max-height: 340px;\r
+  overflow-x: visible;\r
+  padding-right: 15px;\r
+  padding-left: 15px;\r
+  border-top: 1px solid transparent;\r
+  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);\r
+  -webkit-overflow-scrolling: touch;\r
+}\r
+.navbar-collapse.in {\r
+  overflow-y: auto;\r
+}\r
+@media (min-width: 768px) {\r
+  .navbar-collapse {\r
+    width: auto;\r
+    border-top: 0;\r
+    box-shadow: none;\r
+  }\r
+  .navbar-collapse.collapse {\r
+    display: block !important;\r
+    height: auto !important;\r
+    padding-bottom: 0;\r
+    overflow: visible !important;\r
+  }\r
+  .navbar-collapse.in {\r
+    overflow-y: visible;\r
+  }\r
+  .navbar-fixed-top .navbar-collapse,\r
+  .navbar-static-top .navbar-collapse,\r
+  .navbar-fixed-bottom .navbar-collapse {\r
+    padding-left: 0;\r
+    padding-right: 0;\r
+  }\r
+}\r
+.container > .navbar-header,\r
+.container-fluid > .navbar-header,\r
+.container > .navbar-collapse,\r
+.container-fluid > .navbar-collapse {\r
+  margin-right: -15px;\r
+  margin-left: -15px;\r
+}\r
+@media (min-width: 768px) {\r
+  .container > .navbar-header,\r
+  .container-fluid > .navbar-header,\r
+  .container > .navbar-collapse,\r
+  .container-fluid > .navbar-collapse {\r
+    margin-right: 0;\r
+    margin-left: 0;\r
+  }\r
+}\r
+.navbar-static-top {\r
+  z-index: 1000;\r
+  border-width: 0 0 1px;\r
+}\r
+@media (min-width: 768px) {\r
+  .navbar-static-top {\r
+    border-radius: 0;\r
+  }\r
+}\r
+.navbar-fixed-top,\r
+.navbar-fixed-bottom {\r
+  position: fixed;\r
+  right: 0;\r
+  left: 0;\r
+  z-index: 1030;\r
+}\r
+@media (min-width: 768px) {\r
+  .navbar-fixed-top,\r
+  .navbar-fixed-bottom {\r
+    border-radius: 0;\r
+  }\r
+}\r
+.navbar-fixed-top {\r
+  top: 0;\r
+  border-width: 0 0 1px;\r
+}\r
+.navbar-fixed-bottom {\r
+  bottom: 0;\r
+  margin-bottom: 0;\r
+  border-width: 1px 0 0;\r
+}\r
+.navbar-brand {\r
+  float: left;\r
+  padding: 15px 15px;\r
+  font-size: 18px;\r
+  line-height: 20px;\r
+  height: 50px;\r
+}\r
+.navbar-brand:hover,\r
+.navbar-brand:focus {\r
+  text-decoration: none;\r
+}\r
+@media (min-width: 768px) {\r
+  .navbar > .container .navbar-brand,\r
+  .navbar > .container-fluid .navbar-brand {\r
+    margin-left: -15px;\r
+  }\r
+}\r
+.navbar-toggle {\r
+  position: relative;\r
+  float: right;\r
+  margin-right: 15px;\r
+  padding: 9px 10px;\r
+  margin-top: 8px;\r
+  margin-bottom: 8px;\r
+  background-color: transparent;\r
+  background-image: none;\r
+  border: 1px solid transparent;\r
+  border-radius: 0px;\r
+}\r
+.navbar-toggle:focus {\r
+  outline: none;\r
+}\r
+.navbar-toggle .icon-bar {\r
+  display: block;\r
+  width: 22px;\r
+  height: 2px;\r
+  border-radius: 1px;\r
+}\r
+.navbar-toggle .icon-bar + .icon-bar {\r
+  margin-top: 4px;\r
+}\r
+@media (min-width: 768px) {\r
+  .navbar-toggle {\r
+    display: none;\r
+  }\r
+}\r
+.navbar-nav {\r
+  margin: 7.5px -15px;\r
+}\r
+.navbar-nav > li > a {\r
+  padding-top: 10px;\r
+  padding-bottom: 10px;\r
+  line-height: 20px;\r
+}\r
+@media (max-width: 767px) {\r
+  .navbar-nav .open .dropdown-menu {\r
+    position: static;\r
+    float: none;\r
+    width: auto;\r
+    margin-top: 0;\r
+    background-color: transparent;\r
+    border: 0;\r
+    box-shadow: none;\r
+  }\r
+  .navbar-nav .open .dropdown-menu > li > a,\r
+  .navbar-nav .open .dropdown-menu .dropdown-header {\r
+    padding: 5px 15px 5px 25px;\r
+  }\r
+  .navbar-nav .open .dropdown-menu > li > a {\r
+    line-height: 20px;\r
+  }\r
+  .navbar-nav .open .dropdown-menu > li > a:hover,\r
+  .navbar-nav .open .dropdown-menu > li > a:focus {\r
+    background-image: none;\r
+  }\r
+}\r
+@media (min-width: 768px) {\r
+  .navbar-nav {\r
+    float: left;\r
+    margin: 0;\r
+  }\r
+  .navbar-nav > li {\r
+    float: left;\r
+  }\r
+  .navbar-nav > li > a {\r
+    padding-top: 15px;\r
+    padding-bottom: 15px;\r
+  }\r
+  .navbar-nav.navbar-right:last-child {\r
+    margin-right: -15px;\r
+  }\r
+}\r
+@media (min-width: 768px) {\r
+  .navbar-left {\r
+    float: left !important;\r
+  }\r
+  .navbar-right {\r
+    float: right !important;\r
+  }\r
+}\r
+.navbar-form {\r
+  margin-left: -15px;\r
+  margin-right: -15px;\r
+  padding: 10px 15px;\r
+  border-top: 1px solid transparent;\r
+  border-bottom: 1px solid transparent;\r
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\r
+  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\r
+  margin-top: 8px;\r
+  margin-bottom: 8px;\r
+}\r
+@media (min-width: 768px) {\r
+  .navbar-form .form-group {\r
+    display: inline-block;\r
+    margin-bottom: 0;\r
+    vertical-align: middle;\r
+  }\r
+  .navbar-form .form-control {\r
+    display: inline-block;\r
+    width: auto;\r
+    vertical-align: middle;\r
+  }\r
+  .navbar-form .input-group > .form-control {\r
+    width: 100%;\r
+  }\r
+  .navbar-form .control-label {\r
+    margin-bottom: 0;\r
+    vertical-align: middle;\r
+  }\r
+  .navbar-form .radio,\r
+  .navbar-form .checkbox {\r
+    display: inline-block;\r
+    margin-top: 0;\r
+    margin-bottom: 0;\r
+    padding-left: 0;\r
+    vertical-align: middle;\r
+  }\r
+  .navbar-form .radio input[type="radio"],\r
+  .navbar-form .checkbox input[type="checkbox"] {\r
+    float: none;\r
+    margin-left: 0;\r
+  }\r
+  .navbar-form .has-feedback .form-control-feedback {\r
+    top: 0;\r
+  }\r
+}\r
+@media (max-width: 767px) {\r
+  .navbar-form .form-group {\r
+    margin-bottom: 5px;\r
+  }\r
+}\r
+@media (min-width: 768px) {\r
+  .navbar-form {\r
+    width: auto;\r
+    border: 0;\r
+    margin-left: 0;\r
+    margin-right: 0;\r
+    padding-top: 0;\r
+    padding-bottom: 0;\r
+    -webkit-box-shadow: none;\r
+    box-shadow: none;\r
+  }\r
+  .navbar-form.navbar-right:last-child {\r
+    margin-right: -15px;\r
+  }\r
+}\r
+.navbar-nav > li > .dropdown-menu {\r
+  margin-top: 0;\r
+  border-top-right-radius: 0;\r
+  border-top-left-radius: 0;\r
+}\r
+.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\r
+  border-bottom-right-radius: 0;\r
+  border-bottom-left-radius: 0;\r
+}\r
+.navbar-btn {\r
+  margin-top: 8px;\r
+  margin-bottom: 8px;\r
+}\r
+.navbar-btn.btn-sm {\r
+  margin-top: 10px;\r
+  margin-bottom: 10px;\r
+}\r
+.navbar-btn.btn-xs {\r
+  margin-top: 14px;\r
+  margin-bottom: 14px;\r
+}\r
+.navbar-text {\r
+  margin-top: 15px;\r
+  margin-bottom: 15px;\r
+}\r
+@media (min-width: 768px) {\r
+  .navbar-text {\r
+    float: left;\r
+    margin-left: 15px;\r
+    margin-right: 15px;\r
+  }\r
+  .navbar-text.navbar-right:last-child {\r
+    margin-right: 0;\r
+  }\r
+}\r
+.navbar-default {\r
+  background-color: #0d8921;\r
+  border-color: none;\r
+}\r
+.navbar-default .navbar-brand {\r
+  color: #ffffff;\r
+}\r
+.navbar-default .navbar-brand:hover,\r
+.navbar-default .navbar-brand:focus {\r
+  color: #e6e6e6;\r
+  background-color: transparent;\r
+}\r
+.navbar-default .navbar-text {\r
+  color: #ffffff;\r
+}\r
+.navbar-default .navbar-nav > li > a {\r
+  color: #ffffff;\r
+}\r
+.navbar-default .navbar-nav > li > a:hover,\r
+.navbar-default .navbar-nav > li > a:focus {\r
+  color: #dddddd;\r
+  background-color: transparent;\r
+}\r
+.navbar-default .navbar-nav > .active > a,\r
+.navbar-default .navbar-nav > .active > a:hover,\r
+.navbar-default .navbar-nav > .active > a:focus {\r
+  color: #ffffff;\r
+  background-color: #0a6b1a;\r
+}\r
+.navbar-default .navbar-nav > .disabled > a,\r
+.navbar-default .navbar-nav > .disabled > a:hover,\r
+.navbar-default .navbar-nav > .disabled > a:focus {\r
+  color: #cccccc;\r
+  background-color: transparent;\r
+}\r
+.navbar-default .navbar-toggle {\r
+  border-color: #dddddd;\r
+}\r
+.navbar-default .navbar-toggle:hover,\r
+.navbar-default .navbar-toggle:focus {\r
+  background-color: #dddddd;\r
+}\r
+.navbar-default .navbar-toggle .icon-bar {\r
+  background-color: #ffffff;\r
+}\r
+.navbar-default .navbar-collapse,\r
+.navbar-default .navbar-form {\r
+  border-color: none;\r
+}\r
+.navbar-default .navbar-nav > .open > a,\r
+.navbar-default .navbar-nav > .open > a:hover,\r
+.navbar-default .navbar-nav > .open > a:focus {\r
+  background-color: #0a6b1a;\r
+  color: #ffffff;\r
+}\r
+@media (max-width: 767px) {\r
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a {\r
+    color: #ffffff;\r
+  }\r
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,\r
+  .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {\r
+    color: #dddddd;\r
+    background-color: transparent;\r
+  }\r
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a,\r
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,\r
+  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {\r
+    color: #ffffff;\r
+    background-color: #0a6b1a;\r
+  }\r
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,\r
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,\r
+  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {\r
+    color: #cccccc;\r
+    background-color: transparent;\r
+  }\r
+}\r
+.navbar-default .navbar-link {\r
+  color: #ffffff;\r
+}\r
+.navbar-default .navbar-link:hover {\r
+  color: #dddddd;\r
+}\r
+.navbar-inverse {\r
+  background-color: #222222;\r
+  border-color: #080808;\r
+}\r
+.navbar-inverse .navbar-brand {\r
+  color: #999999;\r
+}\r
+.navbar-inverse .navbar-brand:hover,\r
+.navbar-inverse .navbar-brand:focus {\r
+  color: #ffffff;\r
+  background-color: transparent;\r
+}\r
+.navbar-inverse .navbar-text {\r
+  color: #999999;\r
+}\r
+.navbar-inverse .navbar-nav > li > a {\r
+  color: #999999;\r
+}\r
+.navbar-inverse .navbar-nav > li > a:hover,\r
+.navbar-inverse .navbar-nav > li > a:focus {\r
+  color: #ffffff;\r
+  background-color: transparent;\r
+}\r
+.navbar-inverse .navbar-nav > .active > a,\r
+.navbar-inverse .navbar-nav > .active > a:hover,\r
+.navbar-inverse .navbar-nav > .active > a:focus {\r
+  color: #ffffff;\r
+  background-color: #080808;\r
+}\r
+.navbar-inverse .navbar-nav > .disabled > a,\r
+.navbar-inverse .navbar-nav > .disabled > a:hover,\r
+.navbar-inverse .navbar-nav > .disabled > a:focus {\r
+  color: #444444;\r
+  background-color: transparent;\r
+}\r
+.navbar-inverse .navbar-toggle {\r
+  border-color: #333333;\r
+}\r
+.navbar-inverse .navbar-toggle:hover,\r
+.navbar-inverse .navbar-toggle:focus {\r
+  background-color: #333333;\r
+}\r
+.navbar-inverse .navbar-toggle .icon-bar {\r
+  background-color: #ffffff;\r
+}\r
+.navbar-inverse .navbar-collapse,\r
+.navbar-inverse .navbar-form {\r
+  border-color: #101010;\r
+}\r
+.navbar-inverse .navbar-nav > .open > a,\r
+.navbar-inverse .navbar-nav > .open > a:hover,\r
+.navbar-inverse .navbar-nav > .open > a:focus {\r
+  background-color: #080808;\r
+  color: #ffffff;\r
+}\r
+@media (max-width: 767px) {\r
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {\r
+    border-color: #080808;\r
+  }\r
+  .navbar-inverse .navbar-nav .open .dropdown-menu .divider {\r
+    background-color: #080808;\r
+  }\r
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {\r
+    color: #999999;\r
+  }\r
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,\r
+  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {\r
+    color: #ffffff;\r
+    background-color: transparent;\r
+  }\r
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,\r
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,\r
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {\r
+    color: #ffffff;\r
+    background-color: #080808;\r
+  }\r
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,\r
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,\r
+  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {\r
+    color: #444444;\r
+    background-color: transparent;\r
+  }\r
+}\r
+.navbar-inverse .navbar-link {\r
+  color: #999999;\r
+}\r
+.navbar-inverse .navbar-link:hover {\r
+  color: #ffffff;\r
+}\r
+.breadcrumb {\r
+  padding: 8px 15px;\r
+  margin-bottom: 20px;\r
+  list-style: none;\r
+  background-color: #f5f5f5;\r
+  border-radius: 0px;\r
+}\r
+.breadcrumb > li {\r
+  display: inline-block;\r
+}\r
+.breadcrumb > li + li:before {\r
+  content: "\00a0";\r
+  padding: 0 5px;\r
+  color: #cccccc;\r
+}\r
+.breadcrumb > .active {\r
+  color: #999999;\r
+}\r
+.pagination {\r
+  display: inline-block;\r
+  padding-left: 0;\r
+  margin: 20px 0;\r
+  border-radius: 0px;\r
+}\r
+.pagination > li {\r
+  display: inline;\r
+}\r
+.pagination > li > a,\r
+.pagination > li > span {\r
+  position: relative;\r
+  float: left;\r
+  padding: 6px 12px;\r
+  line-height: 1.428571429;\r
+  text-decoration: none;\r
+  color: #0d8921;\r
+  background-color: #ffffff;\r
+  border: 1px solid #dddddd;\r
+  margin-left: -1px;\r
+}\r
+.pagination > li:first-child > a,\r
+.pagination > li:first-child > span {\r
+  margin-left: 0;\r
+  border-bottom-left-radius: 0px;\r
+  border-top-left-radius: 0px;\r
+}\r
+.pagination > li:last-child > a,\r
+.pagination > li:last-child > span {\r
+  border-bottom-right-radius: 0px;\r
+  border-top-right-radius: 0px;\r
+}\r
+.pagination > li > a:hover,\r
+.pagination > li > span:hover,\r
+.pagination > li > a:focus,\r
+.pagination > li > span:focus {\r
+  color: #064310;\r
+  background-color: #eeeeee;\r
+  border-color: #dddddd;\r
+}\r
+.pagination > .active > a,\r
+.pagination > .active > span,\r
+.pagination > .active > a:hover,\r
+.pagination > .active > span:hover,\r
+.pagination > .active > a:focus,\r
+.pagination > .active > span:focus {\r
+  z-index: 2;\r
+  color: #ffffff;\r
+  background-color: #0d8921;\r
+  border-color: #0d8921;\r
+  cursor: default;\r
+}\r
+.pagination > .disabled > span,\r
+.pagination > .disabled > span:hover,\r
+.pagination > .disabled > span:focus,\r
+.pagination > .disabled > a,\r
+.pagination > .disabled > a:hover,\r
+.pagination > .disabled > a:focus {\r
+  color: #999999;\r
+  background-color: #ffffff;\r
+  border-color: #dddddd;\r
+  cursor: not-allowed;\r
+}\r
+.pagination-lg > li > a,\r
+.pagination-lg > li > span {\r
+  padding: 10px 16px;\r
+  font-size: 18px;\r
+}\r
+.pagination-lg > li:first-child > a,\r
+.pagination-lg > li:first-child > span {\r
+  border-bottom-left-radius: 0px;\r
+  border-top-left-radius: 0px;\r
+}\r
+.pagination-lg > li:last-child > a,\r
+.pagination-lg > li:last-child > span {\r
+  border-bottom-right-radius: 0px;\r
+  border-top-right-radius: 0px;\r
+}\r
+.pagination-sm > li > a,\r
+.pagination-sm > li > span {\r
+  padding: 5px 10px;\r
+  font-size: 12px;\r
+}\r
+.pagination-sm > li:first-child > a,\r
+.pagination-sm > li:first-child > span {\r
+  border-bottom-left-radius: 0px;\r
+  border-top-left-radius: 0px;\r
+}\r
+.pagination-sm > li:last-child > a,\r
+.pagination-sm > li:last-child > span {\r
+  border-bottom-right-radius: 0px;\r
+  border-top-right-radius: 0px;\r
+}\r
+.pager {\r
+  padding-left: 0;\r
+  margin: 20px 0;\r
+  list-style: none;\r
+  text-align: center;\r
+}\r
+.pager li {\r
+  display: inline;\r
+}\r
+.pager li > a,\r
+.pager li > span {\r
+  display: inline-block;\r
+  padding: 5px 14px;\r
+  background-color: #ffffff;\r
+  border: 1px solid #dddddd;\r
+  border-radius: 15px;\r
+}\r
+.pager li > a:hover,\r
+.pager li > a:focus {\r
+  text-decoration: none;\r
+  background-color: #eeeeee;\r
+}\r
+.pager .next > a,\r
+.pager .next > span {\r
+  float: right;\r
+}\r
+.pager .previous > a,\r
+.pager .previous > span {\r
+  float: left;\r
+}\r
+.pager .disabled > a,\r
+.pager .disabled > a:hover,\r
+.pager .disabled > a:focus,\r
+.pager .disabled > span {\r
+  color: #999999;\r
+  background-color: #ffffff;\r
+  cursor: not-allowed;\r
+}\r
+.label {\r
+  display: inline;\r
+  padding: .2em .6em .3em;\r
+  font-size: 75%;\r
+  font-weight: bold;\r
+  line-height: 1;\r
+  color: #ffffff;\r
+  text-align: center;\r
+  white-space: nowrap;\r
+  vertical-align: baseline;\r
+  border-radius: .25em;\r
+}\r
+.label[href]:hover,\r
+.label[href]:focus {\r
+  color: #ffffff;\r
+  text-decoration: none;\r
+  cursor: pointer;\r
+}\r
+.label:empty {\r
+  display: none;\r
+}\r
+.btn .label {\r
+  position: relative;\r
+  top: -1px;\r
+}\r
+.label-default {\r
+  background-color: #999999;\r
+}\r
+.label-default[href]:hover,\r
+.label-default[href]:focus {\r
+  background-color: #808080;\r
+}\r
+.label-primary {\r
+  background-color: #0d8921;\r
+}\r
+.label-primary[href]:hover,\r
+.label-primary[href]:focus {\r
+  background-color: #095a16;\r
+}\r
+.label-success {\r
+  background-color: #5cb85c;\r
+}\r
+.label-success[href]:hover,\r
+.label-success[href]:focus {\r
+  background-color: #449d44;\r
+}\r
+.label-info {\r
+  background-color: #5bc0de;\r
+}\r
+.label-info[href]:hover,\r
+.label-info[href]:focus {\r
+  background-color: #31b0d5;\r
+}\r
+.label-warning {\r
+  background-color: #f0ad4e;\r
+}\r
+.label-warning[href]:hover,\r
+.label-warning[href]:focus {\r
+  background-color: #ec971f;\r
+}\r
+.label-danger {\r
+  background-color: #d9534f;\r
+}\r
+.label-danger[href]:hover,\r
+.label-danger[href]:focus {\r
+  background-color: #c9302c;\r
+}\r
+.badge {\r
+  display: inline-block;\r
+  min-width: 10px;\r
+  padding: 3px 7px;\r
+  font-size: 12px;\r
+  font-weight: bold;\r
+  color: : #fff;\r
+  line-height: 1;\r
+  vertical-align: baseline;\r
+  white-space: nowrap;\r
+  text-align: center;\r
+  background-color: #999999;\r
+  border-radius: 10px;\r
+}\r
+.badge:empty {\r
+  display: none;\r
+}\r
+.btn .badge {\r
+  position: relative;\r
+  top: -1px;\r
+}\r
+.btn-xs .badge {\r
+  top: 0;\r
+  padding: 1px 5px;\r
+}\r
+a.badge:hover,\r
+a.badge:focus {\r
+  color: #ffffff;\r
+  text-decoration: none;\r
+  cursor: pointer;\r
+}\r
+a.list-group-item.active > .badge,\r
+.nav-pills > .active > a > .badge {\r
+  color: #0d8921;\r
+  background-color: #ffffff;\r
+}\r
+.nav-pills > li > a > .badge {\r
+  margin-left: 3px;\r
+}\r
+.jumbotron {\r
+  padding: 30px;\r
+  margin-bottom: 30px;\r
+  color: inherit;\r
+  background-color: #ffffff;\r
+}\r
+.jumbotron h1,\r
+.jumbotron .h1 {\r
+  color: inherit;\r
+}\r
+.jumbotron p {\r
+  margin-bottom: 15px;\r
+  font-size: 21px;\r
+  font-weight: 200;\r
+}\r
+.container .jumbotron {\r
+  border-radius: 0px;\r
+}\r
+.jumbotron .container {\r
+  max-width: 100%;\r
+}\r
+@media screen and (min-width: 768px) {\r
+  .jumbotron {\r
+    padding-top: 48px;\r
+    padding-bottom: 48px;\r
+  }\r
+  .container .jumbotron {\r
+    padding-left: 60px;\r
+    padding-right: 60px;\r
+  }\r
+  .jumbotron h1,\r
+  .jumbotron .h1 {\r
+    font-size: 63px;\r
+  }\r
+}\r
+.thumbnail {\r
+  display: block;\r
+  padding: 4px;\r
+  margin-bottom: 20px;\r
+  line-height: 1.428571429;\r
+  background-color: #f2f2f2;\r
+  border: 1px solid #dddddd;\r
+  border-radius: 0px;\r
+  -webkit-transition: all 0.2s ease-in-out;\r
+  transition: all 0.2s ease-in-out;\r
+}\r
+.thumbnail > img,\r
+.thumbnail a > img {\r
+  margin-left: auto;\r
+  margin-right: auto;\r
+}\r
+a.thumbnail:hover,\r
+a.thumbnail:focus,\r
+a.thumbnail.active {\r
+  border-color: #0d8921;\r
+}\r
+.thumbnail .caption {\r
+  padding: 9px;\r
+  color: #333333;\r
+}\r
+.alert {\r
+  padding: 15px;\r
+  margin-bottom: 20px;\r
+  border: 1px solid transparent;\r
+  border-radius: 0px;\r
+}\r
+.alert h4 {\r
+  margin-top: 0;\r
+  color: inherit;\r
+}\r
+.alert .alert-link {\r
+  font-weight: bold;\r
+}\r
+.alert > p,\r
+.alert > ul {\r
+  margin-bottom: 0;\r
+}\r
+.alert > p + p {\r
+  margin-top: 5px;\r
+}\r
+.alert-dismissable {\r
+  padding-right: 35px;\r
+}\r
+.alert-dismissable .close {\r
+  position: relative;\r
+  top: -2px;\r
+  right: -21px;\r
+  color: inherit;\r
+}\r
+.alert-success {\r
+  background-color: #dff0d8;\r
+  border-color: #d6e9c6;\r
+  color: #5cb85c;\r
+}\r
+.alert-success hr {\r
+  border-top-color: #c9e2b3;\r
+}\r
+.alert-success .alert-link {\r
+  color: #449d44;\r
+}\r
+.alert-info {\r
+  background-color: #d9edf7;\r
+  border-color: #bce8f1;\r
+  color: #5bc0de;\r
+}\r
+.alert-info hr {\r
+  border-top-color: #a6e1ec;\r
+}\r
+.alert-info .alert-link {\r
+  color: #31b0d5;\r
+}\r
+.alert-warning {\r
+  background-color: #fcf8e3;\r
+  border-color: #fbeed5;\r
+  color: #f0ad4e;\r
+}\r
+.alert-warning hr {\r
+  border-top-color: #f8e5be;\r
+}\r
+.alert-warning .alert-link {\r
+  color: #ec971f;\r
+}\r
+.alert-danger {\r
+  background-color: #f2dede;\r
+  border-color: #eed3d7;\r
+  color: #d9534f;\r
+}\r
+.alert-danger hr {\r
+  border-top-color: #e6c1c7;\r
+}\r
+.alert-danger .alert-link {\r
+  color: #c9302c;\r
+}\r
+@-webkit-keyframes progress-bar-stripes {\r
+  from {\r
+    background-position: 40px 0;\r
+  }\r
+  to {\r
+    background-position: 0 0;\r
+  }\r
+}\r
+@keyframes progress-bar-stripes {\r
+  from {\r
+    background-position: 40px 0;\r
+  }\r
+  to {\r
+    background-position: 0 0;\r
+  }\r
+}\r
+.progress {\r
+  overflow: hidden;\r
+  height: 20px;\r
+  margin-bottom: 20px;\r
+  background-color: #f5f5f5;\r
+  border-radius: 0px;\r
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\r
+  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\r
+}\r
+.progress-bar {\r
+  float: left;\r
+  width: 0%;\r
+  height: 100%;\r
+  font-size: 12px;\r
+  line-height: 20px;\r
+  color: #ffffff;\r
+  text-align: center;\r
+  background-color: #0d8921;\r
+  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\r
+  box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\r
+  -webkit-transition: width 0.6s ease;\r
+  transition: width 0.6s ease;\r
+}\r
+.progress-striped .progress-bar {\r
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\r
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\r
+  background-size: 40px 40px;\r
+}\r
+.progress.active .progress-bar {\r
+  -webkit-animation: progress-bar-stripes 2s linear infinite;\r
+  animation: progress-bar-stripes 2s linear infinite;\r
+}\r
+.progress-bar-success {\r
+  background-color: #5cb85c;\r
+}\r
+.progress-striped .progress-bar-success {\r
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\r
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\r
+}\r
+.progress-bar-info {\r
+  background-color: #5bc0de;\r
+}\r
+.progress-striped .progress-bar-info {\r
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\r
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\r
+}\r
+.progress-bar-warning {\r
+  background-color: #f0ad4e;\r
+}\r
+.progress-striped .progress-bar-warning {\r
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\r
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\r
+}\r
+.progress-bar-danger {\r
+  background-color: #d9534f;\r
+}\r
+.progress-striped .progress-bar-danger {\r
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\r
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\r
+}\r
+.media,\r
+.media-body {\r
+  overflow: hidden;\r
+  zoom: 1;\r
+}\r
+.media,\r
+.media .media {\r
+  margin-top: 15px;\r
+}\r
+.media:first-child {\r
+  margin-top: 0;\r
+}\r
+.media-object {\r
+  display: block;\r
+}\r
+.media-heading {\r
+  margin: 0 0 5px;\r
+}\r
+.media > .pull-left {\r
+  margin-right: 10px;\r
+}\r
+.media > .pull-right {\r
+  margin-left: 10px;\r
+}\r
+.media-list {\r
+  padding-left: 0;\r
+  list-style: none;\r
+}\r
+.list-group {\r
+  margin-bottom: 20px;\r
+  padding-left: 0;\r
+}\r
+.list-group-item {\r
+  position: relative;\r
+  display: block;\r
+  padding: 10px 15px;\r
+  margin-bottom: -1px;\r
+  background-color: #ffffff;\r
+  border: 1px solid #dddddd;\r
+}\r
+.list-group-item:first-child {\r
+  border-top-right-radius: 0px;\r
+  border-top-left-radius: 0px;\r
+}\r
+.list-group-item:last-child {\r
+  margin-bottom: 0;\r
+  border-bottom-right-radius: 0px;\r
+  border-bottom-left-radius: 0px;\r
+}\r
+.list-group-item > .badge {\r
+  float: right;\r
+}\r
+.list-group-item > .badge + .badge {\r
+  margin-right: 5px;\r
+}\r
+a.list-group-item {\r
+  color: #555555;\r
+}\r
+a.list-group-item .list-group-item-heading {\r
+  color: #333333;\r
+}\r
+a.list-group-item:hover,\r
+a.list-group-item:focus {\r
+  text-decoration: none;\r
+  background-color: #f5f5f5;\r
+}\r
+a.list-group-item.active,\r
+a.list-group-item.active:hover,\r
+a.list-group-item.active:focus {\r
+  z-index: 2;\r
+  color: #ffffff;\r
+  background-color: #0d8921;\r
+  border-color: #0d8921;\r
+}\r
+a.list-group-item.active .list-group-item-heading,\r
+a.list-group-item.active:hover .list-group-item-heading,\r
+a.list-group-item.active:focus .list-group-item-heading {\r
+  color: inherit;\r
+}\r
+a.list-group-item.active .list-group-item-text,\r
+a.list-group-item.active:hover .list-group-item-text,\r
+a.list-group-item.active:focus .list-group-item-text {\r
+  color: #cccccc;\r
+}\r
+.list-group-item-success {\r
+  color: #5cb85c;\r
+  background-color: #dff0d8;\r
+}\r
+a.list-group-item-success {\r
+  color: #5cb85c;\r
+}\r
+a.list-group-item-success .list-group-item-heading {\r
+  color: inherit;\r
+}\r
+a.list-group-item-success:hover,\r
+a.list-group-item-success:focus {\r
+  color: #5cb85c;\r
+  background-color: #d0e9c6;\r
+}\r
+a.list-group-item-success.active,\r
+a.list-group-item-success.active:hover,\r
+a.list-group-item-success.active:focus {\r
+  color: #fff;\r
+  background-color: #5cb85c;\r
+  border-color: #5cb85c;\r
+}\r
+.list-group-item-info {\r
+  color: #5bc0de;\r
+  background-color: #d9edf7;\r
+}\r
+a.list-group-item-info {\r
+  color: #5bc0de;\r
+}\r
+a.list-group-item-info .list-group-item-heading {\r
+  color: inherit;\r
+}\r
+a.list-group-item-info:hover,\r
+a.list-group-item-info:focus {\r
+  color: #5bc0de;\r
+  background-color: #c4e3f3;\r
+}\r
+a.list-group-item-info.active,\r
+a.list-group-item-info.active:hover,\r
+a.list-group-item-info.active:focus {\r
+  color: #fff;\r
+  background-color: #5bc0de;\r
+  border-color: #5bc0de;\r
+}\r
+.list-group-item-warning {\r
+  color: #f0ad4e;\r
+  background-color: #fcf8e3;\r
+}\r
+a.list-group-item-warning {\r
+  color: #f0ad4e;\r
+}\r
+a.list-group-item-warning .list-group-item-heading {\r
+  color: inherit;\r
+}\r
+a.list-group-item-warning:hover,\r
+a.list-group-item-warning:focus {\r
+  color: #f0ad4e;\r
+  background-color: #faf2cc;\r
+}\r
+a.list-group-item-warning.active,\r
+a.list-group-item-warning.active:hover,\r
+a.list-group-item-warning.active:focus {\r
+  color: #fff;\r
+  background-color: #f0ad4e;\r
+  border-color: #f0ad4e;\r
+}\r
+.list-group-item-danger {\r
+  color: #d9534f;\r
+  background-color: #f2dede;\r
+}\r
+a.list-group-item-danger {\r
+  color: #d9534f;\r
+}\r
+a.list-group-item-danger .list-group-item-heading {\r
+  color: inherit;\r
+}\r
+a.list-group-item-danger:hover,\r
+a.list-group-item-danger:focus {\r
+  color: #d9534f;\r
+  background-color: #ebcccc;\r
+}\r
+a.list-group-item-danger.active,\r
+a.list-group-item-danger.active:hover,\r
+a.list-group-item-danger.active:focus {\r
+  color: #fff;\r
+  background-color: #d9534f;\r
+  border-color: #d9534f;\r
+}\r
+.list-group-item-heading {\r
+  margin-top: 0;\r
+  margin-bottom: 5px;\r
+}\r
+.list-group-item-text {\r
+  margin-bottom: 0;\r
+  line-height: 1.3;\r
+}\r
+.panel {\r
+  margin-bottom: 20px;\r
+  background-color: #ffffff;\r
+  border: 1px solid transparent;\r
+  border-radius: 0px;\r
+  -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\r
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\r
+}\r
+.panel-body {\r
+  padding: 15px;\r
+}\r
+.panel-heading {\r
+  padding: 10px 15px;\r
+  border-bottom: 1px solid transparent;\r
+  border-top-right-radius: -1px;\r
+  border-top-left-radius: -1px;\r
+}\r
+.panel-heading > .dropdown .dropdown-toggle {\r
+  color: inherit;\r
+}\r
+.panel-title {\r
+  margin-top: 0;\r
+  margin-bottom: 0;\r
+  font-size: 16px;\r
+  color: inherit;\r
+}\r
+.panel-title > a {\r
+  color: inherit;\r
+}\r
+.panel-footer {\r
+  padding: 10px 15px;\r
+  background-color: #f5f5f5;\r
+  border-top: 1px solid #dddddd;\r
+  border-bottom-right-radius: -1px;\r
+  border-bottom-left-radius: -1px;\r
+}\r
+.panel > .list-group {\r
+  margin-bottom: 0;\r
+}\r
+.panel > .list-group .list-group-item {\r
+  border-width: 1px 0;\r
+  border-radius: 0;\r
+}\r
+.panel > .list-group .list-group-item:first-child {\r
+  border-top: 0;\r
+}\r
+.panel > .list-group .list-group-item:last-child {\r
+  border-bottom: 0;\r
+}\r
+.panel > .list-group:first-child .list-group-item:first-child {\r
+  border-top-right-radius: -1px;\r
+  border-top-left-radius: -1px;\r
+}\r
+.panel > .list-group:last-child .list-group-item:last-child {\r
+  border-bottom-right-radius: -1px;\r
+  border-bottom-left-radius: -1px;\r
+}\r
+.panel-heading + .list-group .list-group-item:first-child {\r
+  border-top-width: 0;\r
+}\r
+.panel > .table,\r
+.panel > .table-responsive > .table {\r
+  margin-bottom: 0;\r
+}\r
+.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,\r
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,\r
+.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,\r
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,\r
+.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,\r
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,\r
+.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,\r
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {\r
+  border-top-left-radius: -1px;\r
+}\r
+.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,\r
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,\r
+.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,\r
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,\r
+.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,\r
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,\r
+.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,\r
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {\r
+  border-top-right-radius: -1px;\r
+}\r
+.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,\r
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,\r
+.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\r
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\r
+.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,\r
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,\r
+.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,\r
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {\r
+  border-bottom-left-radius: -1px;\r
+}\r
+.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,\r
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,\r
+.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\r
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\r
+.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,\r
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,\r
+.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,\r
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {\r
+  border-bottom-right-radius: -1px;\r
+}\r
+.panel > .panel-body + .table,\r
+.panel > .panel-body + .table-responsive {\r
+  border-top: 1px solid #dddddd;\r
+}\r
+.panel > .table > tbody:first-child > tr:first-child th,\r
+.panel > .table > tbody:first-child > tr:first-child td {\r
+  border-top: 0;\r
+}\r
+.panel > .table-bordered,\r
+.panel > .table-responsive > .table-bordered {\r
+  border: 0;\r
+}\r
+.panel > .table-bordered > thead > tr > th:first-child,\r
+.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,\r
+.panel > .table-bordered > tbody > tr > th:first-child,\r
+.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,\r
+.panel > .table-bordered > tfoot > tr > th:first-child,\r
+.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,\r
+.panel > .table-bordered > thead > tr > td:first-child,\r
+.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,\r
+.panel > .table-bordered > tbody > tr > td:first-child,\r
+.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,\r
+.panel > .table-bordered > tfoot > tr > td:first-child,\r
+.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {\r
+  border-left: 0;\r
+}\r
+.panel > .table-bordered > thead > tr > th:last-child,\r
+.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,\r
+.panel > .table-bordered > tbody > tr > th:last-child,\r
+.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,\r
+.panel > .table-bordered > tfoot > tr > th:last-child,\r
+.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,\r
+.panel > .table-bordered > thead > tr > td:last-child,\r
+.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,\r
+.panel > .table-bordered > tbody > tr > td:last-child,\r
+.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,\r
+.panel > .table-bordered > tfoot > tr > td:last-child,\r
+.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {\r
+  border-right: 0;\r
+}\r
+.panel > .table-bordered > thead > tr:first-child > td,\r
+.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,\r
+.panel > .table-bordered > tbody > tr:first-child > td,\r
+.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,\r
+.panel > .table-bordered > thead > tr:first-child > th,\r
+.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,\r
+.panel > .table-bordered > tbody > tr:first-child > th,\r
+.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {\r
+  border-bottom: 0;\r
+}\r
+.panel > .table-bordered > tbody > tr:last-child > td,\r
+.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,\r
+.panel > .table-bordered > tfoot > tr:last-child > td,\r
+.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,\r
+.panel > .table-bordered > tbody > tr:last-child > th,\r
+.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,\r
+.panel > .table-bordered > tfoot > tr:last-child > th,\r
+.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {\r
+  border-bottom: 0;\r
+}\r
+.panel > .table-responsive {\r
+  border: 0;\r
+  margin-bottom: 0;\r
+}\r
+.panel-group {\r
+  margin-bottom: 20px;\r
+}\r
+.panel-group .panel {\r
+  margin-bottom: 0;\r
+  border-radius: 0px;\r
+  overflow: hidden;\r
+}\r
+.panel-group .panel + .panel {\r
+  margin-top: 5px;\r
+}\r
+.panel-group .panel-heading {\r
+  border-bottom: 0;\r
+}\r
+.panel-group .panel-heading + .panel-collapse .panel-body {\r
+  border-top: 1px solid #dddddd;\r
+}\r
+.panel-group .panel-footer {\r
+  border-top: 0;\r
+}\r
+.panel-group .panel-footer + .panel-collapse .panel-body {\r
+  border-bottom: 1px solid #dddddd;\r
+}\r
+.panel-default {\r
+  border-color: #dddddd;\r
+}\r
+.panel-default > .panel-heading {\r
+  color: #333333;\r
+  background-color: #f5f5f5;\r
+  border-color: #dddddd;\r
+}\r
+.panel-default > .panel-heading + .panel-collapse .panel-body {\r
+  border-top-color: #dddddd;\r
+}\r
+.panel-default > .panel-footer + .panel-collapse .panel-body {\r
+  border-bottom-color: #dddddd;\r
+}\r
+.panel-primary {\r
+  border-color: #0d8921;\r
+}\r
+.panel-primary > .panel-heading {\r
+  color: #ffffff;\r
+  background-color: #0d8921;\r
+  border-color: #0d8921;\r
+}\r
+.panel-primary > .panel-heading + .panel-collapse .panel-body {\r
+  border-top-color: #0d8921;\r
+}\r
+.panel-primary > .panel-footer + .panel-collapse .panel-body {\r
+  border-bottom-color: #0d8921;\r
+}\r
+.panel-success {\r
+  border-color: #d6e9c6;\r
+}\r
+.panel-success > .panel-heading {\r
+  color: #5cb85c;\r
+  background-color: #dff0d8;\r
+  border-color: #d6e9c6;\r
+}\r
+.panel-success > .panel-heading + .panel-collapse .panel-body {\r
+  border-top-color: #d6e9c6;\r
+}\r
+.panel-success > .panel-footer + .panel-collapse .panel-body {\r
+  border-bottom-color: #d6e9c6;\r
+}\r
+.panel-info {\r
+  border-color: #bce8f1;\r
+}\r
+.panel-info > .panel-heading {\r
+  color: #5bc0de;\r
+  background-color: #d9edf7;\r
+  border-color: #bce8f1;\r
+}\r
+.panel-info > .panel-heading + .panel-collapse .panel-body {\r
+  border-top-color: #bce8f1;\r
+}\r
+.panel-info > .panel-footer + .panel-collapse .panel-body {\r
+  border-bottom-color: #bce8f1;\r
+}\r
+.panel-warning {\r
+  border-color: #fbeed5;\r
+}\r
+.panel-warning > .panel-heading {\r
+  color: #f0ad4e;\r
+  background-color: #fcf8e3;\r
+  border-color: #fbeed5;\r
+}\r
+.panel-warning > .panel-heading + .panel-collapse .panel-body {\r
+  border-top-color: #fbeed5;\r
+}\r
+.panel-warning > .panel-footer + .panel-collapse .panel-body {\r
+  border-bottom-color: #fbeed5;\r
+}\r
+.panel-danger {\r
+  border-color: #eed3d7;\r
+}\r
+.panel-danger > .panel-heading {\r
+  color: #d9534f;\r
+  background-color: #f2dede;\r
+  border-color: #eed3d7;\r
+}\r
+.panel-danger > .panel-heading + .panel-collapse .panel-body {\r
+  border-top-color: #eed3d7;\r
+}\r
+.panel-danger > .panel-footer + .panel-collapse .panel-body {\r
+  border-bottom-color: #eed3d7;\r
+}\r
+.well {\r
+  min-height: 20px;\r
+  padding: 19px;\r
+  margin-bottom: 20px;\r
+  background-color: #f5f5f5;\r
+  border: 1px solid #e3e3e3;\r
+  border-radius: 0px;\r
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\r
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\r
+}\r
+.well blockquote {\r
+  border-color: #ddd;\r
+  border-color: rgba(0, 0, 0, 0.15);\r
+}\r
+.well-lg {\r
+  padding: 24px;\r
+  border-radius: 0px;\r
+}\r
+.well-sm {\r
+  padding: 9px;\r
+  border-radius: 0px;\r
+}\r
+.close {\r
+  float: right;\r
+  font-size: 21px;\r
+  font-weight: bold;\r
+  line-height: 1;\r
+  color: #000000;\r
+  text-shadow: 0 1px 0 #ffffff;\r
+  opacity: 0.2;\r
+  filter: alpha(opacity=20);\r
+}\r
+.close:hover,\r
+.close:focus {\r
+  color: #000000;\r
+  text-decoration: none;\r
+  cursor: pointer;\r
+  opacity: 0.5;\r
+  filter: alpha(opacity=50);\r
+}\r
+button.close {\r
+  padding: 0;\r
+  cursor: pointer;\r
+  background: transparent;\r
+  border: 0;\r
+  -webkit-appearance: none;\r
+}\r
+.modal-open {\r
+  overflow: hidden;\r
+}\r
+.modal {\r
+  display: none;\r
+  overflow: auto;\r
+  overflow-y: scroll;\r
+  position: fixed;\r
+  top: 0;\r
+  right: 0;\r
+  bottom: 0;\r
+  left: 0;\r
+  z-index: 1050;\r
+  -webkit-overflow-scrolling: touch;\r
+  outline: 0;\r
+}\r
+.modal.fade .modal-dialog {\r
+  -webkit-transform: translate(0, -25%);\r
+  -ms-transform: translate(0, -25%);\r
+  transform: translate(0, -25%);\r
+  -webkit-transition: -webkit-transform 0.3s ease-out;\r
+  -moz-transition: -moz-transform 0.3s ease-out;\r
+  -o-transition: -o-transform 0.3s ease-out;\r
+  transition: transform 0.3s ease-out;\r
+}\r
+.modal.in .modal-dialog {\r
+  -webkit-transform: translate(0, 0);\r
+  -ms-transform: translate(0, 0);\r
+  transform: translate(0, 0);\r
+}\r
+.modal-dialog {\r
+  position: relative;\r
+  width: auto;\r
+  margin: 10px;\r
+}\r
+.modal-content {\r
+  position: relative;\r
+  background-color: #ffffff;\r
+  border: 1px solid #999999;\r
+  border: 1px solid rgba(0, 0, 0, 0.2);\r
+  border-radius: 0px;\r
+  -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\r
+  box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\r
+  background-clip: padding-box;\r
+  outline: none;\r
+}\r
+.modal-backdrop {\r
+  position: fixed;\r
+  top: 0;\r
+  right: 0;\r
+  bottom: 0;\r
+  left: 0;\r
+  z-index: 1040;\r
+  background-color: #000000;\r
+}\r
+.modal-backdrop.fade {\r
+  opacity: 0;\r
+  filter: alpha(opacity=0);\r
+}\r
+.modal-backdrop.in {\r
+  opacity: 0.5;\r
+  filter: alpha(opacity=50);\r
+}\r
+.modal-header {\r
+  padding: 15px;\r
+  border-bottom: 1px solid #e5e5e5;\r
+  min-height: 16.428571429px;\r
+}\r
+.modal-header .close {\r
+  margin-top: -2px;\r
+}\r
+.modal-title {\r
+  margin: 0;\r
+  line-height: 1.428571429;\r
+}\r
+.modal-body {\r
+  position: relative;\r
+  padding: 20px;\r
+}\r
+.modal-footer {\r
+  margin-top: 15px;\r
+  padding: 19px 20px 20px;\r
+  text-align: right;\r
+  border-top: 1px solid #e5e5e5;\r
+}\r
+.modal-footer .btn + .btn {\r
+  margin-left: 5px;\r
+  margin-bottom: 0;\r
+}\r
+.modal-footer .btn-group .btn + .btn {\r
+  margin-left: -1px;\r
+}\r
+.modal-footer .btn-block + .btn-block {\r
+  margin-left: 0;\r
+}\r
+@media (min-width: 768px) {\r
+  .modal-dialog {\r
+    width: 600px;\r
+    margin: 30px auto;\r
+  }\r
+  .modal-content {\r
+    -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\r
+    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\r
+  }\r
+  .modal-sm {\r
+    width: 300px;\r
+  }\r
+}\r
+@media (min-width: 992px) {\r
+  .modal-lg {\r
+    width: 900px;\r
+  }\r
+}\r
+.tooltip {\r
+  position: absolute;\r
+  z-index: 1030;\r
+  display: block;\r
+  visibility: visible;\r
+  font-size: 12px;\r
+  line-height: 1.4;\r
+  opacity: 0;\r
+  filter: alpha(opacity=0);\r
+}\r
+.tooltip.in {\r
+  opacity: 0.9;\r
+  filter: alpha(opacity=90);\r
+}\r
+.tooltip.top {\r
+  margin-top: -3px;\r
+  padding: 5px 0;\r
+}\r
+.tooltip.right {\r
+  margin-left: 3px;\r
+  padding: 0 5px;\r
+}\r
+.tooltip.bottom {\r
+  margin-top: 3px;\r
+  padding: 5px 0;\r
+}\r
+.tooltip.left {\r
+  margin-left: -3px;\r
+  padding: 0 5px;\r
+}\r
+.tooltip-inner {\r
+  max-width: 200px;\r
+  padding: 3px 8px;\r
+  color: #ffffff;\r
+  text-align: center;\r
+  text-decoration: none;\r
+  background-color: #000000;\r
+  border-radius: 0px;\r
+}\r
+.tooltip-arrow {\r
+  position: absolute;\r
+  width: 0;\r
+  height: 0;\r
+  border-color: transparent;\r
+  border-style: solid;\r
+}\r
+.tooltip.top .tooltip-arrow {\r
+  bottom: 0;\r
+  left: 50%;\r
+  margin-left: -5px;\r
+  border-width: 5px 5px 0;\r
+  border-top-color: #000000;\r
+}\r
+.tooltip.top-left .tooltip-arrow {\r
+  bottom: 0;\r
+  left: 5px;\r
+  border-width: 5px 5px 0;\r
+  border-top-color: #000000;\r
+}\r
+.tooltip.top-right .tooltip-arrow {\r
+  bottom: 0;\r
+  right: 5px;\r
+  border-width: 5px 5px 0;\r
+  border-top-color: #000000;\r
+}\r
+.tooltip.right .tooltip-arrow {\r
+  top: 50%;\r
+  left: 0;\r
+  margin-top: -5px;\r
+  border-width: 5px 5px 5px 0;\r
+  border-right-color: #000000;\r
+}\r
+.tooltip.left .tooltip-arrow {\r
+  top: 50%;\r
+  right: 0;\r
+  margin-top: -5px;\r
+  border-width: 5px 0 5px 5px;\r
+  border-left-color: #000000;\r
+}\r
+.tooltip.bottom .tooltip-arrow {\r
+  top: 0;\r
+  left: 50%;\r
+  margin-left: -5px;\r
+  border-width: 0 5px 5px;\r
+  border-bottom-color: #000000;\r
+}\r
+.tooltip.bottom-left .tooltip-arrow {\r
+  top: 0;\r
+  left: 5px;\r
+  border-width: 0 5px 5px;\r
+  border-bottom-color: #000000;\r
+}\r
+.tooltip.bottom-right .tooltip-arrow {\r
+  top: 0;\r
+  right: 5px;\r
+  border-width: 0 5px 5px;\r
+  border-bottom-color: #000000;\r
+}\r
+.popover {\r
+  position: absolute;\r
+  top: 0;\r
+  left: 0;\r
+  z-index: 1010;\r
+  display: none;\r
+  max-width: 276px;\r
+  padding: 1px;\r
+  text-align: left;\r
+  background-color: #ffffff;\r
+  background-clip: padding-box;\r
+  border: 1px solid #cccccc;\r
+  border: 1px solid rgba(0, 0, 0, 0.2);\r
+  border-radius: 0px;\r
+  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\r
+  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\r
+  white-space: normal;\r
+}\r
+.popover.top {\r
+  margin-top: -10px;\r
+}\r
+.popover.right {\r
+  margin-left: 10px;\r
+}\r
+.popover.bottom {\r
+  margin-top: 10px;\r
+}\r
+.popover.left {\r
+  margin-left: -10px;\r
+}\r
+.popover-title {\r
+  margin: 0;\r
+  padding: 8px 14px;\r
+  font-size: 14px;\r
+  font-weight: normal;\r
+  line-height: 18px;\r
+  background-color: #f7f7f7;\r
+  border-bottom: 1px solid #ebebeb;\r
+  border-radius: 5px 5px 0 0;\r
+}\r
+.popover-content {\r
+  padding: 9px 14px;\r
+}\r
+.popover > .arrow,\r
+.popover > .arrow:after {\r
+  position: absolute;\r
+  display: block;\r
+  width: 0;\r
+  height: 0;\r
+  border-color: transparent;\r
+  border-style: solid;\r
+}\r
+.popover > .arrow {\r
+  border-width: 11px;\r
+}\r
+.popover > .arrow:after {\r
+  border-width: 10px;\r
+  content: "";\r
+}\r
+.popover.top > .arrow {\r
+  left: 50%;\r
+  margin-left: -11px;\r
+  border-bottom-width: 0;\r
+  border-top-color: #999999;\r
+  border-top-color: rgba(0, 0, 0, 0.25);\r
+  bottom: -11px;\r
+}\r
+.popover.top > .arrow:after {\r
+  content: " ";\r
+  bottom: 1px;\r
+  margin-left: -10px;\r
+  border-bottom-width: 0;\r
+  border-top-color: #ffffff;\r
+}\r
+.popover.right > .arrow {\r
+  top: 50%;\r
+  left: -11px;\r
+  margin-top: -11px;\r
+  border-left-width: 0;\r
+  border-right-color: #999999;\r
+  border-right-color: rgba(0, 0, 0, 0.25);\r
+}\r
+.popover.right > .arrow:after {\r
+  content: " ";\r
+  left: 1px;\r
+  bottom: -10px;\r
+  border-left-width: 0;\r
+  border-right-color: #ffffff;\r
+}\r
+.popover.bottom > .arrow {\r
+  left: 50%;\r
+  margin-left: -11px;\r
+  border-top-width: 0;\r
+  border-bottom-color: #999999;\r
+  border-bottom-color: rgba(0, 0, 0, 0.25);\r
+  top: -11px;\r
+}\r
+.popover.bottom > .arrow:after {\r
+  content: " ";\r
+  top: 1px;\r
+  margin-left: -10px;\r
+  border-top-width: 0;\r
+  border-bottom-color: #ffffff;\r
+}\r
+.popover.left > .arrow {\r
+  top: 50%;\r
+  right: -11px;\r
+  margin-top: -11px;\r
+  border-right-width: 0;\r
+  border-left-color: #999999;\r
+  border-left-color: rgba(0, 0, 0, 0.25);\r
+}\r
+.popover.left > .arrow:after {\r
+  content: " ";\r
+  right: 1px;\r
+  border-right-width: 0;\r
+  border-left-color: #ffffff;\r
+  bottom: -10px;\r
+}\r
+.carousel {\r
+  position: relative;\r
+}\r
+.carousel-inner {\r
+  position: relative;\r
+  overflow: hidden;\r
+  width: 100%;\r
+}\r
+.carousel-inner > .item {\r
+  display: none;\r
+  position: relative;\r
+  -webkit-transition: 0.6s ease-in-out left;\r
+  transition: 0.6s ease-in-out left;\r
+}\r
+.carousel-inner > .item > img,\r
+.carousel-inner > .item > a > img {\r
+  line-height: 1;\r
+}\r
+.carousel-inner > .active,\r
+.carousel-inner > .next,\r
+.carousel-inner > .prev {\r
+  display: block;\r
+}\r
+.carousel-inner > .active {\r
+  left: 0;\r
+}\r
+.carousel-inner > .next,\r
+.carousel-inner > .prev {\r
+  position: absolute;\r
+  top: 0;\r
+  width: 100%;\r
+}\r
+.carousel-inner > .next {\r
+  left: 100%;\r
+}\r
+.carousel-inner > .prev {\r
+  left: -100%;\r
+}\r
+.carousel-inner > .next.left,\r
+.carousel-inner > .prev.right {\r
+  left: 0;\r
+}\r
+.carousel-inner > .active.left {\r
+  left: -100%;\r
+}\r
+.carousel-inner > .active.right {\r
+  left: 100%;\r
+}\r
+.carousel-control {\r
+  position: absolute;\r
+  top: 0;\r
+  left: 0;\r
+  bottom: 0;\r
+  width: 15%;\r
+  opacity: 0.5;\r
+  filter: alpha(opacity=50);\r
+  font-size: 20px;\r
+  color: #ffffff;\r
+  text-align: center;\r
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\r
+}\r
+.carousel-control.left {\r
+  background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.5) 0%), color-stop(rgba(0, 0, 0, 0.0001) 100%));\r
+  background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\r
+  background-repeat: repeat-x;\r
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);\r
+}\r
+.carousel-control.right {\r
+  left: auto;\r
+  right: 0;\r
+  background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.0001) 0%), color-stop(rgba(0, 0, 0, 0.5) 100%));\r
+  background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\r
+  background-repeat: repeat-x;\r
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);\r
+}\r
+.carousel-control:hover,\r
+.carousel-control:focus {\r
+  outline: none;\r
+  color: #ffffff;\r
+  text-decoration: none;\r
+  opacity: 0.9;\r
+  filter: alpha(opacity=90);\r
+}\r
+.carousel-control .icon-prev,\r
+.carousel-control .icon-next,\r
+.carousel-control .glyphicon-chevron-left,\r
+.carousel-control .glyphicon-chevron-right {\r
+  position: absolute;\r
+  top: 50%;\r
+  z-index: 5;\r
+  display: inline-block;\r
+}\r
+.carousel-control .icon-prev,\r
+.carousel-control .glyphicon-chevron-left {\r
+  left: 50%;\r
+}\r
+.carousel-control .icon-next,\r
+.carousel-control .glyphicon-chevron-right {\r
+  right: 50%;\r
+}\r
+.carousel-control .icon-prev,\r
+.carousel-control .icon-next {\r
+  width: 20px;\r
+  height: 20px;\r
+  margin-top: -10px;\r
+  margin-left: -10px;\r
+  font-family: serif;\r
+}\r
+.carousel-control .icon-prev:before {\r
+  content: '\2039';\r
+}\r
+.carousel-control .icon-next:before {\r
+  content: '\203a';\r
+}\r
+.carousel-indicators {\r
+  position: absolute;\r
+  bottom: 10px;\r
+  left: 50%;\r
+  z-index: 15;\r
+  width: 60%;\r
+  margin-left: -30%;\r
+  padding-left: 0;\r
+  list-style: none;\r
+  text-align: center;\r
+}\r
+.carousel-indicators li {\r
+  display: inline-block;\r
+  width: 10px;\r
+  height: 10px;\r
+  margin: 1px;\r
+  text-indent: -999px;\r
+  border: 1px solid #ffffff;\r
+  border-radius: 10px;\r
+  cursor: pointer;\r
+  background-color: #000 \9;\r
+  background-color: rgba(0, 0, 0, 0);\r
+}\r
+.carousel-indicators .active {\r
+  margin: 0;\r
+  width: 12px;\r
+  height: 12px;\r
+  background-color: #ffffff;\r
+}\r
+.carousel-caption {\r
+  position: absolute;\r
+  left: 15%;\r
+  right: 15%;\r
+  bottom: 20px;\r
+  z-index: 10;\r
+  padding-top: 20px;\r
+  padding-bottom: 20px;\r
+  color: #ffffff;\r
+  text-align: center;\r
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\r
+}\r
+.carousel-caption .btn {\r
+  text-shadow: none;\r
+}\r
+@media screen and (min-width: 768px) {\r
+  .carousel-control .glyphicons-chevron-left,\r
+  .carousel-control .glyphicons-chevron-right,\r
+  .carousel-control .icon-prev,\r
+  .carousel-control .icon-next {\r
+    width: 30px;\r
+    height: 30px;\r
+    margin-top: -15px;\r
+    margin-left: -15px;\r
+    font-size: 30px;\r
+  }\r
+  .carousel-caption {\r
+    left: 20%;\r
+    right: 20%;\r
+    padding-bottom: 30px;\r
+  }\r
+  .carousel-indicators {\r
+    bottom: 20px;\r
+  }\r
+}\r
+.clearfix:before,\r
+.clearfix:after,\r
+.container:before,\r
+.container:after,\r
+.container-fluid:before,\r
+.container-fluid:after,\r
+.row:before,\r
+.row:after,\r
+.form-horizontal .form-group:before,\r
+.form-horizontal .form-group:after,\r
+.btn-toolbar:before,\r
+.btn-toolbar:after,\r
+.btn-group-vertical > .btn-group:before,\r
+.btn-group-vertical > .btn-group:after,\r
+.nav:before,\r
+.nav:after,\r
+.navbar:before,\r
+.navbar:after,\r
+.navbar-header:before,\r
+.navbar-header:after,\r
+.navbar-collapse:before,\r
+.navbar-collapse:after,\r
+.pager:before,\r
+.pager:after,\r
+.panel-body:before,\r
+.panel-body:after,\r
+.modal-footer:before,\r
+.modal-footer:after {\r
+  content: " ";\r
+  display: table;\r
+}\r
+.clearfix:after,\r
+.container:after,\r
+.container-fluid:after,\r
+.row:after,\r
+.form-horizontal .form-group:after,\r
+.btn-toolbar:after,\r
+.btn-group-vertical > .btn-group:after,\r
+.nav:after,\r
+.navbar:after,\r
+.navbar-header:after,\r
+.navbar-collapse:after,\r
+.pager:after,\r
+.panel-body:after,\r
+.modal-footer:after {\r
+  clear: both;\r
+}\r
+.center-block {\r
+  display: block;\r
+  margin-left: auto;\r
+  margin-right: auto;\r
+}\r
+.pull-right {\r
+  float: right !important;\r
+}\r
+.pull-left {\r
+  float: left !important;\r
+}\r
+.hide {\r
+  display: none !important;\r
+}\r
+.show {\r
+  display: block !important;\r
+}\r
+.invisible {\r
+  visibility: hidden;\r
+}\r
+.text-hide {\r
+  font: 0/0 a;\r
+  color: transparent;\r
+  text-shadow: none;\r
+  background-color: transparent;\r
+  border: 0;\r
+}\r
+.hidden {\r
+  display: none !important;\r
+  visibility: hidden !important;\r
+}\r
+.affix {\r
+  position: fixed;\r
+}\r
+@-ms-viewport {\r
+  width: device-width;\r
+}\r
+.visible-xs,\r
+.visible-sm,\r
+.visible-md,\r
+.visible-lg {\r
+  display: none !important;\r
+}\r
+@media (max-width: 767px) {\r
+  .visible-xs {\r
+    display: block !important;\r
+  }\r
+  table.visible-xs {\r
+    display: table;\r
+  }\r
+  tr.visible-xs {\r
+    display: table-row !important;\r
+  }\r
+  th.visible-xs,\r
+  td.visible-xs {\r
+    display: table-cell !important;\r
+  }\r
+}\r
+@media (min-width: 768px) and (max-width: 991px) {\r
+  .visible-sm {\r
+    display: block !important;\r
+  }\r
+  table.visible-sm {\r
+    display: table;\r
+  }\r
+  tr.visible-sm {\r
+    display: table-row !important;\r
+  }\r
+  th.visible-sm,\r
+  td.visible-sm {\r
+    display: table-cell !important;\r
+  }\r
+}\r
+@media (min-width: 992px) and (max-width: 1199px) {\r
+  .visible-md {\r
+    display: block !important;\r
+  }\r
+  table.visible-md {\r
+    display: table;\r
+  }\r
+  tr.visible-md {\r
+    display: table-row !important;\r
+  }\r
+  th.visible-md,\r
+  td.visible-md {\r
+    display: table-cell !important;\r
+  }\r
+}\r
+@media (min-width: 1200px) {\r
+  .visible-lg {\r
+    display: block !important;\r
+  }\r
+  table.visible-lg {\r
+    display: table;\r
+  }\r
+  tr.visible-lg {\r
+    display: table-row !important;\r
+  }\r
+  th.visible-lg,\r
+  td.visible-lg {\r
+    display: table-cell !important;\r
+  }\r
+}\r
+@media (max-width: 767px) {\r
+  .hidden-xs {\r
+    display: none !important;\r
+  }\r
+}\r
+@media (min-width: 768px) and (max-width: 991px) {\r
+  .hidden-sm {\r
+    display: none !important;\r
+  }\r
+}\r
+@media (min-width: 992px) and (max-width: 1199px) {\r
+  .hidden-md {\r
+    display: none !important;\r
+  }\r
+}\r
+@media (min-width: 1200px) {\r
+  .hidden-lg {\r
+    display: none !important;\r
+  }\r
+}\r
+.visible-print {\r
+  display: none !important;\r
+}\r
+@media print {\r
+  .visible-print {\r
+    display: block !important;\r
+  }\r
+  table.visible-print {\r
+    display: table;\r
+  }\r
+  tr.visible-print {\r
+    display: table-row !important;\r
+  }\r
+  th.visible-print,\r
+  td.visible-print {\r
+    display: table-cell !important;\r
+  }\r
+}\r
+@media print {\r
+  .hidden-print {\r
+    display: none !important;\r
+  }\r
+}\r
+   \r
diff --git a/share/nitweb/views/class.html b/share/nitweb/views/class.html
new file mode 100644 (file)
index 0000000..4950546
--- /dev/null
@@ -0,0 +1,56 @@
+<div class='container-fluid'>
+       <div class='page-header'>
+               <h2><entity-signature mentity='mentity.intro'/></h2>
+               <entity-link mentity='mentity.mpackage' /> :: {{mentity.name}}
+       </div>
+
+       <ul class='nav nav-tabs'>
+               <li role='presentation' class='active'>
+                       <a data-toggle='tab' data-target='#doc'>
+                               <span class='glyphicon glyphicon-book'/> Doc
+                       </a>
+               </li>
+               <li role='presentation'>
+                       <a data-toggle='tab' data-target='#all_props'>
+                               <span class='glyphicon glyphicon-tags'/> All properties
+                       </a>
+               </li>
+               <li role='presentation'>
+                       <a data-toggle='tab' role='tab' data-target='#linearization' aria-controls='linearization' ng-click='entityCtrl.loadEntityLinearization()'>
+                               <span class='glyphicon glyphicon-arrow-down'/> Linearization
+                       </a>
+               </li>
+       </ul>
+
+       <div class='tab-content'>
+               <div role='tabpanel' class='tab-pane fade in active' id='doc'>
+                       <entity-doc mentity='mentity.intro'/>
+
+                       <entity-list list-title='Parents'
+                               list-entities='mentity.parents'
+                               list-object-filter='{}' />
+
+                       <entity-list list-title='Constructors'
+                               list-entities='mentity.all_mproperties'
+                               list-object-filter='{is_init: true}' />
+
+                       <entity-list list-title='Introduced properties'
+                               list-entities='mentity.intro_mproperties'
+                               list-object-filter='{is_init: "!true"}' />
+
+                       <entity-list list-title='Redefined properties'
+                               list-entities='mentity.redef_mproperties'
+                               list-object-filter='{is_init: "!true"}' />
+               </div>
+               <div role='tabpanel' class='tab-pane fade' id='all_props'>
+                       <entity-list list-title='All properties' list-entities='mentity.all_mproperties'
+                               list-object-filter='{}' />
+               </div>
+               <div role='tabpanel' class='tab-pane fade' id='linearization'>
+                       <entity-linearization
+                               list-title='Class definitions'
+                               list-entities='linearization'
+                               list-focus='mentity.intro' />
+               </div>
+       </div>
+</div>
diff --git a/share/nitweb/views/classdef.html b/share/nitweb/views/classdef.html
new file mode 100644 (file)
index 0000000..6c5354b
--- /dev/null
@@ -0,0 +1,43 @@
+<div class='container-fluid' ng-init='entityCtrl.loadEntityLinearization()'>
+       <div class='page-header'>
+               <h2><entity-signature mentity='mentity'/></h2>
+               <entity-link mentity='mentity.mpackage' />
+               :: <entity-link mentity='mentity.mmodule' />
+               :: {{mentity.name}}
+       </div>
+
+       <ul class='nav nav-tabs' role='tablist'>
+               <li role='presentation' class='warning'>
+                       <a ng-href='{{mentity.mclass.web_url}}'>
+                               <span class='glyphicon glyphicon-chevron-left'/> Go to class
+                       </a>
+               </li>
+               <li role='presentation' class='active'>
+                       <a data-toggle='tab' role='tab' data-target='#linearization' aria-controls='linearization'>
+                               <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'>
+               <div role='tabpanel' class='tab-pane fade in active' id='linearization'>
+                       <entity-linearization
+                               list-title='Class definitions'
+                               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>
+</div>
diff --git a/share/nitweb/views/group.html b/share/nitweb/views/group.html
new file mode 100644 (file)
index 0000000..ca19974
--- /dev/null
@@ -0,0 +1,30 @@
+<div class='container-fluid'>
+               <div class='page-header'>
+                       <h2><entity-signature mentity='mentity' /></h2>
+                       <entity-link mentity='mentity.mpackage' /> :: {{mentity.name}}
+               </div>
+
+               <ul class='nav nav-tabs'>
+                       <li class='active'>
+                               <a data-toggle='tab' data-target='#doc'>
+                                       <span class='glyphicon glyphicon-book'/> Doc
+                               </a>
+                       </li>
+               </ul>
+
+               <div class='tab-content'>
+                       <div class='tab-pane fade in active' id='doc'>
+                               <entity-doc mentity='mentity'/>
+
+                               <entity-list list-title='Parent group' list-entities='[mentity.parent]'
+                                       list-object-filter='{}' ng-if='mentity.parent' />
+
+                               <entity-list list-title='Subgroups' list-entities='mentity.mgroups'
+                                       list-object-filter='{}' />
+
+                               <entity-list list-title='Modules' list-entities='mentity.mmodules'
+                                       list-object-filter='{}' />
+                       </div>
+               </div>
+       </div>
+</div>
diff --git a/share/nitweb/views/index.html b/share/nitweb/views/index.html
new file mode 100644 (file)
index 0000000..b7bee8e
--- /dev/null
@@ -0,0 +1,70 @@
+<div class='container-fluid'>
+       <div class='page-header'>
+               <h2>Welcome to NitWeb!</h2>
+               <p class='text-muted'>The Nit knowledge base.</p>
+       </div>
+
+       <ul class='nav nav-tabs' role='tablist'>
+               <li role='presentation' class='active'>
+                       <a data-toggle='tab' role='tab' data-target='#highlighted' aria-controls='highlighted'>
+                               <span class='glyphicon glyphicon-book'/> Highlighed
+                       </a>
+               </li>
+               <li role='presentation'>
+                       <a data-toggle='tab' role='tab' data-target='#required' aria-controls='required'
+                               ng-click='indexCtrl.loadMostRequired()'>
+                               <span class='glyphicon glyphicon-book'/> Most required
+                       </a>
+               </li>
+               <li role='presentation'>
+                       <a data-toggle='tab' role='tab' data-target='#bytags' aria-controls='bytags'
+                               ng-click='indexCtrl.loadByTags()'>
+                               <span class='glyphicon glyphicon-book'/> By tags
+                       </a>
+               </li>
+       </ul>
+       <table class='table'>
+               <tr>
+                       <td ng-repeat='(key, value) in stats'>
+                               <h5><strong>{{value}}</strong>&nbsp;<span>{{key}}</span></h5>
+                       </td>
+               </tr>
+       </table>
+
+       <div class='container-fluid'>
+               <div class='col-xs-9'>
+                       <div class='tab-content'>
+                               <div role='tabpanel' class='tab-pane fade in active' id='highlighted'>
+                                       <entity-list list-title='Highlighted packages'
+                                               list-entities='highlighted'
+                                               list-object-filter='{}' />
+                               </div>
+                               <div role='tabpanel' class='tab-pane fade' id='required'>
+                                       <entity-list list-title='Most required'
+                                               list-entities='required'
+                                               list-object-filter='{}' />
+                               </div>
+                               <div role='tabpanel' class='tab-pane fade' id='bytags'>
+                                       <h3>Tags</h3>
+                                       <div class='container-fluid'>
+                                               <div class='col-xs-3' ng-repeat='(tag, packages) in bytags'>
+                                                       <span class='badge'>{{packages.length}}</span>
+                                                       <a ng-click='indexCtrl.scrollTo(tag)'>{{tag}}</a>
+                                               </div>
+                                       </div>
+                                       <div ng-repeat='(tag, packages) in bytags'>
+                                               <entity-list list-id='{{tag}}' list-title='{{tag}}'
+                                                       list-entities='packages'
+                                                       list-object-filter='{}' />
+                                       </div>
+                               </div>
+                       </div>
+               </div>
+               <div class='col-xs-3'>
+                       <contributor-list list-title='Maintainers'
+                                       list-contributors='contributors.maintainers' />
+                       <contributor-list list-title='Contributors'
+                                       list-contributors='contributors.contributors' />
+               </div>
+       </div>
+</div>
diff --git a/share/nitweb/views/module.html b/share/nitweb/views/module.html
new file mode 100644 (file)
index 0000000..9a15c76
--- /dev/null
@@ -0,0 +1,52 @@
+<div class='container-fluid'>
+       <div class='page-header'>
+               <h2><entity-signature mentity='mentity'/></h2>
+               <entity-link mentity='mentity.mpackage' /> :: {{mentity.name}}
+       </div>
+
+       <ul class='nav nav-tabs'>
+               <li role='presentation' class='active'>
+                       <a data-toggle='tab' data-target='#doc'>
+                               <span class='glyphicon glyphicon-book'/> Doc
+                       </a>
+               </li>
+               <li role='presentation'>
+                       <a data-toggle='tab' data-target='#code' ng-click="entityCtrl.loadEntityCode()">
+                               <span class='glyphicon glyphicon-console'/> Code
+                       </a>
+               </li>
+               <li role='presentation'>
+                       <a data-toggle='tab' data-target='#defs' ng-click="entityCtrl.loadEntityDefs()">
+                               <span class='glyphicon glyphicon-asterisk'/> Class definitions
+                       </a>
+               </li>
+       </ul>
+
+       <div class='tab-content'>
+               <div role='tabpanel' class='tab-pane fade in active' id='doc'>
+                       <entity-doc mentity='mentity'/>
+
+                       <entity-list list-title='Imported modules' list-entities='mentity.imports'
+                               list-object-filter='{}' />
+
+                       <entity-list list-title='Introduced classes' list-entities='mentity.intro_mclasses'
+                               list-object-filter='{}' />
+
+                       <entity-list list-title='Class redefinitions' list-entities='mentity.redef_mclassdefs'
+                               list-object-filter='{}' />
+
+               </div>
+               <div 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 role='tabpanel' class='tab-pane fade' id='defs'>
+                       <entity-list list-title='Class definitions' list-entities='defs'
+                               list-object-filter='{}' />
+               </div>
+       </div>
+</div>
diff --git a/share/nitweb/views/package.html b/share/nitweb/views/package.html
new file mode 100644 (file)
index 0000000..ee19aa5
--- /dev/null
@@ -0,0 +1,22 @@
+<div class='container-fluid'>
+       <div class='page-header'>
+               <h2><entity-signature mentity='mentity'/></h2>
+       </div>
+
+       <ul class='nav nav-tabs' role='tablist'>
+               <li role='presentation' class='active'>
+                       <a data-toggle='tab' role='tab' data-target='#doc' aria-controls="doc">
+                               <span class='glyphicon glyphicon-book'/> Doc
+                       </a>
+               </li>
+       </ul>
+
+       <div class='tab-content'>
+               <div role='tabpanel' class='tab-pane fade in active' id='doc'>
+                       <entity-doc mentity='mentity'/>
+
+                       <entity-list list-title='Groups' list-entities='mentity.mgroups'
+                               list-object-filter='{}' />
+               </div>
+       </div>
+</div>
diff --git a/share/nitweb/views/propdef.html b/share/nitweb/views/propdef.html
new file mode 100644 (file)
index 0000000..87b0ff3
--- /dev/null
@@ -0,0 +1,44 @@
+<div class='container-fluid' ng-init='entityCtrl.loadEntityLinearization()'>
+       <div class='page-header'>
+               <h2><entity-signature mentity='mentity'/></h2>
+               <entity-link mentity='mentity.mpackage' />
+               :: <entity-link mentity='mentity.mmodule' />
+               :: <entity-link mentity='mentity.mclassdef' />
+               :: {{mentity.name}}
+       </div>
+
+       <ul class='nav nav-tabs'>
+               <li role='presentation' class='warning'>
+                       <a href='{{mentity.mproperty.web_url}}'>
+                               <span class='glyphicon glyphicon-chevron-left'/> Go to property
+                       </a>
+               </li>
+               <li role='presentation' class='active'>
+                       <a data-toggle='tab' role='tab' data-target='#linearization' aria-controls='linearization'>
+                               <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'>
+               <div role='tabpanel' class='tab-pane fade in active' id='linearization'>
+                       <entity-linearization
+                               list-title='Class definitions'
+                               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>
+</div>
diff --git a/share/nitweb/views/property.html b/share/nitweb/views/property.html
new file mode 100644 (file)
index 0000000..fea0da7
--- /dev/null
@@ -0,0 +1,34 @@
+<div class='container-fluid'>
+
+       <div class='page-header'>
+               <h2><entity-signature mentity='mentity.intro'/></h2>
+               <entity-link mentity='mentity.mpackage' />
+               :: <entity-link mentity='mentity.intro_mclassdef' />
+               :: {{mentity.name}}
+       </div>
+
+       <ul class='nav nav-tabs'>
+               <li role='presentation' class='active'>
+                       <a data-toggle='tab' data-target='#doc'>
+                               <span class='glyphicon glyphicon-book'/> Doc
+                       </a>
+               </li>
+               <li role='presentation'>
+                       <a data-toggle='tab' role='tab' data-target='#linearization' aria-controls='linearization' ng-click='entityCtrl.loadEntityLinearization()'>
+                               <span class='glyphicon glyphicon-arrow-down'/> Linearization
+                       </a>
+               </li>
+       </ul>
+
+       <div class='tab-content'>
+               <div role='tabpanel' class='tab-pane fade in active' id='doc'>
+                       <entity-doc mentity='mentity.intro'/>
+               </div>
+               <div role='tabpanel' class='tab-pane fade' id='linearization'>
+                       <entity-linearization
+                               list-title='Class definitions'
+                               list-entities='linearization'
+                               list-focus='mentity.intro' />
+               </div>
+       </div>
+</div>
index 1743179..92510bc 100644 (file)
@@ -362,8 +362,9 @@ class Catalog
                var doc_score = 0.0
                for g in mpackage.mgroups do
                        mmodules += g.mmodules.length
-                       entity_score += 1.0
-                       if g.mdoc != null then doc_score += 1.0
+                       var gs = 1.0
+                       entity_score += gs
+                       if g.mdoc != null then doc_score += gs
                        for m in g.mmodules do
                                var source = m.location.file
                                if source != null then
@@ -382,21 +383,23 @@ class Catalog
                                                loc += file.line_starts.length - 1
                                        end
                                end
-                               entity_score += 1.0
-                               if m.mdoc != null then doc_score += 1.0
+                               var ms = gs
+                               if m.is_test_suite then ms /= 100.0
+                               entity_score += ms
+                               if m.mdoc != null then doc_score += ms else ms /= 10.0
                                for cd in m.mclassdefs do
-                                       var s = 0.2
-                                       if not cd.is_intro then s /= 10.0
-                                       if not cd.mclass.visibility <= private_visibility then s /= 10.0
-                                       entity_score += s
-                                       if cd.mdoc != null then doc_score += s
+                                       var cs = ms * 0.2
+                                       if not cd.is_intro then cs /= 100.0
+                                       if not cd.mclass.visibility <= private_visibility then cs /= 100.0
+                                       entity_score += cs
+                                       if cd.mdoc != null then doc_score += cs
                                        mclasses += 1
                                        for pd in cd.mpropdefs do
-                                               s = 0.1
-                                               if not pd.is_intro then s /= 10.0
-                                               if not pd.mproperty.visibility <= private_visibility then s /= 10.0
-                                               entity_score += s
-                                               if pd.mdoc != null then doc_score += s
+                                               var ps = ms * 0.1
+                                               if not pd.is_intro then ps /= 100.0
+                                               if not pd.mproperty.visibility <= private_visibility then ps /= 100.0
+                                               entity_score += ps
+                                               if pd.mdoc != null then doc_score += ps
                                                if not pd isa MMethodDef then continue
                                                mmethods += 1
                                        end
index 967d335..d1626cb 100644 (file)
@@ -379,6 +379,13 @@ redef class ModelBuilder
                var mgrouppath = path.join_path("..").simplify_path
                var mgroup = identify_group(mgrouppath)
 
+               if mgroup != null then
+                       var mpackage = mgroup.mpackage
+                       if not mpackage.accept(path) then
+                               mgroup = null
+                               toolcontext.info("module `{path}` excluded from package `{mpackage}`", 2)
+                       end
+               end
                if mgroup == null then
                        # singleton package
                        var loc = new Location.opaque_file(path)
@@ -473,6 +480,13 @@ redef class ModelBuilder
                        if not stopper.file_exists then
                                # Recursively get the parent group
                                parent = identify_group(parentpath)
+                               if parent != null then do
+                                       var mpackage = parent.mpackage
+                                       if not mpackage.accept(dirpath) then
+                                               toolcontext.info("directory `{dirpath}` excluded from package `{mpackage}`", 2)
+                                               parent = null
+                                       end
+                               end
                                if parent == null then
                                        # Parent is not a group, thus we are not a group either
                                        mgroups[rdp] = null
@@ -686,7 +700,7 @@ redef class ModelBuilder
                if decl != null then
                        var decl_name = decl.n_name.n_id.text
                        if decl_name != mmodule.name then
-                               error(decl.n_name, "Error: module name mismatch; declared {decl_name} file named {mmodule.name}.")
+                               warning(decl.n_name, "module-name-mismatch", "Error: module name mismatch; declared {decl_name} file named {mmodule.name}.")
                        end
                end
 
@@ -1064,6 +1078,27 @@ redef class MPackage
        #
        # Some packages, like stand-alone packages or virtual packages have no `ini` file associated.
        var ini: nullable ConfigTree = null
+
+       # Array of relative source paths excluded according to the `source.exclude` key of the `ini`
+       var excludes: nullable Array[String] is lazy do
+               var ini = self.ini
+               if ini == null then return null
+               var exclude = ini["source.exclude"]
+               if exclude == null then return null
+               var excludes = exclude.split(":")
+               return excludes
+       end
+
+       # Does the source inclusion/inclusion rules of the package `ini` accept such path?
+       fun accept(filepath: String): Bool
+       do
+               var excludes = self.excludes
+               if excludes != null then
+                       var relpath = root.filepath.relpath(filepath)
+                       if excludes.has(relpath) then return false
+               end
+               return true
+       end
 end
 
 redef class MGroup
index 79970e5..bd7d12c 100644 (file)
@@ -37,6 +37,11 @@ redef class MEntity
        fun collect_modifiers: Array[String] do
                return new Array[String]
        end
+
+       # Collect `self` linearization anchored on `mainmodule`.
+       fun collect_linearization(mainmodule: MModule): nullable Array[MEntity] do
+               return null
+       end
 end
 
 redef class MPackage
@@ -163,6 +168,12 @@ redef class MClass
 
        redef fun collect_modifiers do return intro.collect_modifiers
 
+       redef fun collect_linearization(mainmodule) do
+               var mclassdefs = self.mclassdefs.to_a
+               mainmodule.linearize_mclassdefs(mclassdefs)
+               return mclassdefs
+       end
+
        # Collect direct parents of `self` with `visibility >= to min_visibility`.
        fun collect_parents(view: ModelView): Set[MClass] do
                var res = new HashSet[MClass]
@@ -411,6 +422,15 @@ end
 
 redef class MClassDef
 
+       redef fun collect_linearization(mainmodule) do
+               var mclassdefs = new Array[MClassDef]
+               for mclassdef in in_hierarchy.as(not null).greaters do
+                       if mclassdef.mclass == self.mclass then mclassdefs.add mclassdef
+               end
+               mainmodule.linearize_mclassdefs(mclassdefs)
+               return mclassdefs
+       end
+
        # Collect mpropdefs in 'self' with `visibility >= min_visibility`.
        fun collect_mpropdefs(view: ModelView): Set[MPropDef] do
                var res = new HashSet[MPropDef]
@@ -457,6 +477,12 @@ end
 
 redef class MProperty
        redef fun collect_modifiers do return intro.collect_modifiers
+
+       redef fun collect_linearization(mainmodule) do
+               var mpropdefs = self.mpropdefs.to_a
+               mainmodule.linearize_mpropdefs(mpropdefs)
+               return mpropdefs
+       end
 end
 
 redef class MPropDef
@@ -486,4 +512,16 @@ redef class MPropDef
                end
                return res
        end
+
+       redef fun collect_linearization(mainmodule) do
+               var mpropdefs = new Array[MPropDef]
+               var mentity = self
+               while not mentity.is_intro do
+                       mpropdefs.add mentity
+                       mentity = mentity.lookup_next_definition(mainmodule, mentity.mclassdef.bound_mtype)
+               end
+               mpropdefs.add mentity
+               mainmodule.linearize_mpropdefs(mpropdefs)
+               return mpropdefs
+       end
 end
index 85bb115..5cc235f 100644 (file)
@@ -348,6 +348,12 @@ class ModelBuilder
                var name = qid.n_id.text
                var qname = qid.full_name
 
+               if bad_class_names[mmodule].has(qname) then
+                       error(qid, "Error: class `{qname}` not found in module `{mmodule}`.")
+                       return
+               end
+               bad_class_names[mmodule].add(qname)
+
                var all_classes = model.get_mclasses_by_name(name)
                var hints = new Array[String]
 
@@ -404,6 +410,10 @@ class ModelBuilder
                error(qid, "Error: class `{qname}` not found in module `{mmodule}`.")
        end
 
+       # List of already reported bad class names.
+       # Used to not perform and repeat hints again and again.
+       private var bad_class_names = new MultiHashMap[MModule, String]
+
        # Return the static type associated to the node `ntype`.
        # `mmodule` and `mclassdef` is the context where the call is made (used to understand formal types)
        # In case of problem, an error is displayed on `ntype` and null is returned.
index 9410b4d..a28bb6f 100644 (file)
@@ -64,6 +64,12 @@ if opt_eval.value then
        modelbuilder.load_rt_module(parent, amodule, "-")
 
        mmodules = [amodule.mmodule.as(not null)]
+else if progname == "-" then
+       var content = stdin.read_all
+       var amodule = toolcontext.parse_module(content)
+       toolcontext.check_errors
+       modelbuilder.load_rt_module(null, amodule, "-")
+       mmodules = [amodule.mmodule.as(not null)]
 else
        mmodules = modelbuilder.parse([progname])
 end
index 5c478f0..42dc5dd 100644 (file)
@@ -561,10 +561,16 @@ end
 
 # Get files or groups
 var args = tc.option_context.rest
+var mmodules
 if opt_no_parse.value then
-       modelbuilder.scan_full(args)
+       mmodules = modelbuilder.scan_full(args)
 else
-       modelbuilder.parse_full(args)
+       mmodules = modelbuilder.parse_full(args)
+end
+var mpackages = new Set[MPackage]
+for m in mmodules do
+       var p = m.mpackage
+       if p != null then mpackages.add p
 end
 
 # Scan packages and compute information
@@ -586,7 +592,7 @@ for p in model.mpackages do
        end
 end
 
-if not opt_no_git.value then for p in model.mpackages do
+if not opt_no_git.value then for p in mpackages do
        catalog.git_info(p)
 end
 
@@ -703,7 +709,7 @@ css.write_to_file(out/"style.css")
 
 # PAGES
 
-for p in model.mpackages do
+for p in mpackages do
        # print p
        var f = "p/{p.name}.html"
        catalog.package_page(p)
@@ -726,7 +732,7 @@ index.add catalog.list_best(catalog.score)
 if catalog.deps.not_empty then
        index.add "<h2>Most Required</h2>\n"
        var reqs = new Counter[MPackage]
-       for p in model.mpackages do
+       for p in mpackages do
                reqs[p] = catalog.deps[p].smallers.length - 1
        end
        index.add catalog.list_best(reqs)
@@ -743,7 +749,7 @@ index.add """
 <div class="sidebar">
 <h3>Stats</h3>
 <ul class="box">
-<li>{{{model.mpackages.length}}} packages</li>
+<li>{{{mpackages.length}}} packages</li>
 <li>{{{catalog.maint2proj.length}}} maintainers</li>
 <li>{{{catalog.contrib2proj.length}}} contributors</li>
 <li>{{{catalog.tag2proj.length}}} tags</li>
@@ -775,6 +781,6 @@ page = catalog.new_page("")
 page.more_head.add "<title>Projets of Nit</title>"
 page.add """<div class="content">\n<h1>People of Nit</h1>\n"""
 page.add "<h2>Table of Projets</h2>\n"
-page.add catalog.table_packages(model.mpackages)
+page.add catalog.table_packages(mpackages.to_a)
 page.add "</div>\n"
 page.write_to_file(out/"table.html")
index f875083..4321214 100644 (file)
@@ -178,7 +178,10 @@ if opt_tree.value then
                var pa = mp.mgroup
                while pa != null and not pa.is_interesting do pa = pa.parent
                ot.add(pa, mp)
-               if pa != null then mgroups.add pa
+               while pa != null do
+                       mgroups.add pa
+                       pa = pa.parent
+               end
        end
        for g in mgroups do
                var pa = g.parent
index 878d3f2..996fe27 100644 (file)
@@ -20,7 +20,7 @@ import testing
 
 var toolcontext = new ToolContext
 
-toolcontext.option_context.add_option(toolcontext.opt_full, toolcontext.opt_output, toolcontext.opt_dir, toolcontext.opt_noact, toolcontext.opt_pattern, toolcontext.opt_file, toolcontext.opt_gen_unit, toolcontext.opt_gen_force, toolcontext.opt_gen_private, toolcontext.opt_gen_show, toolcontext.opt_nitc)
+toolcontext.option_context.add_option(toolcontext.opt_full, toolcontext.opt_output, toolcontext.opt_dir, toolcontext.opt_noact, toolcontext.opt_pattern, toolcontext.opt_autosav, toolcontext.opt_gen_unit, toolcontext.opt_gen_force, toolcontext.opt_gen_private, toolcontext.opt_gen_show, toolcontext.opt_nitc)
 toolcontext.tooldescription = "Usage: nitunit [OPTION]... <file.nit>...\nExecutes the unit tests from Nit source files."
 
 toolcontext.process_options(args)
@@ -31,10 +31,6 @@ if toolcontext.opt_gen_unit.value then
                print "Option --pattern cannot be used with --gen-suite"
                exit(0)
        end
-       if toolcontext.opt_file.value != null then
-               print "Option --target-file cannot be used with --gen-suite"
-               exit(0)
-       end
 else
        if toolcontext.opt_gen_force.value then
                print "Option --force must be used with --gen-suite"
@@ -92,25 +88,69 @@ end
 
 for m in mmodules do
        page.add modelbuilder.test_markdown(m)
-       page.add modelbuilder.test_unit(m)
+       var ts = modelbuilder.test_unit(m)
+       if ts != null then page.add ts
 end
 
 var file = toolcontext.opt_output.value
 if file == null then file = "nitunit.xml"
 page.write_to_file(file)
-# print docunits results
-print "DocUnits:"
-if modelbuilder.unit_entities == 0 then
-       print "No doc units found"
-else if modelbuilder.failed_entities == 0 and not toolcontext.opt_noact.value then
-       print "DocUnits Success"
+
+# Print results
+printn "Docunits: Entities: {modelbuilder.total_entities}; Documented ones: {modelbuilder.doc_entities}; With nitunits: {modelbuilder.unit_entities}"
+if modelbuilder.unit_entities == 0 or toolcontext.opt_noact.value then
+       print ""
+else
+       printn "; Failures: "
+       var cpt = modelbuilder.failed_entities
+       if toolcontext.opt_no_color.value then
+               print cpt
+       else if cpt == 0 then
+               print "0".green.bold
+       else
+               print cpt.to_s.red.bold
+       end
 end
-print "Entities: {modelbuilder.total_entities}; Documented ones: {modelbuilder.doc_entities}; With nitunits: {modelbuilder.unit_entities}; Failures: {modelbuilder.failed_entities}"
-# print testsuites results
-print "\nTestSuites:"
-if modelbuilder.total_tests == 0 then
-       print "No test cases found"
-else if modelbuilder.failed_tests == 0 and not toolcontext.opt_noact.value then
-       print "TestSuites Success"
+printn "Test suites: Classes: {modelbuilder.total_classes}; Test Cases: {modelbuilder.total_tests}"
+if modelbuilder.total_tests == 0 or toolcontext.opt_noact.value then
+       print ""
+else
+       printn "; Failures: "
+       var cpt = modelbuilder.failed_tests
+       if toolcontext.opt_no_color.value then
+               print cpt
+       else if cpt == 0 then
+               print "0".green.bold
+       else
+               print cpt.to_s.red.bold
+       end
 end
-print "Class suites: {modelbuilder.total_classes}; Test Cases: {modelbuilder.total_tests}; Failures: {modelbuilder.failed_tests}"
+
+var total = modelbuilder.unit_entities + modelbuilder.total_tests
+var fail = modelbuilder.failed_entities + modelbuilder.failed_tests
+if toolcontext.opt_noact.value then
+       # nothing
+else if total == 0 then
+       var head = "[NOTHING]"
+       if not toolcontext.opt_no_color.value then
+               head = head.yellow
+       end
+       print "{head} No unit tests to execute."
+else if fail == 0 then
+       var head = "[SUCCESS]"
+       if not toolcontext.opt_no_color.value then
+               head = head.green.bold
+       end
+       print "{head} All {total} tests passed."
+else
+       var head = "[FAILURE]"
+       if not toolcontext.opt_no_color.value then
+               head = head.red.bold
+       end
+       print "{head} {fail}/{total} tests failed."
+
+       print "`{test_dir}` is not removed for investigation."
+       exit 1
+end
+
+test_dir.rmdir
index c055ee9..a1a41a3 100644 (file)
@@ -43,22 +43,68 @@ private class NitwebPhase
                var model = mainmodule.model
                var modelbuilder = toolcontext.modelbuilder
 
+               # Build catalog
+               var catalog = new Catalog(modelbuilder)
+               for mpackage in model.mpackages do
+                       catalog.deps.add_node(mpackage)
+                       for mgroup in mpackage.mgroups do
+                               for mmodule in mgroup.mmodules do
+                                       for imported in mmodule.in_importation.direct_greaters do
+                                               var ip = imported.mpackage
+                                               if ip == null or ip == mpackage then continue
+                                               catalog.deps.add_edge(mpackage, ip)
+                                       end
+                               end
+                       end
+                       catalog.git_info(mpackage)
+                       catalog.package_page(mpackage)
+               end
+
                # Run the server
                var host = toolcontext.opt_host.value or else "localhost"
                var port = toolcontext.opt_port.value
 
                var app = new App
-               app.use("/random", new RandomAction(model))
-               app.use("/doc/:namespace", new DocAction(model, modelbuilder))
-               app.use("/code/:namespace", new CodeAction(model, modelbuilder))
-               app.use("/search/:namespace", new SearchAction(model))
-               app.use("/uml/:namespace", new UMLDiagramAction(model, mainmodule))
-               app.use("/", new TreeAction(model))
+
+               app.use_before("/*", new RequestClock)
+               app.use("/api", new APIRouter(model, modelbuilder, mainmodule, catalog))
+               app.use("/doc/:namespace", new DocAction(model, mainmodule, modelbuilder))
+               app.use("/*", new StaticHandler(toolcontext.share_dir / "nitweb", "index.html"))
+               app.use_after("/*", new ConsoleLog)
 
                app.listen(host, port.to_i)
        end
 end
 
+# Group all api handlers in one router.
+class APIRouter
+       super Router
+
+       # Model to pass to handlers.
+       var model: Model
+
+       # ModelBuilder to pass to handlers.
+       var modelbuilder: ModelBuilder
+
+       # Mainmodule to pass to handlers.
+       var mainmodule: MModule
+
+       # Catalog to pass to handlers.
+       var catalog: Catalog
+
+       init do
+               use("/catalog", new APICatalogRouter(model, mainmodule, catalog))
+               use("/list", new APIList(model, mainmodule))
+               use("/search", new APISearch(model, mainmodule))
+               use("/random", new APIRandom(model, mainmodule))
+               use("/entity/:id", new APIEntity(model, mainmodule))
+               use("/code/:id", new APIEntityCode(model, mainmodule, modelbuilder))
+               use("/uml/:id", new APIEntityUML(model, mainmodule))
+               use("/linearization/:id", new APIEntityLinearization(model, mainmodule))
+               use("/defs/:id", new APIEntityDefs(model, mainmodule))
+       end
+end
+
 # build toolcontext
 var toolcontext = new ToolContext
 var tpl = new Template
index 292ff28..c7dae85 100644 (file)
@@ -3,6 +3,8 @@ name=nitc
 tags=devel,cli
 maintainer=Jean Privat <jean@pryen.org>
 license=Apache-2.0
+[source]
+exclude=parser/parser_abs.nit:parser/.parser-nofact.nit
 [upstream]
 browse=https://github.com/nitlang/nit/tree/master/src
 git=https://github.com/nitlang/nit.git
index c072314..88e2b7e 100644 (file)
@@ -255,6 +255,8 @@ class RapidTypeAnalysis
                                add_cast(paramtype)
                        end
 
+                       if mmethoddef.is_abstract then continue
+
                        var npropdef = modelbuilder.mpropdef2node(mmethoddef)
 
                        if npropdef isa AClassdef then
@@ -314,7 +316,9 @@ class RapidTypeAnalysis
                                if not check_depth(rt) then continue
                                #print "{ot}/{t} -> {rt}"
                                live_types.add(rt)
-                               todo_types.add(rt)
+                               # unshift means a deep-first visit.
+                               # So that the `check_depth` limit is reached sooner.
+                               todo_types.unshift(rt)
                        end
                end
                #print "MType {live_types.length}: {live_types.join(", ")}"
index 88651ef..36ce1f9 100644 (file)
@@ -35,7 +35,7 @@ redef class ToolContext
        # Working directory for testing.
        fun test_dir: String do
                var dir = opt_dir.value
-               if dir == null then return ".nitunit"
+               if dir == null then return "nitunit.out"
                return dir
        end
 
@@ -97,15 +97,19 @@ ulimit -t {{{ulimit_usertime}}} 2> /dev/null
 
        # Show a single-line status to use as a progression.
        #
-       # Note that the line starts with `'\r'` and is not ended by a `'\n'`.
+       # If `has_progress_bar` is true, then the output is a progress bar.
+       # The printed the line starts with `'\r'` and is not ended by a `'\n'`.
        # So it is expected that:
        # * no other output is printed between two calls
        # * the last `show_unit_status` is followed by a new-line
-       fun show_unit_status(name: String, tests: SequenceRead[UnitTest], more_message: nullable String)
+       #
+       # If `has_progress_bar` is false, then only the first and last state is shown
+       fun show_unit_status(name: String, tests: SequenceRead[UnitTest])
        do
                var esc = 27.code_point.to_s
-               var line = "\r{esc}[K* {name} ["
+               var line = "\r\x1B[K==== {name} ["
                var done = tests.length
+               var fails = 0
                for t in tests do
                        if not t.is_done then
                                line += " "
@@ -114,15 +118,53 @@ ulimit -t {{{ulimit_usertime}}} 2> /dev/null
                                line += ".".green.bold
                        else
                                line += "X".red.bold
+                               fails += 1
+                       end
+               end
+
+               if not has_progress_bar then
+                       if done == 0 then
+                               print "==== {name} | tests: {tests.length}"
                        end
+                       return
                end
-               line += "] {done}/{tests.length}"
-               if more_message != null then
-                       line += " " + more_message
+
+               if done < tests.length then
+                       line += "] {done}/{tests.length}"
+               else
+                       line += "] tests: {tests.length} "
+                       if fails == 0 then
+                               line += "OK".green.bold
+                       else
+                               line += "KO: {fails}".red.bold
+                       end
                end
                printn "{line}"
        end
 
+       # Is a progress bar printed?
+       #
+       # true if color (because and non-verbose mode
+       # (because verbose mode messes up with the progress bar).
+       fun has_progress_bar: Bool
+       do
+               return not opt_no_color.value and opt_verbose.value <= 0
+       end
+
+       # Clear the line if `has_progress_bar` (no-op else)
+       fun clear_progress_bar
+       do
+               if has_progress_bar then printn "\r\x1B[K"
+       end
+
+       # Show the full description of the test-case.
+       #
+       # The output honors `--no-color`.
+       #
+       # `more message`, if any, is added after the error message.
+       fun show_unit(test: UnitTest, more_message: nullable String) do
+               print test.to_screen(more_message, not opt_no_color.value)
+       end
 end
 
 # A unit test is an elementary test discovered, run and reported by nitunit.
@@ -156,34 +198,51 @@ abstract class UnitTest
        # The location where the error occurred, if it makes sense.
        var error_location: nullable Location = null is writable
 
+       # Additional noteworthy information when a test success.
+       var info: nullable String = null
+
        # A colorful `[OK]` or `[KO]`.
-       fun status_tag: String do
+       fun status_tag(color: nullable Bool): String do
+               color = color or else true
                if not is_done then
                        return "[  ]"
                else if error != null then
-                       return "[KO]".red.bold
+                       var res = "[KO]"
+                       if color then res = res.red.bold
+                       return res
                else
-                       return "[OK]".green.bold
+                       var res = "[OK]"
+                       if color then res = res.green.bold
+                       return res
                end
        end
 
        # The full (color) description of the test-case.
        #
        # `more message`, if any, is added after the error message.
-       fun to_screen(more_message: nullable String): String do
+       fun to_screen(more_message: nullable String, color: nullable Bool): String do
+               color = color or else true
                var res
                var error = self.error
                if error != null then
                        if more_message != null then error += " " + more_message
                        var loc = error_location or else location
-                       res = "{status_tag} {full_name}\n     {loc.to_s.yellow}: {error}\n{loc.colored_line("1;31")}"
+                       if color then
+                               res = "{status_tag(color)} {full_name}\n     {loc.to_s.yellow}: {error}\n{loc.colored_line("1;31")}"
+                       else
+                               res = "{status_tag(color)} {full_name}\n     {loc}: {error}"
+                       end
                        var output = self.raw_output
                        if output != null then
                                res += "\n     Output\n\t{output.chomp.replace("\n", "\n\t")}\n"
                        end
                else
-                       res = "{status_tag} {full_name}"
+                       res = "{status_tag(color)} {full_name}"
                        if more_message != null then res += more_message
+                       var info = self.info
+                       if info != null then
+                               res += "\n     {info}"
+                       end
                end
                return res
        end
index 2465d37..75e0830 100644 (file)
@@ -78,15 +78,17 @@ class NitUnitExecutor
        # All extracted docunits
        var docunits = new Array[DocUnit]
 
-       fun show_status(more_message: nullable String)
+       fun show_status
        do
-               toolcontext.show_unit_status(name, docunits, more_message)
+               toolcontext.show_unit_status(name, docunits)
        end
 
        fun mark_done(du: DocUnit)
        do
                du.is_done = true
-               show_status(du.full_name + " " + du.status_tag)
+               toolcontext.clear_progress_bar
+               toolcontext.show_unit(du)
+               show_status
        end
 
        # Execute all the docunits
@@ -96,33 +98,41 @@ class NitUnitExecutor
                        return
                end
 
+               # Try to group each nitunit into a single source file to fasten the compilation
                var simple_du = new Array[DocUnit]
                show_status
                for du in docunits do
                        # Skip existing errors
                        if du.error != null then
-                               mark_done(du)
                                continue
                        end
 
                        var ast = toolcontext.parse_something(du.block)
                        if ast isa AExpr then
                                simple_du.add du
+                       end
+               end
+               test_simple_docunits(simple_du)
+
+               # Now test them in order
+               for du in docunits do
+                       if du.error != null then
+                               # Nothing to execute. Conclude
+                       else if du.test_file != null then
+                               # Already compiled. Execute it.
+                               execute_simple_docunit(du)
                        else
+                               # Need to try to compile it, then execute it
                                test_single_docunit(du)
                        end
+                       mark_done(du)
                end
 
-               test_simple_docunits(simple_du)
-
+               # Final status
                show_status
                print ""
 
                for du in docunits do
-                       print du.to_screen
-               end
-
-               for du in docunits do
                        testsuite.add du.to_xml
                end
        end
@@ -164,28 +174,35 @@ class NitUnitExecutor
 
                if res != 0 then
                        # Compilation error.
-                       # Fall-back to individual modes:
-                       for du in dus do
-                               test_single_docunit(du)
-                       end
+                       # They will be executed independently
                        return
                end
 
+               # Compilation was a success.
+               # Store what need to be executed for each one.
                i = 0
                for du in dus do
                        i += 1
-                       toolcontext.info("Execute doc-unit {du.full_name} in {file} {i}", 1)
-                       var res2 = toolcontext.safe_exec("{file.to_program_name}.bin {i} >'{file}.out1' 2>&1 </dev/null")
-                       du.was_exec = true
+                       du.test_file = file
+                       du.test_arg = i
+               end
+       end
 
-                       var content = "{file}.out1".to_path.read_all
-                       du.raw_output = content
+       # Execute a docunit compiled by `test_single_docunit`
+       fun execute_simple_docunit(du: DocUnit)
+       do
+               var file = du.test_file.as(not null)
+               var i = du.test_arg.as(not null)
+               toolcontext.info("Execute doc-unit {du.full_name} in {file} {i}", 1)
+               var res2 = toolcontext.safe_exec("{file.to_program_name}.bin {i} >'{file}.out1' 2>&1 </dev/null")
+               du.was_exec = true
 
-                       if res2 != 0 then
-                               du.error = "Runtime error in {file} with argument {i}"
-                               toolcontext.modelbuilder.failed_entities += 1
-                       end
-                       mark_done(du)
+               var content = "{file}.out1".to_path.read_all
+               du.raw_output = content
+
+               if res2 != 0 then
+                       du.error = "Runtime error in {file} with argument {i}"
+                       toolcontext.modelbuilder.failed_entities += 1
                end
        end
 
@@ -222,7 +239,6 @@ class NitUnitExecutor
                        du.error = "Runtime error in {file}"
                        toolcontext.modelbuilder.failed_entities += 1
                end
-               mark_done(du)
        end
 
        # Create and fill the header of a unit file `file`.
@@ -258,7 +274,7 @@ class NitUnitExecutor
                if mmodule != null then
                        opts.add "-I {mmodule.filepath.dirname}"
                end
-               var cmd = "{nitc} --ignore-visibility --no-color '{file}' {opts.join(" ")} >'{file}.out1' 2>&1 </dev/null -o '{file}.bin'"
+               var cmd = "{nitc} --ignore-visibility --no-color -q '{file}' {opts.join(" ")} >'{file}.out1' 2>&1 </dev/null -o '{file}.bin'"
                var res = toolcontext.safe_exec(cmd)
                return res
        end
@@ -349,11 +365,11 @@ private class NitunitDecorator
                var mdoc = executor.mdoc
                assert mdoc != null
 
-               var next_number = 0
+               var next_number = 1
                var name = executor.xml_name
                if executor.docunits.not_empty and executor.docunits.last.mdoc == mdoc then
                        next_number = executor.docunits.last.number + 1
-                       name += "+" + next_number.to_s
+                       name += "#" + next_number.to_s
                end
 
                var res = new DocUnit(mdoc, next_number, "", executor.xml_classname, name)
@@ -376,10 +392,23 @@ class DocUnit
        # The numbering of self in mdoc (starting with 0)
        var number: Int
 
+       # The generated Nit source file that contains the unit-test
+       #
+       # Note that a same generated file can be used for multiple tests.
+       # See `test_arg` that is used to distinguish them
+       var test_file: nullable String = null
+
+       # The command-line argument to use when executing the test, if any.
+       var test_arg: nullable Int = null
+
        redef fun full_name do
                var mentity = mdoc.original_mentity
                if mentity != null then
-                       return mentity.full_name
+                       var res = mentity.full_name
+                       if number > 1 then
+                               res += "#{number}"
+                       end
+                       return res
                else
                        return xml_classname + "." + xml_name
                end
@@ -543,7 +572,7 @@ redef class ModelBuilder
        fun test_mdoc(mdoc: MDoc): HTMLTag
        do
                var ts = new HTMLTag("testsuite")
-               var file = mdoc.location.to_s
+               var file = mdoc.location.file.filename
 
                toolcontext.info("nitunit: doc-unit file {file}", 2)
 
index bbe9372..f102c6c 100644 (file)
@@ -20,10 +20,10 @@ import html
 private import annotation
 
 redef class ToolContext
-       # -- target-file
-       var opt_file = new OptionString("Specify test suite location", "-t", "--target-file")
        # --pattern
        var opt_pattern = new OptionString("Only run test case with name that match pattern", "-p", "--pattern")
+       # --autosav
+       var opt_autosav = new OptionBool("Automatically create/update .res files for black box testing", "--autosav")
 end
 
 # Used to test nitunit test files.
@@ -32,20 +32,9 @@ class NitUnitTester
        # `ModelBuilder` used to parse test files.
        var mbuilder: ModelBuilder
 
-       # Parse a file and return the contained `MModule`.
-       private fun parse_module_unit(file: String): nullable MModule do
-               var mmodule = mbuilder.parse([file]).first
-               if mbuilder.get_mmodule_annotation("test_suite", mmodule) == null then return null
-               mbuilder.run_phases
-               return mmodule
-       end
-
-       # Compile and execute the test suite for a NitUnit `file`.
-       fun test_module_unit(file: String): nullable TestSuite do
+       # Compile and execute `mmodule` as a test suite.
+       fun test_module_unit(mmodule: MModule): TestSuite do
                var toolcontext = mbuilder.toolcontext
-               var mmodule = parse_module_unit(file)
-               # is the module a test_suite?
-               if mmodule == null then return null
                var suite = new TestSuite(mmodule, toolcontext)
                # method to execute before all tests in the module
                var before_module = mmodule.before_test
@@ -132,9 +121,9 @@ class TestSuite
        # Test to be executed after the whole test suite.
        var after_module: nullable TestCase = null
 
-       fun show_status(more_message: nullable String)
+       fun show_status
        do
-               toolcontext.show_unit_status("Test-suite of module " + mmodule.full_name, test_cases, more_message)
+               toolcontext.show_unit_status("Test-suite of module " + mmodule.full_name, test_cases)
        end
 
        # Execute the test suite
@@ -150,17 +139,16 @@ class TestSuite
                if not before_module == null then before_module.run
                for case in test_cases do
                        case.run
-                       show_status(case.full_name + " " + case.status_tag)
+                       toolcontext.clear_progress_bar
+                       toolcontext.show_unit(case)
+                       show_status
                end
 
-               show_status
-               print ""
-
                var after_module = self.after_module
                if not after_module == null then after_module.run
-               for case in test_cases do
-                       print case.to_screen
-               end
+
+               show_status
+               print ""
        end
 
        # Write the test unit for `self` in a nit compilable file.
@@ -209,7 +197,7 @@ class TestSuite
                        return
                end
                var include_dir = module_file.filename.dirname
-               var cmd = "{nitc} --no-color '{file}.nit' -I {include_dir} -o '{file}.bin' > '{file}.out' 2>&1 </dev/null"
+               var cmd = "{nitc} --no-color -q '{file}.nit' -I {include_dir} -o '{file}.bin' > '{file}.out' 2>&1 </dev/null"
                var res = toolcontext.safe_exec(cmd)
                var f = new FileReader.open("{file}.out")
                var msg = f.read_all
@@ -270,7 +258,8 @@ class TestCase
                var test_file = test_suite.test_file
                var res_name = "{test_file}_{method_name.escape_to_c}"
                var res = toolcontext.safe_exec("{test_file}.bin {method_name} > '{res_name}.out1' 2>&1 </dev/null")
-               self.raw_output = "{res_name}.out1".to_path.read_all
+               var raw_output = "{res_name}.out1".to_path.read_all
+               self.raw_output = raw_output
                # set test case result
                if res != 0 then
                        error = "Runtime Error in file {test_file}.nit"
@@ -280,17 +269,36 @@ class TestCase
                        var mmodule = test_method.mclassdef.mmodule
                        var file = mmodule.filepath
                        if file != null then
-                               var sav = file.dirname / mmodule.name + ".sav" / test_method.name + ".res"
-                               if sav.file_exists then
+                               var tries = [ file.dirname / mmodule.name + ".sav" / test_method.name + ".res",
+                                       file.dirname / "sav" / test_method.name + ".res" ,
+                                       file.dirname / test_method.name + ".res" ]
+                               var savs = [ for t in tries do if t.file_exists then t ]
+                               if savs.length == 1 then
+                                       var sav = savs.first
                                        toolcontext.info("Diff output with {sav}", 1)
                                        res = toolcontext.safe_exec("diff -u --label 'expected:{sav}' --label 'got:{res_name}.out1' '{sav}' '{res_name}.out1' > '{res_name}.diff' 2>&1 </dev/null")
-                                       if res != 0 then
+                                       if res == 0 then
+                                               # OK
+                                       else if toolcontext.opt_autosav.value then
+                                               raw_output.write_to_file(sav)
+                                               info = "Expected output updated: {sav} (--autoupdate)"
+                                       else
                                                self.raw_output = "Diff\n" + "{res_name}.diff".to_path.read_all
                                                error = "Difference with expected output: diff -u {sav} {res_name}.out1"
                                                toolcontext.modelbuilder.failed_tests += 1
                                        end
-                               else
-                                       toolcontext.info("No diff: {sav} not found", 2)
+                               else if savs.length > 1 then
+                                       toolcontext.info("Conflicting diffs: {savs.join(", ")}", 1)
+                                       error = "Conflicting expected output: {savs.join(", ", " and ")} all exist"
+                                       toolcontext.modelbuilder.failed_tests += 1
+                               else if not raw_output.is_empty then
+                                       toolcontext.info("No diff: {tries.join(", ", " or ")} not found", 1)
+                                       if toolcontext.opt_autosav.value then
+                                               var sav = tries.first
+                                               sav.dirname.mkdir
+                                               raw_output.write_to_file(sav)
+                                               info = "Expected output saved: {sav} (--autoupdate)"
+                                       end
                                end
                        end
                end
@@ -323,7 +331,7 @@ end
 
 redef class MClassDef
        # Is the class a TestClass?
-       # i.e. begins with "Test"
+       # i.e. is a subclass of `TestSuite`
        private fun is_test: Bool do
                var in_hierarchy = self.in_hierarchy
                if in_hierarchy == null then return false
@@ -368,33 +376,14 @@ redef class ModelBuilder
        # Number of failed tests.
        var failed_tests = 0
 
-       # Run NitUnit test file for mmodule (if exists).
-       fun test_unit(mmodule: MModule): HTMLTag do
-               var ts = new HTMLTag("testsuite")
-               toolcontext.info("nitunit: test-suite test_{mmodule}", 2)
-               var f = toolcontext.opt_file.value
-               var test_file = "test_{mmodule.name}.nit"
-               if f != null then
-                       test_file = f
-               else if not test_file.file_exists then
-                       var module_file = mmodule.location.file
-                       if module_file == null then
-                               toolcontext.info("Skip test for {mmodule}, no file found", 2)
-                               return ts
-                       end
-                       var include_dir = module_file.filename.dirname
-                       test_file = "{include_dir}/{test_file}"
-               end
-               if not test_file.file_exists then
-                       toolcontext.info("Skip test for {mmodule}, no file {test_file} found", 2)
-                       return ts
-               end
+       # Run NitUnit test suite for `mmodule` (if it is one).
+       fun test_unit(mmodule: MModule): nullable HTMLTag do
+               # is the module a test_suite?
+               if get_mmodule_annotation("test_suite", mmodule) == null then return null
+               toolcontext.info("nitunit: test-suite {mmodule}", 2)
+
                var tester = new NitUnitTester(self)
-               var res = tester.test_module_unit(test_file)
-               if res == null then
-                       toolcontext.info("Skip test for {mmodule}, no test suite found", 2)
-                       return ts
-               end
+               var res = tester.test_module_unit(mmodule)
                return res.to_xml
        end
 end
diff --git a/src/web/api_catalog.nit b/src/web/api_catalog.nit
new file mode 100644 (file)
index 0000000..72d207f
--- /dev/null
@@ -0,0 +1,138 @@
+# 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.
+
+module api_catalog
+
+import web_base
+import catalog
+
+# Group all api handlers in one router.
+class APICatalogRouter
+       super Router
+
+       # Model to pass to handlers.
+       var model: Model
+
+       # Mainmodule to pass to handlers.
+       var mainmodule: MModule
+
+       # Catalog to pass to handlers.
+       var catalog: Catalog
+
+       init do
+               use("/highlighted", new APICatalogHighLighted(model, mainmodule, catalog))
+               use("/required", new APICatalogMostRequired(model, mainmodule, catalog))
+               use("/bytags", new APICatalogByTags(model, mainmodule, catalog))
+               use("/contributors", new APICatalogContributors(model, mainmodule, catalog))
+               use("/stats", new APICatalogStats(model, mainmodule, catalog))
+       end
+end
+
+abstract class APICatalogHandler
+       super APIHandler
+
+       var catalog: Catalog
+
+       # List the 10 best packages from `cpt`
+       fun list_best(cpt: Counter[MPackage]): JsonArray do
+               var res = new JsonArray
+               var best = cpt.sort
+               for i in [1..10] do
+                       if i > best.length then break
+                       res.add best[best.length-i]
+               end
+               return res
+       end
+
+       # List packages by group.
+       fun list_by(map: MultiHashMap[Object, MPackage]): JsonObject do
+               var res = new JsonObject
+               var keys = map.keys.to_a
+               alpha_comparator.sort(keys)
+               for k in keys do
+                       var projs = map[k].to_a
+                       alpha_comparator.sort(projs)
+                       res[k.to_s.html_escape] = new JsonArray.from(projs)
+               end
+               return res
+       end
+end
+
+class APICatalogStats
+       super APICatalogHandler
+
+       redef fun get(req, res) do
+               var obj = new JsonObject
+               obj["packages"] = model.mpackages.length
+               obj["maintainers"] = catalog.maint2proj.length
+               obj["contributors"] = catalog.contrib2proj.length
+               obj["modules"] = catalog.mmodules.sum
+               obj["classes"] = catalog.mclasses.sum
+               obj["methods"] = catalog.mmethods.sum
+               obj["loc"] = catalog.loc.sum
+               res.json obj
+       end
+end
+
+class APICatalogHighLighted
+       super APICatalogHandler
+
+       redef fun get(req, res) do res.json list_best(catalog.score)
+end
+
+class APICatalogMostRequired
+       super APICatalogHandler
+
+       redef fun get(req, res) do
+               if catalog.deps.not_empty then
+                       var reqs = new Counter[MPackage]
+                       for p in model.mpackages do
+                               reqs[p] = catalog.deps[p].smallers.length - 1
+                       end
+                       res.json list_best(reqs)
+                       return
+               end
+               res.json new JsonArray
+       end
+end
+
+class APICatalogByTags
+       super APICatalogHandler
+
+       redef fun get(req, res) do res.json list_by(catalog.tag2proj)
+end
+
+class APICatalogContributors
+       super APICatalogHandler
+
+       redef fun get(req, res) do
+               var obj = new JsonObject
+               obj["maintainers"] = new JsonArray.from(catalog.maint2proj.keys)
+               obj["contributors"] = new JsonArray.from(catalog.contrib2proj.keys)
+               res.json obj
+       end
+end
+
+redef class Person
+       super Jsonable
+
+       redef fun to_json do
+               var obj = new JsonObject
+               obj["name"] = name
+               obj["email"] = email
+               obj["page"] = page
+               obj["hash"] = (email or else "").md5.to_lower
+               return obj.to_json
+       end
+end
diff --git a/src/web/model_api.nit b/src/web/model_api.nit
new file mode 100644 (file)
index 0000000..0d4c205
--- /dev/null
@@ -0,0 +1,230 @@
+# 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.
+
+module model_api
+
+import web_base
+import highlight
+import uml
+
+# List all mentities.
+#
+# MEntities can be filtered on their kind using the `k` parameter.
+# Allowed kinds are `package`, `group`, `module`, `class`, `classdef`, `property`, `propdef`.
+#
+# List size can be limited with the `n` parameter.
+#
+# Example: `GET /list?k=module?n=10`
+class APIList
+       super APIHandler
+
+       # List mentities depending on the `k` kind parameter.
+       fun list_mentities(req: HttpRequest): Array[MEntity] do
+               var k = req.string_arg("k")
+               var mentities = new Array[MEntity]
+               if k == "package" then
+                       for mentity in view.mpackages do mentities.add mentity
+               else if k == "group" then
+                       for mentity in view.mgroups do mentities.add mentity
+               else if k == "module" then
+                       for mentity in view.mmodules do mentities.add mentity
+               else if k == "class" then
+                       for mentity in view.mclasses do mentities.add mentity
+               else if k == "classdef" then
+                       for mentity in view.mclassdefs do mentities.add mentity
+               else if k == "property" then
+                       for mentity in view.mproperties do mentities.add mentity
+               else if k == "propdef" then
+                       for mentity in view.mpropdefs do mentities.add mentity
+               else
+                       for mentity in view.mentities do mentities.add mentity
+               end
+               return mentities
+       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")
+               if n != null then
+                       return mentities.sub(0, n)
+               end
+               return mentities
+       end
+
+       redef fun get(req, res) do
+               var mentities = list_mentities(req)
+               mentities = limit_mentities(req, mentities)
+               res.json new JsonArray.from(mentities)
+       end
+end
+
+# Search mentities from a query string.
+#
+# Example: `GET /search?q=Arr`
+class APISearch
+       super APIList
+
+       redef fun list_mentities(req) do
+               var q = req.string_arg("q")
+               var mentities = new Array[MEntity]
+               if q == null then return mentities
+               for mentity in view.mentities do
+                       if mentity.name.has_prefix(q) then mentities.add mentity
+               end
+               return mentities
+       end
+end
+
+# Return a random list of MEntities.
+#
+# Example: `GET /random?n=10&k=module`
+class APIRandom
+       super APIList
+
+       # Randomize mentities order.
+       fun randomize_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
+               var res = mentities.to_a
+               res.shuffle
+               return res
+       end
+
+       redef fun get(req, res) do
+               var mentities = list_mentities(req)
+               mentities = limit_mentities(req, mentities)
+               mentities = randomize_mentities(req, mentities)
+               res.json new JsonArray.from(mentities)
+       end
+end
+
+# Return the JSON representation of a MEntity.
+#
+# Example: `GET /entity/core::Array`
+class APIEntity
+       super APIHandler
+
+       redef fun get(req, res) do
+               var mentity = mentity_from_uri(req, res)
+               if mentity == null then return
+               res.json mentity.api_json(self)
+       end
+end
+
+# Linearize super definitions of a MClassDef or a MPropDef if any.
+#
+# Example: `GET /entity/core::Array/linearization`
+class APIEntityLinearization
+       super APIHandler
+
+       redef fun get(req, res) do
+               var mentity = mentity_from_uri(req, res)
+               if mentity == null then
+                       res.error 404
+                       return
+               end
+               var lin = mentity.collect_linearization(mainmodule)
+               if lin == null then
+                       res.error 404
+                       return
+               end
+               res.json new JsonArray.from(lin)
+       end
+end
+
+# List definitions of a MEntity.
+#
+# Example: `GET /defs/core::Array`
+class APIEntityDefs
+       super APIHandler
+
+       redef fun get(req, res) do
+               var mentity = mentity_from_uri(req, res)
+               var arr = new JsonArray
+               if mentity isa MModule then
+                       for mclassdef in mentity.mclassdefs do arr.add mclassdef
+               else if mentity isa MClass then
+                       for mclassdef in mentity.mclassdefs do arr.add mclassdef
+               else if mentity isa MClassDef then
+                       for mpropdef in mentity.mpropdefs do arr.add mpropdef
+               else if mentity isa MProperty then
+                       for mpropdef in mentity.mpropdefs do arr.add mpropdef
+               else
+                       res.error 404
+                       return
+               end
+               res.json arr
+       end
+end
+
+# Return a UML representation of MEntity.
+#
+# Example: `GET /entity/core::Array/uml`
+class APIEntityUML
+       super APIHandler
+
+       redef fun get(req, res) do
+               var mentity = mentity_from_uri(req, res)
+               var dot
+               if mentity isa MClassDef then mentity = mentity.mclass
+               if mentity isa MClass then
+                       var uml = new UMLModel(view, mainmodule)
+                       dot = uml.generate_class_uml.write_to_string
+               else if mentity isa MModule then
+                       var uml = new UMLModel(view, mentity)
+                       dot = uml.generate_package_uml.write_to_string
+               else
+                       res.error 404
+                       return
+               end
+               res.send render_svg(dot)
+       end
+
+       # Render a `dot` string as a svg image.
+       fun render_svg(dot: String): String do
+               var proc = new ProcessDuplex("dot", "-Tsvg")
+               var svg = proc.write_and_read(dot)
+               proc.close
+               proc.wait
+               return svg
+       end
+end
+
+# Return the source code of MEntity.
+#
+# Example: `GET /entity/core::Array/code`
+class APIEntityCode
+       super APIHandler
+
+       # Modelbuilder used to access sources.
+       var modelbuilder: ModelBuilder
+
+       redef fun get(req, res) do
+               var mentity = mentity_from_uri(req, res)
+               if mentity == null then return
+               var source = render_source(mentity)
+               if source == null then
+                       res.error 404
+                       return
+               end
+               res.send source
+       end
+
+       # Highlight `mentity` source code.
+       private fun render_source(mentity: MEntity): nullable HTMLTag do
+               var node = modelbuilder.mentity2node(mentity)
+               if node == null then return null
+               var hl = new HighlightVisitor
+               hl.enter_visit node
+               return hl.html
+       end
+end
index 6f581f4..2a22001 100644 (file)
@@ -16,3 +16,5 @@
 module web
 
 import web_actions
+import model_api
+import api_catalog
index e9198ae..cb193fd 100644 (file)
@@ -20,7 +20,7 @@ import uml
 
 # Display the tree of all loaded mentities.
 class TreeAction
-       super ModelAction
+       super ModelHandler
 
        redef fun get(req, res) do
                var model = init_model_view(req)
@@ -29,51 +29,9 @@ class TreeAction
        end
 end
 
-# Display the list of mentities matching `namespace`.
-class SearchAction
-       super ModelAction
-
-       # TODO handle more than full namespaces.
-       redef fun get(req, res) do
-               var namespace = req.param("namespace")
-               var model = init_model_view(req)
-               var mentity = find_mentity(model, namespace)
-               if mentity == null then
-                       res.error(404)
-                       return
-               end
-               if req.is_json_asked then
-                       res.json(mentity.to_json)
-                       return
-               end
-               var view = new HtmlResultPage(namespace or else "null", [mentity])
-               res.send_view(view)
-       end
-end
-
-# Display a MEntity source code.
-class CodeAction
-       super ModelAction
-
-       # Modelbuilder used to access sources.
-       var modelbuilder: ModelBuilder
-
-       redef fun get(req, res) do
-               var namespace = req.param("namespace")
-               var model = init_model_view(req)
-               var mentity = find_mentity(model, namespace)
-               if mentity == null then
-                       res.error(404)
-                       return
-               end
-               var view = new HtmlSourcePage(modelbuilder, mentity)
-               res.send_view(view)
-       end
-end
-
 # Display the doc of a MEntity.
 class DocAction
-       super ModelAction
+       super ModelHandler
 
        # Modelbuilder used to access sources.
        var modelbuilder: ModelBuilder
@@ -86,76 +44,7 @@ class DocAction
                        res.error(404)
                        return
                end
-               if req.is_json_asked then
-                       res.json(mentity.to_json)
-                       return
-               end
-
                var view = new HtmlDocPage(modelbuilder, mentity)
                res.send_view(view)
        end
 end
-
-# Return an UML diagram for `namespace`.
-class UMLDiagramAction
-       super ModelAction
-
-       # Mainmodule used for hierarchy flattening.
-       var mainmodule: MModule
-
-       redef fun get(req, res) do
-               var namespace = req.param("namespace")
-               var model = init_model_view(req)
-               var mentity = find_mentity(model, namespace)
-               if mentity == null then
-                       res.error(404)
-                       return
-               end
-
-               var dot
-               if mentity isa MClassDef then mentity = mentity.mclass
-               if mentity isa MClass then
-                       var uml = new UMLModel(model, mainmodule)
-                       dot = uml.generate_class_uml.write_to_string
-               else if mentity isa MModule then
-                       var uml = new UMLModel(model, mentity)
-                       dot = uml.generate_package_uml.write_to_string
-               else
-                       res.error(404)
-                       return
-               end
-               var view = new HtmlDotPage(dot, mentity.as(not null).html_name)
-               res.send_view(view)
-       end
-end
-
-# Return a random list of MEntities.
-class RandomAction
-       super ModelAction
-
-       redef fun get(req, res) do
-               var n = req.int_arg("n") or else 10
-               var k = req.string_arg("k") or else "modules"
-               var model = init_model_view(req)
-               var mentities: Array[MEntity]
-               if k == "modules" then
-                       mentities = model.mmodules.to_a
-               else if k == "classdefs" then
-                       mentities = model.mclassdefs.to_a
-               else
-                       mentities = model.mpropdefs.to_a
-               end
-               mentities.shuffle
-               mentities = mentities.sub(0, n)
-               if req.is_json_asked then
-                       var json = new JsonArray
-                       for mentity in mentities do
-                               json.add mentity.to_json
-                       end
-                       res.json(json)
-                       return
-               end
-               var view = new HtmlResultPage("random", mentities)
-               res.send_view(view)
-       end
-end
index 258ec60..fcaac53 100644 (file)
@@ -17,15 +17,19 @@ module web_base
 
 import model::model_views
 import model::model_json
+import doc_down
 import popcorn
 
 # Specific nitcorn Action that uses a Model
-class ModelAction
+class ModelHandler
        super Handler
 
        # Model to use.
        var model: Model
 
+       # MModule used to flatten model.
+       var mainmodule: MModule
+
        # Find the MEntity ` with `full_name`.
        fun find_mentity(model: ModelView, full_name: nullable String): nullable MEntity do
                if full_name == null then return null
@@ -47,6 +51,42 @@ class ModelAction
        end
 end
 
+# Specific handler for nitweb API.
+abstract class APIHandler
+       super ModelHandler
+
+       # The JSON API does not filter anything by default.
+       #
+       # So we can cache the model view.
+       var view: ModelView is lazy do
+               var view = new ModelView(model)
+               view.min_visibility = private_visibility
+               view.include_fictive = true
+               view.include_empty_doc = true
+               view.include_attribute = true
+               view.include_test_suite = true
+               return view
+       end
+
+       # Try to load the mentity from uri with `/:id`.
+       #
+       # Send 400 if `:id` is null.
+       # Send 404 if no entity is found.
+       # Return null in both cases.
+       fun mentity_from_uri(req: HttpRequest, res: HttpResponse): nullable MEntity do
+               var id = req.param("id")
+               if id == null then
+                       res.error 400
+                       return null
+               end
+               var mentity = find_mentity(view, id)
+               if mentity == null then
+                       res.error 404
+               end
+               return mentity
+       end
+end
+
 # A NitView is rendered by an action.
 interface NitView
        # Renders this view and returns something that can be written to a HTTP response.
@@ -58,9 +98,147 @@ redef class HttpResponse
        fun send_view(view: NitView, status: nullable Int) do send(view.render, status)
 end
 
-redef class HttpRequest
-       # Does the client asked for a json formatted response?
-       #
-       # Checks the URL get parameter `?json=true`.
-       fun is_json_asked: Bool do return bool_arg("json") or else false
+redef class MEntity
+
+       # URL to `self` within the web interface.
+       fun web_url: String is abstract
+
+       # URL to `self` within the JSON api.
+       fun api_url: String do return "/api/entity/" / full_name
+
+       redef fun json do
+               var obj = super
+               obj["web_url"] = web_url
+               obj["api_url"] = api_url
+               return obj
+       end
+
+       # Get the full json repesentation of `self` with MEntityRefs resolved.
+       fun api_json(handler: ModelHandler): JsonObject do return json
+end
+
+redef class MEntityRef
+       redef fun json do
+               var obj = super
+               obj["web_url"] = mentity.web_url
+               obj["api_url"] = mentity.api_url
+               obj["name"] = mentity.name
+               obj["mdoc"] = mentity.mdoc_or_fallback
+               obj["visibility"] = mentity.visibility
+               obj["location"] = mentity.location
+               var modifiers = new JsonArray
+               for modifier in mentity.collect_modifiers do
+                       modifiers.add modifier
+               end
+               obj["modifiers"] = modifiers
+               return obj
+       end
+end
+
+redef class MDoc
+
+       # Add doc down processing
+       redef fun json do
+               var obj = super
+               obj["synopsis"] = synopsis
+               obj["documentation"] = documentation
+               obj["comment"] = comment
+               obj["html_synopsis"] = html_synopsis.write_to_string
+               obj["html_documentation"] = html_documentation.write_to_string
+               obj["html_comment"] = html_comment.write_to_string
+               return obj
+       end
+end
+
+redef class MPackage
+       redef var web_url = "/package/{full_name}" is lazy
+end
+
+redef class MGroup
+       redef var web_url = "/group/{full_name}" is lazy
+end
+
+redef class MModule
+       redef var web_url = "/module/{full_name}" is lazy
+
+       redef fun api_json(handler) do
+               var obj = super
+               obj["intro_mclassdefs"] = to_mentity_refs(collect_intro_mclassdefs(private_view))
+               obj["redef_mclassdefs"] = to_mentity_refs(collect_redef_mclassdefs(private_view))
+               obj["imports"] = to_mentity_refs(in_importation.direct_greaters)
+               return obj
+       end
+end
+
+redef class MClass
+       redef var web_url = "/class/{full_name}" is lazy
+
+       redef fun api_json(handler) do
+               var obj = super
+               obj["all_mproperties"] = to_mentity_refs(collect_accessible_mproperties(private_view))
+               obj["intro_mproperties"] = to_mentity_refs(collect_intro_mproperties(private_view))
+               obj["redef_mproperties"] = to_mentity_refs(collect_redef_mproperties(private_view))
+               var poset = hierarchy_poset(handler.mainmodule, private_view)
+               obj["parents"] = to_mentity_refs(poset[self].direct_greaters)
+               return obj
+       end
+end
+
+redef class MClassDef
+       redef var web_url = "/classdef/{full_name}" is lazy
+
+       redef fun json do
+               var obj = super
+               obj["intro"] = to_mentity_ref(mclass.intro)
+               obj["mpackage"] = to_mentity_ref(mmodule.mpackage)
+               return obj
+       end
+
+       redef fun api_json(handler) do
+               var obj = super
+               obj["intro_mpropdefs"] = to_mentity_refs(collect_intro_mpropdefs(private_view))
+               obj["redef_mpropdefs"] = to_mentity_refs(collect_redef_mpropdefs(private_view))
+               return obj
+       end
+end
+
+redef class MProperty
+       redef var web_url = "/property/{full_name}" is lazy
+
+       redef fun json do
+               var obj = super
+               obj["intro_mclass"] = to_mentity_ref(intro_mclassdef.mclass)
+               obj["mpackage"] = to_mentity_ref(intro_mclassdef.mmodule.mpackage)
+               return obj
+       end
+end
+
+redef class MPropDef
+       redef var web_url = "/propdef/{full_name}" is lazy
+
+       redef fun json do
+               var obj = super
+               obj["intro"] = to_mentity_ref(mproperty.intro)
+               obj["intro_mclassdef"] = to_mentity_ref(mproperty.intro.mclassdef)
+               obj["mmodule"] = to_mentity_ref(mclassdef.mmodule)
+               obj["mgroup"] = to_mentity_ref(mclassdef.mmodule.mgroup)
+               obj["mpackage"] = to_mentity_ref(mclassdef.mmodule.mpackage)
+               return obj
+       end
+end
+
+redef class MClassType
+       redef var web_url = mclass.web_url is lazy
+end
+
+redef class MNullableType
+       redef var web_url = mtype.web_url is lazy
+end
+
+redef class MParameterType
+       redef var web_url = mclass.web_url is lazy
+end
+
+redef class MVirtualType
+       redef var web_url = mproperty.web_url is lazy
 end
index 58016e2..d8f8c9f 100644 (file)
@@ -35,34 +35,6 @@ class HtmlHomePage
        end
 end
 
-# Display a search results list.
-class HtmlResultPage
-       super NitView
-
-       # Initial query.
-       var query: String
-
-       # Result set
-       var results: Array[MEntity]
-
-       redef fun render do
-               var tpl = new Template
-               tpl.add new Header(1, "Results for {query}")
-               if results.is_empty then
-                       tpl.add "<p>No result for {query}.<p>"
-                       return tpl
-               end
-               var list = new UnorderedList
-               for mentity in results do
-                       var link = mentity.html_link
-                       link.text = mentity.html_full_name
-                       list.add_li new ListItem(link)
-               end
-               tpl.add list
-               return tpl
-       end
-end
-
 # Display the source for each mentities
 class HtmlSourcePage
        super NitView
@@ -119,29 +91,3 @@ class HtmlDocPage
                return tpl
        end
 end
-
-# Display the source for each mentities
-class HtmlDotPage
-       super NitView
-
-       # Dot to process.
-       var dot: Text
-
-       # Page title.
-       var title: String
-
-       redef fun render do
-               var tpl = new Template
-               tpl.add new Header(1, title)
-               tpl.add render_dot
-               return tpl
-       end
-
-       private fun render_dot: String do
-               var proc = new ProcessDuplex("dot", "-Tsvg", "-Tcmapx")
-               var svg = proc.write_and_read(dot)
-               proc.close
-               proc.wait
-               return svg
-       end
-end
index b70b71d..6a693ff 100644 (file)
@@ -6,3 +6,4 @@ neo
 mpi
 emscripten
 ui_test
+readline
diff --git a/tests/base_gen_infinite2.nit b/tests/base_gen_infinite2.nit
new file mode 100644 (file)
index 0000000..05c4fb6
--- /dev/null
@@ -0,0 +1,32 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import end
+
+interface Object
+       type SELF: Object
+       fun output_class_name is intern
+end
+
+class A[E]
+       new do return new B[E]
+end
+
+class B[E]
+       super A[E]
+
+       fun foo: A[E] do return new A[SELF]
+end
+
+(new B[Object]).foo.output_class_name
index cbaafd9..ba1a276 100644 (file)
@@ -5,3 +5,4 @@ base_simple3.nit
 -e 'print "hello world"'
 -n -e 'print line' test_prog/README.md test_prog/test_prog.nit
 test_ffi_c_interpreter.nit
+-
diff --git a/tests/nit_args8.inputs b/tests/nit_args8.inputs
new file mode 100644 (file)
index 0000000..c878eeb
--- /dev/null
@@ -0,0 +1 @@
+print "Hello!"
index 4634f40..d93ce27 100644 (file)
@@ -4,6 +4,7 @@ nit_args3
 nit_args4
 nit_args5
 nit_args6
+nit_args8
 nitvm_args1
 nitvm_args3
 nitc_args1
index 568f76a..5f5a31a 100644 (file)
@@ -3,4 +3,6 @@ base_simple3.nit project1
 -t -r base_simple3.nit project1
 -s base_simple3.nit project1
 -M base_simple3.nit base_simple_import.nit
--td project1/module3.nit
+-td project1/module3.nit project1/subdir/subdir2/subdir3/submodule.nit
+test_prog --no-color
+test_prog/game/excluded.nit test_prog/game/excluded_dir/more.nit -t --no-color
index 396ac26..1ef81d1 100644 (file)
@@ -1,3 +1,4 @@
 --no-color -W test_advice_repeated_types.nit
 --no-color base_simple3.nit; echo $?
 --no-color error_mod_unk.nit; echo $?
+--no-color test_prog
index 390709a..f0bc3ee 100644 (file)
@@ -1,7 +1,7 @@
-test_nitunit.nit --no-color -o $WRITE
+test_nitunit.nit test_test_nitunit.nit --no-color -o $WRITE
 test_nitunit.nit --gen-suite --only-show
 test_nitunit.nit --gen-suite --only-show --private
-test_nitunit2.nit -o $WRITE
+test_nitunit2.nit --no-color -o $WRITE
 test_doc2.nit --no-color -o $WRITE
 test_nitunit3 --no-color -o $WRITE
 test_nitunit_md.md --no-color -o $WRITE
index a68b4cc..b8ae2ec 100644 (file)
@@ -4,6 +4,7 @@ nit_args3
 nit_args4
 nit_args5
 nit_args6
+nit_args8
 nitvm_args1
 nitvm_args3
 nitc_args1
diff --git a/tests/project1/subdir/subdir2/subdir3/submodule.nit b/tests/project1/subdir/subdir2/subdir3/submodule.nit
new file mode 100644 (file)
index 0000000..3355283
--- /dev/null
@@ -0,0 +1 @@
+import module0
diff --git a/tests/project1/uselessdir/uselessdir2/uselessfile b/tests/project1/uselessdir/uselessdir2/uselessfile
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/sav/base_gen_infinite2.res b/tests/sav/base_gen_infinite2.res
new file mode 100644 (file)
index 0000000..e38f38e
--- /dev/null
@@ -0,0 +1 @@
+B[B[Object]]
diff --git a/tests/sav/nit_args8.res b/tests/sav/nit_args8.res
new file mode 100644 (file)
index 0000000..10ddd6d
--- /dev/null
@@ -0,0 +1 @@
+Hello!
index 19b015f..f97fee4 100644 (file)
@@ -76,7 +76,7 @@
 <h3>Quality</h3>
 <ul class="box">
 <li>28 warnings (63/kloc)</li>
-<li>93% documented</li>
+<li>95% documented</li>
 </ul>
 <h3>Tags</h3>
 <a href="../index.html#tag_test">test</a>, <a href="../index.html#tag_game">game</a><h3>Requirements</h3>
diff --git a/tests/sav/nitce/base_gen_infinite2.res b/tests/sav/nitce/base_gen_infinite2.res
new file mode 100644 (file)
index 0000000..223b783
--- /dev/null
@@ -0,0 +1 @@
+B
diff --git a/tests/sav/nitcg/fixme/base_gen_infinite2.res b/tests/sav/nitcg/fixme/base_gen_infinite2.res
new file mode 100644 (file)
index 0000000..2f63d29
--- /dev/null
@@ -0,0 +1 @@
+Fatal Error: limitation in the rapidtype analysis engine: a type depth of 256 is too important, the problematic type is `A[A[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[Sys]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]`.
diff --git a/tests/sav/nitcs/fixme/base_gen_infinite2.res b/tests/sav/nitcs/fixme/base_gen_infinite2.res
new file mode 100644 (file)
index 0000000..2f63d29
--- /dev/null
@@ -0,0 +1 @@
+Fatal Error: limitation in the rapidtype analysis engine: a type depth of 256 is too important, the problematic type is `A[A[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[Sys]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]`.
diff --git a/tests/sav/nitcsg/fixme/base_gen_infinite2.res b/tests/sav/nitcsg/fixme/base_gen_infinite2.res
new file mode 100644 (file)
index 0000000..2f63d29
--- /dev/null
@@ -0,0 +1 @@
+Fatal Error: limitation in the rapidtype analysis engine: a type depth of 256 is too important, the problematic type is `A[A[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[B[Sys]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]`.
index 01782ba..9238b51 100644 (file)
@@ -1,8 +1,35 @@
+Empty README for group `excluded` (readme-warning)
+Errors: 0. Warnings: 1.
+MGroupPage excluded
+       # excluded.section
+               ## excluded.intro
+               ## excluded.concerns
+               ## excluded.concern
+               ## excluded.concern
+               ## excluded-.concern
+                       ### excluded-.definition
+                               #### excluded-.intros_redefs
+                                       ##### list.group
+                                               ###### excluded-.intros
+                                               ###### excluded-.redefs
+
+MModulePage excluded
+       # excluded.section
+               ## excluded-.intro
+               ## excluded-.importation
+                       ### excluded-.graph
+                       ### list.group
+                               #### excluded-.imports
+                               #### excluded-.clients
+
 OverviewPage Overview
        # home.article
                ## packages.section
+                       ### excluded.definition
                        ### test_prog.definition
 
+ReadmePage excluded
+
 ReadmePage test_prog
        # mdarticle-0
 
@@ -1007,24 +1034,24 @@ MModulePage rpg
                                #### test_prog__rpg__rpg.imports
                                #### test_prog__rpg__rpg.clients
 
-Generated 96 pages
+Generated 99 pages
  list:
-  MPropertyPage: 58 (60.41%)
-  MClassPage: 20 (20.83%)
-  MModulePage: 8 (8.33%)
-  MGroupPage: 4 (4.16%)
-  ReadmePage: 4 (4.16%)
-  SearchPage: 1 (1.04%)
-  OverviewPage: 1 (1.04%)
-Found 182 mentities
+  MPropertyPage: 58 (58.58%)
+  MClassPage: 20 (20.20%)
+  MModulePage: 9 (9.09%)
+  ReadmePage: 5 (5.05%)
+  MGroupPage: 5 (5.05%)
+  SearchPage: 1 (1.01%)
+  OverviewPage: 1 (1.01%)
+Found 185 mentities
  list:
-  MMethodDef: 68 (37.36%)
-  MMethod: 57 (31.31%)
-  MClassDef: 22 (12.08%)
-  MClass: 20 (10.98%)
-  MModule: 8 (4.39%)
-  MGroup: 4 (2.19%)
+  MMethodDef: 68 (36.75%)
+  MMethod: 57 (30.81%)
+  MClassDef: 22 (11.89%)
+  MClass: 20 (10.81%)
+  MModule: 9 (4.86%)
+  MGroup: 5 (2.70%)
+  MPackage: 2 (1.08%)
   MVirtualTypeDef: 1 (0.54%)
   MVirtualTypeProp: 1 (0.54%)
-  MPackage: 1 (0.54%)
 quicksearch-list.js
index eca970e..2e6f89d 100644 (file)
@@ -9,4 +9,7 @@ project1 (\e[33mproject1\e[m)
 |--\e[1mproject1\e[m (\e[33mproject1/project1.nit\e[m)
 `--subdir (\e[33mproject1/subdir\e[m)
    |--\e[1mmodule4\e[m (\e[33mproject1/subdir/module4.nit\e[m)
-   `--\e[1mmodule_0\e[m (\e[33mproject1/subdir/module_0.nit\e[m)
+   |--\e[1mmodule_0\e[m (\e[33mproject1/subdir/module_0.nit\e[m)
+   `--subdir2 (\e[33mproject1/subdir/subdir2\e[m)
+      `--subdir3 (\e[33mproject1/subdir/subdir2/subdir3\e[m)
+         `--\e[1msubmodule\e[m (\e[33mproject1/subdir/subdir2/subdir3/submodule.nit\e[m)
index eca970e..2e6f89d 100644 (file)
@@ -9,4 +9,7 @@ project1 (\e[33mproject1\e[m)
 |--\e[1mproject1\e[m (\e[33mproject1/project1.nit\e[m)
 `--subdir (\e[33mproject1/subdir\e[m)
    |--\e[1mmodule4\e[m (\e[33mproject1/subdir/module4.nit\e[m)
-   `--\e[1mmodule_0\e[m (\e[33mproject1/subdir/module_0.nit\e[m)
+   |--\e[1mmodule_0\e[m (\e[33mproject1/subdir/module_0.nit\e[m)
+   `--subdir2 (\e[33mproject1/subdir/subdir2\e[m)
+      `--subdir3 (\e[33mproject1/subdir/subdir2/subdir3\e[m)
+         `--\e[1msubmodule\e[m (\e[33mproject1/subdir/subdir2/subdir3/submodule.nit\e[m)
index c5ec486..ed5df5e 100644 (file)
@@ -9,7 +9,10 @@ project1 (\e[33mproject1\e[m)
 |--\e[1mproject1\e[m (\e[33mproject1/project1.nit\e[m)
 `--subdir (\e[33mproject1/subdir\e[m)
    |--\e[1mmodule4\e[m (\e[33mproject1/subdir/module4.nit\e[m)
-   `--\e[1mmodule_0\e[m (\e[33mproject1/subdir/module_0.nit\e[m)
+   |--\e[1mmodule_0\e[m (\e[33mproject1/subdir/module_0.nit\e[m)
+   `--subdir2 (\e[33mproject1/subdir/subdir2\e[m)
+      `--subdir3 (\e[33mproject1/subdir/subdir2/subdir3\e[m)
+         `--\e[1msubmodule\e[m (\e[33mproject1/subdir/subdir2/subdir3/submodule.nit\e[m)
 project2 (\e[33mproject1/project2\e[m)
 |--\e[1mfoo\e[m (\e[33mproject1/project2/foo.nit\e[m)
 `--\e[1mproject2\e[m (\e[33mproject1/project2/project2.nit\e[m)
index 56570ed..64895a3 100644 (file)
@@ -8,3 +8,4 @@ project1>subdir>\e[1mmodule_0\e[m (\e[33mproject1/subdir/module_0.nit\e[m)
 project1>\e[1mmodule_01\e[m (\e[33mproject1/module_01.nit\e[m)
 project1>\e[1mmodule_02\e[m (\e[33mproject1/module_02.nit\e[m)
 project1>\e[1mproject1\e[m (\e[33mproject1/project1.nit\e[m)
+project1>subdir>subdir2>subdir3>\e[1msubmodule\e[m (\e[33mproject1/subdir/subdir2/subdir3/submodule.nit\e[m)
index 8ebf96a..7cf6fd3 100644 (file)
@@ -2,4 +2,7 @@ project1 (\e[33mproject1\e[m)
 |--\e[1mmodule1\e[m (\e[33mproject1/module1.nit\e[m)
 |--\e[1mmodule3\e[m (\e[33mproject1/module3.nit\e[m)\e[37m (module4)\e[m
 `--subdir (\e[33mproject1/subdir\e[m)
-   `--\e[1mmodule4\e[m (\e[33mproject1/subdir/module4.nit\e[m)\e[37m (module1)\e[m
+   |--\e[1mmodule4\e[m (\e[33mproject1/subdir/module4.nit\e[m)\e[37m (module1)\e[m
+   `--subdir2 (\e[33mproject1/subdir/subdir2\e[m)
+      `--subdir3 (\e[33mproject1/subdir/subdir2/subdir3\e[m)
+         `--\e[1msubmodule\e[m (\e[33mproject1/subdir/subdir2/subdir3/submodule.nit\e[m)
diff --git a/tests/sav/nitls_args7.res b/tests/sav/nitls_args7.res
new file mode 100644 (file)
index 0000000..a0a686e
--- /dev/null
@@ -0,0 +1,12 @@
+test_prog: Test program for model tools. (test_prog)
+|--game: Gaming group (test_prog/game)
+|  `--\e[1mgame\e[m: A game abstraction for RPG. (test_prog/game/game.nit)
+|--platform: Fictive Crappy Platform. (test_prog/platform)
+|  `--\e[1mplatform\e[m: Declares base types allowed on the platform. (test_prog/platform/platform.nit)
+|--rpg: Role Playing Game group (test_prog/rpg)
+|  |--\e[1mcareers\e[m: Careers of the game. (test_prog/rpg/careers.nit)
+|  |--\e[1mcharacter\e[m: Characters are playable entity in the world. (test_prog/rpg/character.nit)
+|  |--\e[1mcombat\e[m: COmbat interactions between characters. (test_prog/rpg/combat.nit)
+|  |--\e[1mraces\e[m: Races of the game. (test_prog/rpg/races.nit)
+|  `--\e[1mrpg\e[m: A worlg RPG abstraction. (test_prog/rpg/rpg.nit)
+`--\e[1mtest_prog\e[m: A test program with a fake model to check model tools. (test_prog/test_prog.nit)
diff --git a/tests/sav/nitls_args8.res b/tests/sav/nitls_args8.res
new file mode 100644 (file)
index 0000000..b80e7ce
--- /dev/null
@@ -0,0 +1,2 @@
+\e[1mexcluded\e[m (test_prog/game/excluded.nit)
+\e[1mmore\e[m (test_prog/game/excluded_dir/more.nit)
index cbfe0bc..d64cff1 100644 (file)
@@ -1,43 +1,36 @@
-\r\e[K* Docunits of module test_nitunit::test_nitunit [    ] 0/4\r\e[K* Docunits of module test_nitunit::test_nitunit [   \e[1m\e[31mX\e[m\e[m] 1/4 test_nitunit$X$foo1 \e[1m\e[31m[KO]\e[m\e[m\r\e[K* Docunits of module test_nitunit::test_nitunit [\e[1m\e[32m.\e[m\e[m  \e[1m\e[31mX\e[m\e[m] 2/4 test_nitunit::test_nitunit \e[1m\e[32m[OK]\e[m\e[m\r\e[K* Docunits of module test_nitunit::test_nitunit [\e[1m\e[32m.\e[m\e[m\e[1m\e[31mX\e[m\e[m \e[1m\e[31mX\e[m\e[m] 3/4 test_nitunit$X \e[1m\e[31m[KO]\e[m\e[m\r\e[K* Docunits of module test_nitunit::test_nitunit [\e[1m\e[32m.\e[m\e[m\e[1m\e[31mX\e[m\e[m\e[1m\e[31mX\e[m\e[m\e[1m\e[31mX\e[m\e[m] 4/4 test_nitunit$X$foo \e[1m\e[31m[KO]\e[m\e[m\r\e[K* Docunits of module test_nitunit::test_nitunit [\e[1m\e[32m.\e[m\e[m\e[1m\e[31mX\e[m\e[m\e[1m\e[31mX\e[m\e[m\e[1m\e[31mX\e[m\e[m] 4/4
-\e[1m\e[32m[OK]\e[m\e[m test_nitunit::test_nitunit
-\e[1m\e[31m[KO]\e[m\e[m test_nitunit$X
-     \e[33mtest_nitunit.nit:21,7--22,0\e[m: Runtime error in .nitunit/test_nitunit-2.nit
-       #     \e[1;31massert false\e[0m
-             ^
+==== Docunits of module test_nitunit::test_nitunit | tests: 4
+[OK] test_nitunit::test_nitunit
+[KO] test_nitunit$X
+     test_nitunit.nit:21,7--22,0: Runtime error in nitunit.out/test_nitunit-2.nit
      Output
-       Runtime error: Assert failed (.nitunit/test_nitunit-2.nit:5)
+       Runtime error: Assert failed (nitunit.out/test_nitunit-2.nit:5)
 
-\e[1m\e[31m[KO]\e[m\e[m test_nitunit$X$foo
-     \e[33mtest_nitunit.nit:24,8--25,0\e[m: Compilation error in .nitunit/test_nitunit-3.nit
-               #     \e[1;31massert undefined_identifier\e[0m
-                     ^
+[KO] test_nitunit$X$foo
+     test_nitunit.nit:24,8--25,0: Compilation error in nitunit.out/test_nitunit-3.nit
      Output
-       .nitunit/test_nitunit-3.nit:5,8--27: Error: method or variable `undefined_identifier` unknown in `Sys`.
+       nitunit.out/test_nitunit-3.nit:5,8--27: Error: method or variable `undefined_identifier` unknown in `Sys`.
 
-\e[1m\e[31m[KO]\e[m\e[m test_nitunit$X$foo1
-     \e[33mtest_nitunit.nit:28,15\e[m: Syntax Error: unexpected operator '!'.
-               #     assert \e[1;31m!\e[0m@#$%^&*()
-                            ^
-\r\e[K* Test-suite of module test_test_nitunit::test_test_nitunit [   ] 0/3\r\e[K* Test-suite of module test_test_nitunit::test_test_nitunit [\e[1m\e[32m.\e[m\e[m  ] 1/3 test_test_nitunit$TestX$test_foo \e[1m\e[32m[OK]\e[m\e[m\r\e[K* Test-suite of module test_test_nitunit::test_test_nitunit [\e[1m\e[32m.\e[m\e[m\e[1m\e[31mX\e[m\e[m ] 2/3 test_test_nitunit$TestX$test_foo1 \e[1m\e[31m[KO]\e[m\e[m\r\e[K* Test-suite of module test_test_nitunit::test_test_nitunit [\e[1m\e[32m.\e[m\e[m\e[1m\e[31mX\e[m\e[m\e[1m\e[32m.\e[m\e[m] 3/3 test_test_nitunit$TestX$test_foo2 \e[1m\e[32m[OK]\e[m\e[m\r\e[K* Test-suite of module test_test_nitunit::test_test_nitunit [\e[1m\e[32m.\e[m\e[m\e[1m\e[31mX\e[m\e[m\e[1m\e[32m.\e[m\e[m] 3/3
-\e[1m\e[32m[OK]\e[m\e[m test_test_nitunit$TestX$test_foo
-\e[1m\e[31m[KO]\e[m\e[m test_test_nitunit$TestX$test_foo1
-     \e[33mtest_test_nitunit.nit:36,2--40,4\e[m: Runtime Error in file .nitunit/gen_test_test_nitunit.nit
-               \e[1;31m# will fail\e[0m
-               ^
+[KO] test_nitunit$X$foo1
+     test_nitunit.nit:28,15: Syntax Error: unexpected operator '!'.
+
+==== Test-suite of module test_test_nitunit::test_test_nitunit | tests: 3
+[OK] test_test_nitunit$TestX$test_foo
+[KO] test_test_nitunit$TestX$test_foo1
+     test_test_nitunit.nit:36,2--40,4: Runtime Error in file nitunit.out/gen_test_test_nitunit.nit
      Output
        Runtime error: Assert failed (test_test_nitunit.nit:39)
 
-\e[1m\e[32m[OK]\e[m\e[m test_test_nitunit$TestX$test_foo2
-DocUnits:
-Entities: 27; Documented ones: 4; With nitunits: 4; Failures: 3
+[OK] test_test_nitunit$TestX$test_foo2
 
-TestSuites:
-Class suites: 1; Test Cases: 3; Failures: 1
+Docunits: Entities: 34; Documented ones: 6; With nitunits: 4; Failures: 3
+Test suites: Classes: 1; Test Cases: 3; Failures: 1
+[FAILURE] 4/7 tests failed.
+`nitunit.out` is not removed for investigation.
 <testsuites><testsuite package="test_nitunit::test_nitunit"><testcase classname="nitunit.test_nitunit::test_nitunit.&lt;module&gt;" name="&lt;module&gt;"><system-err></system-err><system-out>assert true
-</system-out></testcase><testcase classname="nitunit.test_nitunit::test_nitunit.test_nitunit::X" name="&lt;class&gt;"><error>Runtime error in .nitunit&#47;test_nitunit-2.nit</error><system-err>Runtime error: Assert failed (.nitunit&#47;test_nitunit-2.nit:5)
+</system-out></testcase><testcase classname="nitunit.test_nitunit::test_nitunit.test_nitunit::X" name="&lt;class&gt;"><error>Runtime error in nitunit.out&#47;test_nitunit-2.nit</error><system-err>Runtime error: Assert failed (nitunit.out&#47;test_nitunit-2.nit:5)
 </system-err><system-out>assert false
-</system-out></testcase><testcase classname="nitunit.test_nitunit::test_nitunit.test_nitunit::X" name="test_nitunit::X::foo"><failure>Compilation error in .nitunit&#47;test_nitunit-3.nit</failure><system-err>.nitunit&#47;test_nitunit-3.nit:5,8--27: Error: method or variable `undefined_identifier` unknown in `Sys`.
+</system-out></testcase><testcase classname="nitunit.test_nitunit::test_nitunit.test_nitunit::X" name="test_nitunit::X::foo"><failure>Compilation error in nitunit.out&#47;test_nitunit-3.nit</failure><system-err>nitunit.out&#47;test_nitunit-3.nit:5,8--27: Error: method or variable `undefined_identifier` unknown in `Sys`.
 </system-err><system-out>assert undefined_identifier
 </system-out></testcase><testcase classname="nitunit.test_nitunit::test_nitunit.test_nitunit::X" name="test_nitunit::X::foo1"><failure>Syntax Error: unexpected operator &#39;!&#39;.</failure><system-out>assert !@#$%^&amp;*()
-</system-out></testcase></testsuite><testsuite package="test_test_nitunit"><testcase classname="nitunit.test_test_nitunit::test_test_nitunit.test_test_nitunit::TestX" name="test_test_nitunit::TestX::test_foo"><system-err></system-err></testcase><testcase classname="nitunit.test_test_nitunit::test_test_nitunit.test_test_nitunit::TestX" name="test_test_nitunit::TestX::test_foo1"><error>Runtime Error in file .nitunit&#47;gen_test_test_nitunit.nit</error><system-err>Runtime error: Assert failed (test_test_nitunit.nit:39)
+</system-out></testcase></testsuite><testsuite package="test_test_nitunit::test_test_nitunit"></testsuite><testsuite package="test_test_nitunit"><testcase classname="nitunit.test_test_nitunit::test_test_nitunit.test_test_nitunit::TestX" name="test_test_nitunit::TestX::test_foo"><system-err></system-err></testcase><testcase classname="nitunit.test_test_nitunit::test_test_nitunit.test_test_nitunit::TestX" name="test_test_nitunit::TestX::test_foo1"><error>Runtime Error in file nitunit.out&#47;gen_test_test_nitunit.nit</error><system-err>Runtime error: Assert failed (test_test_nitunit.nit:39)
 </system-err></testcase><testcase classname="nitunit.test_test_nitunit::test_test_nitunit.test_test_nitunit::TestX" name="test_test_nitunit::TestX::test_foo2"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
index 95b3751..7a74b0c 100644 (file)
@@ -1,14 +1,11 @@
-\r\e[K* Docunits of module test_nitunit2::test_nitunit2 [   ] 0/3\r\e[K* Docunits of module test_nitunit2::test_nitunit2 [\e[1m\e[32m.\e[m\e[m  ] 1/3 test_nitunit2::test_nitunit2$core::Sys$foo1 \e[1m\e[32m[OK]\e[m\e[m\r\e[K* Docunits of module test_nitunit2::test_nitunit2 [\e[1m\e[32m.\e[m\e[m\e[1m\e[32m.\e[m\e[m ] 2/3 test_nitunit2::test_nitunit2$core::Sys$bar2 \e[1m\e[32m[OK]\e[m\e[m\r\e[K* Docunits of module test_nitunit2::test_nitunit2 [\e[1m\e[32m.\e[m\e[m\e[1m\e[32m.\e[m\e[m\e[1m\e[32m.\e[m\e[m] 3/3 test_nitunit2::test_nitunit2$core::Sys$foo3 \e[1m\e[32m[OK]\e[m\e[m\r\e[K* Docunits of module test_nitunit2::test_nitunit2 [\e[1m\e[32m.\e[m\e[m\e[1m\e[32m.\e[m\e[m\e[1m\e[32m.\e[m\e[m] 3/3
-\e[1m\e[32m[OK]\e[m\e[m test_nitunit2::test_nitunit2$core::Sys$foo1
-\e[1m\e[32m[OK]\e[m\e[m test_nitunit2::test_nitunit2$core::Sys$bar2
-\e[1m\e[32m[OK]\e[m\e[m test_nitunit2::test_nitunit2$core::Sys$foo3
-DocUnits:
-DocUnits Success
-Entities: 4; Documented ones: 3; With nitunits: 3; Failures: 0
+==== Docunits of module test_nitunit2::test_nitunit2 | tests: 3
+[OK] test_nitunit2::test_nitunit2$core::Sys$foo1
+[OK] test_nitunit2::test_nitunit2$core::Sys$bar2
+[OK] test_nitunit2::test_nitunit2$core::Sys$foo3
 
-TestSuites:
-No test cases found
-Class suites: 0; Test Cases: 0; Failures: 0
+Docunits: Entities: 4; Documented ones: 3; With nitunits: 3; Failures: 0
+Test suites: Classes: 0; Test Cases: 0
+[SUCCESS] All 3 tests passed.
 <testsuites><testsuite package="test_nitunit2::test_nitunit2"><testcase classname="nitunit.test_nitunit2::test_nitunit2.core::Sys" name="test_nitunit2::test_nitunit2::Sys::foo1"><system-err></system-err><system-out>if true then
 
    assert true
@@ -22,4 +19,4 @@ end
 </system-out></testcase><testcase classname="nitunit.test_nitunit2::test_nitunit2.core::Sys" name="test_nitunit2::test_nitunit2::Sys::foo3"><system-err></system-err><system-out>var a = 1
 assert a == 1
 assert a == 1
-</system-out></testcase></testsuite><testsuite></testsuite></testsuites>
\ No newline at end of file
+</system-out></testcase></testsuite></testsuites>
\ No newline at end of file
index f161044..1c9dc40 100644 (file)
@@ -1,15 +1,12 @@
-\r\e[K* Docunits of module test_doc2::test_doc2 [   ] 0/3\r\e[K* Docunits of module test_doc2::test_doc2 [\e[1m\e[32m.\e[m\e[m  ] 1/3 test_doc2::test_doc2$core::Sys$foo1 \e[1m\e[32m[OK]\e[m\e[m\r\e[K* Docunits of module test_doc2::test_doc2 [\e[1m\e[32m.\e[m\e[m\e[1m\e[32m.\e[m\e[m ] 2/3 test_doc2::test_doc2$core::Sys$foo2 \e[1m\e[32m[OK]\e[m\e[m\r\e[K* Docunits of module test_doc2::test_doc2 [\e[1m\e[32m.\e[m\e[m\e[1m\e[32m.\e[m\e[m\e[1m\e[32m.\e[m\e[m] 3/3 test_doc2::test_doc2$core::Sys$foo3 \e[1m\e[32m[OK]\e[m\e[m\r\e[K* Docunits of module test_doc2::test_doc2 [\e[1m\e[32m.\e[m\e[m\e[1m\e[32m.\e[m\e[m\e[1m\e[32m.\e[m\e[m] 3/3
-\e[1m\e[32m[OK]\e[m\e[m test_doc2::test_doc2$core::Sys$foo1
-\e[1m\e[32m[OK]\e[m\e[m test_doc2::test_doc2$core::Sys$foo2
-\e[1m\e[32m[OK]\e[m\e[m test_doc2::test_doc2$core::Sys$foo3
-DocUnits:
-DocUnits Success
-Entities: 6; Documented ones: 5; With nitunits: 3; Failures: 0
+==== Docunits of module test_doc2::test_doc2 | tests: 3
+[OK] test_doc2::test_doc2$core::Sys$foo1
+[OK] test_doc2::test_doc2$core::Sys$foo2
+[OK] test_doc2::test_doc2$core::Sys$foo3
 
-TestSuites:
-No test cases found
-Class suites: 0; Test Cases: 0; Failures: 0
+Docunits: Entities: 6; Documented ones: 5; With nitunits: 3; Failures: 0
+Test suites: Classes: 0; Test Cases: 0
+[SUCCESS] All 3 tests passed.
 <testsuites><testsuite package="test_doc2::test_doc2"><testcase classname="nitunit.test_doc2::test_doc2.core::Sys" name="test_doc2::test_doc2::Sys::foo1"><system-err></system-err><system-out>assert true # tested
 </system-out></testcase><testcase classname="nitunit.test_doc2::test_doc2.core::Sys" name="test_doc2::test_doc2::Sys::foo2"><system-err></system-err><system-out>assert true # tested
 </system-out></testcase><testcase classname="nitunit.test_doc2::test_doc2.core::Sys" name="test_doc2::test_doc2::Sys::foo3"><system-err></system-err><system-out>assert true # tested
-</system-out></testcase></testsuite><testsuite></testsuite></testsuites>
\ No newline at end of file
+</system-out></testcase></testsuite></testsuites>
\ No newline at end of file
index 13ff875..fa6339c 100644 (file)
@@ -1,27 +1,22 @@
-\r\e[K* Docunits of group test_nitunit3> [  ] 0/2\r\e[K* Docunits of group test_nitunit3> [ \e[1m\e[31mX\e[m\e[m] 1/2 test_nitunit3> \e[1m\e[31m[KO]\e[m\e[m\r\e[K* Docunits of group test_nitunit3> [\e[1m\e[31mX\e[m\e[m\e[1m\e[31mX\e[m\e[m] 2/2 test_nitunit3> \e[1m\e[31m[KO]\e[m\e[m\r\e[K* Docunits of group test_nitunit3> [\e[1m\e[31mX\e[m\e[m\e[1m\e[31mX\e[m\e[m] 2/2
-\e[1m\e[31m[KO]\e[m\e[m test_nitunit3>
-     \e[33mtest_nitunit3/README.md:4,2--15,0\e[m: Runtime error in .nitunit/test_nitunit3-0.nit with argument 1
-       ~\e[1;31m~\e[0m
-        ^
+==== Docunits of group test_nitunit3> | tests: 2
+[KO] test_nitunit3>
+     test_nitunit3/README.md:4,2--15,0: Runtime error in nitunit.out/test_nitunit3-0.nit with argument 1
      Output
-       Runtime error: Assert failed (.nitunit/test_nitunit3-0.nit:7)
+       Runtime error: Assert failed (nitunit.out/test_nitunit3-0.nit:7)
 
-\e[1m\e[31m[KO]\e[m\e[m test_nitunit3>
-     \e[33mtest_nitunit3/README.md:7,3--5\e[m: Syntax Error: unexpected malformed character '\].
-       ~~\e[1;31m~
-;\e[0m
-         ^
-\r\e[K* Docunits of module test_nitunit3::test_nitunit3 [ ] 0/1\r\e[K* Docunits of module test_nitunit3::test_nitunit3 [\e[1m\e[32m.\e[m\e[m] 1/1 test_nitunit3::test_nitunit3 \e[1m\e[32m[OK]\e[m\e[m\r\e[K* Docunits of module test_nitunit3::test_nitunit3 [\e[1m\e[32m.\e[m\e[m] 1/1
-\e[1m\e[32m[OK]\e[m\e[m test_nitunit3::test_nitunit3
-DocUnits:
-Entities: 2; Documented ones: 2; With nitunits: 3; Failures: 2
+[KO] test_nitunit3>#2
+     test_nitunit3/README.md:7,3--5: Syntax Error: unexpected malformed character '\].
 
-TestSuites:
-No test cases found
-Class suites: 0; Test Cases: 0; Failures: 0
-<testsuites><testsuite package="test_nitunit3&gt;"><testcase classname="nitunit.test_nitunit3&gt;" name="&lt;group&gt;"><error>Runtime error in .nitunit&#47;test_nitunit3-0.nit with argument 1</error><system-err>Runtime error: Assert failed (.nitunit&#47;test_nitunit3-0.nit:7)
+==== Docunits of module test_nitunit3::test_nitunit3 | tests: 1
+[OK] test_nitunit3::test_nitunit3
+
+Docunits: Entities: 2; Documented ones: 2; With nitunits: 3; Failures: 2
+Test suites: Classes: 0; Test Cases: 0
+[FAILURE] 2/3 tests failed.
+`nitunit.out` is not removed for investigation.
+<testsuites><testsuite package="test_nitunit3&gt;"><testcase classname="nitunit.test_nitunit3&gt;" name="&lt;group&gt;"><error>Runtime error in nitunit.out&#47;test_nitunit3-0.nit with argument 1</error><system-err>Runtime error: Assert failed (nitunit.out&#47;test_nitunit3-0.nit:7)
 </system-err><system-out>assert false
 assert true
-</system-out></testcase><testcase classname="nitunit.test_nitunit3&gt;" name="&lt;group&gt;+1"><failure>Syntax Error: unexpected malformed character &#39;\].</failure><system-out>;&#39;\][]
+</system-out></testcase><testcase classname="nitunit.test_nitunit3&gt;" name="&lt;group&gt;#2"><failure>Syntax Error: unexpected malformed character &#39;\].</failure><system-out>;&#39;\][]
 </system-out></testcase></testsuite><testsuite package="test_nitunit3::test_nitunit3"><testcase classname="nitunit.test_nitunit3::test_nitunit3.&lt;module&gt;" name="&lt;module&gt;"><system-err></system-err><system-out>assert true
-</system-out></testcase></testsuite><testsuite></testsuite></testsuites>
\ No newline at end of file
+</system-out></testcase></testsuite></testsuites>
\ No newline at end of file
index bf0c504..3ac61a8 100644 (file)
@@ -1,18 +1,15 @@
-\r\e[K* Docunits of file test_nitunit_md.md:1,0--15,0 [ ] 0/1\r\e[K* Docunits of file test_nitunit_md.md:1,0--15,0 [\e[1m\e[31mX\e[m\e[m] 1/1 nitunit.<file>.test_nitunit_md.md:1,0--15,0 \e[1m\e[31m[KO]\e[m\e[m\r\e[K* Docunits of file test_nitunit_md.md:1,0--15,0 [\e[1m\e[31mX\e[m\e[m] 1/1
-\e[1m\e[31m[KO]\e[m\e[m nitunit.<file>.test_nitunit_md.md:1,0--15,0
-     \e[33mtest_nitunit_md.md:4,2--16,0\e[m: Runtime error in .nitunit/file-0.nit with argument 1
-       ~\e[1;31m~\e[0m
-        ^
+==== Docunits of file test_nitunit_md.md | tests: 1
+[KO] nitunit.<file>.test_nitunit_md.md
+     test_nitunit_md.md:4,2--16,0: Runtime error in nitunit.out/file-0.nit with argument 1
      Output
-       Runtime error: Assert failed (.nitunit/file-0.nit:8)
+       Runtime error: Assert failed (nitunit.out/file-0.nit:8)
 
-DocUnits:
-Entities: 1; Documented ones: 1; With nitunits: 1; Failures: 1
 
-TestSuites:
-No test cases found
-Class suites: 0; Test Cases: 0; Failures: 0
-<testsuites><testsuite package="test_nitunit_md.md:1,0--15,0"><testcase classname="nitunit.&lt;file&gt;" name="test_nitunit_md.md:1,0--15,0"><error>Runtime error in .nitunit&#47;file-0.nit with argument 1</error><system-err>Runtime error: Assert failed (.nitunit&#47;file-0.nit:8)
+Docunits: Entities: 1; Documented ones: 1; With nitunits: 1; Failures: 1
+Test suites: Classes: 0; Test Cases: 0
+[FAILURE] 1/1 tests failed.
+`nitunit.out` is not removed for investigation.
+<testsuites><testsuite package="test_nitunit_md.md"><testcase classname="nitunit.&lt;file&gt;" name="test_nitunit_md.md"><error>Runtime error in nitunit.out&#47;file-0.nit with argument 1</error><system-err>Runtime error: Assert failed (nitunit.out&#47;file-0.nit:8)
 </system-err><system-out>var a = 1
 assert 1 == 1
 assert false
index c3e9bf7..d81c785 100644 (file)
@@ -1,23 +1,16 @@
-\r\e[K* Docunits of module test_doc3::test_doc3 [   ] 0/3\r\e[K* Docunits of module test_doc3::test_doc3 [\e[1m\e[31mX\e[m\e[m  ] 1/3 test_doc3::test_doc3$core::Sys$foo1 \e[1m\e[31m[KO]\e[m\e[m\r\e[K* Docunits of module test_doc3::test_doc3 [\e[1m\e[31mX\e[m\e[m\e[1m\e[31mX\e[m\e[m ] 2/3 test_doc3::test_doc3$core::Sys$foo2 \e[1m\e[31m[KO]\e[m\e[m\r\e[K* Docunits of module test_doc3::test_doc3 [\e[1m\e[31mX\e[m\e[m\e[1m\e[31mX\e[m\e[m\e[1m\e[31mX\e[m\e[m] 3/3 test_doc3::test_doc3$core::Sys$foo3 \e[1m\e[31m[KO]\e[m\e[m\r\e[K* Docunits of module test_doc3::test_doc3 [\e[1m\e[31mX\e[m\e[m\e[1m\e[31mX\e[m\e[m\e[1m\e[31mX\e[m\e[m] 3/3
-\e[1m\e[31m[KO]\e[m\e[m test_doc3::test_doc3$core::Sys$foo1
-     \e[33mtest_doc3.nit:17,9--15\e[m: Syntax Error: unexpected identifier 'garbage'.
-       #      *\e[1;31mgarbage\e[0m*
-               ^
-\e[1m\e[31m[KO]\e[m\e[m test_doc3::test_doc3$core::Sys$foo2
-     \e[33mtest_doc3.nit:23,4--10\e[m: Syntax Error: unexpected identifier 'garbage'.
-       # *\e[1;31mgarbage\e[0m*
-          ^
-\e[1m\e[31m[KO]\e[m\e[m test_doc3::test_doc3$core::Sys$foo3
-     \e[33mtest_doc3.nit:30,4--10\e[m: Syntax Error: unexpected identifier 'garbage'.
-       # *\e[1;31mgarbage\e[0m*
-          ^
-DocUnits:
-Entities: 6; Documented ones: 5; With nitunits: 3; Failures: 3
+==== Docunits of module test_doc3::test_doc3 | tests: 3
+[KO] test_doc3::test_doc3$core::Sys$foo1
+     test_doc3.nit:17,9--15: Syntax Error: unexpected identifier 'garbage'.
+[KO] test_doc3::test_doc3$core::Sys$foo2
+     test_doc3.nit:23,4--10: Syntax Error: unexpected identifier 'garbage'.
+[KO] test_doc3::test_doc3$core::Sys$foo3
+     test_doc3.nit:30,4--10: Syntax Error: unexpected identifier 'garbage'.
 
-TestSuites:
-No test cases found
-Class suites: 0; Test Cases: 0; Failures: 0
+Docunits: Entities: 6; Documented ones: 5; With nitunits: 3; Failures: 3
+Test suites: Classes: 0; Test Cases: 0
+[FAILURE] 3/3 tests failed.
+`nitunit.out` is not removed for investigation.
 <testsuites><testsuite package="test_doc3::test_doc3"><testcase classname="nitunit.test_doc3::test_doc3.core::Sys" name="test_doc3::test_doc3::Sys::foo1"><failure>Syntax Error: unexpected identifier &#39;garbage&#39;.</failure><system-out> *garbage*
 </system-out></testcase><testcase classname="nitunit.test_doc3::test_doc3.core::Sys" name="test_doc3::test_doc3::Sys::foo2"><failure>Syntax Error: unexpected identifier &#39;garbage&#39;.</failure><system-out>*garbage*
 </system-out></testcase><testcase classname="nitunit.test_doc3::test_doc3.core::Sys" name="test_doc3::test_doc3::Sys::foo3"><failure>Syntax Error: unexpected identifier &#39;garbage&#39;.</failure><system-out>*garbage*
-</system-out></testcase></testsuite><testsuite></testsuite></testsuites>
\ No newline at end of file
+</system-out></testcase></testsuite></testsuites>
\ No newline at end of file
index 5806e40..31a2cd3 100644 (file)
@@ -1,48 +1,56 @@
-\r\e[K* Test-suite of module test_nitunit4::test_nitunit4 [   ] 0/3\r\e[K* Test-suite of module test_nitunit4::test_nitunit4 [\e[1m\e[31mX\e[m\e[m  ] 1/3 test_nitunit4$TestTestSuite$test_foo \e[1m\e[31m[KO]\e[m\e[m\r\e[K* Test-suite of module test_nitunit4::test_nitunit4 [\e[1m\e[31mX\e[m\e[m\e[1m\e[32m.\e[m\e[m ] 2/3 test_nitunit4$TestTestSuite$test_bar \e[1m\e[32m[OK]\e[m\e[m\r\e[K* Test-suite of module test_nitunit4::test_nitunit4 [\e[1m\e[31mX\e[m\e[m\e[1m\e[32m.\e[m\e[m\e[1m\e[31mX\e[m\e[m] 3/3 test_nitunit4$TestTestSuite$test_baz \e[1m\e[31m[KO]\e[m\e[m\r\e[K* Test-suite of module test_nitunit4::test_nitunit4 [\e[1m\e[31mX\e[m\e[m\e[1m\e[32m.\e[m\e[m\e[1m\e[31mX\e[m\e[m] 3/3
-\e[1m\e[31m[KO]\e[m\e[m test_nitunit4$TestTestSuite$test_foo
-     \e[33mtest_nitunit4/test_nitunit4.nit:22,2--26,4\e[m: Runtime Error in file .nitunit/gen_test_nitunit4.nit
-               \e[1;31mfun test_foo do\e[0m
-               ^
+==== Test-suite of module test_nitunit4::test_nitunit4 | tests: 4
+[KO] test_nitunit4$TestTestSuite$test_foo
+     test_nitunit4/test_nitunit4.nit:22,2--26,4: Runtime Error in file nitunit.out/gen_test_nitunit4.nit
      Output
        Before Test
        Tested method
        After Test
        Runtime error: Assert failed (test_nitunit4/test_nitunit4_base.nit:31)
 
-\e[1m\e[32m[OK]\e[m\e[m test_nitunit4$TestTestSuite$test_bar
-\e[1m\e[31m[KO]\e[m\e[m test_nitunit4$TestTestSuite$test_baz
-     \e[33mtest_nitunit4/test_nitunit4.nit:32,2--34,4\e[m: Difference with expected output: diff -u test_nitunit4/test_nitunit4.sav/test_baz.res .nitunit/gen_test_nitunit4_test_baz.out1
-               \e[1;31mfun test_baz do\e[0m
-               ^
+[OK] test_nitunit4$TestTestSuite$test_bar
+[KO] test_nitunit4$TestTestSuite$test_baz
+     test_nitunit4/test_nitunit4.nit:32,2--34,4: Difference with expected output: diff -u test_nitunit4/test_baz.res nitunit.out/gen_test_nitunit4_test_baz.out1
      Output
        Diff
-       --- expected:test_nitunit4/test_nitunit4.sav/test_baz.res
-       +++ got:.nitunit/gen_test_nitunit4_test_baz.out1
+       --- expected:test_nitunit4/test_baz.res
+       +++ got:nitunit.out/gen_test_nitunit4_test_baz.out1
        @@ -1 +1,3 @@
        -Bad result file
        +Before Test
        +Tested method
        +After Test
 
-DocUnits:
-No doc units found
-Entities: 12; Documented ones: 0; With nitunits: 0; Failures: 0
+[KO] test_nitunit4$TestTestSuite$test_sav_conflict
+     test_nitunit4/test_nitunit4.nit:36,2--38,4: Conflicting expected output: test_nitunit4/test_nitunit4.sav/test_sav_conflict.res, test_nitunit4/sav/test_sav_conflict.res and test_nitunit4/test_sav_conflict.res all exist
+     Output
+       Before Test
+       Tested method
+       After Test
 
-TestSuites:
-Class suites: 1; Test Cases: 3; Failures: 2
-<testsuites><testsuite package="test_nitunit4&gt;"></testsuite><testsuite package="test_nitunit4::nitunit4"></testsuite><testsuite package="test_nitunit4"><testcase classname="nitunit.test_nitunit4::test_nitunit4.test_nitunit4::TestTestSuite" name="test_nitunit4::TestTestSuite::test_foo"><error>Runtime Error in file .nitunit&#47;gen_test_nitunit4.nit</error><system-err>Before Test
+
+==== Test-suite of module test_nitunit4::test_nitunit4_base | tests: 0
+==== Test-suite of module test_nitunit4::test_nitunit4_base | tests: 0
+
+Docunits: Entities: 13; Documented ones: 0; With nitunits: 0
+Test suites: Classes: 2; Test Cases: 4; Failures: 3
+[FAILURE] 3/4 tests failed.
+`nitunit.out` is not removed for investigation.
+<testsuites><testsuite package="test_nitunit4&gt;"></testsuite><testsuite package="test_nitunit4::nitunit4"></testsuite><testsuite package="test_nitunit4::test_nitunit4"></testsuite><testsuite package="test_nitunit4"><testcase classname="nitunit.test_nitunit4::test_nitunit4.test_nitunit4::TestTestSuite" name="test_nitunit4::TestTestSuite::test_foo"><error>Runtime Error in file nitunit.out&#47;gen_test_nitunit4.nit</error><system-err>Before Test
 Tested method
 After Test
 Runtime error: Assert failed (test_nitunit4&#47;test_nitunit4_base.nit:31)
 </system-err></testcase><testcase classname="nitunit.test_nitunit4::test_nitunit4.test_nitunit4::TestTestSuite" name="test_nitunit4::TestTestSuite::test_bar"><system-err>Before Test
 Tested method
 After Test
-</system-err></testcase><testcase classname="nitunit.test_nitunit4::test_nitunit4.test_nitunit4::TestTestSuite" name="test_nitunit4::TestTestSuite::test_baz"><error>Difference with expected output: diff -u test_nitunit4&#47;test_nitunit4.sav&#47;test_baz.res .nitunit&#47;gen_test_nitunit4_test_baz.out1</error><system-err>Diff
---- expected:test_nitunit4&#47;test_nitunit4.sav&#47;test_baz.res
-+++ got:.nitunit&#47;gen_test_nitunit4_test_baz.out1
+</system-err></testcase><testcase classname="nitunit.test_nitunit4::test_nitunit4.test_nitunit4::TestTestSuite" name="test_nitunit4::TestTestSuite::test_baz"><error>Difference with expected output: diff -u test_nitunit4&#47;test_baz.res nitunit.out&#47;gen_test_nitunit4_test_baz.out1</error><system-err>Diff
+--- expected:test_nitunit4&#47;test_baz.res
++++ got:nitunit.out&#47;gen_test_nitunit4_test_baz.out1
 @@ -1 +1,3 @@
 -Bad result file
 +Before Test
 +Tested method
 +After Test
-</system-err></testcase></testsuite><testsuite package="test_nitunit4::test_nitunit4"></testsuite><testsuite></testsuite><testsuite package="test_nitunit4::test_nitunit4_base"></testsuite><testsuite></testsuite></testsuites>
\ No newline at end of file
+</system-err></testcase><testcase classname="nitunit.test_nitunit4::test_nitunit4.test_nitunit4::TestTestSuite" name="test_nitunit4::TestTestSuite::test_sav_conflict"><error>Conflicting expected output: test_nitunit4&#47;test_nitunit4.sav&#47;test_sav_conflict.res, test_nitunit4&#47;sav&#47;test_sav_conflict.res and test_nitunit4&#47;test_sav_conflict.res all exist</error><system-err>Before Test
+Tested method
+After Test
+</system-err></testcase></testsuite><testsuite package="test_nitunit4::test_nitunit4_base"></testsuite><testsuite package="test_nitunit4_base"></testsuite></testsuites>
\ No newline at end of file
index 25574f7..e0353bf 100644 (file)
@@ -1,13 +1,13 @@
  population: 3
  minimum value: 2
- maximum value: 11
- total value: 16
- average value: 5.33
+ maximum value: 12
+ total value: 22
+ average value: 7.33
  distribution:
-  <=2: sub-population=1 (33.33%); cumulated value=2 (12.50%)
-  <=4: sub-population=1 (33.33%); cumulated value=3 (18.75%)
-  <=16: sub-population=1 (33.33%); cumulated value=11 (68.75%)
+  <=2: sub-population=1 (33.33%); cumulated value=2 (9.09%)
+  <=8: sub-population=1 (33.33%); cumulated value=8 (36.36%)
+  <=16: sub-population=1 (33.33%); cumulated value=12 (54.54%)
  list:
-  nit: 11 (68.75%)
-  : 3 (18.75%)
-  ini: 2 (12.50%)
+  nit: 12 (54.54%)
+  : 8 (36.36%)
+  ini: 2 (9.09%)
index 4ac86da..fdc3be7 100644 (file)
@@ -1,12 +1,12 @@
 # mpackages:
-test_prog
+excluded test_prog
 ------------------------------------
-test_prog
+excluded test_prog
 
 # mmodules:
-careers character combat game platform races rpg test_prog
+careers character combat excluded game platform races rpg test_prog
 ------------------------------------
-careers character combat game platform races rpg test_prog
+careers character combat excluded game platform races rpg test_prog
 
 # mclasses:
 Alcoholic Bool Career Character Combatable Dwarf Elf Float Game Human Int List Magician Object Race Starter String Sys Warrior Weapon
index 9e5ceab..38c4890 100644 (file)
@@ -1,4 +1,4 @@
-Runtime error: Cast failed. Expected `E`, got `Bool` (../lib/core/collection/array.nit:989)
+Runtime error: Cast failed. Expected `E`, got `Bool` (../lib/core/collection/array.nit:991)
 NativeString
 0x4e
 Nit
diff --git a/tests/sav/test_readline.res b/tests/sav/test_readline.res
new file mode 100644 (file)
index 0000000..666312a
--- /dev/null
@@ -0,0 +1,9 @@
+prompt>line 1
+line 1
+prompt>line 2
+line 2
+prompt>line 2bis
+line 2bis
+prompt>line 3 \b \bine\b \b\b \b\b \b3\b \b
+line 3
+prompt>
\ No newline at end of file
index 01532de..b1fe714 100644 (file)
@@ -2,6 +2,9 @@ test_prog/
 |--test_prog/README.md
 |--test_prog/game
 |  |--test_prog/game/README.md
+|  |--test_prog/game/excluded.nit
+|  |--test_prog/game/excluded_dir
+|  |  `--test_prog/game/excluded_dir/more.nit
 |  `--test_prog/game/game.nit
 |--test_prog/package.ini
 |--test_prog/platform
diff --git a/tests/test_nitunit4/sav/test_sav_conflict.res b/tests/test_nitunit4/sav/test_sav_conflict.res
new file mode 100644 (file)
index 0000000..7b3a785
--- /dev/null
@@ -0,0 +1 @@
+BAD
index cd13aca..8864263 100644 (file)
@@ -32,4 +32,8 @@ class TestTestSuite
        fun test_baz do
                print "Tested method"
        end
+
+       fun test_sav_conflict do
+               print "Tested method"
+       end
 end
diff --git a/tests/test_nitunit4/test_nitunit4.sav/test_sav_conflict.res b/tests/test_nitunit4/test_nitunit4.sav/test_sav_conflict.res
new file mode 100644 (file)
index 0000000..7b3a785
--- /dev/null
@@ -0,0 +1 @@
+BAD
diff --git a/tests/test_nitunit4/test_sav_conflict.res b/tests/test_nitunit4/test_sav_conflict.res
new file mode 100644 (file)
index 0000000..7b3a785
--- /dev/null
@@ -0,0 +1 @@
+BAD
diff --git a/tests/test_prog/game/excluded.nit b/tests/test_prog/game/excluded.nit
new file mode 100644 (file)
index 0000000..d296d4b
--- /dev/null
@@ -0,0 +1 @@
+This is not a valid Nit program
diff --git a/tests/test_prog/game/excluded_dir/more.nit b/tests/test_prog/game/excluded_dir/more.nit
new file mode 100644 (file)
index 0000000..d296d4b
--- /dev/null
@@ -0,0 +1 @@
+This is not a valid Nit program
index ff4b70d..bfcd472 100644 (file)
@@ -5,6 +5,8 @@ tags=test,game
 maintainer=John Doe <jdoe@example.com> (http://www.example.com/~jdoe), Spider-Man
 more_contributors=Riri <riri@example.com>, Fifi (http://www.example.com/~fifi), Loulou
 license=Apache-2.0
+[source]
+exclude=game/excluded.nit:game/excluded_dir
 [upstream]
 browse=https://github.com/nitlang/nit/tree/master/tests/test_prog
 git=https://github.com/nitlang/nit.git
diff --git a/tests/test_readline.inputs b/tests/test_readline.inputs
new file mode 100644 (file)
index 0000000..59f6fe7
--- /dev/null
@@ -0,0 +1,4 @@
+line 1
+line 2
+line 2bis
+line 3 \bine\b\b\b3\b
similarity index 77%
rename from lib/popcorn/tests/Makefile
rename to tests/test_readline.nit
index 7e78098..c254b24 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright 2013 Alexandre Terrasa <alexandre@moz-code.org>.
+# 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.
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-all: tests
+import readline
 
-check: clean
-       ./tests.sh
-
-clean:
-       rm -rf out/
+loop
+       var line = readline("prompt>", true)
+       if line == null then break
+       print line
+end