writers: Format Writers (typed Doc -> target text)#

base defines the Writer protocol/registry, the Capability contract the degradation engine consults, and the target-native marker recovery seam required by ENCLAVE-capable extensions. Markdown dialects drive mdformat through the token bridge; rST, Typst, Plane, and wikitext load direct typed-tree renderers on demand. Importing this package keeps those direct writers lazy.

Format Writers (typed Doc -> target text).

base defines the Writer protocol/registry, the Capability contract the degradation engine consults, and the target-native marker recovery seam required by ENCLAVE-capable extensions. Markdown dialects drive mdformat through the token bridge; rST, Typst, Plane, and wikitext load direct typed-tree renderers on demand. Importing this package keeps those direct writers lazy.

class myform.writers.Capability(*values)#

How a writer can honor one node kind (the degradation engine’s contract).

NATIVE renders idiomatically with no loss; APPROX renders via a lowering rule and is reported as approximated; CARRY cannot render the node itself but carries its attendant info into a replacement, reported as replaced; DROP cannot render at all and is dropped with a report entry – never silently.

ENCLAVE is the fourth lossy fate and the only one that is not in-band: the node is preserved verbatim out of band, in its source form, and referenced in-band by a stable, addressable marker. Nothing is approximated and nothing is lost – the content is deferred to a carrier the target can point at, which is what makes a construct the target cannot express survivable rather than merely reported.

Added in v1.1, after the surface published at v1.0.0. Third-party writers and rules that predate it never emit it, and consumers that cannot interpret it must fall back to fallback (CARRY) rather than raising – an enclave is a carry that happens to keep its original.

property fallback: Capability#

The nearest v1.0.0 capability, for consumers that predate this member.

Every v1.0.0 member is its own fallback; ENCLAVE degrades to CARRY, which is the documented contract for extensions compiled against the older surface (spec D2).

class myform.writers.GfmWriter#

Writer for GitHub-Flavored Markdown (the mdformat gfm extension stack).

name: ClassVar[str] = 'gfm'#

The format name this writer targets (set by concrete subclasses).

extensions: ClassVar[tuple[str, ...]] = ('myform_gfm', 'footnote')#

The mdformat parser-extension names whose renderers/postprocessors apply.

capabilities: ClassVar[Mapping[str, Capability]] = {'blockBreak': Capability.DROP, 'definitionDescription': Capability.CARRY, 'definitionList': Capability.CARRY, 'definitionTerm': Capability.CARRY, 'embed': Capability.CARRY, 'frontMatter': Capability.CARRY, 'highlight': Capability.APPROX, 'ignored': Capability.NATIVE, 'inlineMath': Capability.APPROX, 'math': Capability.APPROX, 'mystComment': Capability.APPROX, 'mystDirective': Capability.APPROX, 'mystRole': Capability.CARRY, 'mystTarget': Capability.CARRY}#

Node type -> Capability; kinds not listed fall back to default_capability.

default_capability: ClassVar[Capability] = 1#

The capability assumed for node kinds absent from capabilities.

class myform.writers.MarkdownWriter#

Shared base for markdown-target writers (MyST, GFM): bridge, engine, MDRenderer.

extensions: ClassVar[tuple[str, ...]] = ()#

The mdformat parser-extension names whose renderers/postprocessors apply.

write(doc: Doc, options: Mapping[str, Any] | None = None) → str#

Render doc through the token bridge and mdformat’s MDRenderer.

The document’s env snapshot is deep-copied into the render so reference links and other document-level state survive, and repeated writes stay independent. This is the exact render path mdformat.text drives, which is what makes formatting (convert(text, f, f)) a byte-for-byte fixed point.

class myform.writers.MystWriter#

Writer for MyST markdown (the mdformat myst extension stack); all-NATIVE.

name: ClassVar[str] = 'myst'#

The format name this writer targets (set by concrete subclasses).

extensions: ClassVar[tuple[str, ...]] = ('myst',)#

The mdformat parser-extension names whose renderers/postprocessors apply.

capabilities: ClassVar[Mapping[str, Capability]] = {'delete': Capability.APPROX, 'embed': Capability.CARRY, 'highlight': Capability.APPROX}#

the mdformat myst stack has no s renderer, so a foreign-sourced delete (e.g. from GFM) must be lowered (the '*' delete rule unwraps it to its retained children) rather than crash MDRenderer with KeyError: 's'. MyST source never emits delete, so identity holds.

Type:

MyST is the model’s home format, so every kind is NATIVE except strikethrough

default_capability: ClassVar[Capability] = 1#

The capability assumed for node kinds absent from capabilities.

class myform.writers.ObsidianWriter#

Writer for Obsidian-flavored Markdown (GFM + footnotes + properties + the dialect).

name: ClassVar[str] = 'obsidian'#

The format name this writer targets (set by concrete subclasses).

extensions: ClassVar[tuple[str, ...]] = ('gfm', 'footnote', 'front_matters', 'obsidian')#

