|
| 1 | +"""Publish raw markdown alongside rendered HTML. |
| 2 | +
|
| 3 | +For every page the website serves at `/{type}/{slug}`, also publish the source |
| 4 | +markdown at `/{type}/{slug}.md`. Lets LLMs and AI agents fetch clean content |
| 5 | +without HTML chrome (the Mintlify / Anthropic-docs pattern). |
| 6 | +
|
| 7 | +Output goes into `afterpython/_website/static/{type}/{slug}.md`. SvelteKit's |
| 8 | +static adapter then serves each file at the matching URL with no SvelteKit-side |
| 9 | +code changes. |
| 10 | +
|
| 11 | +Flow |
| 12 | +---- |
| 13 | +build_url_md() # entry point |
| 14 | + └─ for each content_type in CONTENT_TYPES: |
| 15 | + └─ for each `*.md` / `*.ipynb` under afterpython/{type}/: |
| 16 | + ├─ _slugify(stem) → URL slug |
| 17 | + ├─ .md → shutil.copy(source, dest) (frontmatter preserved) |
| 18 | + └─ .ipynb → jupytext.read + write(fmt="md") (cell outputs stripped) |
| 19 | +""" |
| 20 | + |
| 21 | +from __future__ import annotations |
| 22 | + |
| 23 | +import shutil |
| 24 | +from pathlib import Path |
| 25 | + |
| 26 | +import click |
| 27 | +import jupytext |
| 28 | + |
| 29 | +import afterpython as ap |
| 30 | +from afterpython._typing import tContentType |
| 31 | +from afterpython.const import CONTENT_TYPES |
| 32 | + |
| 33 | + |
| 34 | +def _slugify(stem: str) -> str: |
| 35 | + """Mirror MyST's URL slug rule so each `.md` lands at the same URL as its |
| 36 | + rendered HTML sibling. |
| 37 | +
|
| 38 | + `package_management.md` -> `/doc/package-management.md` |
| 39 | + `CONTRIBUTING.md` -> `/doc/contributing.md` |
| 40 | + """ |
| 41 | + return stem.lower().replace("_", "-") |
| 42 | + |
| 43 | + |
| 44 | +def _publish_md(source: Path, dest: Path) -> None: |
| 45 | + """Copy a `.md` source verbatim. Frontmatter is preserved — LLMs can use |
| 46 | + it (date, authors, tags) and it's harmless if ignored. |
| 47 | + """ |
| 48 | + shutil.copy(source, dest) |
| 49 | + |
| 50 | + |
| 51 | +def _publish_ipynb(source: Path, dest: Path) -> None: |
| 52 | + """Convert a notebook to clean markdown via jupytext. |
| 53 | +
|
| 54 | + jupytext strips cell outputs by default, producing a leaner file than |
| 55 | + `nbconvert` would. The `md` format is jupytext's default — clean enough |
| 56 | + for LLM consumption while still round-trippable if anyone needs the |
| 57 | + notebook back. |
| 58 | + """ |
| 59 | + notebook = jupytext.read(source) |
| 60 | + jupytext.write(notebook, dest, fmt="md") |
| 61 | + |
| 62 | + |
| 63 | +def _publish_for_type(content_type: tContentType) -> int: |
| 64 | + """Walk one content type's source dir; publish every supported file. |
| 65 | + Returns the number of files written. |
| 66 | + """ |
| 67 | + source_dir = ap.paths.afterpython_path / content_type |
| 68 | + if not source_dir.exists(): |
| 69 | + return 0 |
| 70 | + |
| 71 | + dest_dir = ap.paths.website_path / "static" / content_type |
| 72 | + dest_dir.mkdir(parents=True, exist_ok=True) |
| 73 | + |
| 74 | + count = 0 |
| 75 | + for source in sorted(source_dir.rglob("*")): |
| 76 | + if "_build" in source.parts: |
| 77 | + continue |
| 78 | + if not source.is_file(): |
| 79 | + continue |
| 80 | + |
| 81 | + slug = _slugify(source.stem) |
| 82 | + dest = dest_dir / f"{slug}.md" |
| 83 | + |
| 84 | + if source.suffix == ".md": |
| 85 | + _publish_md(source, dest) |
| 86 | + elif source.suffix == ".ipynb": |
| 87 | + _publish_ipynb(source, dest) |
| 88 | + else: |
| 89 | + continue |
| 90 | + |
| 91 | + count += 1 |
| 92 | + return count |
| 93 | + |
| 94 | + |
| 95 | +def build_url_md() -> None: |
| 96 | + """Publish raw markdown for every content type alongside the rendered HTML. |
| 97 | +
|
| 98 | + Runs as part of `postbuild()` after MyST's HTML has been copied into |
| 99 | + `_website/static/{type}/`. Each `.md` ends up as a sibling of the rendered |
| 100 | + `{slug}/index.html`, served at `/{type}/{slug}.md` by SvelteKit's static |
| 101 | + adapter. |
| 102 | + """ |
| 103 | + for content_type in CONTENT_TYPES: |
| 104 | + count = _publish_for_type(content_type) |
| 105 | + if count > 0: |
| 106 | + click.echo(f"Published {count} url-md file(s) for {content_type}/") |
0 commit comments