Browse Source

Resolve #23.

pull/28/head
Jan Černohorský 11 months ago
parent
commit
7769b12cda
  1. 9
      src/formatitko/command.py
  2. 10
      src/formatitko/html_generator.py
  3. 12
      src/formatitko/images.py
  4. 4
      src/formatitko/katex.py
  5. 8
      src/formatitko/latex_generator.py
  6. 160
      src/formatitko/output_generator.py

9
src/formatitko/command.py

@ -1,5 +1,4 @@
from panflute import Div, Span, Para, Element from panflute import Div, Span, Para, Element
from typing import List
# Import local files # Import local files
from .util import nullify, import_md from .util import nullify, import_md
@ -11,7 +10,7 @@ class Command:
# This distinction is needed because while transforming the tree, inline # This distinction is needed because while transforming the tree, inline
# elements cannot be replaced with block ones # elements cannot be replaced with block ones
class InlineCommand(Span, Command): class InlineCommand(Span, Command):
def replaceSelf(self, content: List[Element]) -> Span: def replaceSelf(self, content: list[Element]) -> Span:
try: try:
return Span(*content) return Span(*content)
except TypeError: except TypeError:
@ -22,7 +21,7 @@ class InlineCommand(Span, Command):
pass pass
class BlockCommand(Div, Command): class BlockCommand(Div, Command):
def replaceSelf(self, content: List[Element]) -> Div: def replaceSelf(self, content: list[Element]) -> Div:
return Div(*content) return Div(*content)
pass pass
@ -58,7 +57,7 @@ def handle_command_define(e: Element, c: Context):
# #
# These two types, appending and printing, cannot be mixed. # These two types, appending and printing, cannot be mixed.
def executeCommand(source, element: Element, ctx: Context) -> List[Element]: def executeCommand(source, element: Element, ctx: Context) -> list[Element]:
mode = 'empty' mode = 'empty'
text = "" text = ""
content = [] content = []
@ -79,7 +78,7 @@ def executeCommand(source, element: Element, ctx: Context) -> List[Element]:
mode = 'elements' mode = 'elements'
content.append(e) content.append(e)
def appendChildren(l: List[Element]): def appendChildren(l: list[Element]):
for e in l: for e in l:
appendChild(e) appendChild(e)

10
src/formatitko/html_generator.py

@ -2,7 +2,7 @@ from panflute import Cite, Emph, Image, LineBreak, Link, Math, Note, RawInline,
from panflute import BulletList, Citation, CodeBlock, Definition, DefinitionItem, DefinitionList, Header, HorizontalRule, LineBlock, LineItem, ListItem, Null, OrderedList, Para, Plain, RawBlock, TableBody, TableFoot, TableHead from panflute import BulletList, Citation, CodeBlock, Definition, DefinitionItem, DefinitionList, Header, HorizontalRule, LineBlock, LineItem, ListItem, Null, OrderedList, Para, Plain, RawBlock, TableBody, TableFoot, TableHead
from panflute import TableRow, TableCell, Caption, Doc from panflute import TableRow, TableCell, Caption, Doc
from panflute import ListContainer, Element from panflute import ListContainer, Element
from typing import Union, Dict from typing import Union
import os import os
import io import io
@ -40,16 +40,16 @@ class HTMLGenerator(OutputGenerator):
# text = text.replace(" ", " ") # Don't replace no-break spaces with HTML escapes, because we trust unicode? # text = text.replace(" ", " ") # Don't replace no-break spaces with HTML escapes, because we trust unicode?
return text return text
def start_tag(self, tag: str, attributes: Dict[str,str]={}) -> str: def start_tag(self, tag: str, attributes: dict[str,str]={}) -> str:
words = [tag] words = [tag]
for key, value in attributes.items(): for key, value in attributes.items():
words.append(f"{key}=\"{self.escape_special_chars(value)}\"") words.append(f"{key}=\"{self.escape_special_chars(value)}\"")
return "<" + " ".join(words) + ">" return "<" + " ".join(words) + ">"
def end_tag(self, tag: str, attributes: Dict[str,str]={}) -> str: def end_tag(self, tag: str, attributes: dict[str,str]={}) -> str:
return "</" + tag + ">" return "</" + tag + ">"
def single_tag(self, tag: str, attributes: Dict[str,str]={}) -> str: def single_tag(self, tag: str, attributes: dict[str,str]={}) -> str:
return self.start_tag(tag, attributes) return self.start_tag(tag, attributes)
def tagname(self, e) -> str: def tagname(self, e) -> str:
@ -82,7 +82,7 @@ class HTMLGenerator(OutputGenerator):
except KeyError: except KeyError:
return type(e).__name__.lower() return type(e).__name__.lower()
def common_attributes(self, e) -> Dict[str,str]: def common_attributes(self, e) -> dict[str,str]:
attributes = {} attributes = {}
if hasattr(e, "identifier") and e.identifier != "": if hasattr(e, "identifier") and e.identifier != "":
attributes["id"] = e.identifier attributes["id"] = e.identifier

