Skip to content

Commit 12ee981

Browse files
committed
add script to convert episodes to pre-filled notebooks
1 parent 839406d commit 12ee981

2 files changed

Lines changed: 105 additions & 0 deletions

File tree

.github/workflows/md_to_ipynb.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Convert Markdown to Jupyter Notebooks
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
11+
jobs:
12+
convert:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout repository
16+
uses: actions/checkout@v4
17+
18+
- name: Set up Python
19+
uses: actions/setup-python@v4
20+
with:
21+
python-version: "3.8"
22+
23+
- name: Install dependencies
24+
run: pip install nbformat pandoc
25+
26+
- name: Convert Markdown files to Jupyter Notebooks
27+
run: python scripts/md_to_ipynb.py
28+
29+
- name: Commit and push changes
30+
run: |
31+
git config --global user.name "github-actions"
32+
git config --global user.email "github-actions@github.com"
33+
git add notebooks/*.ipynb
34+
git commit -m "Auto-generate Jupyter notebooks from Markdown" || echo "No changes to commit"
35+
git push

scripts/md_to_ipynb.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import os
2+
import nbformat
3+
import re
4+
from nbformat.v4 import new_notebook, new_markdown_cell, new_code_cell
5+
6+
# Paths
7+
episodes_dir = "episodes"
8+
notebooks_dir = "notebooks"
9+
10+
# List of Markdown files to ignore (no conversion needed)
11+
ignore_list = [
12+
"01-Introduction.md",
13+
"02-Data-storage.md",
14+
"03-Notebooks-as-controllers.md",
15+
"05-Interacting-with-code-repo.md",
16+
"09-Resource-management-cleanup.md"
17+
]
18+
19+
# Ensure notebooks directory exists
20+
os.makedirs(notebooks_dir, exist_ok=True)
21+
22+
# Regular expression to detect code blocks (matches ```language\n...\n```)
23+
code_block_pattern = re.compile(r"```(\w+)?\n(.*?)\n```", re.DOTALL)
24+
25+
def split_markdown(md_content):
26+
"""Splits Markdown content into separate Markdown and Code cells."""
27+
cells = []
28+
position = 0
29+
30+
for match in code_block_pattern.finditer(md_content):
31+
# Extract text before the code block as Markdown
32+
before_code = md_content[position:match.start()].strip()
33+
if before_code:
34+
cells.append(new_markdown_cell(before_code))
35+
36+
# Extract code block content
37+
code_content = match.group(2).strip()
38+
if code_content:
39+
cells.append(new_code_cell(code_content))
40+
41+
position = match.end()
42+
43+
# Add any remaining Markdown content after the last code block
44+
remaining_md = md_content[position:].strip()
45+
if remaining_md:
46+
cells.append(new_markdown_cell(remaining_md))
47+
48+
return cells
49+
50+
# Convert each Markdown file in episodes/
51+
for filename in os.listdir(episodes_dir):
52+
if filename.endswith(".md") and filename not in ignore_list:
53+
md_path = os.path.join(episodes_dir, filename)
54+
ipynb_path = os.path.join(notebooks_dir, filename.replace(".md", ".ipynb"))
55+
56+
# Read Markdown content
57+
with open(md_path, "r", encoding="utf-8") as f:
58+
md_content = f.read()
59+
60+
# Split into Markdown and Code cells
61+
notebook_cells = split_markdown(md_content)
62+
63+
# Create Jupyter notebook
64+
nb = new_notebook(cells=notebook_cells)
65+
66+
# Save as .ipynb
67+
with open(ipynb_path, "w", encoding="utf-8") as f:
68+
nbformat.write(nb, f)
69+
70+
print("Conversion complete! Excluded:", ", ".join(ignore_list))

0 commit comments

Comments
 (0)