Merge: git: ignore nitunit.xml files
authorJean Privat <jean@pryen.org>
Tue, 25 Nov 2014 04:05:36 +0000 (23:05 -0500)
committerJean Privat <jean@pryen.org>
Tue, 25 Nov 2014 04:05:36 +0000 (23:05 -0500)
These nasty files start swarming all over my Nit folder...

Ignore!

Signed-off-by: Alexandre Terrasa <alexandre@moz-code.org>

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

88 files changed:
contrib/nitiwiki/.gitignore [new file with mode: 0644]
contrib/nitiwiki/Makefile [new file with mode: 0644]
contrib/nitiwiki/README.md [new file with mode: 0644]
contrib/nitiwiki/examples/default/config.ini [new file with mode: 0644]
contrib/nitiwiki/examples/default/pages/index.md [new file with mode: 0644]
contrib/nitiwiki/examples/default/templates/footer.html [new file with mode: 0644]
contrib/nitiwiki/examples/default/templates/header.html [new file with mode: 0644]
contrib/nitiwiki/examples/default/templates/menu.html [new file with mode: 0644]
contrib/nitiwiki/examples/default/templates/template.html [new file with mode: 0644]
contrib/nitiwiki/examples/nitiwiki/assets/css/main.css [new file with mode: 0644]
contrib/nitiwiki/examples/nitiwiki/assets/logo.png [new file with mode: 0644]
contrib/nitiwiki/examples/nitiwiki/config.ini [new file with mode: 0644]
contrib/nitiwiki/examples/nitiwiki/pages/index.md [new symlink]
contrib/nitiwiki/examples/nitiwiki/templates/footer.html [new file with mode: 0644]
contrib/nitiwiki/examples/nitiwiki/templates/header.html [new file with mode: 0644]
contrib/nitiwiki/examples/nitiwiki/templates/menu.html [new file with mode: 0644]
contrib/nitiwiki/examples/nitiwiki/templates/template.html [new file with mode: 0644]
contrib/nitiwiki/src/nitiwiki.nit [new file with mode: 0644]
contrib/nitiwiki/src/wiki_base.nit [new file with mode: 0644]
contrib/nitiwiki/src/wiki_html.nit [new file with mode: 0644]
contrib/nitiwiki/tests/Makefile [new file with mode: 0644]
contrib/nitiwiki/tests/nitiwiki.args [new file with mode: 0644]
contrib/nitiwiki/tests/nitiwiki_render.args [new file with mode: 0644]
contrib/nitiwiki/tests/nitiwiki_status.args [new file with mode: 0644]
contrib/nitiwiki/tests/res/nitiwiki.res [new file with mode: 0644]
contrib/nitiwiki/tests/res/nitiwiki_render.res [new file with mode: 0644]
contrib/nitiwiki/tests/res/nitiwiki_status.res [new file with mode: 0644]
contrib/nitiwiki/tests/tests.sh [new file with mode: 0755]
contrib/nitiwiki/tests/wiki1/assets/css/main.css [new file with mode: 0644]
contrib/nitiwiki/tests/wiki1/config.ini [new file with mode: 0644]
contrib/nitiwiki/tests/wiki1/config2.ini [new file with mode: 0644]
contrib/nitiwiki/tests/wiki1/pages/index.md [new file with mode: 0644]
contrib/nitiwiki/tests/wiki1/templates/footer.html [new file with mode: 0644]
contrib/nitiwiki/tests/wiki1/templates/header.html [new file with mode: 0644]
contrib/nitiwiki/tests/wiki1/templates/menu.html [new file with mode: 0644]
contrib/nitiwiki/tests/wiki1/templates/template.html [new file with mode: 0644]
lib/android/assets_and_resources.nit
lib/android/audio.nit
lib/android/bundle/bundle.nit
lib/android/intent/intent_api10.nit
lib/android/platform.nit
lib/android/shared_preferences/shared_preferences_api10.nit
lib/android/vibration.nit
lib/app/data_store.nit
lib/c.nit
lib/java/io.nit
lib/java/java.nit
lib/markdown/markdown.nit
lib/markdown/test_markdown.nit
lib/mnit_android/android_app.nit
lib/pthreads/extra.nit [new file with mode: 0644]
lib/pthreads/pthreads.nit
lib/standard/kernel.nit
lib/standard/math.nit
lib/trees/abstract_tree.nit
lib/trees/bintree.nit
lib/trees/rbtree.nit
src/compiler/android_annotations.nit
src/compiler/android_platform.nit
src/doc/doc_model.nit
src/docdown.nit [moved from src/markdown.nit with 99% similarity]
src/interpreter/naive_interpreter.nit
src/modelize/modelize_property.nit
src/test_docdown.nit
src/testing/testing_doc.nit
tests/Makefile
tests/base_self_type.nit [new file with mode: 0644]
tests/listfull.sh
tests/nitiwiki.args [new file with mode: 0644]
tests/sav/base_self_type.res [new file with mode: 0644]
tests/sav/base_self_type_alt1.res [new file with mode: 0644]
tests/sav/base_self_type_alt2.res [new file with mode: 0644]
tests/sav/base_self_type_alt3.res [new file with mode: 0644]
tests/sav/error_class_glob.res
tests/sav/nitg-e/base_self_type.res [new file with mode: 0644]
tests/sav/nitg-e/base_self_type_alt2.res [new file with mode: 0644]
tests/sav/nitg-e/fixme/base_gen_reassign_alt4.res
tests/sav/nitg-e/fixme/base_gen_reassign_alt5.res
tests/sav/nitg-e/fixme/base_gen_reassign_alt6.res
tests/sav/nitiwiki.res [new file with mode: 0644]
tests/sav/nitiwiki_args1.res [new file with mode: 0644]
tests/sav/nitiwiki_args2.res [new file with mode: 0644]
tests/sav/nituml_args3.res
tests/sav/nituml_args4.res
tests/sav/test_c_alt1.res
tests/sav/test_c_alt2.res
tests/sav/test_c_alt3.res
tests/sav/test_c_alt4.res

