| diff --git a/CHANGES.rst b/CHANGES.rst |
| index eb204dd..3eb99a7 100644 |
| --- a/CHANGES.rst |
| +++ b/CHANGES.rst |
| @@ -3,6 +3,14 @@ Changelog |
| |
| Here is the full history of mistune. |
| |
| +Version 0.7 |
| +~~~~~~~~~~~ |
| + |
| +Release date not decided. |
| + |
| +* Fix the breaking change in version 0.6 with options: **parse_inline_html** and **parse_block_html** |
| + |
| + |
| Version 0.6 |
| ~~~~~~~~~~~ |
| |
| diff --git a/README.rst b/README.rst |
| index 894833b..f6cb5f9 100644 |
| --- a/README.rst |
| +++ b/README.rst |
| @@ -38,12 +38,6 @@ Installing mistune with pip:: |
| |
| $ pip install mistune |
| |
| -If pip is not available, try easy_install:: |
| - |
| - $ easy_install mistune |
| - |
| -Cython Feature |
| -~~~~~~~~~~~~~~ |
| |
| Mistune can be faster, if you compile with cython:: |
| |
| @@ -59,10 +53,49 @@ A simple API that render a markdown formatted text: |
| |
| import mistune |
| |
| - mistune.markdown('I am using **markdown**') |
| - # output: <p>I am using <strong>markdown</strong></p> |
| + mistune.markdown('I am using **mistune markdown parser**') |
| + # output: <p>I am using <strong>mistune markdown parser</strong></p> |
| + |
| +If you care about performance, it is better to re-use the Markdown instance: |
| + |
| +.. code:: python |
| + |
| + import mistune |
| + |
| + markdown = mistune.Markdown() |
| + markdown('I am using **mistune markdown parser**') |
| + |
| +Mistune has enabled all features by default. You don't have to configure |
| +anything. But there are options for you to change the parser behaviors. |
| + |
| + |
| +Options |
| +------- |
| + |
| +Here is a list of all options that will affect the rendering results, |
| +configure them with ``mistune.Renderer``: |
| + |
| +.. code:: python |
| + |
| + renderer = mistune.Renderer(escape=True, hard_wrap=True) |
| + # use this renderer instance |
| + markdown = mistune.Markdown(renderer=renderer) |
| + markdown(text) |
| + |
| +* **escape**: if set to *True*, all raw html tags will be escaped. |
| +* **hard_wrap**: if set to *True*, it will has GFM line breaks feature. |
| +* **use_xhtml**: if set to *True*, all tags will be in xhtml, for example: ``<hr />``. |
| +* **parse_html**: parse text in block and inline level html. |
| +* **parse_block_html**: parse text only in block level html. |
| +* **parse_inline_html**: parse text only in inline level html. |
| + |
| +When using the default renderer, you can use one of the following shortcuts:: |
| + |
| + mistune.markdown(text, escape=True, hard_wrap=True) |
| + |
| + markdown = mistune.Markdown(escape=True, hard_wrap=True) |
| + markdown(text) |
| |
| -Mistune has all features by default. You don't have to configure anything. |
| |
| Renderer |
| -------- |
| @@ -79,7 +112,7 @@ Here is an example of code highlighting: |
| from pygments.lexers import get_lexer_by_name |
| from pygments.formatters import HtmlFormatter |
| |
| - class MyRenderer(mistune.Renderer): |
| + class HighlightRenderer(mistune.Renderer): |
| def block_code(self, code, lang): |
| if not lang: |
| return '\n<pre><code>%s</code></pre>\n' % \ |
| @@ -88,9 +121,9 @@ Here is an example of code highlighting: |
| formatter = HtmlFormatter() |
| return highlight(code, lexer, formatter) |
| |
| - renderer = MyRenderer() |
| - md = mistune.Markdown(renderer=renderer) |
| - print(md.render('Some Markdown text.')) |
| + renderer = HighlightRenderer() |
| + markdown = mistune.Markdown(renderer=renderer) |
| + print(markdown('Some code text.')) |
| |
| |
| Block Level |
| @@ -127,34 +160,18 @@ Here is a list of span level renderer API:: |
| linebreak() |
| newline() |
| link(link, title, content) |
| - tag(html) |
| strikethrough(text) |
| text(text) |
| + inline_html(text) |
| |
| +Footnotes |
| +~~~~~~~~~ |
| |
| -Options |
| -------- |
| - |
| -Here is a list of all options that will affect the rendering results: |
| - |
| -.. code:: python |
| - |
| - renderer = mistune.Renderer(escape=True) |
| - md = mistune.Markdown(renderer=renderer) |
| - md.render(text) |
| - |
| -* **escape**: if set to *True*, all raw html tags will be escaped. |
| -* **hard_wrap**: if set to *True*, it will has GFM line breaks feature. |
| -* **use_xhtml**: if set to *True*, all tags will be in xhtml, for example: ``<hr />``. |
| -* **parse_html**: parse text in block level html. |
| - |
| -When using the default renderer, you can use one of the following shorthands:: |
| - |
| - mistune.markdown(text, escape=True) |
| - |
| - md = mistune.Markdown(escape=True) |
| - md.render(text) |
| +Here is a list of renderers related to footnotes:: |
| |
| + footnote_ref(key, index) |
| + footnote_item(key, text) |
| + footnotes(text) |
| |
| Lexers |
| ------ |
| @@ -172,33 +189,23 @@ It is an inline grammar, which requires custom ``InlineGrammar`` and |
| import copy |
| from mistune import Renderer, InlineGrammar, InlineLexer |
| |
| - class MyRenderer(Renderer): |
| + class WikiLinkRenderer(Renderer): |
| def wiki_link(self, alt, link): |
| return '<a href="%s">%s</a>' % (link, alt) |
| |
| + class WikiLinkInlineLexer(InlineLexer): |
| + def enable_wiki_link(self): |
| + # add wiki_link rules |
| + self.rules.wiki_link = re.compile( |
| + r'\[\[' # [[ |
| + r'([\s\S]+?\|[\s\S]+?)' # Page 2|Page 2 |
| + r'\]\](?!\])' # ]] |
| + ) |
| |
| - class MyInlineGrammar(InlineGrammar): |
| - # it would take a while for creating the right regex |
| - wiki_link = re.compile( |
| - r'\[\[' # [[ |
| - r'([\s\S]+?\|[\s\S]+?)' # Page 2|Page 2 |
| - r'\]\](?!\])' # ]] |
| - ) |
| - |
| - |
| - class MyInlineLexer(InlineLexer): |
| - default_rules = copy.copy(InlineLexer.default_rules) |
| - |
| - # Add wiki_link parser to default rules |
| - # you can insert it any place you like |
| - default_rules.insert(3, 'wiki_link') |
| - |
| - def __init__(self, renderer, rules=None, **kwargs): |
| - if rules is None: |
| - # use the inline grammar |
| - rules = MyInlineGrammar() |
| - |
| - super(MyInlineLexer, self).__init__(renderer, rules, **kwargs) |
| + # Add wiki_link parser to default rules |
| + # you can insert it some place you like |
| + # but place matters, maybe 3 is not good |
| + self.default_rules.insert(3, 'wiki_link') |
| |
| def output_wiki_link(self, m): |
| text = m.group(1) |
| @@ -211,8 +218,10 @@ You should pass the inline lexer to ``Markdown`` parser: |
| |
| .. code:: python |
| |
| - renderer = MyRenderer() |
| - inline = MyInlineLexer(renderer) |
| + renderer = WikiLinkRenderer() |
| + inline = WikiLinkInlineLexer(renderer) |
| + # enable the feature |
| + inline.enable_wiki_link() |
| markdown = Markdown(renderer, inline=inline) |
| markdown('[[Link Text|Wiki Link]]') |
| |
| @@ -220,12 +229,21 @@ It is the same with block level lexer. It would take a while to understand |
| the whole mechanism. But you won't do the trick a lot. |
| |
| |
| -Contribution |
| ------------- |
| +Contribution & Extensions |
| +------------------------- |
| |
| Mistune itself doesn't accept any extension. It will always be a simple one |
| file script. |
| |
| If you want to add features, you can head over to `mistune-contrib`_. |
| |
| +Here are some extensions already in `mistune-contrib`_: |
| + |
| +* Math/MathJax features |
| +* Highlight Code Renderer |
| +* TOC table of content features |
| +* MultiMarkdown Metadata parser |
| + |
| +Get inspired with the contrib repository. |
| + |
| .. _`mistune-contrib`: https://github.com/lepture/mistune-contrib |
| diff --git a/mistune.py b/mistune.py |
| index 316f86d..86d215e 100644 |
| --- a/mistune.py |
| +++ b/mistune.py |
| @@ -476,6 +476,11 @@ class InlineLexer(object): |
| 'double_emphasis', 'emphasis', 'code', |
| 'linebreak', 'strikethrough', 'text', |
| ] |
| + inline_html_rules = [ |
| + 'escape', 'autolink', 'url', 'link', 'reflink', |
| + 'nolink', 'double_emphasis', 'emphasis', 'code', |
| + 'linebreak', 'strikethrough', 'text', |
| + ] |
| |
| def __init__(self, renderer, rules=None, **kwargs): |
| self.renderer = renderer |
| @@ -491,6 +496,10 @@ class InlineLexer(object): |
| self._in_link = False |
| self._in_footnote = False |
| |
| + kwargs.update(self.renderer.options) |
| + _to_parse = kwargs.get('parse_html') or kwargs.get('parse_inline_html') |
| + self._parse_inline_html = _to_parse |
| + |
| def __call__(self, text): |
| return self.output(text) |
| |
| @@ -553,7 +562,15 @@ class InlineLexer(object): |
| return self.renderer.autolink(link, False) |
| |
| def output_inline_html(self, m): |
| - return self.renderer.inline_html(m.group(0)) |
| + text = m.group(0) |
| + if self._parse_inline_html: |
| + if m.group(1) == 'a': |
| + self._in_link = True |
| + text = self.output(text, rules=self.inline_html_rules) |
| + self._in_link = False |
| + else: |
| + text = self.output(text, rules=self.inline_html_rules) |
| + return self.renderer.inline_html(text) |
| |
| def output_footnote(self, m): |
| key = _keyify(m.group(1)) |
| @@ -909,6 +926,10 @@ class Markdown(object): |
| self.footnotes = [] |
| self.tokens = [] |
| |
| + # detect if it should parse text in block html |
| + _to_parse = kwargs.get('parse_html') or kwargs.get('parse_block_html') |
| + self._parse_block_html = _to_parse |
| + |
| def __call__(self, text): |
| return self.parse(text) |
| |
| @@ -1072,7 +1093,7 @@ class Markdown(object): |
| |
| def output_block_html(self): |
| text = self.token['text'] |
| - if self.options.get('parse_html') and not self.token.get('pre'): |
| + if self._parse_block_html and not self.token.get('pre'): |
| text = self.inline(text) |
| return self.renderer.block_html(text) |
| |
| diff --git a/tests/test_cases.py b/tests/test_cases.py |
| index 933fa4c..3853a67 100644 |
| --- a/tests/test_cases.py |
| +++ b/tests/test_cases.py |
| @@ -99,12 +99,36 @@ def test_use_xhtml(): |
| assert '<img src="bar" alt="foo" title="title" />' in ret |
| |
| |
| -def test_block_html(): |
| +def test_parse_html(): |
| ret = mistune.markdown('<div>**foo**</div>') |
| assert '<strong>' not in ret |
| ret = mistune.markdown('<div>**foo**</div>', parse_html=True) |
| assert '<strong>' in ret |
| |
| + ret = mistune.markdown('<span>**foo**</span>') |
| + assert '<strong>' not in ret |
| + ret = mistune.markdown('<span>**foo**</span>', parse_html=True) |
| + assert '<strong>' in ret |
| + |
| + ret = mistune.markdown('<span>http://example.com</span>', parse_html=True) |
| + assert 'href' in ret |
| + ret = mistune.markdown('<a>http://example.com</a>', parse_html=True) |
| + assert 'href' not in ret |
| + |
| + |
| +def test_parse_inline_html(): |
| + ret = mistune.markdown('<div>**foo**</div>', parse_inline_html=True) |
| + assert '<strong>' not in ret |
| + ret = mistune.markdown('<span>**foo**</span>', parse_inline_html=True) |
| + assert '<strong>' in ret |
| + |
| + |
| +def test_parse_block_html(): |
| + ret = mistune.markdown('<div>**foo**</div>', parse_block_html=True) |
| + assert '<strong>' in ret |
| + ret = mistune.markdown('<span>**foo**</span>', parse_block_html=True) |
| + assert '<strong>' not in ret |
| + |
| |
| def test_trigger_more_cases(): |
| markdown = mistune.Markdown( |
| @@ -114,79 +138,3 @@ def test_trigger_more_cases(): |
| ) |
| ret = markdown.render('foo[^foo]\n\n[^foo]: foo\n\n[^foo]: bar\n') |
| assert 'bar' not in ret |
| - |
| - |
| -def test_custom_lexer(): |
| - import copy |
| - |
| - class MyInlineGrammar(mistune.InlineGrammar): |
| - # it would take a while for creating the right regex |
| - wiki_link = re.compile( |
| - r'\[\[' # [[ |
| - r'([\s\S]+?\|[\s\S]+?)' # Page 2|Page 2 |
| - r'\]\](?!\])' # ]] |
| - ) |
| - |
| - class MyInlineLexer(mistune.InlineLexer): |
| - default_rules = copy.copy(mistune.InlineLexer.default_rules) |
| - default_rules.insert(3, 'wiki_link') |
| - |
| - def __init__(self, renderer, rules=None, **kwargs): |
| - if rules is None: |
| - rules = MyInlineGrammar() |
| - |
| - super(MyInlineLexer, self).__init__(renderer, rules, **kwargs) |
| - |
| - def output_wiki_link(self, m): |
| - text = m.group(1) |
| - alt, link = text.split('|') |
| - return '<a href="%s">%s</a>' % (link, alt) |
| - |
| - markdown = mistune.Markdown(inline=MyInlineLexer) |
| - ret = markdown('[[Link Text|Wiki Link]]') |
| - assert '<a href' in ret |
| - |
| - |
| -def test_token_tree(): |
| - """Tests a Renderer that returns a list from the placeholder method.""" |
| - |
| - class CustomRenderer(mistune.Renderer): |
| - def placeholder(self): |
| - return [] |
| - |
| - def __getattribute__(self, name): |
| - """Saves the arguments to each Markdown handling method.""" |
| - found = CustomRenderer.__dict__.get(name) |
| - if found: |
| - return object.__getattribute__(self, name) |
| - |
| - def fake_method(*args, **kwargs): |
| - return [(name, args, kwargs)] |
| - return fake_method |
| - |
| - with open(os.path.join(root, 'fixtures', 'data', 'tree.md')) as f: |
| - content = f.read() |
| - |
| - expected = [ |
| - ('header', ([('text', ('Title here',), {})], 2, 'Title here'), {}), |
| - ('paragraph', ([('text', ('Some text.',), {})],), {}), |
| - ('paragraph', |
| - ([('text', ('In two paragraphs. And then a list.',), {})],), |
| - {}), |
| - ('list', |
| - ([('list_item', ([('text', ('foo',), {})],), {}), |
| - ('list_item', |
| - ([('text', ('bar',), {}), |
| - ('list', |
| - ([('list_item', ([('text', ('meep',), {})],), {}), |
| - ('list_item', ([('text', ('stuff',), {})],), {})], |
| - True), |
| - {})],), |
| - {})], |
| - False), |
| - {}) |
| - ] |
| - |
| - processor = mistune.Markdown(renderer=CustomRenderer()) |
| - found = processor.render(content) |
| - assert expected == found, "Expected:\n%r\n\nFound:\n%r" % (expected, found) |
| diff --git a/tests/test_subclassing.py b/tests/test_subclassing.py |
| index 2cebfc0..f0df225 100644 |
| --- a/tests/test_subclassing.py |
| +++ b/tests/test_subclassing.py |
| @@ -2,6 +2,7 @@ |
| |
| import os |
| import re |
| +import copy |
| import mistune |
| |
| root = os.path.dirname(__file__) |
| @@ -73,7 +74,7 @@ class MarkdownWithMath(mistune.Markdown): |
| ) |
| |
| |
| -class CustomRenderer(mistune.Renderer): |
| +class MathRenderer(mistune.Renderer): |
| def block_math(self, text): |
| return '$$%s$$' % text |
| |
| @@ -92,7 +93,7 @@ def assert_data(filename): |
| else: |
| text = filename |
| |
| - rv = MarkdownWithMath(renderer=CustomRenderer()).render(text) |
| + rv = MarkdownWithMath(renderer=MathRenderer()).render(text) |
| assert text in rv |
| |
| |
| @@ -109,3 +110,82 @@ def test_markdown2html_math(): |
| def test_math_paragraph(): |
| # https://github.com/ipython/ipython/issues/6724 |
| assert_data('math-paragraph.md') |
| + |
| + |
| +class WikiInlineGrammar(mistune.InlineGrammar): |
| + # it would take a while for creating the right regex |
| + wiki_link = re.compile( |
| + r'\[\[' # [[ |
| + r'([\s\S]+?\|[\s\S]+?)' # Page 2|Page 2 |
| + r'\]\](?!\])' # ]] |
| + ) |
| + |
| + |
| +class WikiInlineLexer(mistune.InlineLexer): |
| + default_rules = copy.copy(mistune.InlineLexer.default_rules) |
| + default_rules.insert(3, 'wiki_link') |
| + |
| + def __init__(self, renderer, rules=None, **kwargs): |
| + if rules is None: |
| + rules = WikiInlineGrammar() |
| + |
| + super(WikiInlineLexer, self).__init__(renderer, rules, **kwargs) |
| + |
| + def output_wiki_link(self, m): |
| + text = m.group(1) |
| + alt, link = text.split('|') |
| + return '<a href="%s">%s</a>' % (link, alt) |
| + |
| + |
| +def test_custom_lexer(): |
| + markdown = mistune.Markdown(inline=WikiInlineLexer) |
| + ret = markdown('[[Link Text|Wiki Link]]') |
| + assert '<a href' in ret |
| + |
| + |
| +class TokenTreeRenderer(mistune.Renderer): |
| + # options is required |
| + options = {} |
| + |
| + def placeholder(self): |
| + return [] |
| + |
| + def __getattribute__(self, name): |
| + """Saves the arguments to each Markdown handling method.""" |
| + found = TokenTreeRenderer.__dict__.get(name) |
| + if found is not None: |
| + return object.__getattribute__(self, name) |
| + |
| + def fake_method(*args, **kwargs): |
| + return [(name, args, kwargs)] |
| + return fake_method |
| + |
| + |
| +def test_token_tree(): |
| + """Tests a Renderer that returns a list from the placeholder method.""" |
| + with open(os.path.join(root, 'fixtures', 'data', 'tree.md')) as f: |
| + content = f.read() |
| + |
| + expected = [ |
| + ('header', ([('text', ('Title here',), {})], 2, 'Title here'), {}), |
| + ('paragraph', ([('text', ('Some text.',), {})],), {}), |
| + ('paragraph', |
| + ([('text', ('In two paragraphs. And then a list.',), {})],), |
| + {}), |
| + ('list', |
| + ([('list_item', ([('text', ('foo',), {})],), {}), |
| + ('list_item', |
| + ([('text', ('bar',), {}), |
| + ('list', |
| + ([('list_item', ([('text', ('meep',), {})],), {}), |
| + ('list_item', ([('text', ('stuff',), {})],), {})], |
| + True), |
| + {})],), |
| + {})], |
| + False), |
| + {}) |
| + ] |
| + |
| + processor = mistune.Markdown(renderer=TokenTreeRenderer()) |
| + found = processor.render(content) |
| + assert expected == found, "Expected:\n%r\n\nFound:\n%r" % (expected, found) |