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