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