12
src/formatitko/images.py

@ -1,4 +1,4 @@
from typing import List, Union, Tuple from typing import Union
import os import os
import shutil import shutil
import subprocess import subprocess
@ -14,7 +14,7 @@ class ImageMagickError(Exception):
pass pass
class ImageProcessor: class ImageProcessor:
def __init__(self, public_dir: str, web_path: str, cache_dir: str, *lookup_dirs: List[str]): def __init__(self, public_dir: str, web_path: str, cache_dir: str, *lookup_dirs: list[str]):
self.public_dir = public_dir self.public_dir = public_dir
self.cache_dir = cache_dir self.cache_dir = cache_dir
self.lookup_dirs = lookup_dirs self.lookup_dirs = lookup_dirs
@ -24,7 +24,7 @@ class ImageProcessor:
if not os.path.exists(self.cache_dir): if not os.path.exists(self.cache_dir):
os.mkdir(self.cache_dir) os.mkdir(self.cache_dir)
def process_image(self, input_filename: str, format: str, source_dir: str, width: int=None, height:int=None, quality: int=None, dpi: int=None, fit: bool=True, deps: List[str]=[]) -> str: def process_image(self, input_filename: str, format: str, source_dir: str, width: int=None, height:int=None, quality: int=None, dpi: int=None, fit: bool=True, deps: list[str]=[]) -> str:
name = os.path.basename(input_filename) name = os.path.basename(input_filename)
base, ext = os.path.splitext(name) base, ext = os.path.splitext(name)
ext = ext[1:] ext = ext[1:]
@ -85,7 +85,7 @@ class ImageProcessor:
return target_name return target_name
def is_outdated(self, target: str, deps: List[str]): def is_outdated(self, target: str, deps: list[str]):
target_timestamp = os.path.getmtime(target) target_timestamp = os.path.getmtime(target)
for dep in deps: for dep in deps:
dep_timestamp = os.path.getmtime(dep) dep_timestamp = os.path.getmtime(dep)
@ -113,7 +113,7 @@ class ImageProcessor:
raise e raise e
return target_name if relative else target_path return target_name if relative else target_path
def get_image_size(self, input_filename: str, additional_dirs: List[str]=[]) -> Tuple[int, int]: def get_image_size(self, input_filename: str, additional_dirs: list[str]=[]) -> tuple[int, int]:
full_path = self.find_image(input_filename, additional_dirs) full_path = self.find_image(input_filename, additional_dirs)
if full_path is None: if full_path is None:
raise FileNotFoundError(f'Image {input_filename} not found.') raise FileNotFoundError(f'Image {input_filename} not found.')
@ -121,7 +121,7 @@ class ImageProcessor:
return Image.open(full_path).size return Image.open(full_path).size
def find_image(self, input_filename: str, additional_dirs: List[str]=[]) -> Union[str, None]: def find_image(self, input_filename: str, additional_dirs: list[str]=[]) -> Union[str, None]:
for dir in [*self.lookup_dirs, *additional_dirs]: for dir in [*self.lookup_dirs, *additional_dirs]:
if os.path.isfile(dir + "/" + input_filename): if os.path.isfile(dir + "/" + input_filename):
return dir + "/" + input_filename return dir + "/" + input_filename

