From 8b96881f6151a902ab75f660922cf7d99790e76e Mon Sep 17 00:00:00 2001 From: Nacai <111849193+B67687@users.noreply.github.com> Date: Thu, 9 Apr 2026 20:23:44 +0800 Subject: [PATCH 1/2] Fix GitHub Pages notebook rendering --- .github/workflows/deploy-pages.yml | 53 +++++++ .gitignore | 1 + build_pages.py | 213 +++++++++++++++++++++++++++++ pages-requirements.txt | 2 + 4 files changed, 269 insertions(+) create mode 100644 .github/workflows/deploy-pages.yml create mode 100644 build_pages.py create mode 100644 pages-requirements.txt diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml new file mode 100644 index 0000000..c6444f6 --- /dev/null +++ b/.github/workflows/deploy-pages.yml @@ -0,0 +1,53 @@ +name: Deploy GitHub Pages + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Pages + uses: actions/configure-pages@v5 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install build dependencies + run: pip install -r pages-requirements.txt + + - name: Build Pages site + run: python build_pages.py + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: _site + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + needs: build + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 15201ac..5f80dca 100644 --- a/.gitignore +++ b/.gitignore @@ -145,6 +145,7 @@ venv.bak/ # mkdocs documentation /site +/_site # mypy .mypy_cache/ diff --git a/build_pages.py b/build_pages.py new file mode 100644 index 0000000..cdfa5a6 --- /dev/null +++ b/build_pages.py @@ -0,0 +1,213 @@ +from __future__ import annotations + +import html +import os +import re +import shutil +import stat +import subprocess +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent +OUTPUT_DIR = Path(os.environ.get("PAGES_OUTPUT_DIR", ROOT / "_site")) +EXCLUDED_PARTS = { + ".git", + ".github", + ".ipynb_checkpoints", + "__pycache__", + "_site", +} +STATIC_SUFFIXES = { + ".png", + ".jpg", + ".jpeg", + ".gif", + ".svg", + ".webp", + ".ico", + ".txt", + ".yml", + ".yaml", +} + + +def should_skip(path: Path) -> bool: + return any(part in EXCLUDED_PARTS for part in path.parts) + + +def handle_remove_readonly(func, path, excinfo) -> None: + os.chmod(path, stat.S_IWRITE) + func(path) + + +def clear_output_dir() -> None: + if OUTPUT_DIR.exists(): + shutil.rmtree(OUTPUT_DIR, onexc=handle_remove_readonly) + OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + + +def run_nbconvert(notebook_path: Path) -> Path: + relative_path = notebook_path.relative_to(ROOT) + destination = OUTPUT_DIR / relative_path.with_suffix(".html") + destination.parent.mkdir(parents=True, exist_ok=True) + + command = [ + sys.executable, + "-m", + "nbconvert", + "--to", + "html", + "--output", + destination.stem, + "--output-dir", + str(destination.parent), + str(notebook_path), + ] + subprocess.run(command, check=True) + rewrite_html_links(destination) + return destination + + +def rewrite_notebook_links(text: str) -> str: + return re.sub(r"(?P[\(/\"'=])(?P[^\"')>]+?)\.ipynb(?P[\"')>#?])", r"\g\g.html\g", text) + + +def rewrite_html_links(html_path: Path) -> None: + content = html_path.read_text(encoding="utf-8") + updated = rewrite_notebook_links(content) + if updated != content: + html_path.write_text(updated, encoding="utf-8") + + +def copy_static_files() -> None: + for path in ROOT.rglob("*"): + if path.is_dir() or should_skip(path): + continue + if path == ROOT / "README.md": + continue + if path.suffix.lower() not in STATIC_SUFFIXES: + continue + + destination = OUTPUT_DIR / path.relative_to(ROOT) + destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(path, destination) + + +def render_markdown(markdown_text: str) -> str: + try: + import markdown + except ImportError as exc: + raise RuntimeError( + "The 'markdown' package is required to build the Pages homepage. " + "Install it with 'pip install markdown'." + ) from exc + + return markdown.markdown( + markdown_text, + extensions=[ + "extra", + "toc", + "tables", + "fenced_code", + "sane_lists", + ], + output_format="html5", + ) + + +def build_homepage() -> None: + readme_path = ROOT / "README.md" + readme_text = readme_path.read_text(encoding="utf-8") + readme_text = rewrite_notebook_links(readme_text) + body = render_markdown(readme_text) + + page = f""" + + + + + MathLearningNotes + + + + +
+
+ Notebook links on this site open rendered HTML pages generated from the repository's .ipynb files. +
+
+ {body} +
+
+ + +""" + (OUTPUT_DIR / "index.html").write_text(page, encoding="utf-8") + + +def create_nojekyll_file() -> None: + (OUTPUT_DIR / ".nojekyll").write_text("", encoding="utf-8") + + +def main() -> None: + clear_output_dir() + + notebooks = sorted( + path for path in ROOT.rglob("*.ipynb") if not should_skip(path) + ) + if not notebooks: + raise RuntimeError("No notebooks were found to convert.") + + for notebook in notebooks: + print(f"Converting {notebook.relative_to(ROOT)}") + run_nbconvert(notebook) + + copy_static_files() + build_homepage() + create_nojekyll_file() + + print(f"Built {len(notebooks)} notebooks into {OUTPUT_DIR}") + + +if __name__ == "__main__": + try: + main() + except subprocess.CalledProcessError as exc: + command = " ".join(html.escape(part) for part in exc.cmd) + raise SystemExit(f"Build failed while running: {command}") from exc diff --git a/pages-requirements.txt b/pages-requirements.txt new file mode 100644 index 0000000..1b5965d --- /dev/null +++ b/pages-requirements.txt @@ -0,0 +1,2 @@ +nbconvert>=7 +markdown From 35960fff24f645c36e879191fe0add81bd465295 Mon Sep 17 00:00:00 2001 From: Nacai <111849193+B67687@users.noreply.github.com> Date: Thu, 9 Apr 2026 20:24:52 +0800 Subject: [PATCH 2/2] Run Pages build on pull requests --- .github/workflows/deploy-pages.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index c6444f6..0557d3b 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -4,6 +4,9 @@ on: push: branches: - main + pull_request: + branches: + - main workflow_dispatch: permissions: @@ -42,6 +45,7 @@ jobs: path: _site deploy: + if: github.event_name != 'pull_request' environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }}