Skip to content

Commit 6ab46c0

Browse files
authored
add github workflows to deploy notebooks to github pages
1 parent fecab8d commit 6ab46c0

File tree

8 files changed

+360
-2
lines changed

8 files changed

+360
-2
lines changed

.github/.gitkeep

Whitespace-only changes.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: Colab to GitHub Notebook Metadata Sanitizer
2+
on:
3+
push:
4+
paths:
5+
- '**.ipynb'
6+
7+
jobs:
8+
fix-notebook-render:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: write
12+
steps:
13+
- name: Checkout Repo
14+
uses: actions/checkout@v4
15+
16+
- name: Setup Python
17+
uses: actions/setup-python@v4
18+
with:
19+
python-version: '3.10'
20+
21+
- name: Install nbformat
22+
run: pip install nbformat
23+
24+
- name: Clean Notebooks
25+
run: |
26+
python - <<EOF
27+
import nbformat
28+
import glob
29+
import os
30+
31+
# Find all notebooks in the repo
32+
for path in glob.glob("**/*.ipynb", recursive=True):
33+
with open(path, 'r', encoding='utf-8') as f:
34+
nb = nbformat.read(f, as_version=4)
35+
36+
# 1. Strip the widget metadata (Fixes the GitHub 'state' error)
37+
if 'widgets' in nb.metadata:
38+
del nb.metadata['widgets']
39+
print(f"Fixed metadata in {path}")
40+
41+
# 2. Clear problematic outputs (Login widgets and HTML videos)
42+
for cell in nb.cells:
43+
if cell.cell_type == 'code':
44+
source = cell.source.lower()
45+
if 'notebook_login' in source or '%%html' in source:
46+
cell.outputs = []
47+
print(f"Cleared widget/video output in {path}")
48+
49+
with open(path, 'w', encoding='utf-8') as f:
50+
nbformat.write(nb, f)
51+
EOF
52+
53+
- name: Commit and Push Changes
54+
run: |
55+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
56+
git config --local user.name "github-actions[bot]"
57+
git add .
58+
git diff --quiet && git diff --staged --quiet || (git commit -m "chore: sanitize notebook for github rendering" && git push)
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
name: Deploy Notebooks to GH Pages
2+
on:
3+
push:
4+
branches: [ main ]
5+
6+
permissions:
7+
contents: read
8+
pages: write
9+
id-token: write
10+
11+
jobs:
12+
build-and-deploy:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v4
17+
18+
- name: Set up Python
19+
uses: actions/setup-python@v5
20+
with:
21+
python-version: '3.10'
22+
cache: 'pip'
23+
24+
- name: Install dependencies
25+
run: |
26+
# Install jupyter and nbconvert (always needed)
27+
pip install jupyter nbconvert
28+
29+
# Install requirements.txt if it exists
30+
if [ -f requirements.txt ]; then
31+
pip install -r requirements.txt
32+
fi
33+
34+
- name: Convert Notebooks to HTML
35+
run: |
36+
mkdir -p public
37+
38+
# Find ALL .ipynb files recursively, preserving directory structure
39+
find . -name "*.ipynb" -not -path "./public/*" -not -path "./.git/*" | while read notebook; do
40+
# Remove leading "./" from path
41+
clean_path="${notebook#./}"
42+
43+
# Get the directory of the notebook
44+
notebook_dir=$(dirname "$clean_path")
45+
46+
# Create the corresponding directory in public/
47+
mkdir -p "public/$notebook_dir"
48+
49+
echo "Converting $clean_path to public/$notebook_dir/"
50+
jupyter nbconvert --to html --template lab --output-dir="public/$notebook_dir" "$clean_path" || echo "Warning: Failed to convert $clean_path"
51+
done
52+
53+
# Copy ALL supporting files (images, data, etc.) - find all non-.ipynb files
54+
find . -type f -not -name "*.ipynb" \
55+
-not -path "./public/*" \
56+
-not -path "./.git/*" \
57+
-not -path "./.github/*" \
58+
-not -name "requirements.txt" \
59+
-not -name "README.md" \
60+
-not -name "LICENSE" \
61+
-not -name ".gitignore" \
62+
-not -name "*.yml" \
63+
-not -name "*.py" | while read file; do
64+
clean_path="${file#./}"
65+
target_dir="public/$(dirname "$clean_path")"
66+
mkdir -p "$target_dir"
67+
cp "$file" "$target_dir/" 2>/dev/null || true
68+
done
69+
70+
# Clean up notebook headers and inject blog header loader
71+
find public -name "*.html" -not -name "index.html" -exec sed -i '/<header class="site-header">/,/<\/header>/d' {} +
72+
find public -name "*.html" -not -name "index.html" -exec sed -i 's|<body>|<body><script src="https://chizkidd.github.io/header-loader.js"></script>|' {} +
73+
74+
# Get repo name and current date
75+
REPO_NAME="${GITHUB_REPOSITORY#*/}"
76+
LAST_UPDATED=$(date +"%B %d, %Y")
77+
78+
# Get repo title (from README or use repo name)
79+
REPO_TITLE=$(grep -m1 "^# " README.md 2>/dev/null | sed 's/^# //' || echo "$REPO_NAME")
80+
81+
echo "=== Building index for: $REPO_TITLE ==="
82+
83+
# Generate the index.html landing page
84+
cat > public/index.html << HTMLEOF
85+
<!DOCTYPE html>
86+
<html>
87+
<head>
88+
<meta charset="UTF-8">
89+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
90+
<title>$REPO_TITLE</title>
91+
<style>
92+
* { margin: 0; padding: 0; }
93+
html, body { height: 100%; }
94+
body {
95+
font-family: Helvetica, Arial, sans-serif;
96+
font-size: 16px;
97+
line-height: 1.5;
98+
font-weight: 300;
99+
background-color: #fdfdfd;
100+
display: grid;
101+
grid-template-rows: auto 1fr auto;
102+
min-height: 100vh;
103+
}
104+
h1, h2, h3, h4, h5, h6 { font-size: 100%; font-weight: 400; }
105+
a { color: #2a7ae2; text-decoration: none; }
106+
a:hover { color: #000; text-decoration: underline; }
107+
a:visited { color: #205caa; }
108+
.wrap { max-width: 800px; padding: 0 30px; margin: 0 auto; }
109+
.page-content { padding: 30px 0; background-color: #fff; }
110+
.header-matched-heading {
111+
color: #333;
112+
font-size: 32px;
113+
letter-spacing: -1.2px;
114+
font-weight: 400;
115+
text-align: left;
116+
line-height: 1.1;
117+
margin-top: 30px;
118+
margin-bottom: 25px;
119+
}
120+
.section-heading {
121+
font-size: 20px;
122+
font-weight: 500;
123+
color: #555;
124+
border-bottom: 1px solid #f0f0f0;
125+
padding-bottom: 5px;
126+
margin-top: 40px;
127+
margin-bottom: 15px;
128+
}
129+
.home-nav-list {
130+
padding-left: 20px;
131+
margin-bottom: 30px;
132+
list-style-type: disc;
133+
}
134+
.home-nav-list li { margin-bottom: 8px; }
135+
.home-nav-list a { font-size: 1.0em; font-weight: 300; color: #2a7ae2; }
136+
.home-nav-list a:hover { color: #000; text-decoration: underline; }
137+
.site-footer {
138+
border-top: 1px solid #e8e8e8;
139+
padding: 30px 0;
140+
}
141+
.site-footer p {
142+
font-size: 15px;
143+
letter-spacing: -.3px;
144+
color: #828282;
145+
text-align: center;
146+
}
147+
.site-footer a { color: #2a7ae2; }
148+
.last-updated { margin-top: 10px; font-size: 13px; color: #999; }
149+
@media screen and (max-width: 600px) {
150+
.wrap { padding: 0 12px; }
151+
}
152+
</style>
153+
</head>
154+
<body>
155+
<script src="https://chizkidd.github.io/header-loader.js"></script>
156+
157+
<div class="page-content">
158+
<div class="wrap">
159+
<h1 class="header-matched-heading">$REPO_TITLE</h1>
160+
<div id="notebook-sections"></div>
161+
</div>
162+
</div>
163+
164+
<footer class="site-footer">
165+
<div class="wrap">
166+
<p>
167+
<a href="https://github.com/${GITHUB_REPOSITORY}" target="_blank">View Repository</a>
168+
</p>
169+
<p class="last-updated">Last updated: $LAST_UPDATED</p>
170+
</div>
171+
</footer>
172+
173+
<script>
174+
const sections = document.getElementById('notebook-sections');
175+
HTMLEOF
176+
177+
# Build JavaScript array of all notebooks
178+
echo " const notebooks = [" >> public/index.html
179+
find public -name "*.html" -not -name "index.html" | while read html_file; do
180+
# Get relative path from public/
181+
rel_path="${html_file#public/}"
182+
# Get display name (remove .html, replace underscores/dashes with spaces)
183+
display_name=$(basename "$rel_path" .html | sed 's/[_-]/ /g')
184+
# Get folder for grouping
185+
folder=$(dirname "$rel_path")
186+
echo " {file: '$rel_path', name: '$display_name', folder: '$folder'}," >> public/index.html
187+
done
188+
echo " ];" >> public/index.html
189+
190+
cat >> public/index.html << 'JSEOF'
191+
192+
// Group notebooks by folder
193+
const grouped = {};
194+
notebooks.forEach(nb => {
195+
const folder = nb.folder === '.' ? 'Root' : nb.folder;
196+
if (!grouped[folder]) grouped[folder] = [];
197+
grouped[folder].push(nb);
198+
});
199+
200+
// Sort folders and notebooks within folders
201+
Object.keys(grouped).sort().forEach(folder => {
202+
const section = document.createElement('div');
203+
section.className = 'section';
204+
205+
const heading = document.createElement('h2');
206+
heading.className = 'section-heading';
207+
heading.textContent = folder.replace(/[_-]/g, ' ').replace(/\//g, ' / ');
208+
section.appendChild(heading);
209+
210+
const list = document.createElement('ul');
211+
list.className = 'home-nav-list';
212+
213+
// Sort notebooks alphabetically
214+
grouped[folder].sort((a, b) => a.name.localeCompare(b.name));
215+
216+
grouped[folder].forEach(nb => {
217+
const li = document.createElement('li');
218+
const a = document.createElement('a');
219+
a.href = nb.file;
220+
a.textContent = nb.name;
221+
li.appendChild(a);
222+
list.appendChild(li);
223+
});
224+
225+
section.appendChild(list);
226+
sections.appendChild(section);
227+
});
228+
</script>
229+
</body>
230+
</html>
231+
JSEOF
232+
233+
- name: Upload artifact
234+
uses: actions/upload-pages-artifact@v3
235+
with:
236+
path: 'public'
237+
238+
- name: Deploy to GitHub Pages
239+
id: deployment
240+
uses: actions/deploy-pages@v4
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: Colab to GitHub Notebook Metadata Sanitizer
2+
on:
3+
push:
4+
paths:
5+
- '**.ipynb'
6+
7+
jobs:
8+
fix-notebook-render:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: write
12+
steps:
13+
- name: Checkout Repo
14+
uses: actions/checkout@v4
15+
16+
- name: Setup Python
17+
uses: actions/setup-python@v4
18+
with:
19+
python-version: '3.10'
20+
21+
- name: Install nbformat
22+
run: pip install nbformat
23+
24+
- name: Clean Notebooks
25+
run: |
26+
python - <<EOF
27+
import nbformat
28+
import glob
29+
import os
30+
31+
# Find all notebooks in the repo
32+
for path in glob.glob("**/*.ipynb", recursive=True):
33+
with open(path, 'r', encoding='utf-8') as f:
34+
nb = nbformat.read(f, as_version=4)
35+
36+
# 1. Strip the widget metadata (Fixes the GitHub 'state' error)
37+
if 'widgets' in nb.metadata:
38+
del nb.metadata['widgets']
39+
print(f"Fixed metadata in {path}")
40+
41+
# 2. Clear problematic outputs (Login widgets and HTML videos)
42+
for cell in nb.cells:
43+
if cell.cell_type == 'code':
44+
source = cell.source.lower()
45+
if 'notebook_login' in source or '%%html' in source:
46+
cell.outputs = []
47+
print(f"Cleared widget/video output in {path}")
48+
49+
with open(path, 'w', encoding='utf-8') as f:
50+
nbformat.write(nb, f)
51+
EOF
52+
53+
- name: Commit and Push Changes
54+
run: |
55+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
56+
git config --local user.name "github-actions[bot]"
57+
git add .
58+
git diff --quiet && git diff --staged --quiet || (git commit -m "chore: sanitize notebook for github rendering" && git push)

.github/workflows/workflows/deploy-notebooks.yml

Whitespace-only changes.

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
![Sanitizer Status](https://github.com/chizkidd/huggingface-deep-RL-course/actions/workflows/clean_notebooks.yml/badge.svg)
1+
[![Notebooks Sanitizer Status](https://github.com/chizkidd/huggingface-deep-RL-course/actions/workflows/clean-notebooks.yml/badge.svg)](https://github.com/chizkidd/huggingface-deep-RL-course/actions/workflows/clean-notebooks.yml)
2+
[![Notebooks Deployment Status](https://github.com/chizkidd/huggingface-deep-RL-course/actions/workflows/deploy-notebooks.yml/badge.svg)](https://github.com/chizkidd/huggingface-deep-RL-course/actions/workflows/deploy-notebooks.yml)
3+
[![View Notebooks](https://img.shields.io/badge/View-Live%20Notebooks-blue?logo=github)](https://chizkidd.github.io/huggingface-deep-RL-course/)
24

35
# Hugging Face Deep RL Course
46

notebooks/unit8b/unit8_part2.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@
364364
},
365365
"source": [
366366
"## Training the agent\n",
367-
"- We're going to train the agent for 4000000 steps it will take approximately 20min"
367+
"- We're going to train the agent for **4000000** steps it will take approximately 20min"
368368
]
369369
},
370370
{

0 commit comments

Comments
 (0)