#!/usr/bin/env python3
"""Rpartition des langages sur plusieurs dpts, en lignes relles.
Sert produire les pourcentages du README sans dpendre d'un service tiers
(github-readme-stats ne voit que les dpts publics, et son instance hberge
tombe rgulirement). Ici on compte ce qui est sur le disque, dpts privs
compris.
Usage :
python3 tools/languages.py ../Colibri ../The-new-MEE6 ../elipse-rsai
python3 tools/languages.py --json langs.json
Ce qui est exclu : les dossiers d'artefacts (node_modules, .next, dist, build,
coverage, target, vendor) et tout ce qui n'a pas d'extension reconnue. On
compte des LIGNES, pas des octets : un binaire commit ne peut pas gonfler le
rsultat, et un fichier minifi compte pour ce qu'il est une ligne.
"""
import argparse
import collections
import json
import os
import sys
EXCLUDE_DIRS = {
"node_modules", ".next", ".git", "dist", "build", "coverage", ".turbo",
"out", ".venv", "__pycache__", ".gradle", "target", "vendor", ".cache",
}
# Plusieurs extensions peuvent pointer vers un mme langage (.ts et .tsx).
LANGUAGES = {
".ts": "TypeScript", ".tsx": "TypeScript",
".js": "JavaScript", ".jsx": "JavaScript", ".mjs": "JavaScript", ".cjs": "JavaScript",
".dart": "Dart", ".py": "Python", ".java": "Java", ".kt": "Kotlin",
".ps1": "PowerShell", ".sh": "Shell", ".bash": "Shell",
".sql": "SQL", ".prisma": "Prisma",
".css": "HTML / CSS", ".scss": "HTML / CSS", ".html": "HTML / CSS",
".php": "PHP", ".rs": "Rust", ".go": "Go", ".c": "C", ".cpp": "C++",
}
def count_repo(path):
"""Lignes par langage pour un dpt. Les fichiers illisibles sont ignors."""
counts = collections.Counter()
for root, dirs, files in os.walk(path):
dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS]
for name in files:
lang = LANGUAGES.get(os.path.splitext(name)[1].lower())
if lang is None:
continue
try:
with open(os.path.join(root, name), "rb") as fh:
counts[lang] += sum(1 for _ in fh)
except OSError:
continue
return counts
def main():
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("chemins", nargs="+", help="dpts parcourir")
ap.add_argument("--json", help="crire le rsultat dans ce fichier")
ap.add_argument("--seuil", type=float, default=0.1,
help="masquer les langages sous ce pourcentage (dfaut 0.1)")
args = ap.parse_args()
total = collections.Counter()
per_repo = {}
for path in args.chemins:
if not os.path.isdir(path):
print(f"ignor (introuvable) : {path}", file=sys.stderr)
continue
counts = count_repo(path)
per_repo[os.path.basename(os.path.abspath(path))] = dict(counts)
total += counts
grand = sum(total.values())
if not grand:
print("aucune ligne compte", file=sys.stderr)
return 1
print(f"{len(per_repo)} dpts {grand:,} lignes".replace(",", " "))
print("-" * 42)
for lang, n in total.most_common():
pct = 100.0 * n / grand
if pct < args.seuil:
continue
print(f"{lang:9,}".replace(",", " ") + f"{pct:>7.1f} %")
if args.json:
payload = {"total_lignes": grand, "par_langage": dict(total),
"par_depot": per_repo}
with open(args.json, "w", encoding="utf-8") as fh:
json.dump(payload, fh, ensure_ascii=False, indent=2)
print(f"\n JSON crit : {args.json}")
return 0
if __name__ == "__main__":
sys.exit(main())