Merge: metrics: `--nullables` distinguishes safe and unsafe calls on `null`
[nit.git] / lib / markdown / markdown.nit
index 3d0a0a1..7c65426 100644 (file)
@@ -31,7 +31,7 @@ import template
 class MarkdownProcessor
 
        # `MarkdownEmitter` used for ouput.
-       var emitter: MarkdownEmitter is noinit
+       var emitter: MarkdownEmitter is noinit, protected writable
 
        # Work in extended mode (default).
        #
@@ -118,7 +118,7 @@ class MarkdownProcessor
        init do self.emitter = new MarkdownEmitter(self)
 
        # Process the mardown `input` string and return the processed output.
-       fun process(input: String): Streamable do
+       fun process(input: String): Writable do
                # init processor
                link_refs.clear
                last_link_ref = null
@@ -244,7 +244,7 @@ class MarkdownProcessor
        #
        # Markdown allows link refs to be defined over two lines:
        #
-       #       [id]: http://example.com/longish/path/to/resource/here
+       #       [id]: http://example.com/longish/path/to/resource/here
        #               "Optional Title Here"
        #
        private var last_link_ref: nullable LinkRef = null
@@ -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.
@@ -537,7 +543,7 @@ class MarkdownEmitter
        end
 
        # Append `e` to current buffer.
-       fun add(e: Streamable) do
+       fun add(e: Writable) do
                if e isa Text then
                        current_buffer.append e
                else
@@ -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
@@ -2000,7 +2009,7 @@ abstract class TokenLinkOrImage
                                comment = lr.title
                        end
                else
-               var tid = name.write_to_string.replace("\n", " ").to_lower
+                       var tid = name.write_to_string.replace("\n", " ").to_lower
                        if v.processor.link_refs.has_key(tid) then
                                var lr = v.processor.link_refs[tid]
                                link = lr.link
@@ -2266,6 +2275,7 @@ redef class Text
        # Safe mode can be activated to limit reading to valid xml.
        private fun read_xml(out: FlatBuffer, start: Int, safe_mode: Bool): Int do
                var pos = 0
+               var is_valid = true
                var is_close_tag = false
                if start + 1 >= length then return -1
                if self[start + 1] == '/' then
@@ -2283,7 +2293,11 @@ redef class Text
                        pos = read_xml_until(tmp, pos, ' ', '/', '>')
                        if pos == -1 then return -1
                        var tag = tmp.write_to_string.trim.to_lower
-                       if tag.is_html_unsafe then
+                       if not tag.is_valid_html_tag then
+                               out.append "<"
+                               pos = -1
+                       else if tag.is_html_unsafe then
+                               is_valid = false
                                out.append "<"
                                if is_close_tag then out.add '/'
                                out.append tmp
@@ -2306,7 +2320,11 @@ redef class Text
                        if pos == -1 then return -1
                end
                if self[pos] == '>' then
-                       out.add '>'
+                       if is_valid then
+                               out.add '>'
+                       else
+                               out.append ">"
+                       end
                        return pos
                end
                return -1
@@ -2378,6 +2396,14 @@ redef class Text
                return tpl.write_to_string.to_lower
        end
 
+       private fun is_valid_html_tag: Bool do
+               if is_empty then return false
+               for c in self do
+                       if not c.is_alpha then return false
+               end
+               return true
+       end
+
        # Read and escape the markdown contained in `self`.
        private fun escape(out: FlatBuffer, c: Char, pos: Int): Int do
                if c == '\\' or c == '[' or c == ']' or c == '(' or c == ')' or c == '{' or
@@ -2395,7 +2421,6 @@ redef class Text
        private fun meta_from_fence: nullable Text do
                for i in [0..chars.length[ do
                        var c = chars[i]
-                       print c
                        if c != ' ' and c != '`' and c != '~' then
                                return substring_from(i).trim
                        end
@@ -2426,7 +2451,7 @@ redef class String
        #    var md = "**Hello World!**"
        #    var html = md.md_to_html
        #    assert html == "<p><strong>Hello World!</strong></p>\n"
-       fun md_to_html: Streamable do
+       fun md_to_html: Writable do
                var processor = new MarkdownProcessor
                return processor.process(self)
        end