Experimental error handling with snippets of input for OutputGenerator. #54

Merged
jan merged 5 commits from error-handling into master 9 months ago
  1. 33
      src/formatitko/formatitko.py
  2. 2
      src/formatitko/katex.py
  3. 49
      src/formatitko/nop_processor.py
  4. 91
      src/formatitko/output_generator.py
  5. 7
      src/formatitko/transform_processor.py
  6. 9
      test/test-files/test-partial.md

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

2
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"]

49
src/formatitko/nop_processor.py

@ -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))
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, self.context) 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

91
src/formatitko/output_generator.py

@ -3,18 +3,56 @@ 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 sys
class UnknownElementError(Exception):
"An unknown Element has been passed to the OutputGenerator, probably because panflute introduced a new one."
pass
class FormatitkoRecursiveError(Exception):
"A generic exception which wraps other exceptions and adds element-based traceback"
elements: list[Union[Element, ListContainer, list[Union[Element, ListContainer]]]]
context: Context
def __init__(self, e: Union[Element, ListContainer, list[Union[Element, ListContainer]]], context: Context, *args):
self.elements = [e]
self.context = context
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)
def print_filename_recursive(context: Context):
return context.filename +\
((" (included from " + print_filename_recursive(context.parent) + ")") if context.parent else "")
eprint(f"Error occured in file {print_filename_recursive(self.context)} in ", end="")
line = None
for i in range(len(self.elements)-1, 0, -1):
if hasattr(self.elements[i], "content") and len(self.elements[i].content) > 0 and isinstance(self.elements[i].content[0], Inline) and line is None:
line = self.elements[i]
eprint(type(self.elements[i]).__name__ + "[" + (str(self.elements[i-1].index) if isinstance(self.elements[i-1].index, int) else "") + "]", end=": ")
if line:
eprint()
eprint('on line: "' + stringify(line).strip() + '"', end="")
eprint()
eprint("in element: " + str(self.elements[0]).replace("\n", "\\n"))
sys.tracebacklimit = 0
raise self.__cause__ from None
class OutputGenerator:
_empty_lines: int
context: Union[Context, None]
@ -101,28 +139,35 @@ 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:
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 FormatitkoRecursiveError as err:
if not isinstance(e, ListContainer):
err.add_element(e)
raise err
except Exception as err:
raise FormatitkoRecursiveError(e, self.context) from err
def escape_special_chars(self, text: str) -> str:
return text

7
src/formatitko/transform_processor.py

@ -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]] = []

9
test/test-files/test-partial.md

@ -56,6 +56,15 @@ $$
$$
<!--There is an inline *emphasis with $math \error$*.-->
<!--
```python {.run}
print("bruh")
raise Exception("Jsem piča")
```
-->
![This is a figure, go figure...](logo.svg){width=25%}What
![This is a figure, go figure...](logo.pdf){width=50%}

Loading…
Cancel
Save