From: Alexandre Terrasa Date: Tue, 29 May 2018 23:55:11 +0000 (-0400) Subject: lib/markdown2: introduce markdown rendering to markdown X-Git-Url: http://nitlanguage.org lib/markdown2: introduce markdown rendering to markdown Signed-off-by: Alexandre Terrasa --- diff --git a/lib/markdown2/markdown_md_rendering.nit b/lib/markdown2/markdown_md_rendering.nit new file mode 100644 index 0000000..02439e1 --- /dev/null +++ b/lib/markdown2/markdown_md_rendering.nit @@ -0,0 +1,376 @@ +# 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. + +# Markdown rendering of Markdown documents +module markdown_md_rendering + +import markdown_rendering + +# Markdown document renderer to Markdown +class MarkdownRenderer + super MdRenderer + + # Markdown output under construction + private var md: Buffer is noinit + + # Render `node` as Markdown + redef fun render(node) do + reset + enter_visit(node) + return md.write_to_string + end + + redef fun visit(node) do node.render_md(self) + + # Reset internal state + fun reset do + md = new Buffer + end + + # Current indentation level + private var indent = 0 + + # Are we currently in a blockquote? + var in_quote = 0 + + # Add a `md` string to the output + fun add_raw(md: String) do self.md.append(md) + + # Add a blank line to the output + fun add_line do add_raw "\n" + + # Add an indentation depending on `ident` level + fun add_indent do + add_raw " " * indent + end +end + +private class TextLengthVisitor + super MdVisitor + + var length = 0 + + redef fun visit(node) do node.process_len(self) +end + +redef class MdNode + + # Render `self` as Markdown + fun render_md(v: MarkdownRenderer) do visit_all(v) + + private fun process_len(v: TextLengthVisitor) do visit_all(v) +end + +redef class MdDocument + redef fun render_md(v) do + var node = first_child + while node != null do + v.enter_visit(node) + node = node.next + if node != null then + v.add_line + end + end + end +end + +# Blocks + +redef class MdBlockQuote + redef fun render_md(v) do + v.in_quote += 1 + var node = first_child + while node != null do + v.add_indent + v.add_raw "> " + v.enter_visit(node) + node = node.next + end + v.in_quote -= 1 + end +end + +redef class MdIndentedCodeBlock + redef fun render_md(v) do + var literal = self.literal + if literal == null then return + + var lines = literal.split("\n") + for i in [0..lines.length[ do + if i == lines.length - 1 then continue + var line = lines[i] + if line.is_empty then + v.add_raw "\n" + else + v.add_indent + if use_tabs then + v.add_raw "\t" + else + v.add_raw " " * 4 + end + v.add_raw line + v.add_line + end + end + end +end + +redef class MdFencedCodeBlock + redef fun render_md(v) do + var info = self.info + v.add_indent + v.add_raw fence_char.to_s * fence_length + v.add_raw info or else "" + for line in (literal or else "").split("\n") do + v.add_line + if not line.is_empty then + v.add_indent + end + v.add_raw line + end + v.add_indent + v.add_raw fence_char.to_s * fence_length + v.add_line + end +end + +redef class MdHeading + redef fun render_md(v) do + if is_setext then + visit_all(v) + var length_visitor = new TextLengthVisitor + length_visitor.enter_visit(self) + v.add_line + if level == 1 then + v.add_raw "=" * length_visitor.length + else + v.add_raw "-" * length_visitor.length + end + else + v.add_raw "#" * level + v.add_raw " " + visit_all(v) + if has_atx_trailing then + v.add_raw " " + v.add_raw "#" * level + end + end + v.add_line + end +end + +redef class MdOrderedList + # Children numbering + private var md_numbering: Int = start_number is lazy +end + +redef class MdListItem + redef fun render_md(v) do + var parent = self.parent + var is_tight = parent.as(MdListBlock).is_tight + + v.add_indent + if parent isa MdUnorderedList then + v.add_raw parent.bullet_marker.to_s + v.indent += 2 + else if parent isa MdOrderedList then + v.add_raw "{parent.md_numbering}{parent.delimiter.to_s}" + v.indent += 3 + end + + var node = first_child + if node != null then + v.add_raw " " + else + v.add_line + end + while node != null do + v.enter_visit(node) + node = node.next + if node != null and not is_tight then + v.add_line + end + end + + if next != null and not is_tight then + v.add_line + end + + if parent isa MdUnorderedList then + v.indent -= 2 + else if parent isa MdOrderedList then + parent.md_numbering += 1 + v.indent -= 3 + end + end +end + +redef class MdParagraph + redef fun render_md(v) do + if not parent isa MdBlockQuote and not parent isa MdListItem or prev != null then + v.add_indent + end + # if parent isa MdBlockQuote then + # v.add_raw "> " + # var node = first_child + # while node != null do + # v.enter_visit(node) + # if node isa MdSoftLineBreak or node isa MdHardLineBreak then + # v.add_raw "> " + # end + # node = node.next + # end + # v.add_line + # return + # end + visit_all(v) + v.add_line + end +end + +redef class MdThematicBreak + redef fun render_md(v) do + v.add_raw original_pattern + v.add_line + end +end + +redef class MdHtmlBlock + redef fun render_md(v) do + v.add_raw literal or else "" + v.add_line + end +end + +# Inlines + +redef class MdHardLineBreak + redef fun render_md(v) do + if has_backslash then + v.add_raw "\\" + else + v.add_raw " " + end + v.add_line + v.add_indent + v.add_raw "> " * v.in_quote + end + + redef fun process_len(v) do + super + v.length += 1 + end +end + +redef class MdSoftLineBreak + redef fun render_md(v) do + v.add_line + v.add_indent + v.add_raw "> " * v.in_quote + end + + redef fun process_len(v) do + super + v.length += 1 + end +end + +redef class MdCode + redef fun render_md(v) do + v.add_raw delimiter + v.add_raw literal + v.add_raw delimiter + end + + redef fun process_len(v) do + super + v.length += delimiter.length + end +end + +redef class MdDelimited + redef fun render_md(v) do + v.add_raw delimiter + visit_all(v) + v.add_raw delimiter + end + + redef fun process_len(v) do + super + v.length += delimiter.length * 2 + end +end + +redef class MdHtmlInline + redef fun render_md(v) do + v.add_raw literal + end + + redef fun process_len(v) do + v.length += literal.length + end +end + +redef class MdLinkOrImage + redef fun render_md(v) do + var title = self.title + v.add_raw "[" + visit_all(v) + v.add_raw "]" + v.add_raw "(" + if has_brackets then + v.add_raw "<" + end + v.add_raw destination + if has_brackets then + v.add_raw ">" + end + if title != null and not title.is_empty then + v.add_raw " \"" + v.add_raw title.replace("\"", "\\\"") + v.add_raw "\"" + end + v.add_raw ")" + end +end + + +redef class MdImage + redef fun render_md(v) do + v.add_raw "!" + super + end +end + +redef class MdLink + redef fun render_md(v) do + if is_autolink then + v.add_raw "<" + v.add_raw destination + v.add_raw ">" + return + end + super + end +end + +redef class MdText + redef fun render_md(v) do + v.add_raw literal + end + + redef fun process_len(v) do + v.length += literal.length + end +end diff --git a/lib/markdown2/tests/test_markdown_md.nit b/lib/markdown2/tests/test_markdown_md.nit new file mode 100644 index 0000000..6705a2f --- /dev/null +++ b/lib/markdown2/tests/test_markdown_md.nit @@ -0,0 +1,663 @@ +# 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. + +# Tests for markdown rendering to markdown +module test_markdown_md is test + +import test_markdown +import markdown_md_rendering + +abstract class TestMarkdownMd + super TestMarkdown + + var md_renderer = new MarkdownRenderer + + fun md_to_md(md: String): String do + var doc = md_parser.parse(md) + doc.debug + return md_renderer.render(doc) + end +end + +class TestMdHeadings + super TestMarkdownMd + test + + fun test_no_trailings is test do + var md = """# foo\n## foo\n### foo\n#### foo\n##### foo\n###### foo\n""" + var exp = """# foo\n\n## foo\n\n### foo\n\n#### foo\n\n##### foo\n\n###### foo\n""" + assert md_to_md(md) == exp + end + + fun test_trailings is test do + var md = """# foo #\n## foo ##\n### foo ###\n#### foo ####\n##### foo #####\n""" + var exp = """# foo #\n\n## foo ##\n\n### foo ###\n\n#### foo ####\n\n##### foo #####\n""" + assert md_to_md(md) == exp + end + + fun test_setext is test do + var md = """Foo *bar*\n=========\nFoo *bar*\n---------\n""" + var exp = """Foo *bar*\n=========\n\nFoo *bar*\n---------\n""" + assert md_to_md(md) == exp + end +end + +class TestMdBlockQuotes + super TestMarkdownMd + test + + fun test191 is test do + var md = """> # Foo\n> bar\n> baz\n""" + assert md_to_md(md) == md + end + + fun test197 is test do + var md = """> foo\n---\n""" + var exp = """> foo\n\n---\n""" + assert md_to_md(md) == exp + end + + fun test198 is test do + var md = """> - foo\n- bar\n""" + var exp = """> - foo\n\n- bar\n""" + assert md_to_md(md) == exp + end + + fun test206 is test do + var md = """> foo\n> bar\n""" + assert md_to_md(md) == md + end + + fun test213 is test do + var md = """> > > foo\n> bar\n""" + var exp = """> > > foo\n> > > bar\n""" + assert md_to_md(md) == exp + end +end + +class TestMdLists + super TestMarkdownMd + test + + fun test264 is test do + var md = """- foo\n- bar\n+ baz\n""" + var exp = """- foo\n- bar\n\n+ baz\n""" + assert md_to_md(md) == exp + end + + fun test265 is test do + var md = """1. foo\n2. bar\n3) baz\n""" + var exp = """1. foo\n2. bar\n\n3) baz\n""" + assert md_to_md(md) == exp + end + + fun test270 is test do + var md = """- foo\n - bar\n - baz\n\n bim\n""" + assert md_to_md(md) == md + end + + fun test273 is test do + var md = """- a\n - b\n - c\n - d\n - e\n - f\n- g\n""" + var exp = """- a\n- b\n- c\n- d\n- e\n- f\n- g\n""" + assert md_to_md(md) == exp + end + + fun test274 is test do + var md = """1. a\n\n 2. b\n\n 3. c\n""" + var exp = """1. a\n\n2. b\n\n3. c\n""" + assert md_to_md(md) == exp + end + + fun test289 is test do + var md = """- a\n - b\n - c\n\n- d\n - e\n - f\n""" + var exp = """- a\n\n - b\n - c\n\n- d\n\n - e\n - f\n""" + assert md_to_md(md) == exp + end +end + +class TestMdkListItems + super TestMarkdownMd + test + + fun test217 is test do + var md = """1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n""" + var exp = """1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n""" + assert md_to_md(md) == exp + end + + fun test219 is test do + var md = """- one\n\n two\n""" + assert md_to_md(md) == md + end + + fun test221 is test do + var md = """ - one\n\n two\n""" + var exp = """- one\n\n two\n""" + assert md_to_md(md) == exp + end + + # FIXME + # fun test223 is test do + # var md = """>>- one\n>>\n > > two\n""" + # var exp = """> > - one\n> >\n> > two\n""" + # assert md_to_md(md) == exp + # end + + fun test225 is test do + var md = """- foo\n\n bar\n""" + assert md_to_md(md) == md + end + + fun test228 is test do + var md = """123456789. ok\n""" + assert md_to_md(md) == md + end + + fun test230 is test do + var md = """0. ok\n""" + assert md_to_md(md) == md + end + + fun test246 is test do + var md = """1. foo\n2.\n3. bar\n""" + assert md_to_md(md) == md + end + + fun test254 is test do + var md = """ 1. A paragraph\n with two lines.\n""" + var exp = """1. A paragraph\n with two lines.\n""" + assert md_to_md(md) == exp + end + + # FIXME + # fun test255 is test do + # var md = """> 1. > Blockquote\n> continued here.\n""" + # var exp = """> 1. > Blockquote\n > continued here.\n""" + # assert md_to_md(md) == exp + # end +end + +class TestMdFencedCodeBlocks + super TestMarkdownMd + test + + fun test88 is test do + var md = """```\nfoo\n```\n""" + assert md_to_md(md) == md + end + + fun test92 is test do + var md = """~~~\nfoo\n~~~\n""" + assert md_to_md(md) == md + end + + fun test111 is test do + var md = """```ruby\ndef foo(x)\n return 3\nend\n```\n""" + assert md_to_md(md) == md + end + + fun test112 is test do + var md = """~~~~~~\nSome markdown:\n~~~\n**hello**\n~~~\n~~~~~~\n""" + assert md_to_md(md) == md + end +end + +class TestMdIndentedCodeBlocks + super TestMarkdownMd + test + + fun test75 is test do + var md = """ a code block\n""" + assert md_to_md(md) == md + end + + fun test76 is test do + var md = """ a simple\n indented code block\n""" + assert md_to_md(md) == md + end + + fun test80 is test do + var md = """ chunk1\n\n chunk2\n \n \n \n chunk3\n""" + assert md_to_md(md) == """ chunk1\n\n chunk2\n\n\n\n chunk3\n""" + end + + fun test85 is test do + var md = """ foo\n bar\n""" + assert md_to_md(md) == md + end + + fun test87 is test do + var md = """\t\tfoo \n""" + assert md_to_md(md) == md + end +end + +class TestMdThematicBreaks + super TestMarkdownMd + test + + fun test13 is test do + var md = """***\n---\n___\n""" + var exp = """***\n\n---\n\n___\n""" + assert md_to_md(md) == exp + end + + fun test17 is test do + var md = """ ***\n ***\n ***\n""" + var exp = """***\n\n***\n\n***\n""" + assert md_to_md(md) == exp + end + + fun test20 is test do + var md = """_____________________________________\n""" + assert md_to_md(md) == md + end + + fun test21 is test do + var md = """ - - -\n""" + var exp = """- - -\n""" + assert md_to_md(md) == exp + end + + fun test22 is test do + var md = """ ** * ** * ** * **\n""" + var exp = """** * ** * ** * **\n""" + assert md_to_md(md) == exp + end + + fun test23 is test do + var md = """- - - -\n""" + assert md_to_md(md) == md + end +end + +class TestMdParagraphs + super TestMarkdownMd + test + + fun test182 is test do + var md = """aaa\n\nbbb\n""" + assert md_to_md(md) == md + end + + fun test183 is test do + var md = """aaa\nbbb\n\nccc\nddd\n""" + assert md_to_md(md) == md + end + + fun test186 is test do + var md = """aaa\n bbb\n ccc\n""" + var exp = """aaa\nbbb\nccc\n""" + assert md_to_md(md) == exp + end + + fun test187 is test do + var md = """ aaa\nbbb\n""" + var exp = """aaa\nbbb\n""" + assert md_to_md(md) == exp + end +end + +class TestMdHTMLBlocks + super TestMarkdownMd + test + + fun test116 is test do + var md = """
\n
\n*Hello*,\n\n_world_.\n
\n\n
\n""" + assert md_to_md(md) == md + end + + fun test119 is test do + var md = """\n*foo*\n""" + assert md_to_md(md) == md + end + + fun test120 is test do + var md = """
\n\n*Markdown*\n\n
\n""" + assert md_to_md(md) == md + end + + fun test121 is test do + var md = """
\n
\n""" + assert md_to_md(md) == md + end + + fun test124 is test do + var md = """
\nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags\n\n""" + assert md_to_md(md) == md + end + + fun test138 is test do + var md = """\n""" + assert md_to_md(md) == md + end + + fun test139 is test do + var md = """\nh1 {color:red;}\n\np {color:blue;}\n\n""" + assert md_to_md(md) == md + end + + fun test149 is test do + var md = """\n""" + assert md_to_md(md) == md + end + + fun test150 is test do + var md = """ \n\n \n""" + assert md_to_md(md) == md + end +end + +# Inlines + +class TestMdLinks + super TestMarkdownMd + test + + fun test_autolink is test do + var md = """\n""" + assert md_to_md(md) == md + end + + fun test_automail is test do + var md = """\n""" + assert md_to_md(md) == md + end + + fun test462 is test do + var md = """[link](/uri "title")\n""" + assert md_to_md(md) == md + end + + fun test463 is test do + var md = """[link](/uri)\n""" + assert md_to_md(md) == md + end + + fun test464 is test do + var md = """[link]()\n""" + assert md_to_md(md) == md + end + + fun test467 is test do + var md = """[link]()\n""" + assert md_to_md(md) == md + end + + fun test483 is test do + var md = """[link](/url 'title "and" title')\n""" + assert md_to_md(md) == """[link](/url "title \\"and\\" title")\n""" + end + + fun test490 is test do + var md = """[link *foo **bar** `#`*](/uri)\n""" + assert md_to_md(md) == md + end +end + +class TestMdImages + super TestMarkdownMd + test + + fun test546 is test do + var md = """![foo](/url "title")\n""" + assert md_to_md(md) == md + end + + fun test552 is test do + var md = """![foo](train.jpg)\n""" + assert md_to_md(md) == md + end + + fun test554 is test do + var md = """![foo]()\n""" + assert md_to_md(md) == md + end + + fun test555 is test do + var md = """![foo](train.jpg)\n""" + assert md_to_md(md) == md + end +end + +class TestMdCodeSpans + super TestMarkdownMd + test + + fun test316 is test do + var md = """`foo`\n""" + assert md_to_md(md) == md + end + + fun test319 is test do + var md = """``foo``\n""" + assert md_to_md(md) == md + end + + fun test332 is test do + var md = """`foo``bar``\n""" + assert md_to_md(md) == md + end +end + +class TestMdEmphasisAndStrongEmphasis + super TestMarkdownMd + test + + fun test333 is test do + var md = """*foo bar*\n""" + assert md_to_md(md) == md + end + + fun test335 is test do + var md = """a*"foo"*\n""" + assert md_to_md(md) == md + end + + fun test336 is test do + var md = """* a *\n""" + assert md_to_md(md) == md + end + + fun test351 is test do + var md = """*(*foo*)*\n""" + assert md_to_md(md) == md + end + + fun test360 is test do + var md = """**foo bar**\n""" + assert md_to_md(md) == md + end + + fun test361 is test do + var md = """** foo bar**\n""" + assert md_to_md(md) == md + end + + fun test364 is test do + var md = """__foo bar__\n""" + assert md_to_md(md) == md + end + + fun test393 is test do + var md = """*foo**bar**baz*\n""" + assert md_to_md(md) == md + end +end + +class TestMdLineBreaks + super TestMarkdownMd + test + + fun test609 is test do + var md = """foo\\\nbaz\n""" + assert md_to_md(md) == md + end + + fun test612 is test do + var md = """foo\\\n bar\n""" + var exp = """foo\\\nbar\n""" + assert md_to_md(md) == exp + end + + fun test613 is test do + var md = """*foo \nbar*\n""" + assert md_to_md(md) == md + end + + fun test619 is test do + var md = """foo\\\n""" + assert md_to_md(md) == md + end + + fun test623 is test do + var md = """foo\nbaz\n""" + assert md_to_md(md) == md + end +end + +class TestMdRawHTML + super TestMarkdownMd + test + + fun test590 is test do + var md = """\n""" + assert md_to_md(md) == md + end + + fun test591 is test do + var md = """Foo \n""" + assert md_to_md(md) == md + end + + fun test596 is test do + var md = """\n""" + assert md_to_md(md) == md + end + + fun test599 is test do + var md = """foo \n""" + assert md_to_md(md) == md + end + + fun test601 is test do + var md = """foo foo -->\n\nfoo \n""" + assert md_to_md(md) == md + end + + fun test602 is test do + var md = """foo \n""" + assert md_to_md(md) == md + end + + fun test604 is test do + var md = """foo &<]]>\n""" + assert md_to_md(md) == md + end + + fun test606 is test do + var md = """foo \n""" + assert md_to_md(md) == md + end +end + +class TestMdTabs + super TestMarkdownMd + test + + fun test1 is test do + var md = """\tfoo\tbaz\t\tbim\n""" + assert md_to_md(md) == md + end + + fun test2 is test do + var md = """ \tfoo\tbaz\t\tbim\n""" + var exp = """ foo\tbaz\t\tbim\n""" + assert md_to_md(md) == exp + end + + fun test3 is test do + var md = """ a\ta\n ὐ\ta\n""" + assert md_to_md(md) == md + end + + fun test4 is test do + var md = """ - foo\n\n\tbar\n""" + var exp = """- foo\n\n bar\n""" + assert md_to_md(md) == exp + end + + fun test8 is test do + var md = """ foo\n\tbar\n""" + var exp = """ foo\n bar\n""" + assert md_to_md(md) == exp + end + + fun test9 is test do + var md = """ - foo\n - bar\n\t - baz\n""" + var exp = """- foo\n - bar\n - baz\n""" + assert md_to_md(md) == exp + end + + fun test10 is test do + var md = """#\tFoo\n""" + var exp = """# Foo\n""" + assert md_to_md(md) == exp + end + + fun test11 is test do + var md = """*\t*\t*\t\n""" + assert md_to_md(md) == md + end +end + +class TestMdBackslashEscapes + super TestMarkdownMd + test + + fun test292 is test do + var md = """\\\t\\A\\a\\ \\3\\φ\\«\n""" + assert md_to_md(md) == md + end + + fun test295 is test do + var md = """foo\\\nbar\n""" + assert md_to_md(md) == md + end + + fun test297 is test do + var md = """ \\[\\]\n""" + assert md_to_md(md) == md + end + + fun test298 is test do + var md = """~~~\n\\[\\]\n~~~\n""" + assert md_to_md(md) == md + end + + fun test299 is test do + var md = """\n""" + assert md_to_md(md) == md + end + + fun test300 is test do + var md = """\n""" + assert md_to_md(md) == md + end +end