Creating plugins
Live preview + developing plugins
Live preview will also watch the implementation of your Python plugin implementations. 🙂
Life is too short for slow dev/test loops.
Using Zig
Some of the built-in plugins also have Zig implementations, but this is not yet supported for custom plugins.
Using Python
Note: your Python will be executed via a new Python interpreter that is included within the main WASM blob.
Lifecycle Hooks
onDocParsed(doc: Doc, c: Context)
afterAllDocsRendered(c: Context)
Doc
doc.pid → str # "path in docset"
doc.docset → Docset
doc.page_pathname → str
doc.page_url → str
doc.root → Node
doc.md → str
Docset
docset.docWithPid(…) → Doc
docset.fileWithPid(…) → bytes
Context
c.addInlineCSS(…)
c.addInlineJS(…)
c.addHeadTag(…)
c.setPageLang(…)
c.addFileToZip(pizo=, file_bytes=)
c.assetFromExternalSubproc(…) → AssetRef
c.assetFromExternalRequest(…) → AssetRef
c.assetFromExternalRender(…) → AssetRef
# ^ These return immediately, and the
# process happens in the background
c.escapeHTML(…)
c.finalHtmlBytesForPagePathname(…)
# ^ (only during `afterAllDocsRendered`)
c.log(…)
c.warn(…)
c.error(…)
# ^ TODO: make a diagram like in
# "The Birth and Death of JavaScript",
# showing how many layers of abstraction
# these logs need to bubble up through
# to appear on your live preview page
AssetRef
asset.asImageNode() → ImageNode
Node
from markdownmesh import NodeType as T
node.isa(T.CodeBlock) → bool
node.text_attr → str
node.as_flattened_text → str
node.dict → dict
node.json → str
node.parent
node.children
node.siblings_after
node.siblings_before
node.preceding_sibling_or_none
node.delete()
node.replaceWithNodes(nodes)
node.setHtmlAttribute(k, v)
node.findNodes(…) → Sequence[Node]
node.findImageNodes(…) → Sequence[ImageNode]
node.findCodeBlockNodes(…) → Sequence[CodeBlockNode]
…
CodeBlockNode extends Node
.code # This is the same as .text_attr
.info_string # str, possibly empty
.lang # First word, if non-empty, else None
.meta # Remaining text, if any, else None
Examples
Example 1
The main function of an early draft of import-markdown:
def onDocParsed(doc: Doc, c: Context):
# We're looking for an Image element
for imgnode in doc.root.findImageNodes():
# ...whose `.src` is a Markdown file
src_pathname = pathname_of(imgnode.src)
src_fragment_slug = parse_fragment_slug_or_none(imgnode.src)
if not src_pathname.endswith(".md"):
continue
# ...which is preceded
before = imgnode.preceding_sibling_or_none
if before is None:
continue
# ...by the Text "!"
if before.isa(T.Text) and before.text_attr == "!":
# TODO: handle more generally, including Text(text="... !")
# ...then it's an import
import_parent = imgnode.parent
# ...which might be followed by a Text node that defines a filter
filter_function = None
filter_node = None
siblings_after = imgnode.siblings_after
if len(siblings_after) > 0:
s = siblings_after[0]
if s.isa(T.Text) and first_word_of(s.text_attr) in FILTERS_BY_INVOCATION:
filter_function = FILTERS_BY_INVOCATION[first_word_of(s.text_attr)]
filter_node = s
import_parent_had_no_other_children = bool(len(import_parent.children) == (2 if filter_function is None else 3))
# So now we'll import the Markdown doc that was linked to
doc_being_imported = doc.docset.docWithPid(src_pathname, relative_to=doc,
temp_go_beyond_edition=True)
new_nodes = doc_being_imported.root.children
# ...and apply the fragment slug, if any,
if src_fragment_slug is not None:
new_nodes = extract_section_nodes_from_nodes(new_nodes, src_fragment_slug)
# ...and apply the specified filter, if any,
if filter_function:
new_nodes = filter_function(new_nodes, c)
# TODO: postprocess the result of this based on:
# - new_nodes type: Block, blocks, inline, or inlines?
# - imgnode.parent type: Paragraph, TableCell, ...?
# ...and replace the `!:…` nodes with the new nodes
before.delete()
if filter_node is not None:
filter_node.delete()
imgnode.replaceWithNodes(new_nodes)
# ...and if this import was the sole contents of a Paragraph node, then drop that paragraph wrapper
if import_parent_had_no_other_children and import_parent.isa(T.Paragraph):
import_parent.replaceWithNodes(import_parent.children)