Misc modification to markdown lib to facilitate custon Markdown parsing from external tools.
Pull-Request: #1371
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
module wiki_links
import wiki_base
-intrude import markdown
+import markdown::wikilinks
redef class Nitiwiki
# Looks up a WikiEntry by its `name`.
emitter = new MarkdownEmitter(self)
emitter.decorator = new NitiwikiDecorator(wiki, context)
end
-
- redef fun token_at(text, pos) do
- var token = super
- if not token isa TokenLink then return token
- if pos + 1 < text.length then
- var c = text[pos + 1]
- if c == '[' then return new TokenWikiLink(pos, c)
- end
- return token
- end
end
private class NitiwikiDecorator
# Article used to contextualize links.
var context: WikiArticle
- fun add_wikilink(v: MarkdownEmitter, link: Text, name, comment: nullable Text) do
+ 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
v.add "</a>"
end
end
-
-# A NitiWiki link token.
-#
-# Something of the form `[[foo]]`.
-#
-# Allowed formats:
-#
-# * `[[Wikilink]]`
-# * `[[Wikilink/Bar]]`
-# * `[[Wikilink#foo]]`
-# * `[[Wikilink/Bar#foo]]`
-# * `[[title|Wikilink]]`
-# * `[[title|Wikilink/Bar]]`
-# * `[[title|Wikilink/Bar#foo]]`
-class TokenWikiLink
- super TokenLink
-
- redef fun emit_hyper(v) do
- v.decorator.as(NitiwikiDecorator).add_wikilink(v, link.as(not null), name, comment)
- end
-
- redef fun check_link(v, out, start, token) do
- var md = v.current_text
- var pos = start + 2
- var tmp = new FlatBuffer
- pos = md.read_md_link_id(tmp, pos)
- if pos < start then return -1
- var name = tmp.write_to_string
- if name.has("|") then
- var parts = name.split_once_on("|")
- self.name = parts.first
- self.link = parts[1]
- else
- self.name = null
- self.link = name
- end
- pos += 1
- pos = md.skip_spaces(pos)
- if pos < start then return -1
- pos += 1
- return pos
- end
-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.
+
+# Decorators for `markdown` parsing.
+module decorators
+
+import markdown
+
+# `Decorator` that outputs markdown.
+class MdDecorator
+ super Decorator
+
+ redef var headlines = new ArrayMap[String, HeadLine]
+
+ redef fun add_ruler(v, block) do v.add "***\n"
+
+ redef fun add_headline(v, block) do
+ # save headline
+ var txt = block.block.first_line.value
+ var id = strip_id(txt)
+ var lvl = block.depth
+ headlines[id] = new HeadLine(id, txt, lvl)
+ v.add "{"#" * lvl} "
+ v.emit_in block
+ v.addn
+ end
+
+ redef fun add_paragraph(v, block) do
+ v.emit_in block
+ v.addn
+ end
+
+ redef fun add_code(v, block) do
+ if block isa BlockFence and block.meta != null then
+ v.add "~~~{block.meta.to_s}"
+ else
+ v.add "~~~"
+ end
+ v.addn
+ v.emit_in block
+ v.add "~~~"
+ v.addn
+ end
+
+ redef fun add_blockquote(v, block) do
+ v.add "> "
+ v.emit_in block
+ v.addn
+ end
+
+ redef fun add_unorderedlist(v, block) do
+ in_unorderedlist = true
+ v.emit_in block
+ in_unorderedlist = false
+ end
+ private var in_unorderedlist = false
+
+ redef fun add_orderedlist(v, block) do
+ in_orderedlist = true
+ current_li = 0
+ v.emit_in block
+ in_unorderedlist = false
+ end
+ private var in_orderedlist = false
+ private var current_li = 0
+
+ redef fun add_listitem(v, block) do
+ if in_unorderedlist then
+ v.add "* "
+ else if in_orderedlist then
+ current_li += 1
+ v.add "{current_li} "
+ end
+ v.emit_in block
+ v.addn
+ end
+
+ redef fun add_em(v, text) do
+ v.add "*"
+ v.add text
+ v.add "*"
+ end
+
+ redef fun add_strong(v, text) do
+ v.add "**"
+ v.add text
+ v.add "**"
+ end
+
+ redef fun add_strike(v, text) do
+ v.add "~~"
+ v.add text
+ v.add "~~"
+ end
+
+ redef fun add_image(v, link, name, comment) do
+ v.add "!["
+ v.add name
+ v.add "]("
+ append_value(v, link)
+ if comment != null and not comment.is_empty then
+ v.add " "
+ append_value(v, comment)
+ end
+ v.add ")"
+ end
+
+ redef fun add_link(v, link, name, comment) do
+ v.add "["
+ v.add name
+ v.add "]("
+ append_value(v, link)
+ if comment != null and not comment.is_empty then
+ v.add " "
+ append_value(v, comment)
+ end
+ v.add ")"
+ end
+
+ redef fun add_abbr(v, name, comment) do
+ v.add "<abbr title=\""
+ append_value(v, comment)
+ v.add "\">"
+ v.emit_text(name)
+ v.add "</abbr>"
+ end
+
+ redef fun add_span_code(v, text, from, to) do
+ v.add "`"
+ append_code(v, text, from, to)
+ v.add "`"
+ end
+
+ redef fun add_line_break(v) do
+ v.add "\n"
+ end
+
+ redef fun append_value(v, text) do for c in text do escape_char(v, c)
+
+ redef fun escape_char(v, c) do v.addc(c)
+
+ redef fun append_code(v, buffer, from, to) do
+ for i in [from..to[ do
+ v.addc buffer[i]
+ end
+ end
+
+ redef fun strip_id(txt) do
+ # strip id
+ var b = new FlatBuffer
+ for c in txt do
+ if c == ' ' then
+ b.add '_'
+ else
+ if not c.is_letter and
+ not c.is_digit and
+ not allowed_id_chars.has(c) then continue
+ b.add c
+ end
+ end
+ var res = b.to_s
+ var key = res
+ # check for multiple id definitions
+ if headlines.has_key(key) then
+ var i = 1
+ key = "{res}_{i}"
+ while headlines.has_key(key) do
+ i += 1
+ key = "{res}_{i}"
+ end
+ end
+ return key
+ end
+
+ private var allowed_id_chars: Array[Char] = ['-', '_', ':', '.']
+end
# The emitter use a `Decorator` to select the output format.
class MarkdownEmitter
+ # Kind of processor used for parsing.
+ type PROCESSOR: MarkdownProcessor
+
# Processor containing link refs.
- var processor: MarkdownProcessor
+ var processor: PROCESSOR
+
+ # Kind of decorator used for decoration.
+ type DECORATOR: Decorator
# Decorator used for output.
# Default is `HTMLDecorator`
- var decorator: Decorator = new HTMLDecorator is writable
+ var decorator: DECORATOR is writable, lazy do
+ return new HTMLDecorator
+ end
# Create a new `MarkdownEmitter` using a custom `decorator`.
- init with_decorator(processor: MarkdownProcessor, decorator: Decorator) do
+ init with_decorator(processor: PROCESSOR, decorator: DECORATOR) do
init processor
self.decorator = decorator
end
fun emit_in(block: Block) do block.emit_in(self)
# Transform and emit mardown text
- fun emit_text(text: Text) do
- emit_text_until(text, 0, null)
- end
+ fun emit_text(text: Text) do emit_text_until(text, 0, null)
# Transform and emit mardown text starting at `from` and
# until a token with the same type as `token` is found.
end
# Append `c` to current buffer.
- fun addc(c: Char) do current_buffer.add c
+ fun addc(c: Char) do add c.to_s
# Append a "\n" line break.
- fun addn do current_buffer.add '\n'
+ fun addn do add "\n"
end
# A Link Reference.
# Default decorator used is `HTMLDecorator`.
interface Decorator
+ # Kind of emitter used for decoration.
+ type EMITTER: MarkdownEmitter
+
# Render a ruler block.
- fun add_ruler(v: MarkdownEmitter, block: BlockRuler) is abstract
+ fun add_ruler(v: EMITTER, block: BlockRuler) is abstract
# Render a headline block with corresponding level.
- fun add_headline(v: MarkdownEmitter, block: BlockHeadline) is abstract
+ fun add_headline(v: EMITTER, block: BlockHeadline) is abstract
# Render a paragraph block.
- fun add_paragraph(v: MarkdownEmitter, block: BlockParagraph) is abstract
+ fun add_paragraph(v: EMITTER, block: BlockParagraph) is abstract
# Render a code or fence block.
- fun add_code(v: MarkdownEmitter, block: BlockCode) is abstract
+ fun add_code(v: EMITTER, block: BlockCode) is abstract
# Render a blockquote.
- fun add_blockquote(v: MarkdownEmitter, block: BlockQuote) is abstract
+ fun add_blockquote(v: EMITTER, block: BlockQuote) is abstract
# Render an unordered list.
- fun add_unorderedlist(v: MarkdownEmitter, block: BlockUnorderedList) is abstract
+ fun add_unorderedlist(v: EMITTER, block: BlockUnorderedList) is abstract
# Render an ordered list.
- fun add_orderedlist(v: MarkdownEmitter, block: BlockOrderedList) is abstract
+ fun add_orderedlist(v: EMITTER, block: BlockOrderedList) is abstract
# Render a list item.
- fun add_listitem(v: MarkdownEmitter, block: BlockListItem) is abstract
+ fun add_listitem(v: EMITTER, block: BlockListItem) is abstract
# Render an emphasis text.
- fun add_em(v: MarkdownEmitter, text: Text) is abstract
+ fun add_em(v: EMITTER, text: Text) is abstract
# Render a strong text.
- fun add_strong(v: MarkdownEmitter, text: Text) is abstract
+ fun add_strong(v: EMITTER, text: Text) is abstract
# Render a strike text.
#
# Extended mode only (see `MarkdownProcessor::ext_mode`)
- fun add_strike(v: MarkdownEmitter, text: Text) is abstract
+ fun add_strike(v: EMITTER, text: Text) is abstract
# Render a link.
- fun add_link(v: MarkdownEmitter, link: Text, name: Text, comment: nullable Text) is abstract
+ fun add_link(v: EMITTER, link: Text, name: Text, comment: nullable Text) is abstract
# Render an image.
- fun add_image(v: MarkdownEmitter, link: Text, name: Text, comment: nullable Text) is abstract
+ fun add_image(v: EMITTER, link: Text, name: Text, comment: nullable Text) is abstract
# Render an abbreviation.
- fun add_abbr(v: MarkdownEmitter, name: Text, comment: Text) is abstract
+ fun add_abbr(v: EMITTER, name: Text, comment: Text) is abstract
# Render a code span reading from a buffer.
- fun add_span_code(v: MarkdownEmitter, buffer: Text, from, to: Int) is abstract
+ fun add_span_code(v: EMITTER, buffer: Text, from, to: Int) is abstract
# Render a text and escape it.
- fun append_value(v: MarkdownEmitter, value: Text) is abstract
+ fun append_value(v: EMITTER, value: Text) is abstract
# Render code text from buffer and escape it.
- fun append_code(v: MarkdownEmitter, buffer: Text, from, to: Int) is abstract
+ fun append_code(v: EMITTER, buffer: Text, from, to: Int) is abstract
# Render a character escape.
- fun escape_char(v: MarkdownEmitter, char: Char) is abstract
+ fun escape_char(v: EMITTER, char: Char) is abstract
# Render a line break
- fun add_line_break(v: MarkdownEmitter) is abstract
+ fun add_line_break(v: EMITTER) is abstract
# Generate a new html valid id from a `String`.
fun strip_id(txt: String): String is abstract
--- /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.
+
+# Wikilinks handling.
+#
+# Wikilinks are on the form `[[link]]`.
+# They can also contain a custom title with the syntax `[[title|link]]`.
+#
+# By importing this module, you enable the `MarkdownProcessor` to recognize
+# `TokenWikiLink` but nothing will happen until you define a
+# `Decorator::add_wikilink` customized to your applciation domain.
+module wikilinks
+
+intrude import markdown
+
+# `MarkdownProcessor` is now able to parse wikilinks.
+redef class MarkdownProcessor
+
+ redef fun token_at(text, pos) do
+ var token = super
+ if not token isa TokenLink then return token
+ if pos + 1 < text.length then
+ var c = text[pos + 1]
+ if c == '[' then return new TokenWikiLink(pos, c)
+ end
+ return token
+ end
+end
+
+redef class Decorator
+
+ # Renders a `[[wikilink]]` item.
+ fun add_wikilink(v: EMITTER, link: Text, name, comment: nullable Text) do
+ if name != null then
+ v.add "[[{name}|{link}]]"
+ else
+ v.add "[[{link}]]"
+ end
+ end
+end
+
+# A NitiWiki link token.
+#
+# Something of the form `[[foo]]`.
+#
+# Allowed formats:
+#
+# * `[[Wikilink]]`
+# * `[[Wikilink/Bar]]`
+# * `[[Wikilink#foo]]`
+# * `[[Wikilink/Bar#foo]]`
+# * `[[title|Wikilink]]`
+# * `[[title|Wikilink/Bar]]`
+# * `[[title|Wikilink/Bar#foo]]`
+class TokenWikiLink
+ super TokenLink
+
+ redef fun emit_hyper(v) do
+ v.decorator.add_wikilink(v, link.as(not null), name, comment)
+ end
+
+ redef fun check_link(v, out, start, token) do
+ var md = v.current_text
+ var pos = start + 2
+ var tmp = new FlatBuffer
+ pos = md.read_md_link_id(tmp, pos)
+ if pos < start then return -1
+ var name = tmp.write_to_string
+ if name.has("|") then
+ var parts = name.split_once_on("|")
+ self.name = parts.first
+ self.link = parts[1]
+ else
+ self.name = null
+ self.link = name
+ end
+ pos += 1
+ pos = md.skip_spaces(pos)
+ if pos < start then return -1
+ pos += 1
+ return pos
+ end
+end