Skip to content

Instantly share code, notes, and snippets.

@certik
Created March 10, 2026 14:07
Show Gist options
  • Select an option

  • Save certik/327b417b53677939cc7fe853f77ebc13 to your computer and use it in GitHub Desktop.

Select an option

Save certik/327b417b53677939cc7fe853f77ebc13 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
"""
Compute PR-per-person statistics from LFortran release notes.
Usage:
python pr_stats.py [VERSION]
VERSION e.g. "0.61.0" or "v0.61.0". Omit for the latest release.
Looks up each contributor's real name via the GitHub API.
"""
from __future__ import annotations
import os
import re
import sys
import json
import urllib.request
import urllib.error
from collections import Counter
REPO = "lfortran/lfortran"
def fetch_release_body(version: str | None = None) -> tuple[str, str]:
"""Fetch release notes markdown from GitHub. Returns (tag_name, body)."""
if version:
tag = version if version.startswith("v") else f"v{version}"
url = f"https://api.github.com/repos/{REPO}/releases/tags/{tag}"
else:
url = f"https://api.github.com/repos/{REPO}/releases/latest"
headers = {"User-Agent": "lfortran-stats"}
token = os.environ.get("GITHUB_TOKEN")
if token:
headers["Authorization"] = f"token {token}"
req = urllib.request.Request(url, headers=headers)
try:
with urllib.request.urlopen(req) as resp:
data = json.loads(resp.read())
except urllib.error.HTTPError as e:
sys.exit(f"Error fetching release: {e.code} {e.reason}\n"
f" URL: {url}\n"
f" Hint: set GITHUB_TOKEN env var to avoid rate limits")
return data["tag_name"], data.get("body", "")
def parse_prs(text: str) -> Counter:
"""Return a Counter mapping GitHub username -> number of PRs."""
counts: Counter = Counter()
for line in text.splitlines():
m = re.search(r"by @([A-Za-z0-9_-]+(?:\[bot\])?)", line)
if m:
counts[m.group(1)] += 1
return counts
def lookup_name(username: str) -> str:
"""Fetch the user's real name from the GitHub API."""
if username.endswith("[bot]"):
return username
url = f"https://api.github.com/users/{username}"
headers = {"User-Agent": "lfortran-stats"}
token = os.environ.get("GITHUB_TOKEN")
if token:
headers["Authorization"] = f"token {token}"
req = urllib.request.Request(url, headers=headers)
try:
with urllib.request.urlopen(req) as resp:
data = json.loads(resp.read())
return data.get("name") or username
except Exception:
return username
def main():
version = sys.argv[1] if len(sys.argv) > 1 else None
tag, body = fetch_release_body(version)
print(f"Release: {tag}\n")
counts = parse_prs(body)
# Look up real names
names: dict[str, str] = {}
for username in sorted(counts):
names[username] = lookup_name(username)
# Build rows sorted by PR count descending, then alphabetically
rows = sorted(counts.items(), key=lambda x: (-x[1], x[0]))
total = sum(counts.values())
# Compute column widths
name_col = []
for username, count in rows:
real = names[username]
if real != username:
label = f"{real} (@{username})"
else:
label = f"@{username}"
name_col.append(label)
max_name = max(len(n) for n in name_col)
max_name = max(max_name, len("Total"))
count_width = len(str(total))
# Print table
hdr_name = "Contributor"
hdr_prs = "PRs"
max_name = max(max_name, len(hdr_name))
count_width = max(count_width, len(hdr_prs))
print(f"{'─' * (max_name + count_width + 5)}")
print(f" {hdr_name:<{max_name}} {hdr_prs:>{count_width}}")
print(f"{'─' * (max_name + count_width + 5)}")
for label, (username, count) in zip(name_col, rows):
print(f" {label:<{max_name}} {count:>{count_width}}")
print(f"{'─' * (max_name + count_width + 5)}")
print(f" {'Total':<{max_name}} {total:>{count_width}}")
print(f"{'─' * (max_name + count_width + 5)}")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment