mdhtml/python/mdhtml at main · AnswerDotAI/mdhtml · GitHub
Skip to content

Latest commit

 

History

History
"""Generate README.md from a notebook: `nbdev-readme` without quarto. The notebook's stored
outputs are used as found (nothing executes), `hide` cells drop, leading `#|` directive lines
strip, a leading raw frontmatter message is consumed, and images embedded in outputs or
attachments are extracted to a `{stem}_files/` directory the command owns and clears. A
do-not-edit comment goes on top unless `head` replaces or suppresses it."""
import sys, shutil, tomllib
from pathlib import Path

from fastcore.script import call_parse

from aidialog.dialog import Dialog, dlg2md, sraw
from aidialog.ipynb import read_ipynb

from .md import md2gfm

__all__ = ["main"]

_WARN = "<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->"


def _nbdev_cfg(start):
    "Project root and `[tool.nbdev]` table of the nearest pyproject.toml carrying one, else `start` and empty"
    for d in [start, *start.parents]:
        f = d/"pyproject.toml"
        if not f.exists(): continue
        if (t := tomllib.loads(f.read_text(encoding="utf-8")).get("tool", {}).get("nbdev")) is not None: return d, t
    return start, {}


def _strip_directives(s):
    lines = s.split("\n")
    n = 0
    while n < len(lines) and lines[n].startswith("#|"): n += 1
    return "\n".join(lines[n:]).lstrip("\n")


@call_parse(pos=["file"])
def main(
    file: str = None,  # Notebook to render (default: the configured `readme_nb` under `nbs_path`)
    out: str = None,  # Output path (default: README.md at the project root)
    head: str = None,  # Text placed above the output (default: a do-not-edit comment; pass '' for none)
):
    "Create README.md, and a `{stem}_files/` directory of extracted images, from a notebook's stored outputs"
    root, cfg = _nbdev_cfg(Path.cwd())
    file = Path(file) if file else root/cfg.get("nbs_path", "nbs")/cfg.get("readme_nb", "index.ipynb")
    out = Path(out) if out else root/"README.md"
    ms = [m for m in read_ipynb(file).messages if not m.has_directive("hide")]
    if ms and ms[0].msg_type == sraw and ms[0].content.startswith("---"): ms = ms[1:]
    for m in ms: m.content = _strip_directives(m.content)
    imgdir = out.parent/f"{out.stem}_files"
    shutil.rmtree(imgdir, ignore_errors=True)
    res = md2gfm(dlg2md(Dialog(ms)), dest=out, raw=("md", "html"), imgdir=imgdir)
    if head is None: head = _WARN
    if head: out.write_text(f"{head}\n\n{res}")
    for w in res.warnings: print(w, file=sys.stderr)