Skip to content

API Reference

Transformers

md_transformer.transformer.MarkdownTransformer

Bases: ABC

Source code in src/md_transformer/transformer.py
class MarkdownTransformer(abc.ABC):
    token_type = None

    @abc.abstractmethod
    def transform(self, node):
        ...

md_transformer.transformer.CodeFenceTransformer

Bases: MarkdownTransformer

Source code in src/md_transformer/transformer.py
class CodeFenceTransformer(MarkdownTransformer):
    token_type = CodeFence
    marker = None

    def transform(self, node):
        match node.info_string.split():
            case self.marker, *args:
                node.children[0].content = self.transform_content(node.content, *args)
                self.finish(node)

    def finish(self, node):
        pass

md_transformer.transformer.MacroExpander

Bases: MarkdownTransformer

Source code in src/md_transformer/transformer.py
class MacroExpander(MarkdownTransformer):
    token_type = RawText

    def transform(self, node):
        node.content = re.sub(
            rf"{{% *{self.command}(?: +(.*))?%}}",
            lambda m: self.expand(*shlex.split(m.group(1) or "")),
            node.content,
        )

    @abc.abstractmethod
    def expand(self, *args):
        pass

md_transformer.transformer.FixLinebreak

Bases: MarkdownTransformer

Source code in src/md_transformer/transformer.py
class FixLinebreak(MarkdownTransformer):
    token_type = LineBreak

    def transform(self, node):
        node.soft = False

Example Transformers

md_transformer.example_transformers.BashExecute

Bases: CodeFenceTransformer

Source code in src/md_transformer/example_transformers.py
class BashExecute(CodeFenceTransformer):
    marker = "bash-execute"

    def __init__(self, process_root):
        self.process_root = process_root

    def transform_content(self, content):
        lines = content.splitlines()
        new_content = []
        with contextlib.chdir(self.process_root):
            for line in lines:
                new_content.append(line)
                result = subprocess.run(
                    line.lstrip(" $"),
                    shell=True,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.STDOUT,
                    text=True,
                    env=os.environ,
                )
                new_content.extend(result.stdout.splitlines())
        return "\n".join(new_content).rstrip("\n") + "\n"

    def finish(self, node):
        node.info_string = "sh"

md_transformer.example_transformers.SavePythonCode

Bases: CodeFenceTransformer

Source code in src/md_transformer/example_transformers.py
class SavePythonCode(CodeFenceTransformer):
    marker = "python-code"

    def __init__(self, process_root):
        self.process_root = process_root

    def transform_content(self, content, path):
        path = path.strip()
        (self.process_root / path).write_text(content)
        return f"# {path}\n\n" + content

    def finish(self, node):
        node.info_string = "python"

md_transformer.example_transformers.IncludePython

Bases: CodeFenceTransformer

Source code in src/md_transformer/example_transformers.py
class IncludePython(CodeFenceTransformer):
    marker = "include-python"

    def transform_content(self, content, path):
        parts = path.split(".")
        o = importlib.import_module(parts[0])
        for i, p in enumerate(parts[1:], 1):
            try:
                o = getattr(o, p)
            except AttributeError:
                o = importlib.import_module(".".join(parts[: i + 1]))

        return inspect.getsource(o)

    def finish(self, node):
        node.info_string = "python"

Core Functions

md_transformer.transformer.transform(fh_in, fh_out, transformers, max_line_length=100)

Source code in src/md_transformer/transformer.py
def transform(fh_in, fh_out, transformers, max_line_length=100):
    with MarkdownRenderer(max_line_length=max_line_length) as renderer:
        text = expand_includes(fh_in.read())
        doc = mistletoe.Document(text)
        _transform(doc, list(transformers) + [FixLinebreak()])
        print(renderer.render(doc), file=fh_out)

md_transformer.transformer.transform_text(text, transformers, max_line_length=100)

Source code in src/md_transformer/transformer.py
def transform_text(text, transformers, max_line_length=100):
    with MarkdownRenderer(max_line_length=max_line_length) as renderer:
        text = expand_includes(text)
        doc = mistletoe.Document(text)
        _transform(doc, list(transformers) + [FixLinebreak()])
        return renderer.render(doc)

md_transformer.transformer.expand_includes(text)

Source code in src/md_transformer/transformer.py
def expand_includes(text):
    def load(match):
        return pathlib.Path(match.group(1).strip()).read_text()

    return re.sub(r"{% *include +([^ ]+) *%}", load, text)