##// END OF EJS Templates
nbconvert: don't require mistune unless it's used...
Min RK -
Show More
@@ -0,0 +1,118
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
1 """Markdown filters
1 """Markdown filters
2
2
3 This file contains a collection of utility filters for dealing with
3 This file contains a collection of utility filters for dealing with
4 markdown within Jinja templates.
4 markdown within Jinja templates.
5 """
5 """
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 from __future__ import print_function
9 from __future__ import print_function
10
10
11 # Stdlib imports
12 import os
11 import os
13 import subprocess
12 import subprocess
14 from io import TextIOWrapper, BytesIO
13 from io import TextIOWrapper, BytesIO
15 import re
16
14
17 import mistune
15 try:
18 from pygments import highlight
16 from .markdown_mistune import markdown2html_mistune
19 from pygments.lexers import get_lexer_by_name
17 except ImportError as e:
20 from pygments.formatters import HtmlFormatter
18 # store in variable for Python 3
21 from pygments.util import ClassNotFound
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 from IPython.nbconvert.utils.pandoc import pandoc
24 from IPython.nbconvert.utils.pandoc import pandoc
26 from IPython.nbconvert.utils.exceptions import ConversionException
25 from IPython.nbconvert.utils.exceptions import ConversionException
27 from IPython.utils.decorators import undoc
28 from IPython.utils.process import get_output_error_code
26 from IPython.utils.process import get_output_error_code
29 from IPython.utils.py3compat import cast_bytes
27 from IPython.utils.py3compat import cast_bytes
30 from IPython.utils.version import check_version
28 from IPython.utils.version import check_version
31
29
32
30
33 marked = os.path.join(os.path.dirname(__file__), "marked.js")
31 marked = os.path.join(os.path.dirname(__file__), "marked.js")
34 _node = None
32 _node = None
35
33
36 __all__ = [
34 __all__ = [
37 'markdown2html',
35 'markdown2html',
38 'markdown2html_pandoc',
36 'markdown2html_pandoc',
39 'markdown2html_marked',
37 'markdown2html_marked',
40 'markdown2html_mistune',
38 'markdown2html_mistune',
41 'markdown2latex',
39 'markdown2latex',
42 'markdown2rst',
40 'markdown2rst',
43 ]
41 ]
44
42
45 class NodeJSMissing(ConversionException):
43 class NodeJSMissing(ConversionException):
46 """Exception raised when node.js is missing."""
44 """Exception raised when node.js is missing."""
47 pass
45 pass
48
46
47
49 def markdown2latex(source, markup='markdown', extra_args=None):
48 def markdown2latex(source, markup='markdown', extra_args=None):
50 """Convert a markdown string to LaTeX via pandoc.
49 """Convert a markdown string to LaTeX via pandoc.
51
50
52 This function will raise an error if pandoc is not installed.
51 This function will raise an error if pandoc is not installed.
53 Any error messages generated by pandoc are printed to stderr.
52 Any error messages generated by pandoc are printed to stderr.
54
53
55 Parameters
54 Parameters
56 ----------
55 ----------
57 source : string
56 source : string
58 Input string, assumed to be valid markdown.
57 Input string, assumed to be valid markdown.
59 markup : string
58 markup : string
60 Markup used by pandoc's reader
59 Markup used by pandoc's reader
61 default : pandoc extended markdown
60 default : pandoc extended markdown
62 (see http://johnmacfarlane.net/pandoc/README.html#pandocs-markdown)
61 (see http://johnmacfarlane.net/pandoc/README.html#pandocs-markdown)
63
62
64 Returns
63 Returns
65 -------
64 -------
66 out : string
65 out : string
67 Output as returned by pandoc.
66 Output as returned by pandoc.
68 """
67 """
69 return pandoc(source, markup, 'latex', extra_args=extra_args)
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 def markdown2html_pandoc(source, extra_args=None):
71 def markdown2html_pandoc(source, extra_args=None):
169 """Convert a markdown string to HTML via pandoc"""
72 """Convert a markdown string to HTML via pandoc"""
170 extra_args = extra_args or ['--mathjax']
73 extra_args = extra_args or ['--mathjax']
171 return pandoc(source, 'markdown', 'html', extra_args=extra_args)
74 return pandoc(source, 'markdown', 'html', extra_args=extra_args)
172
75
76
173 def _find_nodejs():
77 def _find_nodejs():
174 global _node
78 global _node
175 if _node is None:
79 if _node is None:
176 # prefer md2html via marked if node.js >= 0.9.12 is available
80 # prefer md2html via marked if node.js >= 0.9.12 is available
177 # node is called nodejs on debian, so try that first
81 # node is called nodejs on debian, so try that first
178 _node = 'nodejs'
82 _node = 'nodejs'
179 if not _verify_node(_node):
83 if not _verify_node(_node):
180 _node = 'node'
84 _node = 'node'
181 return _node
85 return _node
182
86
183 def markdown2html_marked(source, encoding='utf-8'):
87 def markdown2html_marked(source, encoding='utf-8'):
184 """Convert a markdown string to HTML via marked"""
88 """Convert a markdown string to HTML via marked"""
185 command = [_find_nodejs(), marked]
89 command = [_find_nodejs(), marked]
186 try:
90 try:
187 p = subprocess.Popen(command,
91 p = subprocess.Popen(command,
188 stdin=subprocess.PIPE, stdout=subprocess.PIPE
92 stdin=subprocess.PIPE, stdout=subprocess.PIPE
189 )
93 )
190 except OSError as e:
94 except OSError as e:
191 raise NodeJSMissing(
95 raise NodeJSMissing(
192 "The command '%s' returned an error: %s.\n" % (" ".join(command), e) +
96 "The command '%s' returned an error: %s.\n" % (" ".join(command), e) +
193 "Please check that Node.js is installed."
97 "Please check that Node.js is installed."
194 )
98 )
195 out, _ = p.communicate(cast_bytes(source, encoding))
99 out, _ = p.communicate(cast_bytes(source, encoding))
196 out = TextIOWrapper(BytesIO(out), encoding, 'replace').read()
100 out = TextIOWrapper(BytesIO(out), encoding, 'replace').read()
197 return out.rstrip('\n')
101 return out.rstrip('\n')
198
102
199 # The mistune renderer is the default, because it's simple to depend on it
103 # The mistune renderer is the default, because it's simple to depend on it
200 markdown2html = markdown2html_mistune
104 markdown2html = markdown2html_mistune
201
105
202 def markdown2rst(source, extra_args=None):
106 def markdown2rst(source, extra_args=None):
203 """Convert a markdown string to ReST via pandoc.
107 """Convert a markdown string to ReST via pandoc.
204
108
205 This function will raise an error if pandoc is not installed.
109 This function will raise an error if pandoc is not installed.
206 Any error messages generated by pandoc are printed to stderr.
110 Any error messages generated by pandoc are printed to stderr.
207
111
208 Parameters
112 Parameters
209 ----------
113 ----------
210 source : string
114 source : string
211 Input string, assumed to be valid markdown.
115 Input string, assumed to be valid markdown.
212
116
213 Returns
117 Returns
214 -------
118 -------
215 out : string
119 out : string
216 Output as returned by pandoc.
120 Output as returned by pandoc.
217 """
121 """
218 return pandoc(source, 'markdown', 'rst', extra_args=extra_args)
122 return pandoc(source, 'markdown', 'rst', extra_args=extra_args)
219
123
220 def _verify_node(cmd):
124 def _verify_node(cmd):
221 """Verify that the node command exists and is at least the minimum supported
125 """Verify that the node command exists and is at least the minimum supported
222 version of node.
126 version of node.
223
127
224 Parameters
128 Parameters
225 ----------
129 ----------
226 cmd : string
130 cmd : string
227 Node command to verify (i.e 'node')."""
131 Node command to verify (i.e 'node')."""
228 try:
132 try:
229 out, err, return_code = get_output_error_code([cmd, '--version'])
133 out, err, return_code = get_output_error_code([cmd, '--version'])
230 except OSError:
134 except OSError:
231 # Command not found
135 # Command not found
232 return False
136 return False
233 if return_code:
137 if return_code:
234 # Command error
138 # Command error
235 return False
139 return False
236 return check_version(out.lstrip('v'), '0.9.12')
140 return check_version(out.lstrip('v'), '0.9.12')
General Comments 0
You need to be logged in to leave comments. Login now