from panflute import Div,Span,Para from typing import List # Import local files from util import * from context import Context from mj_show import show class Command: pass # This distinction is needed because while transforming the tree, inline # elements cannot be replaced with block ones class InlineCommand(Span, Command): def replaceSelf(self, content: List[Element]) -> Span: try: return Span(*content) except TypeError: if len(content) == 1 and isinstance(content[0], Para): return Span(*content[0].content) else: raise SyntaxError(f"The command {self.attributes['c']} returned multiple Paragraphs and must be executed using `::: {{c={self.attributes['c']}}}\\n:::`.") pass class BlockCommand(Div, Command): def replaceSelf(self, content: List[Element]) -> Div: return Div(*content) pass # This function is called in trasform.py, defining a command which can be # called later using the function below def handle_command_define(e: Element, c: Context): if "define" in e.attributes: if not c.get_command(e.attributes["define"]): c.set_command(e.attributes["define"], compile(e.text, '', 'exec')) return nullify(e) else: raise NameError(f"Command already defined: '{e.attributes['define']}'") if "redefine" in e.attributes: c.set_command(e.attributes["redefine"], compile(e.text, '', 'exec')) return nullify(e) return e # This function executes commands and inline runnable code blocks (see # transform.py for their syntax). Context can be accessed using `ctx` and there # are four functions available to create output from these commands and the # element the command has been called on (including its .content) can be # accessed using `element`. Arguments can be passed down to the comand using # the element's attributes. # # print and println append text to a buffer which is then interpreted as # markdown with the current context. # # appendChild and appendChildren append panflute elements to a list which is # then transformed. A command which does nothing looks like this: # ```python {define=nop} # appendChildren(element.content) # ``` # # These two types, appending and printing, cannot be mixed. def executeCommand(source, element: Element, ctx: Context) -> List[Element]: mode = 'empty' text = "" content = [] def print(s: str=""): nonlocal mode, text if mode == 'elements': raise SyntaxError("Cannot use `print` and `appendChild` in one command at the same time.") mode = 'text' text += s def println(s: str=""): print(s+"\n") def appendChild(e: Element): nonlocal mode, content if mode == 'text': raise SyntaxError("Cannot use `print` and `appendChild` in one command at the same time.") mode = 'elements' content.append(e) def appendChildren(l: List[Element]): for e in l: appendChild(e) import panflute as pf exec(source) if mode == 'text': return import_md(text, standalone=False) if mode == 'elements': return content return []