Source code for pyndoc.writers.typst_writer

from pyndoc.ast.basic_blocks import ASTBlock
from pyndoc.ast.blocks import (
    Space,
    Str,
    SoftBreak,
    Header,
    Para,
    Emph,
    Strong,
    Code,
    CodeBlock,
    BulletList,
    Plain,
    OrderedList,
    Table,
)


[docs] class TypstWriter: """ A class for converting an Abstract Syntax Tree (AST) representation of a document into Typst format. """ def __init__(self) -> None: """ Initializes the Typst writer and sets up a mapping of block types to processing methods. """ self.block_handlers = { "Para": self._process_para, "Emph": self._process_emph, "Strong": self._process_strong, "Code": self._process_code, "CodeBlock": self._process_code_block, "Header": self._process_header, "BulletList": self._process_bullet_list, "OrderedList": self._process_ordered_list, "Table": self._process_table, "Str": self._process_str, "Space": self._process_space, "SoftBreak": self._process_soft_break, } def _get_typst_representation(self, ast_tree: list[ASTBlock]) -> str: """ Converts the given AST tree into a Typst document. :param ast_tree: List of AST blocks representing the document structure. :return: String containing the Typst representation of the document. """ result = "" result += "\n".join(self._process_block(block) for block in ast_tree) return result def _process_block(self, block: ASTBlock) -> str: """ Processes a single AST block using the appropriate handler. :param block: The AST block to process. :return: The Typst representation of the block. """ handler = self.block_handlers.get(block.__class__.__name__, self._process_unknown) try: return handler(block) except Exception as e: return f"// Error processing block {block.__class__.__name__}: {str(e)}" def _process_para(self, block: Para) -> str: """ Processes a paragraph block. :param block: The paragraph block. :return: The Typst representation of the paragraph. """ return f"{self._process_contents(block.contents.contents)}\n\n" def _process_emph(self, block: Emph) -> str: """ Processes an emphasized (italic) text block. :param block: The emphasized text block. :return: The Typst representation of the emphasized text. """ return f"_{self._process_contents(block.contents.contents)}_" def _process_strong(self, block: Strong) -> str: """ Processes a strong (bold) text block. :param block: The strong text block. :return: The Typst representation of the strong text. """ contents = self._process_contents(block.contents.contents) return f"*{contents}*" def _process_code(self, block: Code) -> str: """ Processes inline code. :param block: The inline code block. :return: The Typst representation of the inline code. """ return f"`{block.contents}`" def _process_code_block(self, block: CodeBlock) -> str: """ Processes a block of code. :param block: The code block. :return: The Typst representation of the code block. """ return f"```\n{block.contents}\n```" def _process_header(self, block: Header) -> str: """ Processes a header block. :param block: The header block. :return: The Typst representation of the header. """ level = block.contents.metadata[0] if block.contents.metadata else 1 command = "=" * level return f"{command} {self._process_contents(block.contents.contents)}" def _process_bullet_list(self, block: BulletList) -> str: """ Processes a bullet list. :param block: The bullet list block. :return: The Typst representation of the bullet list. """ items = "\n".join( (f"{block.contents.metadata[0] * ' '}- {self._process_contents(item.contents.contents)}" if isinstance(item, Plain) else self._process_block(item)) for item in block.contents.contents if isinstance(item, (BulletList, Plain, OrderedList)) ) return items def _process_ordered_list(self, block: OrderedList) -> str: """ Processes an ordered list. :param block: The ordered list block. :return: The Typst representation of the ordered list. """ items = "\n".join( (f"+ {self._process_contents(item.contents.contents)}" if isinstance(item, Plain) else self._process_block(item)) for item in block.contents.contents if isinstance(item, (Plain, BulletList, OrderedList)) ) return items def _process_table(self, block: Table) -> str: """ Processes a table. :param block: The table block. :return: The Typst representation of the table. """ num_columns = len(block.contents.contents[0].contents.contents[0].contents.contents) headers = [] for row in block.contents.contents[0].contents.contents: headers.append( ", ".join(f"[{self._process_contents(cell.contents.contents)}]" for cell in row.contents.contents) ) body_rows = [] for row in block.contents.contents[1].contents.contents: body_rows.append( ", ".join(f"[{self._process_contents(cell.contents.contents)}]" for cell in row.contents.contents) ) table_representation = f"#table(\n columns: {num_columns},\n " + ",\n ".join(headers + body_rows) + "\n)" return table_representation def _process_str(self, block: Str) -> str: """ Processes a string block. :param block: The string block. :return: The Typst representation of the string. """ return block.contents def _process_space(self, block: Space) -> str: """ Processes a space block. :param block: The space block. :return: A single space character. """ return " " def _process_soft_break(self, block: SoftBreak) -> str: """ Processes a soft break. :param block: The soft break block. :return: A newline character. """ return "\n" def _process_unknown(self, block: ASTBlock) -> str: """ Handles unknown block types. :param block: The unknown AST block. :return: A Typst comment indicating an unknown block type. """ return f"// Unknown block: {block.__class__.__name__}" def _process_contents(self, contents: list) -> str: """ Processes a list of content blocks. :param contents: List of AST blocks or strings. :return: A concatenated string of processed blocks. """ try: return "".join(self._process_block(item) for item in contents) except Exception as e: return f"// Error processing contents: {str(e)}"
[docs] def print_tree(self, ast_tree: list[ASTBlock]) -> None: """ Prints the Typst representation of an AST tree. :param ast_tree: List of AST blocks representing the document structure. """ print(self._get_typst_representation(ast_tree))
[docs] def write_tree_to_file(self, filename: str, ast_tree: list[ASTBlock]) -> None: """ Writes the Typst representation of an AST tree to a file. :param filename: The name of the file to write to. :param ast_tree: List of AST blocks representing the document structure. """ with open(filename, "w") as fp: fp.write(self._get_typst_representation(ast_tree))