4
src/formatitko/katex.py

@ -3,8 +3,6 @@ import subprocess
import tempfile import tempfile
import json import json
import os import os
from typing import Dict
class KatexError(Exception): class KatexError(Exception):
pass pass
@ -45,7 +43,7 @@ class KatexClient:
self._client.connect(self._socket_file) self._client.connect(self._socket_file)
def render(self, tex: str, options: Dict={}): def render(self, tex: str, options: dict={}):
# Send formulas to translate # Send formulas to translate
self._client.sendall((json.dumps({"formulas":[{"tex":tex}], "options":options})+"\n").encode("utf-8")) self._client.sendall((json.dumps({"formulas":[{"tex":tex}], "options":options})+"\n").encode("utf-8"))

8
src/formatitko/latex_generator.py

@ -2,7 +2,7 @@ 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 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 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 TableRow, TableCell, Caption, Doc
from typing import Union, Dict from typing import Union
from .whitespace import NBSP from .whitespace import NBSP
from .transform import FQuoted from .transform import FQuoted
@ -34,13 +34,13 @@ class LaTeXGenerator(OutputGenerator):
text = text.replace(" ", "~") # We use unicode no-break spaces to force nbsp in output text = text.replace(" ", "~") # We use unicode no-break spaces to force nbsp in output
return text return text
def start_tag(self, tag: str, attributes: Dict[str,str]={}) -> str: def start_tag(self, tag: str, attributes: dict[str,str]={}) -> str:
return "\\" + tag + "{" return "\\" + tag + "{"
def end_tag(self, tag: str, attributes: Dict[str,str]={}) -> str: def end_tag(self, tag: str, attributes: dict[str,str]={}) -> str:
return "}" return "}"
def single_tag(self, tag: str, attributes: Dict[str,str]={}) -> str: def single_tag(self, tag: str, attributes: dict[str,str]={}) -> str:
return "\\" + tag + "{}" return "\\" + tag + "{}"

160
src/formatitko/output_generator.py