diff --git a/contrib/nitiwiki/.gitignore b/contrib/nitiwiki/.gitignore
new file mode 100644 (file)
index 0000000..89f9ac0
--- /dev/null
@@ -0,0 +1 @@
+out/
diff --git a/contrib/nitiwiki/Makefile b/contrib/nitiwiki/Makefile
new file mode 100644 (file)
index 0000000..8e385f9
--- /dev/null
@@ -0,0 +1,15 @@
+all: nitiwiki
+
+nitiwiki:
+       mkdir -p bin
+       ../../bin/nitc src/nitiwiki.nit -o bin/nitiwiki
+
+tests: nitiwiki
+       cd tests; make
+
+doc:
+       ../../bin/nitdoc -d doc src/nitiwiki.nit
+
+clean:
+       rm -rf bin
+       rm -rf -- .nit_compile 2> /dev/null || true
diff --git a/contrib/nitiwiki/README.md b/contrib/nitiwiki/README.md
new file mode 100644 (file)
index 0000000..abe84ad
--- /dev/null
@@ -0,0 +1,291 @@
+# nitiwiki, a simple wiki manager based on markdown.
+
+Basically, nitiwiki compiles a set of markdown files into an HTML wiki.
+
+The wiki content is structured by `sections` represented by the wiki folders. Sections can contain `articles` represented by markdown files.
+
+Features:
+
+* automatic wiki structure from folders hierarchy
+* automatic site menu
+* automatic sitemap
+* automatic summaries
+* easy and rapid templating
+* customizable section templates and menus
+* rsync synchronization
+* git synchronization
+
+## Wiki structure
+
+Basic wiki structure:
+
+       root
+       |- assets
+       |- out
+       |- pages
+       |- templates
+       |       |- footer.html
+       |       |- header.html
+       |       |- menu.html
+       |       `- template.html
+       `- config.ini
+
+### pages
+
+This is where goes all the content of your wiki.
+Nitiwiki will render an article for each markdown file found in `pages`.
+
+You can categorize your content in sections using sub-folders:
+
+       pages
+       |- section1
+       |       `- index.md
+       |- section2
+       |       `- index.md
+       |- page1.md
+       |- page2.md
+       `- index.md
+
+### assets
+
+This is where you store CSS and JavaScript files used in the design of your site.
+
+You can also use this directory to put some images or other files that will be
+used in all your pages.
+
+       assets
+       |- css
+       |- js
+       `- logo.png
+
+### templates
+
+This folder contains the templates used to generate the HTML pages of your wiki.
+
+The main template is called `template.html`.
+It contains the HTML structure of your pages and some macros that will be replaced
+by the wiki articles.
+
+### out
+
+This is where your wiki will be rendered by nitiwiki.
+Do not put anything in this folder since it will be overwritten at the
+next wiki rendering.
+
+The wiki rendering consists in:
+
+1. copy the `assets` directory to `out`
+2. copy attached article files from `pages` to `out`
+3. translate markdown files from `pages` to html files in `out`
+
+### config.ini
+
+This is the main config file of your wiki. For more details see [Configure the wiki](#Configure_the_wiki).
+
+## Manage the wiki
+
+### Create a new wiki
+
+Just move to the directory where you want to store your source files and type:
+
+       nitiwiki init
+
+This command will import the base structure of your wiki in the current directory.
+At this point nitiwiki has created the main configuration file of your site:
+`config.ini`.
+
+### Configure the wiki
+
+All the nitiwiki configuration is done using
+[ini files](http://en.wikipedia.org/wiki/INI_file).
+
+The wiki configuration is contained in the `config.ini` file located at the root
+directory of your wiki.
+This file can be edited to change nitiwiki settings.
+
+Settings:
+
+* `wiki.name`: Displayed name
+* `wiki.desc`: Long description
+* `wiki.logo`: Logo image url
+* `wiki.root_url`: Base url used to resolve links
+* `wiki.root_dir`: Absolute path of base directory
+* `wiki.source_dir`: Source directory (relative path from `wiki.root_dir`)
+* `wiki.out_dir`: Output directory (relative)
+* `wiki.assets_dir`: Assets directory (relative)
+* `wiki.templates_dir`: Templates directory (relative)
+* `wiki.template`: Wiki main template file
+* `wiki.header`: Wiki main header template file
+* `wiki.footer`: Wiki main footer template file
+* `wiki.menu`: Wiki main menu template file
+* `wiki.rsync_dir`: Directory used to rsync output
+* `wiki.git_origin`: Git origin used to fetch data
+* `wiki.git_branch`: Git branch used to fetch data
+
+For more details on each option see `WikiConfig`.
+
+### Add content
+
+To add content in your wiki simply add markdown files (with `.md` extension) into the `pages/` folder.
+
+Once you have done your changes, use:
+
+       nitiwiki --status
+
+This will show the impacts of your changes on the wiki structure.
+
+Then type:
+
+       nitiwiki --render
+
+This will the generate the html output of your new content.
+The option `--force` can be used to regenerate all the wiki.
+This can be uselful when you perform changes on the template files.
+
+### Configure sections
+
+Section appearance can be customized using config files.
+
+Each section in the `pages/` folder can contain a `config.ini` file.
+Options set on a section will be propagated to all its children unless
+they have their own config file.
+
+Allowed options are:
+
+* `section.title`: Custom title for this section
+* `section.template`: Custom template file
+* `section.header`: Custom header template file
+* `section.footer`: Custom footer template file
+* `section.menu`: Custom menu template file
+* `section.is_hidden`: Set this to `true` will hide the section in all menus and
+  sitemaps.
+
+## Customize templates
+
+Templating your wiki involves modifying 4 template files:
+
+* `template.html`
+* `header.html`
+* `footer.html`
+* `menu.html`
+
+Each of these file contains an HTML skeletton used by nitiwiki to render your files.
+Templates can contains macros marked `%MACRO%` that will be replaced by dynamic content.
+
+Every template can access to:
+
+* `ROOT_URL`: Wiki root url
+* `TITLE`: Wiki name
+* `SUBTITLE`: Wiki description
+* `LOGO`: Wiki logo image path
+
+Additionnal macros can be used in specialized templates.
+
+### Main template
+
+The template file `template.html` represents the overall structure of your wiki pages.
+
+       <!DOCTYPE html>
+       <html>
+               <head>
+                       <title>%TITLE%</title>
+                       <link href="%ROOT_URL%/assets/css/main.css" rel="stylesheet">
+               </head>
+               <body>
+                       %HEADER%
+                       %TOP_MENU%
+                       <div>
+                               %BODY%
+                               %FOOTER%
+                       </div>
+               </body>
+       </html>
+
+Additionnal macros:
+
+* `HEADER`: Wiki header (see [Header template](#Header_template))
+* `FOOTER`: Wiki footer (see [Footer template](#Footer_template))
+* `TOP_MENU`: Wiki top menu (see [Topmenu template](#Topmenu_template))
+* `HEADER`: Wiki header (see [Header template](#Header_template))
+* `BODY`: Wiki body content
+
+### Header template
+
+The template file `header.html` is generated on top of all the wiki pages.
+
+       <header>
+               <a href="#"><img src="%ROOT_URL%/%LOGO%" alt="logo"/></a>
+               <h2>%SUBTITLE%</h2>
+               <h1>%TITLE%</h1>
+       </header>
+
+### Footer template
+
+The template file `footer.html` is generated on the bottom of all the wiki pages.
+
+       <footer>
+               <p>%TITLE% &copy; %YEAR%</p>
+               <p>last modification %GEN_TIME%</p>
+       </footer>
+
+Additionnal macros:
+
+* `YEAR`: Current year
+* `GEN_TIME`: Page generation date
+
+### Topmenu template
+
+The template file `menu.html` contains the menu structure generated on all your pages.
+
+Its content can be static:
+
+       <nav class="menu">
+               <ul class="nav navbar-nav">
+                       <li><a href="#">Home</a></li>
+                       <li><a href="#">Page1</a></li>
+                       <li><a href="#">Page2</a></li>
+               </ul>
+       </nav>
+
+Or dynamic using the macro `MENUS`:
+
+       <nav class="menu">
+               <ul class="nav navbar-nav">
+               %MENUS%
+               </ul>
+       </nav>
+
+## Advanced usages
+
+### Working with git
+
+nitiwiki allows you to store your wiki changes in git.
+Using the option `--fetch` will update the local wiki instance
+according to git informations found in the config file.
+
+Be sure to set `wiki.git_origin` and `wiki.git_branch`
+in order to correctly pull changes.
+
+To automatically update your wiki when changes are pushed on the
+origin repository you can use the following command in a git hook:
+
+       nitiwiki --fetch --render
+
+### Working with a remote server
+
+Sometimes you cannot do what you want on your webserver (like setting crons).
+For this purpose, nitiwiki provide a quick way to update a distant instance
+through `ssh` using `rsync`.
+
+With the option `--rsync`, nitwiki will update a distant location with the
+last rendered output. This way you can manually update your webserver
+after changes or set a cron on a different server that you can control.
+
+Using the following command in your cron will update the web server instance
+from git:
+
+       nitiwiki --fetch --render --rsync
+
+Be sure to set `wiki.rsync_dir` in order to correctly push your changes.
+When using `--rsync`, keep in mind that the rendered output must be configured
+to work on the web server and set `wiki.root_url` accordingly.
diff --git a/contrib/nitiwiki/examples/default/config.ini b/contrib/nitiwiki/examples/default/config.ini
new file mode 100644 (file)
index 0000000..21e85ef
--- /dev/null
@@ -0,0 +1,5 @@
+wiki.name=MyWiki
+wiki.desc=proudly powered by nit
+wiki.logo=assets/logo.png
+wiki.root_url=http://localhost/
+wiki.root_dir=/full/path/to/your/wiki/root/dir
diff --git a/contrib/nitiwiki/examples/default/pages/index.md b/contrib/nitiwiki/examples/default/pages/index.md
new file mode 100644 (file)
index 0000000..b212ceb
--- /dev/null
@@ -0,0 +1 @@
+# Welcome on nitiwiki
diff --git a/contrib/nitiwiki/examples/default/templates/footer.html b/contrib/nitiwiki/examples/default/templates/footer.html
new file mode 100644 (file)
index 0000000..7c8de99
--- /dev/null
@@ -0,0 +1,4 @@
+<footer>
+    <p>%TITLE% &copy; %YEAR%</p>
+    <p>last modification %GEN_TIME%</p>
+</footer>
diff --git a/contrib/nitiwiki/examples/default/templates/header.html b/contrib/nitiwiki/examples/default/templates/header.html
new file mode 100644 (file)
index 0000000..d86aac9
--- /dev/null
@@ -0,0 +1,5 @@
+<header>
+    <a href="#"><img src="%ROOT_URL%/%LOGO%" alt="logo"/></a>
+    <h2>%SUBTITLE%</h2>
+    <h1>%TITLE%</h1>
+</header>
diff --git a/contrib/nitiwiki/examples/default/templates/menu.html b/contrib/nitiwiki/examples/default/templates/menu.html
new file mode 100644 (file)
index 0000000..76a6f95
--- /dev/null
@@ -0,0 +1,5 @@
+<nav class="menu">
+    <ul class="nav navbar-nav">
+    %MENUS%
+    </ul>
+</nav>
diff --git a/contrib/nitiwiki/examples/default/templates/template.html b/contrib/nitiwiki/examples/default/templates/template.html
new file mode 100644 (file)
index 0000000..692a999
--- /dev/null
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>%TITLE%</title>
+        <link href="%ROOT_URL%/assets/css/main.css" rel="stylesheet">
+    </head>
+    <body>
+        %HEADER%
+        %TOP_MENU%
+        <div>
+            %BODY%
+            %FOOTER%
+        </div>
+    </body>
+</html>
diff --git a/contrib/nitiwiki/examples/nitiwiki/assets/css/main.css b/contrib/nitiwiki/examples/nitiwiki/assets/css/main.css
new file mode 100644 (file)
index 0000000..c00394a
--- /dev/null
@@ -0,0 +1,30 @@
+header { margin: 15px 0; }
+header img {
+       padding: 10px;
+       float: left;
+}
+header h1, header h2 { display: none; }
+
+footer {
+       margin-top: 30px;
+       text-align: center;
+}
+
+.menu {
+       border-top: 6px solid #47CA42;
+       background-color: #0d8921;
+}
+
+.menu .navbar-brand, .menu a { color: #fff; }
+.menu .navbar-nav>.active>a, .menu .navbar-nav>li>a:hover { background-color: #47CA42; }
+
+.sidebar { margin-top: 20px; }
+
+.summary a { color: #333; }
+.summary a:hover { text-decoration: none; }
+.summary a:hover, .summary li:active>a { color: #428bca; }
+.summary li { padding: 2px 0; font-weight: bold }
+.summary li li { font-size: 13px;}
+.summary li li li { font-size: 12px; font-weight: normal }
+
+.breadcrumb { margin-top: 20px; }
diff --git a/contrib/nitiwiki/examples/nitiwiki/assets/logo.png b/contrib/nitiwiki/examples/nitiwiki/assets/logo.png
new file mode 100644 (file)
index 0000000..daccc80
Binary files /dev/null and b/contrib/nitiwiki/examples/nitiwiki/assets/logo.png differ
diff --git a/contrib/nitiwiki/examples/nitiwiki/config.ini b/contrib/nitiwiki/examples/nitiwiki/config.ini
new file mode 100644 (file)
index 0000000..16eaffe
--- /dev/null
@@ -0,0 +1,6 @@
+wiki.name=nitiwiki
+wiki.desc=proudly powered by nit
+wiki.logo=assets/logo.png
+wiki.root_url=http://moz-code.org/nitiwiki/
+wiki.root_dir=.
+wiki.rsync_dir=moz-code.org:nitiwiki/
diff --git a/contrib/nitiwiki/examples/nitiwiki/pages/index.md b/contrib/nitiwiki/examples/nitiwiki/pages/index.md
new file mode 120000 (symlink)
index 0000000..8a33348
--- /dev/null
@@ -0,0 +1 @@
+../../../README.md
\ No newline at end of file
diff --git a/contrib/nitiwiki/examples/nitiwiki/templates/footer.html b/contrib/nitiwiki/examples/nitiwiki/templates/footer.html
new file mode 100644 (file)
index 0000000..94229ea
--- /dev/null
@@ -0,0 +1,10 @@
+<footer class="row">
+       <div class="container-fluid">
+               <div class="well well-sm">
+                       <p><strong>%TITLE% &copy; %YEAR%</strong></p>
+                       <p class="text-muted"><em>last modification %GEN_TIME%</em></p>
+                       <p class="text-muted">Proudly powered by
+                               <a href="http://nitlanguage.org">nit</a>!</p>
+               </div>
+       </div>
+</footer>
diff --git a/contrib/nitiwiki/examples/nitiwiki/templates/header.html b/contrib/nitiwiki/examples/nitiwiki/templates/header.html
new file mode 100644 (file)
index 0000000..8b06ab8
--- /dev/null
@@ -0,0 +1,9 @@
+<div class="container-fluid header">
+       <div class="container">
+               <header>
+                       <a href="http://uqam.ca"><img src="%ROOT_URL%/%LOGO%" alt="logo" /></a>
+                       <h2>%SUBTITLE%</h2>
+                       <h1>%TITLE%</h1>
+               </header>
+       </div>
+</div>
diff --git a/contrib/nitiwiki/examples/nitiwiki/templates/menu.html b/contrib/nitiwiki/examples/nitiwiki/templates/menu.html
new file mode 100644 (file)
index 0000000..1ff832e
--- /dev/null
@@ -0,0 +1,20 @@
+<nav class="menu" role="navigation">
+       <div class="container">
+               <!-- Brand and toggle get grouped for better mobile display -->
+               <div class="navbar-header">
+                       <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
+                               <span class="sr-only">Toggle navigation</span>
+                               <span class="icon-bar"></span>
+                               <span class="icon-bar"></span>
+                               <span class="icon-bar"></span>
+                       </button>
+                       <a class="navbar-brand" href="%ROOT_URL%index.html">%TITLE%</a>
+               </div>
+               <!-- Collect the nav links, forms, and other content for toggling -->
+               <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
+                       <ul class="nav navbar-nav">
+                       %MENUS%
+                       </ul>
+               </div><!-- /.navbar-collapse -->
+       </div>
+</nav>
diff --git a/contrib/nitiwiki/examples/nitiwiki/templates/template.html b/contrib/nitiwiki/examples/nitiwiki/templates/template.html
new file mode 100644 (file)
index 0000000..7c43833
--- /dev/null
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+       <head>
+               <meta charset="utf-8">
+               <meta http-equiv="X-UA-Compatible" content="IE=edge">
+               <meta name="viewport" content="width=device-width, initial-scale=1">
+               <title>%TITLE%</title>
+
+               <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet">
+               <link href="%ROOT_URL%/assets/css/main.css" rel="stylesheet">
+
+               <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
+               <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
+               <!--[if lt IE 9]>
+               <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
+               <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+               <![endif]-->
+       </head>
+       <body>
+               %HEADER%
+               %TOP_MENU%
+               <div class="container">
+                       <div class="row">
+                               %BODY%
+                       </div>
+                       %FOOTER%
+               </div>
+
+               <script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
+               <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
+       </body>
+</html>
diff --git a/contrib/nitiwiki/src/nitiwiki.nit b/contrib/nitiwiki/src/nitiwiki.nit
new file mode 100644 (file)
index 0000000..200f971
--- /dev/null
@@ -0,0 +1,163 @@
+# 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.
+
+# A wiki engine based on markdown files and git.
+module nitiwiki
+
+import wiki_html
+
+# Locate nit directory
+private fun compute_nit_dir(opt_nit_dir: OptionString): String do
+       # the option has precedence
+       var res = opt_nit_dir.value
+       if res != null then
+               if not check_nit_dir(res) then
+                       print "Fatal Error: --nit-dir does not seem to be a valid base Nit directory: {res}"
+                       exit 1
+               end
+               return res
+       end
+
+       # then the environ variable has precedence
+       res = "NIT_DIR".environ
+       if not res.is_empty then
+               if not check_nit_dir(res) then
+                       print "Fatal Error: NIT_DIR does not seem to be a valid base Nit directory: {res}"
+                       exit 1
+               end
+               return res
+       end
+
+       # find the runpath of the program from argv[0]
+       res = "{sys.program_name.dirname}/.."
+       if check_nit_dir(res) then return res.simplify_path
+
+       # find the runpath of the process from /proc
+       var exe = "/proc/self/exe"
+       if exe.file_exists then
+               res = exe.realpath
+               res = res.dirname.join_path("..")
+               if check_nit_dir(res) then return res.simplify_path
+       end
+
+       # search in the PATH
+       var ps = "PATH".environ.split(":")
+       for p in ps do
+               res = p/".."
+               if check_nit_dir(res) then return res.simplify_path
+       end
+
+       print "Fatal Error: Cannot locate a valid base nit directory. It is quite unexpected. Try to set the environment variable `NIT_DIR` or to use the `--nit-dir` option."
+       exit 1
+       abort
+end
+
+private fun check_nit_dir(res: String): Bool do
+       return res.file_exists and "{res}/src/nit.nit".file_exists
+end
+
+var opt_help = new OptionBool("Display this help message", "-h", "--help")
+var opt_verbose = new OptionCount("Verbose level", "-v")
+var opt_config = new OptionString("Path to config.ini file", "-c", "--config")
+var opt_init = new OptionBool("Initialize a new wiki in the current directory", "--init")
+var opt_status = new OptionBool("Display wiki status", "-s", "--status")
+var opt_render = new OptionBool("Render the out directory from markdown sources", "-r", "--render")
+var opt_force = new OptionBool("Force render even if source files are unchanged", "-f", "--force")
+var opt_clean = new OptionBool("Clean the output directory", "--clean")
+var opt_rsync = new OptionBool("Synchronize outdir with distant wiki using rsync", "-s", "--rsync")
+var opt_fetch = new OptionBool("Render local source from git repo", "--fetch")
+var opt_nit_dir = new OptionString("Nit base directory", "--nit-dir")
+
+var context = new OptionContext
+context.add_option(opt_help, opt_verbose, opt_config)
+context.add_option(opt_init, opt_status, opt_render, opt_force)
+context.add_option(opt_clean, opt_rsync, opt_fetch, opt_nit_dir)
+context.parse(args)
+
+var config_filename = "config.ini"
+
+# --help
+if opt_help.value then
+       context.usage
+       exit 0
+end
+
+# --init
+if opt_init.value then
+       if config_filename.file_exists then
+               print "Already in a nitiwiki directory."
+               exit 0
+       end
+       var nitiwiki_home = "{compute_nit_dir(opt_nit_dir)}/contrib/nitiwiki"
+       var tpl = "{nitiwiki_home}/examples/default/"
+       if not tpl.file_exists then
+               print "Cannot find {tpl} files."
+               print "Maybe your NIT_DIR is not set properly?"
+               print "You can initialize nitiwiki manually by copying the default skeletton here."
+               exit 1
+       end
+       sys.system "cp -R {tpl}/* ."
+       print "Initialized new nitiwiki."
+       print "Set wiki settings by editing {config_filename}."
+       exit 0
+end
+
+# load config files
+
+# --config
+var config_file = opt_config.value
+if config_file == null then
+       config_file = config_filename
+end
+
+if not config_file.file_exists then
+       print "Not in a nitiwiki directory."
+       print "Use --init to initialize one here."
+       exit 0
+end
+
+var config = new WikiConfig(config_file)
+var wiki = new Nitiwiki(config)
+
+# --verbose
+wiki.verbose_level = opt_verbose.value
+
+# --clean
+if opt_clean.value then
+       wiki.clean
+end
+
+# --fetch
+if opt_fetch.value then
+       wiki.fetch
+end
+
+# --render
+if opt_render.value then
+       wiki.parse
+       # --force
+       wiki.force_render = opt_force.value
+       wiki.render
+end
+
+# --rsync
+if opt_rsync.value then
+       wiki.sync
+end
+
+# --status
+if opt_status.value or args.is_empty then
+       wiki.parse
+       wiki.status
+end
diff --git a/contrib/nitiwiki/src/wiki_base.nit b/contrib/nitiwiki/src/wiki_base.nit
new file mode 100644 (file)
index 0000000..2c0276b
--- /dev/null
@@ -0,0 +1,744 @@
+# 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.
+
+# Base entities of a nitiwiki.
+module wiki_base
+
+import template::macro
+import markdown
+import opts
+import ini
+
+# A Nitiwiki instance.
+#
+# Nitiwiki provide all base services used by `WikiSection` and `WikiArticle`.
+# It manages content and renders pages.
+#
+# Each nitiwiki instance is linked to a config file.
+# This file show to `nitiwiki` that a wiki is present in the current directory.
+# Without it, nitiwiki will consider the directory as empty.
+class Nitiwiki
+
+       # Wiki config object.
+       var config: WikiConfig
+
+       # Default config filename.
+       var config_filename = "config.ini"
+
+       # Force render on all file even if the source is unmodified.
+       var force_render = false is writable
+
+       # Verbosity level.
+       var verbose_level = 0 is writable
+
+       # Delete all the output files.
+       fun clean do
+               var out_dir = expand_path(config.root_dir, config.out_dir)
+               if out_dir.file_exists then out_dir.rmdir
+       end
+
+       # Synchronize local output with the distant `WikiConfig::rsync_dir`.
+       fun sync do
+               var root = expand_path(config.root_dir, config.out_dir)
+               sys.system "rsync -vr --delete {root}/ {config.rsync_dir}"
+       end
+
+       fun fetch do
+               sys.system "git pull {config.git_origin} {config.git_branch}"
+       end
+
+       # Analyze wiki files from `dir` to build wiki entries.
+       #
+       # This method build a hierarchical structure of `WikiSection` and `WikiArticle`
+       # based on the markdown source structure.
+       fun parse do
+               var dir = expand_path(config.root_dir, config.source_dir)
+               root_section = new_section(dir)
+               var files = list_md_files(dir)
+               for file in files do
+                       new_article(file)
+               end
+       end
+
+       # Show wiki status.
+       fun status do
+               print "nitiWiki"
+               print "name: {config.wiki_name}"
+               print "config: {config.ini_file}"
+               print "url: {config.root_url}"
+               print ""
+               if root_section.is_dirty then
+                       print "There is modified files:"
+                       var paths = entries.keys.to_a
+                       var s = new DefaultComparator
+                       s.sort(paths)
+                       for path in paths do
+                               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.is_new then
+                                       print " + {name}"
+                               else
+                                       print " * {name}"
+                               end
+                       end
+                       print ""
+                       print "Use nitiwiki --render to render modified files"
+               else
+                       print "Wiki is up-to-date"
+                       print ""
+                       print "Use nitiwiki --fetch to pull modification from origin"
+                       print "Use nitiwiki --rsync to synchronize distant output"
+               end
+       end
+
+       # Display msg if `level >= verbose_level`
+       fun message(msg: String, level: Int) do
+               if level <= verbose_level then print msg
+       end
+
+       # List markdown source files from a directory.
+       fun list_md_files(dir: String): Array[String] do
+               var files = new Array[String]
+               var pipe = new IProcess("find", dir, "-name", "*.md")
+               while not pipe.eof do
+                       var file = pipe.read_line
+                       if file == "" then break # last line
+                       file = file.substring(0, file.length - 1) # strip last oef
+                       var name = file.basename(".md")
+                       if name == "header" or name == "footer" or name == "menu" then continue
+                       files.add file
+               end
+               pipe.close
+               pipe.wait
+               if pipe.status != 0 then exit 1
+               var s = new DefaultComparator
+               s.sort(files)
+               return files
+       end
+
+       # Does `src` have been modified since `target` creation?
+       #
+       # Always returns `true` if `--force` is on.
+       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
+       end
+
+       # Create a new `WikiSection`.
+       #
+       # `path` is used to determine the place in the wiki hierarchy.
+       protected fun new_section(path: String): WikiSection do
+               path = path.simplify_path
+               if entries.has_key(path) then return entries[path].as(WikiSection)
+               var root = expand_path(config.root_dir, config.source_dir)
+               var name = path.basename("")
+               var section = new WikiSection(self, name)
+               entries[path] = section
+               if path == root then return section
+               var ppath = path.dirname
+               if ppath != path  then
+                       var parent = new_section(ppath)
+                       parent.add_child(section)
+               end
+               section.try_load_config
+               return section
+       end
+
+       # Create a new `WikiArticle`.
+       #
+       # `path` is used to determine the ancestor sections.
+       protected fun new_article(path: String): WikiArticle do
+               if entries.has_key(path) then return entries[path].as(WikiArticle)
+               var article = new WikiArticle.from_source(self, path)
+               var section = new_section(path.dirname)
+               section.add_child(article)
+               entries[path] = article
+               return article
+       end
+
+       # Wiki entries found in the last `lookup_hierarchy`.
+       var entries = new HashMap[String, WikiEntry]
+
+       # The root `WikiSection` of the site found in the last `lookup_hierarchy`.
+       var root_section: WikiSection is noinit
+
+       # Does a template named `name` exists for this wiki?
+       fun has_template(name: String): Bool do
+               return expand_path(config.root_dir, config.templates_dir, name).file_exists
+       end
+
+       # Load a template file as a `TemplateString`.
+       #
+       # REQUIRE: `has_template`
+       fun load_template(name: String): TemplateString do
+               assert has_template(name)
+               var file = expand_path(config.root_dir, config.templates_dir, name)
+               var tpl = new TemplateString.from_file(file)
+               if tpl.has_macro("ROOT_URL") then
+                       tpl.replace("ROOT_URL", config.root_url)
+               end
+               if tpl.has_macro("TITLE") then
+                       tpl.replace("TITLE", config.wiki_name)
+               end
+               if tpl.has_macro("SUBTITLE") then
+                       tpl.replace("SUBTITLE", config.wiki_desc)
+               end
+               if tpl.has_macro("LOGO") then
+                       tpl.replace("LOGO", config.wiki_logo)
+               end
+               return tpl
+       end
+
+       # Join `parts` as a path and simplify it
+       fun expand_path(parts: String...): String do
+               var path = ""
+               for part in parts do
+                       path = path.join_path(part)
+               end
+               return path.simplify_path
+       end
+
+       fun pretty_name(name: String): String do
+               name = name.replace("_", " ")
+               name = name.capitalized
+               return name
+       end
+end
+
+# A wiki is composed of hierarchical entries.
+abstract class WikiEntry
+
+       # `Nitiwiki` this entry belongs to.
+       var wiki: Nitiwiki
+
+       # Entry data
+
+       # Entry internal name.
+       #
+       # Mainly used in urls.
+       var name: String
+
+       # Displayed title for `self`.
+       #
+       # If `self` is the root entry then display the wiki `WikiConfig::wiki_name` instead.
+       fun title: String do
+               if is_root then return wiki.config.wiki_name
+               return wiki.pretty_name(name)
+       end
+
+       # Is this section rendered from a source document?
+       #
+       # Source is an abstract concept at this level.
+       # It can represent a directory, a source file,
+       # a part of a file, everything needed to
+       # extend this base framework.
+       fun has_source: Bool do return src_path != null
+
+       # Entry creation time.
+       #
+       # 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
+       end
+
+       # Entry last modification time.
+       #
+       # 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
+       end
+
+       # Entry list rendering time.
+       #
+       # Returns `-1` if `is_new`.
+       fun last_render_time: Int do
+               if is_new then return -1
+               return out_full_path.file_stat.mtime
+       end
+
+       # Entries hierarchy
+
+       # Type of the parent entry.
+       type PARENT: WikiEntry
+
+       # Parent entry if any.
+       var parent: nullable PARENT = null
+
+       # Does `self` have a parent?
+       fun is_root: Bool do return parent == null
+
+       # Children labelled by `name`.
+       var children = new HashMap[String, WikiEntry]
+
+       # Does `self` have a child nammed `name`?
+       fun has_child(name: String): Bool do return children.keys.has(name)
+
+       # Retrieve the child called `name`.
+       fun child(name: String): WikiEntry do return children[name]
+
+       # Add a sub-entry to `self`.
+       fun add_child(entry: WikiEntry) do
+               entry.parent = self
+               children[entry.name] = entry
+       end
+
+       # Paths and urls
+
+       # Breadcrumbs from the `Nitiwiki::root_section` to `self`.
+       #
+       # Result is returned as an array containg ordered entries:
+       # `breadcrumbs.first` is the root entry and
+       # `breadcrumbs.last == self`
+       fun breadcrumbs: Array[WikiEntry] is cached do
+               var path = new Array[WikiEntry]
+               var entry: nullable WikiEntry = self
+               while entry != null and not entry.is_root do
+                       path.add entry
+                       entry = entry.parent
+               end
+               return path.reversed
+       end
+
+       # Relative path from `wiki.config.root_dir` to source if any.
+       fun src_path: nullable String is abstract
+
+       # Absolute path to the source if any.
+       fun src_full_path: nullable String do
+               var src = src_path
+               if src == null then return null
+               return wiki.config.root_dir.join_path(src)
+       end
+
+       # Relative path from `wiki.config.root_dir` to rendered output.
+       #
+       # Like `src_path`, this method can represent a
+       # directory or a file.
+       fun out_path: String is abstract
+
+       # Absolute path to the output.
+       fun out_full_path: String do return wiki.config.root_dir.join_path(out_path)
+
+       # Rendering
+
+       # Does `self` have already been rendered?
+       fun is_new: Bool do return not out_full_path.file_exists
+
+       # Does `self` rendered output is outdated?
+       #
+       # Returns `true` if `is_new` then check in children.
+       fun is_dirty: Bool do
+               if is_new then return true
+               if has_source then
+                       if last_edit_time >= last_render_time then return true
+               end
+               for child in children.values do
+                       if child.is_dirty then return true
+               end
+               return false
+       end
+
+       # Render `self` and `children` is needed.
+       fun render do for child in children.values do child.render
+
+       # Templating
+
+       # Template file for `self`.
+       #
+       # Each entity can use a custom template.
+       # By default the template is inherited from the parent.
+       #
+       # If the root does not have a custom template,
+       # 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
+       end
+
+       # Header template file for `self`.
+       #
+       # Behave like `template_file`.
+       fun header_file: String do
+               if is_root then return wiki.config.header_file
+               return parent.header_file
+       end
+
+       # Footer template file for `self`.
+       #
+       # Behave like `template_file`.
+       fun footer_file: String do
+               if is_root then return wiki.config.footer_file
+               return parent.footer_file
+       end
+
+       # Menu template file for `self`.
+       #
+       # Behave like `template_file`.
+       fun menu_file: String do
+               if is_root then return wiki.config.menu_file
+               return parent.menu_file
+       end
+
+       # Display the entry `name`.
+       redef fun to_s do return name
+end
+
+# Each WikiSection is related to a source directory.
+#
+# A section can contain other sub-sections or pages.
+class WikiSection
+       super WikiEntry
+
+       # A section can only have another section as parent.
+       redef type PARENT: WikiSection
+
+       redef fun title do
+               if has_config then
+                       var title = config.title
+                       if title != null then return title
+               end
+               return super
+       end
+
+       # Is this section hidden?
+       #
+       # Hidden section are rendered but not linked in menus.
+       fun is_hidden: Bool do
+               if has_config then return config.is_hidden
+               return false
+       end
+
+       # Source directory.
+       redef fun src_path: String do
+               if parent == null then
+                       return wiki.config.source_dir
+               else
+                       return wiki.expand_path(parent.src_path, name)
+               end
+       end
+
+       # Config
+
+       # Custom configuration file for this section.
+       var config: nullable SectionConfig = null
+
+       # Does this section have its own config file?
+       fun has_config: Bool do return config != null
+
+       # Try to load the config file for this section.
+       private fun try_load_config do
+               var cfile = wiki.expand_path(wiki.config.root_dir, src_path, wiki.config_filename)
+               if not cfile.file_exists then return
+               wiki.message("Custom config for section {name}", 2)
+               config = new SectionConfig(cfile)
+       end
+
+       # Templating
+
+       # Also check custom config.
+       redef fun template_file do
+               if has_config then
+                       var tpl = config.template_file
+                       if tpl != null then return tpl
+               end
+               if is_root then return wiki.config.template_file
+               return parent.template_file
+       end
+
+       # Also check custom config.
+       redef fun header_file do
+               if has_config then
+                       var tpl = config.header_file
+                       if tpl != null then return tpl
+               end
+               if is_root then return wiki.config.header_file
+               return parent.header_file
+       end
+
+       # Also check custom config.
+       redef fun footer_file do
+               if has_config then
+                       var tpl = config.footer_file
+                       if tpl != null then return tpl
+               end
+               if is_root then return wiki.config.footer_file
+               return parent.footer_file
+       end
+
+       # Also check custom config.
+       redef fun menu_file do
+               if has_config then
+                       var tpl = config.menu_file
+                       if tpl != null then return tpl
+               end
+               if is_root then return wiki.config.menu_file
+               return parent.menu_file
+       end
+end
+
+# Each WikiArticle is related to a HTML file.
+#
+# Article can be created from scratch using this API or
+# automatically from a markdown source file (see: `from_source`).
+class WikiArticle
+       super WikiEntry
+
+       # Articles can only have `WikiSection` as parents.
+       redef type PARENT: WikiSection
+
+       redef fun title: String do
+               if name == "index" and parent != null then return parent.title
+               return super
+       end
+
+       # Page content.
+       #
+       # What you want to be displayed in the page.
+       var content: nullable Streamable = null
+
+       # Headlines ids and titles.
+       var headlines = new ArrayMap[String, HeadLine]
+
+       # Create a new articleu sing a markdown source file.
+       init from_source(wiki: Nitiwiki, md_file: String) do
+               src_full_path = md_file
+               init(wiki, md_file.basename(".md"))
+               var md_proc = new MarkdownProcessor
+               content = md_proc.process(md)
+               headlines = md_proc.emitter.decorator.headlines
+       end
+
+       redef var src_full_path: nullable String = null
+
+       redef fun src_path do
+               if src_full_path == null then return null
+               return src_full_path.substring_from(wiki.config.root_dir.length)
+       end
+
+       # The page markdown source content.
+       #
+       # Extract the markdown text from `source_file`.
+       #
+       # REQUIRE: `has_source`.
+       fun md: String is cached do
+               assert has_source
+               var file = new IFStream.open(src_full_path.to_s)
+               var md = file.read_all
+               file.close
+               return md
+       end
+
+       # Returns true if has source and
+       # `last_edit_date` > 'last_render_date'.
+       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)
+               end
+               return false
+       end
+
+       redef fun to_s do return "{name} ({parent or else "null"})"
+end
+
+# Wiki configuration class.
+#
+# This class provides services that ensure static typing when accessing the `config.ini` file.
+class WikiConfig
+       super ConfigTree
+
+       # Returns the config value at `key` or return `default` if no key was found.
+       private fun value_or_default(key: String, default: String): String do
+               if not has_key(key) then return default
+               return self[key]
+       end
+
+       # Site name displayed.
+       #
+       # The title is used as home title and in headers.
+       #
+       # * key: `wiki.name`
+       # * default: `MyWiki`
+       fun wiki_name: String is cached do return value_or_default("wiki.name", "MyWiki")
+
+       # Site description.
+       #
+       # Displayed in header.
+       #
+       # * key: `wiki.desc`
+       # * default: ``
+       fun wiki_desc: String is cached do return value_or_default("wiki.desc", "")
+
+       # Site logo url.
+       #
+       # Url of the image to be displayed in header.
+       #
+       # * key: `wiki.logo`
+       # * default: ``
+       fun wiki_logo: String is cached do return value_or_default("wiki.logo", "")
+
+       # Root url of the wiki.
+       #
+       # * key: `wiki.root_url`
+       # * default: `http://localhost/`
+       fun root_url: String is cached do return value_or_default("wiki.root_url", "http://localhost/")
+
+
+       # Root directory of the wiki.
+       #
+       # Directory where the wiki files are stored locally.
+       #
+       # * key: `wiki.root_dir`
+       # * default: `./`
+       fun root_dir: String is cached do return value_or_default("wiki.root_dir", "./").simplify_path
+
+       # Pages directory.
+       #
+       # Directory where markdown source files are stored.
+       #
+       # * key: `wiki.source_dir
+       # * default: `pages/`
+       fun source_dir: String is cached do
+               return value_or_default("wiki.source_dir", "pages/").simplify_path
+       end
+
+       # Output directory.
+       #
+       # Directory where public wiki files are generated.
+       # **This path MUST be relative to `root_dir`.**
+       #
+       # * key: `wiki.out_dir`
+       # * default: `out/`
+       fun out_dir: String is cached do return value_or_default("wiki.out_dir", "out/").simplify_path
+
+       # Asset files directory.
+       #
+       # Directory where public assets like JS scripts or CSS files are stored.
+       # **This path MUST be relative to `root_dir`.**
+       #
+       # * key: `wiki.assets_dir`
+       # * default: `assets/`
+       fun assets_dir: String is cached do
+               return value_or_default("wiki.assets_dir", "assets/").simplify_path
+       end
+
+       # Template files directory.
+       #
+       # Directory where template used in HTML generation are stored.
+       # **This path MUST be relative to `root_dir`.**
+       #
+       # * key: `wiki.templates_dir`
+       # * default: `templates/`
+       fun templates_dir: String is cached do
+               return value_or_default("wiki.templates_dir", "templates/").simplify_path
+       end
+
+       # Main template file.
+       #
+       # The main template is used to specify the overall structure of a page.
+       #
+       # * key: `wiki.template`
+       # * default: `template.html`
+       fun template_file: String is cached do
+               return value_or_default("wiki.template", "template.html")
+       end
+
+       # Main header template file.
+       #
+       # Used to specify the structure of the page header.
+       # This is generally the place where you want to put your logo and wiki title.
+       #
+       # * key: `wiki.header`
+       # * default: `header.html`
+       fun header_file: String is cached do
+               return value_or_default("wiki.header", "header.html")
+       end
+
+       # Main menu template file.
+       #
+       # Used to specify the menu structure.
+       #
+       # * key: `wiki.menu`
+       # * default: `menu.html`
+       fun menu_file: String is cached do
+               return value_or_default("wiki.menu", "menu.html")
+       end
+
+       # Main footer file.
+       #
+       # The main footer is used to specify the structure of the page footer.
+       # This is generally the place where you want to put your copyright.
+       #
+       # * key: `wiki.footer`
+       # * default: `footer.html`
+       fun footer_file: String is cached do
+               return value_or_default("wiki.footer", "footer.html")
+       end
+
+       # Directory used by rsync to upload wiki files.
+       #
+       # This information is used to update your distant wiki files (like the webserver).
+       #
+       # * key: `wiki.rsync_dir`
+       # * default: ``
+       fun rsync_dir: String is cached do return value_or_default("wiki.rsync_dir", "")
+
+       # Remote repository used to pull modifications on sources.
+       #
+       # * key: `wiki.git_origin`
+       # * default: `origin`
+       fun git_origin: String is cached do return value_or_default("wiki.git_origin", "origin")
+
+       # Remote branch used to pull modifications on sources.
+       #
+       # * key: `wiki.git_branch`
+       # * default: `master`
+       fun git_branch: String is cached do return value_or_default("wiki.git_branch", "master")
+end
+
+# WikiSection custom configuration.
+#
+# Each section can provide its own config file to customize
+# appearance or behavior.
+class SectionConfig
+       super ConfigTree
+
+       # Returns the config value at `key` or `null` if no key was found.
+       private fun value_or_null(key: String): nullable String do
+               if not has_key(key) then return null
+               return self[key]
+       end
+
+       # Is this section hidden in sitemap and trees and menus?
+       fun is_hidden: Bool do return value_or_null("section.hidden") == "true"
+
+       # Custom section title if any.
+       fun title: nullable String do return value_or_null("section.title")
+
+       # Custom template file if any.
+       fun template_file: nullable String do return value_or_null("section.template")
+
+       # Custom header file if any.
+       fun header_file: nullable String do return value_or_null("section.header")
+
+       # Custom menu file if any.
+       fun menu_file: nullable String do return value_or_null("section.menu")
+
+       # Custom footer file if any.
+       fun footer_file: nullable String do return value_or_null("section.footer")
+end
diff --git a/contrib/nitiwiki/src/wiki_html.nit b/contrib/nitiwiki/src/wiki_html.nit
new file mode 100644 (file)
index 0000000..c984bc8
--- /dev/null
@@ -0,0 +1,435 @@
+# 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.
+
+# HTML wiki rendering
+module wiki_html
+
+import wiki_base
+
+redef class Nitiwiki
+
+       # Render HTML output looking for changes in the markdown sources.
+       fun render do
+               if not root_section.is_dirty and not force_render then return
+               var out_dir = expand_path(config.root_dir, config.out_dir)
+               out_dir.mkdir
+               copy_assets
+               root_section.add_child make_sitemap
+               root_section.render
+       end
+
+       # Copy the asset directory to the (public) output directory.
+       private fun copy_assets do
+               var src = expand_path(config.root_dir, config.assets_dir)
+               var out = expand_path(config.root_dir, config.out_dir)
+               if need_render(src, expand_path(out, config.assets_dir)) then
+                       if src.file_exists then sys.system "cp -R {src} {out}"
+               end
+       end
+
+       # Build the wiki sitemap page.
+       private fun make_sitemap: WikiSitemap do
+               var sitemap = new WikiSitemap(self, "sitemap")
+               sitemap.is_dirty = true
+               return sitemap
+       end
+end
+
+redef class WikiEntry
+
+       # Url to `self` once generated.
+       fun url: String do return wiki.config.root_url.join_path(breadcrumbs.join("/"))
+
+       # Get a `<a>` template link to `self`
+       fun tpl_link: Streamable do
+               return "<a href=\"{url}\">{title}</a>"
+       end
+end
+
+redef class WikiSection
+
+       # Output directory (where to ouput the HTML pages for this section).
+       redef fun out_path: String do
+               if parent == null then
+                       return wiki.config.out_dir
+               else
+                       return wiki.expand_path(parent.out_path, name)
+               end
+       end
+
+       redef fun render do
+               if not is_dirty and not wiki.force_render then return
+               if is_new then
+                       out_full_path.mkdir
+               else
+                       sys.system "touch {out_full_path}"
+               end
+               if has_source then
+                       wiki.message("Render section {out_path}", 1)
+                       copy_files
+               end
+               var index = self.index
+               if index isa WikiSectionIndex then
+                       index.is_dirty = true
+                       add_child index
+               end
+               super
+       end
+
+       # Copy attached files from `src_path` to `out_path`.
+       private fun copy_files do
+               assert has_source
+               var dir = src_full_path.to_s
+               for name in dir.files do
+                       if name == wiki.config_filename then continue
+                       if name.has_suffix(".md") then continue
+                       if has_child(name) then continue
+                       var src = wiki.expand_path(dir, name)
+                       var out = wiki.expand_path(out_full_path, name)
+                       if not wiki.need_render(src, out) then continue
+                       sys.system "cp -R {src} {out_full_path}"
+               end
+       end
+
+       # The index page for this section.
+       #
+       # If no file `index.md` exists for this section,
+       # a summary is generated using contained articles.
+       fun index: WikiArticle is cached do
+               for child in children.values do
+                       if child isa WikiArticle and child.is_index then return child
+               end
+               return new WikiSectionIndex(wiki, self)
+       end
+
+       redef fun tpl_link do return index.tpl_link
+
+       # Render the section hierarchy as a html tree.
+       #
+       # `limit` is used to specify the max-depth of the tree.
+       #
+       # The generated tree will be something like this:
+       #
+       #    <ul>
+       #     <li>section 1</li>
+       #     <li>section 2
+       #      <ul>
+       #       <li>section 2.1</li>
+       #       <li>section 2.2</li>
+       #      </ul>
+       #     </li>
+       #    </ul>
+       fun tpl_tree(limit: Int): Template do
+               return tpl_tree_intern(limit, 1)
+       end
+
+       # Build the template tree for this section recursively.
+       protected fun tpl_tree_intern(limit, count: Int): Template do
+               var out = new Template
+               var index = index
+               out.add "<li>"
+               out.add tpl_link
+               if (limit < 0 or count < limit) and
+                  (children.length > 1 or (children.length == 1)) then
+                       out.add " <ul>"
+                       for child in children.values do
+                               if child == index then continue
+                               if child isa WikiArticle then
+                                       out.add "<li>"
+                                       out.add child.tpl_link
+                                       out.add "</li>"
+                               else if child isa WikiSection and not child.is_hidden then
+                                       out.add child.tpl_tree_intern(limit, count + 1)
+                               end
+                       end
+                       out.add " </ul>"
+               end
+               out.add "</li>"
+               return out
+       end
+end
+
+redef class WikiArticle
+
+       redef fun out_path: String do
+               if parent == null then
+                       return wiki.expand_path(wiki.config.out_dir, "{name}.html")
+               else
+                       return wiki.expand_path(parent.out_path, "{name}.html")
+               end
+       end
+
+       redef fun url do
+               if parent == null then
+                       return wiki.config.root_url.join_path("{name}.html")
+               else
+                       return parent.url.join_path("{name}.html")
+               end
+       end
+
+       # Is `self` an index page?
+       #
+       # Checks if `self.name == "index"`.
+       fun is_index: Bool do return name == "index"
+
+       redef fun render do
+               if not is_dirty and not wiki.force_render then return
+               wiki.message("Render article {name}", 2)
+               var file = out_full_path
+               file.dirname.mkdir
+               tpl_page.write_to_file file
+               super
+       end
+
+
+       # Replace macros in the template by wiki data.
+       private fun tpl_page: TemplateString do
+               var tpl = wiki.load_template(template_file)
+               if tpl.has_macro("TOP_MENU") then
+                       tpl.replace("TOP_MENU", tpl_menu)
+               end
+               if tpl.has_macro("HEADER") then
+                       tpl.replace("HEADER", tpl_header)
+               end
+               if tpl.has_macro("BODY") then
+                       tpl.replace("BODY", tpl_article)
+               end
+               if tpl.has_macro("FOOTER") then
+                       tpl.replace("FOOTER", tpl_footer)
+               end
+               return tpl
+       end
+
+       # Generate the HTML header for this article.
+       fun tpl_header: Streamable do
+               var file = header_file
+               if not wiki.has_template(file) then return ""
+               return wiki.load_template(file)
+       end
+
+       # Generate the HTML page for this article.
+       fun tpl_article: TplArticle do
+               var article = new TplArticle
+               article.body = content
+               article.breadcrumbs = new TplBreadcrumbs(self)
+               tpl_sidebar.blocks.add tpl_summary
+               article.sidebar = tpl_sidebar
+               return article
+       end
+
+       # Sidebar for this page.
+       var tpl_sidebar = new TplSidebar
+
+       # Generate the HTML summary for this article.
+       #
+       # Based on `headlines`
+       fun tpl_summary: Streamable do
+               var headlines = self.headlines
+               var tpl = new Template
+               tpl.add "<ul class=\"summary list-unstyled\">"
+               var iter = headlines.iterator
+               while iter.is_ok do
+                       var hl = iter.item
+                       # parse title as markdown
+                       var title = hl.title.md_to_html.to_s
+                       title = title.substring(3, title.length - 8)
+                       tpl.add "<li><a href=\"#{hl.id}\">{title}</a>"
+                       iter.next
+                       if iter.is_ok then
+                               if iter.item.level > hl.level then
+                                       tpl.add "<ul class=\"list-unstyled\">"
+                               else if iter.item.level < hl.level then
+                                       tpl.add "</li>"
+                                       tpl.add "</ul>"
+                               end
+                       else
+                               tpl.add "</li>"
+                       end
+               end
+               tpl.add "</ul>"
+               return tpl
+       end
+
+       # Generate the HTML menu for this article.
+       fun tpl_menu: Streamable do
+               var file = menu_file
+               if not wiki.has_template(file) then return ""
+               var tpl = wiki.load_template(file)
+               if tpl.has_macro("MENUS") then
+                       var items = new Template
+                       for child in wiki.root_section.children.values do
+                               if child isa WikiArticle and child.is_index then continue
+                               if child isa WikiSection and child.is_hidden then continue
+                               items.add "<li"
+                               if self == child or self.breadcrumbs.has(child) then
+                                       items.add " class=\"active\""
+                               end
+                               items.add ">"
+                               items.add child.tpl_link
+                               items.add "</li>"
+                       end
+                       tpl.replace("MENUS", items)
+               end
+               return tpl
+       end
+
+       # Generate the HTML footer for this article.
+       fun tpl_footer: Streamable do
+               var file = footer_file
+               if not wiki.has_template(file) then return ""
+               var tpl = wiki.load_template(file)
+               var time = new Tm.gmtime
+               if tpl.has_macro("YEAR") then
+                       tpl.replace("YEAR", (time.year + 1900).to_s)
+               end
+               if tpl.has_macro("GEN_TIME") then
+                       tpl.replace("GEN_TIME", time.to_s)
+               end
+               return tpl
+       end
+end
+
+# A `WikiArticle` that contains the sitemap tree.
+class WikiSitemap
+       super WikiArticle
+
+       redef fun tpl_article do
+               var article = new TplArticle.with_title("Sitemap")
+               article.body = new TplPageTree(wiki.root_section, -1)
+               return article
+       end
+
+       redef var is_dirty = false
+end
+
+# A `WikiArticle` that contains the section index tree.
+class WikiSectionIndex
+       super WikiArticle
+
+       # The section described by `self`.
+       var section: WikiSection
+
+       init(wiki: Nitiwiki, section: WikiSection) do
+               super(wiki, "index")
+               self.section = section
+       end
+
+       redef var is_dirty = false
+
+       redef fun tpl_article do
+               var article = new TplArticle.with_title(section.title)
+               article.body = new TplPageTree(section, -1)
+               article.breadcrumbs = new TplBreadcrumbs(self)
+               return article
+       end
+end
+
+# Article HTML output.
+class TplArticle
+       super Template
+
+       var title: nullable Streamable = null
+       var body: nullable Streamable = null
+       var sidebar: nullable TplSidebar = null
+       var breadcrumbs: nullable TplBreadcrumbs = null
+
+       init with_title(title: Streamable) do
+               self.title = title
+       end
+
+       redef fun rendering do
+               if sidebar != null then
+                       add "<div class=\"col-sm-3 sidebar\">"
+                       add sidebar.as(not null)
+                       add "</div>"
+                       add "<div class=\"col-sm-9 content\">"
+               else
+                       add "<div class=\"col-sm-12 content\">"
+               end
+               if body != null then
+                       add "<article>"
+                       if breadcrumbs != null then
+                               add breadcrumbs.as(not null)
+                       end
+                       if title != null then
+                               add "<h1>"
+                               add title.as(not null)
+                               add "</h1>"
+                       end
+                       add     body.as(not null)
+                       add " </article>"
+               end
+               add "</div>"
+       end
+end
+
+# A collection of HTML blocks displayed on the side of a page.
+class TplSidebar
+       super Template
+
+       # Blocks are `Stremable` pieces that will be rendered in the sidebar.
+       var blocks = new Array[Streamable]
+
+       redef fun rendering do
+               for block in blocks do
+                       add "<div class=\"sideblock\">"
+                       add block
+                       add "</div>"
+               end
+       end
+end
+
+# An HTML breadcrumbs that show the path from a `WikiArticle` to the `Nitiwiki` root.
+class TplBreadcrumbs
+       super Template
+
+       # Bread crumb article.
+       var article: WikiArticle
+
+       redef fun rendering do
+               var path = article.breadcrumbs
+               if path.is_empty or path.length <= 2 and article.is_index then return
+               add "<ol class=\"breadcrumb\">"
+               for entry in path do
+                       if entry == path.last then
+                               add "<li class=\"active\">"
+                               add entry.title
+                               add "</li>"
+                       else
+                               if article.parent == entry and article.is_index then continue
+                               add "<li>"
+                               add entry.tpl_link
+                               add "</li>"
+                       end
+               end
+               add "</ol>"
+       end
+end
+
+# An HTML tree that show the section pages structure.
+class TplPageTree
+       super Template
+
+       # Builds the page tree from `root`.
+       var root: WikiSection
+
+       # Limits the tree depth to `max_depth` levels.
+       var max_depth: Int
+
+       redef fun rendering do
+               add "<ul>"
+               add root.tpl_tree(-1)
+               add "</ul>"
+       end
+end
diff --git a/contrib/nitiwiki/tests/Makefile b/contrib/nitiwiki/tests/Makefile
new file mode 100644 (file)
index 0000000..0dce637
--- /dev/null
@@ -0,0 +1,21 @@
+# Copyright 2013 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.
+
+all: tests
+
+tests: clean
+       ./tests.sh
+
+clean:
+       rm -rf out/
diff --git a/contrib/nitiwiki/tests/nitiwiki.args b/contrib/nitiwiki/tests/nitiwiki.args
new file mode 100644 (file)
index 0000000..b5abeca
--- /dev/null
@@ -0,0 +1 @@
+nitiwiki
diff --git a/contrib/nitiwiki/tests/nitiwiki_render.args b/contrib/nitiwiki/tests/nitiwiki_render.args
new file mode 100644 (file)
index 0000000..6f7050e
--- /dev/null
@@ -0,0 +1 @@
+nitiwiki --config wiki1/config.ini --clean --render -v
diff --git a/contrib/nitiwiki/tests/nitiwiki_status.args b/contrib/nitiwiki/tests/nitiwiki_status.args
new file mode 100644 (file)
index 0000000..aa96d13
--- /dev/null
@@ -0,0 +1 @@
+nitiwiki --config wiki1/config.ini --clean --status
diff --git a/contrib/nitiwiki/tests/res/nitiwiki.res b/contrib/nitiwiki/tests/res/nitiwiki.res
new file mode 100644 (file)
index 0000000..232b5ef
--- /dev/null
@@ -0,0 +1,2 @@
+Not in a nitiwiki directory.
+Use --init to initialize one here.
diff --git a/contrib/nitiwiki/tests/res/nitiwiki_render.res b/contrib/nitiwiki/tests/res/nitiwiki_render.res
new file mode 100644 (file)
index 0000000..adf25a7
--- /dev/null
@@ -0,0 +1 @@
+Render section out
diff --git a/contrib/nitiwiki/tests/res/nitiwiki_status.res b/contrib/nitiwiki/tests/res/nitiwiki_status.res
new file mode 100644 (file)
index 0000000..936a764
--- /dev/null
@@ -0,0 +1,10 @@
+nitiWiki
+name: wiki1
+config: wiki1/config.ini
+url: http://localhost/
+
+There is modified files:
+ + pages
+ + /pages/index.md
+
+Use nitiwiki --render to render modified files
diff --git a/contrib/nitiwiki/tests/tests.sh b/contrib/nitiwiki/tests/tests.sh
new file mode 100755 (executable)
index 0000000..2acbe94
--- /dev/null
@@ -0,0 +1,92 @@
+#!/bin/bash
+
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014 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
+
+# check args files
+test_args()
+{
+       local test="$1"
+       local args=`cat $test.args`
+       local outdir=$OUT/$test.files
+
+       echo $BIN/$args > $OUT/$test.bin
+       chmod +x $OUT/$test.bin
+       OUTDIR=$outdir $OUT/$test.bin > $OUT/$test.res 2> $OUT/$test.err
+
+       if [ -r $outdir ]; then
+               ls -aR $outdir >> $OUT/$test.res
+       fi
+
+       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 [ ! -r "$RES/$test.res" ]; then
+               return 0
+       elif [ ! -s $OUT/$test.diff ]; then
+               return 1
+       else
+               return 2
+       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 *.args`; do
+       ((all++))
+       test="${file%.*}"
+       echo -n "* $test: "
+
+       test_args $test
+       check_result $test
+
+       case "$?" in
+               0)
+                       echo "skip ($test.res not found)"
+                       ((sk++))
+                       continue;;
+               1)
+                       echo "success"
+                       ((ok++))
+                       ;;
+               2)
+                       echo "error (diff $OUT/$test.res $RES/$test.res)"
+                       ((ko++))
+                       ;;
+       esac
+done
+echo ""
+echo "==> success $ok/$all ($ko tests failed, $sk skipped)"
diff --git a/contrib/nitiwiki/tests/wiki1/assets/css/main.css b/contrib/nitiwiki/tests/wiki1/assets/css/main.css
new file mode 100644 (file)
index 0000000..d58b78d
--- /dev/null
@@ -0,0 +1 @@
+dummy { margin: 0 }
diff --git a/contrib/nitiwiki/tests/wiki1/config.ini b/contrib/nitiwiki/tests/wiki1/config.ini
new file mode 100644 (file)
index 0000000..c24bdd7
--- /dev/null
@@ -0,0 +1,2 @@
+wiki.name=wiki1
+wiki.root_dir=wiki1/
diff --git a/contrib/nitiwiki/tests/wiki1/config2.ini b/contrib/nitiwiki/tests/wiki1/config2.ini
new file mode 100644 (file)
index 0000000..4ebb07b
--- /dev/null
@@ -0,0 +1,3 @@
+wiki.name=wiki2
+wiki.desc=the one used by nit/tests.sh
+wiki.root_dir=../contrib/nitiwiki/tests/wiki1/
diff --git a/contrib/nitiwiki/tests/wiki1/pages/index.md b/contrib/nitiwiki/tests/wiki1/pages/index.md
new file mode 100644 (file)
index 0000000..cc0be1e
--- /dev/null
@@ -0,0 +1 @@
+# Hello World!
diff --git a/contrib/nitiwiki/tests/wiki1/templates/footer.html b/contrib/nitiwiki/tests/wiki1/templates/footer.html
new file mode 100644 (file)
index 0000000..7506b39
--- /dev/null
@@ -0,0 +1,10 @@
+<div class="row footer">
+       <div class="container-fluid">
+               <div class="well well-sm">
+                       <p><strong>%TITLE% &copy; %YEAR%</strong></p>
+                       <p class="text-muted"><em>last modification %GEN_TIME%</em></p>
+                       <p class="text-muted">Proudly powered by
+                               <a href="http://nitlanguage.org">nit</a>!</p>
+               </div>
+       </div>
+</div>
diff --git a/contrib/nitiwiki/tests/wiki1/templates/header.html b/contrib/nitiwiki/tests/wiki1/templates/header.html
new file mode 100644 (file)
index 0000000..1cb06bf
--- /dev/null
@@ -0,0 +1,9 @@
+<div class="container-fluid header">
+       <div class="container">
+               <div class="header">
+                       <a href="http://uqam.ca"><img src="%ROOT_URL%/%LOGO%" alt="logo" /></a>
+                       <h2>%SUBTITLE%</h2>
+                       <h1>%TITLE%</h1>
+               </div>
+       </div>
+</div>
diff --git a/contrib/nitiwiki/tests/wiki1/templates/menu.html b/contrib/nitiwiki/tests/wiki1/templates/menu.html
new file mode 100644 (file)
index 0000000..1ff832e
--- /dev/null
@@ -0,0 +1,20 @@
+<nav class="menu" role="navigation">
+       <div class="container">
+               <!-- Brand and toggle get grouped for better mobile display -->
+               <div class="navbar-header">
+                       <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
+                               <span class="sr-only">Toggle navigation</span>
+                               <span class="icon-bar"></span>
+                               <span class="icon-bar"></span>
+                               <span class="icon-bar"></span>
+                       </button>
+                       <a class="navbar-brand" href="%ROOT_URL%index.html">%TITLE%</a>
+               </div>
+               <!-- Collect the nav links, forms, and other content for toggling -->
+               <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
+                       <ul class="nav navbar-nav">
+                       %MENUS%
+                       </ul>
+               </div><!-- /.navbar-collapse -->
+       </div>
+</nav>
diff --git a/contrib/nitiwiki/tests/wiki1/templates/template.html b/contrib/nitiwiki/tests/wiki1/templates/template.html
new file mode 100644 (file)
index 0000000..6b3b126
--- /dev/null
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+       <head>
+               <meta charset="utf-8">
+               <meta http-equiv="X-UA-Compatible" content="IE=edge">
+               <meta name="viewport" content="width=device-width, initial-scale=1">
+               <title>%TITLE%</title>
+
+               <link href="%ROOT_URL%/assets/vendors/bootstrap/bootstrap-3.2.0-dist/css/bootstrap.min.css" rel="stylesheet">
+               <link href="%ROOT_URL%/assets/css/main.css" rel="stylesheet">
+
+               <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
+               <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
+               <!--[if lt IE 9]>
+               <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
+               <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+               <![endif]-->
+       </head>
+       <body>
+               %HEADER%
+               %TOP_MENU%
+               <div class="container">
+                       <div class="row">
+                               %BODY%
+                       </div>
+                       %FOOTER%
+               </div>
+
+               <script src="%ROOT_URL%/vendors/jquery/jquery-1.11.1.min.js"></script>
+               <script src="%ROOT_URL%/vendors/bootstrap/bootstrap-3.2.0-dist/js/bootstrap.min.js"></script>
+       </body>
+</html>
index 9470dd8..82ae3d1 100644 (file)
@@ -46,7 +46,6 @@ in "Java" `{
 # This is a low-level class, use `AssetManager` instead
 extern class NativeAssetManager in "Java" `{ android.content.res.AssetManager `}
        super JavaObject
-       redef type SELF: NativeAssetManager
 
        fun close in "Java" `{ recv.close(); `}
 
@@ -172,7 +171,6 @@ end
 # This is a low-level class, use `ResourcesManager` instead
 extern class NativeResources in "Java" `{ android.content.res.Resources `}
        super JavaObject
-       redef type SELF: NativeResources
 
        fun get_assets:NativeAssetManager in "Java" `{ return recv.getAssets(); `}
        fun get_color(id: Int): Int in "Java" `{ return recv.getColor((int)id); `}
@@ -283,7 +281,6 @@ end
 # An android Bitmap, get an instance using the AssetManager or the ResourceManager
 extern class NativeBitmap in "Java" `{ android.graphics.Bitmap `}
        super JavaObject
-       redef type SELF: NativeBitmap
 
        # Create a NativeBitmap from a NativeInputStream retrieved with `open` function of the AssetManager
        # Called by the AssetManager
@@ -299,7 +296,6 @@ end
 # Android AssetFileDescriptor, can be retrieve by AssetManager and used to load a sound in a SoundPool
 extern class NativeAssetFileDescriptor in "Java" `{ android.content.res.AssetFileDescriptor `}
        super JavaObject
-       redef type SELF: NativeAssetFileDescriptor
 
        fun close in "Java" `{
                try {
index e640621..366c7d4 100644 (file)
@@ -38,7 +38,6 @@ in "Java" `{
 # AudioManager of the application, used to manage the audio mode
 extern class NativeAudioManager in "Java" `{ android.media.AudioManager `}
        super JavaObject
-       redef type SELF: NativeAudioManager
 
        fun mode: Int in "Java" `{ return recv.getMode(); `}
        fun mode=(i: Int) in "Java" `{ recv.setMode((int)i); `}
@@ -61,7 +60,6 @@ end
 # This is a low-level class, use `MediaPlater` instead
 extern class NativeMediaPlayer in "Java" `{ android.media.MediaPlayer `}
        super JavaObject
-       redef type SELF: NativeMediaPlayer
 
        new in "Java" `{ return new MediaPlayer(); `}
        fun start in "Java" `{ recv.start(); `}
@@ -108,7 +106,6 @@ end
 # This is a low-level class, use `SoundPool`instead
 extern class NativeSoundPool in "Java" `{ android.media.SoundPool `}
        super JavaObject
-       redef type SELF: NativeSoundPool
 
        new(max_streams, stream_type, src_quality: Int) in "Java" `{
                return new SoundPool((int)max_streams, (int)stream_type, (int)src_quality);
index 9528e05..5a083d2 100644 (file)
@@ -31,7 +31,6 @@ in "Java" `{
 
 extern class NativeBundle in "Java" `{ android.os.Bundle `}
        super JavaObject
-       redef type SELF: NativeBundle
 
        fun clone: JavaObject in "Java" `{ return recv.clone(); `}
        fun size: Int in "Java" `{ return recv.size(); `}
