Merge: Markdown: some improvement and fixes
authorJean Privat <jean@pryen.org>
Sat, 23 May 2015 01:01:25 +0000 (21:01 -0400)
committerJean Privat <jean@pryen.org>
Sat, 23 May 2015 01:01:25 +0000 (21:01 -0400)
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>

contrib/nitiwiki/src/wiki_links.nit
lib/markdown/decorators.nit [new file with mode: 0644]
lib/markdown/markdown.nit
lib/markdown/wikilinks.nit [new file with mode: 0644]

index bbbfea0..bfdeb86 100644 (file)
@@ -16,7 +16,7 @@
 module wiki_links
 
 import wiki_base
-intrude import markdown
+import markdown::wikilinks
 
 redef class Nitiwiki
        # Looks up a WikiEntry by its `name`.
@@ -189,16 +189,6 @@ class NitiwikiMdProcessor
                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
@@ -210,7 +200,7 @@ 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
@@ -250,46 +240,3 @@ private class NitiwikiDecorator
                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
diff --git a/lib/markdown/decorators.nit b/lib/markdown/decorators.nit
new file mode 100644 (file)
index 0000000..1a83394
--- /dev/null
@@ -0,0 +1,186 @@
+# 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
index 7b7d319..7c65426 100644 (file)
@@ -456,15 +456,23 @@ 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
@@ -481,9 +489,7 @@ class MarkdownEmitter
        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.
@@ -546,10 +552,10 @@ class MarkdownEmitter
        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.
@@ -580,64 +586,67 @@ end
 # 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
diff --git a/lib/markdown/wikilinks.nit b/lib/markdown/wikilinks.nit
new file mode 100644 (file)
index 0000000..473bc18
--- /dev/null
@@ -0,0 +1,94 @@
+# 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