##// END OF EJS Templates
Merge pull request #4342 from abhinav-upadhyay/unnessary-variable-assignment...
Thomas Kluyver -
r12948:f8bc560e merge
parent child Browse files
Show More
@@ -1,338 +1,337 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 A module to change reload() so that it acts recursively.
3 A module to change reload() so that it acts recursively.
4 To enable it type::
4 To enable it type::
5
5
6 import __builtin__, deepreload
6 import __builtin__, deepreload
7 __builtin__.reload = deepreload.reload
7 __builtin__.reload = deepreload.reload
8
8
9 You can then disable it with::
9 You can then disable it with::
10
10
11 __builtin__.reload = deepreload.original_reload
11 __builtin__.reload = deepreload.original_reload
12
12
13 Alternatively, you can add a dreload builtin alongside normal reload with::
13 Alternatively, you can add a dreload builtin alongside normal reload with::
14
14
15 __builtin__.dreload = deepreload.reload
15 __builtin__.dreload = deepreload.reload
16
16
17 This code is almost entirely based on knee.py, which is a Python
17 This code is almost entirely based on knee.py, which is a Python
18 re-implementation of hierarchical module import.
18 re-implementation of hierarchical module import.
19 """
19 """
20 #*****************************************************************************
20 #*****************************************************************************
21 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
21 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
22 #
22 #
23 # Distributed under the terms of the BSD License. The full license is in
23 # Distributed under the terms of the BSD License. The full license is in
24 # the file COPYING, distributed as part of this software.
24 # the file COPYING, distributed as part of this software.
25 #*****************************************************************************
25 #*****************************************************************************
26
26
27 import __builtin__
27 import __builtin__
28 from contextlib import contextmanager
28 from contextlib import contextmanager
29 import imp
29 import imp
30 import sys
30 import sys
31
31
32 from types import ModuleType
32 from types import ModuleType
33 from warnings import warn
33 from warnings import warn
34
34
35 original_import = __builtin__.__import__
35 original_import = __builtin__.__import__
36
36
37 @contextmanager
37 @contextmanager
38 def replace_import_hook(new_import):
38 def replace_import_hook(new_import):
39 saved_import = __builtin__.__import__
39 saved_import = __builtin__.__import__
40 __builtin__.__import__ = new_import
40 __builtin__.__import__ = new_import
41 try:
41 try:
42 yield
42 yield
43 finally:
43 finally:
44 __builtin__.__import__ = saved_import
44 __builtin__.__import__ = saved_import
45
45
46 def get_parent(globals, level):
46 def get_parent(globals, level):
47 """
47 """
48 parent, name = get_parent(globals, level)
48 parent, name = get_parent(globals, level)
49
49
50 Return the package that an import is being performed in. If globals comes
50 Return the package that an import is being performed in. If globals comes
51 from the module foo.bar.bat (not itself a package), this returns the
51 from the module foo.bar.bat (not itself a package), this returns the
52 sys.modules entry for foo.bar. If globals is from a package's __init__.py,
52 sys.modules entry for foo.bar. If globals is from a package's __init__.py,
53 the package's entry in sys.modules is returned.
53 the package's entry in sys.modules is returned.
54
54
55 If globals doesn't come from a package or a module in a package, or a
55 If globals doesn't come from a package or a module in a package, or a
56 corresponding entry is not found in sys.modules, None is returned.
56 corresponding entry is not found in sys.modules, None is returned.
57 """
57 """
58 orig_level = level
58 orig_level = level
59
59
60 if not level or not isinstance(globals, dict):
60 if not level or not isinstance(globals, dict):
61 return None, ''
61 return None, ''
62
62
63 pkgname = globals.get('__package__', None)
63 pkgname = globals.get('__package__', None)
64
64
65 if pkgname is not None:
65 if pkgname is not None:
66 # __package__ is set, so use it
66 # __package__ is set, so use it
67 if not hasattr(pkgname, 'rindex'):
67 if not hasattr(pkgname, 'rindex'):
68 raise ValueError('__package__ set to non-string')
68 raise ValueError('__package__ set to non-string')
69 if len(pkgname) == 0:
69 if len(pkgname) == 0:
70 if level > 0:
70 if level > 0:
71 raise ValueError('Attempted relative import in non-package')
71 raise ValueError('Attempted relative import in non-package')
72 return None, ''
72 return None, ''
73 name = pkgname
73 name = pkgname
74 else:
74 else:
75 # __package__ not set, so figure it out and set it
75 # __package__ not set, so figure it out and set it
76 if '__name__' not in globals:
76 if '__name__' not in globals:
77 return None, ''
77 return None, ''
78 modname = globals['__name__']
78 modname = globals['__name__']
79
79
80 if '__path__' in globals:
80 if '__path__' in globals:
81 # __path__ is set, so modname is already the package name
81 # __path__ is set, so modname is already the package name
82 globals['__package__'] = name = modname
82 globals['__package__'] = name = modname
83 else:
83 else:
84 # Normal module, so work out the package name if any
84 # Normal module, so work out the package name if any
85 lastdot = modname.rfind('.')
85 lastdot = modname.rfind('.')
86 if lastdot < 0 and level > 0:
86 if lastdot < 0 and level > 0:
87 raise ValueError("Attempted relative import in non-package")
87 raise ValueError("Attempted relative import in non-package")
88 if lastdot < 0:
88 if lastdot < 0:
89 globals['__package__'] = None
89 globals['__package__'] = None
90 return None, ''
90 return None, ''
91 globals['__package__'] = name = modname[:lastdot]
91 globals['__package__'] = name = modname[:lastdot]
92
92
93 dot = len(name)
93 dot = len(name)
94 for x in xrange(level, 1, -1):
94 for x in xrange(level, 1, -1):
95 try:
95 try:
96 dot = name.rindex('.', 0, dot)
96 dot = name.rindex('.', 0, dot)
97 except ValueError:
97 except ValueError:
98 raise ValueError("attempted relative import beyond top-level "
98 raise ValueError("attempted relative import beyond top-level "
99 "package")
99 "package")
100 name = name[:dot]
100 name = name[:dot]
101
101
102 try:
102 try:
103 parent = sys.modules[name]
103 parent = sys.modules[name]
104 except:
104 except:
105 if orig_level < 1:
105 if orig_level < 1:
106 warn("Parent module '%.200s' not found while handling absolute "
106 warn("Parent module '%.200s' not found while handling absolute "
107 "import" % name)
107 "import" % name)
108 parent = None
108 parent = None
109 else:
109 else:
110 raise SystemError("Parent module '%.200s' not loaded, cannot "
110 raise SystemError("Parent module '%.200s' not loaded, cannot "
111 "perform relative import" % name)
111 "perform relative import" % name)
112
112
113 # We expect, but can't guarantee, if parent != None, that:
113 # We expect, but can't guarantee, if parent != None, that:
114 # - parent.__name__ == name
114 # - parent.__name__ == name
115 # - parent.__dict__ is globals
115 # - parent.__dict__ is globals
116 # If this is violated... Who cares?
116 # If this is violated... Who cares?
117 return parent, name
117 return parent, name
118
118
119 def load_next(mod, altmod, name, buf):
119 def load_next(mod, altmod, name, buf):
120 """
120 """
121 mod, name, buf = load_next(mod, altmod, name, buf)
121 mod, name, buf = load_next(mod, altmod, name, buf)
122
122
123 altmod is either None or same as mod
123 altmod is either None or same as mod
124 """
124 """
125
125
126 if len(name) == 0:
126 if len(name) == 0:
127 # completely empty module name should only happen in
127 # completely empty module name should only happen in
128 # 'from . import' (or '__import__("")')
128 # 'from . import' (or '__import__("")')
129 return mod, None, buf
129 return mod, None, buf
130
130
131 dot = name.find('.')
131 dot = name.find('.')
132 if dot == 0:
132 if dot == 0:
133 raise ValueError('Empty module name')
133 raise ValueError('Empty module name')
134
134
135 if dot < 0:
135 if dot < 0:
136 subname = name
136 subname = name
137 next = None
137 next = None
138 else:
138 else:
139 subname = name[:dot]
139 subname = name[:dot]
140 next = name[dot+1:]
140 next = name[dot+1:]
141
141
142 if buf != '':
142 if buf != '':
143 buf += '.'
143 buf += '.'
144 buf += subname
144 buf += subname
145
145
146 result = import_submodule(mod, subname, buf)
146 result = import_submodule(mod, subname, buf)
147 if result is None and mod != altmod:
147 if result is None and mod != altmod:
148 result = import_submodule(altmod, subname, subname)
148 result = import_submodule(altmod, subname, subname)
149 if result is not None:
149 if result is not None:
150 buf = subname
150 buf = subname
151
151
152 if result is None:
152 if result is None:
153 raise ImportError("No module named %.200s" % name)
153 raise ImportError("No module named %.200s" % name)
154
154
155 return result, next, buf
155 return result, next, buf
156
156
157 # Need to keep track of what we've already reloaded to prevent cyclic evil
157 # Need to keep track of what we've already reloaded to prevent cyclic evil
158 found_now = {}
158 found_now = {}
159
159
160 def import_submodule(mod, subname, fullname):
160 def import_submodule(mod, subname, fullname):
161 """m = import_submodule(mod, subname, fullname)"""
161 """m = import_submodule(mod, subname, fullname)"""
162 # Require:
162 # Require:
163 # if mod == None: subname == fullname
163 # if mod == None: subname == fullname
164 # else: mod.__name__ + "." + subname == fullname
164 # else: mod.__name__ + "." + subname == fullname
165
165
166 global found_now
166 global found_now
167 if fullname in found_now and fullname in sys.modules:
167 if fullname in found_now and fullname in sys.modules:
168 m = sys.modules[fullname]
168 m = sys.modules[fullname]
169 else:
169 else:
170 print 'Reloading', fullname
170 print 'Reloading', fullname
171 found_now[fullname] = 1
171 found_now[fullname] = 1
172 oldm = sys.modules.get(fullname, None)
172 oldm = sys.modules.get(fullname, None)
173
173
174 if mod is None:
174 if mod is None:
175 path = None
175 path = None
176 elif hasattr(mod, '__path__'):
176 elif hasattr(mod, '__path__'):
177 path = mod.__path__
177 path = mod.__path__
178 else:
178 else:
179 return None
179 return None
180
180
181 try:
181 try:
182 # This appears to be necessary on Python 3, because imp.find_module()
182 # This appears to be necessary on Python 3, because imp.find_module()
183 # tries to import standard libraries (like io) itself, and we don't
183 # tries to import standard libraries (like io) itself, and we don't
184 # want them to be processed by our deep_import_hook.
184 # want them to be processed by our deep_import_hook.
185 with replace_import_hook(original_import):
185 with replace_import_hook(original_import):
186 fp, filename, stuff = imp.find_module(subname, path)
186 fp, filename, stuff = imp.find_module(subname, path)
187 except ImportError:
187 except ImportError:
188 return None
188 return None
189
189
190 try:
190 try:
191 m = imp.load_module(fullname, fp, filename, stuff)
191 m = imp.load_module(fullname, fp, filename, stuff)
192 except:
192 except:
193 # load_module probably removed name from modules because of
193 # load_module probably removed name from modules because of
194 # the error. Put back the original module object.
194 # the error. Put back the original module object.
195 if oldm:
195 if oldm:
196 sys.modules[fullname] = oldm
196 sys.modules[fullname] = oldm
197 raise
197 raise
198 finally:
198 finally:
199 if fp: fp.close()
199 if fp: fp.close()
200
200
201 add_submodule(mod, m, fullname, subname)
201 add_submodule(mod, m, fullname, subname)
202
202
203 return m
203 return m
204
204
205 def add_submodule(mod, submod, fullname, subname):
205 def add_submodule(mod, submod, fullname, subname):
206 """mod.{subname} = submod"""
206 """mod.{subname} = submod"""
207 if mod is None:
207 if mod is None:
208 return #Nothing to do here.
208 return #Nothing to do here.
209
209
210 if submod is None:
210 if submod is None:
211 submod = sys.modules[fullname]
211 submod = sys.modules[fullname]
212
212
213 setattr(mod, subname, submod)
213 setattr(mod, subname, submod)
214
214
215 return
215 return
216
216
217 def ensure_fromlist(mod, fromlist, buf, recursive):
217 def ensure_fromlist(mod, fromlist, buf, recursive):
218 """Handle 'from module import a, b, c' imports."""
218 """Handle 'from module import a, b, c' imports."""
219 if not hasattr(mod, '__path__'):
219 if not hasattr(mod, '__path__'):
220 return
220 return
221 for item in fromlist:
221 for item in fromlist:
222 if not hasattr(item, 'rindex'):
222 if not hasattr(item, 'rindex'):
223 raise TypeError("Item in ``from list'' not a string")
223 raise TypeError("Item in ``from list'' not a string")
224 if item == '*':
224 if item == '*':
225 if recursive:
225 if recursive:
226 continue # avoid endless recursion
226 continue # avoid endless recursion
227 try:
227 try:
228 all = mod.__all__
228 all = mod.__all__
229 except AttributeError:
229 except AttributeError:
230 pass
230 pass
231 else:
231 else:
232 ret = ensure_fromlist(mod, all, buf, 1)
232 ret = ensure_fromlist(mod, all, buf, 1)
233 if not ret:
233 if not ret:
234 return 0
234 return 0
235 elif not hasattr(mod, item):
235 elif not hasattr(mod, item):
236 import_submodule(mod, item, buf + '.' + item)
236 import_submodule(mod, item, buf + '.' + item)
237
237
238 def deep_import_hook(name, globals=None, locals=None, fromlist=None, level=-1):
238 def deep_import_hook(name, globals=None, locals=None, fromlist=None, level=-1):
239 """Replacement for __import__()"""
239 """Replacement for __import__()"""
240 parent, buf = get_parent(globals, level)
240 parent, buf = get_parent(globals, level)
241
241
242 head, name, buf = load_next(parent, None if level < 0 else parent, name, buf)
242 head, name, buf = load_next(parent, None if level < 0 else parent, name, buf)
243
243
244 tail = head
244 tail = head
245 while name:
245 while name:
246 tail, name, buf = load_next(tail, tail, name, buf)
246 tail, name, buf = load_next(tail, tail, name, buf)
247
247
248 # If tail is None, both get_parent and load_next found
248 # If tail is None, both get_parent and load_next found
249 # an empty module name: someone called __import__("") or
249 # an empty module name: someone called __import__("") or
250 # doctored faulty bytecode
250 # doctored faulty bytecode
251 if tail is None:
251 if tail is None:
252 raise ValueError('Empty module name')
252 raise ValueError('Empty module name')
253
253
254 if not fromlist:
254 if not fromlist:
255 return head
255 return head
256
256
257 ensure_fromlist(tail, fromlist, buf, 0)
257 ensure_fromlist(tail, fromlist, buf, 0)
258 return tail
258 return tail
259
259
260 modules_reloading = {}
260 modules_reloading = {}
261
261
262 def deep_reload_hook(m):
262 def deep_reload_hook(m):
263 """Replacement for reload()."""
263 """Replacement for reload()."""
264 if not isinstance(m, ModuleType):
264 if not isinstance(m, ModuleType):
265 raise TypeError("reload() argument must be module")
265 raise TypeError("reload() argument must be module")
266
266
267 name = m.__name__
267 name = m.__name__
268
268
269 if name not in sys.modules:
269 if name not in sys.modules:
270 raise ImportError("reload(): module %.200s not in sys.modules" % name)
270 raise ImportError("reload(): module %.200s not in sys.modules" % name)
271
271
272 global modules_reloading
272 global modules_reloading
273 try:
273 try:
274 return modules_reloading[name]
274 return modules_reloading[name]
275 except:
275 except:
276 modules_reloading[name] = m
276 modules_reloading[name] = m
277
277
278 dot = name.rfind('.')
278 dot = name.rfind('.')
279 if dot < 0:
279 if dot < 0:
280 subname = name
280 subname = name
281 path = None
281 path = None
282 else:
282 else:
283 try:
283 try:
284 parent = sys.modules[name[:dot]]
284 parent = sys.modules[name[:dot]]
285 except KeyError:
285 except KeyError:
286 modules_reloading.clear()
286 modules_reloading.clear()
287 raise ImportError("reload(): parent %.200s not in sys.modules" % name[:dot])
287 raise ImportError("reload(): parent %.200s not in sys.modules" % name[:dot])
288 subname = name[dot+1:]
288 subname = name[dot+1:]
289 path = getattr(parent, "__path__", None)
289 path = getattr(parent, "__path__", None)
290
290
291 try:
291 try:
292 # This appears to be necessary on Python 3, because imp.find_module()
292 # This appears to be necessary on Python 3, because imp.find_module()
293 # tries to import standard libraries (like io) itself, and we don't
293 # tries to import standard libraries (like io) itself, and we don't
294 # want them to be processed by our deep_import_hook.
294 # want them to be processed by our deep_import_hook.
295 with replace_import_hook(original_import):
295 with replace_import_hook(original_import):
296 fp, filename, stuff = imp.find_module(subname, path)
296 fp, filename, stuff = imp.find_module(subname, path)
297 finally:
297 finally:
298 modules_reloading.clear()
298 modules_reloading.clear()
299
299
300 try:
300 try:
301 newm = imp.load_module(name, fp, filename, stuff)
301 newm = imp.load_module(name, fp, filename, stuff)
302 except:
302 except:
303 # load_module probably removed name from modules because of
303 # load_module probably removed name from modules because of
304 # the error. Put back the original module object.
304 # the error. Put back the original module object.
305 sys.modules[name] = m
305 sys.modules[name] = m
306 raise
306 raise
307 finally:
307 finally:
308 if fp: fp.close()
308 if fp: fp.close()
309
309
310 modules_reloading.clear()
310 modules_reloading.clear()
311 return newm
311 return newm
312
312
313 # Save the original hooks
313 # Save the original hooks
314 try:
314 try:
315 original_reload = __builtin__.reload
315 original_reload = __builtin__.reload
316 except AttributeError:
316 except AttributeError:
317 original_reload = imp.reload # Python 3
317 original_reload = imp.reload # Python 3
318
318
319 # Replacement for reload()
319 # Replacement for reload()
320 def reload(module, exclude=['sys', 'os.path', '__builtin__', '__main__']):
320 def reload(module, exclude=['sys', 'os.path', '__builtin__', '__main__']):
321 """Recursively reload all modules used in the given module. Optionally
321 """Recursively reload all modules used in the given module. Optionally
322 takes a list of modules to exclude from reloading. The default exclude
322 takes a list of modules to exclude from reloading. The default exclude
323 list contains sys, __main__, and __builtin__, to prevent, e.g., resetting
323 list contains sys, __main__, and __builtin__, to prevent, e.g., resetting
324 display, exception, and io hooks.
324 display, exception, and io hooks.
325 """
325 """
326 global found_now
326 global found_now
327 for i in exclude:
327 for i in exclude:
328 found_now[i] = 1
328 found_now[i] = 1
329 try:
329 try:
330 with replace_import_hook(deep_import_hook):
330 with replace_import_hook(deep_import_hook):
331 ret = deep_reload_hook(module)
331 return deep_reload_hook(module)
332 finally:
332 finally:
333 found_now = {}
333 found_now = {}
334 return ret
335
334
336 # Uncomment the following to automatically activate deep reloading whenever
335 # Uncomment the following to automatically activate deep reloading whenever
337 # this module is imported
336 # this module is imported
338 #__builtin__.reload = reload
337 #__builtin__.reload = reload
@@ -1,252 +1,251 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tools for handling LaTeX.
2 """Tools for handling LaTeX.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2010 IPython Development Team.
9 # Copyright (C) 2010 IPython Development Team.
10 #
10 #
11 # Distributed under the terms of the Modified BSD License.
11 # Distributed under the terms of the Modified BSD License.
12 #
12 #
13 # The full license is in the file COPYING.txt, distributed with this software.
13 # The full license is in the file COPYING.txt, distributed with this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 from io import BytesIO
20 from io import BytesIO
21 from base64 import encodestring
21 from base64 import encodestring
22 import os
22 import os
23 import tempfile
23 import tempfile
24 import shutil
24 import shutil
25 import subprocess
25 import subprocess
26
26
27 from IPython.utils.process import find_cmd, FindCmdError
27 from IPython.utils.process import find_cmd, FindCmdError
28 from IPython.config.configurable import SingletonConfigurable
28 from IPython.config.configurable import SingletonConfigurable
29 from IPython.utils.traitlets import List, CBool, CUnicode
29 from IPython.utils.traitlets import List, CBool, CUnicode
30 from IPython.utils.py3compat import bytes_to_str
30 from IPython.utils.py3compat import bytes_to_str
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Tools
33 # Tools
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36
36
37 class LaTeXTool(SingletonConfigurable):
37 class LaTeXTool(SingletonConfigurable):
38 """An object to store configuration of the LaTeX tool."""
38 """An object to store configuration of the LaTeX tool."""
39
39
40 backends = List(
40 backends = List(
41 CUnicode, ["matplotlib", "dvipng"],
41 CUnicode, ["matplotlib", "dvipng"],
42 help="Preferred backend to draw LaTeX math equations. "
42 help="Preferred backend to draw LaTeX math equations. "
43 "Backends in the list are checked one by one and the first "
43 "Backends in the list are checked one by one and the first "
44 "usable one is used. Note that `matplotlib` backend "
44 "usable one is used. Note that `matplotlib` backend "
45 "is usable only for inline style equations. To draw "
45 "is usable only for inline style equations. To draw "
46 "display style equations, `dvipng` backend must be specified. ",
46 "display style equations, `dvipng` backend must be specified. ",
47 # It is a List instead of Enum, to make configuration more
47 # It is a List instead of Enum, to make configuration more
48 # flexible. For example, to use matplotlib mainly but dvipng
48 # flexible. For example, to use matplotlib mainly but dvipng
49 # for display style, the default ["matplotlib", "dvipng"] can
49 # for display style, the default ["matplotlib", "dvipng"] can
50 # be used. To NOT use dvipng so that other repr such as
50 # be used. To NOT use dvipng so that other repr such as
51 # unicode pretty printing is used, you can use ["matplotlib"].
51 # unicode pretty printing is used, you can use ["matplotlib"].
52 config=True)
52 config=True)
53
53
54 use_breqn = CBool(
54 use_breqn = CBool(
55 True,
55 True,
56 help="Use breqn.sty to automatically break long equations. "
56 help="Use breqn.sty to automatically break long equations. "
57 "This configuration takes effect only for dvipng backend.",
57 "This configuration takes effect only for dvipng backend.",
58 config=True)
58 config=True)
59
59
60 packages = List(
60 packages = List(
61 ['amsmath', 'amsthm', 'amssymb', 'bm'],
61 ['amsmath', 'amsthm', 'amssymb', 'bm'],
62 help="A list of packages to use for dvipng backend. "
62 help="A list of packages to use for dvipng backend. "
63 "'breqn' will be automatically appended when use_breqn=True.",
63 "'breqn' will be automatically appended when use_breqn=True.",
64 config=True)
64 config=True)
65
65
66 preamble = CUnicode(
66 preamble = CUnicode(
67 help="Additional preamble to use when generating LaTeX source "
67 help="Additional preamble to use when generating LaTeX source "
68 "for dvipng backend.",
68 "for dvipng backend.",
69 config=True)
69 config=True)
70
70
71
71
72 def latex_to_png(s, encode=False, backend=None, wrap=False):
72 def latex_to_png(s, encode=False, backend=None, wrap=False):
73 """Render a LaTeX string to PNG.
73 """Render a LaTeX string to PNG.
74
74
75 Parameters
75 Parameters
76 ----------
76 ----------
77 s : str
77 s : str
78 The raw string containing valid inline LaTeX.
78 The raw string containing valid inline LaTeX.
79 encode : bool, optional
79 encode : bool, optional
80 Should the PNG data bebase64 encoded to make it JSON'able.
80 Should the PNG data bebase64 encoded to make it JSON'able.
81 backend : {matplotlib, dvipng}
81 backend : {matplotlib, dvipng}
82 Backend for producing PNG data.
82 Backend for producing PNG data.
83 wrap : bool
83 wrap : bool
84 If true, Automatically wrap `s` as a LaTeX equation.
84 If true, Automatically wrap `s` as a LaTeX equation.
85
85
86 None is returned when the backend cannot be used.
86 None is returned when the backend cannot be used.
87
87
88 """
88 """
89 allowed_backends = LaTeXTool.instance().backends
89 allowed_backends = LaTeXTool.instance().backends
90 if backend is None:
90 if backend is None:
91 backend = allowed_backends[0]
91 backend = allowed_backends[0]
92 if backend not in allowed_backends:
92 if backend not in allowed_backends:
93 return None
93 return None
94 if backend == 'matplotlib':
94 if backend == 'matplotlib':
95 f = latex_to_png_mpl
95 f = latex_to_png_mpl
96 elif backend == 'dvipng':
96 elif backend == 'dvipng':
97 f = latex_to_png_dvipng
97 f = latex_to_png_dvipng
98 else:
98 else:
99 raise ValueError('No such backend {0}'.format(backend))
99 raise ValueError('No such backend {0}'.format(backend))
100 bin_data = f(s, wrap)
100 bin_data = f(s, wrap)
101 if encode and bin_data:
101 if encode and bin_data:
102 bin_data = encodestring(bin_data)
102 bin_data = encodestring(bin_data)
103 return bin_data
103 return bin_data
104
104
105
105
106 def latex_to_png_mpl(s, wrap):
106 def latex_to_png_mpl(s, wrap):
107 try:
107 try:
108 from matplotlib import mathtext
108 from matplotlib import mathtext
109 except ImportError:
109 except ImportError:
110 return None
110 return None
111
111
112 if wrap:
112 if wrap:
113 s = '${0}$'.format(s)
113 s = '${0}$'.format(s)
114 mt = mathtext.MathTextParser('bitmap')
114 mt = mathtext.MathTextParser('bitmap')
115 f = BytesIO()
115 f = BytesIO()
116 mt.to_png(f, s, fontsize=12)
116 mt.to_png(f, s, fontsize=12)
117 return f.getvalue()
117 return f.getvalue()
118
118
119
119
120 def latex_to_png_dvipng(s, wrap):
120 def latex_to_png_dvipng(s, wrap):
121 try:
121 try:
122 find_cmd('latex')
122 find_cmd('latex')
123 find_cmd('dvipng')
123 find_cmd('dvipng')
124 except FindCmdError:
124 except FindCmdError:
125 return None
125 return None
126 try:
126 try:
127 workdir = tempfile.mkdtemp()
127 workdir = tempfile.mkdtemp()
128 tmpfile = os.path.join(workdir, "tmp.tex")
128 tmpfile = os.path.join(workdir, "tmp.tex")
129 dvifile = os.path.join(workdir, "tmp.dvi")
129 dvifile = os.path.join(workdir, "tmp.dvi")
130 outfile = os.path.join(workdir, "tmp.png")
130 outfile = os.path.join(workdir, "tmp.png")
131
131
132 with open(tmpfile, "w") as f:
132 with open(tmpfile, "w") as f:
133 f.writelines(genelatex(s, wrap))
133 f.writelines(genelatex(s, wrap))
134
134
135 with open(os.devnull, 'w') as devnull:
135 with open(os.devnull, 'w') as devnull:
136 subprocess.check_call(
136 subprocess.check_call(
137 ["latex", "-halt-on-error", tmpfile], cwd=workdir,
137 ["latex", "-halt-on-error", tmpfile], cwd=workdir,
138 stdout=devnull, stderr=devnull)
138 stdout=devnull, stderr=devnull)
139
139
140 subprocess.check_call(
140 subprocess.check_call(
141 ["dvipng", "-T", "tight", "-x", "1500", "-z", "9",
141 ["dvipng", "-T", "tight", "-x", "1500", "-z", "9",
142 "-bg", "transparent", "-o", outfile, dvifile], cwd=workdir,
142 "-bg", "transparent", "-o", outfile, dvifile], cwd=workdir,
143 stdout=devnull, stderr=devnull)
143 stdout=devnull, stderr=devnull)
144
144
145 with open(outfile, "rb") as f:
145 with open(outfile, "rb") as f:
146 bin_data = f.read()
146 return f.read()
147 finally:
147 finally:
148 shutil.rmtree(workdir)
148 shutil.rmtree(workdir)
149 return bin_data
150
149
151
150
152 def kpsewhich(filename):
151 def kpsewhich(filename):
153 """Invoke kpsewhich command with an argument `filename`."""
152 """Invoke kpsewhich command with an argument `filename`."""
154 try:
153 try:
155 find_cmd("kpsewhich")
154 find_cmd("kpsewhich")
156 proc = subprocess.Popen(
155 proc = subprocess.Popen(
157 ["kpsewhich", filename],
156 ["kpsewhich", filename],
158 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
157 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
159 (stdout, stderr) = proc.communicate()
158 (stdout, stderr) = proc.communicate()
160 return stdout.strip()
159 return stdout.strip()
161 except FindCmdError:
160 except FindCmdError:
162 pass
161 pass
163
162
164
163
165 def genelatex(body, wrap):
164 def genelatex(body, wrap):
166 """Generate LaTeX document for dvipng backend."""
165 """Generate LaTeX document for dvipng backend."""
167 lt = LaTeXTool.instance()
166 lt = LaTeXTool.instance()
168 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
167 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
169 yield r'\documentclass{article}'
168 yield r'\documentclass{article}'
170 packages = lt.packages
169 packages = lt.packages
171 if breqn:
170 if breqn:
172 packages = packages + ['breqn']
171 packages = packages + ['breqn']
173 for pack in packages:
172 for pack in packages:
174 yield r'\usepackage{{{0}}}'.format(pack)
173 yield r'\usepackage{{{0}}}'.format(pack)
175 yield r'\pagestyle{empty}'
174 yield r'\pagestyle{empty}'
176 if lt.preamble:
175 if lt.preamble:
177 yield lt.preamble
176 yield lt.preamble
178 yield r'\begin{document}'
177 yield r'\begin{document}'
179 if breqn:
178 if breqn:
180 yield r'\begin{dmath*}'
179 yield r'\begin{dmath*}'
181 yield body
180 yield body
182 yield r'\end{dmath*}'
181 yield r'\end{dmath*}'
183 elif wrap:
182 elif wrap:
184 yield '$${0}$$'.format(body)
183 yield '$${0}$$'.format(body)
185 else:
184 else:
186 yield body
185 yield body
187 yield r'\end{document}'
186 yield r'\end{document}'
188
187
189
188
190 _data_uri_template_png = """<img src="data:image/png;base64,%s" alt=%s />"""
189 _data_uri_template_png = """<img src="data:image/png;base64,%s" alt=%s />"""
191
190
192 def latex_to_html(s, alt='image'):
191 def latex_to_html(s, alt='image'):
193 """Render LaTeX to HTML with embedded PNG data using data URIs.
192 """Render LaTeX to HTML with embedded PNG data using data URIs.
194
193
195 Parameters
194 Parameters
196 ----------
195 ----------
197 s : str
196 s : str
198 The raw string containing valid inline LateX.
197 The raw string containing valid inline LateX.
199 alt : str
198 alt : str
200 The alt text to use for the HTML.
199 The alt text to use for the HTML.
201 """
200 """
202 base64_data = bytes_to_str(latex_to_png(s, encode=True), 'ascii')
201 base64_data = bytes_to_str(latex_to_png(s, encode=True), 'ascii')
203 if base64_data:
202 if base64_data:
204 return _data_uri_template_png % (base64_data, alt)
203 return _data_uri_template_png % (base64_data, alt)
205
204
206
205
207 # From matplotlib, thanks to mdboom. Once this is in matplotlib releases, we
206 # From matplotlib, thanks to mdboom. Once this is in matplotlib releases, we
208 # will remove.
207 # will remove.
209 def math_to_image(s, filename_or_obj, prop=None, dpi=None, format=None):
208 def math_to_image(s, filename_or_obj, prop=None, dpi=None, format=None):
210 """
209 """
211 Given a math expression, renders it in a closely-clipped bounding
210 Given a math expression, renders it in a closely-clipped bounding
212 box to an image file.
211 box to an image file.
213
212
214 *s*
213 *s*
215 A math expression. The math portion should be enclosed in
214 A math expression. The math portion should be enclosed in
216 dollar signs.
215 dollar signs.
217
216
218 *filename_or_obj*
217 *filename_or_obj*
219 A filepath or writable file-like object to write the image data
218 A filepath or writable file-like object to write the image data
220 to.
219 to.
221
220
222 *prop*
221 *prop*
223 If provided, a FontProperties() object describing the size and
222 If provided, a FontProperties() object describing the size and
224 style of the text.
223 style of the text.
225
224
226 *dpi*
225 *dpi*
227 Override the output dpi, otherwise use the default associated
226 Override the output dpi, otherwise use the default associated
228 with the output format.
227 with the output format.
229
228
230 *format*
229 *format*
231 The output format, eg. 'svg', 'pdf', 'ps' or 'png'. If not
230 The output format, eg. 'svg', 'pdf', 'ps' or 'png'. If not
232 provided, will be deduced from the filename.
231 provided, will be deduced from the filename.
233 """
232 """
234 from matplotlib import figure
233 from matplotlib import figure
235 # backend_agg supports all of the core output formats
234 # backend_agg supports all of the core output formats
236 from matplotlib.backends import backend_agg
235 from matplotlib.backends import backend_agg
237 from matplotlib.font_manager import FontProperties
236 from matplotlib.font_manager import FontProperties
238 from matplotlib.mathtext import MathTextParser
237 from matplotlib.mathtext import MathTextParser
239
238
240 if prop is None:
239 if prop is None:
241 prop = FontProperties()
240 prop = FontProperties()
242
241
243 parser = MathTextParser('path')
242 parser = MathTextParser('path')
244 width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
243 width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
245
244
246 fig = figure.Figure(figsize=(width / 72.0, height / 72.0))
245 fig = figure.Figure(figsize=(width / 72.0, height / 72.0))
247 fig.text(0, depth/height, s, fontproperties=prop)
246 fig.text(0, depth/height, s, fontproperties=prop)
248 backend_agg.FigureCanvasAgg(fig)
247 backend_agg.FigureCanvasAgg(fig)
249 fig.savefig(filename_or_obj, dpi=dpi, format=format)
248 fig.savefig(filename_or_obj, dpi=dpi, format=format)
250
249
251 return depth
250 return depth
252
251
General Comments 0
You need to be logged in to leave comments. Login now