##// END OF EJS Templates
Merge pull request #2138 from tkf/breqn...
Min RK -
r7980:a2ca01af merge
parent child Browse files
Show More
@@ -0,0 +1,119 b''
1 # encoding: utf-8
2 """Tests for IPython.utils.path.py"""
3
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2008-2011 The IPython Development Team
6 #
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
10
11 import nose.tools as nt
12
13 from IPython.lib import latextools
14 from IPython.testing.decorators import onlyif_cmds_exist
15 from IPython.testing.tools import monkeypatch
16 from IPython.utils.process import FindCmdError
17
18
19 def test_latex_to_png_dvipng_fails_when_no_cmd():
20 """
21 `latex_to_png_dvipng` should return None when there is no required command
22 """
23 for command in ['latex', 'dvipng']:
24 yield (check_latex_to_png_dvipng_fails_when_no_cmd, command)
25
26
27 def check_latex_to_png_dvipng_fails_when_no_cmd(command):
28 def mock_find_cmd(arg):
29 if arg == command:
30 raise FindCmdError
31
32 with monkeypatch(latextools, "find_cmd", mock_find_cmd):
33 nt.assert_equals(latextools.latex_to_png_dvipng("whatever", True),
34 None)
35
36
37 @onlyif_cmds_exist('latex', 'dvipng')
38 def test_latex_to_png_dvipng_runs():
39 """
40 Test that latex_to_png_dvipng just runs without error.
41 """
42 def mock_kpsewhich(filename):
43 nt.assert_equals(filename, "breqn.sty")
44 return None
45
46 for (s, wrap) in [("$$x^2$$", False), ("x^2", True)]:
47 yield (latextools.latex_to_png_dvipng, s, wrap)
48
49 with monkeypatch(latextools, "kpsewhich", mock_kpsewhich):
50 yield (latextools.latex_to_png_dvipng, s, wrap)
51
52
53 def test_genelatex_no_wrap():
54 """
55 Test genelatex with wrap=False.
56 """
57 def mock_kpsewhich(filename):
58 assert False, ("kpsewhich should not be called "
59 "(called with {0})".format(filename))
60
61 with monkeypatch(latextools, "kpsewhich", mock_kpsewhich):
62 nt.assert_equals(
63 '\n'.join(latextools.genelatex("body text", False)),
64 r'''\documentclass{article}
65 \usepackage{amsmath}
66 \usepackage{amsthm}
67 \usepackage{amssymb}
68 \usepackage{bm}
69 \pagestyle{empty}
70 \begin{document}
71 body text
72 \end{document}''')
73
74
75 def test_genelatex_wrap_with_breqn():
76 """
77 Test genelatex with wrap=True for the case breqn.sty is installed.
78 """
79 def mock_kpsewhich(filename):
80 nt.assert_equals(filename, "breqn.sty")
81 return "path/to/breqn.sty"
82
83 with monkeypatch(latextools, "kpsewhich", mock_kpsewhich):
84 nt.assert_equals(
85 '\n'.join(latextools.genelatex("x^2", True)),
86 r'''\documentclass{article}
87 \usepackage{amsmath}
88 \usepackage{amsthm}
89 \usepackage{amssymb}
90 \usepackage{bm}
91 \usepackage{breqn}
92 \pagestyle{empty}
93 \begin{document}
94 \begin{dmath*}
95 x^2
96 \end{dmath*}
97 \end{document}''')
98
99
100 def test_genelatex_wrap_without_breqn():
101 """
102 Test genelatex with wrap=True for the case breqn.sty is not installed.
103 """
104 def mock_kpsewhich(filename):
105 nt.assert_equals(filename, "breqn.sty")
106 return None
107
108 with monkeypatch(latextools, "kpsewhich", mock_kpsewhich):
109 nt.assert_equals(
110 '\n'.join(latextools.genelatex("x^2", True)),
111 r'''\documentclass{article}
112 \usepackage{amsmath}
113 \usepackage{amsthm}
114 \usepackage{amssymb}
115 \usepackage{bm}
116 \pagestyle{empty}
117 \begin{document}
118 $$x^2$$
119 \end{document}''')
@@ -66,6 +66,7 b' from IPython.core.prefilter import PrefilterManager'
66 from IPython.core.profiledir import ProfileDir
66 from IPython.core.profiledir import ProfileDir
67 from IPython.core.pylabtools import pylab_activate
67 from IPython.core.pylabtools import pylab_activate
68 from IPython.core.prompts import PromptManager
68 from IPython.core.prompts import PromptManager
69 from IPython.lib.latextools import LaTeXTool
69 from IPython.utils import PyColorize
70 from IPython.utils import PyColorize
70 from IPython.utils import io
71 from IPython.utils import io
71 from IPython.utils import py3compat
72 from IPython.utils import py3compat
@@ -497,6 +498,7 b' class InteractiveShell(SingletonConfigurable):'
497 self.init_display_pub()
498 self.init_display_pub()
498 self.init_displayhook()
499 self.init_displayhook()
499 self.init_reload_doctest()
500 self.init_reload_doctest()
501 self.init_latextool()
500 self.init_magics()
502 self.init_magics()
501 self.init_logstart()
503 self.init_logstart()
502 self.init_pdb()
504 self.init_pdb()
@@ -689,7 +691,13 b' class InteractiveShell(SingletonConfigurable):'
689 doctest_reload()
691 doctest_reload()
690 except ImportError:
692 except ImportError:
691 warn("doctest module does not exist.")
693 warn("doctest module does not exist.")
692
694
695 def init_latextool(self):
696 """Configure LaTeXTool."""
697 cfg = LaTeXTool.instance(config=self.config)
698 if cfg not in self.configurables:
699 self.configurables.append(cfg)
700
693 def init_virtualenv(self):
701 def init_virtualenv(self):
694 """Add a virtualenv to sys.path so the user can import modules from it.
702 """Add a virtualenv to sys.path so the user can import modules from it.
695 This isn't perfect: it doesn't use the Python interpreter with which the
703 This isn't perfect: it doesn't use the Python interpreter with which the
@@ -65,7 +65,7 b' def print_display_png(o):'
65 s = s.strip('$')
65 s = s.strip('$')
66 # As matplotlib does not support display style, dvipng backend is
66 # As matplotlib does not support display style, dvipng backend is
67 # used here.
67 # used here.
68 png = latex_to_png('$$%s$$' % s, backend='dvipng')
68 png = latex_to_png(s, backend='dvipng', wrap=True)
69 return png
69 return png
70
70
71
71
@@ -25,13 +25,50 b' 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
29 from IPython.utils.traitlets import Instance, List, CBool, CUnicode
28
30
29 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
30 # Tools
32 # Tools
31 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
32
34
33
35
34 def latex_to_png(s, encode=False, backend='mpl'):
36 class LaTeXTool(SingletonConfigurable):
37 """An object to store configuration of the LaTeX tool."""
38
39 backends = List(
40 CUnicode, ["matplotlib", "dvipng"],
41 help="Preferred backend to draw LaTeX math equations. "
42 "Backends in the list are checked one by one and the first "
43 "usable one is used. Note that `matplotlib` backend "
44 "is usable only for inline style equations. To draw "
45 "display style equations, `dvipng` backend must be specified. ",
46 # It is a List instead of Enum, to make configuration more
47 # flexible. For example, to use matplotlib mainly but dvipng
48 # for display style, the default ["matplotlib", "dvipng"] can
49 # be used. To NOT use dvipng so that other repr such as
50 # unicode pretty printing is used, you can use ["matplotlib"].
51 config=True)
52
53 use_breqn = CBool(
54 True,
55 help="Use breqn.sty to automatically break long equations. "
56 "This configuration takes effect only for dvipng backend.",
57 config=True)
58
59 packages = List(
60 ['amsmath', 'amsthm', 'amssymb', 'bm'],
61 help="A list of packages to use for dvipng backend. "
62 "'breqn' will be automatically appended when use_breqn=True.",
63 config=True)
64
65 preamble = CUnicode(
66 help="Additional preamble to use when generating LaTeX source "
67 "for dvipng backend.",
68 config=True)
69
70
71 def latex_to_png(s, encode=False, backend=None, wrap=False):
35 """Render a LaTeX string to PNG.
72 """Render a LaTeX string to PNG.
36
73
37 Parameters
74 Parameters
@@ -40,37 +77,46 b" def latex_to_png(s, encode=False, backend='mpl'):"
40 The raw string containing valid inline LaTeX.
77 The raw string containing valid inline LaTeX.
41 encode : bool, optional
78 encode : bool, optional
42 Should the PNG data bebase64 encoded to make it JSON'able.
79 Should the PNG data bebase64 encoded to make it JSON'able.
43 backend : {mpl, dvipng}
80 backend : {matplotlib, dvipng}
44 Backend for producing PNG data.
81 Backend for producing PNG data.
82 wrap : bool
83 If true, Automatically wrap `s` as a LaTeX equation.
45
84
46 None is returned when the backend cannot be used.
85 None is returned when the backend cannot be used.
47
86
48 """
87 """
49 if backend == 'mpl':
88 allowed_backends = LaTeXTool.instance().backends
89 if backend is None:
90 backend = allowed_backends[0]
91 if backend not in allowed_backends:
92 return None
93 if backend == 'matplotlib':
50 f = latex_to_png_mpl
94 f = latex_to_png_mpl
51 elif backend == 'dvipng':
95 elif backend == 'dvipng':
52 f = latex_to_png_dvipng
96 f = latex_to_png_dvipng
53 else:
97 else:
54 raise ValueError('No such backend {0}'.format(backend))
98 raise ValueError('No such backend {0}'.format(backend))
55 bin_data = f(s)
99 bin_data = f(s, wrap)
56 if encode and bin_data:
100 if encode and bin_data:
57 bin_data = encodestring(bin_data)
101 bin_data = encodestring(bin_data)
58 return bin_data
102 return bin_data
59
103
60
104
61 def latex_to_png_mpl(s):
105 def latex_to_png_mpl(s, wrap):
62 try:
106 try:
63 from matplotlib import mathtext
107 from matplotlib import mathtext
64 except ImportError:
108 except ImportError:
65 return None
109 return None
66
110
111 if wrap:
112 s = '${0}$'.format(s)
67 mt = mathtext.MathTextParser('bitmap')
113 mt = mathtext.MathTextParser('bitmap')
68 f = StringIO()
114 f = StringIO()
69 mt.to_png(f, s, fontsize=12)
115 mt.to_png(f, s, fontsize=12)
70 return f.getvalue()
116 return f.getvalue()
71
117
72
118
73 def latex_to_png_dvipng(s):
119 def latex_to_png_dvipng(s, wrap):
74 try:
120 try:
75 find_cmd('latex')
121 find_cmd('latex')
76 find_cmd('dvipng')
122 find_cmd('dvipng')
@@ -83,9 +129,7 b' def latex_to_png_dvipng(s):'
83 outfile = os.path.join(workdir, "tmp.png")
129 outfile = os.path.join(workdir, "tmp.png")
84
130
85 with open(tmpfile, "w") as f:
131 with open(tmpfile, "w") as f:
86 f.write(_latex_header)
132 f.writelines(genelatex(s, wrap))
87 f.write(s)
88 f.write(_latex_footer)
89
133
90 subprocess.check_call(
134 subprocess.check_call(
91 ["latex", "-halt-on-errror", tmpfile], cwd=workdir,
135 ["latex", "-halt-on-errror", tmpfile], cwd=workdir,
@@ -103,17 +147,42 b' def latex_to_png_dvipng(s):'
103 return bin_data
147 return bin_data
104
148
105
149
106 _latex_header = r'''
150 def kpsewhich(filename):
107 \documentclass{article}
151 """Invoke kpsewhich command with an argument `filename`."""
108 \usepackage{amsmath}
152 try:
109 \usepackage{amsthm}
153 find_cmd("kpsewhich")
110 \usepackage{amssymb}
154 proc = subprocess.Popen(
111 \usepackage{bm}
155 ["kpsewhich", filename],
112 \pagestyle{empty}
156 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
113 \begin{document}
157 (stdout, stderr) = proc.communicate()
114 '''
158 return stdout.strip()
115
159 except FindCmdError:
116 _latex_footer = r'\end{document}'
160 pass
161
162
163 def genelatex(body, wrap):
164 """Generate LaTeX document for dvipng backend."""
165 lt = LaTeXTool.instance()
166 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
167 yield r'\documentclass{article}'
168 packages = lt.packages
169 if breqn:
170 packages = packages + ['breqn']
171 for pack in packages:
172 yield r'\usepackage{{{0}}}'.format(pack)
173 yield r'\pagestyle{empty}'
174 if lt.preamble:
175 yield lt.preamble
176 yield r'\begin{document}'
177 if breqn:
178 yield r'\begin{dmath*}'
179 yield body
180 yield r'\end{dmath*}'
181 elif wrap:
182 yield '$${0}$$'.format(body)
183 else:
184 yield body
185 yield r'\end{document}'
117
186
118
187
119 _data_uri_template_png = """<img src="data:image/png;base64,%s" alt=%s />"""
188 _data_uri_template_png = """<img src="data:image/png;base64,%s" alt=%s />"""
@@ -72,6 +72,9 b' from ipunittest import ipdoctest, ipdocstring'
72 # numpy.testing.decorators, we expose all of it here.
72 # numpy.testing.decorators, we expose all of it here.
73 from IPython.external.decorators import *
73 from IPython.external.decorators import *
74
74
75 # For onlyif_cmd_exists decorator
76 from IPython.utils.process import is_cmd_found
77
75 #-----------------------------------------------------------------------------
78 #-----------------------------------------------------------------------------
76 # Classes and functions
79 # Classes and functions
77 #-----------------------------------------------------------------------------
80 #-----------------------------------------------------------------------------
@@ -342,3 +345,14 b' else:'
342
345
343 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
346 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
344 "where we can use unicode in filenames."))
347 "where we can use unicode in filenames."))
348
349
350 def onlyif_cmds_exist(*commands):
351 """
352 Decorator to skip test when at least one of `commands` is not found.
353 """
354 for cmd in commands:
355 if not is_cmd_found(cmd):
356 return skip("This test runs only if command '{0}' "
357 "is installed".format(cmd))
358 return null_deco
@@ -369,3 +369,14 b' def make_tempfile(name):'
369 yield
369 yield
370 finally:
370 finally:
371 os.unlink(name)
371 os.unlink(name)
372
373
374 @contextmanager
375 def monkeypatch(obj, name, attr):
376 """
377 Context manager to replace attribute named `name` in `obj` with `attr`.
378 """
379 orig = getattr(obj, name)
380 setattr(obj, name, attr)
381 yield
382 setattr(obj, name, orig)
@@ -72,6 +72,15 b' def find_cmd(cmd):'
72 return os.path.abspath(path)
72 return os.path.abspath(path)
73
73
74
74
75 def is_cmd_found(cmd):
76 """Check whether executable `cmd` exists or not and return a bool."""
77 try:
78 find_cmd(cmd)
79 return True
80 except FindCmdError:
81 return False
82
83
75 def pycmd2argv(cmd):
84 def pycmd2argv(cmd):
76 r"""Take the path of a python command and return a list (argv-style).
85 r"""Take the path of a python command and return a list (argv-style).
77
86
General Comments 0
You need to be logged in to leave comments. Login now