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