Just enough cleaning to get rid of all those warnings.
Pull-Request: #921
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>
tests/*.xml
tests/nitunit
+nitunit.xml
+
*.stub.nit.[ch]
.metadata/*
end
redef class Array[E]
- super StringCapable
-
redef fun to_s: String do
var l = length
var its = _items
na[i] = tmp
i += 1
end
- var ns = calloc_string(sl + 1)
+ var ns = new NativeString(sl + 1)
ns[sl] = '\0'
i = 0
var off = 0
redef class Object
# Factorize cast
- fun json_as_a: Array[nullable Object] do return self.as(Array[nullable Object])
+ fun json_as_a: JsonArray do return self.as(JsonArray)
# Factorize cast
- fun json_as_map: Map[String, nullable Object] do return self.as(Map[String, nullable Object])
+ fun json_as_map: JsonObject do return self.as(JsonObject)
end
redef class GithubCurl
# Get a given pull request (PR)
- fun getpr(number: Int): Map[String, nullable Object]
+ fun getpr(number: Int): JsonObject
do
var pr = get_and_check("https://api.github.com/repos/privat/nit/pulls/{number}")
var prm = pr.json_as_map
end
# Get reviewers of a PR
- fun getrev(pr: Map[String, nullable Object]): Array[String]
+ fun getrev(pr: JsonObject): Array[String]
do
var number = pr["number"].as(Int)
var user = pr["user"].json_as_map["login"].as(String)
import github_api
-# The proprieties introduced by this redef are to be used only on HashMap
+# The proprieties introduced by this redef are to be used only on a JSON object
# representing a Github repository.
-redef class HashMap[K, V]
+redef class JsonObject
# The repository has at least 50% Java code
fun has_lots_of_java: Bool
do
- assert self isa HashMap[String, nullable Object]
var java_count = 0
if keys.has("Java") then java_count = self["Java"].as(Int)
# The repository has at least 100 lines of C code
fun has_some_c: Bool
do
- assert self isa HashMap[String, nullable Object]
var c_count = 0
if keys.has("C") then c_count = self["C"].as(Int)
return c_count > 100
loop
# Get a page of the main query
var uri = "https://api.github.com/search/repositories?q={main_query}&page={page}&per_page={per_page}&sort=stars"
- var obj = curl.get_and_check(uri).as(HashMap[String, nullable Object])
+ var obj = curl.get_and_check(uri).as(JsonObject)
# Main object has "total_count" and "items"
- var items = obj["items"].as(Array[nullable Object])
+ var items = obj["items"].as(JsonArray)
# "items" is an array of Json objects
for item in items do
- assert item isa HashMap[String, nullable Object]
+ assert item isa JsonObject
# Each item has "name" and "languages_url"
assert item.keys.has("name")
# Download the language list
var lang_url = item["languages_url"].as(String)
- var langs = curl.get_and_check(lang_url).as(HashMap[String, nullable Object])
+ var langs = curl.get_and_check(lang_url).as(JsonObject)
# The project is of interest if it has lots of Java and at least some C
var may_be_of_interest = langs.has_lots_of_java and langs.has_some_c
exp = e d+;
e = ('e'|'E') ('+'|'-')?;
-string = '"' (Any-'\\'-'"' | '\\'Any)* '"';
+hexdig = '0'..'9' | 'a'..'z' | 'A'..'Z';
+string = '"' (Any - '\\' - '"' | '\\' (
+ '\\' |
+ '"' |
+ '/' |
+ 'b' |
+ 'f' |
+ 'n' |
+ 'r' |
+ 't' |
+ 'u' hexdig hexdig hexdig hexdig
+ ))* '"';
blank = (' '|'\n'|'\t')+;
--- /dev/null
+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
--- /dev/null
+# 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% © %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.
--- /dev/null
+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
--- /dev/null
+# Welcome on nitiwiki
--- /dev/null
+<footer>
+ <p>%TITLE% © %YEAR%</p>
+ <p>last modification %GEN_TIME%</p>
+</footer>
--- /dev/null
+<header>
+ <a href="#"><img src="%ROOT_URL%/%LOGO%" alt="logo"/></a>
+ <h2>%SUBTITLE%</h2>
+ <h1>%TITLE%</h1>
+</header>
--- /dev/null
+<nav class="menu">
+ <ul class="nav navbar-nav">
+ %MENUS%
+ </ul>
+</nav>
--- /dev/null
+<!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>
--- /dev/null
+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; }
--- /dev/null
+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/
--- /dev/null
+../../../README.md
\ No newline at end of file
--- /dev/null
+<footer class="row">
+ <div class="container-fluid">
+ <div class="well well-sm">
+ <p><strong>%TITLE% © %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>
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<!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>
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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/
--- /dev/null
+nitiwiki --config wiki1/config.ini --clean --render -v
--- /dev/null
+nitiwiki --config wiki1/config.ini --clean --status
--- /dev/null
+Not in a nitiwiki directory.
+Use --init to initialize one here.
--- /dev/null
+Render section out
--- /dev/null
+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
--- /dev/null
+#!/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)"
--- /dev/null
+dummy { margin: 0 }
--- /dev/null
+wiki.name=wiki1
+wiki.root_dir=wiki1/
--- /dev/null
+wiki.name=wiki2
+wiki.desc=the one used by nit/tests.sh
+wiki.root_dir=../contrib/nitiwiki/tests/wiki1/
--- /dev/null
+# Hello World!
--- /dev/null
+<div class="row footer">
+ <div class="container-fluid">
+ <div class="well well-sm">
+ <p><strong>%TITLE% © %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>
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<!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>
module ab
var words = gets.split(" ")
+if words.length != 2 then
+ print "Expected two numbers"
+ return
+end
print words[0].to_i + words[1].to_i
# 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(); `}
# 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); `}
# 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
# 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 {
# 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); `}
# 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(); `}
# 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);
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(); `}
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); `}
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
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],
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(); `}
# 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); `}
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
# 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
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]; `}
redef class NativeString
super NativeCArray
redef type E: Char
- redef type SELF: NativeString
redef fun +(offset) `{ return recv + offset; `}
end
# A counter counts occurrences of things
# Use this instead of a `HashMap[E, Int]`
+#
+# ~~~
+# var c = new Counter[String].from(["a", "a", "b", "b", "b", "c"])
+# assert c["a"] == 2
+# assert c["b"] == 3
+# assert c["c"] == 1
+# assert c["d"] == 0
+# ~~~
+#
+# The counter class can also be used to gather statistical informations.
+#
+# ~~~~
+# assert c.length == 3 # because 3 distinct values
+# assert c.max == "b" # because "b" has the most count (3)
+# assert c.avg == 2.0 # because it is the mean of the counts
+# ~~~~
class Counter[E: Object]
super Map[E, Int]
# Total number of counted occurrences
+ #
+ # ~~~
+ # var c = new Counter[String]
+ # assert c.sum == 0
+ # c.inc_all(["a", "a", "b", "b", "b", "c"])
+ # assert c.sum == 6
+ # ~~~
var sum: Int = 0
private var map = new HashMap[E, Int]
sum += 1
end
+ # Count one more for each element of `es`
+ fun inc_all(es: Collection[E])
+ do
+ for e in es do inc(e)
+ end
+
+ # A new Counter initialized with `inc_all`.
+ init from(es: Collection[E])
+ do
+ inc_all(es)
+ end
+
# Return an array of elements sorted by occurrences
+ #
+ # ~~~
+ # var c = new Counter[String].from(["a", "a", "b", "b", "b", "c"])
+ # assert c.sort == ["c", "a", "b"]
+ # ~~~
fun sort: Array[E]
do
var res = map.keys.to_a
# @toimplement by default just call `to_s` on the element
protected fun element_to_s(e: E): String
do
- do return e.to_s
+ return e.to_s
end
# Display statistical information
end
end
- # Return the element with the highest value
+ # Return the element with the highest value (aka. the mode)
+ #
+ # ~~~
+ # var c = new Counter[String].from(["a", "a", "b", "b", "b", "c"])
+ # assert c.max == "b"
+ # ~~~
+ #
+ # If more than one max exists, the first one is returned.
fun max: nullable E do
var max: nullable Int = null
var elem: nullable E = null
end
# Return the couple with the lowest value
+ #
+ # ~~~
+ # var c = new Counter[String].from(["a", "a", "b", "b", "b", "c"])
+ # assert c.min == "c"
+ # ~~~
+ #
+ # If more than one min exists, the first one is returned.
fun min: nullable E do
var min: nullable Int = null
var elem: nullable E = null
return elem
end
- # Values average
+ # Values average (aka. arithmetic mean)
+ #
+ # ~~~
+ # var c = new Counter[String].from(["a", "a", "b", "b", "b", "c"])
+ # assert c.avg == 2.0
+ # ~~~
fun avg: Float do
if values.is_empty then return 0.0
return (sum / values.length).to_f
end
# The standard derivation of the counter values
+ #
+ # ~~~
+ # var c = new Counter[String].from(["a", "a", "b", "b", "b", "c"])
+ # assert c.std_dev > 0.81
+ # assert c.std_dev < 0.82
+ # ~~~
fun std_dev: Float do
var avg = self.avg
var sum = 0.0
class DummyArray
super Set[Int]
- super ArrayCapable[Int]
private var capacity: Int
redef var length: Int
private var keys: NativeArray[Int]
init(capacity: Int)
do
_capacity = capacity
- _keys = calloc_array(capacity)
- _values = calloc_array(capacity)
+ _keys = new NativeArray[Int](capacity)
+ _values = new NativeArray[Int](capacity)
end
end
# Get the requested URI, and check the HTTP response. Then convert to JSON
# and check for Github errors.
- fun get_and_check(uri: String): nullable Object
+ fun get_and_check(uri: String): nullable Jsonable
do
var request = new CurlHTTPRequest(uri, self)
request.user_agent = user_agent
var response = request.execute
if response isa CurlResponseSuccess then
- var obj = response.body_str.json_to_nit_object
- if obj isa HashMap[String, nullable Object] then
+ var obj = response.body_str.parse_json
+ if obj isa JsonObject then
if obj.keys.has("message") then
print "Message from Github API: {obj["message"] or else ""}"
print "Requested URI: {uri}"
super
end
-redef class HashCollection[K,N]
+redef class HashCollection[K]
redef fun node_at_idx(i,k)
do
sys.gt_count += 1
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(); `}
extern class NativeFileInputStream in "Java" `{ java.io.FileInputStream `}
super JavaObject
- redef type SELF: NativeFileInputStream
fun available: Int in "Java" `{
try {
extern class NativeFileOutputStream in "Java" `{ java.io.FileOutputStream `}
super JavaObject
- redef type SELF: NativeFileOutputStream
fun close in "Java" `{
try {
extern class NativeFileDescriptor in "Java" `{ java.io.FileDescriptor `}
super JavaObject
- redef type SELF: NativeFileDescriptor
+
fun sync in "Java" `{
try{
recv.sync();
extern class NativeInputStream in "Java" `{ java.io.InputStream `}
super JavaObject
- redef type SELF: NativeInputStream
fun available: Int in "Java" `{
try {
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);
end
redef extern class JavaObject
- type SELF: JavaObject
# Returns a global reference to the Java object behind this reference
#
# to get the underlying Json data. It can also be used as any Json types.
module dynamic
+import error
private import static
-import standard
class JsonValue
var value: nullable Object
#
# assert """{"a": 123}""".to_json_value.is_map
# assert not "123".to_json_value.is_map
- fun is_map: Bool do return value isa HashMap[String, nullable Object]
+ fun is_map: Bool do return value isa MapRead[String, nullable Object]
# Get this value as a `Map[String, JsonValue]`
#
# require: `self.is_map`
fun to_map: Map[String, JsonValue] do
var value = value
- assert value isa HashMap[String, nullable Object]
+ assert value isa MapRead[String, nullable Object]
var map = new HashMap[String, JsonValue]
for k, v in value do map[k] = new JsonValue(v)
# assert "[1, 2, 3, 4, 5]".to_json_value.is_array
# assert "[null, true, false, 0.0, 1, \"str\"]".to_json_value.is_array
# assert """["a", "b", "c"]""".to_json_value.is_array
- fun is_array: Bool do return value isa Array[nullable Object]
+ fun is_array: Bool do return value isa SequenceRead[nullable Object]
# Get this value as an `Array[JsonValue]`
#
fun to_a: Array[JsonValue]
do
var value = value
- assert value isa Array[nullable Object]
+ assert value isa SequenceRead[nullable Object]
var a = new Array[JsonValue]
for e in value do a.add(new JsonValue(e))
return a
end
+ ### Error
+
+ # Is this value an error?
+ #
+ # assert "[]".to_json_value[0].is_error
+ # assert "[".to_json_value.is_error
+ # assert not "[]".to_json_value.is_error
+ fun is_error: Bool do return value isa Error
+
+ # Get this value as a `Error`.
+ #
+ # require: `self.is_error`
+ fun to_error: Error do return value.as(Error)
+
+ ### JsonParseError
+
+ # Is this value a parse error?
+ #
+ # assert "[".to_json_value.is_parse_error
+ # assert not "[]".to_json_value.is_parse_error
+ fun is_parse_error: Bool do return value isa JsonParseError
+
+ # Get this value as a `JsonParseError`.
+ #
+ # require: `self.is_parse_error`
+ fun to_parse_error: JsonParseError do return value.as(JsonParseError)
+
+ ### Children access
+
# Iterator over the values of the array `self`
#
# require: `self.is_array`
# assert """{"a": 123}""".to_json_value["a"].to_i == 123
# assert """{"123": "a"}""".to_json_value[123].to_s == "a"
# assert """{"John Smith": 1980}""".to_json_value[["John ", "Smith"]].to_i == 1980
+ # assert """{"a": 123}""".to_json_value["b"].is_error
#
# assert """["a", "b", "c"]""".to_json_value[0].to_s == "a"
- fun [](key: Object): JsonValue
- do
+ # assert """["a", "b", "c"]""".to_json_value[3].is_error
+ fun [](key: Object): JsonValue do
var value = value
- if value isa HashMap[String, nullable Object] then
- return new JsonValue(value[key.to_s])
- else if value isa Array[nullable Object] then
- assert key isa Int
- return new JsonValue(value[key])
- else abort
+ var result: nullable Object
+ if is_error then
+ return self
+ else if value isa MapRead[String, nullable Object] then
+ key = key.to_s
+ if value.has_key(key) then
+ result = value[key]
+ else
+ result = new JsonKeyError("Key `{key}` not found.", self, key)
+ end
+ else if value isa SequenceRead[nullable Object] then
+ if key isa Int then
+ if key < value.length and key >= 0 then
+ result = value[key]
+ else
+ result = new JsonKeyError("Index `{key}` out of bounds.",
+ self, key)
+ end
+ else
+ result = new JsonKeyError("Invalid key type. Expecting `Int`. Got `{key.class_name}`.",
+ self, key)
+ end
+ else
+ result = new JsonKeyError("Invalid `[]` access on a `{json_type}` JsonValue.",
+ self, key)
+ end
+ return new JsonValue(result)
end
# Advanced query to get a value within the map `self` or it's children.
# assert """{"a": {"t": true, "f": false}}""".to_json_value.get("a").is_map
# assert """{"a": {"t": true, "f": false}}""".to_json_value.get("a.t").to_bool
# assert not """{"a": {"t": true, "f": false}}""".to_json_value.get("a.f").to_bool
+ # assert """{"a": {"t": true, "f": false}}""".to_json_value.get("a.t.t").is_error
# assert """{"a": {"b": {"c": {"d": 123}}}}""".to_json_value.get("a.b.c.d").to_i == 123
- fun get(query: String): JsonValue
- do
+ # assert """{"a": {"b": {"c": {"d": 123}}}}""".to_json_value.get("a.z.c.d").is_error
+ fun get(query: String): JsonValue do
var keys = query.split(".")
var value = value
- for key in keys do
- assert value isa HashMap[String, nullable Object]
- value = value[key]
+ if is_error then return self
+ for i in [0..keys.length[ do
+ var key = keys[i]
+ if value isa MapRead[String, nullable Object] then
+ if value.has_key(key) then
+ value = value[key]
+ else
+ var sub_query = sub_query_to_s(keys, i)
+ var e = new JsonKeyError("Key `{key}` not found.",
+ self, sub_query)
+ return new JsonValue(e)
+ end
+ else
+ var sub_query = sub_query_to_s(keys, i)
+ var val_type = (new JsonValue(value)).json_type
+ var e = new JsonKeyError("Value at `{sub_query}` is not a map. Got type `{val_type}`",
+ self, sub_query)
+ return new JsonValue(e)
+ end
end
return new JsonValue(value)
end
+
+ # Concatenate all keys up to `last` for debugging purposes.
+ #
+ # Note: This method deletes elements in `keys`.
+ private fun sub_query_to_s(keys: Array[String], last: Int): String do
+ last += 1
+ for j in [last..keys.length[ do keys.pop
+ return keys.join(".")
+ end
+
+ # Return a human-readable description of the type.
+ #
+ # For debugging purpose only.
+ fun json_type: String do
+ if is_array then return "array"
+ if is_bool then return "bool"
+ if is_float then return "float"
+ if is_int then return "int"
+ if is_null then return "null"
+ if is_map then return "map"
+ if is_string then return "string"
+ if is_parse_error then return "parse_error"
+ if is_error then return "error"
+ return "undefined"
+ end
end
-redef class String
+# Keyed access failed.
+class JsonKeyError
+ super Error
+
+ # The value on which the access was requested.
+ var json_value: JsonValue
+
+ # The requested key.
+ #
+ # In the case of `JsonValue.get`, the sub-query that failed.
+ var key: Object
+end
+
+redef class Text
# Parse `self` to obtain a `JsonValue`
- fun to_json_value: JsonValue
- do
- var value = json_to_nit_object
+ fun to_json_value: JsonValue do
+ var value = parse_json
return new JsonValue(value)
end
end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Errors related to JSON parsing.
+module json::error
+
+import nitcc_runtime
+
+# Ill-formed JSON.
+class JsonParseError
+ super Error
+
+ # The location of the error in the original text.
+ var position: nullable Position
+
+ redef fun to_s do
+ var p = position
+ if p isa Position then
+ return "[{p}] {super}"
+ else
+ return super
+ end
+ end
+end
# This file is part of NIT ( http://www.nitlanguage.org ).
#
# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+# Copyright 2014 Jean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# See the License for the specific language governing permissions and
# limitations under the License.
-# Offers two APIs to manipulate read Json strings.
+# Provides two APIs to manipulate JSON strings.
+#
+# Both `dynamic` and `static` modules provide at least a method to parse a
+# value written in JSON, but only `static` provide a method to translate a
+# value into JSON.
#
# The `dynamic` module provides a simple interface to get information
-# from a Json document. You must be careful as all services are provided on
+# from a JSON document. You must be careful as all services are provided on
# each nodes and a wrongful use can `abort`.
#
-# The `static` module converts a Json string to a nullable Nit object. The object
-# must then be type checked before it can be used.
+# The `static` module provides a common interface for anything that can be
+# translated into a JSON document. The provided parsing method return a
+# nullable Nit object that must then be type-checked before it can be used.
module json
import static
private fun dfastate_28: DFAState28 do return once new DFAState28
private fun dfastate_29: DFAState29 do return once new DFAState29
private fun dfastate_30: DFAState30 do return once new DFAState30
+ private fun dfastate_31: DFAState31 do return once new DFAState31
+ private fun dfastate_32: DFAState32 do return once new DFAState32
+ private fun dfastate_33: DFAState33 do return once new DFAState33
+ private fun dfastate_34: DFAState34 do return once new DFAState34
end
class MyNToken
super NToken
super DFAState
redef fun trans(char) do
var c = char.ascii
- return dfastate_2
+ if c <= 33 then return null
+ if c <= 34 then return dfastate_2
+ if c <= 46 then return null
+ if c <= 47 then return dfastate_2
+ if c <= 91 then return null
+ if c <= 92 then return dfastate_2
+ if c <= 97 then return null
+ if c <= 98 then return dfastate_2
+ if c <= 101 then return null
+ if c <= 102 then return dfastate_2
+ if c <= 109 then return null
+ if c <= 110 then return dfastate_2
+ if c <= 113 then return null
+ if c <= 114 then return dfastate_2
+ if c <= 115 then return null
+ if c <= 116 then return dfastate_2
+ if c <= 117 then return dfastate_31
+ return null
+ end
+end
+private class DFAState31
+ super DFAState
+ redef fun trans(char) do
+ var c = char.ascii
+ if c <= 47 then return null
+ if c <= 57 then return dfastate_32
+ if c <= 64 then return null
+ if c <= 90 then return dfastate_32
+ if c <= 96 then return null
+ if c <= 122 then return dfastate_32
+ return null
+ end
+end
+private class DFAState32
+ super DFAState
+ redef fun trans(char) do
+ var c = char.ascii
+ if c <= 47 then return null
+ if c <= 57 then return dfastate_33
+ if c <= 64 then return null
+ if c <= 90 then return dfastate_33
+ if c <= 96 then return null
+ if c <= 122 then return dfastate_33
+ return null
+ end
+end
+private class DFAState33
+ super DFAState
+ redef fun trans(char) do
+ var c = char.ascii
+ if c <= 47 then return null
+ if c <= 57 then return dfastate_34
+ if c <= 64 then return null
+ if c <= 90 then return dfastate_34
+ if c <= 96 then return null
+ if c <= 122 then return dfastate_34
+ return null
+ end
+end
+private class DFAState34
+ super DFAState
+ redef fun trans(char) do
+ var c = char.ascii
+ if c <= 47 then return null
+ if c <= 57 then return dfastate_2
+ if c <= 64 then return null
+ if c <= 90 then return dfastate_2
+ if c <= 96 then return null
+ if c <= 122 then return dfastate_2
+ return null
end
end
# This file is part of NIT ( http://www.nitlanguage.org ).
#
# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+# Copyright 2014 Alexandre Terrasa <alexandre@moz-concept.com>
+# Copyright 2014 Jean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# Static interface to get Nit objects from a Json string.
#
-# `String::json_to_nit_object` returns an equivalent Nit object from
+# `Text::parse_json` returns an equivalent Nit object from
# the Json source. This object can then be type checked by the usual
# languages features (`isa` and `as`).
module static
-import standard
+import error
private import json_parser
private import json_lexer
+# Something that can be translated to JSON.
+interface Jsonable
+ # Encode `self` in JSON.
+ #
+ # SEE: `append_json`
+ fun to_json: String is abstract
+
+ # Append the JSON representation of `self` to the specified buffer.
+ #
+ # SEE: `to_json`
+ fun append_json(buffer: Buffer) do
+ buffer.append(to_json)
+ end
+end
+
+redef class Text
+ super Jsonable
+
+ redef fun append_json(buffer) do
+ buffer.add '\"'
+ for i in [0..self.length[ do
+ var char = self[i]
+ if char == '\\' then
+ buffer.append "\\\\"
+ else if char == '\"' then
+ buffer.append "\\\""
+ else if char == '\/' then
+ buffer.append "\\/"
+ else if char < 16.ascii then
+ if char == '\n' then
+ buffer.append "\\n"
+ else if char == '\r' then
+ buffer.append "\\r"
+ else if char == '\t' then
+ buffer.append "\\t"
+ else if char == 0x0C.ascii then
+ buffer.append "\\f"
+ else if char == 0x08.ascii then
+ buffer.append "\\b"
+ else
+ buffer.append "\\u000{char.ascii.to_hex}"
+ end
+ else if char < ' ' then
+ buffer.append "\\u00{char.ascii.to_hex}"
+ else
+ buffer.add char
+ end
+ end
+ buffer.add '\"'
+ end
+
+ # Encode `self` in JSON.
+ #
+ # assert "\t\"http://example.com\"\r\n\0\\".to_json ==
+ # "\"\\t\\\"http:\\/\\/example.com\\\"\\r\\n\\u0000\\\\\""
+ redef fun to_json do
+ var buffer = new FlatBuffer
+ append_json(buffer)
+ return buffer.write_to_string
+ end
+
+ # Parse `self` as JSON.
+ #
+ # If `self` is not a valid JSON document or contains an unsupported escape
+ # sequence, return a `JSONParseError`.
+ #
+ # Example with `JsonObject`:
+ #
+ # var obj = "\{\"foo\": \{\"bar\": true, \"goo\": [1, 2, 3]\}\}".parse_json
+ # assert obj isa JsonObject
+ # assert obj["foo"] isa JsonObject
+ # assert obj["foo"].as(JsonObject)["bar"] == true
+ #
+ # Example with `JsonArray`:
+ #
+ # var arr = "[1, 2, 3]".parse_json
+ # assert arr isa JsonArray
+ # assert arr.length == 3
+ # assert arr.first == 1
+ # assert arr.last == 3
+ #
+ # Example with `String`:
+ #
+ # var str = "\"foo, bar, baz\"".parse_json
+ # assert str isa String
+ # assert str == "foo, bar, baz"
+ #
+ # Example of a syntaxic error:
+ #
+ # var bad = "\{foo: \"bar\"\}".parse_json
+ # assert bad isa JsonParseError
+ # assert bad.position.col_start == 2
+ fun parse_json: nullable Jsonable do
+ var lexer = new Lexer_json(to_s)
+ var parser = new Parser_json
+ var tokens = lexer.lex
+ parser.tokens.add_all(tokens)
+ var root_node = parser.parse
+ if root_node isa NStart then
+ return root_node.n_0.to_nit_object
+ else if root_node isa NError then
+ return new JsonParseError(root_node.message, root_node.position)
+ else abort
+ end
+end
+
+redef class Buffer
+
+ # Append the JSON representation of `jsonable` to `self`.
+ #
+ # Append `"null"` for `null`.
+ private fun append_json_of(jsonable: nullable Jsonable) do
+ if jsonable isa Jsonable then
+ append jsonable.to_json
+ else
+ append "null"
+ end
+ end
+end
+
+redef class Int
+ super Jsonable
+
+ # Encode `self` in JSON.
+ #
+ # assert 0.to_json == "0"
+ # assert (-42).to_json == "-42"
+ redef fun to_json do return self.to_s
+end
+
+redef class Float
+ super Jsonable
+
+ # Encode `self` in JSON.
+ #
+ # Note: Because this method use `to_s`, it may lose precision.
+ #
+ # ~~~
+ # # Will not work as expected.
+ # # assert (-0.0).to_json == "-0.0"
+ #
+ # assert (.5).to_json == "0.5"
+ # assert (0.0).to_json == "0.0"
+ # ~~~
+ redef fun to_json do return self.to_s
+end
+
+redef class Bool
+ super Jsonable
+
+ # Encode `self` in JSON.
+ #
+ # assert true.to_json == "true"
+ # assert false.to_json == "false"
+ redef fun to_json do return self.to_s
+end
+
+# A map that can be translated into a JSON object.
+interface JsonMapRead[K: String, V: nullable Jsonable]
+ super MapRead[K, V]
+ super Jsonable
+
+ redef fun append_json(buffer) do
+ buffer.append "\{"
+ var it = iterator
+ if it.is_ok then
+ append_json_entry(it, buffer)
+ while it.is_ok do
+ buffer.append ","
+ append_json_entry(it, buffer)
+ end
+ end
+ it.finish
+ buffer.append "\}"
+ end
+
+ # Encode `self` in JSON.
+ #
+ # var obj = new JsonObject
+ # obj["foo"] = "bar"
+ # assert obj.to_json == "\{\"foo\":\"bar\"\}"
+ # obj = new JsonObject
+ # obj["baz"] = null
+ # assert obj.to_json == "\{\"baz\":null\}"
+ redef fun to_json do
+ var buffer = new FlatBuffer
+ append_json(buffer)
+ return buffer.write_to_string
+ end
+
+ private fun append_json_entry(iterator: MapIterator[String, nullable Jsonable],
+ buffer: Buffer) do
+ buffer.append iterator.key.to_json
+ buffer.append ":"
+ buffer.append_json_of(iterator.item)
+ iterator.next
+ end
+end
+
+# A JSON Object.
+class JsonObject
+ super JsonMapRead[String, nullable Jsonable]
+ super HashMap[String, nullable Jsonable]
+end
+
+# A sequence that can be translated into a JSON array.
+class JsonSequenceRead[E: nullable Jsonable]
+ super Jsonable
+ super SequenceRead[E]
+
+ redef fun append_json(buffer) do
+ buffer.append "["
+ var it = iterator
+ if it.is_ok then
+ append_json_entry(it, buffer)
+ while it.is_ok do
+ buffer.append ","
+ append_json_entry(it, buffer)
+ end
+ end
+ it.finish
+ buffer.append "]"
+ end
+
+ # Encode `self` in JSON.
+ #
+ # var arr = new JsonArray.with_items("foo", null)
+ # assert arr.to_json == "[\"foo\",null]"
+ # arr.pop
+ # assert arr.to_json =="[\"foo\"]"
+ # arr.pop
+ # assert arr.to_json =="[]"
+ redef fun to_json do
+ var buffer = new FlatBuffer
+ append_json(buffer)
+ return buffer.write_to_string
+ end
+
+ private fun append_json_entry(iterator: Iterator[nullable Jsonable],
+ buffer: Buffer) do
+ buffer.append_json_of(iterator.item)
+ iterator.next
+ end
+end
+
+# A JSON array.
+class JsonArray
+ super JsonSequenceRead[nullable Jsonable]
+ super Array[nullable Jsonable]
+end
+
+redef class JsonParseError
+ super Jsonable
+
+ # Get the JSON representation of `self`.
+ #
+ # var err = new JsonParseError("foo", new Position(1, 2, 3, 4, 5, 6))
+ # assert err.to_json == "\{\"error\":\"JsonParseError\"," +
+ # "\"position\":\{" +
+ # "\"pos_start\":1,\"pos_end\":2," +
+ # "\"line_start\":3,\"line_end\":4," +
+ # "\"col_start\":5,\"col_end\":6" +
+ # "\},\"message\":\"foo\"\}"
+ redef fun to_json do
+ return "\{\"error\":\"JsonParseError\"," +
+ "\"position\":{position.to_json}," +
+ "\"message\":{message.to_json}\}"
+ end
+end
+
+redef class Position
+ super Jsonable
+
+ # Get the JSON representation of `self`.
+ #
+ # var pos = new Position(1, 2, 3, 4, 5, 6)
+ # assert pos.to_json == "\{" +
+ # "\"pos_start\":1,\"pos_end\":2," +
+ # "\"line_start\":3,\"line_end\":4," +
+ # "\"col_start\":5,\"col_end\":6" +
+ # "\}"
+ redef fun to_json do
+ return "\{\"pos_start\":{pos_start},\"pos_end\":{pos_end}," +
+ "\"line_start\":{line_start},\"line_end\":{line_end}," +
+ "\"col_start\":{col_start},\"col_end\":{col_end}\}"
+ end
+end
+
+################################################################################
+# Redef parser
+
redef class Nvalue
- fun to_nit_object: nullable Object is abstract
+ fun to_nit_object: nullable Jsonable is abstract
end
redef class Nvalue_number
end
redef class Nstring
- # FIXME support \n, etc.
- fun to_nit_string: String do return text.substring(1, text.length-2).
- replace("\\\\", "\\").replace("\\\"", "\"").replace("\\b", "\b").
- replace("\\/", "/").replace("\\n", "\n").replace("\\r", "\r").
- replace("\\t", "\t")
+ fun to_nit_string: String do
+ var res = new FlatBuffer
+ var i = 1
+ while i < text.length - 1 do
+ var char = text[i]
+ if char == '\\' then
+ i += 1
+ char = text[i]
+ if char == 'b' then
+ char = 0x08.ascii
+ else if char == 'f' then
+ char = 0x0C.ascii
+ else if char == 'n' then
+ char = '\n'
+ else if char == 'r' then
+ char = '\r'
+ else if char == 't' then
+ char = '\t'
+ else if char == 'u' then
+ var code = text.substring(i + 1, 4).to_hex
+ # TODO UTF-16 escaping is not supported yet.
+ if code >= 128 then
+ char = '?'
+ else
+ char = code.ascii
+ end
+ i += 4
+ end
+ # `"`, `/` or `\` => Keep `char` as-is.
+ end
+ res.add char
+ i += 1
+ end
+ return res.write_to_string
+ end
end
redef class Nvalue_object
- redef fun to_nit_object
- do
- var obj = new HashMap[String, nullable Object]
+ redef fun to_nit_object do
+ var obj = new JsonObject
var members = n_members
if members != null then
var pairs = members.pairs
redef class Npair
fun name: String do return n_string.to_nit_string
- fun value: nullable Object do return n_value.to_nit_object
+ fun value: nullable Jsonable do return n_value.to_nit_object
end
redef class Nvalue_array
redef fun to_nit_object
do
- var arr = new Array[nullable Object]
+ var arr = new JsonArray
var elements = n_elements
if elements != null then
var items = elements.items
redef class Nelements_head
redef fun items do return [n_value]
end
-
-redef class Text
- fun json_to_nit_object: nullable Object
- do
- var lexer = new Lexer_json(to_s)
- var parser = new Parser_json
- var tokens = lexer.lex
- parser.tokens.add_all(tokens)
- var root_node = parser.parse
- if root_node isa NStart then
- return root_node.n_0.to_nit_object
- else if root_node isa NLexerError then
- var pos = root_node.position
- print "Json lexer error: {root_node.message} at {pos or else "<unknown>"} for {root_node}"
- return null
- else if root_node isa NParserError then
- var pos = root_node.position
- print "Json parsing error: {root_node.message} at {pos or else "<unknown>"} for {root_node}"
- return null
- else abort
- end
-end
class JsonDeserializer
super Deserializer
- var root: nullable Object
- var path = new Array[HashMap[String, nullable Object]]
+ var root: nullable Jsonable
+ var path = new Array[JsonObject]
var id_to_object = new HashMap[Int, Object]
var just_opened_id: nullable Int = null
init(text: Text)
do
- var root = text.json_to_nit_object
- if root isa HashMap[String, nullable Object] then path.add(root)
+ var root = text.parse_json
+ if root isa JsonObject then path.add(root)
self.root = root
end
# Convert from simple Json object to Nit object
private fun convert_object(object: nullable Object): nullable Object
do
- if object isa HashMap[String, nullable Object] then
+ if object isa JsonObject then
assert object.keys.has("__kind")
var kind = object["__kind"]
end
redef class Char
- redef fun serialize_to_json(v) do v.stream.write "\{\"__kind\": \"char\", \"__val\": \"{to_s.to_json_s}\"\}"
+ redef fun serialize_to_json(v) do v.stream.write "\{\"__kind\": \"char\", \"__val\": {to_s.to_json}\}"
end
redef class String
- redef fun serialize_to_json(v) do v.stream.write("\"{to_json_s}\"")
-
- private fun to_json_s: String do return self.replace("\\", "\\\\").
- replace("\"", "\\\"").replace("/", "\\/").
- replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t")
- # FIXME add support for unicode char when supported by Nit strings
- # FIXME add support for \f! # .replace("\f", "\\f")
- # FIXME add support for \b .replace("\b", "\\b")
+ redef fun serialize_to_json(v) do v.stream.write(to_json)
end
redef class NativeString
v.notify_of_creation self
var length = v.deserialize_attribute("__length").as(Int)
- var arr = v.path.last["__items"].as(Array[nullable Object])
+ var arr = v.path.last["__items"].as(SequenceRead[nullable Object])
for i in length.times do
var obj = v.convert_object(arr[i])
self.add obj
# SEE: `String::md_to_html` for a shortcut.
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.
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
return new LineOther
end
+ # Get the token kind at `pos`.
+ fun token_at(text: Text, pos: Int): Token do
+ var c0: Char
+ var c1: Char
+ var c2: Char
+
+ if pos > 0 then
+ c0 = text[pos - 1]
+ else
+ c0 = ' '
+ end
+ var c = text[pos]
+
+ if pos + 1 < text.length then
+ c1 = text[pos + 1]
+ else
+ c1 = ' '
+ end
+ if pos + 2 < text.length then
+ c2 = text[pos + 2]
+ else
+ c2 = ' '
+ end
+
+ if c == '*' then
+ if c1 == '*' then
+ if c0 != ' ' or c2 != ' ' then
+ return new TokenStrongStar(pos, c)
+ else
+ return new TokenEmStar(pos, c)
+ end
+ end
+ if c0 != ' ' or c1 != ' ' then
+ return new TokenEmStar(pos, c)
+ else
+ return new TokenNone(pos, c)
+ end
+ else if c == '_' then
+ if c1 == '_' then
+ if c0 != ' ' or c2 != ' 'then
+ return new TokenStrongUnderscore(pos, c)
+ else
+ 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
+ return new TokenNone(pos, c)
+ end
+ else if c == '!' then
+ if c1 == '[' then return new TokenImage(pos, c)
+ return new TokenNone(pos, c)
+ else if c == '[' then
+ return new TokenLink(pos, c)
+ else if c == ']' then
+ return new TokenNone(pos, c)
+ else if c == '`' then
+ if c1 == '`' then
+ return new TokenCodeDouble(pos, c)
+ else
+ return new TokenCodeSingle(pos, c)
+ end
+ else if c == '\\' then
+ if c1 == '\\' or c1 == '[' or c1 == ']' or c1 == '(' or c1 == ')' or c1 == '{' or c1 == '}' or c1 == '#' or c1 == '"' or c1 == '\'' or c1 == '.' or c1 == '<' or c1 == '>' or c1 == '*' or c1 == '+' or c1 == '-' or c1 == '_' or c1 == '!' or c1 == '`' or c1 == '~' or c1 == '^' then
+ return new TokenEscape(pos, c)
+ else
+ return new TokenNone(pos, c)
+ end
+ else if c == '<' then
+ return new TokenHTML(pos, c)
+ else if c == '&' then
+ return new TokenEntity(pos, c)
+ else
+ if ext_mode then
+ if c == '~' and c1 == '~' then
+ return new TokenStrike(pos, c)
+ end
+ end
+ return new TokenNone(pos, c)
+ end
+ end
+
+ # Find the position of a `token` in `self`.
+ fun find_token(text: Text, start: Int, token: Token): Int do
+ var pos = start
+ while pos < text.length do
+ if token_at(text, pos).is_same_type(token) then
+ return pos
+ end
+ pos += 1
+ end
+ return -1
+ end
end
# Emit output corresponding to blocks content.
# Default is `HTMLDecorator`
var decorator: Decorator = new HTMLDecorator is writable
- # Create a new `MardownEmitter` using the default `HTMLDecorator`
- init(processor: MarkdownProcessor) do
- self.processor = processor
- end
-
# Create a new `MarkdownEmitter` using a custom `decorator`.
init with_decorator(processor: MarkdownProcessor, decorator: Decorator) do
init processor
current_text = text
current_pos = start
while current_pos < text.length do
- var mt = text.token_at(current_pos)
+ var mt = processor.token_at(text, current_pos)
if (token != null and not token isa TokenNone) and
(mt.is_same_type(token) or
(token isa TokenEmStar and mt isa TokenStrongStar) or
# 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
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
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
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
var next_empty: Bool = false is writable
# Initialize a new MDLine from its string value
- init(value: String) do
- self.value = value
+ init do
self.leading = process_leading
if leading != value.length then
self.is_empty = false
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
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
redef fun emit(v) do
var a = pos + next_pos + 1
- var b = v.current_text.find_token(a, self)
+ var b = v.processor.find_token(v.current_text.as(not null), a, self)
if b > 0 then
v.current_pos = b + next_pos
while a < b and v.current_text[a] == ' ' do a += 1
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
redef class Text
- # Get the token kind at `pos`.
- private fun token_at(pos: Int): Token do
- var c0: Char
- var c1: Char
- var c2: Char
-
- if pos > 0 then
- c0 = self[pos - 1]
- else
- c0 = ' '
- end
- var c = self[pos]
-
- if pos + 1 < length then
- c1 = self[pos + 1]
- else
- c1 = ' '
- end
- if pos + 2 < length then
- c2 = self[pos + 2]
- else
- c2 = ' '
- end
-
- if c == '*' then
- if c1 == '*' then
- if c0 != ' ' or c2 != ' ' then
- return new TokenStrongStar(pos, c)
- else
- return new TokenEmStar(pos, c)
- end
- end
- if c0 != ' ' or c1 != ' ' then
- return new TokenEmStar(pos, c)
- else
- return new TokenNone(pos, c)
- end
- else if c == '_' then
- if c1 == '_' then
- if c0 != ' ' or c2 != ' 'then
- return new TokenStrongUnderscore(pos, c)
- else
- return new TokenEmUnderscore(pos, c)
- end
- end
- if c0 != ' ' or c1 != ' ' then
- return new TokenEmUnderscore(pos, c)
- else
- return new TokenNone(pos, c)
- end
- else if c == '!' then
- if c1 == '[' then return new TokenImage(pos, c)
- return new TokenNone(pos, c)
- else if c == '[' then
- return new TokenLink(pos, c)
- else if c == ']' then
- return new TokenNone(pos, c)
- else if c == '`' then
- if c1 == '`' then
- return new TokenCodeDouble(pos, c)
- else
- return new TokenCodeSingle(pos, c)
- end
- else if c == '\\' then
- if c1 == '\\' or c1 == '[' or c1 == ']' or c1 == '(' or c1 == ')' or c1 == '{' or c1 == '}' or c1 == '#' or c1 == '"' or c1 == '\'' or c1 == '.' or c1 == '<' or c1 == '>' or c1 == '*' or c1 == '+' or c1 == '-' or c1 == '_' or c1 == '!' or c1 == '`' or c1 == '~' or c1 == '^' then
- return new TokenEscape(pos, c)
- else
- return new TokenNone(pos, c)
- end
- else if c == '<' then
- 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
- return new TokenNone(pos, c)
- end
- end
-
- # Find the position of a `token` in `self`.
- private fun find_token(start: Int, token: Token): Int do
- var pos = start
- while pos < length do
- if token_at(pos).is_same_type(token) then
- return pos
- end
- pos += 1
- end
- return -1
- end
-
# Get the position of the next non-space character.
private fun skip_spaces(start: Int): Int do
var pos = start
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)
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:
assert res == exp
end
- fun test_process_code3 do
+ fun test_process_code_ext1 do
var test = """
Here is an example of AppleScript:
~~~
assert res == exp
end
- fun test_process_code4 do
+ fun test_process_code_ext2 do
var test = """
Here is an example of AppleScript:
```
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 = """
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.
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.
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.
<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
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
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
# 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
# cURL requests compatible with the JSON REST APIs.
module curl_json
-import jsonable
+import json::static
intrude import curl
# An abstract request that defines most of the standard options for Neo4j REST API
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Errors thrown by the `neo4j` library.
+module neo4j::error
+
+import json::static
+
+# An error thrown by the `neo4j` API.
+#
+# var error = new NeoError("ErrorMessage", "ErrorName")
+# assert error.to_json == "\{\"error\":\"ErrorName\",\"message\":\"ErrorMessage\"\}"
+class NeoError
+ super Error
+ super Jsonable
+
+ # The name of the error.
+ #
+ # Used to programmatically distinguish this kind of error from others.
+ var name: String
+
+ redef fun to_json do
+ return "\{\"error\":{name.to_json},\"message\":{message.to_json}\}"
+ end
+
+ redef fun to_s do return "[{name}] {super}"
+end
# end
# assert 2 == graph.nodes.length
init from_json(t: Text) do
- from_json_object(t.to_jsonable.as(JsonObject))
+ from_json_object(t.parse_json.as(JsonObject))
end
# Retrieve the graph from the specified JSON object.
# assert ["foo", "Bar"] == node.labels
# assert 42 == node["baz"]
init from_json(t: Text) do
- from_json_object(t.to_jsonable.as(JsonObject))
+ from_json_object(t.parse_json.as(JsonObject))
end
# Retrieve the node from the specified JSON value.
module neo4j
import curl_json
+import error
# Handles Neo4j server start and stop command
#
# Parse the cURL `response` as a JSON string
private fun parse_response(response: CurlResponse): Jsonable do
if response isa CurlResponseSuccess then
- if response.body_str.is_empty then
+ var str = response.body_str
+ if str.is_empty then return new JsonObject
+ var res = str.parse_json
+ if res isa JsonParseError then
+ var e = new NeoError(res.to_s, "JsonParseError")
+ e.cause = res
+ return e
+ end
+ if res == null then
+ # empty response wrap it in empty object
return new JsonObject
- else
- var str = response.body_str
- var res = str.to_jsonable
- if res == null then
- # empty response wrap it in empty object
- return new JsonObject
- else if res isa JsonObject and res.has_key("exception") then
- var error = "Neo4jError::{res["exception"] or else "null"}"
- var msg = ""
- if res.has_key("message") then
- msg = res["message"].to_s
- end
- return new JsonError(error, msg.to_json)
- else
- return res
+ else if res isa JsonObject and res.has_key("exception") then
+ var error = "Neo4jError::{res["exception"] or else "null"}"
+ var msg = ""
+ if res.has_key("message") then
+ msg = res["message"].to_s
end
+ return new NeoError(msg, error)
+ else
+ return res
end
else if response isa CurlResponseFailed then
- return new JsonError("Curl error", "{response.error_msg} ({response.error_code})")
+ return new NeoError("{response.error_msg} ({response.error_code})", "CurlError")
else
- return new JsonError("Curl error", "Unexpected response '{response}'")
+ return new NeoError("Unexpected response \"{response}\".", "CurlError")
end
end
end
fun save_edges(edges: Collection[NeoEdge]) do for edge in edges do save_edge(edge)
# Execute the batch and update local nodes
- fun execute: List[JsonError] do
+ fun execute: List[NeoError] do
var request = new JsonPOST(client.batch_url, client.curl)
# request.headers["X-Stream"] = "true"
var json_jobs = new JsonArray
end
# Associate data from response in original nodes and edges
- private fun finalize_batch(response: Jsonable): List[JsonError] do
- var errors = new List[JsonError]
+ private fun finalize_batch(response: Jsonable): List[NeoError] do
+ var errors = new List[NeoError]
if not response isa JsonArray then
- errors.add(new JsonError("Neo4jError", "Unexpected batch response format"))
+ errors.add(new NeoError("Unexpected batch response format.", "Neo4jError"))
return errors
end
# print " {res.length} jobs executed"
for res in response do
if not res isa JsonObject then
- errors.add(new JsonError("Neo4jError", "Unexpected job format in batch response"))
+ errors.add(new NeoError("Unexpected job format in batch response.", "Neo4jError"))
continue
end
var id = res["id"].as(Int)
--- /dev/null
+# 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
// 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
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);
# 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));
`}
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
#
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
# 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
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
# assert a == b
class Array[E]
super AbstractArray[E]
- super ArrayCapable[E]
redef fun [](index)
do
var c = _capacity
if cap <= c then return
while c <= cap do c = c * 2 + 2
- var a = calloc_array(c)
+ var a = new NativeArray[E](c)
if _capacity > 0 then _items.copy_to(a, _length)
_items = a
_capacity = c
init with_capacity(cap: Int)
do
assert positive: cap >= 0
- _items = calloc_array(cap)
+ _items = new NativeArray[E](cap)
_capacity = cap
_length = 0
end
init filled_with(value: E, count: Int)
do
assert positive: count >= 0
- _items = calloc_array(count)
+ _items = new NativeArray[E](count)
_capacity = count
_length = count
var i = 0
# Native classes ##############################################################
-# Subclasses of this class can create native arrays
-interface ArrayCapable[E]
- # Get a new array of `size` elements.
- protected fun calloc_array(size: Int): NativeArray[E] is intern
-end
-
# Native Nit array
# Access are unchecked and it has a fixed size
# Not for public use: may become private.
import array
# A HashCollection is an array of HashNode[K] indexed by the K hash value
-private abstract class HashCollection[K: Object, N: HashNode[Object]]
- super ArrayCapable[nullable N]
+private abstract class HashCollection[K: Object]
+ type N: HashNode[K]
var array: nullable NativeArray[nullable N] = null # Used to store items
var capacity: Int = 0 # Size of _array
_last_accessed_key = null
# get a new array
- var new_array = calloc_array(cap)
+ var new_array = new NativeArray[nullable N](cap)
_array = new_array
# clean the new array
# Keys of such a map cannot be null and require a working `hash` method
class HashMap[K: Object, V]
super Map[K, V]
- super HashCollection[K, HashMapNode[K, V]]
+ super HashCollection[K]
+
+ redef type N: HashMapNode[K, V] is fixed
redef fun [](key)
do
# Keys of such a map cannot be null and require a working `hash` method
class HashSet[E: Object]
super Set[E]
- super HashCollection[E, HashSetNode[E]]
+ super HashCollection[E]
+
+ redef type N: HashSetNode[E] is fixed
redef fun length do return _the_length
#
# 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.
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
# 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"
# High-level abstraction for all text representations
abstract class Text
super Comparable
- super StringCapable
redef type OTHER: Text
redef fun reversed
do
- var native = calloc_string(self.length + 1)
+ var native = new NativeString(self.length + 1)
var length = self.length
var items = self.items
var pos = 0
redef fun to_upper
do
- var outstr = calloc_string(self.length + 1)
+ var outstr = new NativeString(self.length + 1)
var out_index = 0
var myitems = self.items
redef fun to_lower
do
- var outstr = calloc_string(self.length + 1)
+ var outstr = new NativeString(self.length + 1)
var out_index = 0
var myitems = self.items
if real_items != null then
return real_items.as(not null)
else
- var newItems = calloc_string(length + 1)
+ var newItems = new NativeString(length + 1)
self.items.copy_to(newItems, length, index_from, 0)
newItems[length] = '\0'
self.real_items = newItems
var total_length = my_length + its_length
- var target_string = calloc_string(my_length + its_length + 1)
+ var target_string = new NativeString(my_length + its_length + 1)
self.items.copy_to(target_string, my_length, index_from, 0)
if s isa FlatString then
var my_items = self.items
- var target_string = calloc_string((final_length) + 1)
+ var target_string = new NativeString(final_length + 1)
target_string[final_length] = '\0'
# The COW flag can be set at false here, since
# it does a copy of the current `Buffer`
written = false
- var a = calloc_string(c+1)
+ var a = new NativeString(c+1)
if length > 0 then items.copy_to(a, length, 0, 0)
items = a
capacity = c
redef fun to_cstring
do
if is_dirty then
- var new_native = calloc_string(length + 1)
+ var new_native = new NativeString(length + 1)
new_native[length] = '\0'
if length > 0 then items.copy_to(new_native, length, 0, 0)
real_items = new_native
do
capacity = s.length + 1
length = s.length
- items = calloc_string(capacity)
+ items = new NativeString(capacity)
if s isa FlatString then
s.items.copy_to(items, length, s.index_from, 0)
else if s isa FlatBuffer then
do
assert cap >= 0
# _items = new NativeString.calloc(cap)
- items = calloc_string(cap+1)
+ items = new NativeString(cap+1)
capacity = cap
length = 0
end
redef fun reverse
do
written = false
- var ns = calloc_string(capacity)
+ var ns = new NativeString(capacity)
var si = length - 1
var ni = 0
var it = items
private class FlatBufferCharView
super BufferCharView
- super StringCapable
redef type SELFTYPE: FlatBuffer
# Native strings are simple C char *
extern class NativeString `{ char* `}
- super StringCapable
# Creates a new NativeString with a capacity of `length`
new(length: Int) is intern
fun to_s_with_copy: FlatString
do
var length = cstring_length
- var new_self = calloc_string(length + 1)
+ var new_self = new NativeString(length + 1)
copy_to(new_self, length, 0, 0)
var str = new FlatString.with_infos(new_self, length, 0, length - 1)
new_self[length] = '\0'
end
end
-# StringCapable objects can create native strings
-interface StringCapable
-
- # Allocate a string of `size`.
- protected fun calloc_string(size: Int): NativeString is intern
-end
-
redef class Sys
private var args_cache: nullable Sequence[String]
redef fun to_cstring
do
if real_items != null then return real_items.as(not null)
- var new_items = calloc_string(bytelen + 1)
+ var new_items = new NativeString(bytelen + 1)
self.items.copy_to(new_items, bytelen, index[index_from].pos, 0)
new_items[bytelen] = '\0'
self.real_items = new_items
redef fun reversed
do
- var native = calloc_string(self.bytelen + 1)
+ var native = new NativeString(self.bytelen + 1)
var length = self.length
var index = self.index
var pos = 0
var my_real_len = length
var my_real_fin_len = my_real_len * i
- var target_string = calloc_string((finlen) + 1)
+ var target_string = new NativeString((finlen) + 1)
var my_index = index
var new_index = new StringIndex(my_real_fin_len)
redef fun to_upper
do
- var outstr = calloc_string(self.bytelen + 1)
+ var outstr = new NativeString(self.bytelen + 1)
var out_index = 0
var index = self.index
redef fun to_lower
do
- var outstr = calloc_string(self.bytelen + 1)
+ var outstr = new NativeString(self.bytelen + 1)
var out_index = 0
var index = self.index
var real_len = new Container[Int](0)
var length = cstring_length
var x = make_index(length, real_len)
- var new_self = calloc_string(length + 1)
+ var new_self = new NativeString(length + 1)
copy_to(new_self, length, 0, 0)
return new FlatString.with_infos_index(new_self, real_len.item, 0, real_len.item - 1, x, length)
end
end
redef fun reversed do
- var new_str = calloc_string(bytelen)
+ var new_str = new NativeString(bytelen)
var s_pos = bytelen
var my_pos = index_from
var its = items
end
redef fun to_upper do
- var ns = calloc_string(bytelen)
+ var ns = new NativeString(bytelen)
var offset = 0
for i in [0 .. length[
do
end
redef fun to_lower do
- var ns = calloc_string(bytelen)
+ var ns = new NativeString(bytelen)
var offset = 0
for i in [0 .. length[
do
redef fun +(o) do
if o isa Buffer then o = o.to_s
if o isa FlatString then
- var new_str = calloc_string(bytelen + o.bytelen + 1)
+ var new_str = new NativeString(bytelen + o.bytelen + 1)
var new_bytelen = bytelen + o.bytelen
new_str[new_bytelen] = '\0'
var newlen = length + o.length
var new_bytelen = mybtlen * i
var mylen = length
var newlen = mylen * i
- var ns = calloc_string(new_bytelen + 1)
+ var ns = new NativeString(new_bytelen + 1)
ns[new_bytelen] = '\0'
var offset = 0
while i > 0 do
redef fun to_cstring do
if real_items != null then return real_items.as(not null)
- var new_items = calloc_string(bytelen + 1)
+ var new_items = new NativeString(bytelen + 1)
self.items.copy_to(new_items, bytelen, index_from, 0)
new_items[bytelen] = '\0'
self.real_items = new_items
with_capacity(50)
for i in s.substrings do self.append(i)
end
- items = calloc_string(s.bytelen)
+ items = new NativeString(s.bytelen)
if s isa FlatString then
s.items.copy_to(items, s.bytelen, s.index_from, 0)
else
var c = capacity
if cap <= c then return
while c <= cap do c = c * 2 + 2
- var a = calloc_string(c+1)
+ var a = new NativeString(c+1)
if bytelen > 0 then items.copy_to(a, bytelen, 0, 0)
items = a
capacity = c
redef fun reverse
do
- var nns = calloc_string(bytelen)
+ var nns = new NativeString(bytelen)
var ns = items
var btlen = bytelen
var myp = 0
end
redef fun to_cstring do
- var ns = calloc_string(bytelen)
+ var ns = new NativeString(bytelen)
items.copy_to(ns, bytelen, 0, 0)
return ns
end
redef fun to_s_with_copy
do
var length = cstring_length
- var new_self = calloc_string(length + 1)
+ var new_self = new NativeString(length + 1)
copy_to(new_self, length, 0, 0)
return new FlatString.with_bytelen(new_self, 0, length - 1, length)
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
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 ""}\}"
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
# 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
# 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
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
# 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
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")
# 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
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" />
redef fun native_array_instance(elttype: MType, length: RuntimeVariable): RuntimeVariable
do
var ret_type = self.get_class("NativeArray").get_mtype([elttype])
+ ret_type = anchor(ret_type).as(MClassType)
return self.new_expr("NEW_{ret_type.c_name}({length})", ret_type)
end
module doc_model
import model_utils
-import markdown
+import docdown
import doc_templates
import ordered_tree
import model_ext
import toolcontext
import doc_model
+private import json::static
redef class ToolContext
private var opt_dir = new OptionString("output directory", "-d", "--dir")
# All entities are grouped by name to make the research easier.
class QuickSearch
- private var mmodules = new HashSet[MModule]
- private var mclasses = new HashSet[MClass]
- private var mpropdefs = new HashMap[String, Set[MPropDef]]
+ private var table = new QuickSearchTable
var ctx: ToolContext
var model: Model
init do
for mmodule in model.mmodules do
if mmodule.is_fictive then continue
- mmodules.add mmodule
+ add_result_for(mmodule.name, mmodule.full_name, mmodule.nitdoc_url)
end
for mclass in model.mclasses do
if mclass.visibility < ctx.min_visibility then continue
- mclasses.add mclass
+ add_result_for(mclass.name, mclass.full_name, mclass.nitdoc_url)
end
for mproperty in model.mproperties do
if mproperty.visibility < ctx.min_visibility then continue
if mproperty isa MAttribute then continue
- if not mpropdefs.has_key(mproperty.name) then
- mpropdefs[mproperty.name] = new HashSet[MPropDef]
+ for mpropdef in mproperty.mpropdefs do
+ var full_name = mpropdef.mclassdef.mclass.full_name
+ var cls_url = mpropdef.mclassdef.mclass.nitdoc_url
+ var def_url = "{cls_url}#{mpropdef.mproperty.nitdoc_id}"
+ add_result_for(mproperty.name, full_name, def_url)
end
- mpropdefs[mproperty.name].add_all(mproperty.mpropdefs)
end
end
+ private fun add_result_for(query: String, txt: String, url: String) do
+ table[query].add new QuickSearchResult(txt, url)
+ end
+
fun render: Template do
var tpl = new Template
- tpl.add "var nitdocQuickSearchRawList=\{ "
- for mmodule in mmodules do
- tpl.add "\"{mmodule.name}\":["
- tpl.add "\{txt:\"{mmodule.full_name}\",url:\"{mmodule.nitdoc_url}\"\},"
- tpl.add "],"
- end
- for mclass in mclasses do
- var full_name = mclass.intro.mmodule.full_name
- tpl.add "\"{mclass.name}\":["
- tpl.add "\{txt:\"{full_name}\",url:\"{mclass.nitdoc_url}\"\},"
- tpl.add "],"
- end
- for mproperty, mprops in mpropdefs do
- tpl.add "\"{mproperty}\":["
- for mpropdef in mprops do
- var full_name = mpropdef.mclassdef.mclass.full_name
- var cls_url = mpropdef.mclassdef.mclass.nitdoc_url
- var def_url = "{cls_url}#{mpropdef.mproperty.nitdoc_id}"
- tpl.add "\{txt:\"{full_name}\",url:\"{def_url}\"\},"
- end
- tpl.add "],"
- end
- tpl.add " \};"
+ var buffer = new RopeBuffer
+ tpl.add buffer
+ buffer.append "var nitdocQuickSearchRawList="
+ table.append_json buffer
+ buffer.append ";"
return tpl
end
end
+# The result map for QuickSearch.
+private class QuickSearchTable
+ super JsonMapRead[String, QuickSearchResultList]
+ super HashMap[String, QuickSearchResultList]
+
+ redef fun provide_default_value(key) do
+ var v = new QuickSearchResultList
+ self[key] = v
+ return v
+ end
+end
+
+# A QuickSearch result list.
+private class QuickSearchResultList
+ super JsonSequenceRead[QuickSearchResult]
+ super Array[QuickSearchResult]
+end
+
+# A QuickSearch result.
+private class QuickSearchResult
+ super Jsonable
+
+ # The text of the link.
+ var txt: String
+
+ # The destination of the link.
+ var url: String
+
+ redef fun to_json do
+ return "\{\"txt\":{txt.to_json},\"url\":{url.to_json}\}"
+ end
+end
+
# Nitdoc base page
# Define page structure and properties
abstract class NitdocPage
# limitations under the License.
# Transform Nit verbatim documentation into HTML
-module markdown
+module docdown
private import parser
import html
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
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)
# 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
import modelize
import highlight
-import markdown
+import docdown
redef class ModelBuilder
fun test_markdown(page: HTMLTag, mmodule: MModule)
module testing_doc
import testing_base
-intrude import markdown
+intrude import docdown
# Extractor, Executor and Reporter for the tests in a module
class NitUnitExecutor
-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
--- /dev/null
+# 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
../lib/*/examples/*.nit \
../contrib/friendz/src/solver_cmd.nit \
../contrib/pep8analysis/src/pep8analysis.nit \
+ ../contrib/nitiwiki/src/nitiwiki.nit \
*.nit
--- /dev/null
+nitiwiki --config ../contrib/nitiwiki/tests/wiki1/config2.ini --clean --status
+nitiwiki --config ../contrib/nitiwiki/tests/wiki1/config2.ini --clean --render -v
-Runtime error: Assert 'index' failed (../lib/standard/collection/array.nit:258)
+Expected two numbers
--- /dev/null
+X
+X
+X
+Y
+Y
+Y
+Y
+A[Int]
+G[A[Int]]
+B[Bool]
+G[B[Bool]]
--- /dev/null
+alt/base_self_type_alt1.nit:25,25--29: Type error: expected SELF, got X
--- /dev/null
+X
+X
+X
+Y
+Y
+Y
+Y
+A[Int]
+G[A[Int]]
+B[Bool]
+G[B[Bool]]
--- /dev/null
+alt/base_self_type_alt3.nit:50,7: Type error: expected Y, got X
-../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`?
--- /dev/null
+X
+X
+X
+Y
+Y
+Y
+Y
+A
+G
+B
+G
--- /dev/null
+X
+X
+X
+Y
+Y
+Y
+Y
+A
+G
+B
+G
-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
-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
-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
--- /dev/null
+Not in a nitiwiki directory.
+Use --init to initialize one here.
--- /dev/null
+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
--- /dev/null
+Render section out
if name == "Array[Serializable]" then return new Array[Serializable].from_deserializer(self)
if name == "Array[String]" then return new Array[String].from_deserializer(self)
if name == "Array[Object]" then return new Array[Object].from_deserializer(self)
- if name == "Array[Match]" then return new Array[Match].from_deserializer(self)
- if name == "Array[FlatBuffer]" then return new Array[FlatBuffer].from_deserializer(self)
return super
end
end
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];
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];
-Runtime error: Assert failed (../lib/c.nit:64)
+Runtime error: Assert failed (../lib/c.nit:63)
-Runtime error: Assert failed (../lib/c.nit:57)
+Runtime error: Assert failed (../lib/c.nit:56)
0
-Runtime error: Assert failed (../lib/c.nit:57)
+Runtime error: Assert failed (../lib/c.nit:56)
0
-Runtime error: Assert failed (../lib/c.nit:56)
+Runtime error: Assert failed (../lib/c.nit:55)
0
0
1
-Runtime error: Cast failed. Expected `E`, got `Bool` (../lib/standard/collection/array.nit:792)
+Runtime error: Cast failed. Expected `E`, got `Bool` (../lib/standard/collection/array.nit:785)
NativeString
N
Nit
end
class TestNative
- super ArrayCapable[Int]
+
init
do
a[1] = 2
print(a[0])
print(a[1])
- b = calloc_array(5)
+ b = new NativeArray[Int](5)
b[0]=200
b[1]=300
print(b[0])
# This file is part of NIT ( http://www.nitlanguage.org ).
#
# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+# Copyright 2014 Jean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
var a = "\{\"__kind\": \"obj\", \"__id\": 0, \"__class\": \"C\", \"a\": \{\"__kind\": \"obj\", \"__id\": 1, \"__class\": \"A\", \"b\": true, \"c\": \"a\", \"f\": 0.123, \"i\": 1234, \"s\": \"asdf\", \"n\": null, \"array\": [88, \"hello\", null]\}, \"b\": \{\"__kind\": \"obj\", \"__id\": 2, \"__class\": \"B\", \"b\": false, \"c\": \"b\", \"f\": 123.123, \"i\": 2345, \"s\": \"hjkl\", \"n\": null, \"array\": [88, \"hello\", null], \"ii\": 1111, \"ss\": \"qwer\"\}, \"aa\": \{\"__kind\": \"ref\", \"__id\": 1\}\}"
var b = "\{\"__kind\": \"obj\", \"__id\": 0, \"__class\": \"A\", \"b\": true, \"c\": \"a\", \"f\": 0.123, \"i\": 1234, \"s\": \"asdf\", \"n\": null, \"array\": [88, \"hello\", null]\}"
+var c = "\{\"foo\":\"bar\\\"\\\\\\/\\b\\f\\n\\r\\t\\u0020\\u0000\"\}"
+var d = "\{ \"face with tears of joy\" : \"\\uD83D\\uDE02\" \}"
-for s in [a, b] do
- var obj = s.json_to_nit_object
+for s in [a, b, c, d] do
+ var obj = s.parse_json
print "# Json: {s}"
print "# Nit: {obj or else "<null>"}"
end
# limitations under the License.
class Toto
- super ArrayCapable[Int]
+
fun toto
do
- var a = calloc_array(3)
+ var a = new NativeArray[Int](3)
a[0] = 10
a[1] = 20
a[2] = 30
print res1["name"].to_s
print res1["age"].to_s
print res1["status"].to_s
-print res1["groups"].to_s
+print res1["groups"].to_json
print res1.labels.join(" ")
assert res1.out_edges.is_empty
print res4["name"].to_s
print res4["age"].to_s
print res4["status"].to_s
-print res4["groups"].to_s
+print res4["groups"].to_json
print res4.labels.join(" ")
assert res4.in_edges.is_empty
assert not res4.out_edges.is_empty
print res4["name"].to_s
print res4["age"].to_s
print res4["status"].to_s
-print res4["groups"].to_s
+print res4["groups"].to_json
print res4.labels.join(" ")
assert res4.in_edges.is_empty
assert not res4.out_edges.is_empty