From: Jean Privat Date: Tue, 25 Nov 2014 01:05:56 +0000 (-0500) Subject: Merge: Nitiwiki: a quick wiki builder based on markdown files X-Git-Tag: v0.6.11~18 X-Git-Url: http://nitlanguage.org?hp=d70def6d4944e1f860db4c2784f864071ec6bb6f Merge: Nitiwiki: a quick wiki builder based on markdown files Introduces `nitiwiki` a contrib tool that generate an html wiki structure based on markdown files. Behavior is very similar to ikiwiki (but simplified). 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 See http://moz-code.org/nitiwiki/ for online documentation. This is actually the `README.md` file rendered by nitiwiki. The full wiki can be found in `contrib/examples/nitiwiki` Another example can be found at http://moz-code.org/uqam/. This is my TA site, entirely based on nitiwiki. Pull-Request: #765 Reviewed-by: Jean Privat Reviewed-by: Alexis Laferrière Reviewed-by: Lucas Bajolet --- diff --git a/contrib/nitiwiki/.gitignore b/contrib/nitiwiki/.gitignore new file mode 100644 index 0000000..89f9ac0 --- /dev/null +++ b/contrib/nitiwiki/.gitignore @@ -0,0 +1 @@ +out/ diff --git a/contrib/nitiwiki/Makefile b/contrib/nitiwiki/Makefile new file mode 100644 index 0000000..8e385f9 --- /dev/null +++ b/contrib/nitiwiki/Makefile @@ -0,0 +1,15 @@ +all: nitiwiki + +nitiwiki: + mkdir -p bin + ../../bin/nitc src/nitiwiki.nit -o bin/nitiwiki + +tests: nitiwiki + cd tests; make + +doc: + ../../bin/nitdoc -d doc src/nitiwiki.nit + +clean: + rm -rf bin + rm -rf -- .nit_compile 2> /dev/null || true diff --git a/contrib/nitiwiki/README.md b/contrib/nitiwiki/README.md new file mode 100644 index 0000000..abe84ad --- /dev/null +++ b/contrib/nitiwiki/README.md @@ -0,0 +1,291 @@ +# nitiwiki, a simple wiki manager based on markdown. + +Basically, nitiwiki compiles a set of markdown files into an HTML wiki. + +The wiki content is structured by `sections` represented by the wiki folders. Sections can contain `articles` represented by markdown files. + +Features: + +* automatic wiki structure from folders hierarchy +* automatic site menu +* automatic sitemap +* automatic summaries +* easy and rapid templating +* customizable section templates and menus +* rsync synchronization +* git synchronization + +## Wiki structure + +Basic wiki structure: + + root + |- assets + |- out + |- pages + |- templates + | |- footer.html + | |- header.html + | |- menu.html + | `- template.html + `- config.ini + +### pages + +This is where goes all the content of your wiki. +Nitiwiki will render an article for each markdown file found in `pages`. + +You can categorize your content in sections using sub-folders: + + pages + |- section1 + | `- index.md + |- section2 + | `- index.md + |- page1.md + |- page2.md + `- index.md + +### assets + +This is where you store CSS and JavaScript files used in the design of your site. + +You can also use this directory to put some images or other files that will be +used in all your pages. + + assets + |- css + |- js + `- logo.png + +### templates + +This folder contains the templates used to generate the HTML pages of your wiki. + +The main template is called `template.html`. +It contains the HTML structure of your pages and some macros that will be replaced +by the wiki articles. + +### out + +This is where your wiki will be rendered by nitiwiki. +Do not put anything in this folder since it will be overwritten at the +next wiki rendering. + +The wiki rendering consists in: + +1. copy the `assets` directory to `out` +2. copy attached article files from `pages` to `out` +3. translate markdown files from `pages` to html files in `out` + +### config.ini + +This is the main config file of your wiki. For more details see [Configure the wiki](#Configure_the_wiki). + +## Manage the wiki + +### Create a new wiki + +Just move to the directory where you want to store your source files and type: + + nitiwiki init + +This command will import the base structure of your wiki in the current directory. +At this point nitiwiki has created the main configuration file of your site: +`config.ini`. + +### Configure the wiki + +All the nitiwiki configuration is done using +[ini files](http://en.wikipedia.org/wiki/INI_file). + +The wiki configuration is contained in the `config.ini` file located at the root +directory of your wiki. +This file can be edited to change nitiwiki settings. + +Settings: + +* `wiki.name`: Displayed name +* `wiki.desc`: Long description +* `wiki.logo`: Logo image url +* `wiki.root_url`: Base url used to resolve links +* `wiki.root_dir`: Absolute path of base directory +* `wiki.source_dir`: Source directory (relative path from `wiki.root_dir`) +* `wiki.out_dir`: Output directory (relative) +* `wiki.assets_dir`: Assets directory (relative) +* `wiki.templates_dir`: Templates directory (relative) +* `wiki.template`: Wiki main template file +* `wiki.header`: Wiki main header template file +* `wiki.footer`: Wiki main footer template file +* `wiki.menu`: Wiki main menu template file +* `wiki.rsync_dir`: Directory used to rsync output +* `wiki.git_origin`: Git origin used to fetch data +* `wiki.git_branch`: Git branch used to fetch data + +For more details on each option see `WikiConfig`. + +### Add content + +To add content in your wiki simply add markdown files (with `.md` extension) into the `pages/` folder. + +Once you have done your changes, use: + + nitiwiki --status + +This will show the impacts of your changes on the wiki structure. + +Then type: + + nitiwiki --render + +This will the generate the html output of your new content. +The option `--force` can be used to regenerate all the wiki. +This can be uselful when you perform changes on the template files. + +### Configure sections + +Section appearance can be customized using config files. + +Each section in the `pages/` folder can contain a `config.ini` file. +Options set on a section will be propagated to all its children unless +they have their own config file. + +Allowed options are: + +* `section.title`: Custom title for this section +* `section.template`: Custom template file +* `section.header`: Custom header template file +* `section.footer`: Custom footer template file +* `section.menu`: Custom menu template file +* `section.is_hidden`: Set this to `true` will hide the section in all menus and + sitemaps. + +## Customize templates + +Templating your wiki involves modifying 4 template files: + +* `template.html` +* `header.html` +* `footer.html` +* `menu.html` + +Each of these file contains an HTML skeletton used by nitiwiki to render your files. +Templates can contains macros marked `%MACRO%` that will be replaced by dynamic content. + +Every template can access to: + +* `ROOT_URL`: Wiki root url +* `TITLE`: Wiki name +* `SUBTITLE`: Wiki description +* `LOGO`: Wiki logo image path + +Additionnal macros can be used in specialized templates. + +### Main template + +The template file `template.html` represents the overall structure of your wiki pages. + + + + + %TITLE% + + + + %HEADER% + %TOP_MENU% +
+ %BODY% + %FOOTER% +
+ + + +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. + +
+ logo +

