Some fixes for nitiwiki in prevision for the nitlanguage.org site migration and the resolution of #824.
Do not consider b481cea since it belongs to #1368 (and break the loader)
Feature summary:
* ini accept array notation
* customizable sidebar content
* customizable sidebar position
* wikilinks support external links
* better search by title
* better search by path
* make breadcrumbs and summaries optionnal
* add link to github for last_changes and edit mode
Pull-Request: #1444
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
Reviewed-by: Jean Privat <jean@pryen.org>
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 = ""
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
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.
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").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.
fun tpl_article: TplArticle do
var article = new TplArticle
article.body = content
- article.breadcrumbs = new TplBreadcrumbs(self)
- tpl_sidebar.blocks.add tpl_summary
+ if wiki.config.auto_breadcrumbs then
+ article.breadcrumbs = new TplBreadcrumbs(self)
+ end
article.sidebar = tpl_sidebar
+ article.sidebar_pos = wiki.config.sidebar
return article
end
# Sidebar for this page.
- var tpl_sidebar = new TplSidebar
+ var tpl_sidebar: TplSidebar is lazy do
+ var res = new TplSidebar
+ if wiki.config.auto_summary then
+ res.blocks.add tpl_summary
+ end
+ res.blocks.add_all sidebar.blocks
+ return res
+ end
# Generate the HTML summary for this article.
#
if tpl.has_macro("GEN_TIME") then
tpl.replace("GEN_TIME", time.to_s)
end
+ if tpl.has_macro("LAST_CHANGES") then
+ var url = "{wiki.config.last_changes}{src_path or else ""}"
+ tpl.replace("LAST_CHANGES", url)
+ end
+ if tpl.has_macro("EDIT") then
+ var url = "{wiki.config.edit}{src_path or else ""}"
+ tpl.replace("EDIT", url)
+ end
return tpl
end
end
# Sidebar of this article (if any).
var sidebar: nullable TplSidebar = null
+ # Position of the sidebar.
+ #
+ # See `WikiConfig::sidebar`.
+ var sidebar_pos: String = "left"
+
# Breadcrumbs from wiki root to this article.
var breadcrumbs: nullable TplBreadcrumbs = null
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
+ if sidebar_pos == "left" then render_sidebar
+ if sidebar == null then
add "<div class=\"col-sm-12 content\">"
+ else
+ add "<div class=\"col-sm-9 content\">"
end
if body != null then
add "<article>"
add " </article>"
end
add "</div>"
+ if sidebar_pos == "right" then render_sidebar
+ end
+
+ private fun render_sidebar do
+ if sidebar == null then return
+ add "<div class=\"col-sm-3 sidebar\">"
+ add sidebar.as(not null)
+ add "</div>"
end
end
redef fun rendering do
for block in blocks do
- add "<div class=\"sideblock\">"
+ add "<nav class=\"sideblock\">"
add block
- add "</div>"
+ add "</nav>"
end
end
end
#
# Returns `null` if no article can be found.
fun lookup_entry_by_name(context: WikiEntry, name: String): nullable WikiEntry do
- var section = context.parent
+ var section: nullable WikiEntry = context.parent or else context
var res = section.lookup_entry_by_name(name)
if res != null then return res
while section != null do
#
# Returns `null` if no article can be found.
fun lookup_entry_by_title(context: WikiEntry, title: String): nullable WikiEntry do
- var section = context.parent
+ var section: nullable WikiEntry = context.parent or else context
var res = section.lookup_entry_by_title(title)
if res != null then return res
while section != null do
- if section.title == title then return section
+ if section.title.to_lower == title.to_lower then return section
for child in section.children.values do
- if child.title == title then return child
+ if child.title.to_lower == title.to_lower then return child
end
section = section.parent
end
#
# Returns `null` if no article can be found.
fun lookup_entry_by_path(context: WikiEntry, path: String): nullable WikiEntry do
- var entry = context.parent
+ var entry = context.parent or else context
var parts = path.split_with("/")
if path.has_prefix("/") then
entry = root_section
while not parts.is_empty do
var name = parts.shift
if name.is_empty then continue
+ if entry.name == name then continue
if not entry.children.has_key(name) then return null
entry = entry.children[name]
end
redef fun render do
super
if not is_dirty and not wiki.force_render then return
+ render_sidebar_wikilinks
end
# Search in `self` then `self.children` if an entry has the name `name`.
# Search in `self` then `self.children` if an entry has the title `title`.
fun lookup_entry_by_title(title: String): nullable WikiEntry do
for child in children.values do
- if child.title == title then return child
+ if child.title.to_lower == title.to_lower then return child
end
for child in children.values do
var res = child.lookup_entry_by_title(title)
end
return null
end
+
+ private var md_proc: NitiwikiMdProcessor is lazy do
+ return new NitiwikiMdProcessor(wiki, self)
+ end
+
+ # Process wikilinks from sidebar.
+ private fun render_sidebar_wikilinks do
+ var blocks = sidebar.blocks
+ for i in [0..blocks.length[ do
+ blocks[i] = md_proc.process(blocks[i].to_s).write_to_string
+ md_proc.emitter.decorator.headlines.clear
+ end
+ end
end
redef class WikiSection
redef fun render do
super
if not is_dirty and not wiki.force_render or not has_source then return
- var md_proc = new NitiwikiMdProcessor(wiki, self)
content = md_proc.process(md.as(not null))
headlines.recover_with(md_proc.emitter.decorator.headlines)
end
# Article parsed by `self`.
#
# Used to contextualize links.
- var context: WikiArticle
+ var context: WikiEntry
init do
emitter = new MarkdownEmitter(self)
var wiki: Nitiwiki
# Article used to contextualize links.
- var context: WikiArticle
+ var context: WikiEntry
redef fun add_wikilink(v, link, name, comment) do
- var wiki = v.processor.as(NitiwikiMdProcessor).wiki
- var target: nullable WikiEntry = null
var anchor: nullable String = null
- if link.has("#") then
- var parts = link.split_with("#")
- link = parts.first
- anchor = parts.subarray(1, parts.length - 1).join("#")
- end
- if link.has("/") then
- target = wiki.lookup_entry_by_path(context, link.to_s)
- else
- target = wiki.lookup_entry_by_name(context, link.to_s)
- if target == null then
- target = wiki.lookup_entry_by_title(context, link.to_s)
- end
- end
v.add "<a "
- if target != null then
- if name == null then name = target.title
- link = target.url
- else
- wiki.message("Warning: unknown wikilink `{link}` (in {context.src_path.as(not null)})", 0)
- v.add "class=\"broken\" "
+ if not link.has_prefix("http://") and not link.has_prefix("https://") then
+ var wiki = v.processor.as(NitiwikiMdProcessor).wiki
+ var target: nullable WikiEntry = null
+ if link.has("#") then
+ var parts = link.split_with("#")
+ link = parts.first
+ anchor = parts.subarray(1, parts.length - 1).join("#")
+ end
+ if link.has("/") then
+ target = wiki.lookup_entry_by_path(context, link.to_s)
+ else
+ target = wiki.lookup_entry_by_name(context, link.to_s)
+ if target == null then
+ target = wiki.lookup_entry_by_title(context, link.to_s)
+ end
+ end
+ if target != null then
+ if name == null then name = target.title
+ link = target.url
+ else
+ var loc = context.src_path or else context.name
+ wiki.message("Warning: unknown wikilink `{link}` (in {loc})", 0)
+ v.add "class=\"broken\" "
+ end
end
v.add "href=\""
append_value(v, link)
# assert config["foo.bar.baz"] == "foobarbaz"
# assert config["goo.boo.bar"] == "gooboobar"
# assert config["goo.boo.baz.bar"] == "gooboobazbar"
+ #
+ # Using the array notation
+ #
+ # str = """
+ # foo[]=a
+ # foo[]=b
+ # foo[]=c"""
+ # str.write_to_file("config4.ini")
+ # # load file
+ # config = new ConfigTree("config4.ini")
+ # print config.to_map.join(":", ",")
+ # assert config["foo.0"] == "a"
+ # assert config["foo.1"] == "b"
+ # assert config["foo.2"] == "c"
+ # assert config.at("foo").values.join(",") == "a,b,c"
fun load do
roots.clear
var stream = new FileReader.open(ini_file)
var path: nullable String = null
+ var line_number = 0
while not stream.eof do
var line = stream.read_line
+ line_number += 1
if line.is_empty then
continue
else if line.has_prefix(";") then
set_node(path, null)
else
var parts = line.split("=")
+ assert parts.length > 1 else
+ print "Error: malformed ini at line {line_number}"
+ end
var key = parts[0].trim
var val = parts[1].trim
- if path == null then
- set_node(key, val)
+ if path != null then key = "{path}.{key}"
+ if key.has_suffix("[]") then
+ set_array(key, val)
else
- set_node("{path}.{key}", val)
+ set_node(key,val)
end
end
end
private var roots = new Array[ConfigNode]
+ # Append `value` to array at `key`
+ private fun set_array(key: String, value: nullable String) do
+ key = key.substring(0, key.length - 2)
+ var len = 0
+ if has_key(key) then
+ len = get_node(key).children.length
+ end
+ set_node("{key}.{len.to_s}", value)
+ end
+
private fun set_node(key: String, value: nullable String) do
var parts = key.split(".").reversed
var k = parts.pop
pos += 1
pos = md.skip_spaces(pos)
if pos < start then return -1
- pos += 1
return pos
end
end