@ -2,7 +2,7 @@ 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 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 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 TableRow, TableCell, Caption, Doc
from typing import Union, Dict, List from typing import Union
from .whitespace import NBSP from .whitespace import NBSP
from .transform import FQuoted from .transform import FQuoted
@ -20,9 +20,74 @@ class OutputGenerator:
self.indent_str = indent_str self.indent_str = indent_str
self.indent_level = initial_indent_level self.indent_level = initial_indent_level
self._at_start_of_line = True self._at_start_of_line = True
def generate(self, e: Union[Element, ListContainer, List[Union[Element, ListContainer]]]): self.TYPE_DICT_MISC = {
if isinstance(e, List): 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,
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
}
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
}
def generate(self, e: Union[Element, ListContainer, list[Union[Element, ListContainer]]]):
if isinstance(e, list):
for el in e: for el in e:
self.generate(el) self.generate(el)
elif isinstance(e, ListContainer): elif isinstance(e, ListContainer):
@ -33,14 +98,7 @@ class OutputGenerator:
self.generate_Block(e) self.generate_Block(e)
else: else:
try: try:
{ self.TYPE_DICT_MISC[type(e)](e)
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: except KeyError:
raise UnknownElementError(type(e)) raise UnknownElementError(type(e))
@ -80,22 +138,22 @@ class OutputGenerator:
self.output_file.write("\n") self.output_file.write("\n")
self._at_start_of_line = True self._at_start_of_line = True
def start_tag(self, tag: str, attributes: Dict[str,str]={}) -> str: def start_tag(self, tag: str, attributes: dict[str,str]={}) -> str:
return tag return tag
def end_tag(self, tag: str, attributes: Dict[str,str]={}) -> str: def end_tag(self, tag: str, attributes: dict[str,str]={}) -> str:
return "/" + tag return "/" + tag
def single_tag(self, tag: str, attributes: Dict[str,str]={}) -> str: def single_tag(self, tag: str, attributes: dict[str,str]={}) -> str:
return "/" + tag + "/" return "/" + tag + "/"
def tagname(self, e) -> str: def tagname(self, e) -> str:
return type(e).__name__ return type(e).__name__
def common_attributes(self, e: Element) -> Dict[str,str]: def common_attributes(self, e: Element) -> dict[str,str]:
return {} 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): 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: if not tag and e:
tag = self.tagname(e) tag = self.tagname(e)
if attributes is None and e: if attributes is None and e:
@ -124,29 +182,29 @@ class OutputGenerator:
else: else:
self.generate_simple_block_tag(tag, content, attributes) 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]={}): 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.write(self.start_tag(tag, attributes))
self.generate(content) self.generate(content)
self.write(self.end_tag(tag)) 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]={}): 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.writeln(self.start_tag(tag, attributes))
self.indent_more() self.indent_more()
self.generate(content) self.generate(content)
self.indent_less() self.indent_less()
self.writeln(self.end_tag(tag)) self.writeln(self.end_tag(tag))
def generate_raw_inline_tag(self, tag: str, text: str, attributes: Dict[str,str]={}): def generate_raw_inline_tag(self, tag: str, text: str, attributes: dict[str,str]={}):
self.write(self.start_tag(tag, attributes)) self.write(self.start_tag(tag, attributes))
self.write(text) self.write(text)
self.write(self.end_tag(tag)) self.write(self.end_tag(tag))
def generate_raw_block_tag(self, tag: str, text: str, attributes: Dict[str,str]={}): def generate_raw_block_tag(self, tag: str, text: str, attributes: dict[str,str]={}):
self.writeln(self.start_tag(tag, attributes)) self.writeln(self.start_tag(tag, attributes))
self.writeraw(text) self.writeraw(text)
self.writeln(self.end_tag(tag)) self.writeln(self.end_tag(tag))
def generate_empty_block_tag(self, tag: str, attributes: Dict[str,str]={}): def generate_empty_block_tag(self, tag: str, attributes: dict[str,str]={}):
self.writeln(self.single_tag(tag, attributes)) self.writeln(self.single_tag(tag, attributes))
def generate_ListContainer(self, e: ListContainer): def generate_ListContainer(self, e: ListContainer):
@ -154,30 +212,7 @@ class OutputGenerator:
self.generate(child) self.generate(child)
def generate_Inline(self, e: Inline): def generate_Inline(self, e: Inline):
{ self.TYPE_DICT_INLINE[type(e)](e)
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: Str): def generate_Str(self, e: Str):
self.write(self.escape_special_chars(e.text)) self.write(self.escape_special_chars(e.text))
@ -278,36 +313,7 @@ class OutputGenerator:
def generate_Block(self, e: Block): def generate_Block(self, e: Block):
{ self.TYPE_DICT_BLOCK[type(e)](e)
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)
# Block elements # Block elements
@ -420,7 +426,7 @@ class OutputGenerator:
self.generate_simple_tag(e) self.generate_simple_tag(e)
# Maybe move this to ImageProcessor? # Maybe move this to ImageProcessor?
def get_image_processor_args(self, attributes:Dict[str,str]) -> Dict: def get_image_processor_args(self, attributes:dict[str,str]) -> dict:
# Attributes → image processor args # Attributes → image processor args
additional_args = {} additional_args = {}
if "file-width" in attributes: if "file-width" in attributes:

Loading…
Cancel
Save