Skip to content

Commit 825d27e

Browse files
TrevorAustinclaude
andcommitted
add base_url support for GitHub Pages project sites
All internal links were hardcoded to the domain root (/css/..., /lecture_notes/..., etc.), which breaks on project sites served at a subpath like /course_materials/. Threads a --base-url CLI arg through build.py, Jinja2 globals, and image path rewriting. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 677d47d commit 825d27e

8 files changed

Lines changed: 54 additions & 23 deletions

File tree

.github/workflows/build-and-deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
with:
2424
python-version: "3.12"
2525
- run: pip install -r requirements.txt
26-
- run: python build.py
26+
- run: python build.py --base-url "/${{ github.event.repository.name }}"
2727
- uses: actions/upload-pages-artifact@v3
2828
with:
2929
path: _site

build.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import argparse
12
import glob
2-
import os
33
import re
44
import shutil
55
from pathlib import Path
@@ -10,8 +10,12 @@
1010
from preprocessor import split_slides, process_slide, rewrite_image_paths
1111

1212

13-
def build_site(output_dir: str = "_site"):
14-
"""Build the entire static site."""
13+
def build_site(output_dir: str = "_site", base_url: str = ""):
14+
"""Build the entire static site.
15+
16+
base_url: path prefix for project sites (e.g., "/course_materials").
17+
Empty string for sites served at the domain root.
18+
"""
1519
root = Path(__file__).parent
1620
out = Path(output_dir)
1721

@@ -23,6 +27,7 @@ def build_site(output_dir: str = "_site"):
2327
# Set up Jinja2 — autoescape=False since templates use {{ content }}
2428
# with pre-rendered HTML and this is a static site generator, not a web app
2529
env = Environment(loader=FileSystemLoader(root / "templates"), autoescape=False)
30+
env.globals["base_url"] = base_url
2631

2732
# Copy static assets
2833
shutil.copytree(root / "static" / "css", out / "css")
@@ -48,11 +53,11 @@ def build_site(output_dir: str = "_site"):
4853

4954
for i, slide_md in enumerate(slide_strings, 1):
5055
html_content, classes = process_slide(slide_md)
51-
html_content = rewrite_image_paths(html_content)
56+
html_content = rewrite_image_paths(html_content, base_url)
5257

53-
prev_url = f"/lecture_notes/week_{week_num}/{i - 1}/" if i > 1 else ""
54-
next_url = f"/lecture_notes/week_{week_num}/{i + 1}/" if i < total_slides else ""
55-
first_url = f"/lecture_notes/week_{week_num}/1/"
58+
prev_url = f"{base_url}/lecture_notes/week_{week_num}/{i - 1}/" if i > 1 else ""
59+
next_url = f"{base_url}/lecture_notes/week_{week_num}/{i + 1}/" if i < total_slides else ""
60+
first_url = f"{base_url}/lecture_notes/week_{week_num}/1/"
5661