The mdformat parser-extension names whose renderers/postprocessors apply.

capabilities: ClassVar[Mapping[str, Capability]] = {'blockBreak': Capability.DROP, 'definitionDescription': Capability.CARRY, 'definitionList': Capability.CARRY, 'definitionTerm': Capability.CARRY, 'inlineMath': Capability.APPROX, 'math': Capability.APPROX, 'mystRole': Capability.CARRY, 'mystTarget': Capability.CARRY}#

Node type -> Capability; kinds not listed fall back to default_capability.

default_capability: ClassVar[Capability] = 1#

The capability assumed for node kinds absent from capabilities.

write(doc: Doc, options: Mapping[str, Any] | None = None) → str#

Refuse a foreign registry owner only when the built-in writer is used.

class myform.writers.PlaneWriter#

Writer for Plane’s Tiptap description_html (direct typed-model walk).

name: ClassVar[str] = 'plane'#

The format name this writer targets (set by concrete subclasses).

capabilities: ClassVar[Mapping[str, Capability]] = {'blockBreak': Capability.APPROX, 'definitionDescription': Capability.CARRY, 'definitionList': Capability.CARRY, 'definitionTerm': Capability.CARRY, 'embed': Capability.CARRY, 'footnoteDefinition': Capability.CARRY, 'footnoteReference': Capability.CARRY, 'frontMatter': Capability.CARRY, 'highlight': Capability.APPROX, 'ignored': Capability.CARRY, 'inlineMath': Capability.CARRY, 'inlineTypstRaw': Capability.APPROX, 'inlineUnknown': Capability.CARRY, 'math': Capability.CARRY, 'mystComment': Capability.CARRY, 'mystDirective': Capability.APPROX, 'mystRole': Capability.APPROX, 'mystTarget': Capability.CARRY, 'typstRaw': Capability.APPROX, 'unknown': Capability.CARRY}#

Node type -> Capability; kinds not listed fall back to default_capability.

default_capability: ClassVar[Capability] = 1#

The capability assumed for node kinds absent from capabilities.

write(doc: Doc, options: Mapping[str, Any] | None = None) → str#

Render doc as Plane’s description_html wire format.

Parameters:
  • doc – The typed source document (already lowered by the degradation engine).

  • options – Unused; the dialect has no writer-tunable knobs today.

Returns:

HTML wrapped in a single <div> (Plane’s own payloads vary on this – a bare single-paragraph description omits it – but the reader accepts both shapes, so the writer always emits the unambiguous form).

Raises:

UnsupportedPlaneNodeError – If an opaque or unsupported unlowered node reaches the writer.

class myform.writers.RstWriter#

Writer for Sphinx reStructuredText (direct model walk).

name: ClassVar[str] = 'rst'#

The format name this writer targets (set by concrete subclasses).

capabilities: ClassVar[Mapping[str, Capability]] = {'blockBreak': Capability.APPROX, 'delete': Capability.APPROX, 'embed': Capability.CARRY, 'frontMatter': Capability.CARRY, 'highlight': Capability.APPROX, 'ignored': Capability.CARRY, 'inlineUnknown': Capability.CARRY, 'unknown': Capability.CARRY}#

Node type -> Capability; kinds not listed fall back to default_capability.

default_capability: ClassVar[Capability] = 1#

The capability assumed for node kinds absent from capabilities.

write(doc: Doc, options: Mapping[str, Any] | None = None) → str#

Render doc as deterministic Sphinx reStructuredText.

Parameters:
  • doc – The typed document to render.

  • options – Writer options. heading_underline_ladder may be given directly or in an rst sub-mapping; the default is #*=-^".

Returns:

Canonical rST ending in exactly one newline (or '' for an empty document).

Raises:
  • UnsupportedRstNodeError – If degradation has not lowered a non-native construct.

  • ValueError – If the heading ladder option is invalid.

class myform.writers.TypstWriter#

Writer for compiler-valid Typst markup (direct typed-model walk).

name: ClassVar[str] = 'typst'#

The format name this writer targets (set by concrete subclasses).

capabilities: ClassVar[Mapping[str, Capability]] = {'blockBreak': Capability.APPROX, 'embed': Capability.CARRY, 'frontMatter': Capability.CARRY, 'highlight': Capability.APPROX, 'ignored': Capability.CARRY, 'inlineTypstRaw': Capability.APPROX, 'inlineUnknown': Capability.CARRY, 'mystDirective': Capability.APPROX, 'mystRole': Capability.CARRY, 'typstRaw': Capability.APPROX, 'unknown': Capability.CARRY}#

Node type -> Capability; kinds not listed fall back to default_capability.

default_capability: ClassVar[Capability] = 1#

The capability assumed for node kinds absent from capabilities.

write(doc: Doc, options: Mapping[str, Any] | None = None) → str#

Render doc as deterministic Typst source.

Parameters:
  • doc – The typed source document.

  • options – Writer options, either direct (postformat/prelude) or nested under typst as emitted by MyformConfig.

