Extender la librería

Más allá de widgets, transformaciones y subclases de renderer, el parser expone hooks más profundos para casos donde esos no alcanzan.

Reglas propias

Una regla observa el árbol que se está construyendo y reporta diagnósticos. Subclasifica markast.rules.Rule y sobrescribe los métodos que te interesen:

from markast import Parser
from markast.rules import Diagnostic, Rule, Severity


class HeadingMustBeShort(Rule):
    """Marca headings cuyo texto plano supera los 60 caracteres."""

    name = "short-heading"

    def check_heading_children(self, children, level):
        text = "".join(c.get("value", "") for c in children
                       if c.get("type") == "text")
        if len(text) > 60:
            return [Diagnostic(
                code="X100",
                message=f"Heading demasiado largo ({len(text)} chars).",
                context=text[:40] + "…",
                severity=Severity.WARNING,
            )]
        return None


parser = Parser(rules=[HeadingMustBeShort])

Códigos de diagnóstico

CódigoDisparador
W001Imagen dentro de un heading
W002Elemento block donde se requiere inline
W003Nombre de widget desconocido
W004Valor de prop inválido (tipo incorrecto / no está en choices)
W005Prop requerido faltante
W006Imagen dentro de una celda de tabla
W007HTML crudo encontrado (informativo)
W008Referencia a footnote sin definición
W009Anidación de widgets más profunda que el límite configurado

Para reemplazar las reglas builtin (por ejemplo en modo estricto), pasa solo las tuyas. Para extender, incluye BuiltinRules:

from markast.rules.builtin import BuiltinRules
parser = Parser(rules=[BuiltinRules(), HeadingMustBeShort()])

Ajustar la configuración del parser

from markast import Parser, ParserConfig

cfg = ParserConfig(
    features=("tables", "strikethrough", "footnotes"),  # sin autolinks/tasklists
    diagnose_html_blocks=False,
    max_widget_depth=8,
)

parser = Parser(cfg)

Usa cfg.evolve(...) para derivar nuevas configuraciones:

strict = cfg.evolve(max_widget_depth=4)

Reemplazar el tokenizer

Raro, pero posible. Parser construye un Tokenizer de forma perezosa; si necesitas otro (por ejemplo para inyectar plugins extra de markdown-it-py), asígnalo antes del primer parse:

from markast import Parser
from markast.parser.tokenizer import Tokenizer
from mdit_py_plugins.deflist import deflist_plugin


class MyTokenizer(Tokenizer):
    def _build_markdown_it(self):
        md = super()._build_markdown_it()
        md.use(deflist_plugin)
        return md


parser = Parser()
parser._tokenizer = MyTokenizer(parser.config, parser.registry)

Patrón: parser por tenant

Un servicio multi-tenant puede necesitar conjuntos distintos de widgets por tenant. Cachea parsers por id:

from functools import lru_cache
from markast import Parser
from markast.widgets import default_registry, WidgetRegistry


@lru_cache(maxsize=64)
def parser_for(tenant_id: str) -> Parser:
    registry = default_registry.clone()
    for cls in load_tenant_widgets(tenant_id):
        registry.register(cls)
    return Parser(registry=registry, transforms=["normalize", "slugify"])


def render(tenant_id, markdown):
    return parser_for(tenant_id).parse(markdown)

Cada parser es independiente — mutar el registry de uno no afecta a los demás.

Patrón: CI estricto de autoría

Combina reglas propias con doc.has_errors para fallar una build de CI por contenido inválido:

from markast import Parser
from markast.rules import Diagnostic, Rule, Severity


class NoRawHtml(Rule):
    name = "no-raw-html"

    def check_html_block(self, value):
        return [Diagnostic(
            code="C001",
            message="HTML crudo no permitido en este corpus.",
            severity=Severity.ERROR,
        )]


parser = Parser(rules=[NoRawHtml()])

doc = parser.parse(open("article.md").read())
if doc.has_errors:
    for w in doc.warnings:
        print(f"::error::[{w['code']}] {w['message']}")
    raise SystemExit(1)

Dónde está el código fuente

markast/
├── ast/         tipos, factories, walker, exportar schema
├── parser/      tokenizer + builder (block / inline / widget / props)
├── render/      markdown + html
├── widgets/     base / registry / builtins
├── rules/       sistema de diagnósticos + reglas builtin
├── transforms/  normalize / slugify / toc / linkify / typography
├── config.py
├── document.py
├── parser_api.py
└── cli.py

Cada archivo empieza con un docstring de módulo. Si te atascas, esa es la entrada más rápida.