Browse Source

Partial rewrite of error handling

Now the error doesn't handle itself, but offers a helper function to do
it.
error-handling
Jan Černohorský 4 months ago
parent
commit
42a63b3163
  1. 33
      src/formatitko/formatitko.py
  2. 16
      src/formatitko/html_generator.py
  3. 10
      src/formatitko/latex_generator.py
  4. 40
      src/formatitko/nop_processor.py
  5. 77
      src/formatitko/output_generator.py
  6. 56
      src/formatitko/tex_generator.py

33
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__":

16
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("<br>")
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) + ")")

10
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 = {

40
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)):

77
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)

56
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):

Loading…
Cancel
Save