index d07a817..20c85dc 100644 (file)
@@ -33,7 +33,6 @@ in "Java" `{
 
 extern class NativeIntent in "Java" `{ android.content.Intent `}
        super JavaObject
-       redef type SELF: NativeIntent
 
        fun add_category(category: JavaString) in "Java" `{ recv.addCategory(category); `}
        fun add_flags(flags: Int) in "Java" `{ recv.addFlags((int)flags); `}
index dab001d..fca03e0 100644 (file)
@@ -21,6 +21,8 @@ module platform is
        new_annotation max_api_version
        new_annotation target_api_version
        new_annotation android_manifest
+       new_annotation android_manifest_application
+       new_annotation android_manifest_activity
 end
 
 import java
index 8635f86..404aaea 100644 (file)
@@ -33,7 +33,6 @@ in "Java" `{
 
 extern class NativeSharedPreferences in "Java" `{ android.content.SharedPreferences `}
        super JavaObject
-       redef type SELF: NativeSharedPreferences
        
        fun contains(key: JavaString): Bool in "Java" `{ return recv.contains(key); `}
        fun get_all: HashMap[JavaString, JavaObject] import HashMap[JavaString, JavaObject],
@@ -113,7 +112,6 @@ end
 
 extern class NativeSharedPreferencesEditor in "Java" `{ android.content.SharedPreferences$Editor `}
        super JavaObject
-       redef type SELF: NativeSharedPreferencesEditor
 
        fun clear: NativeSharedPreferencesEditor in "Java" `{ return recv.clear(); `}
        fun commit: Bool in "Java" `{ return recv.commit(); `}
