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( |
|
|
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( |
|
|
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