From 75e705c810e41358f8a1e995bdb4d7baa9754653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Ke=C3=9Fler?= Date: Thu, 17 Feb 2022 23:16:41 +0100 Subject: [PATCH] some better error handling --- PyTeX/format/errors.py | 23 ++++++++++++++- PyTeX/format/macros.py | 8 +++-- PyTeX/format/tex_formatter.py | 55 +++++++++++++++++++++++++---------- 3 files changed, 67 insertions(+), 19 deletions(-) diff --git a/PyTeX/format/errors.py b/PyTeX/format/errors.py index a183eae..41d6098 100644 --- a/PyTeX/format/errors.py +++ b/PyTeX/format/errors.py @@ -1,6 +1,27 @@ +from typing import Optional + class PyTeXError(Exception): - pass + def __init__(self, msg, explanation: Optional[str] = None): + self._msg = msg + self._traceback_explanation = [] + if explanation is not None: + self._traceback_explanation.append(explanation) + super().__init__(self._dispstr()) + + def _dispstr(self): + depth = 0 + ret = [self._msg] + for explanation in self._traceback_explanation: + ret.append(' ' * 2 * depth + explanation) + depth += 1 + return '\n'.join(ret) + + def add_explanation(self, explanation: str): + self._traceback_explanation.append(explanation) + + def __str__(self): + return self._dispstr() class PyTeXFormattingError(PyTeXError): diff --git a/PyTeX/format/macros.py b/PyTeX/format/macros.py index a4e56f9..c5fd41b 100644 --- a/PyTeX/format/macros.py +++ b/PyTeX/format/macros.py @@ -158,7 +158,9 @@ class MacroCodeBeginMacro(SingleLineMacro): def _apply(self, line: str, formatter) -> Union[str, List[str]]: if not formatter.mode == FormatterMode.meta: - raise PyTeXInvalidBeginMacroCodeUsageError + raise PyTeXInvalidBeginMacroCodeUsageError( + r"\begin{macrocode} used outside meta context" + ) formatter.mode = FormatterMode.macrocode return r'% \begin{macrocode}' @@ -169,7 +171,9 @@ class MacroCodeEndMacro(SingleLineMacro): def _apply(self, line: str, formatter) -> Union[str, List[str]]: if not formatter.mode == FormatterMode.macrocode: - raise PyTeXInvalidEndMacroCodeUsageError + raise PyTeXInvalidEndMacroCodeUsageError( + r"\end{macrocode} used outside macrocode context" + ) formatter.mode = FormatterMode.meta return r'% \end{macrocode}' diff --git a/PyTeX/format/tex_formatter.py b/PyTeX/format/tex_formatter.py index f7cfcb8..4d6d883 100644 --- a/PyTeX/format/tex_formatter.py +++ b/PyTeX/format/tex_formatter.py @@ -7,6 +7,7 @@ from .macros import Macro from .pytex_formatter import PyTeXFormatter from .enums import * from ..logger import logger +from .errors import * class LineStream: @@ -15,6 +16,7 @@ class LineStream: self._handle = open(filename, 'r') self._cached_lines: List[str] = [] self._file_exhausted: bool = False + self._line_number: int = 0 def current_line(self): return self.future_line(0) @@ -47,6 +49,7 @@ class LineStream: self._cached_lines.append( self._handle.readline() ) + self._line_number += 1 if self._cached_lines[-1] == '': self._handle.close() self._file_exhausted = True @@ -57,6 +60,10 @@ class LineStream: b = len(self._cached_lines) == 0 return a & b + @property + def line_number(self) -> int: + return self._line_number + class TexFormatter(PyTeXFormatter, ABC): def __init__(self, *args, **kwargs): @@ -102,13 +109,21 @@ class TexFormatter(PyTeXFormatter, ABC): self._macros = macros def _handle_macro(self, macro: Macro): - replacement, shipout = macro.apply( - self.line_stream.current_line(), - self - ) + try: + replacement, shipout = macro.apply( + self.line_stream.current_line(), + self + ) + except PyTeXMacroError as e: + e.add_explanation('While applying macro') + raise e if shipout: self.line_stream.pop_line() - self._shipout_line(replacement) + if isinstance(replacement, str): + self._shipout_line(replacement) + else: + for line in replacement: + self._shipout_line(line) else: if isinstance(replacement, str): self.line_stream.set_line(replacement) @@ -150,13 +165,13 @@ class TexFormatter(PyTeXFormatter, ABC): def mode(self, mode: FormatterMode) -> None: self._mode = mode - def _get_provides_text(self) -> str: + def _get_provides_text(self, provided_type: str) -> str: if self.config.has_description: if self.config.tex_flavour == TeXFlavour.LaTeX2e: return \ r'\Provides%s{%s}[%s - %s]' \ % ( - self.config.tex_type.value.capitalize(), + provided_type, self.name, self.attribute_dict[FormatterProperty.date.value], self.attribute_dict[FormatterProperty.description.value] @@ -166,7 +181,7 @@ class TexFormatter(PyTeXFormatter, ABC): return \ '\\ProvidesExpl%s { %s } { %s } { %s }\n { %s }' \ % ( - self.config.tex_type.value.capitalize(), + provided_type, self.name, self.attribute_dict[FormatterProperty.date.value], self.config.version, @@ -183,14 +198,22 @@ class TexFormatter(PyTeXFormatter, ABC): def format_document(self) -> None: while not self.line_stream.exhausted: - recent_replacement = True - while recent_replacement: - recent_replacement = False - for macro in self.macros: - if macro.matches(self.line_stream.current_line()): - self._handle_macro(macro) - recent_replacement = True - break + try: + recent_replacement = True + while recent_replacement: + recent_replacement = False + for macro in self.macros: + if macro.matches(self.line_stream.current_line()): + try: + self._handle_macro(macro) + except PyTeXMacroError as e: + e.add_explanation('while handling macro') + raise e + recent_replacement = True + break + except PyTeXMacroError as e: + e.add_explanation(f'in line {self.line_stream.line_number} ({self.line_stream.current_line().rstrip()})') + pass self._shipout_line(self._post_process_line( self.line_stream.pop_line() ))