index 6226285..f56dbef 100644 (file)
@@ -26,7 +26,6 @@ import native_app_glue
 # Handle to an Android vibrator
 extern class Vibrator in "Java" `{ android.os.Vibrator `}
        super JavaObject
-       redef type SELF: Vibrator
 
        # Vibrate for `n` miliseconds
        fun vibrate(n: Int) in "Java" `{ recv.vibrate(n); `}
index afb6a1d..13891fc 100644 (file)
@@ -34,5 +34,5 @@ interface DataStore
        fun [](key: String): nullable Object is abstract
 
        # Store `value` at `key`
-       fun []=(key: String, value: Serializable) is abstract
+       fun []=(key: String, value: nullable Serializable) is abstract
 end
index fdc7e99..6c843f3 100644 (file)
--- a/lib/c.nit
+++ b/lib/c.nit
@@ -29,7 +29,6 @@ end
 # A native C array, as in a pointer to the first element of the array
 extern class NativeCArray `{ void * `}
        type E: nullable Object
-       type SELF: NativeCArray
 
        fun [](index: E): E is abstract
        fun []=(index: E, val: E) is abstract
@@ -79,7 +78,6 @@ end
 extern class NativeCIntArray `{ int* `}
        super NativeCArray
        redef type E: Int
-       redef type SELF: NativeCIntArray
 
        new(size: Int) `{ return calloc(size, sizeof(int)); `}
        redef fun [](index) `{ return recv[index]; `}
