You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

532 lines
16 KiB

from panflute import Element, ListContainer, Inline, Block
from panflute import Cite, Code, Emph, Image, LineBreak, Link, Math, Note, Quoted, RawInline, SmallCaps, SoftBreak, Space, Span, Str, Strikeout, Strong, Subscript, Superscript, Underline
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 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.path +\
((" (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]
indent_level: int
indent_str: str
output_file: ...
TYPE_DICT_BLOCK: dict[type, Callable]
TYPE_DICT_INLINE: dict[type, Callable]
TYPE_DICT_MISC: dict[type, Callable]
def __init__(self, output_file, indent_str: str="\t", initial_indent_level: int=0):
self.output_file = output_file
self.indent_str = indent_str
self.indent_level = initial_indent_level
self._empty_lines = 1
self.context = None
self.TYPE_DICT_MISC = {
TableRow: self.generate_TableRow,
TableCell: self.generate_TableCell,
Caption: self.generate_Caption,
Doc: self.generate_Doc,
LineItem: self.generate_LineItem,
ListItem: self.generate_ListItem
}
self.TYPE_DICT_BLOCK = {
BlockQuote: self.generate_BlockQuote,
BulletList: self.generate_BulletList,
Citation: self.generate_Citation,
CodeBlock: self.generate_CodeBlock,
Definition: self.generate_Definition,
DefinitionItem: self.generate_DefinitionItem,
DefinitionList: self.generate_DefinitionList,
Div: self.generate_Div,
Figure: self.generate_Figure,
Header: self.generate_Header,
HorizontalRule: self.generate_HorizontalRule,
LineBlock: self.generate_LineBlock,
Null: self.generate_Null,
OrderedList: self.generate_OrderedList,
Para: self.generate_Para,
Plain: self.generate_Plain,
RawBlock: self.generate_RawBlock,
Table: self.generate_Table,
TableBody: self.generate_TableBody,
TableFoot: self.generate_TableFoot,
TableHead: self.generate_TableHead,
BlockGroup: self.generate_BlockGroup
}
self.TYPE_DICT_INLINE = {
Cite: self.generate_Cite,
Code: self.generate_Code,
Emph: self.generate_Emph,
Image: self.generate_Image,
LineBreak: self.generate_LineBreak,
Link: self.generate_Link,
Math: self.generate_Math,
Note: self.generate_Note,
Quoted: self.generate_Quoted,
RawInline: self.generate_RawInline,
SmallCaps: self.generate_SmallCaps,
SoftBreak: self.generate_SoftBreak,
Space: self.generate_Space,
Span: self.generate_Span,
Str: self.generate_Str,
Strikeout: self.generate_Strikeout,
Strong: self.generate_Strong,
Subscript: self.generate_Subscript,
Superscript: self.generate_Superscript,
Underline: self.generate_Underline,
NBSP: self.generate_NBSP,
FQuoted: self.generate_FQuoted,
InlineGroup: self.generate_InlineGroup
}
self.TYPE_DICT_META = {
MetaBlocks: self.generate_MetaBlocks,
MetaBool: self.generate_MetaBool,
MetaInlines: self.generate_MetaInlines,
MetaMap: self.generate_MetaMap,
MetaString: self.generate_MetaString,
}
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 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
def indent(self) -> str:
return self.indent_str*self.indent_level
def indent_more(self):
self.indent_level += 1
def indent_less(self):
self.indent_level -= 1
def write(self, text: str=""):
if self._empty_lines > 0:
self.output_file.write(self.indent())
self.output_file.write(text)
self._empty_lines = 0
def writeln(self, text: str=""):
if self._empty_lines == 0:
self.output_file.write("\n")
self.output_file.write(self.indent())
self.output_file.write(text+"\n")
self._empty_lines = 1
def writeraw(self, text: str=""):
if self._empty_lines == 0:
self.output_file.write("\n")
self.output_file.write(text+"\n")
self._empty_lines = 1
def ensure_empty(self, n: int=1):
while self._empty_lines < n:
self.output_file.write("\n")
self._empty_lines+=1
def endln(self):
self.ensure_empty(1)
def start_tag(self, tag: str, attributes: dict[str,str]={}) -> str:
return tag
def end_tag(self, tag: str, attributes: dict[str,str]={}) -> str:
return "/" + tag
def single_tag(self, tag: str, attributes: dict[str,str]={}) -> str:
return "/" + tag + "/"
def tagname(self, e) -> str:
return type(e).__name__
def common_attributes(self, e: Element) -> dict[str,str]:
return {}
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):
if not tag and e:
tag = self.tagname(e)
if attributes is None:
if e:
attributes = self.common_attributes(e)
else:
attributes = {}
if content is None and e and hasattr(e, "content"):
content = e.content
if content is None and e and hasattr(e, "text"):
content = e.text
if inline is None and e:
inline = isinstance(e, Inline)
if content is None:
self.generate_empty_block_tag(tag, attributes)
return
if inline:
if isinstance(content, str):
self.generate_raw_inline_tag(tag, content, attributes)
else:
self.generate_simple_inline_tag(tag, content, attributes)
else:
if isinstance(content, str):
self.generate_raw_block_tag(tag, content, attributes)
else:
self.generate_simple_block_tag(tag, content, attributes)
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.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.indent_less()
self.writeln(self.end_tag(tag))
def generate_raw_inline_tag(self, tag: str, text: str, attributes: dict[str,str]={}):
self.write(self.start_tag(tag, attributes))
self.write(text)
self.write(self.end_tag(tag))
def generate_raw_block_tag(self, tag: str, text: str, attributes: dict[str,str]={}):
self.writeln(self.start_tag(tag, attributes))
self.writeraw(text)
self.writeln(self.end_tag(tag))
def generate_empty_block_tag(self, tag: str, attributes: dict[str,str]={}):
self.writeln(self.single_tag(tag, attributes))
def generate_ListContainer(self, e: ListContainer):
for child in e:
self.generate(child)
def generate_list(self, e: list):
for el in e:
self.generate(el)
def generate_MetaList(self, e: MetaList):
for child in e:
self.generate(child)
def generate_MetaValue(self, e: MetaValue):
try:
self.TYPE_DICT_META[type(e)](e)
except KeyError:
self.generate(e.content)
def generate_MetaBlocks(self, e: MetaBlocks):
self.generate(e.content)
def generate_MetaInlines(self, e: MetaInlines):
self.generate(e.content)
def generate_MetaBool(self, e: MetaBool):
self.generate_simple_tag(e)
def generate_MetaMap(self, e: MetaMap):
self.generate_simple_tag(e)
def generate_MetaString(self, e: MetaString):
self.generate_simple_tag(e)
def generate_Inline(self, e: Inline):
self.TYPE_DICT_INLINE[type(e)](e)
def generate_Str(self, e: Str):
self.write(self.escape_special_chars(e.text))
def generate_Space(self, e: Space):
self.write(" ")
def generate_NBSP(self, e: NBSP):
self.write("~")
def generate_SoftBreak(self, e: SoftBreak):
self.endln()
def generate_FQuoted(self, e: FQuoted):
if e.style == "cs":
if e.quote_type == "SingleQuote":
self.write("")
self.generate(e.content)
self.write("")
elif e.quote_type == "DoubleQuote":
self.write("")
self.generate(e.content)
self.write("")
elif e.style == "en":
if e.quote_type == "SingleQuote":
self.write("")
self.generate(e.content)
self.write("")
elif e.quote_type == "DoubleQuote":
self.write("")
self.generate(e.content)
self.write("")
else:
if e.quote_type == "SingleQuote":
self.write("'")
self.generate(e.content)
self.write("'")
elif e.quote_type == "DoubleQuote":
self.write("\"")
self.generate(e.content)
self.write("\"")
else:
self.write("\"")
self.generate(e.content)
self.write("\"")
# Inline Elements
def generate_Cite(self, e: Cite):
self.generate_simple_tag(e)
def generate_Emph(self, e: Emph):
self.generate_simple_tag(e)
def generate_Image(self, e: Image):
self.generate_simple_tag(e)
def generate_Link(self, e: Link):
self.generate_simple_tag(e)
def generate_Note(self, e: Note):
self.generate_simple_tag(e)
def generate_Quoted(self, e: Quoted):
self.generate_simple_tag(e)
def generate_SmallCaps(self, e: SmallCaps):
self.generate_simple_tag(e)
def generate_Span(self, e: Span):
self.generate_simple_tag(e)
def generate_Strikeout(self, e: Strikeout):
self.generate_simple_tag(e)
def generate_Strong(self, e: Strong):
self.generate_simple_tag(e)
def generate_Subscript(self, e: Subscript):
self.generate_simple_tag(e)
def generate_Superscript(self, e: Superscript):
self.generate_simple_tag(e)
def generate_Underline(self, e: Underline):
self.generate_simple_tag(e)
# Raw Inline elements
def generate_Math(self, e: Math):
self.generate_simple_tag(e)
def generate_Code(self, e: Code):
self.generate_simple_tag(e)
def generate_RawInline(self, e: RawInline):
self.generate_simple_tag(e)
def generate_Block(self, e: Block):
self.TYPE_DICT_BLOCK[type(e)](e)
# Block elements
def generate_BlockQuote(self, e: BlockQuote):
self.generate_simple_tag(e)
def generate_BulletList(self, e: BulletList):
self.generate_simple_tag(e)
def generate_Citation(self, e: Citation):
self.generate_simple_tag(e)
def generate_Definition(self, e: Definition):
self.generate_simple_tag(e)
def generate_DefinitionItem(self, e: DefinitionItem):
self.generate_simple_tag(e)
def generate_DefinitionList(self, e: DefinitionList):
self.generate_simple_tag(e)
def generate_Div(self, e: Div):
self.generate_simple_tag(e)
def generate_Header(self, e: Header):
self.generate_simple_tag(e)
def generate_LineBlock(self, e: LineBlock):
self.generate_simple_tag(e)
def generate_LineItem(self, e: LineItem):
self.generate_simple_tag(e)
def generate_ListItem(self, e: ListItem):
self.generate_simple_tag(e)
def generate_OrderedList(self, e: OrderedList):
self.generate_simple_tag(e)
def generate_Para(self, e: Para):
self.generate_simple_tag(e)
def generate_Plain(self, e: Plain):
self.generate_simple_tag(e)
def generate_Caption(self, e: Caption):
self.generate_simple_tag(e)
def generate_TableBody(self, e: TableBody):
self.generate_simple_tag(e)
def generate_TableCell(self, e: TableCell):
self.generate_simple_tag(e)
def generate_TableFoot(self, e: TableFoot):
self.generate_simple_tag(e)
def generate_TableHead(self, e: TableHead):
self.generate_simple_tag(e)
def generate_TableRow(self, e: TableRow):
self.generate_simple_tag(e)
def generate_Doc(self, e: Doc):
if "header_content" in e.metadata:
self.generate(e.metadata["header_content"])
self.generate_simple_tag(e)
if "footer_content" in e.metadata:
self.generate(e.metadata["footer_content"])
def generate_BlockGroup(self, e: BlockGroup):
self.generate_simple_tag(e)
def generate_InlineGroup(self, e: InlineGroup):
self.generate_simple_tag(e)
# Special elements with more contents
def generate_Table(self, e: Table):
self.generate_simple_tag(e, content=[e.head, e.content, e.foot])
def generate_Figure(self, e: Figure):
self.generate_simple_tag(e, content=[e.content, e.caption])
# Emtpy tags
def generate_Null(self, e: Null):
self.generate_simple_tag(e)
def generate_HorizontalRule(self, e: HorizontalRule):
self.generate_simple_tag(e)
def generate_LineBreak(self, e: LineBreak):
self.generate_simple_tag(e)
# Raw Block tags
def generate_CodeBlock(self, e: CodeBlock):
self.generate_simple_tag(e)
def generate_RawBlock(self, e: RawBlock):
self.generate_simple_tag(e)
# Maybe move this to ImageProcessor?
def get_image_processor_args(self, attributes:dict[str,str]) -> dict:
# Attributes → image processor args
additional_args = {}
if "file-width" in attributes:
additional_args["width"] = int(attributes["file-width"])
if "file-height" in attributes:
additional_args["height"] = int(attributes["file-height"])
if "file-quality" in attributes:
additional_args["quality"] = int(attributes["file-quality"])
if "file-dpi" in attributes:
additional_args["dpi"] = int(attributes["file-dpi"])
if "file-deps" in attributes:
additional_args["deps"] = attributes["file-deps"].split(",")
return additional_args