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