From b64c7431086250d8404f226b9c96d37e38511b63 Mon Sep 17 00:00:00 2001 From: Mark Eaton Date: Wed, 13 Aug 2025 11:15:41 -0400 Subject: [PATCH] feat: add Gitea action to update language usage stats in profile README.md Co-authored-by: aider (o3) --- .gitea/workflows/language-stats.yml | 37 ++++++++++++ scripts/language_stats.py | 93 +++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 .gitea/workflows/language-stats.yml create mode 100644 scripts/language_stats.py diff --git a/.gitea/workflows/language-stats.yml b/.gitea/workflows/language-stats.yml new file mode 100644 index 0000000..38d438f --- /dev/null +++ b/.gitea/workflows/language-stats.yml @@ -0,0 +1,37 @@ +name: Update Language Stats + +on: + schedule: + - cron: "0 6 * * *" # run every day at 06:00 UTC + workflow_dispatch: # allow manual runs + push: + paths: + - "scripts/language_stats.py" + +jobs: + lang-stats: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Python deps + run: sudo apt-get update && sudo apt-get install -y python3 python3-pip + + - name: Run language stats script + env: + GITEA_BASE_URL: ${{ secrets.GITEA_BASE_URL }} + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + GITEA_USERNAME: ${{ secrets.GITEA_USERNAME }} + run: python3 scripts/language_stats.py + + - name: Commit and push changes + run: | + git config --global user.name "lang-stats-bot" + git config --global user.email "bot@example.com" + git add .profile/README.md + if git diff --cached --quiet; then + echo "No changes to commit" + else + git commit -m "chore(profile): update language stats" + git push + fi diff --git a/scripts/language_stats.py b/scripts/language_stats.py new file mode 100644 index 0000000..0b81eef --- /dev/null +++ b/scripts/language_stats.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +""" +Gitea Language Stats Action +Calculates the percentage of every programming language used +in all repositories of a given account and writes / updates +a markdown table inside `.profile/README.md` delimited by the +markers . + +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 + +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}" + +# ---------- 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_ + +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 = [ + "## 📊 Language Usage\n", + "| Language | Percent |\n", + "|----------|---------|\n", +] +for lang, nbytes in pairs: + pct = nbytes * 100 / total_bytes + bar = "█" * int(pct / 2) # up to 50 chars + lines.append(f"| {lang} | {pct:5.1f}% {bar} |\n") + +block = "\n" + "".join(lines) + "\n" + +# ---------- patch .profile/README.md ---------- +readme_path = ".profile/README.md" +os.makedirs(".profile", exist_ok=True) +if not os.path.exists(readme_path): + with open(readme_path, "w", encoding="utf-8") as fh: + fh.write(block) +else: + with open(readme_path, "r+", encoding="utf-8") as fh: + content = fh.read() + if "" in content and "" in content: + pre = content.split("")[0] + post = content.split("")[-1] + content = pre + block + post + else: + content = content.rstrip() + "\n\n" + block + fh.seek(0) + fh.write(content) + fh.truncate()