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