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