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