##// END OF EJS Templates
Rename mistune renderer class
Thomas Kluyver -
Show More
@@ -1,206 +1,206 b''
1 """Markdown filters
1 """Markdown filters
2 This file contains a collection of utility filters for dealing with
2 This file contains a collection of utility filters for dealing with
3 markdown within Jinja templates.
3 markdown within Jinja templates.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Stdlib imports
18 # Stdlib imports
19 import os
19 import os
20 import subprocess
20 import subprocess
21 from io import TextIOWrapper, BytesIO
21 from io import TextIOWrapper, BytesIO
22 import re
22 import re
23
23
24 import mistune
24 import mistune
25 from pygments import highlight
25 from pygments import highlight
26 from pygments.lexers import get_lexer_by_name
26 from pygments.lexers import get_lexer_by_name
27 from pygments.formatters import HtmlFormatter
27 from pygments.formatters import HtmlFormatter
28
28
29 # IPython imports
29 # IPython imports
30 from IPython.nbconvert.utils.pandoc import pandoc
30 from IPython.nbconvert.utils.pandoc import pandoc
31 from IPython.nbconvert.utils.exceptions import ConversionException
31 from IPython.nbconvert.utils.exceptions import ConversionException
32 from IPython.utils.process import get_output_error_code
32 from IPython.utils.process import get_output_error_code
33 from IPython.utils.py3compat import cast_bytes
33 from IPython.utils.py3compat import cast_bytes
34 from IPython.utils.version import check_version
34 from IPython.utils.version import check_version
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Functions
37 # Functions
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 marked = os.path.join(os.path.dirname(__file__), "marked.js")
39 marked = os.path.join(os.path.dirname(__file__), "marked.js")
40 _node = None
40 _node = None
41
41
42 __all__ = [
42 __all__ = [
43 'markdown2html',
43 'markdown2html',
44 'markdown2html_pandoc',
44 'markdown2html_pandoc',
45 'markdown2html_marked',
45 'markdown2html_marked',
46 'markdown2html_mistune',
46 'markdown2html_mistune',
47 'markdown2latex',
47 'markdown2latex',
48 'markdown2rst',
48 'markdown2rst',
49 ]
49 ]
50
50
51 class NodeJSMissing(ConversionException):
51 class NodeJSMissing(ConversionException):
52 """Exception raised when node.js is missing."""
52 """Exception raised when node.js is missing."""
53 pass
53 pass
54
54
55 def markdown2latex(source):
55 def markdown2latex(source):
56 """Convert a markdown string to LaTeX via pandoc.
56 """Convert a markdown string to LaTeX via pandoc.
57
57
58 This function will raise an error if pandoc is not installed.
58 This function will raise an error if pandoc is not installed.
59 Any error messages generated by pandoc are printed to stderr.
59 Any error messages generated by pandoc are printed to stderr.
60
60
61 Parameters
61 Parameters
62 ----------
62 ----------
63 source : string
63 source : string
64 Input string, assumed to be valid markdown.
64 Input string, assumed to be valid markdown.
65
65
66 Returns
66 Returns
67 -------
67 -------
68 out : string
68 out : string
69 Output as returned by pandoc.
69 Output as returned by pandoc.
70 """
70 """
71 return pandoc(source, 'markdown', 'latex')
71 return pandoc(source, 'markdown', 'latex')
72
72
73 class MathBlockGrammar(mistune.BlockGrammar):
73 class MathBlockGrammar(mistune.BlockGrammar):
74 block_math = re.compile("^\$\$(.*?)\$\$")
74 block_math = re.compile("^\$\$(.*?)\$\$")
75 block_math2 = re.compile(r"^\\begin(.*?)\\end")
75 block_math2 = re.compile(r"^\\begin(.*?)\\end")
76
76
77 class MathBlockLexer(mistune.BlockLexer):
77 class MathBlockLexer(mistune.BlockLexer):
78 default_features = ['block_math', 'block_math2'] + mistune.BlockLexer.default_features
78 default_features = ['block_math', 'block_math2'] + mistune.BlockLexer.default_features
79
79
80 def __init__(self, rules=None, **kwargs):
80 def __init__(self, rules=None, **kwargs):
81 if rules is None:
81 if rules is None:
82 rules = MathBlockGrammar()
82 rules = MathBlockGrammar()
83 super(MathBlockLexer, self).__init__(rules, **kwargs)
83 super(MathBlockLexer, self).__init__(rules, **kwargs)
84
84
85 def parse_block_math(self, m):
85 def parse_block_math(self, m):
86 """Parse a $$math$$ block"""
86 """Parse a $$math$$ block"""
87 self.tokens.append({
87 self.tokens.append({
88 'type': 'block_math',
88 'type': 'block_math',
89 'text': m.group(1)
89 'text': m.group(1)
90 })
90 })
91
91
92 parse_block_math2 = parse_block_math
92 parse_block_math2 = parse_block_math
93
93
94 class MathInlineGrammar(mistune.InlineGrammar):
94 class MathInlineGrammar(mistune.InlineGrammar):
95 math = re.compile("^\$(.+?)\$")
95 math = re.compile("^\$(.+?)\$")
96
96
97 class MathInlineLexer(mistune.InlineLexer):
97 class MathInlineLexer(mistune.InlineLexer):
98 default_features = ['math'] + mistune.InlineLexer.default_features
98 default_features = ['math'] + mistune.InlineLexer.default_features
99
99
100 def __init__(self, renderer, rules=None, **kwargs):
100 def __init__(self, renderer, rules=None, **kwargs):
101 if rules is None:
101 if rules is None:
102 rules = MathInlineGrammar()
102 rules = MathInlineGrammar()
103 super(MathInlineLexer, self).__init__(renderer, rules, **kwargs)
103 super(MathInlineLexer, self).__init__(renderer, rules, **kwargs)
104
104
105 def output_math(self, m):
105 def output_math(self, m):
106 self.renderer.inline_math(m.group(1))
106 self.renderer.inline_math(m.group(1))
107
107
108 class MarkdownWithMath(mistune.Markdown):
108 class MarkdownWithMath(mistune.Markdown):
109 def __init__(self, renderer, **kwargs):
109 def __init__(self, renderer, **kwargs):
110 if 'inline' not in kwargs:
110 if 'inline' not in kwargs:
111 kwargs['inline'] = MathInlineLexer(renderer, **kwargs)
111 kwargs['inline'] = MathInlineLexer(renderer, **kwargs)
112 if 'block' not in kwargs:
112 if 'block' not in kwargs:
113 kwargs['block'] = MathBlockLexer(**kwargs)
113 kwargs['block'] = MathBlockLexer(**kwargs)
114 super(MarkdownWithMath, self).__init__(renderer, **kwargs)
114 super(MarkdownWithMath, self).__init__(renderer, **kwargs)
115
115
116 def parse_block_math(self):
116 def parse_block_math(self):
117 return self.renderer.block_math(self.token['text'])
117 return self.renderer.block_math(self.token['text'])
118
118
119 class MyRenderer(mistune.Renderer):
119 class IPythonRenderer(mistune.Renderer):
120 def block_code(self, code, lang):
120 def block_code(self, code, lang):
121 if not lang:
121 if not lang:
122 return '\n<pre><code>%s</code></pre>\n' % \
122 return '\n<pre><code>%s</code></pre>\n' % \
123 mistune.escape(code)
123 mistune.escape(code)
124 lexer = get_lexer_by_name(lang, stripall=True)
124 lexer = get_lexer_by_name(lang, stripall=True)
125 formatter = HtmlFormatter()
125 formatter = HtmlFormatter()
126 return highlight(code, lexer, formatter)
126 return highlight(code, lexer, formatter)
127
127
128 # Pass math through unaltered - mathjax does the rendering in the browser
128 # Pass math through unaltered - mathjax does the rendering in the browser
129 def block_math(self, text):
129 def block_math(self, text):
130 return '$$%s$$' % text
130 return '$$%s$$' % text
131
131
132 def inline_math(self, text):
132 def inline_math(self, text):
133 return '$%s$' % text
133 return '$%s$' % text
134
134
135 def markdown2html_mistune(source):
135 def markdown2html_mistune(source):
136 """Convert a markdown string to HTML using mistune"""
136 """Convert a markdown string to HTML using mistune"""
137 return MarkdownWithMath(renderer=MyRenderer()).render(source)
137 return MarkdownWithMath(renderer=IPythonRenderer()).render(source)
138
138
139 def markdown2html_pandoc(source):
139 def markdown2html_pandoc(source):
140 """Convert a markdown string to HTML via pandoc"""
140 """Convert a markdown string to HTML via pandoc"""
141 return pandoc(source, 'markdown', 'html', extra_args=['--mathjax'])
141 return pandoc(source, 'markdown', 'html', extra_args=['--mathjax'])
142
142
143 def _find_nodejs():
143 def _find_nodejs():
144 global _node
144 global _node
145 if _node is None:
145 if _node is None:
146 # prefer md2html via marked if node.js >= 0.9.12 is available
146 # prefer md2html via marked if node.js >= 0.9.12 is available
147 # node is called nodejs on debian, so try that first
147 # node is called nodejs on debian, so try that first
148 _node = 'nodejs'
148 _node = 'nodejs'
149 if not _verify_node(_node):
149 if not _verify_node(_node):
150 _node = 'node'
150 _node = 'node'
151 return _node
151 return _node
152
152
153 def markdown2html_marked(source, encoding='utf-8'):
153 def markdown2html_marked(source, encoding='utf-8'):
154 """Convert a markdown string to HTML via marked"""
154 """Convert a markdown string to HTML via marked"""
155 command = [_find_nodejs(), marked]
155 command = [_find_nodejs(), marked]
156 try:
156 try:
157 p = subprocess.Popen(command,
157 p = subprocess.Popen(command,
158 stdin=subprocess.PIPE, stdout=subprocess.PIPE
158 stdin=subprocess.PIPE, stdout=subprocess.PIPE
159 )
159 )
160 except OSError as e:
160 except OSError as e:
161 raise NodeJSMissing(
161 raise NodeJSMissing(
162 "The command '%s' returned an error: %s.\n" % (" ".join(command), e) +
162 "The command '%s' returned an error: %s.\n" % (" ".join(command), e) +
163 "Please check that Node.js is installed."
163 "Please check that Node.js is installed."
164 )
164 )
165 out, _ = p.communicate(cast_bytes(source, encoding))
165 out, _ = p.communicate(cast_bytes(source, encoding))
166 out = TextIOWrapper(BytesIO(out), encoding, 'replace').read()
166 out = TextIOWrapper(BytesIO(out), encoding, 'replace').read()
167 return out.rstrip('\n')
167 return out.rstrip('\n')
168
168
169 # The mistune renderer is the default, because it's simple to depend on it
169 # The mistune renderer is the default, because it's simple to depend on it
170 markdown2html = markdown2html_marked
170 markdown2html = markdown2html_marked
171
171
172 def markdown2rst(source):
172 def markdown2rst(source):
173 """Convert a markdown string to ReST via pandoc.
173 """Convert a markdown string to ReST via pandoc.
174
174
175 This function will raise an error if pandoc is not installed.
175 This function will raise an error if pandoc is not installed.
176 Any error messages generated by pandoc are printed to stderr.
176 Any error messages generated by pandoc are printed to stderr.
177
177
178 Parameters
178 Parameters
179 ----------
179 ----------
180 source : string
180 source : string
181 Input string, assumed to be valid markdown.
181 Input string, assumed to be valid markdown.
182
182
183 Returns
183 Returns
184 -------
184 -------
185 out : string
185 out : string
186 Output as returned by pandoc.
186 Output as returned by pandoc.
187 """
187 """
188 return pandoc(source, 'markdown', 'rst')
188 return pandoc(source, 'markdown', 'rst')
189
189
190 def _verify_node(cmd):
190 def _verify_node(cmd):
191 """Verify that the node command exists and is at least the minimum supported
191 """Verify that the node command exists and is at least the minimum supported
192 version of node.
192 version of node.
193
193
194 Parameters
194 Parameters
195 ----------
195 ----------
196 cmd : string
196 cmd : string
197 Node command to verify (i.e 'node')."""
197 Node command to verify (i.e 'node')."""
198 try:
198 try:
199 out, err, return_code = get_output_error_code([cmd, '--version'])
199 out, err, return_code = get_output_error_code([cmd, '--version'])
200 except OSError:
200 except OSError:
201 # Command not found
201 # Command not found
202 return False
202 return False
203 if return_code:
203 if return_code:
204 # Command error
204 # Command error
205 return False
205 return False
206 return check_version(out.lstrip('v'), '0.9.12')
206 return check_version(out.lstrip('v'), '0.9.12')
General Comments 0
You need to be logged in to leave comments. Login now