@@ -91,7 +89,6 @@ end
 redef class NativeString
        super NativeCArray
        redef type E: Char
-       redef type SELF: NativeString
 
        redef fun +(offset) `{ return recv + offset; `}
 end
index 04245a5..4ab4eab 100644 (file)
@@ -32,7 +32,6 @@ in "Java" `{
 
 extern class NativeFile in "Java" `{ java.io.File `}
        super JavaObject
-       redef type SELF: NativeFile
 
        fun can_execute: Bool in "Java" `{ return recv.canExecute(); `}
        fun can_read: Bool in "Java" `{ return recv.canRead(); `}
@@ -77,7 +76,6 @@ end
 
 extern class NativeFileInputStream in "Java" `{ java.io.FileInputStream `}
        super JavaObject
-       redef type SELF: NativeFileInputStream
 
        fun available: Int in "Java" `{
                try {
@@ -114,7 +112,6 @@ end
 
 extern class NativeFileOutputStream in "Java" `{ java.io.FileOutputStream `}
        super JavaObject
-       redef type SELF: NativeFileOutputStream
 
        fun close in "Java" `{
                try {
@@ -141,7 +138,7 @@ end
 
 extern class NativeFileDescriptor in "Java" `{ java.io.FileDescriptor `}
        super JavaObject
-       redef type SELF: NativeFileDescriptor
+
        fun sync in "Java" `{
                try{
                        recv.sync();
@@ -154,7 +151,6 @@ end
 
 extern class NativeInputStream in "Java" `{ java.io.InputStream `}
        super JavaObject
-       redef type SELF: NativeInputStream
 
        fun available: Int in "Java" `{
                try {
index 0b6a652..c3db430 100644 (file)
@@ -98,8 +98,6 @@ end
 extern class JavaString in "Java" `{ java.lang.String `}
        super JavaObject
 
-       redef type SELF: JavaString
-
        # Get the string from Java and copy it to Nit memory
        fun to_cstring: NativeString import sys, Sys.jni_env `{
                Sys sys = JavaString_sys(recv);
@@ -140,7 +138,6 @@ redef class Text
 end
 
 redef extern class JavaObject
-       type SELF: JavaObject
 
        # Returns a global reference to the Java object behind this reference
        #
index add43e1..2f675d6 100644 (file)
@@ -33,6 +33,88 @@ class MarkdownProcessor
        # `MarkdownEmitter` used for ouput.
        var emitter: MarkdownEmitter is noinit
 
+       # Work in extended mode (default).
+       #
+       # Behavior changes when using extended mode:
+       #
+       # * Lists and code blocks end a paragraph
+       #
+       #   In normal markdown the following:
+       #
+       #               This is a paragraph
+       #               * and this is not a list
+       #
+       #   Will produce:
+       #
+       #               <p>This is a paragraph
+       #               * and this is not a list</p>
+       #
+       #       When using extended mode this changes to:
+       #
+       #               <p>This is a paragraph</p>
+       #               <ul>
+       #               <li>and this is not a list</li>
+       #               </ul>
+       #
+       # * Fences code blocks
+       #
+       #   If you don't want to indent your all your code with 4 spaces,
+       #   you can wrap your code in ``` ``` ``` or `~~~`.
+       #
+       #       Here's an example:
+       #
+       #               ```
+       #               fun test do
+       #                       print "Hello World!"
+       #               end
+       #               ```
+       #
+       # * Code blocks meta
+       #
+       #   If you want to use syntax highlighting tools, most of them need to know what kind
+       #   of language they are highlighting.
+       #   You can add an optional language identifier after the fence declaration to output
+       #   it in the HTML render.
+       #
+       #               ```nit
+       #               import markdown
+       #
+       #               print "# Hello World!".md_to_html
+       #               ```
+       #
+       #   Becomes
+       #
+       #               <pre class="nit"><code>import markdown
+       #
+       #               print "Hello World!".md_to_html
+       #               </code></pre>
+       #
+       # * Underscores (Emphasis)
+       #
+       #   Underscores in the middle of a word like:
+       #
+       #               Con_cat_this
+       #
+       #       normally produces this:
+       #
+       #               <p>Con<em>cat</em>this</p>
+       #
+       #   With extended mode they don't result in emphasis.
+       #
+       #               <p>Con_cat_this</p>
+       #
+       # * Strikethrough
+       #
+       #   Like in [GFM](https://help.github.com/articles/github-flavored-markdown),
+       #   strikethrought span is marked with `~~`.
+       #
+       #               ~~Mistaken text.~~
+       #
+       #   becomes
+       #
+       #               <del>Mistaken text.</del>
+       var ext_mode = true
+
        init do self.emitter = new MarkdownEmitter(self)
 
        # Process the mardown `input` string and return the processed output.
@@ -219,12 +301,14 @@ class MarkdownProcessor
                if value[leading] == '#' then return new LineHeadline
                if value[leading] == '>' then return new LineBlockquote
 
-               if value.length - leading - trailing > 2 then
-                       if value[leading] == '`' and md.count_chars_start('`') >= 3 then
-                               return new LineFence
-                       end
-                       if value[leading] == '~' and md.count_chars_start('~') >= 3 then
-                               return new LineFence
+               if ext_mode then
+                       if value.length - leading - trailing > 2 then
+                               if value[leading] == '`' and md.count_chars_start('`') >= 3 then
+                                       return new LineFence
+                               end
+                               if value[leading] == '~' and md.count_chars_start('~') >= 3 then
+                                       return new LineFence
+                               end
                        end
                end
 
@@ -307,6 +391,14 @@ class MarkdownProcessor
                                        return new TokenEmUnderscore(pos, c)
                                end
                        end
+                       if ext_mode then
+                               if (c0.is_letter or c0.is_digit) and c0 != '_' and
+                                  (c1.is_letter or c1.is_digit) then
+                                       return new TokenNone(pos, c)
+                               else
+                                       return new TokenEmUnderscore(pos, c)
+                               end
+                       end
                        if c0 != ' ' or c1 != ' ' then
                                return new TokenEmUnderscore(pos, c)
                        else
@@ -335,13 +427,12 @@ class MarkdownProcessor
                        return new TokenHTML(pos, c)
                else if c == '&' then
                        return new TokenEntity(pos, c)
-               else if c == '^' then
-                       if c0 == '^' or c1 == '^' then
-                               return new TokenNone(pos, c)
-                       else
-                               return new TokenSuper(pos, c)
-                       end
                else
+                       if ext_mode then
+                               if c == '~' and c1 == '~' then
+                                       return new TokenStrike(pos, c)
+                               end
+                       end
                        return new TokenNone(pos, c)
                end
        end
@@ -519,8 +610,10 @@ interface Decorator
        # Render a strong text.
        fun add_strong(v: MarkdownEmitter, text: Text) is abstract
 
-       # Render a super text.
-       fun add_super(v: MarkdownEmitter, text: Text) is abstract
+       # Render a strike text.
+       #
+       # Extended mode only (see `MarkdownProcessor::ext_mode`)
+       fun add_strike(v: MarkdownEmitter, text: Text) is abstract
 
        # Render a link.
        fun add_link(v: MarkdownEmitter, link: Text, name: Text, comment: nullable Text) is abstract
@@ -594,7 +687,11 @@ class HTMLDecorator
        end
 
        redef fun add_code(v, block) do
-               v.add "<pre><code>"
+               if block isa BlockFence and block.meta != null then
+                       v.add "<pre class=\"{block.meta.to_s}\"><code>"
+               else
+                       v.add "<pre><code>"
+               end
                v.emit_in block
                v.add "</code></pre>\n"
        end
@@ -635,10 +732,10 @@ class HTMLDecorator
                v.add "</strong>"
        end
 
-       redef fun add_super(v, text) do
-               v.add "<sup>"
+       redef fun add_strike(v, text) do
+               v.add "<del>"
                v.add text
-               v.add "</sup>"
+               v.add "</del>"
        end
 
        redef fun add_image(v, link, name, comment) do
@@ -1032,6 +1129,9 @@ end
 class BlockFence
        super BlockCode
 
+       # Any string found after fence token.
+       var meta: nullable Text
+
        # Fence code lines start at 0 spaces.
        redef var line_start = 0
 end
@@ -1409,10 +1509,10 @@ class LineOther
                var was_empty = line.prev_empty
                while line != null and not line.is_empty do
                        var t = v.line_kind(line)
-                       if v.in_list and t isa LineList then
+                       if (v.in_list or v.ext_mode) and t isa LineList then
                                break
                        end
-                       if t isa LineCode or t isa LineFence then
+                       if v.ext_mode and (t isa LineCode or t isa LineFence) then
                                break
                        end
                        if t isa LineHeadline or t isa LineHeadline1 or t isa LineHeadline2 or
@@ -1551,7 +1651,8 @@ class LineFence
                else
                        block = v.current_block.split(v.current_block.last_line.as(not null))
                end
-               block.kind = new BlockFence(block)
+               var meta = block.first_line.value.meta_from_fence
+               block.kind = new BlockFence(block, meta)
                block.first_line.clear
                var last = block.last_line
                if last != null and v.line_kind(last) isa LineFence then
@@ -2038,17 +2139,19 @@ class TokenEscape
        end
 end
 
-# A markdown super token.
-class TokenSuper
+# A markdown strike token.
+#
+# Extended mode only (see `MarkdownProcessor::ext_mode`)
+class TokenStrike
        super Token
 
        redef fun emit(v) do
                var tmp = v.push_buffer
-               var b = v.emit_text_until(v.current_text.as(not null), pos + 1, self)
+               var b = v.emit_text_until(v.current_text.as(not null), pos + 2, self)
                v.pop_buffer
                if b > 0 then
-                       v.decorator.add_super(v, tmp)
-                       v.current_pos = b
+                       v.decorator.add_strike(v, tmp)
+                       v.current_pos = b + 1
                else
                        v.addc char
                end
@@ -2288,6 +2391,18 @@ redef class Text
                return pos
        end
 
+       # Extract string found at end of fence opening.
+       private fun meta_from_fence: nullable Text do
+               for i in [0..chars.length[ do
+                       var c = chars[i]
+                       print c
+                       if c != ' ' and c != '`' and c != '~' then
+                               return substring_from(i).trim
+                       end
+               end
+               return null
+       end
+
        # Is `self` an unsafe HTML element?
        private fun is_html_unsafe: Bool do return html_unsafe_tags.has(self.write_to_string)
 
index dd7fd58..a8d3b9f 100644 (file)
@@ -407,6 +407,36 @@ sit amet, consectetuer adipiscing elit.</p>
                assert res == exp
        end
 
+       fun test_process_list11 do
+               var test = """
+This is a paragraph
+* and this is not a list
+"""
+               var exp = """
+<p>This is a paragraph
+* and this is not a list</p>
+"""
+               var proc = new MarkdownProcessor
+               proc.ext_mode = false
+               var res = proc.process(test).write_to_string
+               assert res == exp
+       end
+
+       fun test_process_list_ext do
+               var test = """
+This is a paragraph
+* and this is not a list
+"""
+               var exp = """
+<p>This is a paragraph</p>
+<ul>
+<li>and this is not a list</li>
+</ul>
+"""
+               var res = test.md_to_html.write_to_string
+               assert res == exp
+       end
+
        fun test_process_code1 do
                var test = """
 This is a normal paragraph:
@@ -448,7 +478,7 @@ end tell
                assert res == exp
        end
 
-       fun test_process_code3 do
+       fun test_process_code_ext1 do
                var test = """
 Here is an example of AppleScript:
 ~~~
@@ -476,7 +506,7 @@ end tell
                assert res == exp
        end
 
-       fun test_process_code4 do
+       fun test_process_code_ext2 do
                var test = """
 Here is an example of AppleScript:
 ```
@@ -504,6 +534,49 @@ end tell
                assert res == exp
        end
 
+       fun test_process_code_ext3 do
+               var proc = new MarkdownProcessor
+               proc.ext_mode = false
+
+               var test = """
+Here is an example of AppleScript:
+    beep
+"""
+               var exp = """
+<p>Here is an example of AppleScript:
+beep</p>
+"""
+               var res = proc.process(test).write_to_string
+               assert res == exp
+       end
+
+       fun test_process_code_ext4 do
+               var test = """
+Here is an example of AppleScript:
+    beep
+"""
+               var exp = """
+<p>Here is an example of AppleScript:</p>
+<pre><code>beep
+</code></pre>
+"""
+               var res = test.md_to_html.write_to_string
+               assert res == exp
+       end
+
+       fun test_process_code_ext5 do
+               var test = """
+```nit
+print "Hello World!"
+```
+"""
+               var exp = """
+<pre class="nit"><code>print "Hello World!"
+</code></pre>
+"""
+               var res = test.md_to_html.write_to_string
+               assert res == exp
+       end
 
        fun test_process_nesting1 do
                var test = """
@@ -641,6 +714,22 @@ __double underscores__
                assert res == exp
        end
 
+       fun test_process_emph3 do
+               var proc = new MarkdownProcessor
+               proc.ext_mode = false
+               var test = "Con_cat_this"
+               var exp = "<p>Con<em>cat</em>this</p>\n"
+               var res = proc.process(test).write_to_string
+               assert res == exp
+       end
+
+       fun test_process_emph_ext do
+               var test = "Con_cat_this"
+               var exp = "<p>Con_cat_this</p>\n"
+               var res = test.md_to_html.write_to_string
+               assert res == exp
+       end
+
        fun test_process_xml1 do
                var test = """
 This is a regular paragraph.
@@ -908,6 +997,23 @@ break</a> with a line-ending space.</p>
                assert res == exp
        end
 
+       fun test_process_strike do
+               var proc = new MarkdownProcessor
+               proc.ext_mode = false
+               var test = "This is how you ~~strike text~~"
+               var exp = "<p>This is how you ~~strike text~~</p>\n"
+               var res = proc.process(test).write_to_string
+               assert exp == res
+       end
+
+       fun test_process_strike_ext do
+               var test = "This is how you ~~strike text~~"
+               var exp = "<p>This is how you <del>strike text</del></p>\n"
+               var res = test.md_to_html.write_to_string
+               assert exp == res
+       end
+
+
        fun test_daring_encoding do
                var test = """
 AT&T has an ampersand in their name.
@@ -1275,6 +1381,9 @@ Here's how you put `` `backticks` `` in a code span.
        end
 
        fun test_daring_pars do
+               var proc = new MarkdownProcessor
+               proc.ext_mode = false
+
                var test = """
 In Markdown 1.0.0 and earlier. Version
 8. This line turns into a list item.
@@ -1295,7 +1404,7 @@ list item.</p>
 <p>Here's one with a bullet.
 * criminey.</p>
 """
-               var res = test.md_to_html.write_to_string
+               var res = proc.process(test).write_to_string
                assert res == exp
        end
 
@@ -2462,10 +2571,6 @@ class TestLine
                assert v.line_kind(subject) isa LineHeadline
                subject = new MDLine("    code")
                assert v.line_kind(subject) isa LineCode
-               subject = new MDLine("  ~~~")
-               assert v.line_kind(subject) isa LineFence
-               subject = new MDLine("  ```")
-               assert v.line_kind(subject) isa LineFence
                subject = new MDLine("   Title  ")
                subject.next = new MDLine("== ")
                assert v.line_kind(subject) isa LineHeadline1
@@ -2498,6 +2603,14 @@ class TestLine
                assert v.line_kind(subject) isa LineOList
        end
 
+       fun test_line_type_ext do
+               var v = new MarkdownProcessor
+               subject = new MDLine("  ~~~")
+               assert v.line_kind(subject) isa LineFence
+               subject = new MDLine("  ```")
+               assert v.line_kind(subject) isa LineFence
+       end
+
        fun test_count_chars do
                subject = new MDLine("")
                assert subject.count_chars('*') == 0
index 725c612..87afb4a 100644 (file)
 # limitations under the License.
 
 # Impements the services of `mnit:app` using the API from the Android ndk
-module android_app
+module android_app is
+       android_manifest_activity """
+               android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+               android:configChanges="orientation|keyboardHidden"
+               android:screenOrientation="portrait""""
+end
 
 import mnit
 import android
diff --git a/lib/pthreads/extra.nit b/lib/pthreads/extra.nit
new file mode 100644 (file)
index 0000000..416b174
--- /dev/null
@@ -0,0 +1,62 @@
+# This file is part of NIT (http://www.nitlanguage.org).
+#
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Offers some POSIX threads services that are not available on all platforms
+module extra is
+       c_compiler_option("-pthread")
+       c_linker_option("-pthread")
+end
+
+intrude import pthreads
+
+in "C" `{
+       // TODO protect with: #ifdef WITH_LIBGC
+       #ifndef ANDROID
+               #define GC_THREADS
+               #include <gc.h>
+       #endif
+`}
+
+redef extern class NativePthread
+       fun cancel: Bool `{
+               return pthread_cancel(*recv);
+       `}
+end
+
+redef class Thread
+       # Cancel the execution of the thread
+       fun cancel
+       do
+               if native == null then return
+               native.cancel
+               native = null
+       end
+end
+
+# Does not return if the running thread is to be cancelled
+fun test_cancel `{ pthread_testcancel(); `}
+
+private extern class NativePthreadBarrier in "C" `{ pthread_barrier_t * `}
+       new(count: Int) `{
+               pthread_barrier_t *barrier = malloc(sizeof(pthread_barrier_t));
+               int res = pthread_barrier_init(barrier, NULL, count);
+               return barrier;
+       `}
+
+       fun destroy `{ pthread_barrier_destroy(recv); `}
+
+       fun wait `{ pthread_barrier_wait(recv); `}
+end
index 1e76e28..94cf078 100644 (file)
@@ -34,9 +34,10 @@ in "C" `{
        // TODO protect with: #ifdef WITH_LIBGC
        // We might have to add the next line to gc_chooser.c too, especially
        // if we get an error like "thread not registered with GC".
+       #ifndef ANDROID
                #define GC_THREADS
                #include <gc.h>
-       //#endif
+       #endif
 `}
 
 redef class Sys
@@ -114,10 +115,6 @@ private extern class NativePthread in "C" `{ pthread_t * `}
                return (nullable_Object)thread_return;
        `}
 
-       fun cancel: Bool `{
-               return pthread_cancel(*recv);
-       `}
-
        fun attr: NativePthreadAttr `{
                pthread_attr_t *pattr = malloc(sizeof(pthread_attr_t));
                pthread_getattr_np(*recv, pattr);
@@ -192,18 +189,6 @@ private extern class NativePthreadMutexAttr in "C" `{ pthread_mutexattr_t * `}
        # pthread_mutexattr_setrobust_np
 end
 
-private extern class NativePthreadBarrier in "C" `{ pthread_barrier_t * `}
-       new(count: Int) `{
-               pthread_barrier_t *barrier = malloc(sizeof(pthread_barrier_t));
-               int res = pthread_barrier_init(barrier, NULL, count);
-               return barrier;
-       `}
-
-       fun destroy `{ pthread_barrier_destroy(recv); `}
-
-       fun wait `{ pthread_barrier_wait(recv); `}
-end
-
 private extern class NativePthreadKey in "C" `{ pthread_key_t * `}
        new `{
                pthread_key_t *key = malloc(sizeof(pthread_key_t));
@@ -222,6 +207,27 @@ private extern class NativePthreadKey in "C" `{ pthread_key_t * `}
        `}
 end
 