Returns:

Self-contained Typst source, including an inlined prelude when explicitly requested.

Raises:
  • UnsupportedTypstNodeError – If an opaque or unsupported unlowered node reaches the writer.

  • TypstPostformatError – If required normalization is unavailable or formatting fails.

class myform.writers.WikitextWriter#

Writer for deterministic, bounded MediaWiki wikitext.

name: ClassVar[str] = 'wikitext'#

The format name this writer targets (set by concrete subclasses).

capabilities: ClassVar[Mapping[str, Capability]] = {'blockBreak': Capability.APPROX, 'blockquote': Capability.NATIVE, 'break': Capability.NATIVE, 'code': Capability.NATIVE, 'definitionDescription': Capability.NATIVE, 'definitionList': Capability.NATIVE, 'definitionTerm': Capability.NATIVE, 'delete': Capability.NATIVE, 'embed': Capability.CARRY, 'emphasis': Capability.NATIVE, 'footnoteDefinition': Capability.NATIVE, 'footnoteReference': Capability.NATIVE, 'frontMatter': Capability.CARRY, 'heading': Capability.NATIVE, 'highlight': Capability.NATIVE, 'ignored': Capability.CARRY, 'image': Capability.NATIVE, 'inlineCode': Capability.NATIVE, 'inlineMath': Capability.NATIVE, 'inlineTypstRaw': Capability.CARRY, 'inlineUnknown': Capability.CARRY, 'link': Capability.NATIVE, 'list': Capability.NATIVE, 'listItem': Capability.NATIVE, 'math': Capability.NATIVE, 'mystComment': Capability.NATIVE, 'mystDirective': Capability.CARRY, 'mystRole': Capability.CARRY, 'mystTarget': Capability.NATIVE, 'paragraph': Capability.NATIVE, 'root': Capability.NATIVE, 'strong': Capability.NATIVE, 'table': Capability.NATIVE, 'tableCell': Capability.NATIVE, 'tableRow': Capability.NATIVE, 'text': Capability.NATIVE, 'thematicBreak': Capability.NATIVE, 'typstRaw': Capability.CARRY, 'unknown': Capability.CARRY}#

Node type -> Capability; kinds not listed fall back to default_capability.

default_capability: ClassVar[Capability] = 3#

The capability assumed for node kinds absent from capabilities.

write(doc: Doc, options: Mapping[str, Any] | None = None) → str#

Render doc as deterministic MediaWiki wikitext.

Parameters:
  • doc – The typed document to render.

  • options – Shared per-format options. The writer currently has no rendering knobs; reader-only values such as title and shared formatting values are ignored.

Returns:

Canonical wikitext ending in one newline, or '' for an empty document.

Raises:

UnsupportedWikitextNodeError – If degradation has not lowered an unsupported node or a content-sensitive shape cannot be represented faithfully.

class myform.writers.Writer#

The writer seam: render a typed Doc as one target format.

REGISTRY: ClassVar[dict[str, Writer]] = {'gfm': <myform.writers.gfm.GfmWriter object>, 'myst': <myform.writers.myst.MystWriter object>, 'obsidian': <myform.writers.obsidian.ObsidianWriter object>, 'plane': <myform.writers.plane.PlaneWriter object>, 'rst': <myform.writers.rst.RstWriter object>, 'typst': <myform.writers.typst.TypstWriter object>, 'wikitext': <myform.writers.wikitext.WikitextWriter object>}#

Format name -> ready-to-use writer instance; populated by register.

name: ClassVar[str] = ''#

The format name this writer targets (set by concrete subclasses).

capabilities: ClassVar[Mapping[str, Capability]] = {}#

Node type -> Capability; kinds not listed fall back to default_capability.

default_capability: ClassVar[Capability] = 1#

The capability assumed for node kinds absent from capabilities.

classmethod register(writer_cls: type[W]) → type[W]#

Class decorator registering a concrete writer under its name.

classmethod get(name: str) → Writer#

Resolve a writer, importing direct backends only when first selected.

classmethod capability(kind: str) → Capability#

Look up this writer’s capability for one node type.

write(doc: Doc, options: Mapping[str, Any] | None = None) → str#

Render doc as this writer’s format (concrete writers implement this).

find_enclave_markers(text: str, options: Mapping[str, Any] | None = None) → tuple[Link, ...]#

Recover target-native enclave marker links from final rendered text.

Writers that declare Capability.ENCLAVE must override this method with a parser for their navigable target syntax. Returning raw path-substring matches does not satisfy the contract; the result is typed evidence that the final target still contains a link.

enclave_marker_options(options: Mapping[str, Any] | None = None) → dict[str, Any]#

Select the non-sensitive options needed to recover enclave markers.

Conversion artifacts are durable and routinely serialized, so the complete writer option mapping is never retained implicitly. An ENCLAVE-capable extension whose marker parser depends on options must override this method and copy only its JSON-safe, marker-relevant settings.