diff --git a/src/formatitko/html_generator.py b/src/formatitko/html_generator.py
index d7dbc03..59db46d 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/katex.py b/src/formatitko/katex.py
index ad431e6..39e521f 100644
--- a/src/formatitko/katex.py
+++ b/src/formatitko/katex.py
@@ -79,7 +79,7 @@ class KatexClient:
if "error" in response:
raise KatexServerError(response["error"])
if "error" in response["results"][0]:
- raise KatexError(response["results"][0]["error"])
+ raise KatexError(response["results"][0]["error"] + " in $" + tex + "$")
else:
return response["results"][0]["html"]
diff --git a/src/formatitko/latex_generator.py b/src/formatitko/latex_generator.py
index 124cf11..31e0325 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/output_generator.py b/src/formatitko/output_generator.py
index 61df1c6..69db2cf 100644
--- a/src/formatitko/output_generator.py
+++ b/src/formatitko/output_generator.py
@@ -3,18 +3,31 @@ from panflute import Cite, Code, Emph, Image, LineBreak, Link, Math, Note, Quote
from panflute import BlockQuote, BulletList, Citation, CodeBlock, Definition, DefinitionItem, DefinitionList, Div, Figure, Header, HorizontalRule, LineBlock, LineItem, ListItem, MetaBlocks, MetaBool, MetaInlines, MetaList, MetaMap, MetaString, Null, OrderedList, Para, Plain, RawBlock, Table, TableBody, TableFoot, TableHead
from panflute import TableRow, TableCell, Caption, Doc
from panflute import MetaValue
+from panflute import stringify
from typing import Union, Callable
from .whitespace import NBSP
from .elements import FQuoted
from .context import Group, InlineGroup, BlockGroup, Context
-import re
+import re, sys
class UnknownElementError(Exception):
"An unknown Element has been passed to the OutputGenerator, probably because panflute introduced a new one."
pass
+
+class OutputGeneratorError(Exception):
+ "A generic exception which wraps other exceptions and adds element-based traceback"
+ elements: list[Union[Element, ListContainer, list[Union[Element, ListContainer]]]]
+
+ def __init__(self, e: Union[Element, ListContainer, list[Union[Element, ListContainer]]], *args):
+ self.elements = [e]
+ super().__init__(args)
+
+ def add_element(self, e: Union[Element, ListContainer, list[Union[Element, ListContainer]]]):
+ self.elements.append(e)
+
class OutputGenerator:
_empty_lines: int
context: Union[Context, None]
@@ -101,28 +114,53 @@ class OutputGenerator:
}
def generate(self, e: Union[Element, ListContainer, list[Union[Element, ListContainer]]]):
- if isinstance(e, Group):
- old_context = self.context
- self.context = e.context
- if isinstance(e, list):
- self.generate_list(e)
- elif isinstance(e, ListContainer):
- self.generate_ListContainer(e)
- elif isinstance(e, Inline):
- self.generate_Inline(e)
- elif isinstance(e, Block):
- self.generate_Block(e)
- elif isinstance(e, MetaValue):
- self.generate_MetaValue(e)
- elif isinstance(e, MetaList):
- self.generate_MetaList(e)
- else:
- try:
- self.TYPE_DICT_MISC[type(e)](e)
- except KeyError:
- raise UnknownElementError(type(e))
- if isinstance(e, Group):
- self.context = old_context
+ 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
+ self.context = e.context
+ if isinstance(e, list):
+ self.generate_list(e)
+ elif isinstance(e, ListContainer):
+ self.generate_ListContainer(e)
+ elif isinstance(e, Inline):
+ self.generate_Inline(e)
+ elif isinstance(e, Block):
+ self.generate_Block(e)
+ elif isinstance(e, MetaValue):
+ self.generate_MetaValue(e)
+ elif isinstance(e, MetaList):
+ self.generate_MetaList(e)
+ else:
+ try:
+ self.TYPE_DICT_MISC[type(e)](e)
+ except KeyError as err:
+ raise UnknownElementError(type(e)) from err
+ if isinstance(e, Group):
+ self.context = old_context
+ except OutputGeneratorError as err:
+ if not isinstance(e, ListContainer):
+ err.add_element(e)
+ raise err
+ except Exception as err:
+ raise OutputGeneratorError(e) from err
def escape_special_chars(self, text: str) -> str:
return text
@@ -210,13 +248,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))
@@ -235,27 +273,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)
@@ -285,33 +323,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("\"")
@@ -434,10 +472,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 c97b90d..a1f2268 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):