diff --git a/src/formatitko/formatitko.py b/src/formatitko/formatitko.py index 9465456..913d36a 100755 --- a/src/formatitko/formatitko.py +++ b/src/formatitko/formatitko.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import argparse +import sys # Import local files from .transform import transform @@ -10,52 +11,54 @@ from .katex import KatexClient from .html import html from .tex import tex from .images import ImageProcessor +from .output_generator import Output_generator from .mj_show import show def main(): - # Initialize command line arguments - parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument("-l", "--img-lookup-dirs", help="Image lookup directories. When processing images, the program will try to find the image in them first. Always looks for images in the same folder as the markdown file.", nargs="+", default=[]) - parser.add_argument("-p", "--img-public-dir", help="Directory to put processed images into. The program will not overwrite existing images.", default="public") - parser.add_argument("-i", "--img-web-path", help="Path where the processed images are available on the website.", default="/") - parser.add_argument("-w", "--output-html", help="The HTML file (for Web) to write into.", default="output.html") - parser.add_argument("-t", "--output-tex", help="The TEX file to write into.", default="output.tex") - parser.add_argument("input_filename", help="The markdown file to process.") - parser.add_argument("--debug", action='store_true') - args = parser.parse_args() - # TODO: Accept path to unix socket for katexClient, then don't init our own, - # just connect to an existing one. For formátíking many files in a row. - - # Use panflute to parse the input MD file - doc = import_md(open(args.input_filename, "r").read()) - - if args.debug: - print(show(doc)) - - # The language metadatum is important, so it's read before transformation and - # then attached to a group inside the Doc - language = doc.get_metadata("language", None, True) - context = Context(doc, args.input_filename) - - # Transform the document. This includes all the fancy formatting this software does. - doc = doc.walk(transform, context) - - # Now wrap the document contents in a group, which is able to pop its language - # setting out to TeX - doc.content = [Group(*doc.content, metadata={"language":language})] - - # Initialize the image processor (this just keeps some basic state) - imageProcessor = ImageProcessor(args.img_public_dir, args.img_web_path, *args.img_lookup_dirs) - - # Initialize KaTeX client (this runs the node app and connects to a unix socket) - with KatexClient() as katexClient: - # Generate HTML and TeX out of the transformed document - open(args.output_html, "w").write(html(doc, katexClient, imageProcessor)) - open(args.output_tex, "w").write(tex(doc, imageProcessor)) - - if args.debug: - print(show(doc)) + # Initialize command line arguments + parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument("-l", "--img-lookup-dirs", help="Image lookup directories. When processing images, the program will try to find the image in them first. Always looks for images in the same folder as the markdown file.", nargs="+", default=[]) + parser.add_argument("-p", "--img-public-dir", help="Directory to put processed images into. The program will not overwrite existing images.", default="public") + parser.add_argument("-i", "--img-web-path", help="Path where the processed images are available on the website.", default="/") + parser.add_argument("-w", "--output-html", help="The HTML file (for Web) to write into.", default="output.html") + parser.add_argument("-t", "--output-tex", help="The TEX file to write into.", default="output.tex") + parser.add_argument("input_filename", help="The markdown file to process.") + parser.add_argument("--debug", action='store_true') + args = parser.parse_args() + # TODO: Accept path to unix socket for katexClient, then don't init our own, + # just connect to an existing one. For formátíking many files in a row. + + # Use panflute to parse the input MD file + doc = import_md(open(args.input_filename, "r").read()) + + if args.debug: + print(show(doc)) + + # The language metadatum is important, so it's read before transformation and + # then attached to a group inside the Doc + language = doc.get_metadata("language", None, True) + context = Context(doc, args.input_filename) + + # Transform the document. This includes all the fancy formatting this software does. + doc = doc.walk(transform, context) + + # Now wrap the document contents in a group, which is able to pop its language + # setting out to TeX + doc.content = [Group(*doc.content, metadata={"language":language})] + + # Initialize the image processor (this just keeps some basic state) + imageProcessor = ImageProcessor(args.img_public_dir, args.img_web_path, *args.img_lookup_dirs) + + # Initialize KaTeX client (this runs the node app and connects to a unix socket) + with KatexClient() as katexClient: + # Generate HTML and TeX out of the transformed document + #open(args.output_html, "w").write(html(doc, katexClient, imageProcessor)) + #open(args.output_tex, "w").write(tex(doc, imageProcessor)) + Output_generator(sys.stdout).generate(doc) + + if args.debug: + print(show(doc)) if __name__ == "__main__": diff --git a/src/formatitko/katex.py b/src/formatitko/katex.py index 99a70fc..d4c9bf5 100644 --- a/src/formatitko/katex.py +++ b/src/formatitko/katex.py @@ -65,5 +65,5 @@ class KatexClient: def __enter__(self): return self - def __exit__(self): + def __exit__(self, type, value, tb): self._server_process.terminate() diff --git a/src/formatitko/output_generator.py b/src/formatitko/output_generator.py new file mode 100644 index 0000000..f5f4df6 --- /dev/null +++ b/src/formatitko/output_generator.py @@ -0,0 +1,340 @@ +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 typing import Union + +from .whitespace import NBSP +from .transform import FQuoted +from .context import Group + +class UnknownElementException(Exception): + "An unknown Element has been passed to the Output_generator, probably because panflute introduced a new one." + pass + +class Output_generator: + 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._at_start_of_line = True + + def indent(self) -> str: + return self.indent_str*self.indent_level + + def iup(self): + self.indent_level += 1 + + def ido(self): + self.indent_level -= 1 + + def write(self, text: str): + if self._at_start_of_line: + self.output_file.write(self.indent()) + self.output_file.write(text) + self._at_start_of_line = False + + def writeln(self, text: str): + if not self._at_start_of_line: + self.output_file.write("\n") + self.output_file.write(self.indent()) + self.output_file.write(text+"\n") + self._at_start_of_line = True + + def writeraw(self, text: str): + if not self._at_start_of_line: + self.output_file.write("\n") + self.output_file.write(text+"\n") + self._at_start_of_line = True + + def endln(self): + if not self._at_start_of_line: + self.output_file.write("\n") + self._at_start_of_line = True + + def stag(self, tag: str) -> str: + return tag + + def etag(self, tag: str) -> str: + return "/" + tag + + def ntag(self, tag: str) -> str: + return "/" + tag + "/" + + def generate(self, e: Union[Element, ListContainer]): + if isinstance(e, ListContainer): + self.generate_ListContainer(e) + elif isinstance(e, Inline): + self.generate_Inline(e) + elif isinstance(e, Block): + self.generate_Block(e) + else: + try: + { + TableRow: self.generate_TableRow, + TableCell: self.generate_TableCell, + Caption: self.generate_Caption, + Doc: self.generate_Doc, + LineItem: self.generate_LineItem, + ListItem: self.generate_ListItem + }[type(e)](e) + except KeyError: + raise UnknownElementException(type(e)) + + def generate_ListContainer(self, e: ListContainer): + for child in e: + self.generate(child) + + def generate_Inline(self, e: 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 + }[type(e)](e) + + def generate_Str(self, e): + self.write(e.text) + + def generate_Space(self, e): + self.write(" ") + + def generate_NBSP(self, e): + self.write("~") + + def generate_SoftBreak(self, e): + self.endln() + + def generate_Cite(self, e): + self.generate_simple_inline_tag(e, "Cite") + + def generate_Emph(self, e): + self.generate_simple_inline_tag(e, "Emph") + + def generate_Image(self, e): + self.generate_simple_inline_tag(e, "Image") + + def generate_LineBreak(self, e): + self.generate_simple_inline_tag(e, "LineBreak") + + def generate_Link(self, e): + self.generate_simple_inline_tag(e, "Link") + + def generate_Note(self, e): + self.generate_simple_inline_tag(e, "Note") + + def generate_Quoted(self, e): + self.generate_simple_inline_tag(e, "Quoted") + + def generate_RawInline(self, e): + self.generate_simple_inline_tag(e, "RawInline") + + def generate_SmallCaps(self, e): + self.generate_simple_inline_tag(e, "SmallCaps") + + def generate_Span(self, e): + self.generate_simple_inline_tag(e, "Span") + + def generate_Strikeout(self, e): + self.generate_simple_inline_tag(e, "Strikeout") + + def generate_Strong(self, e): + self.generate_simple_inline_tag(e, "Strong") + + def generate_Subscript(self, e): + self.generate_simple_inline_tag(e, "Subscript") + + def generate_Superscript(self, e): + self.generate_simple_inline_tag(e, "Superscript") + + def generate_Underline(self, e): + self.generate_simple_inline_tag(e, "Underline") + + def generate_FQuoted(self, e): + self.generate_simple_inline_tag(e, "FQuoted") + + def generate_simple_inline_tag(self, e, tag: str): + self.write(self.stag(tag)+" ") + self.generate(e.content) + self.write(" "+self.etag(tag)) + + def generate_Math(self, e): + self.generate_raw_inline_tag(e, "Math") + + def generate_Code(self, e): + self.generate_raw_inline_tag(e, "Code") + + def generate_RawInline(self, e): + self.generate_raw_inline_tag(e, "RawInline") + + def generate_raw_inline_tag(self, e, tag: str): + self.write(self.stag(tag)+" ") + self.write(e.text) + self.write(" "+self.etag(tag)) + + def generate_Block(self, e: 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, + MetaBlocks: self.generate_MetaBlocks, + MetaBool: self.generate_MetaBool, + MetaInlines: self.generate_MetaInlines, + MetaList: self.generate_MetaList, + MetaMap: self.generate_MetaMap, + MetaString: self.generate_MetaString, + 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, + Group: self.generate_Group + }[type(e)](e) + + def generate_BlockQuote(self, e): + self.generate_simple_block_tag(e, "BlockQuote") + + def generate_BulletList(self, e): + self.generate_simple_block_tag(e, "BulletList") + + def generate_Citation(self, e): + self.generate_simple_block_tag(e, "Citation") + + def generate_Definition(self, e): + self.generate_simple_block_tag(e, "Definition") + + def generate_DefinitionItem(self, e): + self.generate_simple_block_tag(e, "DefinitionItem") + + def generate_DefinitionList(self, e): + self.generate_simple_block_tag(e, "DefinitionList") + + def generate_Div(self, e): + self.generate_simple_block_tag(e, "Div") + + def generate_Figure(self, e): + self.generate_simple_block_tag(e, "Figure") + + def generate_Header(self, e): + self.generate_simple_block_tag(e, "Header") + + def generate_LineBlock(self, e): + self.generate_simple_block_tag(e, "LineBlock") + + def generate_LineItem(self, e): + self.generate_simple_block_tag(e, "LineItem") + + def generate_ListItem(self, e): + self.generate_simple_block_tag(e, "ListItem") + + def generate_MetaBlocks(self, e): + self.generate_simple_block_tag(e, "MetaBlocks") + + def generate_MetaBool(self, e): + self.generate_simple_block_tag(e, "MetaBool") + + def generate_MetaInlines(self, e): + self.generate_simple_block_tag(e, "MetaInlines") + + def generate_MetaList(self, e): + self.generate_simple_block_tag(e, "MetaList") + + def generate_MetaMap(self, e): + self.generate_simple_block_tag(e, "MetaMap") + + def generate_MetaString(self, e): + self.generate_simple_block_tag(e, "MetaString") + + def generate_OrderedList(self, e): + self.generate_simple_block_tag(e, "OrderedList") + + def generate_Para(self, e): + self.generate_simple_block_tag(e, "Para") + + def generate_Plain(self, e): + self.generate_simple_block_tag(e, "Plain") + + def generate_Caption(self, e): + self.generate_simple_block_tag(e, "Caption") + + def generate_Table(self, e): + self.generate_simple_block_tag(e, "Table") + + def generate_TableBody(self, e): + self.generate_simple_block_tag(e, "TableBody") + + def generate_TableCell(self, e): + self.generate_simple_block_tag(e, "TableCell") + + def generate_TableFoot(self, e): + self.generate_simple_block_tag(e, "TableFoot") + + def generate_TableHead(self, e): + self.generate_simple_block_tag(e, "TableHead") + + def generate_TableRow(self, e): + self.generate_simple_block_tag(e, "TableRow") + + def generate_Doc(self, e): + self.generate_simple_block_tag(e, "Doc") + + def generate_Group(self, e): + self.generate_simple_block_tag(e, "Group") + + def generate_simple_block_tag(self, e, tag: str): + self.writeln(self.stag(tag)) + self.iup() + self.generate(e.content) + self.ido() + self.writeln(self.etag(tag)) + + def generate_Null(self, e): + self.generate_empty_block_tag(e, "Null") + + def generate_HorizontalRule(self, e): + self.generate_empty_block_tag(e, "HorizontalRule") + + def generate_empty_block_tag(self, e, tag: str): + self.writeln(self.ntag(tag)) + + def generate_CodeBlock(self, e): + self.generate_raw_block_tag(e, "CodeBlock") + + def generate_RawBlock(self, e): + self.generate_raw_block_tag(e, "RawBlock") + + def generate_raw_block_tag(self, e, tag: str): + self.writeln(self.stag(tag)+"\n") + self.writeraw(e.text) + self.writeln(self.etag(tag)) diff --git a/src/formatitko/transform.py b/src/formatitko/transform.py index 45f64dc..d422060 100644 --- a/src/formatitko/transform.py +++ b/src/formatitko/transform.py @@ -1,4 +1,4 @@ -from panflute import Element, Div, Span, Quoted, Image, CodeBlock, Str, MetaInlines, MetaStr, MetaBool +from panflute import Element, Div, Span, Quoted, Image, CodeBlock, Str, MetaInlines, MetaString, MetaBool import re # Import local files