+private extern class NativePthreadCond in "C" `{ pthread_cond_t * `}
+       new `{
+               pthread_cond_t cond;
+               int r = pthread_cond_init(&cond, NULL);
+               if (r == 0) {
+                       pthread_cond_t *pcond = malloc(sizeof(pthread_cond_t));
+                       memmove(pcond, &cond, sizeof(pthread_cond_t));
+                       return pcond;
+               }
+               return NULL;
+       `}
+
+       fun destroy `{ pthread_cond_destroy(recv); `}
+
+       fun signal `{ pthread_cond_signal(recv); `}
+
+       fun broadcast `{ pthread_cond_broadcast(recv);  `}
+
+       fun wait(mutex: NativePthreadMutex) `{ pthread_cond_wait(recv, mutex); `}
+end
+
 #
 ## Nity part
 #
@@ -272,14 +278,6 @@ abstract class Thread
                return r
        end
 
-       # Cancel the execution of the thread
-       fun cancel
-       do
-               if native == null then return
-               native.cancel
-               native = null
-       end
-
        redef fun finalize
        do
                if native == null then return
@@ -298,9 +296,6 @@ end
 # Exit current thread and return `value` to caller of `Thread::join`
 fun exit_thread(value: nullable Object) `{ pthread_exit(value); `}
 
