add missing links of category descriptions

This commit is contained in:
Vinta Chen
2026-05-02 23:35:24 +08:00
parent c98cbe2cb1
commit b00395a301
5 changed files with 34 additions and 14 deletions

View File

@@ -27,6 +27,7 @@ class ParsedSection(TypedDict):
name: str
slug: str
description: str # plain text, links resolved to text
description_html: str # inline HTML, properly escaped
entries: list[ParsedEntry]
entry_count: int
@@ -113,22 +114,22 @@ def _heading_text(node: SyntaxTreeNode) -> str:
return ""
def _extract_description(nodes: list[SyntaxTreeNode]) -> str:
"""Extract description from the first paragraph if it's a single <em> block.
def _extract_description_children(nodes: list[SyntaxTreeNode]) -> list[SyntaxTreeNode]:
"""Extract description children from the first paragraph if it's a single <em> block.
Pattern: _Libraries for foo._ -> "Libraries for foo."
"""
if not nodes:
return ""
return []
first = nodes[0]
if first.type != "paragraph":
return ""
return []
for child in first.children:
if child.type == "inline" and len(child.children) == 1:
em = child.children[0]
if em.type == "em":
return render_inline_text(em.children)
return ""
return em.children
return []
# --- Entry extraction --------------------------------------------------------
@@ -258,14 +259,17 @@ def _parse_section_entries(content_nodes: list[SyntaxTreeNode]) -> list[ParsedEn
def _build_section(name: str, body: list[SyntaxTreeNode]) -> ParsedSection:
"""Build a ParsedSection from a heading name and its body nodes."""
desc = _extract_description(body)
content_nodes = body[1:] if desc else body
desc_children = _extract_description_children(body)
desc = render_inline_text(desc_children) if desc_children else ""
desc_html = render_inline_html(desc_children) if desc_children else ""
content_nodes = body[1:] if desc_children else body
entries = _parse_section_entries(content_nodes)
entry_count = len(entries) + sum(len(e["also_see"]) for e in entries)
return ParsedSection(
name=name,
slug=slugify(name),
description=desc,
description_html=desc_html,
entries=entries,
entry_count=entry_count,
)

View File

@@ -423,6 +423,17 @@ kbd {
text-wrap: pretty;
}
.category-subtitle a {
color: var(--hero-text);
text-decoration: underline;
text-decoration-color: oklch(100% 0 0 / 0.32);
text-underline-offset: 0.2em;
}
.category-subtitle a:hover {
text-decoration-color: oklch(100% 0 0 / 0.7);
}
.category-results {
padding-top: clamp(2.5rem, 5vw, 3.75rem);
}

View File

@@ -25,8 +25,8 @@
<div class="category-hero-copy">
<h1>{{ category.name }}</h1>
{% if category.description %}
<p class="category-subtitle">{{ category.description }}</p>
{% if category.description_html %}
<p class="category-subtitle">{{ category.description_html | safe }}</p>
{% endif %}
</div>
</div>

View File

@@ -146,7 +146,7 @@ class TestBuild:
## Widgets
_Widget libraries._
_Widget libraries. Also see [awesome-widgets](https://example.com/widgets)._
- [w1](https://example.com) - A widget.
@@ -235,7 +235,7 @@ class TestBuild:
## Widgets
_Widget libraries._
_Widget libraries. Also see [awesome-widgets](https://example.com/widgets)._
- [w1](https://example.com/w1) - A widget.
- [w2](https://github.com/owner/w2) - A starred widget.
@@ -276,12 +276,12 @@ class TestBuild:
assert 'href="/categories/widgets/"' in index_html
assert 'data-value="Widgets"' in index_html
assert parser.title.strip() == "Widgets Python Libraries | Awesome Python"
assert parser.meta_by_name["description"] == "Explore 2 curated Python projects in Widgets. Widget libraries."
assert parser.meta_by_name["description"] == "Explore 2 curated Python projects in Widgets. Widget libraries. Also see awesome-widgets."
assert parser.links_by_rel["canonical"] == "https://awesome-python.com/categories/widgets/"
assert parser.meta_by_property["og:url"] == "https://awesome-python.com/categories/widgets/"
assert '<link rel="alternate" type="text/markdown" href="/index.md" />' not in category_html
assert "<h1>Widgets</h1>" in category_html
assert "Widget libraries." in category_html
assert 'Widget libraries. Also see <a href="https://example.com/widgets" target="_blank" rel="noopener">awesome-widgets</a>.' in category_html
assert 'href="https://example.com/w1"' in category_html
assert "A widget." in category_html
assert 'href="https://github.com/owner/w2"' in category_html

View File

@@ -180,7 +180,9 @@ class TestParseReadmeSections:
groups = parse_readme(MINIMAL_README)
cats = groups[0]["categories"]
assert cats[0]["description"] == "Libraries for alpha stuff."
assert cats[0]["description_html"] == "Libraries for alpha stuff."
assert cats[1]["description"] == "Tools for beta."
assert cats[1]["description_html"] == "Tools for beta."
def test_contributing_skipped(self):
groups = parse_readme(MINIMAL_README)
@@ -216,6 +218,7 @@ class TestParseReadmeSections:
groups = parse_readme(readme)
cats = groups[0]["categories"]
assert cats[0]["description"] == ""
assert cats[0]["description_html"] == ""
assert cats[0]["entries"][0]["name"] == "item"
def test_description_with_link_stripped(self):
@@ -237,6 +240,7 @@ class TestParseReadmeSections:
groups = parse_readme(readme)
cats = groups[0]["categories"]
assert cats[0]["description"] == "Algorithms. Also see awesome-algos."
assert cats[0]["description_html"] == 'Algorithms. Also see <a href="https://example.com" target="_blank" rel="noopener">awesome-algos</a>.'
class TestParseGroupedReadme:
@@ -481,6 +485,7 @@ class TestParseRealReadme:
algos = next(c for c in self.cats if c["name"] == "Algorithms and Design Patterns")
assert "awesome-algorithms" in algos["description"]
assert "https://" not in algos["description"]
assert 'href="https://github.com/tayllan/awesome-algorithms"' in algos["description_html"]
def test_miscellaneous_in_own_group(self):
misc_group = next((g for g in self.groups if g["name"] == "Miscellaneous"), None)