diff --git a/src/formatitko/html.py b/src/formatitko/html.py deleted file mode 100644 index 0403082..0000000 --- a/src/formatitko/html.py +++ /dev/null @@ -1,311 +0,0 @@ -from panflute import * -from pygments import highlight -from pygments.lexers import get_lexer_by_name -from pygments.formatters import HtmlFormatter -from pygments.util import ClassNotFound -import os -from typing import Union - -from .whitespace import NBSP -from .elements import FQuoted -from .katex import KatexClient -from .util import inlinify -from .context import Group -from .images import ImageProcessor - -import warnings -warnings.warn("The html function has been deprecated, is left only for reference and will be removed in future commits. HTML_generator should be used in its place.", DeprecationWarning) - -def html(e: Union[Element, ListContainer], k: KatexClient, i: ImageProcessor, indent_level: int=0, indent_str: str="\t") -> str: - - warnings.warn("The html function has been deprecated, is left only for reference and will be removed in future commits. HTML_generator should be used in its place.", DeprecationWarning) - - # `only` attribute which makes transformed elements appear only in tex - # output or html output - if hasattr(e, "attributes") and "only" in e.attributes and e.attributes["only"] != "html": - return "" - - if isinstance(e, ListContainer): - return ''.join([html(child, k, i, indent_level, indent_str) for child in e]) - - # Bits from which the final element output is built at the end of this - # function. Most elements override this by returning their own output. - tag = e.tag.lower() - attributes = "" - content_foot = "" - content_head = "" - - if isinstance(e, Str): - return e.text.replace(" ", " ") - - # Most elements fit the general template at the end of the function, just - # need their html tag specified. - tags = { - BulletList: "ul", - Doc: "main", - Emph: "em", - Caption: "figcaption", - Para: "p", - Header: "h"+str(e.level) if isinstance(e, Header) else "", - LineBlock: "p", - ListItem: "li", - SmallCaps: "span", - Strikeout: "strike", - Subscript: "sub", - Superscript: "sup", - Underline: "u", - TableBody: "tbody", - TableHead: "thead", - TableFoot: "tfoot", - TableRow: "tr", - TableCell: "td", - } - if type(e) in tags: - tag = tags[type(e)] - - # These are also disabled in pandoc so they shouldn't appear in the AST at all. - not_implemented = { - Citation: True, - Cite: True, - Definition: True, - DefinitionItem: True, - DefinitionList: True - } - if type(e) in not_implemented: - return f'' - - # Elements which can be represented by a simple string - simple_string = { - NBSP: " ", - Space: " ", - Null: "", - LineBreak: f"\n{indent_level*indent_str}
\n{indent_level*indent_str}", - SoftBreak: f" ", - HorizontalRule: f"{indent_level*indent_str}
\n" - } - if type(e) in simple_string: - return simple_string[type(e)] - - if hasattr(e, "identifier") and e.identifier != "": - attributes += f' id="{e.identifier}"' - - if hasattr(e, "classes") and len(e.classes) != 0: - attributes += f' class="{" ".join(e.classes)}"' - - # Attributes are only passed down manually, because we use them internally. - # Maybe this should be a blocklist instead of an allowlist? - - # Overriding elements with their own returns - if isinstance(e, CodeBlock): - if len(e.classes) > 0 and (e.attributes["highlight"] == True or e.attributes["highlight"] == 'True'): - # Syntax highlighting using pygments - for cl in e.classes: - try: - lexer = get_lexer_by_name(cl) - except ClassNotFound: - continue - break - else: - print(f"WARN: Syntax highligher does not have lexer for element with these classes: {e.classes}") - formatter = HtmlFormatter(style=e.attributes["style"]) - result = highlight(e.text, lexer, formatter) - return f'{result}' - else: - return f'
{e.text}
' - - if isinstance(e, Doc): - formatter = HtmlFormatter(style=e.get_metadata("highlight-style") if e.get_metadata("highlight-style") is not None else "default") - content_head = f'' - - if isinstance(e, Image): - url = e.url - - # Attributes → image processor args - additional_args = {} - if "file-width" in e.attributes: - additional_args["width"] = int(e.attributes["file-width"]) - if "file-height" in e.attributes: - additional_args["height"] = int(e.attributes["file-height"]) - if "file-quality" in e.attributes: - additional_args["quality"] = int(e.attributes["file-quality"]) - if "file-dpi" in e.attributes: - additional_args["dpi"] = int(e.attributes["file-dpi"]) - - # The directory of the current file, will also look for images there. - source_dir = e.attributes["source_dir"] - - _, ext = os.path.splitext(url) - ext = ext[1:] - - # Conversions between various formats. - if ext in ["svg", "png", "jpeg", "gif"]: - # Even supported elements have to be 'converted' because the - # processing contains finding and moving them to the output - # directory. - url = i.process_image(url, ext, source_dir, **additional_args) - elif ext in ["pdf", "epdf"]: - if not "dpi" in additional_args: - additional_args["dpi"] = 300 - url = i.process_image(url, "png", source_dir, **additional_args) - elif ext in ["jpg"]: - url = i.process_image(url, "jpeg", source_dir, **additional_args) - else: - url = i.process_image(url, "png", source_dir, **additional_args) - - # Srcset generation - multiple alternative sizes of images browsers can - # choose from. - _, ext = os.path.splitext(url) - ext = ext[1:] - srcset = [] - if ext in ["png", "jpeg"] and (not "no-srcset" in e.attributes or e.attributes["no-srcset"] == False or e.attributes["no-srcset"] == 'False'): - # This is inspired by @vojta001's blogPhoto shortcode he made for - # patek.cz: - # https://gitlab.com/patek-devs/patek.cz/-/blob/master/themes/patek/layouts/shortcodes/blogPhoto.html - width, height = i.get_image_size(url, [i.public_dir]) - sizes = [(640, 360, 85), (1280, 720, 85), (1920, 1080, 90)] # (widht, height, quality) - for size in sizes: - if width <= size[0] and height <= size[1]: - srcset.append((f'{i.web_path}/{url}', f'{width}w')) - break - quality = size[2] if ext == "jpeg" else None - srcset.append((f'{i.web_path}/{i.process_image(url, ext, i.public_dir, width=size[0], height=size[1], quality=quality)}', f'{size[0]}w')) - - url = i.web_path + "/" + url - - attributes = f'{" style=width:"+e.attributes["width"] if "width" in e.attributes else ""} alt="{e.title or html(e.content, k, i, 0, "")}"' - if len(srcset) != 0: - return f'' - else: - return f'' - - # See https://pandoc.org/MANUAL.html#line-blocks - if isinstance(e, LineItem): - return indent_level*indent_str + html(e.content, k, i) + "
\n" - - # Footnotes are placed into parentheses. (And not footnotes (This is how KSP did it before me)) - if isinstance(e, Note): - content_head = "(" - content_foot = ")" - if inlinify(e) is not None: - return f' ({html(inlinify(e), k, i, 0, "")})' - - if isinstance(e, FQuoted): - if e.style == "cs": - if e.quote_type == "SingleQuote": - return f'‚{html(e.content, k, i, 0, "")}‘' - elif e.quote_type == "DoubleQuote": - return f'„{html(e.content, k, i, 0, "")}“' - elif e.style == "en": - if e.quote_type == "SingleQuote": - return f'‘{html(e.content, k, i, 0, "")}’' - elif e.quote_type == "DoubleQuote": - return f'“{html(e.content, k, i, 0, "")}”' - else: - if e.quote_type == "SingleQuote": - return f'\'{html(e.content, k, i, 0, "")}\'' - elif e.quote_type == "DoubleQuote": - return f'"{html(e.content, k, i, 0, "")}"' - else: - return f'"{html(e.content, k, i, 0, "")}"' - - if isinstance(e, Group): - k.begingroup() - ret = html(e.content, k, i, indent_level, indent_str) - k.endgroup() - return ret - - if isinstance(e, Math): - formats = { - "DisplayMath": True, - "InlineMath": False - } - return indent_level*indent_str + k.render(e.text, {"displayMode": formats[e.format]}) - - if isinstance(e, RawInline): - if e.format == "html": - return e.text - else: - return "" - - if isinstance(e, RawBlock): - if e.format == "html": - return f'{e.text}\n' - else: - return "" - - - # Non-overriding elements, they get generated using the template at the end - # of this function - if isinstance(e, Header): - tag = "h"+str(e.level) - - if isinstance(e, Figure): - content_foot = html(e.caption, k, i, indent_level+1, indent_str) - - if isinstance(e, Caption): - tag = "figcaption" - - if isinstance(e, Link): - tag = "a" - attributes += f' href="{e.url}"' - if e.title: - attributes += f' title="{e.title}"' - - if isinstance(e, OrderedList): - tag = "ol" - if e.start and e.start != 1: - attributes += f' start="{e.start}"' - html_styles = { - "Decimal": "1", - "LowerRoman": "i", - "UpperRoman:": "I", - "LowerAlpha": "a", - "UpperAlpha": "A" - } - if e.style and e.style != "DefaultStyle": - attributes += f' type="{html_styles[e.style]}"' - # FIXME: Delimeter styles - - if isinstance(e, Table): - content_head = html(e.head, k, i, indent_level+1, indent_str) - content_foot = html(e.foot, k, i, indent_level+1, indent_str) - # FIXME: Fancy pandoc tables, using colspec - - if isinstance(e, TableCell): - tag = "td" - if e.colspan != 1: - attributes += f' colspan="{e.colspan}"' - if e.rowspan != 1: - attributes += f' rowspan="{e.rowspan}"' - aligns = { - "AlignLeft": "left", - "AlignRight": "right", - "AlignCenter": "center" - } - if e.alignment and e.alignment != "AlignDefault": - attributes += f' style="text-align: {aligns[e.alignment]}"' - - # The default which all non-overriding elements get generated by. This - # includes elements, which were not explicitly mentioned in this function, - # e. g. Strong - - if isinstance(e, Inline): - return f'<{tag}{attributes}>{content_head}{html(e.content, k, i, 0, "") if hasattr(e, "_content") else ""}{e.text if hasattr(e, "text") else ""}{content_foot}' - - out_str = "" - if not isinstance(e, Plain): - out_str += f"{indent_level*indent_str}<{tag}{attributes}>\n" - out_str += content_head - if hasattr(e, "_content"): - if len(e.content) > 0 and isinstance(e.content[0], Inline): - out_str += (indent_level+1)*indent_str - out_str += html(e.content, k, i, indent_level+1, indent_str) - if hasattr(e, "text"): - out_str += e.text - out_str += f"{content_foot}\n" - if not isinstance(e, Plain): - out_str += f"{indent_level*indent_str}\n" - - return out_str - - diff --git a/src/formatitko/tex.py b/src/formatitko/tex.py deleted file mode 100644 index 74b6e6f..0000000 --- a/src/formatitko/tex.py +++ /dev/null @@ -1,270 +0,0 @@ -from panflute import * -import os -from typing import Union - -from .whitespace import NBSP -from .elements import FQuoted -from .util import inlinify -from .context import Group -from .images import ImageProcessor - -# Heavily inspired by: git://git.ucw.cz/labsconf2022.git -def tex(e: Union[Element, ListContainer], i: ImageProcessor, indent_level: int=0, indent_str: str="\t") -> str: - - # `only` attribute which makes transformed elements appear only in tex - # output or html output - if hasattr(e, "attributes") and "only" in e.attributes and e.attributes["only"] != "tex": - return "" - - if isinstance(e, ListContainer): - return ''.join([tex(child, i, indent_level, indent_str) for child in e]) - - # Bits from which the final element output is built at the end of this - # function. Most elements override this by returning their own output. - content_foot = "" - content_head = "" - - arguments = "" - open = "{" - close = "}" - - tag = e.tag.lower() - - tags = { - Header: "h"+chr(64 + e.level) if isinstance(e, Header) else "", - } - if type(e) in tags: - tag = tags[type(e)] - - # These are also disabled in pandoc so they shouldn't appear in the AST at all. - not_implemented = { - Citation: True, - Cite: True, - Definition: True, - DefinitionItem: True, - DefinitionList: True - } - if type(e) in not_implemented: - return f'% FIXME: {type(e)}s not implemented \n' - - # Elements which can be represented by a simple string - simple_string = { - NBSP: "~", - Space: " ", - Null: "", - LineBreak: f"\\\\", - SoftBreak: f" ", - HorizontalRule: "\\hr\n\n" - } - if type(e) in simple_string: - return simple_string[type(e)] - - # Simplest basic elements - if isinstance(e, Str): - return e.text.replace(" ", "~") - - if isinstance(e, Para): - return tex(e.content, i, 0, "")+"\n\n" - - if isinstance(e, Span) or isinstance(e, Plain): - return tex(e.content, i, 0, "") - - # Overriding elements with their own returns - if isinstance(e, Image): - url = e.url - - # TODO: This should use OutputGenerator's get_image_processor_args - # Attributes → image processor args - additional_args = {} - if "file-width" in e.attributes: - additional_args["width"] = int(e.attributes["file-width"]) - if "file-height" in e.attributes: - additional_args["height"] = int(e.attributes["file-height"]) - if "file-quality" in e.attributes: - additional_args["quality"] = int(e.attributes["file-quality"]) - if "file-dpi" in e.attributes: - additional_args["dpi"] = int(e.attributes["file-dpi"]) - - # The directory of the current file, will also look for images there. - source_dir = e.attributes["source_dir"] - - _, ext = os.path.splitext(url) - ext = ext[1:] - - # Conversions between various formats. - if ext in ["pdf", "png", "jpeg"]: - # Even supported elements have to be 'converted' because the - # processing contains finding and moving them to the cache - # directory. - url = i.process_image(url, ext, source_dir, **additional_args) - elif ext in ["svg"]: - url = i.process_image(url, "pdf", source_dir, **additional_args) - elif ext in ["epdf"]: - url = i.process_image(url, "pdf", source_dir, **additional_args) - elif ext in ["jpg"]: - url = i.process_image(url, "jpeg", source_dir, **additional_args) - else: - url = i.process_image(url, "pdf", source_dir, **additional_args) - - url = i.find_image(url, [i.cache_dir]) - width = "" - if "width" in e.attributes: - width = e.attributes["width"] - # 50% → 0.5\hsize - if e.attributes["width"][-1] == "%": - width = str(int(e.attributes["width"][:-1])/100) + "\\hsize" - width = "width " + width - return f'\\image{{{width}}}{{{url}}}' - - if isinstance(e, FQuoted): - if e.style == "cs": - if e.quote_type == "SingleQuote": - return f'‚{tex(e.content, i, 0, "")}‘' - elif e.quote_type == "DoubleQuote": - return f'„{tex(e.content, i, 0, "")}“' - elif e.style == "en": - if e.quote_type == "SingleQuote": - return f'‘{tex(e.content, i, 0, "")}’' - elif e.quote_type == "DoubleQuote": - return f'“{tex(e.content, i, 0, "")}”' - else: - if e.quote_type == "SingleQuote": - return f'\'{tex(e.content, i, 0, "")}\'' - elif e.quote_type == "DoubleQuote": - return f'"{tex(e.content, i, 0, "")}"' - else: - return f'"{tex(e.content, i, 0, "")}"' - - if isinstance(e, Code): - return f'\\verb`{e.text.replace("`", "backtick")}`' - - if isinstance(e, Figure): - return f'\\figure{{{tex(e.content, i, indent_level+1, indent_str)}}}{{{tex(e.caption, i, indent_level+1, indent_str)}}}\n\n' - - # Figure caption - if isinstance(e, Caption): - if inlinify(e) is not None: - return f'\\figcaption{{{tex(e.content, i, 0, "")}}}' - - if isinstance(e, Math): - if e.format == "DisplayMath": - return f'$${e.text}$$\n' - else: - return f'${e.text}$' - - # Footnote - if isinstance(e, Note): - tag = "fn" - if inlinify(e) is not None: - return f'\\fn{{{tex(inlinify(e), i, 0, "")}}}' - - if isinstance(e, Table): - aligns = { - "AlignLeft": "\\quad#\\quad\\hfil", - "AlignRight": "\\quad\\hfil#\\quad", - "AlignCenter": "\\quad\\hfil#\\hfil\\quad", - "AlignDefault": "\\quad#\\quad\\hfil" - } - text = "\strut"+"&".join([aligns[col[0]] for col in e.colspec])+"\cr\n" - text += tex(e.head.content, i, 0, "") - text += "\\noalign{\\hrule}\n" - text += tex(e.content[0].content, i, 0, "") - text += "\\noalign{\\hrule}\n" - text += tex(e.foot.content, i, 0, "") - return "\\vskip1em\n\\halign{"+text+"}\n\\vskip1em\n" - # FIXME: Implement rowspan - - if isinstance(e, TableRow): - return "&".join([("\\multispan"+str(cell.colspan)+" " if cell.colspan > 1 else "")+tex(cell.content, i, 0, "") for cell in e.content])+"\cr\n" - - if isinstance(e, RawInline): - if e.format == "tex": - return e.text - else: - return "" - - if isinstance(e, RawBlock): - if e.format == "tex": - return f'{e.text}\n' - else: - return "" - - # See https://pandoc.org/MANUAL.html#line-blocks - if isinstance(e, LineBlock): - return f'{tex(e.content, i, indent_level+1, indent_str)}\n' - - if isinstance(e, LineItem): - return tex(e.content, i, 0, "") + ("\\\\\n" if e.next else "\n") - - if type(e) is Div: - return f'{tex(e.content, i, indent_level+1, indent_str)}' - - if isinstance(e, Doc): - return tex(e.content, i, indent_level, indent_str)+"\n\\bye" # Is having the \bye a bad idea here? - - - # Non-overriding elements, they get generated using the template at the end - # of this function - if isinstance(e, BulletList): - tag = "list" - open = "" - arguments = "{o}" - close = "\\endlist" - - elif isinstance(e, OrderedList): - tag = "list" - open = "" - styles = { - "DefaultStyle": "n", - "Decimal": "n", - "LowerRoman": "i", - "UpperRoman:": "I", - "LowerAlpha": "a", - "UpperAlpha": "A" - } - style = styles[e.style] - delimiters = { - "DefaultDelim": f"{style}.", - "Period": f"{style}.", - "OneParen": f"{style})", - "TwoParens": f"({style})" - } - style = delimiters[e.delimiter] - arguments = f"{{{style}}}" - close = "\\endlist" - # FIXME: Starting number of list - - elif isinstance(e, ListItem): - tag = ":" - - elif isinstance(e, Link): - if len(e.content) == 1 and isinstance(e.content[0], Str) and e.content[0].text == e.url: - tag = "url" - else: - tag = "linkurl" - arguments = f'{{{e.url}}}' - - elif isinstance(e, Group): - tag = "begingroup" - open = "" - if "lang" in e.metadata and e.metadata["lang"] is not None: - open = "\\language"+e.metadata["lang"] - close = "\\endgroup" - - # The default which all non-overriding elements get generated by. This - # includes elements, which were not explicitly mentioned in this function, - # e. g. Strong, Emph... - - if isinstance(e, Inline): - return f'\\{tag}{arguments}{open}{content_head}{tex(e.content, i, 0, "") if hasattr(e, "_content") else ""}{e.text if hasattr(e, "text") else ""}{content_foot}{close}' - - out_str = "" - out_str = f"\\{tag}{arguments}{open}\n" - out_str += content_head - if hasattr(e, "_content"): - out_str += tex(e.content, i, indent_level+1, indent_str) - if hasattr(e, "text"): - out_str += e.text - out_str += f"{content_foot}\n{close}\n\n" - - return out_str diff --git a/src/formatitko/transform.py b/src/formatitko/transform.py deleted file mode 100644 index 69679d9..0000000 --- a/src/formatitko/transform.py +++ /dev/null @@ -1,176 +0,0 @@ -from panflute import Element, Div, Span, Quoted, Image, CodeBlock, Str, MetaInlines, MetaString, MetaBool, RawBlock -import re -import os - -# Import local files -from .whitespace import Whitespace, NBSP, bavlna -from .util import nullify, import_md -from .context import Context, BlockGroup -from .command import Command, BlockCommand, InlineCommand -from .command_util import handle_command_define, parse_command -from .elements import FQuoted - - -import warnings -warnings.warn("The transform function has been deprecated, is left only for reference and will be removed in future commits. TransformProcessor should be used in its place.", DeprecationWarning) - -# This is where tha magic happens. This function transforms a single element, -# to transform the entire tree, panflute's walk should be used. -def transform(e: Element, c: Context) -> Element: - - warnings.warn("The transform function has been deprecated, is left only for reference and will be removed in future commits. TransformProcessor should be used in its place.", DeprecationWarning) - # Determine if this space should be non-breakable. See whitespace.py. - if isinstance(e, Whitespace) and bavlna(e, c): - e = NBSP() - - if hasattr(e, "attributes"): - # `if` attribute. Only show this element if flag is set. - if "if" in e.attributes: - if not c.is_flag_set(e.attributes["if"]): - return nullify(e) - # `ifn` attribute. Only show this element if flag is NOT set - if "ifn" in e.attributes: - if c.is_flag_set(e.attributes["ifn"]): - return nullify(e) - - # There are multiple ways to call a command so we turn it into a - # unified element first and then call it at the end. This handles the - # []{c=commandname} and - # :::{c=commandname} - # ::: - # syntax. - if (isinstance(e, Div) or isinstance(e, Span)) and "c" in e.attributes: - if isinstance(e, Div): - e = BlockCommand(*e.content, identifier=e.identifier, classes=e.classes, attributes=e.attributes) - else: - e = InlineCommand(*e.content, identifier=e.identifier, classes=e.classes, attributes=e.attributes) - - # Isolated subdocuments using Group and a different Context. Can be - # separate files (using attribute `partial`) or be inline using the - # following syntax: - # ```markdown {.group} - # * file content * - # ``` - # Both can contain their own metadata in a FrontMatter (YAML header) - if (isinstance(e, Div) and "partial" in e.attributes)\ - or (isinstance(e, CodeBlock) and "markdown" in e.classes and "group" in e.classes): - if isinstance(e, Div): - if not c.trusted: # If we're in an untrusted context, we shouldn't allow inclusion of files outside the PWD. - full_path = os.path.abspath(c.dir + "/" + e.attributes["partial"]) - pwd = os.path.abspath(".") - if os.path.commonpath([full_path, pwd]) != os.path.commonpath([pwd]): - return nullify(e) - text = open(c.dir + "/" + e.attributes["partial"], "r").read() - path = c.dir + "/" + e.attributes["partial"] - else: - text = e.text - path = c.path - if "type" in e.attributes and e.attributes["type"] in ["tex", "html"]: - e = RawBlock(text, e.attributes["type"]) - else: - includedDoc = import_md(text) - trusted = True - if "untrusted" in e.attributes and (e.attributes["untrusted"] == True or e.attributes["untrusted"] == 'True'): - trusted = False - if not c.trusted: - trusted = False - nContext = Context(includedDoc, path, c, trusted=trusted) - language = includedDoc.get_metadata("lang") - includedDoc = includedDoc.walk(transform, nContext) - e = BlockGroup(*includedDoc.content, context=nContext, metadata={"lang": language}) - - # Transform panflute's Quoted to custom FQuoted, see above. - if isinstance(e, Quoted): - quote_styles = { - "cs": "cs", - "en": "en", - "sk": "cs", - None: None - } - e = FQuoted(*e.content, quote_type=e.quote_type, style=quote_styles[c.get_metadata("lang")]) - - if isinstance(e, Image): - # Pass down the directory of the current source file for finding image - # files. - e.attributes["source_dir"] = c.dir - # Pass down "no-srcset" metadatum as attribute down to images. - if not "no-srcset" in e.attributes: - e.attributes["no-srcset"] = c.get_metadata("no-srcset") if c.get_metadata("no-srcset") is not None else False - - # Pass down metadata 'highlight' and 'highlight_style' as attribute to CodeBlocks - if isinstance(e, CodeBlock): - if not "highlight" in e.attributes: - e.attributes["highlight"] = c.get_metadata("highlight") if c.get_metadata("highlight") is not None else True - if not "style" in e.attributes: - e.attributes["style"] = c.get_metadata("highlight-style") if c.get_metadata("highlight-style") is not None else "default" - e.attributes["noclasses"] = False - # I think this is supposed to enable inline styles for highlighting when the style differs from the document, but it clearly doesn't work. a) HTML_generator never accesses it and b) Only the top-level document contains a style so you have to ask the top level context, not the current context. - else: - e.attributes["noclasses"] = True - - # Execute python code inside source code block. Works the same as commands. - # Syntax: - # ```python {.run} - # print("woo") - # ``` - if isinstance(e, CodeBlock) and hasattr(e, "classes") and "python" in e.classes and "run" in e.classes: - if not c.trusted: - return nullify(e) - command_output = parse_command(e.text)(BlockCommand(), c) - e = BlockCommand().replaceSelf(*([] if command_output is None else command_output)) - e = e.walk(transform, c) - - # Command defines for calling using BlockCommand and InlineCommand. If - # redefine is used instead of define, the program doesn't check if the - # command already exists. - # Syntax: - # ```python {define=commandname} - # print(wooo) - # ``` - if isinstance(e, CodeBlock) and hasattr(e, "classes") and "python" in e.classes and hasattr(e, "attributes")\ - and ("define" in e.attributes or "redefine" in e.attributes): - if not c.trusted: - return nullify(e) - e = handle_command_define(e, c) - - ## Shorthands - # Shorter (and sometimes the only) forms of certain features - if isinstance(e, Span) and len(e.content) == 1 and isinstance(e.content[0], Str): - ## Handle special command shorthand [!commandname]{} - if re.match(r"^![\w.]+$", e.content[0].text): - e = InlineCommand(identifier=e.identifier, classes=e.classes, attributes={**e.attributes, "c": e.content[0].text[1:]}) - - ## Handle import [#path/file.md]{} - # This is the exact opposite of partials. We take the commands, flags - # and metadata but drop the content. - elif re.match(r"^#.+$", e.content[0].text): - importedDoc = import_md(open(c.dir + "/" + e.content[0].text[1:], "r").read()) - importedDoc.walk(transform, c) - return nullify(e) - - ## Handle metadata print [$key1.key2]{} - # This is a shorthand for just printing the content of some metadata. - elif re.match(r"^\$[\w.]+$", e.content[0].text): - val = c.get_metadata(e.content[0].text[1:], False) - if isinstance(val, MetaInlines): - e = Span(*val.content) - e = e.walk(transform, c) - elif isinstance(val, MetaString): - e = Span(Str(val.string)) - elif isinstance(val, MetaBool): - e = Span(Str(str(val.boolean))) - else: - raise TypeError(f"Cannot print value of metadatum '{e.content[0].text[1:]}' of type '{type(val)}'") - - ## Execute commands - # panflute's walk function transforms the children first, then the root - # element, so the content the command receives is already transformed. - # The output from the command is then transformed manually again. - if isinstance(e, Command): - if not c.get_command(e.attributes["c"]): - raise NameError(f"Command not defined '{e.attributes['c']}'.") - command_output = c.get_command(e.attributes["c"])(e, c) - e = e.replaceSelf(*command_output) - e = e.walk(transform, c) - - return e