Compare commits

...

2 commits

Author SHA1 Message Date
6de4ea2743 Error handling now contains filename. 2024-02-15 18:19:10 +01:00
42a63b3163 Partial rewrite of error handling
Now the error doesn't handle itself, but offers a helper function to do
it.
2024-02-15 18:09:54 +01:00
7 changed files with 139 additions and 109 deletions

View file

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

View file

@ -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) + ")")

View file

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

View file

@ -7,15 +7,20 @@ from typing import Union, Callable
from .whitespace import NBSP
from .elements import FQuoted
from .context import Group, InlineGroup, BlockGroup
from .context import Group, InlineGroup, BlockGroup, Context
from .whitespace import Whitespace
from .command import BlockCommand, InlineCommand, CodeCommand, Command
from .output_generator import FormatitkoRecursiveError
ELCl = Union[Element, ListContainer, list[Union[Element, ListContainer]]]
class DoubleDocError(Exception):
"TransformProcessor should only ever see a single Doc."
pass
class NOPProcessor:
TYPE_DICT: dict[type, Callable]
context: Union[Context, None] = None
class UnknownElementError(Exception):
f"An unknown Element has been passed to the NOPProcessor, probably because panflute introduced a new one."
@ -96,23 +101,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))
if isinstance(e, list):
return self.transform_list(e)
elif isinstance(e, ListContainer):
return self.transform_ListContainer(e)
for transformer in self.get_posttransformers():
e = transformer(e)
for transformer in self.get_pretransformers():
e = transformer(e)
return 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, self.context.filename) from err
def transform_list(self, e: list[Union[Element, ListContainer]]) -> list[Union[Element, ListContainer]]:
for i in range(len(e)):
@ -293,6 +305,9 @@ class NOPProcessor:
return e
def transform_Doc(self, e: Doc) -> Doc:
if self.context is not None:
raise DoubleDocError()
self.context = Context(e, self.root_file_path)
e.content = self.transform(e.content)
return e

View file

@ -10,24 +10,43 @@ 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]]]]
file: str
def __init__(self, e: Union[Element, ListContainer, list[Union[Element, ListContainer]]], *args):
def __init__(self, e: Union[Element, ListContainer, list[Union[Element, ListContainer]]], file: str, *args):
self.elements = [e]
self.file = file
super().__init__(args)
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(f"Error occured in file {self.file} 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 +133,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 +156,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, self.context.filename) from err
def escape_special_chars(self, text: str) -> str:
return text
@ -248,13 +249,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 +274,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 +324,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 +473,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)

View file

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

View file

@ -20,15 +20,10 @@ from .context import Context, CommandCallable
from .whitespace import Whitespace, bavlna
from .command import BlockCommand, InlineCommand, CodeCommand, Command
from .command_util import handle_command_define, parse_command
from .nop_processor import NOPProcessor, ELCl
class DoubleDocError(Exception):
"TransformProcessor should only ever see a single Doc."
pass
from .nop_processor import NOPProcessor, ELCl, DoubleDocError
class TransformProcessor(NOPProcessor):
context: Union[Context, None] = None
root_file_path: str
root_highlight_style: str = "default"
_command_modules: list[tuple[Union[dict[str, CommandCallable], ModuleType], str]] = []