Skip to content

Commit bdded05

Browse files
committed
Add tests, refactor some things
1 parent b57f2ff commit bdded05

File tree

3 files changed

+194
-61
lines changed

3 files changed

+194
-61
lines changed

manim/_config/utils.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from ..constants import RendererType
3232
from ..typing import StrPath, Vector3
3333
from ..utils.color import ManimColor
34-
from ..utils.tex import TexTemplate, TexTemplateFromFile
34+
from ..utils.tex import TexTemplate
3535

3636

3737
def config_file_paths() -> list[Path]:
@@ -827,7 +827,7 @@ def digest_args(self, args: argparse.Namespace) -> Self:
827827

828828
# Handle --tex_template
829829
if args.tex_template:
830-
self.tex_template = TexTemplateFromFile(tex_filename=args.tex_template)
830+
self.tex_template = TexTemplate.from_file(args.tex_template)
831831

832832
if (
833833
self.renderer == RendererType.OPENGL
@@ -1750,19 +1750,19 @@ def tex_template(self) -> TexTemplate:
17501750
if not hasattr(self, "_tex_template") or not self._tex_template:
17511751
fn = self._d["tex_template_file"]
17521752
if fn:
1753-
self._tex_template = TexTemplateFromFile(tex_filename=fn)
1753+
self._tex_template = TexTemplate.from_file(fn)
17541754
else:
17551755
self._tex_template = TexTemplate()
17561756
return self._tex_template
17571757

17581758
@tex_template.setter
1759-
def tex_template(self, val: TexTemplateFromFile | TexTemplate) -> None:
1760-
if isinstance(val, (TexTemplateFromFile, TexTemplate)):
1759+
def tex_template(self, val: TexTemplate) -> None:
1760+
if isinstance(val, TexTemplate):
17611761
self._tex_template = val
17621762

17631763
@property
17641764
def tex_template_file(self) -> Path:
1765-
"""File to read Tex template from (no flag). See :class:`.TexTemplateFromFile`."""
1765+
"""File to read Tex template from (no flag). See :class:`.TexTemplate`."""
17661766
return self._d["tex_template_file"]
17671767

17681768
@tex_template_file.setter

manim/utils/tex.py

Lines changed: 70 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import copy
1010
import re
11+
import warnings
1112
from dataclasses import dataclass, field
1213
from pathlib import Path
1314
from typing import Any
@@ -16,11 +17,9 @@
1617

1718
from manim.typing import StrPath
1819

19-
_DEFAULT_PREAMBLE = r"""
20-
\usepackage[english]{babel}
20+
_DEFAULT_PREAMBLE = r"""\usepackage[english]{babel}
2121
\usepackage{amsmath}
22-
\usepackage{amssymb}
23-
"""
22+
\usepackage{amssymb}"""
2423

2524
_BEGIN_DOCUMENT = r"\begin{document}"
2625
_END_DOCUMENT = r"\end{document}"
@@ -30,7 +29,7 @@
3029
class TexTemplate:
3130
"""TeX templates are used to create ``Tex`` and ``MathTex`` objects."""
3231

33-
_body: str = field(init=False)
32+
_body: str = field(default="", init=False)
3433
"""A custom body, can be set from a file."""
3534

3635
tex_compiler: str = "latex"
@@ -40,29 +39,32 @@ class TexTemplate:
4039
"""The output format resulting from compilation, e.g. ``.dvi`` or ``.pdf``."""
4140

4241
documentclass: str = r"\documentclass[preview]{standalone}"
43-
r"""The command defining the documentclass, e.g. ``\\documentclass[preview]{standalone}``."""
42+
r"""The command defining the documentclass, e.g. ``\documentclass[preview]{standalone}``."""
4443

4544
preamble: str = _DEFAULT_PREAMBLE
46-
r"""The document's preamble, i.e. the part between ``\\documentclass`` and ``\\begin{document}``."""
45+
r"""The document's preamble, i.e. the part between ``\documentclass`` and ``\begin{document}``."""
4746

4847
placeholder_text: str = "YourTextHere"
4948
"""Text in the document that will be replaced by the expression to be rendered."""
5049

5150
post_doc_commands: str = ""
52-
r"""Text (definitions, commands) to be inserted at right after ``\\begin{document}``, e.g. ``\\boldmath``."""
51+
r"""Text (definitions, commands) to be inserted at right after ``\begin{document}``, e.g. ``\boldmath``."""
5352

5453
@property
5554
def body(self) -> str:
5655
"""The entire TeX template."""
5756
return self._body or "\n".join(
58-
[
59-
self.documentclass,
60-
self.preamble,
61-
_BEGIN_DOCUMENT,
62-
self.post_doc_commands,
63-
self.placeholder_text,
64-
_END_DOCUMENT,
65-
]
57+
filter(
58+
None,
59+
[
60+
self.documentclass,
61+
self.preamble,
62+
_BEGIN_DOCUMENT,
63+
self.post_doc_commands,
64+
self.placeholder_text,
65+
_END_DOCUMENT,
66+
],
67+
)
6668
)
6769

6870
@body.setter
@@ -80,63 +82,44 @@ def from_file(cls, file: StrPath = "tex_template.tex", **kwargs: Any) -> Self:
8082
instance.body = Path(file).read_text(encoding="utf-8")
8183
return instance
8284

83-
def _texcode_for_environment(self, environment: str) -> tuple[str, str]:
84-
r"""Processes the tex_environment string to return the correct ``\\begin{environment}[extra]{extra}`` and
85-
``\\end{environment}`` strings.
86-
87-
Parameters
88-
----------
89-
environment
90-
The tex_environment as a string. Acceptable formats include:
91-
``{align*}``, ``align*``, ``{tabular}[t]{cccl}``, ``tabular}{cccl``, ``\\begin{tabular}[t]{cccl}``.
92-
93-
Returns
94-
-------
95-
Tuple[:class:`str`, :class:`str`]
96-
A pair of strings representing the opening and closing of the tex environment, e.g.
97-
``\\begin{tabular}{cccl}`` and ``\\end{tabular}``
98-
"""
99-
100-
environment.removeprefix(r"\begin").removeprefix("{")
101-
102-
# The \begin command takes everything and closes with a brace
103-
begin = r"\begin{" + environment
104-
# If it doesn't end on } or ], assume missing }
105-
if not begin.endswith(("}", "]")):
106-
begin += "}"
107-
108-
# While the \end command terminates at the first closing brace
109-
split_at_brace = re.split("}", environment, 1)
110-
end = r"\end{" + split_at_brace[0] + "}"
111-
112-
return begin, end
113-
11485
def add_to_preamble(self, txt: str, prepend: bool = False) -> Self:
11586
r"""Adds text to the TeX template's preamble (e.g. definitions, packages). Text can be inserted at the beginning or at the end of the preamble.
11687
11788
Parameters
11889
----------
11990
txt
120-
String containing the text to be added, e.g. ``\\usepackage{hyperref}``.
91+
String containing the text to be added, e.g. ``\usepackage{hyperref}``.
12192
prepend
122-
Whether the text should be added at the beginning of the preamble, i.e. right after ``\\documentclass``.
123-
Default is to add it at the end of the preamble, i.e. right before ``\\begin{document}``.
93+
Whether the text should be added at the beginning of the preamble, i.e. right after ``\documentclass``.
94+
Default is to add it at the end of the preamble, i.e. right before ``\begin{document}``.
12495
"""
96+
if self._body:
97+
warnings.warn(
98+
"This TeX template was created with a fixed body, trying to add text the preamble will have no effect.",
99+
UserWarning,
100+
stacklevel=2,
101+
)
125102
if prepend:
126103
self.preamble = txt + "\n" + self.preamble
127104
else:
128105
self.preamble += "\n" + txt
129106
return self
130107

131108
def add_to_document(self, txt: str) -> Self:
132-
r"""Adds text to the TeX template just after \\begin{document}, e.g. ``\\boldmath``.
109+
r"""Adds text to the TeX template just after \begin{document}, e.g. ``\boldmath``.
133110
134111
Parameters
135112
----------
136113
txt
137114
String containing the text to be added.
138115
"""
139-
self.post_doc_commands += "\n" + txt + "\n"
116+
if self._body:
117+
warnings.warn(
118+
"This TeX template was created with a fixed body, trying to add text the document will have no effect.",
119+
UserWarning,
120+
stacklevel=2,
121+
)
122+
self.post_doc_commands += txt
140123
return self
141124

142125
def get_texcode_for_expression(self, expression: str) -> str:
@@ -145,7 +128,7 @@ def get_texcode_for_expression(self, expression: str) -> str:
145128
Parameters
146129
----------
147130
expression
148-
The string containing the expression to be typeset, e.g. ``$\\sqrt{2}$``
131+
The string containing the expression to be typeset, e.g. ``$\sqrt{2}$``
149132
150133
Returns
151134
-------
@@ -162,7 +145,7 @@ def get_texcode_for_expression_in_env(
162145
Parameters
163146
----------
164147
expression
165-
The string containing the expression to be typeset, e.g. ``$\\sqrt{2}$``.
148+
The string containing the expression to be typeset, e.g. ``$\sqrt{2}$``.
166149
environment
167150
The string containing the environment in which the expression should be typeset, e.g. ``align*``.
168151
@@ -171,11 +154,43 @@ def get_texcode_for_expression_in_env(
171154
:class:`str`
172155
LaTeX code based on template, containing the given expression inside its environment, ready for typesetting
173156
"""
174-
begin, end = self._texcode_for_environment(environment)
157+
begin, end = _texcode_for_environment(environment)
175158
return self.body.replace(
176159
self.placeholder_text, "\n".join([begin, expression, end])
177160
)
178161

179162
def copy(self) -> Self:
180163
"""Create a deep copy of the TeX template instance."""
181164
return copy.deepcopy(self)
165+
166+
167+
def _texcode_for_environment(environment: str) -> tuple[str, str]:
168+
r"""Processes the tex_environment string to return the correct ``\begin{environment}[extra]{extra}`` and
169+
``\end{environment}`` strings.
170+
171+
Parameters
172+
----------
173+
environment
174+
The tex_environment as a string. Acceptable formats include:
175+
``{align*}``, ``align*``, ``{tabular}[t]{cccl}``, ``tabular}{cccl``, ``\begin{tabular}[t]{cccl}``.
176+
177+
Returns
178+
-------
179+
Tuple[:class:`str`, :class:`str`]
180+
A pair of strings representing the opening and closing of the tex environment, e.g.
181+
``\begin{tabular}{cccl}`` and ``\end{tabular}``
182+
"""
183+
184+
environment.removeprefix(r"\begin").removeprefix("{")
185+
186+
# The \begin command takes everything and closes with a brace
187+
begin = r"\begin{" + environment
188+
# If it doesn't end on } or ], assume missing }
189+
if not begin.endswith(("}", "]")):
190+
begin += "}"
191+
192+
# While the \end command terminates at the first closing brace
193+
split_at_brace = re.split("}", environment, 1)
194+
end = r"\end{" + split_at_brace[0] + "}"
195+
196+
return begin, end

tests/module/utils/test_tex.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import pytest
2+
3+
from manim.utils.tex import TexTemplate
4+
5+
DEFAULT_BODY = r"""\documentclass[preview]{standalone}
6+
\usepackage[english]{babel}
7+
\usepackage{amsmath}
8+
\usepackage{amssymb}
9+
\begin{document}
10+
YourTextHere
11+
\end{document}"""
12+
13+
BODY_WITH_ADDED_PREAMBLE = r"""\documentclass[preview]{standalone}
14+
\usepackage[english]{babel}
15+
\usepackage{amsmath}
16+
\usepackage{amssymb}
17+
\usepackage{testpackage}
18+
\begin{document}
19+
YourTextHere
20+
\end{document}"""
21+
22+
BODY_WITH_PREPENDED_PREAMBLE = r"""\documentclass[preview]{standalone}
23+
\usepackage{testpackage}
24+
\usepackage[english]{babel}
25+
\usepackage{amsmath}
26+
\usepackage{amssymb}
27+
\begin{document}
28+
YourTextHere
29+
\end{document}"""
30+
31+
BODY_WITH_ADDED_DOCUMENT = r"""\documentclass[preview]{standalone}
32+
\usepackage[english]{babel}
33+
\usepackage{amsmath}
34+
\usepackage{amssymb}
35+
\begin{document}
36+
\boldmath
37+
YourTextHere
38+
\end{document}"""
39+
40+
BODY_REPLACE = r"""\documentclass[preview]{standalone}
41+
\usepackage[english]{babel}
42+
\usepackage{amsmath}
43+
\usepackage{amssymb}
44+
\begin{document}
45+
\sqrt{2}
46+
\end{document}"""
47+
48+
BODY_REPLACE_IN_ENV = r"""\documentclass[preview]{standalone}
49+
\usepackage[english]{babel}
50+
\usepackage{amsmath}
51+
\usepackage{amssymb}
52+
\begin{document}
53+
\begin{align}
54+
\sqrt{2}
55+
\end{align}
56+
\end{document}"""
57+
58+
59+
def test_tex_template_default_body():
60+
template = TexTemplate()
61+
assert template.body == DEFAULT_BODY
62+
63+
64+
def test_tex_template_preamble():
65+
template = TexTemplate()
66+
67+
template.add_to_preamble(r"\usepackage{testpackage}")
68+
assert template.body == BODY_WITH_ADDED_PREAMBLE
69+
70+
71+
def test_tex_template_preprend_preamble():
72+
template = TexTemplate()
73+
74+
template.add_to_preamble(r"\usepackage{testpackage}", prepend=True)
75+
assert template.body == BODY_WITH_PREPENDED_PREAMBLE
76+
77+
78+
def test_tex_template_document():
79+
template = TexTemplate()
80+
81+
template.add_to_document(r"\boldmath")
82+
assert template.body == BODY_WITH_ADDED_DOCUMENT
83+
84+
85+
def test_tex_template_texcode_for_expression():
86+
template = TexTemplate()
87+
88+
assert template.get_texcode_for_expression(r"\sqrt{2}") == BODY_REPLACE
89+
90+
91+
def test_tex_template_texcode_for_expression_in_env():
92+
template = TexTemplate()
93+
94+
assert (
95+
template.get_texcode_for_expression_in_env(r"\sqrt{2}", environment="align")
96+
== BODY_REPLACE_IN_ENV
97+
)
98+
99+
100+
def test_tex_template_fixed_body():
101+
template = TexTemplate()
102+
103+
# Usually set when calling `from_file`
104+
template.body = "dummy"
105+
106+
assert template.body == "dummy"
107+
108+
with pytest.warns(
109+
UserWarning,
110+
match="This TeX template was created with a fixed body, trying to add text the preamble will have no effect.",
111+
):
112+
template.add_to_preamble("dummys")
113+
114+
with pytest.warns(
115+
UserWarning,
116+
match="This TeX template was created with a fixed body, trying to add text the document will have no effect.",
117+
):
118+
template.add_to_document("dummy")

0 commit comments

Comments
 (0)