Repository: ManimCommunity/manim

Issue:
Tex value error when handling compilation issue
## Description of bug / unexpected behavior
<!-- Add a clear and concise description of the problem you encountered. -->

I had some missing tex dependencies that are resolved now, but when manim was gathering the details to report the error it failed as `env_markers_indices` in `tex_file_writing` > `print_tex_error` method was empty. It instead through the value error below in the logs that creates some noise hiding the real issue. 

## Expected behavior
<!-- Add a clear and concise description of what you expected to happen. -->

Expected behaviour is that it should be able to handle empty `env_markers_indices` if there are cases where it will be empty and report the reason for the latex compilation error and not throw a ValueError

## How to reproduce the issue
<!-- Provide a piece of code illustrating the undesired behavior. -->

In my case I was missing latex dependencies that triggered this issue in ubuntu and ran the test

```
pytest manim/tests/test_graphical_units/test_axes.py::test_axes
```

<details><summary>Code for reproducing the problem</summary>

```py
Paste your code here.
```

</details>


## Additional media files
<!-- Paste in the files manim produced on rendering the code above. -->

<details><summary>Images/GIFs</summary>

<!-- PASTE MEDIA HERE -->

</details>


## Logs
<details><summary>Terminal output</summary>
<!-- Add "-v DEBUG" when calling manim to generate more detailed logs -->

```
[gw0] [100%] FAILED test_axes.py::test_axes (1, 480, 854, 4)
[12/04/21 08:58:14] INFO     Writing "x" to /tmp/pytest-o tex_file_writing.py:85
                             f-ryan/pytest-32/popen-gw0/t                       
                             est_axes0/049324c46070fc62.t                       
                             ex                                                 
                    ERROR    LaTeX compilation error:    tex_file_writing.py:265
                             LaTeX Error: File                                  
                             `standalone.cls' not found.                        
                                                                                

tests/test_graphical_units/test_axes.py:6 (test_axes)
scene = <manim.utils.testing._test_class_makers._make_test_scene_class.<locals>._TestedScene object at 0x7f4b94ce2f40>

    @frames_comparison
    def test_axes(scene):
        graph = Axes(
            x_range=[-10, 10, 1],
            y_range=[-10, 10, 1],
            x_length=6,
            y_length=6,
            color=WHITE,
        )
>       labels = graph.get_axis_labels()

test_axes.py:16: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../manim/mobject/coordinate_systems.py:437: in get_axis_labels
    self.get_x_axis_label(x_label),
