diff --git a/website/build.py b/website/build.py index d447c384..27b9adc0 100644 --- a/website/build.py +++ b/website/build.py @@ -20,6 +20,7 @@ from readme_parser import AlsoSee, ParsedGroup, ParsedSection, parse_readme, par GITHUB_REPO_URL_RE = re.compile(r"^https?://github\.com/([^/]+/[^/]+?)(?:\.git)?/?$") MARKDOWN_LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)\s]+)\)") BULLET_LINE_RE = re.compile(r"^\s*-\s") +ANCHOR_LINK_ATTRS_RE = re.compile(r'href="(#[^"]*)" target="_blank" rel="noopener"') SITE_URL = "https://awesome-python.com/" SITEMAP_URL = f"{SITE_URL}sitemap.xml" SITEMAP_NS = "http://www.sitemaps.org/schemas/sitemap/0.9" @@ -466,6 +467,21 @@ def link_llms_category_index_to_canonical_pages(markdown: str, categories: Seque return "".join(out) +def link_description_anchors_to_category_pages(categories: Sequence[ParsedSection]) -> None: + """Point README anchor links in section descriptions at category pages, which lack those anchors.""" + category_paths = {} + for category in categories: + category_paths[f"#{category['slug']}"] = category_path(category) + category_paths[github_markdown_anchor(category["name"])] = category_path(category) + + def replace_attrs(match: re.Match[str]) -> str: + path = category_paths.get(match.group(1)) + return f'href="{path}"' if path else match.group(0) + + for category in categories: + category["description_html"] = ANCHOR_LINK_ATTRS_RE.sub(replace_attrs, category["description_html"]) + + def build_llms_txt( template_text: str, *, @@ -645,6 +661,7 @@ def build(repo_root: Path) -> None: duplicates = {s for s, n in Counter(all_top_level_slugs).items() if n > 1} if duplicates: raise ValueError(f"slug collision in /categories/ namespace: {sorted(duplicates)}. Rename a category or group so their slugs differ.") + link_description_anchors_to_category_pages(categories) total_entries = sum(c["entry_count"] for c in categories) entries = extract_entries(categories, parsed_groups) build_date = datetime.now(UTC) diff --git a/website/tests/test_build.py b/website/tests/test_build.py index 78495fa8..3588a719 100644 --- a/website/tests/test_build.py +++ b/website/tests/test_build.py @@ -295,6 +295,41 @@ class TestBuild: assert "42" in category_html assert "2026-01-01T00:00:00+00:00" in category_html + def test_build_links_description_anchors_to_category_pages(self, tmp_path): + readme = textwrap.dedent("""\ + # Awesome Python + + Intro. + + ## Projects + + **Tools** + + ## Audio & Video + + _Media tools._ + + - [a1](https://example.com/a1) - A media tool. + + ## Widgets + + _Widget libraries. Also see [Audio & Video](#audio--video) and [Gadgets](#gadgets)._ + + - [w1](https://example.com/w1) - A widget. + + # Contributing + + Help! + """) + (tmp_path / "README.md").write_text(readme, encoding="utf-8") + self._copy_real_templates(tmp_path) + + build(tmp_path) + + category_html = (tmp_path / "website" / "output" / "categories" / "widgets" / "index.html").read_text(encoding="utf-8") + assert 'Also see Audio & Video' in category_html + assert 'Gadgets' in category_html + def test_build_creates_llms_text_alternate_without_sponsors(self, tmp_path): readme = textwrap.dedent("""\ # Awesome Python