##// 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 66 from IPython.core.profiledir import ProfileDir
67 67 from IPython.core.pylabtools import pylab_activate
68 68 from IPython.core.prompts import PromptManager
69 from IPython.lib.latextools import LaTeXTool
69 70 from IPython.utils import PyColorize
70 71 from IPython.utils import io
71 72 from IPython.utils import py3compat
@@ -497,6 +498,7 b' class InteractiveShell(SingletonConfigurable):'
497 498 self.init_display_pub()
498 499 self.init_displayhook()
499 500 self.init_reload_doctest()
501 self.init_latextool()
500 502 self.init_magics()
501 503 self.init_logstart()
502 504 self.init_pdb()
@@ -689,7 +691,13 b' class InteractiveShell(SingletonConfigurable):'
689 691 doctest_reload()
690 692 except ImportError:
691 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 701 def init_virtualenv(self):
694 702 """Add a virtualenv to sys.path so the user can import modules from it.
695 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 65 s = s.strip('$')
66 66 # As matplotlib does not support display style, dvipng backend is
67 67 # used here.
68 png = latex_to_png('$$%s$$' % s, backend='dvipng')
68 png = latex_to_png(s, backend='dvipng', wrap=True)
69 69 return png
70 70
71 71
@@ -25,13 +25,50 b' import shutil'
25 25 import subprocess
26 26
27 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 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 72 """Render a LaTeX string to PNG.
36 73
37 74 Parameters
@@ -40,37 +77,46 b" def latex_to_png(s, encode=False, backend='mpl'):"
40 77 The raw string containing valid inline LaTeX.
41 78 encode : bool, optional
42 79 Should the PNG data bebase64 encoded to make it JSON'able.
43 backend : {mpl, dvipng}
80 backend : {matplotlib, dvipng}
44 81 Backend for producing PNG data.
82 wrap : bool
83 If true, Automatically wrap `s` as a LaTeX equation.
45 84
46 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 94 f = latex_to_png_mpl
51 95 elif backend == 'dvipng':
52 96 f = latex_to_png_dvipng
53 97 else:
54 98 raise ValueError('No such backend {0}'.format(backend))
55 bin_data = f(s)
99 bin_data = f(s, wrap)
56 100 if encode and bin_data:
57 101 bin_data = encodestring(bin_data)
58 102 return bin_data
59 103
60 104
61 def latex_to_png_mpl(s):
105 def latex_to_png_mpl(s, wrap):
62 106 try:
63 107 from matplotlib import mathtext
64 108 except ImportError:
65 109 return None
66
110
111 if wrap:
112 s = '${0}$'.format(s)
67 113 mt = mathtext.MathTextParser('bitmap')
68 114 f = StringIO()
69 115 mt.to_png(f, s, fontsize=12)
70 116 return f.getvalue()
71 117
72 118
73 def latex_to_png_dvipng(s):
119 def latex_to_png_dvipng(s, wrap):
74 120 try:
75 121 find_cmd('latex')
76 122 find_cmd('dvipng')
@@ -83,9 +129,7 b' def latex_to_png_dvipng(s):'
83 129 outfile = os.path.join(workdir, "tmp.png")
84 130
85 131 with open(tmpfile, "w") as f:
86 f.write(_latex_header)
87 f.write(s)
88 f.write(_latex_footer)
132 f.writelines(genelatex(s, wrap))
89 133
90 134 subprocess.check_call(
91 135 ["latex", "-halt-on-errror", tmpfile], cwd=workdir,
@@ -103,17 +147,42 b' def latex_to_png_dvipng(s):'
103 147 return bin_data
104 148
105 149
106 _latex_header = r'''
107 \documentclass{article}
108 \usepackage{amsmath}
109 \usepackage{amsthm}
110 \usepackage{amssymb}
111 \usepackage{bm}
112 \pagestyle{empty}
113 \begin{document}
114 '''
115
116 _latex_footer = r'\end{document}'
150 def kpsewhich(filename):
151 """Invoke kpsewhich command with an argument `filename`."""
152 try:
153 find_cmd("kpsewhich")
154 proc = subprocess.Popen(
155 ["kpsewhich", filename],
156 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
157 (stdout, stderr) = proc.communicate()
158 return stdout.strip()
159 except FindCmdError:
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 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 72 # numpy.testing.decorators, we expose all of it here.
73 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 79 # Classes and functions
77 80 #-----------------------------------------------------------------------------
@@ -342,3 +345,14 b' else:'
342 345
343 346 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
344 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 369 yield
370 370 finally:
371 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 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 84 def pycmd2argv(cmd):
76 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