-# Does not return if the running thread is to be cancelled
-fun test_cancel `{ pthread_testcancel(); `}
-
 # Returns the handle to the running `Thread`
 fun thread: Thread
 do
@@ -364,24 +359,36 @@ end
 class Barrier
        super Finalizable
 
+       private var mutex = new Mutex
+       private var cond: nullable NativePthreadCond = new NativePthreadCond
+
        # Number of threads that must be waiting for `wait` to unblock
        var count: Int
 
-       private var native: nullable NativePthreadBarrier is noinit
-
-       init do native = new NativePthreadBarrier(count)
+       private var threads_waiting = 0
 
        # Wait at this barrier and block until there are a `count` threads waiting
-       fun wait do native.wait
+       fun wait
+       do
+               mutex.lock
+               threads_waiting += 1
+               if threads_waiting == count then
+                       threads_waiting = 0
+                       cond.broadcast
+               else
+                       cond.wait(mutex.native.as(not null))
+               end
+               mutex.unlock
+       end
 
        redef fun finalize
        do
-               var native = self.native
-               if native != null then
-                       native.destroy
-                       native.free
+               var cond = self.cond
+               if cond != null then
+                       cond.destroy
+                       cond.free
                end
-               self.native = null
+               self.cond = null
        end
 end
 
index 2e9c9ae..a179e9a 100644 (file)
@@ -30,6 +30,28 @@ import end # Mark this module is a top level one. (must be only one)
 #
 # Currently, Object is also used to collect all top-level methods.
 interface Object
+       # Type of this instance, automatically specialized in every class
+       #
+       # A common use case of the virtual type `SELF` is to type an attribute and
+       # store another instance of the same type as `self`. It can also be used as as
+       # return type to a method producing a copy of `self` or returning an instance
+       # expected to be the exact same type as self.
+       #
+       # This virtual type must be used with caution as it can hinder specialization.
+       # In fact, it imposes strict restrictions on all sub-classes and their usage.
+       # For example, using `SELF` as a return type of a method `foo`
+       # forces all subclasses to ensure that `foo` returns the correct and updated
+       # type.
+       # A dangerous usage take the form of a method typed by `SELF` which creates
+       # and returns a new instance.
+       # If not correctly specialized, this method would break when invoked on a
+       # sub-class.
+       #
+       # A general rule for safe usage of `SELF` is to ensure that inputs typed
+       # `SELF` are stored in attributes typed `SELF` and returned by methods typed
+       # `SELF`, pretty much the same things as you would do with parameter types.
+       type SELF: Object
+
        # The unique object identifier in the class.
        # Unless specific code, you should not use this method.
        # The identifier is used internally to provide a hash value.
@@ -331,6 +353,21 @@ universal Float
                end
        end
 
+       # Compare float numbers with a given precision.
+       #
+       # Because of the loss of precision in floating numbers,
+       # the `==` method is often not the best way to compare them.
+       #
+       # ~~~
+       # assert 0.01.is_approx(0.02, 0.1)   == true
+       # assert 0.01.is_approx(0.02, 0.001) == false
+       # ~~~
+       fun is_approx(other, precision: Float): Bool
+       do
+               assert precision >= 0.0
+               return self <= other + precision and self >= other - precision
+       end
+
        redef fun max(other)
        do
                if self < other then
index 2bbb6aa..6214a65 100644 (file)
@@ -138,6 +138,14 @@ redef class Float
        #     assert 2.0.floor == 2.0
        #     assert (-1.5).floor == -2.0
        fun floor: Float `{ return floor(recv); `}
+
+       # Rounds the value of a float to its nearest integer value
+       #
+       #     assert 1.67.round == 2.0
+       #     assert 1.34.round == 1.0
+       #     assert -1.34.round == -1.0
+       #     assert -1.67.round == -2.0
+       fun round: Float is extern "round"
        
        # Returns a random `Float` in `[0.0 .. self[`.
        fun rand: Float is extern "kernel_Float_Float_rand_0"
index d05663e..6626036 100644 (file)
@@ -42,7 +42,7 @@ end
 class TreeNode[K: Comparable, E]
 
        # TreeNode type
-       type SELF: TreeNode[K, E]
+       type N: TreeNode[K, E]
 
        # `key` for this node
        var key: K
@@ -51,7 +51,7 @@ class TreeNode[K: Comparable, E]
        var value: E
 
        # Direct parent of this node (null if the node is root)
-       var parent: nullable SELF = null is writable
+       var parent: nullable N = null is writable
 
        redef fun to_s do return "\{{value or else ""}\}"
 
index b689700..4c647b3 100644 (file)
@@ -370,38 +370,38 @@ class BinTreeNode[K: Comparable, E]
        private var prev: nullable BinTreeNode[K, E]
        private var next: nullable BinTreeNode[K, E]
 
-       redef type SELF: BinTreeNode[K, E]
+       redef type N: BinTreeNode[K, E]
 
        init(key: K, item: E) do
                super(key, item)
        end
 
-       private var left_node: nullable SELF = null
+       private var left_node: nullable N = null
 
        # `left` tree node child (null if node has no left child)
-       fun left: nullable SELF do return left_node
+       fun left: nullable N do return left_node
 
        # set `left` child for this node (or null if left no child)
        # ENSURE: node.key < key (only if node != null)
-       fun left=(node: nullable SELF) do
+       fun left=(node: nullable N) do
                #assert node != null implies node.key < key
                left_node = node
        end
 
-       private var right_node: nullable SELF = null
+       private var right_node: nullable N = null
 
        # `right` tree node child (null if node has no right child)
-       fun right: nullable SELF do return right_node
+       fun right: nullable N do return right_node
 
        # set `right` child for this node (or null if right no child)
        # ENSURE: node.key < key (only if node != null)
-       fun right=(node: nullable SELF) do
+       fun right=(node: nullable N) do
                #assert node != null implies node.key > key
                right_node = node
        end
 
        # `parent` of the `parent` of this node (null if root)
-       fun grandparent: nullable SELF do
+       fun grandparent: nullable N do
                if parent == null then
                        return null
                else
@@ -411,7 +411,7 @@ class BinTreeNode[K: Comparable, E]
 
        # Other child of the `grandparent`
        # `left` or `right` depends on the position of the current node against its parent
-       fun uncle: nullable SELF do
+       fun uncle: nullable N do
                var g = grandparent
                if g == null then
                        return null
@@ -426,7 +426,7 @@ class BinTreeNode[K: Comparable, E]
 
        # Other child of the parent
        # `left` or `right` depends on the position of the current node against its parent
-       fun sibling: nullable SELF do
+       fun sibling: nullable N do
                if parent == null then
                        return null
                else if self == parent.left then