5762
slide_html = env.get_template("slide.html").render(
5863
content=html_content,
@@ -97,4 +102,11 @@ def build_site(output_dir: str = "_site"):
97102

98103

99104
if __name__ == "__main__":
100-
build_site()
105+
parser = argparse.ArgumentParser(description="Build the static site.")
106+
parser.add_argument(
107+
"--base-url",
108+
default="",
109+
help="URL path prefix for project sites (e.g., /course_materials)",
110+
)
111+
args = parser.parse_args()
112+
build_site(base_url=args.base_url.rstrip("/"))

preprocessor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ def process_slide(slide_md: str) -> tuple[str, list[str]]:
174174
return html, classes
175175

176176

177-
def rewrite_image_paths(html: str) -> str:
177+
def rewrite_image_paths(html: str, base_url: str = "") -> str:
178178
"""Rewrite relative image src paths to absolute /lecture_notes/ paths.
179179
Only affects <img> tags, not <script> or other elements.
180180
"""
@@ -183,6 +183,6 @@ def replace_img_src(match):
183183
src = match.group(2)
184184
if src.startswith(("http://", "https://", "/")):
185185
return match.group(0)
186-
return f'<img{prefix}src="/lecture_notes/{src}"'
186+
return f'<img{prefix}src="{base_url}/lecture_notes/{src}"'
187187

188188
return re.sub(r'<img([^>]*)src="([^"]*)"', replace_img_src, html)

templates/base.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
<meta charset="utf-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1">
66
<title>{% block title %}Web Development{% endblock %}</title>
7-
<link rel="stylesheet" href="/css/style.css">
7+
<link rel="stylesheet" href="{{ base_url }}/css/style.css">
88
</head>
99
<body>
1010
<nav id="header">
1111
<ul>
12-
<li><a href="/">Home</a></li>
13-
<li><a href="/lecture_notes/">Lectures</a></li>
14-
<li><a href="/syllabus/">Syllabus</a></li>
15-
<li><a href="/examples/">Examples</a></li>
12+
<li><a href="{{ base_url }}/">Home</a></li>
13+
<li><a href="{{ base_url }}/lecture_notes/">Lectures</a></li>
14+
<li><a href="{{ base_url }}/syllabus/">Syllabus</a></li>
15+
<li><a href="{{ base_url }}/examples/">Examples</a></li>
1616
</ul>
1717
</nav>
1818
<main>

templates/index.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
<h1>Web Development</h1>
88
<h3>MPCS 52553 — Spring 2026</h3>
99
<ul>
10-
<li><a href="/syllabus/">Syllabus</a></li>
11-
<li><a href="/lecture_notes/">Lecture Notes</a></li>
12-
<li><a href="/examples/">Examples &amp; Labs</a></li>
10+
<li><a href="{{ base_url }}/syllabus/">Syllabus</a></li>
11+
<li><a href="{{ base_url }}/lecture_notes/">Lecture Notes</a></li>
12+
<li><a href="{{ base_url }}/examples/">Examples &amp; Labs</a></li>
1313
</ul>
1414
<p>
1515
Course materials are on

templates/lecture_index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ <h1>Lecture Notes</h1>
1313
</p>
1414
<ul>
1515
{% for week in weeks %}
16-
<li><a href="/lecture_notes/week_{{ week }}/1/">Week {{ week }}</a></li>
16+
<li><a href="{{ base_url }}/lecture_notes/week_{{ week }}/1/">Week {{ week }}</a></li>
1717
{% endfor %}
1818
</ul>
1919
</div>

templates/slide.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,5 @@
3434
{% endblock %}
3535

3636
{% block scripts %}
37-
<script src="/js/navigation.js"></script>
37+
<script src="{{ base_url }}/js/navigation.js"></script>
3838
{% endblock %}

tests/test_build.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import os
2-
import shutil
31
from pathlib import Path
42
from build import build_site
53

@@ -37,3 +35,24 @@ def test_build_no_week_8_old(tmp_path, monkeypatch):
3735
build_site(output_dir=str(tmp_path / "_site"))
3836

3937
assert not (tmp_path / "_site" / "lecture_notes" / "week_8_old").exists()
38+
39+
40+
def test_build_base_url(tmp_path, monkeypatch):
41+
"""base_url is prefixed on all internal links."""
42+
monkeypatch.chdir(Path(__file__).parent.parent)
43+
build_site(output_dir=str(tmp_path / "_site"), base_url="/course_materials")
44+
site = tmp_path / "_site"
45+
46+
# Homepage links use base_url
47+
homepage = (site / "index.html").read_text()
48+
assert 'href="/course_materials/syllabus/"' in homepage
49+
50+
# Slide navigation uses base_url
51+
slide = (site / "lecture_notes" / "week_1" / "2" / "index.html").read_text()
52+
assert 'data-prev="/course_materials/lecture_notes/week_1/1/"' in slide
53+
assert "/course_materials/js/navigation.js" in slide
54+
assert "/course_materials/css/style.css" in slide
55+
56+
# Lecture index uses base_url
57+
lecture_index = (site / "lecture_notes" / "index.html").read_text()
58+
assert "/course_materials/lecture_notes/week_1/1/" in lecture_index

0 commit comments

Comments
 (0)