formatitko/command.py

96 lines
2.9 KiB
Python
Raw Normal View History

from panflute import Div,Span,Para
from typing import List
2022-11-24 16:57:54 +01:00
# Import local files
from util import *
from context import Context
from mj_show import show
2022-11-24 16:57:54 +01:00
class Command:
pass
# This distinction is needed because while transforming the tree, inline
# elements cannot be replaced with block ones
2022-11-24 16:57:54 +01:00
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:
2023-01-30 01:46:50 +01:00
raise SyntaxError(f"The command {self.attributes['c']} returned multiple Paragraphs and must be executed using `::: {{c={self.attributes['c']}}}\\n:::`.")
2022-11-24 16:57:54 +01:00
pass
2023-01-31 00:44:48 +01:00
class BlockCommand(Div, Command):
def replaceSelf(self, content: List[Element]) -> Div:
return Div(*content)
2022-11-24 16:57:54 +01:00
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 += str(s)
def println(s: str=""):
print(str(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 []