diff --git a/.gitignore b/.gitignore index ba307609..9f878b42 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ __pycache__/ website/output/ website/data/* !website/data/pypi_name_overrides.json +!website/data/redirects.json # agents .playwright-cli/ diff --git a/website/build.py b/website/build.py index 67346cec..89dbc4a3 100644 --- a/website/build.py +++ b/website/build.py @@ -772,6 +772,18 @@ def build(repo_root: Path) -> None: parent_category=cat_by_slug[cat_slug], ) + redirects_file = website / "data" / "redirects.json" + redirects = json.loads(redirects_file.read_text(encoding="utf-8")) if redirects_file.exists() else {} + for old_path, new_path in redirects.items(): + tpl_redirect = env.get_template("redirect.html") + stub = site_dir / old_path.strip("/") / "index.html" + if stub.exists(): + raise ValueError(f"redirect source {old_path} is a live page; remove it from redirects.json") + if not (site_dir / new_path.strip("/") / "index.html").exists(): + raise ValueError(f"redirect target {new_path} for {old_path} does not exist") + stub.parent.mkdir(parents=True, exist_ok=True) + stub.write_text(tpl_redirect.render(target_url=SITE_URL + new_path.lstrip("/")), encoding="utf-8") + static_src = website / "static" static_dst = site_dir / "static" if static_src.exists(): diff --git a/website/data/redirects.json b/website/data/redirects.json new file mode 100644 index 00000000..af60550e --- /dev/null +++ b/website/data/redirects.json @@ -0,0 +1,10 @@ +{ + "/categories/": "/", + "/categories/ai-and-agents/pre-trained-models-and-inference/": "/categories/ai-and-agents/pre-trained-models/", + "/categories/cli-tools/productivity-tools/": "/categories/cli-tools/", + "/categories/code-analysis/code-linters/": "/categories/code-analysis/linters-and-formatters/", + "/categories/data-analysis/financial-data/": "/categories/data-ingestion-etl/financial-data/", + "/categories/distributed-computing/batch-processing/": "/categories/distributed-computing/", + "/categories/gui-development/terminal/": "/categories/cli-development/tui-frameworks/", + "/categories/web-servers/rpc/": "/categories/web-apis/rpc/" +} diff --git a/website/templates/redirect.html b/website/templates/redirect.html new file mode 100644 index 00000000..292c443c --- /dev/null +++ b/website/templates/redirect.html @@ -0,0 +1,11 @@ + + + +Redirecting… + + + + +

Redirecting…

+Click here if you are not redirected. + diff --git a/website/tests/test_build.py b/website/tests/test_build.py index 2f0af663..a1d11503 100644 --- a/website/tests/test_build.py +++ b/website/tests/test_build.py @@ -955,6 +955,56 @@ class TestBuild: assert 'data-url="/categories/ai-ml/"' in index_html assert 'data-url="/categories/deep-learning/vision/"' in index_html + _REDIRECT_README = textwrap.dedent("""\ + # Awesome Python + + Intro. + + ## Projects + + **Tools** + + ### Widgets + + - [w1](https://example.com/w1) - A widget. + + ## Contributing + + Help! + """) + + def _write_redirects(self, tmp_path, redirects): + data_dir = tmp_path / "website" / "data" + data_dir.mkdir(parents=True) + (data_dir / "redirects.json").write_text(json.dumps(redirects), encoding="utf-8") + + def test_build_writes_redirect_stub_outside_sitemap(self, tmp_path): + self._copy_real_templates(tmp_path) + (tmp_path / "README.md").write_text(self._REDIRECT_README, encoding="utf-8") + self._write_redirects(tmp_path, {"/categories/old-widgets/": "/categories/widgets/"}) + build(tmp_path) + + site = tmp_path / "website" / "output" + stub = (site / "categories" / "old-widgets" / "index.html").read_text(encoding="utf-8") + assert '' in stub + assert '' in stub + assert '' in stub + assert "old-widgets" not in (site / "sitemap.xml").read_text(encoding="utf-8") + + def test_build_rejects_redirect_to_missing_page(self, tmp_path): + self._copy_real_templates(tmp_path) + (tmp_path / "README.md").write_text(self._REDIRECT_README, encoding="utf-8") + self._write_redirects(tmp_path, {"/categories/old-widgets/": "/categories/gone/"}) + with pytest.raises(ValueError, match="does not exist"): + build(tmp_path) + + def test_build_rejects_redirect_over_live_page(self, tmp_path): + self._copy_real_templates(tmp_path) + (tmp_path / "README.md").write_text(self._REDIRECT_README, encoding="utf-8") + self._write_redirects(tmp_path, {"/categories/widgets/": "/"}) + with pytest.raises(ValueError, match="is a live page"): + build(tmp_path) + # --------------------------------------------------------------------------- # extract_github_repo