index ce17feb..e1fe413 100644 (file)
@@ -130,7 +130,7 @@ end
 class RBTreeNode[K: Comparable, E]
        super BinTreeNode[K, E]
 
-       redef type SELF: RBTreeNode[K, E]
+       redef type N: RBTreeNode[K, E]
 
        # Is the node red?
        private var is_red = true
index cc24d6e..ad34b9a 100644 (file)
@@ -44,6 +44,9 @@ class AndroidProject
        # Custom lines to add to the AndroidManifest.xml in the <application> node
        var manifest_application_lines = new Array[String]
 
+       # Custom lines to add to AndroidManifest.xml as attributes inside the <activity> node
+       var manifest_activity_attributes = new Array[String]
+
        # Minimum API level required for the application to run
        var min_api: nullable Int = null
 
@@ -107,6 +110,9 @@ redef class ModelBuilder
                annots = collect_annotations_on_modules("android_manifest_application", mmodule)
                for an in annots do project.manifest_application_lines.add an.arg_as_string(self) or else ""
 
+               annots = collect_annotations_on_modules("android_manifest_activity", mmodule)
+               for an in annots do project.manifest_activity_attributes.add an.arg_as_string(self) or else ""
+
                # Get the date and time (down to the minute) as string
                var local_time = new Tm.localtime
                var local_time_s = local_time.strftime("%y%m%d%H%M")
index 55468fd..1cf6ba8 100644 (file)
@@ -124,10 +124,12 @@ class AndroidToolchain
 
                # Also copy over the java files
                dir = "{android_project_root}/src/"
-               var extra_java_files = compiler.mainmodule.extra_java_files
-               if extra_java_files != null then for file in extra_java_files do
-                       var path = file.filename
-                       path.file_copy_to("{dir}/{path.basename("")}")
+               for mmodule in compiler.mainmodule.in_importation.greaters do
+                       var extra_java_files = mmodule.extra_java_files
+                       if extra_java_files != null then for file in extra_java_files do
+                               var path = file.filename
+                               path.file_copy_to(dir/path.basename(""))
+                       end
                end
 
                ## Generate delagating makefile
@@ -178,13 +180,11 @@ $(call import-module,android/native_app_glue)
              This will take care of integrating with our NDK code. -->
         <activity android:name="android.app.NativeActivity"
                 android:label="@string/app_name"
-                android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
-                android:configChanges="orientation|keyboardHidden"
-                android:screenOrientation="portrait"
+                {{{project.manifest_activity_attributes.join("\n")}}}
                 {{{icon_declaration}}}>
-            <!-- Tell NativeActivity the name of or .so -->
-            <meta-data android:name=\"{{{app_package}}}\"
-                    android:value=\"{{{app_name}}}\" />
+            <!-- Tell NativeActivity the name of our .so -->
+            <meta-data android:name=\"android.app.lib_name\"
+                    android:value=\"main\" />
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
index 4141f75..0a0e60e 100644 (file)
@@ -16,7 +16,7 @@
 module doc_model
 
 import model_utils
-import markdown
+import docdown
 import doc_templates
 import ordered_tree
 import model_ext
similarity index 99%
rename from src/markdown.nit
rename to src/docdown.nit
index e254480..7df46fe 100644 (file)
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 # Transform Nit verbatim documentation into HTML
-module markdown
+module docdown
 
 private import parser
 import html
index 2879ac9..476608c 100644 (file)
@@ -901,6 +901,8 @@ redef class AMethPropdef
                                return v.bool_instance(args[0].to_f.is_nan)
                        else if pname == "is_inf_extern" then
                                return v.bool_instance(args[0].to_f.is_inf != 0)
+                       else if pname == "round" then
+                               return v.float_instance(args[0].to_f.round)
                        end
                else if cname == "NativeString" then
                        if pname == "new" then
index cb56d38..81b3245 100644 (file)
@@ -54,6 +54,7 @@ redef class ModelBuilder
                        build_properties(mclassdef2nclassdef[superclassdef])
                end
 
+               mclassdef.build_self_type(self, nclassdef)
                for nclassdef2 in nclassdef.all_defs do
                        for npropdef in nclassdef2.n_propdefs do
                                npropdef.build_property(self, mclassdef)
@@ -310,6 +311,50 @@ redef class MClassDef
        # What is the `APropdef` associated to a `MProperty`?
        # Used to check multiple definition of a property.
        var mprop2npropdef: Map[MProperty, APropdef] = new HashMap[MProperty, APropdef]
+
+       # Build the virtual type `SELF` only for introduction `MClassDef`
+       fun build_self_type(modelbuilder: ModelBuilder, nclassdef: AClassdef)
+       do
+               if not is_intro then return
+
+               var name = "SELF"
+               var mprop = modelbuilder.try_get_mproperty_by_name(nclassdef, self, name)
+
+               # If SELF type is declared nowherer?
+               if mprop == null then return
+
+               # SELF is not a virtual type? it is weird but we ignore it
+               if not mprop isa MVirtualTypeProp then return
+
+               # Is this the intro of SELF in the library?
+               var intro = mprop.intro
+               var intro_mclassdef = intro.mclassdef
+               if intro_mclassdef == self then
+                       var nintro = modelbuilder.mpropdef2npropdef[intro]
+
+                       # SELF must be declared in Object, otherwise this will create conflicts
+                       if intro_mclassdef.mclass.name != "Object" then
+                               modelbuilder.error(nintro, "Error: the virtual type SELF must be declared in Object.")
+                       end
+
+                       # SELF must be public
+                       if mprop.visibility != public_visibility then
+                               modelbuilder.error(nintro, "Error: the virtual type SELF must be public.")
+                       end
+
+                       # SELF must not be fixed
+                       if intro.is_fixed then
+                               modelbuilder.error(nintro, "Error: the virtual type SELF cannot be fixed.")
+                       end
+
+                       return
+               end
+
+               # This class introduction inherits a SELF
+               # We insert an artificial property to update it
+               var mpropdef = new MVirtualTypeDef(self, mprop, self.location)
+               mpropdef.bound = mclass.mclass_type
+       end
 end
 
 redef class APropdef
index c546c2e..71a8669 100644 (file)
@@ -17,7 +17,7 @@ module test_docdown
 
 import modelize
 import highlight
-import markdown
+import docdown
 
 redef class ModelBuilder
        fun test_markdown(page: HTMLTag, mmodule: MModule)
index 7bb39ca..e612902 100644 (file)
@@ -16,7 +16,7 @@
 module testing_doc
 
 import testing_base
-intrude import markdown
+intrude import docdown
 
 # Extractor, Executor and Reporter for the tests in a module
 class NitUnitExecutor
index 24c0604..cdd5559 100644 (file)
@@ -1,5 +1,5 @@
 
-PROGS=*.nit ../examples/*.nit ../examples/leapfrog/leapfrog.nit ../examples/shoot/shoot_logic.nit ../contrib/pep8analysis/src/pep8analysis ../lib/*.nit ../src/nitdoc.nit ../src/test_parser.nit ../src/nit.nit ../src/nitmetrics.nit ../src/nitg.nit
+PROGS=*.nit ../examples/*.nit ../examples/leapfrog/leapfrog.nit ../examples/shoot/shoot_logic.nit ../contrib/pep8analysis/src/pep8analysis ../contrib/nitiwiki/src/nitiwiki ../lib/*.nit ../src/nitdoc.nit ../src/test_parser.nit ../src/nit.nit ../src/nitmetrics.nit ../src/nitg.nit
 
 all: niti nitg-g nitg-s
 
diff --git a/tests/base_self_type.nit b/tests/base_self_type.nit
new file mode 100644 (file)
index 0000000..7c5d204
--- /dev/null
@@ -0,0 +1,58 @@
+# 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 kernel
+
+class X
+       fun foo: SELF do return self
+       fun bar(o: SELF) do o.output_class_name
+end
+
+class Y
+       super X
+
+#alt1# redef fun foo do return new X
+#alt2# redef fun foo do return new Y
+end
+
+class A[E]
+       fun foo: Object do return new G[SELF]
+end
+
+class B[F]
+       super A[F]
+end
+
+class G[E:A[nullable Object]]
+end
+
+var x = new X
+x.output_class_name
+x.foo.output_class_name
+x.bar x
+
+var y = new Y
+y.output_class_name
+y.foo.output_class_name
+x.bar y
+y.bar y
+#alt3# y.bar x
+
+var a = new A[Int]
+a.output_class_name
+a.foo.output_class_name
+
+var b = new B[Bool]
+b.output_class_name
+b.foo.output_class_name
index 778d0d3..dbd92f9 100755 (executable)
@@ -14,4 +14,5 @@ printf "%s\n" "$@" \
        ../lib/*/examples/*.nit \
        ../contrib/friendz/src/solver_cmd.nit \
        ../contrib/pep8analysis/src/pep8analysis.nit \
+       ../contrib/nitiwiki/src/nitiwiki.nit \
        *.nit
diff --git a/tests/nitiwiki.args b/tests/nitiwiki.args
new file mode 100644 (file)
index 0000000..ae082af
--- /dev/null
@@ -0,0 +1,2 @@
+nitiwiki --config ../contrib/nitiwiki/tests/wiki1/config2.ini --clean --status
+nitiwiki --config ../contrib/nitiwiki/tests/wiki1/config2.ini --clean --render -v
diff --git a/tests/sav/base_self_type.res b/tests/sav/base_self_type.res
new file mode 100644 (file)
index 0000000..4c75853
--- /dev/null
@@ -0,0 +1,11 @@
+X
+X
+X
+Y
+Y
+Y
+Y
+A[Int]
+G[A[Int]]
+B[Bool]
+G[B[Bool]]
diff --git a/tests/sav/base_self_type_alt1.res b/tests/sav/base_self_type_alt1.res
new file mode 100644 (file)
index 0000000..467d315
--- /dev/null
@@ -0,0 +1 @@
+alt/base_self_type_alt1.nit:25,25--29: Type error: expected SELF, got X
diff --git a/tests/sav/base_self_type_alt2.res b/tests/sav/base_self_type_alt2.res
new file mode 100644 (file)
index 0000000..4c75853
--- /dev/null
@@ -0,0 +1,11 @@
+X
+X
+X
+Y
+Y
+Y
+Y
+A[Int]
+G[A[Int]]
+B[Bool]
+G[B[Bool]]
diff --git a/tests/sav/base_self_type_alt3.res b/tests/sav/base_self_type_alt3.res
new file mode 100644 (file)
index 0000000..78888be
--- /dev/null
@@ -0,0 +1 @@
+alt/base_self_type_alt3.nit:50,7: Type error: expected Y, got X
index 30a32a5..695948f 100644 (file)
@@ -1 +1 @@
-../lib/standard/kernel.nit:79,1--95,3: Fatal error: kernel#Sys does not specialize module_0#Object. Possible duplication of the root class `Object`?
+../lib/standard/kernel.nit:101,1--117,3: Fatal error: kernel#Sys does not specialize module_0#Object. Possible duplication of the root class `Object`?
diff --git a/tests/sav/nitg-e/base_self_type.res b/tests/sav/nitg-e/base_self_type.res
new file mode 100644 (file)
index 0000000..3c20b42
--- /dev/null
@@ -0,0 +1,11 @@
+X
+X
+X
+Y
+Y
+Y
+Y
+A
+G
+B
+G
diff --git a/tests/sav/nitg-e/base_self_type_alt2.res b/tests/sav/nitg-e/base_self_type_alt2.res
new file mode 100644 (file)
index 0000000..3c20b42
--- /dev/null
@@ -0,0 +1,11 @@
+X
+X
+X
+Y
+Y
+Y
+Y
+A
+G
+B
+G
index 56621b6..980ce17 100644 (file)
@@ -1,4 +1,4 @@
-Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:374)
+Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:411)
 11
 21
 31
index 56621b6..980ce17 100644 (file)
@@ -1,4 +1,4 @@
-Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:374)
+Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:411)
 11
 21
 31
index 56621b6..980ce17 100644 (file)
@@ -1,4 +1,4 @@
-Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:374)
+Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:411)
 11
 21
 31
diff --git a/tests/sav/nitiwiki.res b/tests/sav/nitiwiki.res
new file mode 100644 (file)
index 0000000..232b5ef
--- /dev/null
@@ -0,0 +1,2 @@
+Not in a nitiwiki directory.
+Use --init to initialize one here.
diff --git a/tests/sav/nitiwiki_args1.res b/tests/sav/nitiwiki_args1.res
new file mode 100644 (file)
index 0000000..3f5641d
--- /dev/null
@@ -0,0 +1,10 @@
+nitiWiki
+name: wiki2
+config: ../contrib/nitiwiki/tests/wiki1/config2.ini
+url: http://localhost/
+
+There is modified files:
+ + pages
+ + /pages/index.md
+
+Use nitiwiki --render to render modified files
diff --git a/tests/sav/nitiwiki_args2.res b/tests/sav/nitiwiki_args2.res
new file mode 100644 (file)
index 0000000..adf25a7
--- /dev/null
@@ -0,0 +1 @@
+Render section out
index af28f14..e248df6 100644 (file)
@@ -41,7 +41,7 @@ Bool [
 Object -> Bool [dir=back arrowtail=open style=dashed];
 
 Float [
- label = "{Float||}"
+ label = "{Float||+ is_approx(other: Float, precision: Float): Bool\l}"
 ]
 Numeric -> Float [dir=back arrowtail=open style=dashed];
 
index 6189897..9a7b30f 100644 (file)
@@ -41,7 +41,7 @@ Bool [
 Object -> Bool [dir=back arrowtail=open style=dashed];
 
 Float [
- label = "{Float||}"
+ label = "{Float||+ is_approx(other: Float, precision: Float): Bool\l}"
 ]
 Numeric -> Float [dir=back arrowtail=open style=dashed];
 
index aa38be4..9533b69 100644 (file)
@@ -1 +1 @@
-Runtime error: Assert failed (../lib/c.nit:64)
+Runtime error: Assert failed (../lib/c.nit:63)
index 95d7a74..17a3dc5 100644 (file)
@@ -1,2 +1,2 @@
-Runtime error: Assert failed (../lib/c.nit:57)
+Runtime error: Assert failed (../lib/c.nit:56)
 0
index 95d7a74..17a3dc5 100644 (file)
@@ -1,2 +1,2 @@
-Runtime error: Assert failed (../lib/c.nit:57)
+Runtime error: Assert failed (../lib/c.nit:56)
 0
index 3bb984c..fdc9a45 100644 (file)
@@ -1,4 +1,4 @@
-Runtime error: Assert failed (../lib/c.nit:56)
+Runtime error: Assert failed (../lib/c.nit:55)
 0
 0
 1