[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/python/python-docs-de/3.14/tools/branch_diff.py [Back]  [Original]

#!/usr/bin/env python3
"""Vergleicht die Uebersetzungen zweier Branches Datei fuer Datei.

Fuer jede .po-Datei wird verglichen, wie viele Eintraege in beiden Branches
denselben msgstr haben. Der Prozentwert bezieht sich auf die Anzahl der
Eintraege im *neueren* Branch (Vorgabe 3.15), weil der die Zielversion ist:

    Aehnlichkeit = identische Eintraege / Eintraege in 3.15

Ein Eintrag gilt als identisch, wenn msgid (samt msgctxt) und msgstr in
beiden Branches exakt uebereinstimmen -- auch dann, wenn beide leer sind.
Obsolete Eintraege (#~) und der Kopfblock bleiben aussen vor.

Aufruf:
    python branch_diff.py                 # 3.14 gegen 3.15
    python branch_diff.py 3.14 main
    python branch_diff.py --csv bericht.csv
    python branch_diff.py --nur-abweichungen

Es wird nichts ausgecheckt und nichts geaendert -- die Inhalte kommen
ueber "git show" direkt aus den Branches.
"""

import argparse
import csv
import os
import subprocess
import sys
import tempfile

try:
    import polib
except ImportError:
    sys.exit("polib fehlt. Im aktiven venv installieren: pip install polib")

AUSSCHLUSS = (".venv/", ".git/")

KLASSEN = [
    (100.0, "deckungsgleich"),
    (80.0, "ueber 80 %"),
    (50.0, "ueber 50 %"),
    (30.0, "ueber 30 %"),
    (0.0, "unter 30 %"),
]


def git(*args: str) -> str:
    """git aufrufen und stdout zurueckgeben."""
    return subprocess.run(
        ["git", *args], capture_output=True, text=True, check=True
    ).stdout


def po_dateien(branch: str) -> dict:
    """Alle .po-Dateien eines Branches als {pfad: blob-hash}."""
    aus = {}
    for zeile in git("ls-tree", "-r", branch).splitlines():
        kopf, pfad = zeile.split("\t", 1)
        _modus, art, blob = kopf.split()
        if art != "blob" or not pfad.endswith(".po"):
            continue
        if pfad.startswith(AUSSCHLUSS):
            continue
        aus[pfad] = blob
    return aus


def eintraege(blob: str) -> dict:
    """Blob-Inhalt parsen: {(msgctxt, msgid): msgstr}."""
    inhalt = git("cat-file", "blob", blob)
    # polib will einen Dateipfad -- also kurz zwischenspeichern.
    with tempfile.NamedTemporaryFile(
        "w", suffix=".po", encoding="utf-8", delete=False
    ) as f:
        f.write(inhalt)
        tmp = f.name
    try:
        po = polib.pofile(tmp)
        return {(e.msgctxt, e.msgid): e.msgstr for e in po if not e.obsolete}
    finally:
        os.unlink(tmp)


def klasse_von(prozent: float) -> str:
    for grenze, name in KLASSEN:
        if prozent >= grenze:
            return name
    return KLASSEN[-1][1]


def uebersetzt(werte) -> int:
    return sum(1 for v in werte if v.strip())


def main() -> None:
    p = argparse.ArgumentParser(
        description=__doc__,
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    p.add_argument("alt", nargs="?", default="3.14", help="aelterer Branch")
    p.add_argument("neu", nargs="?", default="3.15", help="neuerer Branch")
    p.add_argument("--csv", metavar="DATEI", help="Ergebnis zusaetzlich als CSV")
    p.add_argument(
        "--nur-abweichungen",
        action="store_true",
        help="deckungsgleiche Dateien in der Tabelle weglassen",
    )
    args = p.parse_args()

    alt_dateien = po_dateien(args.alt)
    neu_dateien = po_dateien(args.neu)
    alle = sorted(set(alt_dateien) | set(neu_dateien))
    print(f"{len(alle)} Dateien werden verglichen ...", file=sys.stderr)

    zeilen = []
    for nr, pfad in enumerate(alle, 1):
        if nr % 50 == 0:
            print(f"  {nr}/{len(alle)}", file=sys.stderr)

        a_blob = alt_dateien.get(pfad)
        n_blob = neu_dateien.get(pfad)

        if a_blob is None:
            n = eintraege(n_blob)
            zeilen.append((pfad, 0, len(n), 0, uebersetzt(n.values()),
                           0, None, f"nur in {args.neu}"))
            continue
        if n_blob is None:
            a = eintraege(a_blob)
            zeilen.append((pfad, len(a), 0, uebersetzt(a.values()), 0,
                           0, None, f"nur in {args.alt}"))
            continue

        if a_blob == n_blob:
            # Gleicher Blob -- Inhalt garantiert identisch, kein Parsen noetig.
            a = eintraege(a_blob)
            u = uebersetzt(a.values())
            zeilen.append((pfad, len(a), len(a), u, u, len(a), 100.0,
                           "deckungsgleich"))
            continue

        a = eintraege(a_blob)
        n = eintraege(n_blob)
        gleich = sum(1 for k, v in n.items() if k in a and a[k] == v)
        prozent = 100.0 * gleich / len(n) if n else 0.0
        zeilen.append((pfad, len(a), len(n), uebersetzt(a.values()),
                       uebersetzt(n.values()), gleich, prozent,
                       klasse_von(prozent)))

    # --- Tabelle -----------------------------------------------------------
    rang = {name: i for i, (_g, name) in enumerate(KLASSEN)}
    rang[f"nur in {args.neu}"] = 90
    rang[f"nur in {args.alt}"] = 91
    zeilen.sort(key=lambda z: (rang.get(z[7], 99), -(z[6] or 0), z[0]))

    kopf = (f"| Datei | Eintraege {args.alt} | Eintraege {args.neu} | "
            f"uebers. {args.alt} | uebers. {args.neu} | identisch | "
            f"Aehnlichkeit | Klasse |")
    print()
    print(kopf)
    print("|---|---:|---:|---:|---:|---:|---:|---|")
    for pfad, ea, en, ua, un, gl, pz, kl in zeilen:
        if args.nur_abweichungen and kl == "deckungsgleich":
            continue
        pzt = "" if pz is None else f"{pz:.1f} %"
        print(f"| `{pfad}` | {ea} | {en} | {ua} | {un} | {gl} | {pzt} | {kl} |")

    # --- Zusammenfassung ---------------------------------------------------
    print("\n### Zusammenfassung\n")
    print("| Klasse | Dateien |")
    print("|---|---:|")
    for _grenze, name in KLASSEN:
        anzahl = sum(1 for z in zeilen if z[7] == name)
        print(f"| {name} | {anzahl} |")
    for name in (f"nur in {args.neu}", f"nur in {args.alt}"):
        anzahl = sum(1 for z in zeilen if z[7] == name)
        if anzahl:
            print(f"| {name} | {anzahl} |")
    print(f"| **gesamt** | **{len(zeilen)}** |")

    if args.csv:
        with open(args.csv, "w", newline="", encoding="utf-8") as f:
            w = csv.writer(f)
            w.writerow(["datei", f"eintraege_{args.alt}", f"eintraege_{args.neu}",
                        f"uebersetzt_{args.alt}", f"uebersetzt_{args.neu}",
                        "identisch", "aehnlichkeit_prozent", "klasse"])
            for z in zeilen:
                w.writerow([z[0], z[1], z[2], z[3], z[4], z[5],
                            "" if z[6] is None else f"{z[6]:.1f}", z[7]])
        print(f"\nCSV geschrieben: {args.csv}", file=sys.stderr)


if __name__ == "__main__":
    main()

Web Proxy Viewer  |  New URL  |  Original Page