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