Re: git: 46c495e6e8db - main - Tools/scripts/repology-get-maintainer-stats.py: add convenience script

From: Roman Bogorodskiy <novel_at_freebsd.org>
Date: Sat, 26 Sep 2026 06:06:58 UTC
  Yuri Victorovich wrote:

> The branch main has been updated by yuri:
> 
> URL: https://cgit.FreeBSD.org/ports/commit/?id=46c495e6e8dbbba2854a30dac5335c747fe9bdbf
> 
> commit 46c495e6e8dbbba2854a30dac5335c747fe9bdbf
> Author:     Yuri Victorovich <yuri@FreeBSD.org>
> AuthorDate: 2026-09-25 22:35:18 +0000
> Commit:     Yuri Victorovich <yuri@FreeBSD.org>
> CommitDate: 2026-09-25 23:50:05 +0000
> 
>     Tools/scripts/repology-get-maintainer-stats.py: add convenience script
>     
>     It retrieves and displays aggregate statistics from Repology, same
>     that is displayed on the web site.
> ---
>  Tools/scripts/repology-get-maintainer-stats.py | 202 +++++++++++++++++++++++++
>  1 file changed, 202 insertions(+)
> 
> diff --git a/Tools/scripts/repology-get-maintainer-stats.py b/Tools/scripts/repology-get-maintainer-stats.py
> new file mode 100755
> index 000000000000..9a59a428f750
> --- /dev/null
> +++ b/Tools/scripts/repology-get-maintainer-stats.py
> @@ -0,0 +1,202 @@
> +#!/usr/bin/env python3
> +#
> +# repology-get-maintainer-stats.py - print aggregate FreeBSD ports maintainer
> +# statistics as reported by repology.org.
> +#
> +# MAINTAINER= yuri@FreeBSD.org
> +#
> +
> +import argparse
> +import getpass
> +import importlib.util
> +import re
> +import sys
> +from urllib.parse import quote
> +
> +REPOLOGY_MAINTAINER_URL = "https://repology.org/maintainer/{maintainer}"
> +USER_AGENT = (
> +    "repology-get-maintainer-stats.py "
> +    "(FreeBSD ports tools; https://www.freebsd.org/ports/)"
> +)
> +
> +
> +def check_dependencies() -> None:
> +    """Verify that all non-stdlib dependencies are available."""
> +    missing = []
> +    for module, package in (("requests", "requests"), ("bs4", "beautifulsoup4")):
> +        if importlib.util.find_spec(module) is None:
> +            missing.append(package)
> +    if missing:
> +        print(
> +            "Error: missing required package(s): " + ", ".join(missing),
> +            file=sys.stderr,
> +        )
> +        print(
> +            "Install with: sudo pkg install " + " ".join(missing),
> +            file=sys.stderr,

This needs a Python package prefix for the command to be correct.

> +        )
> +        sys.exit(1)
> +
> +
> +def parse_args() -> argparse.Namespace:
> +    parser = argparse.ArgumentParser(
> +        description="Print FreeBSD ports maintainer statistics from repology.org."
> +    )
> +    parser.add_argument(
> +        "maintainer",
> +        nargs="?",
> +        default=f"{getpass.getuser()}@FreeBSD.org",
> +        help="maintainer e-mail address (default: %(default)s)",
> +    )
> +    args = parser.parse_args()
> +    if len(sys.argv) > 2:
> +        parser.error("at most one maintainer argument is allowed")
> +    return args
> +
> +
> +def fetch_maintainer_page(maintainer: str) -> str:
> +    import requests
> +
> +    url = REPOLOGY_MAINTAINER_URL.format(maintainer=quote(maintainer, safe="@"))
> +    try:
> +        response = requests.get(url, headers={"User-Agent": USER_AGENT}, timeout=30)
> +    except requests.RequestException as exc:
> +        print(f"Error fetching {url}: {exc}", file=sys.stderr)
> +        sys.exit(1)
> +    if response.status_code == 404:
> +        print(
> +            f"Error: maintainer '{maintainer}' was not found on repology.org.",
> +            file=sys.stderr,
> +        )
> +        sys.exit(1)
> +    response.raise_for_status()
> +    return response.text
> +
> +
> +def _cell_text(cell) -> str:
> +    """Return stripped text from a BeautifulSoup table cell."""
> +    return " ".join(cell.stripped_strings)
> +
> +
> +def extract_freebsd_stats(html: str) -> dict[str, str]:
> +    """Parse the repology maintainer page and return FreeBSD Ports aggregate stats."""
> +    from bs4 import BeautifulSoup
> +
> +    soup = BeautifulSoup(html, "html.parser")
> +

What is the reason to parse HTML instead of using API as documented on
https://repology.org/api/v1?