From 666f6e52d0fcaf69e10931e64f72b7707eb7c1b4 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 02:03:00 +0800 Subject: [PATCH] feat(website): add source type badges for non-GitHub entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Detect the hosting source (stdlib, GitLab, Bitbucket, External) from the entry URL and surface it as a small badge in the stars column where a star count would otherwise show an em dash. Stdlib entries also get their own sort tier — between starred entries and other no-star entries — so the standard library is not buried at the bottom of each category. Co-Authored-By: Claude --- website/build.py | 30 +++++++++++++++++++++++++++--- website/static/style.css | 13 +++++++++++++ website/templates/index.html | 1 + 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/website/build.py b/website/build.py index c204cbe..7e87dcb 100644 --- a/website/build.py +++ b/website/build.py @@ -39,6 +39,24 @@ class StarData(TypedDict): GITHUB_REPO_URL_RE = re.compile(r"^https?://github\.com/([^/]+/[^/]+?)(?:\.git)?/?$") +SOURCE_TYPE_DOMAINS = { + "docs.python.org": "stdlib", + "gitlab.com": "GitLab", + "bitbucket.org": "Bitbucket", +} + + +def detect_source_type(url: str) -> str | None: + """Detect source type from URL domain. Returns None for GitHub URLs.""" + if GITHUB_REPO_URL_RE.match(url): + return None + for domain, source_type in SOURCE_TYPE_DOMAINS.items(): + if domain in url: + return source_type + if "github.com" not in url: + return "External" + return None + def extract_github_repo(url: str) -> str | None: """Extract owner/repo from a GitHub repo URL. Returns None for non-GitHub URLs.""" @@ -57,14 +75,19 @@ def load_stars(path: Path) -> dict[str, StarData]: def sort_entries(entries: list[dict]) -> list[dict]: - """Sort entries by stars descending, then name ascending. No-star entries go last.""" + """Sort entries by stars descending, then name ascending. + + Three tiers: starred entries first, stdlib second, other non-starred last. + """ def sort_key(entry: dict) -> tuple[int, int, str]: stars = entry["stars"] name = entry["name"].lower() - if stars is None: + if stars is not None: + return (0, -stars, name) + if entry.get("source_type") == "stdlib": return (1, 0, name) - return (0, -stars, name) + return (2, 0, name) return sorted(entries, key=sort_key) @@ -105,6 +128,7 @@ def extract_entries( "stars": None, "owner": None, "last_commit_at": None, + "source_type": detect_source_type(url), "also_see": entry["also_see"], } seen[url] = merged diff --git a/website/static/style.css b/website/static/style.css index 8f86f88..da7652f 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -334,6 +334,19 @@ th[data-sort].sort-asc::after { text-align: right; } +/* === Source Badges === */ +.source-badge { + display: inline-block; + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.03em; + color: var(--text-muted); + background: var(--bg-input); + padding: 0.15rem 0.45rem; + border-radius: 3px; + white-space: nowrap; +} + /* === Arrow Column === */ .col-arrow { width: 2.5rem; diff --git a/website/templates/index.html b/website/templates/index.html index 9e8b25d..dc5fd55 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -98,6 +98,7 @@ {% if entry.stars is not none %}{{ "{:,}".format(entry.stars) }}{% + elif entry.source_type %}{{ entry.source_type }}{% else %}—{% endif %}