module wiki_base
import template::macro
-import markdown
import opts
import ini
# 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}"
+ var rsync_dir = config.rsync_dir
+ if rsync_dir == "" then
+ message("Error: configure `wiki.rsync_dir` to use rsync.", 0)
+ return
+ end
+ sys.system "rsync -vr --delete -- {root.escape_to_sh}/ {rsync_dir.escape_to_sh}"
end
# Pull data from git repository.
fun fetch do
- sys.system "git pull {config.git_origin} {config.git_branch}"
+ sys.system "git pull {config.git_origin.escape_to_sh} {config.git_branch.escape_to_sh}"
end
# Analyze wiki files from `dir` to build wiki entries.
end
end
+ # Render output.
+ fun render do 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 entry = entries[path]
if not entry.is_dirty then continue
var name = entry.name
- if entry.has_source then name = entry.src_path.to_s
+ if entry.has_source then name = entry.src_path.as(not null)
if entry.is_new then
print " + {name}"
else
end
end
- # Display msg if `level >= verbose_level`
+ # 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 ProcessReader("find", dir, "-name", "*.md")
+ var pipe = new ProcessReader("find", dir, "-name", "*.{config.md_ext}")
while not pipe.eof do
var file = pipe.read_line
if file == "" then break # last line
- var name = file.basename(".md")
+ var name = file.basename(".{config.md_ext}")
if name == "header" or name == "footer" or name == "menu" then continue
files.add file
end
fun need_render(src, target: String): Bool do
if force_render then return true
if not target.file_exists then return true
- return src.file_stat.mtime >= target.file_stat.mtime
+ return src.file_stat.as(not null).mtime >= target.file_stat.as(not null).mtime
end
# Create a new `WikiSection`.
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 name = path.basename
var section = new WikiSection(self, name)
entries[path] = section
if path == root then return section
# `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)
+ message("Found article `{path}`", 2)
var article = new WikiArticle.from_source(self, path)
var section = new_section(path.dirname)
section.add_child(article)
#
# REQUIRE: `has_template`
fun load_template(name: String): TemplateString do
- assert has_template(name)
+ if not has_template(name) then
+ message("Error: can't load template `{name}`", 0)
+ exit 1
+ end
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
return tpl
end
+ # Does a sideblock named `name` exists for this wiki?
+ fun has_sideblock(name: String): Bool do
+ name = "{name}.{config.md_ext}"
+ return expand_path(config.root_dir, config.sidebar_dir, name).file_exists
+ end
+
+ # Load a markdown block with `name` from `WikiConfig::sidebar_dir`.
+ private fun load_sideblock(name: String): nullable String do
+ if not has_sideblock(name) then
+ message("Error: can't load sideblock `{name}`", 0)
+ return null
+ end
+ name = "{name}.{config.md_ext}"
+ var path = expand_path(config.root_dir, config.sidebar_dir, name)
+ var file = new FileReader.open(path)
+ var res = file.read_all
+ file.close
+ return res
+ end
+
# Join `parts` as a path and simplify it
fun expand_path(parts: String...): String do
var path = ""
# Used to translate ids in beautiful page names.
fun pretty_name(name: String): String do
name = name.replace("_", " ")
- name = name.capitalized
+ name = name.capitalized(keep_upper=true)
return name
end
end
# Returns `-1` if not `has_source`.
fun create_time: Int do
if not has_source then return -1
- return src_full_path.file_stat.ctime
+ return src_full_path.as(not null).file_stat.as(not null).ctime
end
# Entry last modification time.
# Returns `-1` if not `has_source`.
fun last_edit_time: Int do
if not has_source then return -1
- return src_full_path.file_stat.mtime
+ return src_full_path.as(not null).file_stat.as(not null).mtime
end
# Entry list rendering time.
# Returns `-1` if `is_new`.
fun last_render_time: Int do
if is_new then return -1
- return out_full_path.file_stat.mtime
+ return out_full_path.file_stat.as(not null).mtime
end
# Entries hierarchy
return path.reversed
end
+ # Sidebar relative to this wiki entry.
+ var sidebar = new WikiSidebar(self)
+
# Relative path from `wiki.config.root_dir` to source if any.
fun src_path: nullable String is abstract
# then returns the main wiki template file.
fun template_file: String do
if is_root then return wiki.config.template_file
- return parent.template_file
+ return parent.as(not null).template_file
end
# Header template file for `self`.
# Behave like `template_file`.
fun header_file: String do
if is_root then return wiki.config.header_file
- return parent.header_file
+ return parent.as(not null).header_file
end
# Footer template file for `self`.
# Behave like `template_file`.
fun footer_file: String do
if is_root then return wiki.config.footer_file
- return parent.footer_file
+ return parent.as(not null).footer_file
end
# Menu template file for `self`.
# Behave like `template_file`.
fun menu_file: String do
if is_root then return wiki.config.menu_file
- return parent.menu_file
+ return parent.as(not null).menu_file
end
# Display the entry `name`.
redef fun title do
if has_config then
- var title = config.title
+ var title = config.as(not null).title
if title != null then return title
end
return super
#
# Hidden section are rendered but not linked in menus.
fun is_hidden: Bool do
- if has_config then return config.is_hidden
+ if has_config then return config.as(not null).is_hidden
return false
end
if parent == null then
return wiki.config.source_dir
else
- return wiki.expand_path(parent.src_path, name)
+ return wiki.expand_path(parent.as(not null).src_path, name)
end
end
# Also check custom config.
redef fun template_file do
if has_config then
- var tpl = config.template_file
+ var tpl = config.as(not null).template_file
if tpl != null then return tpl
end
if is_root then return wiki.config.template_file
- return parent.template_file
+ return parent.as(not null).template_file
end
# Also check custom config.
redef fun header_file do
if has_config then
- var tpl = config.header_file
+ var tpl = config.as(not null).header_file
if tpl != null then return tpl
end
if is_root then return wiki.config.header_file
- return parent.header_file
+ return parent.as(not null).header_file
end
# Also check custom config.
redef fun footer_file do
if has_config then
- var tpl = config.footer_file
+ var tpl = config.as(not null).footer_file
if tpl != null then return tpl
end
if is_root then return wiki.config.footer_file
- return parent.footer_file
+ return parent.as(not null).footer_file
end
# Also check custom config.
redef fun menu_file do
if has_config then
- var tpl = config.menu_file
+ var tpl = config.as(not null).menu_file
if tpl != null then return tpl
end
if is_root then return wiki.config.menu_file
- return parent.menu_file
+ return parent.as(not null).menu_file
end
end
# Articles can only have `WikiSection` as parents.
redef type PARENT: WikiSection
- redef fun title: String do
+ redef fun title do
+ var parent = self.parent
if name == "index" and parent != null then return parent.title
return super
end
# Page content.
#
# What you want to be displayed in the page.
- var content: nullable Writable = null
-
- # Headlines ids and titles.
- var headlines = new ArrayMap[String, HeadLine]
+ var content: nullable Writable = null is writable
- # Create a new articleu sing a markdown source file.
+ # Create a new article using 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
+ init(wiki, md_file.basename(".{wiki.config.md_ext}"))
+ content = md
end
- redef var src_full_path: nullable String = null
+ redef var src_full_path = null
redef fun src_path do
+ var src_full_path = self.src_full_path
if src_full_path == null then return null
- return src_full_path.substring_from(wiki.config.root_dir.length)
+ var res = wiki.config.root_dir.relpath(src_full_path)
+ return res
end
# The page markdown source content.
# Extract the markdown text from `source_file`.
#
# REQUIRE: `has_source`.
- var md: String is lazy do
- assert has_source
- var file = new FileReader.open(src_full_path.to_s)
+ var md: nullable String is lazy do
+ if not has_source then return null
+ var file = new FileReader.open(src_full_path.as(not null))
var md = file.read_all
file.close
return md
redef fun is_dirty do
if super then return true
if has_source then
- return wiki.need_render(src_full_path.to_s, out_full_path)
+ return wiki.need_render(src_full_path.as(not null), out_full_path)
end
return false
end
redef fun to_s do return "{name} ({parent or else "null"})"
end
+# The sidebar is displayed in front of the main panel of a `WikiEntry`.
+class WikiSidebar
+
+ # Wiki used to parse sidebar blocks.
+ var wiki: Nitiwiki is lazy do return entry.wiki
+
+ # WikiEntry this panel is related to.
+ var entry: WikiEntry
+
+ # Blocks are ieces of markdown that will be rendered in the sidebar.
+ var blocks: Array[Text] is lazy do
+ var res = new Array[Text]
+ # TODO get blocks from the entry for more customization
+ for name in entry.wiki.config.sidebar_blocks do
+ var block = wiki.load_sideblock(name)
+ if block == null then continue
+ res.add block
+ end
+ return res
+ end
+end
+
# Wiki configuration class.
#
# This class provides services that ensure static typing when accessing the `config.ini` file.
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]
+ protected fun value_or_default(key: String, default: String): String do
+ return self[key] or else default
end
# Site name displayed.
# * default: ``
var wiki_logo: String is lazy do return value_or_default("wiki.logo", "")
- # Root url of the wiki.
+ # Markdown extension recognized by this wiki.
#
- # * key: `wiki.root_url`
- # * default: `http://localhost/`
- var root_url: String is lazy do return value_or_default("wiki.root_url", "http://localhost/")
-
+ # We allow only one kind of extension per wiki.
+ # Files with other markdown extensions will be treated as resources.
+ #
+ # * key: `wiki.md_ext`
+ # * default: `md`
+ var md_ext: String is lazy do return value_or_default("wiki.md_ext", "md")
# Root directory of the wiki.
#
return value_or_default("wiki.footer", "footer.html")
end
+ # Automatically add a summary.
+ #
+ # * key: `wiki.auto_summary`
+ # * default: `true`
+ var auto_summary: Bool is lazy do
+ return value_or_default("wiki.auto_summary", "true") == "true"
+ end
+
+ # Automatically add breadcrumbs.
+ #
+ # * key: `wiki.auto_breadcrumbs`
+ # * default: `true`
+ var auto_breadcrumbs: Bool is lazy do
+ return value_or_default("wiki.auto_breadcrumbs", "true") == "true"
+ end
+
+ # Sidebar position.
+ #
+ # Position of the sidebar between `left`, `right` and `none`. Any other value
+ # will be considered as `none`.
+ #
+ # * key: `wiki.sidebar`
+ # * default: `left`
+ var sidebar: String is lazy do
+ return value_or_default("wiki.sidebar", "left")
+ end
+
+ # Sidebar markdown block to include.
+ #
+ # Blocks are specified by their filename without the extension.
+ #
+ # * key: `wiki.sidebar.blocks`
+ # * default: `[]`
+ var sidebar_blocks: Array[String] is lazy do
+ var res = new Array[String]
+ if not has_key("wiki.sidebar.blocks") then return res
+ for val in at("wiki.sidebar.blocks").as(not null).values do
+ res.add val
+ end
+ return res
+ end
+
+ # Sidebar files directory.
+ #
+ # Directory where sidebar blocks are stored.
+ # **This path MUST be relative to `root_dir`.**
+ #
+ # * key: `wiki.sidebar_dir`
+ # * default: `sidebar/`
+ var sidebar_dir: String is lazy do
+ return value_or_default("wiki.sidebar_dir", "sidebar/").simplify_path
+ end
+
# Directory used by rsync to upload wiki files.
#
# This information is used to update your distant wiki files (like the webserver).
# * key: `wiki.git_branch`
# * default: `master`
var git_branch: String is lazy do return value_or_default("wiki.git_branch", "master")
+
+ # URL to source versionning used to display last changes
+ #
+ # * key: `wiki.last_changes`
+ # * default: ``
+ var last_changes: String is lazy do return value_or_default("wiki.last_changes", "")
+
+ # URL to source edition.
+ #
+ # * key: `wiki.edit`
+ # * default: ``
+ var edit: String is lazy do return value_or_default("wiki.edit", "")
end
# WikiSection custom configuration.