%SUBTITLE%

+

%TITLE%

+
+ +### Footer template + +The template file `footer.html` is generated on the bottom of all the wiki pages. + +
+

%TITLE% © %YEAR%

+

last modification %GEN_TIME%

+
+ +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: + + + +Or dynamic using the macro `MENUS`: + + + +## Advanced usages + +### Working with git + +nitiwiki allows you to store your wiki changes in git. +Using the option `--fetch` will update the local wiki instance +according to git informations found in the config file. + +Be sure to set `wiki.git_origin` and `wiki.git_branch` +in order to correctly pull changes. + +To automatically update your wiki when changes are pushed on the +origin repository you can use the following command in a git hook: + + nitiwiki --fetch --render + +### Working with a remote server + +Sometimes you cannot do what you want on your webserver (like setting crons). +For this purpose, nitiwiki provide a quick way to update a distant instance +through `ssh` using `rsync`. + +With the option `--rsync`, nitwiki will update a distant location with the +last rendered output. This way you can manually update your webserver +after changes or set a cron on a different server that you can control. + +Using the following command in your cron will update the web server instance +from git: + + nitiwiki --fetch --render --rsync + +Be sure to set `wiki.rsync_dir` in order to correctly push your changes. +When using `--rsync`, keep in mind that the rendered output must be configured +to work on the web server and set `wiki.root_url` accordingly. diff --git a/contrib/nitiwiki/examples/default/config.ini b/contrib/nitiwiki/examples/default/config.ini new file mode 100644 index 0000000..21e85ef --- /dev/null +++ b/contrib/nitiwiki/examples/default/config.ini @@ -0,0 +1,5 @@ +wiki.name=MyWiki +wiki.desc=proudly powered by nit +wiki.logo=assets/logo.png +wiki.root_url=http://localhost/ +wiki.root_dir=/full/path/to/your/wiki/root/dir diff --git a/contrib/nitiwiki/examples/default/pages/index.md b/contrib/nitiwiki/examples/default/pages/index.md new file mode 100644 index 0000000..b212ceb --- /dev/null +++ b/contrib/nitiwiki/examples/default/pages/index.md @@ -0,0 +1 @@ +# Welcome on nitiwiki diff --git a/contrib/nitiwiki/examples/default/templates/footer.html b/contrib/nitiwiki/examples/default/templates/footer.html new file mode 100644 index 0000000..7c8de99 --- /dev/null +++ b/contrib/nitiwiki/examples/default/templates/footer.html @@ -0,0 +1,4 @@ +
+

%TITLE% © %YEAR%

+

last modification %GEN_TIME%

+
diff --git a/contrib/nitiwiki/examples/default/templates/header.html b/contrib/nitiwiki/examples/default/templates/header.html new file mode 100644 index 0000000..d86aac9 --- /dev/null +++ b/contrib/nitiwiki/examples/default/templates/header.html @@ -0,0 +1,5 @@ +
+ logo +

%SUBTITLE%

+

%TITLE%

+
diff --git a/contrib/nitiwiki/examples/default/templates/menu.html b/contrib/nitiwiki/examples/default/templates/menu.html new file mode 100644 index 0000000..76a6f95 --- /dev/null +++ b/contrib/nitiwiki/examples/default/templates/menu.html @@ -0,0 +1,5 @@ + diff --git a/contrib/nitiwiki/examples/default/templates/template.html b/contrib/nitiwiki/examples/default/templates/template.html new file mode 100644 index 0000000..692a999 --- /dev/null +++ b/contrib/nitiwiki/examples/default/templates/template.html @@ -0,0 +1,15 @@ + + + + %TITLE% + + + + %HEADER% + %TOP_MENU% +
+ %BODY% + %FOOTER% +
+ + diff --git a/contrib/nitiwiki/examples/nitiwiki/assets/css/main.css b/contrib/nitiwiki/examples/nitiwiki/assets/css/main.css new file mode 100644 index 0000000..c00394a --- /dev/null +++ b/contrib/nitiwiki/examples/nitiwiki/assets/css/main.css @@ -0,0 +1,30 @@ +header { margin: 15px 0; } +header img { + padding: 10px; + float: left; +} +header h1, header h2 { display: none; } + +footer { + margin-top: 30px; + text-align: center; +} + +.menu { + border-top: 6px solid #47CA42; + background-color: #0d8921; +} + +.menu .navbar-brand, .menu a { color: #fff; } +.menu .navbar-nav>.active>a, .menu .navbar-nav>li>a:hover { background-color: #47CA42; } + +.sidebar { margin-top: 20px; } + +.summary a { color: #333; } +.summary a:hover { text-decoration: none; } +.summary a:hover, .summary li:active>a { color: #428bca; } +.summary li { padding: 2px 0; font-weight: bold } +.summary li li { font-size: 13px;} +.summary li li li { font-size: 12px; font-weight: normal } + +.breadcrumb { margin-top: 20px; } diff --git a/contrib/nitiwiki/examples/nitiwiki/assets/logo.png b/contrib/nitiwiki/examples/nitiwiki/assets/logo.png new file mode 100644 index 0000000..daccc80 Binary files /dev/null and b/contrib/nitiwiki/examples/nitiwiki/assets/logo.png differ diff --git a/contrib/nitiwiki/examples/nitiwiki/config.ini b/contrib/nitiwiki/examples/nitiwiki/config.ini new file mode 100644 index 0000000..16eaffe --- /dev/null +++ b/contrib/nitiwiki/examples/nitiwiki/config.ini @@ -0,0 +1,6 @@ +wiki.name=nitiwiki +wiki.desc=proudly powered by nit +wiki.logo=assets/logo.png +wiki.root_url=http://moz-code.org/nitiwiki/ +wiki.root_dir=. +wiki.rsync_dir=moz-code.org:nitiwiki/ diff --git a/contrib/nitiwiki/examples/nitiwiki/pages/index.md b/contrib/nitiwiki/examples/nitiwiki/pages/index.md new file mode 120000 index 0000000..8a33348 --- /dev/null +++ b/contrib/nitiwiki/examples/nitiwiki/pages/index.md @@ -0,0 +1 @@ +../../../README.md \ No newline at end of file diff --git a/contrib/nitiwiki/examples/nitiwiki/templates/footer.html b/contrib/nitiwiki/examples/nitiwiki/templates/footer.html new file mode 100644 index 0000000..94229ea --- /dev/null +++ b/contrib/nitiwiki/examples/nitiwiki/templates/footer.html @@ -0,0 +1,10 @@ +
+
+
+

%TITLE% © %YEAR%

+

last modification %GEN_TIME%

+

Proudly powered by + nit!

+
+
+
diff --git a/contrib/nitiwiki/examples/nitiwiki/templates/header.html b/contrib/nitiwiki/examples/nitiwiki/templates/header.html new file mode 100644 index 0000000..8b06ab8 --- /dev/null +++ b/contrib/nitiwiki/examples/nitiwiki/templates/header.html @@ -0,0 +1,9 @@ +
+
+
+ logo +

%SUBTITLE%

+

%TITLE%

+
+
+
diff --git a/contrib/nitiwiki/examples/nitiwiki/templates/menu.html b/contrib/nitiwiki/examples/nitiwiki/templates/menu.html new file mode 100644 index 0000000..1ff832e --- /dev/null +++ b/contrib/nitiwiki/examples/nitiwiki/templates/menu.html @@ -0,0 +1,20 @@ + diff --git a/contrib/nitiwiki/examples/nitiwiki/templates/template.html b/contrib/nitiwiki/examples/nitiwiki/templates/template.html new file mode 100644 index 0000000..7c43833 --- /dev/null +++ b/contrib/nitiwiki/examples/nitiwiki/templates/template.html @@ -0,0 +1,32 @@ + + + + + + + %TITLE% + + + + + + + + + + %HEADER% + %TOP_MENU% +
+
+ %BODY% +
+ %FOOTER% +
+ + + + + diff --git a/contrib/nitiwiki/src/nitiwiki.nit b/contrib/nitiwiki/src/nitiwiki.nit new file mode 100644 index 0000000..200f971 --- /dev/null +++ b/contrib/nitiwiki/src/nitiwiki.nit @@ -0,0 +1,163 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# A wiki engine based on markdown files and git. +module nitiwiki + +import wiki_html + +# Locate nit directory +private fun compute_nit_dir(opt_nit_dir: OptionString): String do + # the option has precedence + var res = opt_nit_dir.value + if res != null then + if not check_nit_dir(res) then + print "Fatal Error: --nit-dir does not seem to be a valid base Nit directory: {res}" + exit 1 + end + return res + end + + # then the environ variable has precedence + res = "NIT_DIR".environ + if not res.is_empty then + if not check_nit_dir(res) then + print "Fatal Error: NIT_DIR does not seem to be a valid base Nit directory: {res}" + exit 1 + end + return res + end + + # find the runpath of the program from argv[0] + res = "{sys.program_name.dirname}/.." + if check_nit_dir(res) then return res.simplify_path + + # find the runpath of the process from /proc + var exe = "/proc/self/exe" + if exe.file_exists then + res = exe.realpath + res = res.dirname.join_path("..") + if check_nit_dir(res) then return res.simplify_path + end + + # search in the PATH + var ps = "PATH".environ.split(":") + for p in ps do + res = p/".." + if check_nit_dir(res) then return res.simplify_path + end + + print "Fatal Error: Cannot locate a valid base nit directory. It is quite unexpected. Try to set the environment variable `NIT_DIR` or to use the `--nit-dir` option." + exit 1 + abort +end + +private fun check_nit_dir(res: String): Bool do + return res.file_exists and "{res}/src/nit.nit".file_exists +end + +var opt_help = new OptionBool("Display this help message", "-h", "--help") +var opt_verbose = new OptionCount("Verbose level", "-v") +var opt_config = new OptionString("Path to config.ini file", "-c", "--config") +var opt_init = new OptionBool("Initialize a new wiki in the current directory", "--init") +var opt_status = new OptionBool("Display wiki status", "-s", "--status") +var opt_render = new OptionBool("Render the out directory from markdown sources", "-r", "--render") +var opt_force = new OptionBool("Force render even if source files are unchanged", "-f", "--force") +var opt_clean = new OptionBool("Clean the output directory", "--clean") +var opt_rsync = new OptionBool("Synchronize outdir with distant wiki using rsync", "-s", "--rsync") +var opt_fetch = new OptionBool("Render local source from git repo", "--fetch") +var opt_nit_dir = new OptionString("Nit base directory", "--nit-dir") + +var context = new OptionContext +context.add_option(opt_help, opt_verbose, opt_config) +context.add_option(opt_init, opt_status, opt_render, opt_force) +context.add_option(opt_clean, opt_rsync, opt_fetch, opt_nit_dir) +context.parse(args) + +var config_filename = "config.ini" + +# --help +if opt_help.value then + context.usage + exit 0 +end + +# --init +if opt_init.value then + if config_filename.file_exists then + print "Already in a nitiwiki directory." + exit 0 + end + var nitiwiki_home = "{compute_nit_dir(opt_nit_dir)}/contrib/nitiwiki" + var tpl = "{nitiwiki_home}/examples/default/" + if not tpl.file_exists then + print "Cannot find {tpl} files." + print "Maybe your NIT_DIR is not set properly?" + print "You can initialize nitiwiki manually by copying the default skeletton here." + exit 1 + end + sys.system "cp -R {tpl}/* ." + print "Initialized new nitiwiki." + print "Set wiki settings by editing {config_filename}." + exit 0 +end + +# load config files + +# --config +var config_file = opt_config.value +if config_file == null then + config_file = config_filename +end + +if not config_file.file_exists then + print "Not in a nitiwiki directory." + print "Use --init to initialize one here." + exit 0 +end + +var config = new WikiConfig(config_file) +var wiki = new Nitiwiki(config) + +# --verbose +wiki.verbose_level = opt_verbose.value + +# --clean +if opt_clean.value then + wiki.clean +end + +# --fetch +if opt_fetch.value then + wiki.fetch +end + +# --render +if opt_render.value then + wiki.parse + # --force + wiki.force_render = opt_force.value + wiki.render +end + +# --rsync +if opt_rsync.value then + wiki.sync +end + +# --status +if opt_status.value or args.is_empty then + wiki.parse + wiki.status +end diff --git a/contrib/nitiwiki/src/wiki_base.nit b/contrib/nitiwiki/src/wiki_base.nit new file mode 100644 index 0000000..2c0276b --- /dev/null +++ b/contrib/nitiwiki/src/wiki_base.nit @@ -0,0 +1,744 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Base entities of a nitiwiki. +module wiki_base + +import template::macro +import markdown +import opts +import ini + +# A Nitiwiki instance. +# +# Nitiwiki provide all base services used by `WikiSection` and `WikiArticle`. +# It manages content and renders pages. +# +# Each nitiwiki instance is linked to a config file. +# This file show to `nitiwiki` that a wiki is present in the current directory. +# Without it, nitiwiki will consider the directory as empty. +class Nitiwiki + + # Wiki config object. + var config: WikiConfig + + # Default config filename. + var config_filename = "config.ini" + + # Force render on all file even if the source is unmodified. + var force_render = false is writable + + # Verbosity level. + var verbose_level = 0 is writable + + # Delete all the output files. + fun clean do + var out_dir = expand_path(config.root_dir, config.out_dir) + if out_dir.file_exists then out_dir.rmdir + end + + # Synchronize local output with the distant `WikiConfig::rsync_dir`. + fun sync do + var root = expand_path(config.root_dir, config.out_dir) + sys.system "rsync -vr --delete {root}/ {config.rsync_dir}" + end + + fun fetch do + sys.system "git pull {config.git_origin} {config.git_branch}" + end + + # Analyze wiki files from `dir` to build wiki entries. + # + # This method build a hierarchical structure of `WikiSection` and `WikiArticle` + # based on the markdown source structure. + fun parse do + var dir = expand_path(config.root_dir, config.source_dir) + root_section = new_section(dir) + var files = list_md_files(dir) + for file in files do + new_article(file) + end + end + + # Show wiki status. + fun status do + print "nitiWiki" + print "name: {config.wiki_name}" + print "config: {config.ini_file}" + print "url: {config.root_url}" + print "" + if root_section.is_dirty then + print "There is modified files:" + var paths = entries.keys.to_a + var s = new DefaultComparator + s.sort(paths) + for path in paths do + var entry = entries[path] + if not entry.is_dirty then continue + var name = entry.name + if entry.has_source then name = entry.src_path.to_s + if entry.is_new then + print " + {name}" + else + print " * {name}" + end + end + print "" + print "Use nitiwiki --render to render modified files" + else + print "Wiki is up-to-date" + print "" + print "Use nitiwiki --fetch to pull modification from origin" + print "Use nitiwiki --rsync to synchronize distant output" + end + end + + # Display msg if `level >= verbose_level` + fun message(msg: String, level: Int) do + if level <= verbose_level then print msg + end + + # List markdown source files from a directory. + fun list_md_files(dir: String): Array[String] do + var files = new Array[String] + var pipe = new IProcess("find", dir, "-name", "*.md") + while not pipe.eof do + var file = pipe.read_line + if file == "" then break # last line + file = file.substring(0, file.length - 1) # strip last oef + var name = file.basename(".md") + if name == "header" or name == "footer" or name == "menu" then continue + files.add file + end + pipe.close + pipe.wait + if pipe.status != 0 then exit 1 + var s = new DefaultComparator + s.sort(files) + return files + end + + # Does `src` have been modified since `target` creation? + # + # Always returns `true` if `--force` is on. + fun need_render(src, target: String): Bool do + if force_render then return true + if not target.file_exists then return true + return src.file_stat.mtime >= target.file_stat.mtime + end + + # Create a new `WikiSection`. + # + # `path` is used to determine the place in the wiki hierarchy. + protected fun new_section(path: String): WikiSection do + path = path.simplify_path + if entries.has_key(path) then return entries[path].as(WikiSection) + var root = expand_path(config.root_dir, config.source_dir) + var name = path.basename("") + var section = new WikiSection(self, name) + entries[path] = section + if path == root then return section + var ppath = path.dirname + if ppath != path then + var parent = new_section(ppath) + parent.add_child(section) + end + section.try_load_config + return section + end + + # Create a new `WikiArticle`. + # + # `path` is used to determine the ancestor sections. + protected fun new_article(path: String): WikiArticle do + if entries.has_key(path) then return entries[path].as(WikiArticle) + var article = new WikiArticle.from_source(self, path) + var section = new_section(path.dirname) + section.add_child(article) + entries[path] = article + return article + end + + # Wiki entries found in the last `lookup_hierarchy`. + var entries = new HashMap[String, WikiEntry] + + # The root `WikiSection` of the site found in the last `lookup_hierarchy`. + var root_section: WikiSection is noinit + + # Does a template named `name` exists for this wiki? + fun has_template(name: String): Bool do + return expand_path(config.root_dir, config.templates_dir, name).file_exists + end + + # Load a template file as a `TemplateString`. + # + # REQUIRE: `has_template` + fun load_template(name: String): TemplateString do + assert has_template(name) + var file = expand_path(config.root_dir, config.templates_dir, name) + var tpl = new TemplateString.from_file(file) + if tpl.has_macro("ROOT_URL") then + tpl.replace("ROOT_URL", config.root_url) + end + if tpl.has_macro("TITLE") then + tpl.replace("TITLE", config.wiki_name) + end + if tpl.has_macro("SUBTITLE") then + tpl.replace("SUBTITLE", config.wiki_desc) + end + if tpl.has_macro("LOGO") then + tpl.replace("LOGO", config.wiki_logo) + end + return tpl + end + + # Join `parts` as a path and simplify it + fun expand_path(parts: String...): String do + var path = "" + for part in parts do + path = path.join_path(part) + end + return path.simplify_path + end + + fun pretty_name(name: String): String do + name = name.replace("_", " ") + name = name.capitalized + return name + end +end + +# A wiki is composed of hierarchical entries. +abstract class WikiEntry + + # `Nitiwiki` this entry belongs to. + var wiki: Nitiwiki + + # Entry data + + # Entry internal name. + # + # Mainly used in urls. + var name: String + + # Displayed title for `self`. + # + # If `self` is the root entry then display the wiki `WikiConfig::wiki_name` instead. + fun title: String do + if is_root then return wiki.config.wiki_name + return wiki.pretty_name(name) + end + + # Is this section rendered from a source document? + # + # Source is an abstract concept at this level. + # It can represent a directory, a source file, + # a part of a file, everything needed to + # extend this base framework. + fun has_source: Bool do return src_path != null + + # Entry creation time. + # + # Returns `-1` if not `has_source`. + fun create_time: Int do + if not has_source then return -1 + return src_full_path.file_stat.ctime + end + + # Entry last modification time. + # + # Returns `-1` if not `has_source`. + fun last_edit_time: Int do + if not has_source then return -1 + return src_full_path.file_stat.mtime + end + + # Entry list rendering time. + # + # Returns `-1` if `is_new`. + fun last_render_time: Int do + if is_new then return -1 + return out_full_path.file_stat.mtime + end + + # Entries hierarchy + + # Type of the parent entry. + type PARENT: WikiEntry + + # Parent entry if any. + var parent: nullable PARENT = null + + # Does `self` have a parent? + fun is_root: Bool do return parent == null + + # Children labelled by `name`. + var children = new HashMap[String, WikiEntry] + + # Does `self` have a child nammed `name`? + fun has_child(name: String): Bool do return children.keys.has(name) + + # Retrieve the child called `name`. + fun child(name: String): WikiEntry do return children[name] + + # Add a sub-entry to `self`. + fun add_child(entry: WikiEntry) do + entry.parent = self + children[entry.name] = entry + end + + # Paths and urls + + # Breadcrumbs from the `Nitiwiki::root_section` to `self`. + # + # Result is returned as an array containg ordered entries: + # `breadcrumbs.first` is the root entry and + # `breadcrumbs.last == self` + fun breadcrumbs: Array[WikiEntry] is cached do + var path = new Array[WikiEntry] + var entry: nullable WikiEntry = self + while entry != null and not entry.is_root do + path.add entry + entry = entry.parent + end + return path.reversed + end + + # Relative path from `wiki.config.root_dir` to source if any. + fun src_path: nullable String is abstract + + # Absolute path to the source if any. + fun src_full_path: nullable String do + var src = src_path + if src == null then return null + return wiki.config.root_dir.join_path(src) + end + + # Relative path from `wiki.config.root_dir` to rendered output. + # + # Like `src_path`, this method can represent a + # directory or a file. + fun out_path: String is abstract + + # Absolute path to the output. + fun out_full_path: String do return wiki.config.root_dir.join_path(out_path) + + # Rendering + + # Does `self` have already been rendered? + fun is_new: Bool do return not out_full_path.file_exists + + # Does `self` rendered output is outdated? + # + # Returns `true` if `is_new` then check in children. + fun is_dirty: Bool do + if is_new then return true + if has_source then + if last_edit_time >= last_render_time then return true + end + for child in children.values do + if child.is_dirty then return true + end + return false + end + + # Render `self` and `children` is needed. + fun render do for child in children.values do child.render + + # Templating + + # Template file for `self`. + # + # Each entity can use a custom template. + # By default the template is inherited from the parent. + # + # If the root does not have a custom template, + # then returns the main wiki template file. + fun template_file: String do + if is_root then return wiki.config.template_file + return parent.template_file + end + + # Header template file for `self`. + # + # Behave like `template_file`. + fun header_file: String do + if is_root then return wiki.config.header_file + return parent.header_file + end + + # Footer template file for `self`. + # + # Behave like `template_file`. + fun footer_file: String do + if is_root then return wiki.config.footer_file + return parent.footer_file + end + + # Menu template file for `self`. + # + # Behave like `template_file`. + fun menu_file: String do + if is_root then return wiki.config.menu_file + return parent.menu_file + end + + # Display the entry `name`. + redef fun to_s do return name +end + +# Each WikiSection is related to a source directory. +# +# A section can contain other sub-sections or pages. +class WikiSection + super WikiEntry + + # A section can only have another section as parent. + redef type PARENT: WikiSection + + redef fun title do + if has_config then + var title = config.title + if title != null then return title + end + return super + end + + # Is this section hidden? + # + # Hidden section are rendered but not linked in menus. + fun is_hidden: Bool do + if has_config then return config.is_hidden + return false + end + + # Source directory. + redef fun src_path: String do + if parent == null then + return wiki.config.source_dir + else + return wiki.expand_path(parent.src_path, name) + end + end + + # Config + + # Custom configuration file for this section. + var config: nullable SectionConfig = null + + # Does this section have its own config file? + fun has_config: Bool do return config != null + + # Try to load the config file for this section. + private fun try_load_config do + var cfile = wiki.expand_path(wiki.config.root_dir, src_path, wiki.config_filename) + if not cfile.file_exists then return + wiki.message("Custom config for section {name}", 2) + config = new SectionConfig(cfile) + end + + # Templating + + # Also check custom config. + redef fun template_file do + if has_config then + var tpl = config.template_file + if tpl != null then return tpl + end + if is_root then return wiki.config.template_file + return parent.template_file + end + + # Also check custom config. + redef fun header_file do + if has_config then + var tpl = config.header_file + if tpl != null then return tpl + end + if is_root then return wiki.config.header_file + return parent.header_file + end + + # Also check custom config. + redef fun footer_file do + if has_config then + var tpl = config.footer_file + if tpl != null then return tpl + end + if is_root then return wiki.config.footer_file + return parent.footer_file + end + + # Also check custom config. + redef fun menu_file do + if has_config then + var tpl = config.menu_file + if tpl != null then return tpl + end + if is_root then return wiki.config.menu_file + return parent.menu_file + end +end + +# Each WikiArticle is related to a HTML file. +# +# Article can be created from scratch using this API or +# automatically from a markdown source file (see: `from_source`). +class WikiArticle + super WikiEntry + + # Articles can only have `WikiSection` as parents. + redef type PARENT: WikiSection + + redef fun title: String do + if name == "index" and parent != null then return parent.title + return super + end + + # Page content. + # + # What you want to be displayed in the page. + var content: nullable Streamable = null + + # Headlines ids and titles. + var headlines = new ArrayMap[String, HeadLine] + + # Create a new articleu sing a markdown source file. + init from_source(wiki: Nitiwiki, md_file: String) do + src_full_path = md_file + init(wiki, md_file.basename(".md")) + var md_proc = new MarkdownProcessor + content = md_proc.process(md) + headlines = md_proc.emitter.decorator.headlines + end + + redef var src_full_path: nullable String = null + + redef fun src_path do + if src_full_path == null then return null + return src_full_path.substring_from(wiki.config.root_dir.length) + end + + # The page markdown source content. + # + # Extract the markdown text from `source_file`. + # + # REQUIRE: `has_source`. + fun md: String is cached do + assert has_source + var file = new IFStream.open(src_full_path.to_s) + var md = file.read_all + file.close + return md + end + + # Returns true if has source and + # `last_edit_date` > 'last_render_date'. + redef fun is_dirty do + if super then return true + if has_source then + return wiki.need_render(src_full_path.to_s, out_full_path) + end + return false + end + + redef fun to_s do return "{name} ({parent or else "null"})" +end + +# Wiki configuration class. +# +# This class provides services that ensure static typing when accessing the `config.ini` file. +class WikiConfig + super ConfigTree + + # Returns the config value at `key` or return `default` if no key was found. + private fun value_or_default(key: String, default: String): String do + if not has_key(key) then return default + return self[key] + end + + # Site name displayed. + # + # The title is used as home title and in headers. + # + # * key: `wiki.name` + # * default: `MyWiki` + fun wiki_name: String is cached do return value_or_default("wiki.name", "MyWiki") + + # Site description. + # + # Displayed in header. + # + # * key: `wiki.desc` + # * default: `` + fun wiki_desc: String is cached do return value_or_default("wiki.desc", "") + + # Site logo url. + # + # Url of the image to be displayed in header. + # + # * key: `wiki.logo` + # * default: `` + fun wiki_logo: String is cached do return value_or_default("wiki.logo", "") + + # Root url of the wiki. + # + # * key: `wiki.root_url` + # * default: `http://localhost/` + fun root_url: String is cached do return value_or_default("wiki.root_url", "http://localhost/") + + + # Root directory of the wiki. + # + # Directory where the wiki files are stored locally. + # + # * key: `wiki.root_dir` + # * default: `./` + fun root_dir: String is cached do return value_or_default("wiki.root_dir", "./").simplify_path + + # Pages directory. + # + # Directory where markdown source files are stored. + # + # * key: `wiki.source_dir + # * default: `pages/` + fun source_dir: String is cached do + return value_or_default("wiki.source_dir", "pages/").simplify_path + end + + # Output directory. + # + # Directory where public wiki files are generated. + # **This path MUST be relative to `root_dir`.** + # + # * key: `wiki.out_dir` + # * default: `out/` + fun out_dir: String is cached do return value_or_default("wiki.out_dir", "out/").simplify_path + + # Asset files directory. + # + # Directory where public assets like JS scripts or CSS files are stored. + # **This path MUST be relative to `root_dir`.** + # + # * key: `wiki.assets_dir` + # * default: `assets/` + fun assets_dir: String is cached do + return value_or_default("wiki.assets_dir", "assets/").simplify_path + end + + # Template files directory. + # + # Directory where template used in HTML generation are stored. + # **This path MUST be relative to `root_dir`.** + # + # * key: `wiki.templates_dir` + # * default: `templates/` + fun templates_dir: String is cached do + return value_or_default("wiki.templates_dir", "templates/").simplify_path + end + + # Main template file. + # + # The main template is used to specify the overall structure of a page. + # + # * key: `wiki.template` + # * default: `template.html` + fun template_file: String is cached do + return value_or_default("wiki.template", "template.html") + end + + # Main header template file. + # + # Used to specify the structure of the page header. + # This is generally the place where you want to put your logo and wiki title. + # + # * key: `wiki.header` + # * default: `header.html` + fun header_file: String is cached do + return value_or_default("wiki.header", "header.html") + end + + # Main menu template file. + # + # Used to specify the menu structure. + # + # * key: `wiki.menu` + # * default: `menu.html` + fun menu_file: String is cached do + return value_or_default("wiki.menu", "menu.html") + end + + # Main footer file. + # + # The main footer is used to specify the structure of the page footer. + # This is generally the place where you want to put your copyright. + # + # * key: `wiki.footer` + # * default: `footer.html` + fun footer_file: String is cached do + return value_or_default("wiki.footer", "footer.html") + end + + # Directory used by rsync to upload wiki files. + # + # This information is used to update your distant wiki files (like the webserver). + # + # * key: `wiki.rsync_dir` + # * default: `` + fun rsync_dir: String is cached do return value_or_default("wiki.rsync_dir", "") + + # Remote repository used to pull modifications on sources. + # + # * key: `wiki.git_origin` + # * default: `origin` + fun git_origin: String is cached do return value_or_default("wiki.git_origin", "origin") + + # Remote branch used to pull modifications on sources. + # + # * key: `wiki.git_branch` + # * default: `master` + fun git_branch: String is cached do return value_or_default("wiki.git_branch", "master") +end + +# WikiSection custom configuration. +# +# Each section can provide its own config file to customize +# appearance or behavior. +class SectionConfig + super ConfigTree + + # Returns the config value at `key` or `null` if no key was found. + private fun value_or_null(key: String): nullable String do + if not has_key(key) then return null + return self[key] + end + + # Is this section hidden in sitemap and trees and menus? + fun is_hidden: Bool do return value_or_null("section.hidden") == "true" + + # Custom section title if any. + fun title: nullable String do return value_or_null("section.title") + + # Custom template file if any. + fun template_file: nullable String do return value_or_null("section.template") + + # Custom header file if any. + fun header_file: nullable String do return value_or_null("section.header") + + # Custom menu file if any. + fun menu_file: nullable String do return value_or_null("section.menu") + + # Custom footer file if any. + fun footer_file: nullable String do return value_or_null("section.footer") +end diff --git a/contrib/nitiwiki/src/wiki_html.nit b/contrib/nitiwiki/src/wiki_html.nit new file mode 100644 index 0000000..c984bc8 --- /dev/null +++ b/contrib/nitiwiki/src/wiki_html.nit @@ -0,0 +1,435 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# HTML wiki rendering +module wiki_html + +import wiki_base + +redef class Nitiwiki + + # Render HTML output looking for changes in the markdown sources. + fun render do + if not root_section.is_dirty and not force_render then return + var out_dir = expand_path(config.root_dir, config.out_dir) + out_dir.mkdir + copy_assets + root_section.add_child make_sitemap + root_section.render + end + + # Copy the asset directory to the (public) output directory. + private fun copy_assets do + var src = expand_path(config.root_dir, config.assets_dir) + var out = expand_path(config.root_dir, config.out_dir) + if need_render(src, expand_path(out, config.assets_dir)) then + if src.file_exists then sys.system "cp -R {src} {out}" + end + end + + # Build the wiki sitemap page. + private fun make_sitemap: WikiSitemap do + var sitemap = new WikiSitemap(self, "sitemap") + sitemap.is_dirty = true + return sitemap + end +end + +redef class WikiEntry + + # Url to `self` once generated. + fun url: String do return wiki.config.root_url.join_path(breadcrumbs.join("/")) + + # Get a `` template link to `self` + fun tpl_link: Streamable do + return "{title}" + 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: + # + #
    + #
  • section 1
  • + #
  • section 2 + #
      + #
    • section 2.1
    • + #
    • section 2.2
    • + #
    + #
  • + #
+ 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 "
  • " + out.add tpl_link + if (limit < 0 or count < limit) and + (children.length > 1 or (children.length == 1)) then + out.add "
      " + for child in children.values do + if child == index then continue + if child isa WikiArticle then + out.add "
    • " + out.add child.tpl_link + out.add "
    • " + else if child isa WikiSection and not child.is_hidden then + out.add child.tpl_tree_intern(limit, count + 1) + end + end + out.add "
    " + end + out.add "
  • " + 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 "
      " + 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 "
    • {title}" + iter.next + if iter.is_ok then + if iter.item.level > hl.level then + tpl.add "
        " + else if iter.item.level < hl.level then + tpl.add "" + tpl.add "
      " + end + else + tpl.add "
    • " + end + end + tpl.add "
    " + 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 "" + items.add child.tpl_link + items.add "" + 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 "
    " + add sidebar.as(not null) + add "
    " + add "
    " + else + add "
    " + end + if body != null then + add "
    " + if breadcrumbs != null then + add breadcrumbs.as(not null) + end + if title != null then + add "

    " + add title.as(not null) + add "

    " + end + add body.as(not null) + add "
    " + end + add "
    " + 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 "
    " + add block + add "
    " + 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 "
      " + for entry in path do + if entry == path.last then + add "
    1. " + add entry.title + add "
    2. " + else + if article.parent == entry and article.is_index then continue + add "
    3. " + add entry.tpl_link + add "
    4. " + end + end + add "
    " + 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 "
      " + add root.tpl_tree(-1) + add "
    " + end +end diff --git a/contrib/nitiwiki/tests/Makefile b/contrib/nitiwiki/tests/Makefile new file mode 100644 index 0000000..0dce637 --- /dev/null +++ b/contrib/nitiwiki/tests/Makefile @@ -0,0 +1,21 @@ +# Copyright 2013 Alexandre Terrasa . +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +all: tests + +tests: clean + ./tests.sh + +clean: + rm -rf out/ diff --git a/contrib/nitiwiki/tests/nitiwiki.args b/contrib/nitiwiki/tests/nitiwiki.args new file mode 100644 index 0000000..b5abeca --- /dev/null +++ b/contrib/nitiwiki/tests/nitiwiki.args @@ -0,0 +1 @@ +nitiwiki diff --git a/contrib/nitiwiki/tests/nitiwiki_render.args b/contrib/nitiwiki/tests/nitiwiki_render.args new file mode 100644 index 0000000..6f7050e --- /dev/null +++ b/contrib/nitiwiki/tests/nitiwiki_render.args @@ -0,0 +1 @@ +nitiwiki --config wiki1/config.ini --clean --render -v diff --git a/contrib/nitiwiki/tests/nitiwiki_status.args b/contrib/nitiwiki/tests/nitiwiki_status.args new file mode 100644 index 0000000..aa96d13 --- /dev/null +++ b/contrib/nitiwiki/tests/nitiwiki_status.args @@ -0,0 +1 @@ +nitiwiki --config wiki1/config.ini --clean --status diff --git a/contrib/nitiwiki/tests/res/nitiwiki.res b/contrib/nitiwiki/tests/res/nitiwiki.res new file mode 100644 index 0000000..232b5ef --- /dev/null +++ b/contrib/nitiwiki/tests/res/nitiwiki.res @@ -0,0 +1,2 @@ +Not in a nitiwiki directory. +Use --init to initialize one here. diff --git a/contrib/nitiwiki/tests/res/nitiwiki_render.res b/contrib/nitiwiki/tests/res/nitiwiki_render.res new file mode 100644 index 0000000..adf25a7 --- /dev/null +++ b/contrib/nitiwiki/tests/res/nitiwiki_render.res @@ -0,0 +1 @@ +Render section out diff --git a/contrib/nitiwiki/tests/res/nitiwiki_status.res b/contrib/nitiwiki/tests/res/nitiwiki_status.res new file mode 100644 index 0000000..936a764 --- /dev/null +++ b/contrib/nitiwiki/tests/res/nitiwiki_status.res @@ -0,0 +1,10 @@ +nitiWiki +name: wiki1 +config: wiki1/config.ini +url: http://localhost/ + +There is modified files: + + pages + + /pages/index.md + +Use nitiwiki --render to render modified files diff --git a/contrib/nitiwiki/tests/tests.sh b/contrib/nitiwiki/tests/tests.sh new file mode 100755 index 0000000..2acbe94 --- /dev/null +++ b/contrib/nitiwiki/tests/tests.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2014 Alexandre Terrasa . +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +BIN=../bin +OUT=./out +RES=./res + +# check args files +test_args() +{ + local test="$1" + local args=`cat $test.args` + local outdir=$OUT/$test.files + + echo $BIN/$args > $OUT/$test.bin + chmod +x $OUT/$test.bin + OUTDIR=$outdir $OUT/$test.bin > $OUT/$test.res 2> $OUT/$test.err + + if [ -r $outdir ]; then + ls -aR $outdir >> $OUT/$test.res + fi + + diff $OUT/$test.res $RES/$test.res > $OUT/$test.diff 2> /dev/null +} + +# return +# 0 if the sav not exists +# 1 if the file does match +# 2 if the file does not match +check_result() { + local test="$1" + + if [ ! -r "$RES/$test.res" ]; then + return 0 + elif [ ! -s $OUT/$test.diff ]; then + return 1 + else + return 2 + fi +} + +echo "Testing..." +echo "" + +rm -rf $OUT 2>/dev/null +mkdir $OUT 2>/dev/null + +all=0 +ok=0 +ko=0 +sk=0 + +for file in `ls *.args`; do + ((all++)) + test="${file%.*}" + echo -n "* $test: " + + test_args $test + check_result $test + + case "$?" in + 0) + echo "skip ($test.res not found)" + ((sk++)) + continue;; + 1) + echo "success" + ((ok++)) + ;; + 2) + echo "error (diff $OUT/$test.res $RES/$test.res)" + ((ko++)) + ;; + esac +done +echo "" +echo "==> success $ok/$all ($ko tests failed, $sk skipped)" diff --git a/contrib/nitiwiki/tests/wiki1/assets/css/main.css b/contrib/nitiwiki/tests/wiki1/assets/css/main.css new file mode 100644 index 0000000..d58b78d --- /dev/null +++ b/contrib/nitiwiki/tests/wiki1/assets/css/main.css @@ -0,0 +1 @@ +dummy { margin: 0 } diff --git a/contrib/nitiwiki/tests/wiki1/config.ini b/contrib/nitiwiki/tests/wiki1/config.ini new file mode 100644 index 0000000..c24bdd7 --- /dev/null +++ b/contrib/nitiwiki/tests/wiki1/config.ini @@ -0,0 +1,2 @@ +wiki.name=wiki1 +wiki.root_dir=wiki1/ diff --git a/contrib/nitiwiki/tests/wiki1/config2.ini b/contrib/nitiwiki/tests/wiki1/config2.ini new file mode 100644 index 0000000..4ebb07b --- /dev/null +++ b/contrib/nitiwiki/tests/wiki1/config2.ini @@ -0,0 +1,3 @@ +wiki.name=wiki2 +wiki.desc=the one used by nit/tests.sh +wiki.root_dir=../contrib/nitiwiki/tests/wiki1/ diff --git a/contrib/nitiwiki/tests/wiki1/pages/index.md b/contrib/nitiwiki/tests/wiki1/pages/index.md new file mode 100644 index 0000000..cc0be1e --- /dev/null +++ b/contrib/nitiwiki/tests/wiki1/pages/index.md @@ -0,0 +1 @@ +# Hello World! diff --git a/contrib/nitiwiki/tests/wiki1/templates/footer.html b/contrib/nitiwiki/tests/wiki1/templates/footer.html new file mode 100644 index 0000000..7506b39 --- /dev/null +++ b/contrib/nitiwiki/tests/wiki1/templates/footer.html @@ -0,0 +1,10 @@ + diff --git a/contrib/nitiwiki/tests/wiki1/templates/header.html b/contrib/nitiwiki/tests/wiki1/templates/header.html new file mode 100644 index 0000000..1cb06bf --- /dev/null +++ b/contrib/nitiwiki/tests/wiki1/templates/header.html @@ -0,0 +1,9 @@ +
    +
    +
    + logo +

    %SUBTITLE%

    +

    %TITLE%

    +
    +
    +
    diff --git a/contrib/nitiwiki/tests/wiki1/templates/menu.html b/contrib/nitiwiki/tests/wiki1/templates/menu.html new file mode 100644 index 0000000..1ff832e --- /dev/null +++ b/contrib/nitiwiki/tests/wiki1/templates/menu.html @@ -0,0 +1,20 @@ + diff --git a/contrib/nitiwiki/tests/wiki1/templates/template.html b/contrib/nitiwiki/tests/wiki1/templates/template.html new file mode 100644 index 0000000..6b3b126 --- /dev/null +++ b/contrib/nitiwiki/tests/wiki1/templates/template.html @@ -0,0 +1,32 @@ + + + + + + + %TITLE% + + + + + + + + + + %HEADER% + %TOP_MENU% +
    +
    + %BODY% +
    + %FOOTER% +
    + + + + + diff --git a/src/doc/doc_model.nit b/src/doc/doc_model.nit index 4141f75..0a0e60e 100644 --- a/src/doc/doc_model.nit +++ b/src/doc/doc_model.nit @@ -16,7 +16,7 @@ module doc_model import model_utils -import markdown +import docdown import doc_templates import ordered_tree import model_ext diff --git a/src/markdown.nit b/src/docdown.nit similarity index 99% rename from src/markdown.nit rename to src/docdown.nit index e254480..7df46fe 100644 --- a/src/markdown.nit +++ b/src/docdown.nit @@ -13,7 +13,7 @@ # limitations under the License. # Transform Nit verbatim documentation into HTML -module markdown +module docdown private import parser import html diff --git a/src/test_docdown.nit b/src/test_docdown.nit index c546c2e..71a8669 100644 --- a/src/test_docdown.nit +++ b/src/test_docdown.nit @@ -17,7 +17,7 @@ module test_docdown import modelize import highlight -import markdown +import docdown redef class ModelBuilder fun test_markdown(page: HTMLTag, mmodule: MModule) diff --git a/src/testing/testing_doc.nit b/src/testing/testing_doc.nit index 7bb39ca..e612902 100644 --- a/src/testing/testing_doc.nit +++ b/src/testing/testing_doc.nit @@ -16,7 +16,7 @@ module testing_doc import testing_base -intrude import markdown +intrude import docdown # Extractor, Executor and Reporter for the tests in a module class NitUnitExecutor diff --git a/tests/Makefile b/tests/Makefile index 24c0604..cdd5559 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,5 +1,5 @@ -PROGS=*.nit ../examples/*.nit ../examples/leapfrog/leapfrog.nit ../examples/shoot/shoot_logic.nit ../contrib/pep8analysis/src/pep8analysis ../lib/*.nit ../src/nitdoc.nit ../src/test_parser.nit ../src/nit.nit ../src/nitmetrics.nit ../src/nitg.nit +PROGS=*.nit ../examples/*.nit ../examples/leapfrog/leapfrog.nit ../examples/shoot/shoot_logic.nit ../contrib/pep8analysis/src/pep8analysis ../contrib/nitiwiki/src/nitiwiki ../lib/*.nit ../src/nitdoc.nit ../src/test_parser.nit ../src/nit.nit ../src/nitmetrics.nit ../src/nitg.nit all: niti nitg-g nitg-s diff --git a/tests/listfull.sh b/tests/listfull.sh index 778d0d3..dbd92f9 100755 --- a/tests/listfull.sh +++ b/tests/listfull.sh @@ -14,4 +14,5 @@ printf "%s\n" "$@" \ ../lib/*/examples/*.nit \ ../contrib/friendz/src/solver_cmd.nit \ ../contrib/pep8analysis/src/pep8analysis.nit \ + ../contrib/nitiwiki/src/nitiwiki.nit \ *.nit diff --git a/tests/nitiwiki.args b/tests/nitiwiki.args new file mode 100644 index 0000000..ae082af --- /dev/null +++ b/tests/nitiwiki.args @@ -0,0 +1,2 @@ +nitiwiki --config ../contrib/nitiwiki/tests/wiki1/config2.ini --clean --status +nitiwiki --config ../contrib/nitiwiki/tests/wiki1/config2.ini --clean --render -v diff --git a/tests/sav/nitiwiki.res b/tests/sav/nitiwiki.res new file mode 100644 index 0000000..232b5ef --- /dev/null +++ b/tests/sav/nitiwiki.res @@ -0,0 +1,2 @@ +Not in a nitiwiki directory. +Use --init to initialize one here. diff --git a/tests/sav/nitiwiki_args1.res b/tests/sav/nitiwiki_args1.res new file mode 100644 index 0000000..3f5641d --- /dev/null +++ b/tests/sav/nitiwiki_args1.res @@ -0,0 +1,10 @@ +nitiWiki +name: wiki2 +config: ../contrib/nitiwiki/tests/wiki1/config2.ini +url: http://localhost/ + +There is modified files: + + pages + + /pages/index.md + +Use nitiwiki --render to render modified files diff --git a/tests/sav/nitiwiki_args2.res b/tests/sav/nitiwiki_args2.res new file mode 100644 index 0000000..adf25a7 --- /dev/null +++ b/tests/sav/nitiwiki_args2.res @@ -0,0 +1 @@ +Render section out