You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
95 lines
2.9 KiB
95 lines
2.9 KiB
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, '<string>', '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, '<string>', '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 []
|
|
|