103 lines
3.2 KiB
Python
103 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Gitea Language Stats
|
||
Calculates the percentage of every programming language used
|
||
in all repositories of a given account and prints a markdown
|
||
table to stdout, delimited by the
|
||
markers <!--LANG_STATS_START--> … <!--LANG_STATS_END-->.
|
||
|
||
Required environment variables
|
||
--------------------------------
|
||
GITEA_BASE_URL e.g. https://gitea.example.com
|
||
GITEA_TOKEN personal access-token with `repo` scope
|
||
GITEA_USERNAME the account to analyse
|
||
"""
|
||
import os
|
||
import sys
|
||
import requests
|
||
from collections import defaultdict
|
||
from dotenv import load_dotenv
|
||
|
||
load_dotenv()
|
||
|
||
BASE_URL = os.getenv("GITEA_BASE_URL", "").rstrip("/")
|
||
TOKEN = os.getenv("GITEA_TOKEN")
|
||
USERNAME = os.getenv("GITEA_USERNAME")
|
||
|
||
if not BASE_URL or not TOKEN or not USERNAME:
|
||
sys.exit("GITEA_BASE_URL, GITEA_TOKEN and GITEA_USERNAME must be set")
|
||
|
||
session = requests.Session()
|
||
session.headers["Authorization"] = f"token {TOKEN}"
|
||
|
||
# List of files whose byte-counts should be excluded from the totals.
|
||
# Each tuple: (repository_name, file_path, language_name)
|
||
EXCLUDE_FILES: list[tuple[str, str, str]] = [
|
||
("ar-scavenger-hunt", "static/aframe.js", "JavaScript"),
|
||
("Kingsborough-LibGuide", "groups/home/bootstrap3/Vue3-v.4.html", "HTML"),
|
||
("Kingsborough-LibGuide", "groups/home/bootstrap3/Vue3-v.4-css.html", "HTML"),
|
||
("Kingsborough-LibGuide", "groups/home/bootstrap3/Vue3-v.5.html", "HTML"),
|
||
("Kingsborough-LibGuide", "groups/home/bootstrap3/Vue3-v.5-css.html", "HTML"),
|
||
("Kingsborough-LibGuide", "groups/home/bootstrap3/Vue3-v.6.html", "HTML"),
|
||
("Kingsborough-LibGuide", "groups/home/bootstrap3/Vue3-v.6-css.html", "HTML"),
|
||
]
|
||
|
||
# ---------- collect repositories ----------
|
||
page = 1
|
||
repos = []
|
||
while True:
|
||
resp = session.get(
|
||
f"{BASE_URL}/api/v1/users/{USERNAME}/repos",
|
||
params={"page": page, "limit": 50},
|
||
timeout=30,
|
||
)
|
||
resp.raise_for_status()
|
||
batch = resp.json()
|
||
if not batch:
|
||
break
|
||
repos.extend(batch)
|
||
page += 1
|
||
|
||
# ---------- aggregate language bytes ----------
|
||
totals: dict[str, int] = defaultdict(int)
|
||
|
||
for repo in repos:
|
||
langs = session.get(
|
||
f"{BASE_URL}/api/v1/repos/{USERNAME}/{repo['name']}/languages",
|
||
timeout=30,
|
||
).json()
|
||
for lang, bytes_ in langs.items():
|
||
totals[lang] += bytes_
|
||
|
||
# ---------- subtract excluded files ----------
|
||
for repo_name, file_path, language in EXCLUDE_FILES:
|
||
try:
|
||
meta = session.get(
|
||
f"{BASE_URL}/api/v1/repos/{USERNAME}/{repo_name}/contents/{file_path}",
|
||
timeout=30,
|
||
)
|
||
if meta.ok:
|
||
size = meta.json().get("size", 0)
|
||
totals[language] = max(0, totals[language] - size)
|
||
except requests.RequestException:
|
||
# Ignore failures – continue with best-effort stats
|
||
pass
|
||
|
||
total_bytes = sum(totals.values())
|
||
if total_bytes == 0:
|
||
sys.exit("No language data returned from API.")
|
||
|
||
# ---------- build markdown table ----------
|
||
pairs = sorted(totals.items(), key=lambda x: x[1], reverse=True)
|
||
|
||
lines = [
|
||
"## 📊 Languages\n",
|
||
]
|
||
for lang, nbytes in pairs:
|
||
pct = nbytes * 100 / total_bytes
|
||
bar = "█" * int(pct / 2) # up to 50 chars
|
||
lines.append(f"{bar} {lang}{pct:5.1f}%\\")
|
||
|
||
for line in lines:
|
||
print(line)
|