../../manim/mobject/coordinate_systems.py:291: in get_x_axis_label
    return self._get_axis_label(
../../manim/mobject/coordinate_systems.py:391: in _get_axis_label
    label = self._create_label_tex(label)
../../manim/mobject/coordinate_systems.py:359: in _create_label_tex
    label_tex = MathTex(label_tex)
../../manim/mobject/svg/tex_mobject.py:304: in __init__
    raise compilation_error
../../manim/mobject/svg/tex_mobject.py:283: in __init__
    super().__init__(
../../manim/mobject/svg/tex_mobject.py:90: in __init__
    file_name = tex_to_svg_file(
../../manim/utils/tex_file_writing.py:46: in tex_to_svg_file
    dvi_file = compile_tex(
../../manim/utils/tex_file_writing.py:181: in compile_tex
    print_all_tex_errors(log_file, tex_compiler, tex_file)
../../manim/utils/tex_file_writing.py:253: in print_all_tex_errors
    print_tex_error(tex_compilation_log, error_index, tex)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

tex_compilation_log = ['This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020/Debian) (preloaded format=latex 2021.11.25)  4 DEC 202.../test_axes0/049324c46070fc62.tex\n', '(/tmp/pytest-of-ryan/pytest-32/popen-gw0/test_axes0/049324c46070fc62.tex\n', ...]
error_start_index = 9
tex_source = ['\\documentclass[preview]{standalone}\n', '\n', '\\usepackage[english]{babel}\n', '\\usepackage{amsmath}\n', '\\usepackage{amssymb}\n', '\n', ...]

    def print_tex_error(tex_compilation_log, error_start_index, tex_source):
        logger.error(
            f"LaTeX compilation error: {tex_compilation_log[error_start_index][2:]}",
        )
    
        # TeX errors eventually contain a line beginning 'l.xxx` where xxx is the line number that caused the compilation
        # failure. This code finds the next such line after the error current error message
        line_of_tex_error = (
            int(
                [
                    log_line
                    for log_line in tex_compilation_log[error_start_index:]
                    if log_line.startswith("l.")
                ][0]
                .split(" ")[0]
                .split(".")[1],
            )
            - 1
        )
        # our tex error may be on a line outside our user input because of post-processing
        if line_of_tex_error >= len(tex_source):
            return None
    
        # all lines numbers containing '\begin{' or '\end{' - except the Manim added center and document tags
        env_markers_indices = [
            idx
            for idx, log_line in enumerate(tex_source)
            if any(marker in log_line for marker in [r"\begin{", r"\end{"])
        ][2:-2]
    
        context = "Context of error:\n\n"
        if line_of_tex_error in env_markers_indices:
            context += "".join(tex_source[line_of_tex_error - 1 : line_of_tex_error + 1])
        else:
>           marker_before_error = max(
                idx for idx in env_markers_indices if idx < line_of_tex_error
            )
E           ValueError: max() arg is an empty sequence

../../manim/utils/tex_file_writing.py:298: ValueError








Coverage.py warning: Module manim was previously imported, but not measured (module-not-measured)


============================== 1 failed in 2.80s ===============================
Manim Community v0.11.0


Process finished with exit code 1
```

<!-- Insert screenshots here (only when absolutely necessary, we prefer copy/pasted output!) -->

</details>


## System specifications

<details><summary>System Details</summary>

- OS (with version, e.g Windows 10 v2004 or macOS 10.15 (Catalina)): Ubuntu 21.10
- RAM: 16gb
- Python version (`python/py/python3 --version`): 3.9.7
- Installed modules (provide output from `pip list`):
```
PASTE HERE
```
</details>

<details><summary>LaTeX details</summary>

+ LaTeX distribution (e.g. TeX Live 2020): Version 3.14159265 (TeX Live 2020/Debian) (preloaded format=tex)
+ Installed LaTeX packages:
<!-- output of `tlmgr list --only-installed` for TeX Live or a screenshot of the Packages page for MikTeX -->
</details>

<details><summary>FFMPEG</summary>

Output of `ffmpeg -version`:

```
PASTE HERE
```
</details>

## Additional comments
<!-- Add further context that you think might be relevant for this issue here. -->



Discussion:
This is one of the most-reported errors in our Discord, it would be great to improve the error message.

Resolving patch:
diff --git a/manim/utils/tex_file_writing.py b/manim/utils/tex_file_writing.py
--- a/manim/utils/tex_file_writing.py
+++ b/manim/utils/tex_file_writing.py
@@ -84,7 +84,7 @@ def generate_tex_file(expression, environment=None, tex_template=None):
 
     result = os.path.join(tex_dir, tex_hash(output)) + ".tex"
     if not os.path.exists(result):
-        logger.info('Writing "{}" to {}'.format("".join(expression), result))
+        logger.info(f"Writing {expression} to %(path)s", {"path": f"{result}"})
         with open(result, "w", encoding="utf-8") as outfile:
             outfile.write(output)
     return result
@@ -142,11 +142,16 @@ def tex_compilation_command(tex_compiler, output_format, tex_file, tex_dir):
     return " ".join(commands)
 
 
-def insight_inputenc_error(match):
-    code_point = chr(int(match[1], 16))
+def insight_inputenc_error(matching):
+    code_point = chr(int(matching[1], 16))
     name = unicodedata.name(code_point)
-    yield f"TexTemplate does not support character '{name}' (U+{match[1]})"
-    yield "See the documentation for manim.mobject.svg.tex_mobject for details on using a custom TexTemplate"
+    yield f"TexTemplate does not support character '{name}' (U+{matching[1]})."
+    yield "See the documentation for manim.mobject.svg.tex_mobject for details on using a custom TexTemplate."
+
+
+def insight_package_not_found_error(matching):
+    yield f"You do not have package {matching[1]} installed."
+    yield f"Install {matching[1]} it using your LaTeX package manager, or check for typos."
 
 
 def compile_tex(tex_file, tex_compiler, output_format):
@@ -260,6 +265,10 @@ def print_all_tex_errors(log_file, tex_compiler, tex_file):
         r"inputenc Error: Unicode character (?:.*) \(U\+([0-9a-fA-F]+)\)",
         insight_inputenc_error,
     ),
+    (
+        r"LaTeX Error: File `(.*?[clsty])' not found",
+        insight_package_not_found_error,
+    ),
 ]
 
 
@@ -286,36 +295,26 @@ def print_tex_error(tex_compilation_log, error_start_index, tex_source):
     if line_of_tex_error >= len(tex_source):
         return None
 
-    # all lines numbers containing '\begin{' or '\end{' - except the Manim added center and document tags
-    env_markers_indices = [
-        idx
-        for idx, log_line in enumerate(tex_source)
-        if any(marker in log_line for marker in [r"\begin{", r"\end{"])
-    ][2:-2]
-
-    context = "Context of error:\n\n"
-    if line_of_tex_error in env_markers_indices:
-        context += "".join(tex_source[line_of_tex_error - 1 : line_of_tex_error + 1])
+    context = ["Context of error: \n"]
+    if line_of_tex_error < 3:
+        context += tex_source[: line_of_tex_error + 3]
+        context[-4] = "-> " + context[-4]
+    elif line_of_tex_error > len(tex_source) - 3:
+        context += tex_source[line_of_tex_error - 1 :]
+        context[1] = "-> " + context[1]
     else:
-        marker_before_error = max(
-            idx for idx in env_markers_indices if idx < line_of_tex_error
-        )
-        marker_after_error = min(
-            idx for idx in env_markers_indices if idx > line_of_tex_error
-        )
-        context += "".join(tex_source[marker_before_error:marker_after_error])
+        context += tex_source[line_of_tex_error - 3 : line_of_tex_error + 3]
+        context[-4] = "-> " + context[-4]
+
+    context = "".join(context)
     logger.error(context)
 
-    for prog, get_insight in LATEX_ERROR_INSIGHTS:
-        error_end_index = [
-            idx
-            for idx, _ in enumerate(tex_compilation_log[error_start_index:])
-            if _.startswith("l.")
-        ][0]
-        match = re.search(
-            prog,
-            "".join(tex_compilation_log[error_start_index:error_end_index]),
+    for insights in LATEX_ERROR_INSIGHTS:
+        prob, get_insight = insights
+        matching = re.search(
+            prob,
+            "".join(tex_compilation_log[error_start_index])[2:],
         )
-        if match is not None:
-            for insight in get_insight(match):
+        if matching is not None:
+            for insight in get_insight(matching):
                 logger.info(insight)


Test patch:
diff --git a/tests/control_data/logs_data/bad_tex_scene_BadTex.txt b/tests/control_data/logs_data/bad_tex_scene_BadTex.txt
new file mode 100644
--- /dev/null
+++ b/tests/control_data/logs_data/bad_tex_scene_BadTex.txt
@@ -0,0 +1,8 @@
+{"levelname": "INFO", "module": "logger_utils", "message": "Log file will be saved in <>"}
+{"levelname": "INFO", "module": "tex_file_writing", "message": "Writing \\frac{1}{0} to <>"}
+{"levelname": "ERROR", "module": "tex_file_writing", "message": "LaTeX compilation error: LaTeX Error: File `notapackage.sty' not found.\n"}
+{"levelname": "ERROR", "module": "tex_file_writing", "message": "Context of error: \n\\documentclass[preview]{standalone}\n-> \\usepackage{notapackage}\n\\begin{document}\n\n\\begin{center}\n"}
+{"levelname": "INFO", "module": "tex_file_writing", "message": "You do not have package notapackage.sty installed."}
+{"levelname": "INFO", "module": "tex_file_writing", "message": "Install notapackage.sty it using your LaTeX package manager, or check for typos."}
+{"levelname": "ERROR", "module": "tex_file_writing", "message": "LaTeX compilation error: Emergency stop.\n"}
+{"levelname": "ERROR", "module": "tex_file_writing", "message": "Context of error: \n\\documentclass[preview]{standalone}\n-> \\usepackage{notapackage}\n\\begin{document}\n\n\\begin{center}\n"}
diff --git a/tests/test_logging/bad_tex_scene.py b/tests/test_logging/bad_tex_scene.py
new file mode 100644
--- /dev/null
+++ b/tests/test_logging/bad_tex_scene.py
@@ -0,0 +1,9 @@
+from manim import Scene, Tex, TexTemplate
+
+
+class BadTex(Scene):
+    def construct(self):
+        tex_template = TexTemplate(preamble=r"\usepackage{notapackage}")
+        some_tex = r"\frac{1}{0}"
+        my_tex = Tex(some_tex, tex_template=tex_template)
+        self.add(my_tex)
diff --git a/tests/test_logging/test_logging.py b/tests/test_logging/test_logging.py
--- a/tests/test_logging/test_logging.py
+++ b/tests/test_logging/test_logging.py
@@ -76,3 +76,26 @@ def test_error_logging(tmp_path, python_version):
 
     _, err, exitcode = capture(command)
     assert exitcode != 0 and len(err) > 0
+
+
+@logs_comparison(
+    "bad_tex_scene_BadTex.txt",
+    Path("logs/bad_tex_scene_BadTex.log"),
+)
+def test_tex_error_logs(tmp_path, python_version):
+    bad_tex_scene = os.path.join("tests", "test_logging", "bad_tex_scene.py")
+    command = [
+        python_version,
+        "-m",
+        "manim",
+        "-ql",
+        "--log_to_file",
+        "-v",
+        "INFO",
+        "--media_dir",
+        str(tmp_path),
+        bad_tex_scene,
+        "BadTex",
+    ]
+    _, err, exitcode = capture(command)
+    assert exitcode != 0 and len(err) > 0