From 42a63b3163a1bb406aab3c6d7a85446844cb3fea Mon Sep 17 00:00:00 2001 From: Greenscreener Date: Thu, 15 Feb 2024 17:43:10 +0100 Subject: [PATCH] Partial rewrite of error handling Now the error doesn't handle itself, but offers a helper function to do it. --- src/formatitko/formatitko.py | 33 ++++++++++--- src/formatitko/html_generator.py | 16 +++---- src/formatitko/latex_generator.py | 10 ++-- src/formatitko/nop_processor.py | 40 +++++++++------- src/formatitko/output_generator.py | 77 +++++++++++++++--------------- src/formatitko/tex_generator.py | 56 +++++++++++----------- 6 files changed, 129 insertions(+), 103 deletions(-) diff --git a/src/formatitko/formatitko.py b/src/formatitko/formatitko.py index 9b3b942..9ee76ee 100755 --- a/src/formatitko/formatitko.py +++ b/src/formatitko/formatitko.py @@ -14,7 +14,7 @@ from .katex import KatexClient from .html import html from .tex import tex from .images import ImageProcessor -from .output_generator import OutputGenerator +from .output_generator import OutputGenerator, FormatitkoRecursiveError from .html_generator import HTMLGenerator from .transform_processor import TransformProcessor from .pandoc_processor import PandocProcessor @@ -54,9 +54,15 @@ def main(): doc = import_md(open(args.input_filename, "r").read()) if args.debug: - OutputGenerator(sys.stdout).generate(doc) + try: + OutputGenerator(sys.stdout).generate(doc) + except FormatitkoRecursiveError as e: + e.pretty_print() - doc = TransformProcessor(args.input_filename).transform(doc) + try: + doc = TransformProcessor(args.input_filename).transform(doc) + except FormatitkoRecursiveError as e: + e.pretty_print() # Initialize the image processor (this just keeps some basic state) imageProcessor = ImageProcessor(args.img_public_dir, args.img_web_path, args.img_cache_dir, *args.img_lookup_dirs) @@ -65,11 +71,18 @@ def main(): # Initialize KaTeX client (this runs the node app and connects to a unix socket) with KatexClient(socket=args.katex_socket) as katexClient: with open(args.output_html, "w") as file: - HTMLGenerator(file, katexClient, imageProcessor).generate(doc) + try: + HTMLGenerator(file, katexClient, imageProcessor).generate(doc) + except FormatitkoRecursiveError as e: + e.pretty_print() if args.output_tex is not None: with open(args.output_tex, "w") as file: - UCWTexGenerator(file, imageProcessor).generate(doc) + try: + UCWTexGenerator(file, imageProcessor).generate(doc) + except FormatitkoRecursiveError as e: + e.pretty_print() + if args.output_md is not None: with open(args.output_md, "w") as file: @@ -83,7 +96,10 @@ def main(): if args.output_tex is None: fd = tempfile.NamedTemporaryFile(dir=".", suffix=".tex") with open(fd.name, "w") as file: - UCWTexGenerator(file, imageProcessor).generate(doc) + try: + UCWTexGenerator(file, imageProcessor).generate(doc) + except FormatitkoRecursiveError as e: + e.pretty_print() filename = fd.name else: filename = args.output_tex @@ -93,7 +109,10 @@ def main(): if args.debug: print("-----------------------------------") - OutputGenerator(sys.stdout).generate(doc) + try: + OutputGenerator(sys.stdout).generate(doc) + except FormatitkoRecursiveError as e: + e.pretty_print() if __name__ == "__main__": diff --git a/src/formatitko/html_generator.py b/src/formatitko/html_generator.py index 59db46d..d7dbc03 100644 --- a/src/formatitko/html_generator.py +++ b/src/formatitko/html_generator.py @@ -29,10 +29,10 @@ class HTMLGenerator(OutputGenerator): self.imageProcessor = imageProcessor super().__init__(output_file, *args, **kwargs) - def _generate(self, e: Union[Element, ListContainer]): + def generate(self, e: Union[Element, ListContainer]): if hasattr(e, "attributes") and "only" in e.attributes and e.attributes["only"] != "html": return - super()._generate(e) + super().generate(e) def escape_special_chars(self, text: str) -> str: text = text.replace("&", "&") @@ -190,7 +190,7 @@ class HTMLGenerator(OutputGenerator): attributes["alt"] = e.title else: fake_out = io.StringIO() - HTMLGenerator(fake_out, self.katexClient, self.imageProcessor)._generate(e.content) + HTMLGenerator(fake_out, self.katexClient, self.imageProcessor).generate(e.content) attributes["alt"] = fake_out.getvalue() if len(srcset) != 0: @@ -202,7 +202,7 @@ class HTMLGenerator(OutputGenerator): img = RawInline(self.single_tag("img", attributes)) link = Link(img, url=url) - self._generate(link) + self.generate(link) def generate_InlineGroup(self, e: InlineGroup): self.generate_Group(e) @@ -216,10 +216,10 @@ class HTMLGenerator(OutputGenerator): self.katexClient.endgroup() def generate_Plain(self, e: Plain): - self._generate(e.content) + self.generate(e.content) def generate_LineItem(self, e: LineItem): - self._generate(e.content) + self.generate(e.content) self.write("
") self.endln() @@ -229,12 +229,12 @@ class HTMLGenerator(OutputGenerator): tag = self.tagname(e) if inline is not None: self.write(self.start_tag(tag)+" (") - self._generate(inline) + self.generate(inline) self.write(") "+self.end_tag(tag)) else: self.writeln(self.start_tag(tag) + "(") self.indent_more() - self._generate(e.content) + self.generate(e.content) self.indent_less() self.writeln(self.end_tag(tag) + ")") diff --git a/src/formatitko/latex_generator.py b/src/formatitko/latex_generator.py index 31e0325..124cf11 100644 --- a/src/formatitko/latex_generator.py +++ b/src/formatitko/latex_generator.py @@ -17,10 +17,10 @@ class LaTeXGenerator(OutputGenerator): self.imageProcessor = imageProcessor super().__init__(output_file, *args, **kwargs) - def _generate(self, e: Union[Element, ListContainer]): + def generate(self, e: Union[Element, ListContainer]): if hasattr(e, "attributes") and "only" in e.attributes and e.attributes["only"] != "tex": return - super()._generate(e) + super().generate(e) def escape_special_chars(self, text: str) -> str: text = text.replace("&", "\\&") @@ -58,14 +58,14 @@ class LaTeXGenerator(OutputGenerator): self.endln() def generate_Para(self, e: Para): - self._generate(e.content) + self.generate(e.content) self.writeln("") # This ensures an empty line def generate_Plain(self, e: Plain): - self._generate(e.content) + self.generate(e.content) def generate_Span(self, e: Plain): - self._generate(e.content) + self.generate(e.content) def generate_Header(self, e: Header): tag = { diff --git a/src/formatitko/nop_processor.py b/src/formatitko/nop_processor.py index 2f09ffb..5a89228 100644 --- a/src/formatitko/nop_processor.py +++ b/src/formatitko/nop_processor.py @@ -10,6 +10,7 @@ from .elements import FQuoted from .context import Group, InlineGroup, BlockGroup from .whitespace import Whitespace from .command import BlockCommand, InlineCommand, CodeCommand, Command +from .output_generator import FormatitkoRecursiveError ELCl = Union[Element, ListContainer, list[Union[Element, ListContainer]]] @@ -96,23 +97,30 @@ class NOPProcessor: return [] def transform(self, e: ELCl) -> ELCl: - if isinstance(e, list): - return self.transform_list(e) - elif isinstance(e, ListContainer): - return self.transform_ListContainer(e) - - for transformer in self.get_pretransformers(): - e = transformer(e) - try: - e = self.TYPE_DICT[type(e)](e) - except KeyError: - raise self.UnknownElementError(type(e)) - - for transformer in self.get_posttransformers(): - e = transformer(e) - - return e + if isinstance(e, list): + return self.transform_list(e) + elif isinstance(e, ListContainer): + return self.transform_ListContainer(e) + + for transformer in self.get_pretransformers(): + e = transformer(e) + + try: + e = self.TYPE_DICT[type(e)](e) + except KeyError: + raise self.UnknownElementError(type(e)) + + for transformer in self.get_posttransformers(): + e = transformer(e) + + return e + except FormatitkoRecursiveError as err: + if not isinstance(e, ListContainer): + err.add_element(e) + raise err + except Exception as err: + raise FormatitkoRecursiveError(e) from err def transform_list(self, e: list[Union[Element, ListContainer]]) -> list[Union[Element, ListContainer]]: for i in range(len(e)): diff --git a/src/formatitko/output_generator.py b/src/formatitko/output_generator.py index 69db2cf..9869b87 100644 --- a/src/formatitko/output_generator.py +++ b/src/formatitko/output_generator.py @@ -10,14 +10,15 @@ from .whitespace import NBSP from .elements import FQuoted from .context import Group, InlineGroup, BlockGroup, Context -import re, sys + +import sys class UnknownElementError(Exception): "An unknown Element has been passed to the OutputGenerator, probably because panflute introduced a new one." pass -class OutputGeneratorError(Exception): +class FormatitkoRecursiveError(Exception): "A generic exception which wraps other exceptions and adds element-based traceback" elements: list[Union[Element, ListContainer, list[Union[Element, ListContainer]]]] @@ -28,6 +29,22 @@ class OutputGeneratorError(Exception): def add_element(self, e: Union[Element, ListContainer, list[Union[Element, ListContainer]]]): self.elements.append(e) + def pretty_print(self): + def eprint(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) + eprint("Error occured in ", end="") + for i in range(len(self.elements)-1, 0, -1): + if hasattr(self.elements[i], "content") and isinstance(self.elements[i].content[0], Inline): + eprint() + eprint('on line: "' + stringify(self.elements[i]) + '"', end="") + break + eprint(type(self.elements[i]).__name__ + "[" + str(self.elements[i-1].index) + "]", end=": ") + eprint() + eprint("in element: " + str(self.elements[0])) + sys.tracebacklimit = 0 + raise self.__cause__ from None + + class OutputGenerator: _empty_lines: int context: Union[Context, None] @@ -114,24 +131,6 @@ class OutputGenerator: } def generate(self, e: Union[Element, ListContainer, list[Union[Element, ListContainer]]]): - try: - self._generate(e) - except OutputGeneratorError as err: - def eprint(*args, **kwargs): - print(*args, file=sys.stderr, **kwargs) - eprint("Error occured in ", end="") - for i in range(len(err.elements)-1, 0, -1): - if hasattr(err.elements[i], "content") and isinstance(err.elements[i].content[0], Inline): - eprint() - eprint('on line: "' + stringify(err.elements[i]) + '"', end="") - break - eprint(type(err.elements[i]).__name__ + "[" + str(err.elements[i-1].index) + "]", end=": ") - eprint() - eprint("in element: " + str(err.elements[0])) - sys.tracebacklimit = 0 - raise err.__cause__ from None - - def _generate(self, e: Union[Element, ListContainer, list[Union[Element, ListContainer]]]): try: if isinstance(e, Group): old_context = self.context @@ -155,12 +154,12 @@ class OutputGenerator: raise UnknownElementError(type(e)) from err if isinstance(e, Group): self.context = old_context - except OutputGeneratorError as err: + except FormatitkoRecursiveError as err: if not isinstance(e, ListContainer): err.add_element(e) raise err except Exception as err: - raise OutputGeneratorError(e) from err + raise FormatitkoRecursiveError(e) from err def escape_special_chars(self, text: str) -> str: return text @@ -248,13 +247,13 @@ class OutputGenerator: def generate_simple_inline_tag(self, tag: str, content: Union[ListContainer, Element, list[Union[Element, ListContainer]]], attributes: dict[str,str]={}): self.write(self.start_tag(tag, attributes)) - self._generate(content) + self.generate(content) self.write(self.end_tag(tag)) def generate_simple_block_tag(self, tag: str, content: Union[ListContainer, Element, list[Union[Element, ListContainer]]], attributes: dict[str,str]={}): self.writeln(self.start_tag(tag, attributes)) self.indent_more() - self._generate(content) + self.generate(content) self.indent_less() self.writeln(self.end_tag(tag)) @@ -273,27 +272,27 @@ class OutputGenerator: def generate_ListContainer(self, e: ListContainer): for child in e: - self._generate(child) + self.generate(child) def generate_list(self, e: list): for el in e: - self._generate(el) + self.generate(el) def generate_MetaList(self, e: MetaList): for child in e: - self._generate(child) + self.generate(child) def generate_MetaValue(self, e: MetaValue): try: self.TYPE_DICT_META[type(e)](e) except KeyError: - self._generate(e.content) + self.generate(e.content) def generate_MetaBlocks(self, e: MetaBlocks): - self._generate(e.content) + self.generate(e.content) def generate_MetaInlines(self, e: MetaInlines): - self._generate(e.content) + self.generate(e.content) def generate_MetaBool(self, e: MetaBool): self.generate_simple_tag(e) @@ -323,33 +322,33 @@ class OutputGenerator: if e.style == "cs": if e.quote_type == "SingleQuote": self.write("‚") - self._generate(e.content) + self.generate(e.content) self.write("‘") elif e.quote_type == "DoubleQuote": self.write("„") - self._generate(e.content) + self.generate(e.content) self.write("“") elif e.style == "en": if e.quote_type == "SingleQuote": self.write("‘") - self._generate(e.content) + self.generate(e.content) self.write("’") elif e.quote_type == "DoubleQuote": self.write("“") - self._generate(e.content) + self.generate(e.content) self.write("”") else: if e.quote_type == "SingleQuote": self.write("'") - self._generate(e.content) + self.generate(e.content) self.write("'") elif e.quote_type == "DoubleQuote": self.write("\"") - self._generate(e.content) + self.generate(e.content) self.write("\"") else: self.write("\"") - self._generate(e.content) + self.generate(e.content) self.write("\"") @@ -472,10 +471,10 @@ class OutputGenerator: def generate_Doc(self, e: Doc): if "header_content" in e.metadata: - self._generate(e.metadata["header_content"]) + self.generate(e.metadata["header_content"]) self.generate_simple_tag(e) if "footer_content" in e.metadata: - self._generate(e.metadata["footer_content"]) + self.generate(e.metadata["footer_content"]) def generate_BlockGroup(self, e: BlockGroup): self.generate_simple_tag(e) diff --git a/src/formatitko/tex_generator.py b/src/formatitko/tex_generator.py index a1f2268..c97b90d 100644 --- a/src/formatitko/tex_generator.py +++ b/src/formatitko/tex_generator.py @@ -41,10 +41,10 @@ class UCWTexGenerator(OutputGenerator): text = text.replace("​", "") return text - def _generate(self, e: Union[Element, ListContainer]): + def generate(self, e: Union[Element, ListContainer]): if hasattr(e, "attributes") and "only" in e.attributes and e.attributes["only"] != "tex": return - super()._generate(e) + super().generate(e) def writepar(self, text: str): self.ensure_empty(2) @@ -60,7 +60,7 @@ class UCWTexGenerator(OutputGenerator): def generate_Para(self, e: Para): self.ensure_empty(2) - self._generate(e.content) + self.generate(e.content) self.ensure_empty(2) def generate_HorizontalRule(self, e: HorizontalRule): @@ -72,7 +72,7 @@ class UCWTexGenerator(OutputGenerator): self.writeln(r"\ucwmodule{verb}") self.writeln(r"\ucwmodule{link}") self.writeln(r"\input formatitko.tex") - self._generate(e.content) + self.generate(e.content) self.writeln(r"\bye") def get_language_macro(self, lang: str): @@ -88,21 +88,21 @@ class UCWTexGenerator(OutputGenerator): def generate_InlineGroup(self, e: InlineGroup): self.write(r"{") self.write(self.get_language_macro(self.context.get_metadata("lang"))) - self._generate(e.content) + self.generate(e.content) self.write(r"}") def generate_BlockGroup(self, e: BlockGroup): self.writeln(r"\begingroup") self.indent_more() self.writeln(self.get_language_macro(self.context.get_metadata("lang"))) - self._generate(e.content) + self.generate(e.content) self.indent_less() self.writeln(r"\endgroup") def generate_Header(self, e: Header): self.ensure_empty(2) self.write("\\"+"sub"*(e.level-1)+"section{") - self._generate(e.content) + self.generate(e.content) self.write(r"}") self.ensure_empty(2) @@ -156,12 +156,12 @@ class UCWTexGenerator(OutputGenerator): self.writeln(r"\vskip5pt") self.writeln(r"\centerline{") self.indent_more() - self._generate(e.content) + self.generate(e.content) self.indent_less() self.writeln(r"}") self.writeln(r"\centerline{") self.indent_more() - self._generate(e.caption) + self.generate(e.caption) self.indent_less() self.writeln(r"}") self.writeln(r"\vskip5pt{}") @@ -173,7 +173,7 @@ class UCWTexGenerator(OutputGenerator): else: self.write(r"{\I{}") self._italic+=1 - self._generate(e.content) + self.generate(e.content) self._italic-=1 self.write(r"}") @@ -183,7 +183,7 @@ class UCWTexGenerator(OutputGenerator): else: self.write(r"{\bf{}") self._bold+=1 - self._generate(e.content) + self.generate(e.content) self._bold-=1 self.write(r"}") @@ -204,7 +204,7 @@ class UCWTexGenerator(OutputGenerator): def generate_Note(self, e: Note): self.write(r"\fn{") - self._generate(inlinify(e)) + self.generate(inlinify(e)) self.write(r"}") def generate_Table(self, e: Table): @@ -217,11 +217,11 @@ class UCWTexGenerator(OutputGenerator): self.writeln(r"\vskip1em") self.writeln(r"\halign{\strut"+"&".join([aligns[col[0]] for col in e.colspec])+r"\cr") self.indent_more() - self._generate(e.head.content) + self.generate(e.head.content) self.writeln(r"\noalign{\hrule}") - self._generate(e.content[0].content) + self.generate(e.content[0].content) self.writeln(r"\noalign{\hrule}") - self._generate(e.foot.content) + self.generate(e.foot.content) self.indent_less() self.writeln("}") self.writeln(r"\vskip1em") @@ -230,7 +230,7 @@ class UCWTexGenerator(OutputGenerator): for cell in e.content: if cell.colspan > 1: self.write(r"\multispan"+str(cell.colspan)+"{} ") - self._generate(cell.content) + self.generate(cell.content) if cell.next: self.write(" & ") self.write(r"\cr") @@ -245,10 +245,10 @@ class UCWTexGenerator(OutputGenerator): self.writeraw(e.text) def generate_Plain(self, e: Plain): - self._generate(e.content) + self.generate(e.content) def generate_Span(self, e: Span): - self._generate(e.content) + self.generate(e.content) def generate_CodeBlock(self, e: CodeBlock): self.writeln(r"\verbatim{") @@ -256,15 +256,15 @@ class UCWTexGenerator(OutputGenerator): self.writeln(r"}") def generate_Div(self, e: Div): - self._generate(e.content) + self.generate(e.content) def generate_LineBlock(self, e: LineBlock): self.writeln() - self._generate(e.content) + self.generate(e.content) self.writeln() def generate_LineItem(self, e: LineItem): - self._generate(e.content) + self.generate(e.content) if e.next: self.write(r"\\") self.endln() @@ -273,7 +273,7 @@ class UCWTexGenerator(OutputGenerator): self.ensure_empty(2) self.writeln(r"\list{o}") self.indent_more() - self._generate(e.content) + self.generate(e.content) self.indent_less() self.write(r"\endlist") self.ensure_empty(2) @@ -298,7 +298,7 @@ class UCWTexGenerator(OutputGenerator): style = delimiters[e.delimiter] self.writeln(r"\list{"+style+r"}") self.indent_more() - self._generate(e.content) + self.generate(e.content) self.indent_less() self.writeln(r"\endlist") self.ensure_empty(2) @@ -306,13 +306,13 @@ class UCWTexGenerator(OutputGenerator): def generate_ListItem(self, e: ListItem): self.endln() self.write(r"\:") - self._generate(e.content) + self.generate(e.content) self.endln() def generate_BlockQuote(self, e: BlockQuote): self.writeln(r"\blockquote{") self.indent_more() - self._generate(e.content) + self.generate(e.content) self.indent_less() self.writeln(r"}") @@ -321,17 +321,17 @@ class UCWTexGenerator(OutputGenerator): self.write(r"\url{") else: self.write(r"\linkurl{"+e.url+r"}{") - self._generate(e.content) + self.generate(e.content) self.write(r"}") # } def generate_Subscript(self, e: Subscript): self.write(r"\subscript{") - self._generate(e.content) + self.generate(e.content) self.write(r"}") def generate_Superscript(self, e: Superscript): self.write(r"\superscript{") - self._generate(e.content) + self.generate(e.content) self.write(r"}") def generate_simple_tag(self, e: Union[Element, None] = None, tag: str = "", attributes: Union[dict[str, str], None] = None, content: Union[ListContainer, Element, list[Union[Element, ListContainer]], str, None] = None, inline: Union[bool, None] = None):