From 0fb4c2b5e27c0859c56d42e502b48560ff174638 Mon Sep 17 00:00:00 2001 From: glenn schrooyen Date: Sun, 1 Nov 2026 03:27:45 +0100 Subject: [PATCH] MAINT-20 part 2: same-code script checks -OO code, added/removed files, allowed paths, docstring list Co-Authored-By: Claude --- goodwe_controller/tools/maint20_same_code.py | 85 +++++++++++++++++--- 1 file changed, 75 insertions(+), 10 deletions(-) diff --git a/goodwe_controller/tools/maint20_same_code.py b/goodwe_controller/tools/maint20_same_code.py index de3b5de..610b56c 100644 --- a/goodwe_controller/tools/maint20_same_code.py +++ b/goodwe_controller/tools/maint20_same_code.py @@ -5,12 +5,19 @@ For each FILE (default: every goodwe_controller/app/*.py that differs from BASE_REF, default release/1.0) the ast.dump() of the tree with docstrings -removed must be identical at BASE_REF and in the working tree. Also prints -code lines vs comment-only + docstring lines (whole lines, via tokenize) before -and after. Run from the repository root. Exit 1 on any code difference. +removed must be identical at BASE_REF and in the working tree, and so must +the `python -OO` compiled code (a wholly removed docstring leaves a no-op +there). An added or removed app/*.py file is a code difference. The diff +against BASE_REF may touch only goodwe_controller/app/*.py, +goodwe_controller/DECISIONS/*.md and this script. Also prints code lines vs +comment-only + docstring lines (whole lines, via tokenize) before and after, +and the docstrings removed entirely or added. Run from the repository root. +Exit 1 on any difference. """ import ast import io +import os +import re import subprocess import sys import tokenize @@ -53,29 +60,87 @@ def counts(src): return len(code), len(doc) + len(com - code - doc) +def qualnames(src): + out = {} + + def walk(node, prefix): + for n in ast.iter_child_nodes(node): + if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef, + ast.ClassDef)): + q = prefix + n.name + out[q] = docstring(n) is not None + walk(n, q + ".") + out[""] = docstring(ast.parse(src)) is not None + walk(ast.parse(src), "") + return out + + +def code_sig(co): + consts = tuple(code_sig(c) if hasattr(c, "co_code") else c + for c in co.co_consts) + return (co.co_name, co.co_code, consts, co.co_names, co.co_varnames, + co.co_freevars, co.co_cellvars, co.co_flags, co.co_argcount, + co.co_stacksize, co.co_exceptiontable) + + +def oo_same(old, new): + return (code_sig(compile(old, "f", "exec", optimize=2)) + == code_sig(compile(new, "f", "exec", optimize=2))) + + def git(*a): return subprocess.run(["git", *a], check=True, capture_output=True, text=True).stdout +def exists(base, f): + return subprocess.run(["git", "cat-file", "-e", f"{base}:{f}"], + capture_output=True).returncode == 0 + + +ALLOWED = re.compile(r"^goodwe_controller/(app/[^/]+\.py|DECISIONS/[^/]+\.md|" + r"tools/maint20_same_code\.py)$") + + +def ratio(m, c): + return f"{m / c:.2f}" if c else "n/a" + + def main(argv): base = argv[0] if argv else "release/1.0" - files = argv[1:] or [ - f for f in git("diff", "--name-only", base, "--", - "goodwe_controller/app").split() - if f.endswith(".py")] bad = 0 print(f"base {base} = {git('rev-parse', base).strip()}") + changed = git("diff", "--name-only", base).split() + for f in changed: + if not ALLOWED.match(f): + print(f"FAIL: diff touches {f}, outside app/*.py, DECISIONS/*.md " + "and this script") + bad += 1 + files = argv[1:] or [f for f in changed if re.match( + r"^goodwe_controller/app/[^/]+\.py$", f)] for f in files: + in_base, in_head = exists(base, f), os.path.exists(f) + if in_base != in_head: + print(f"CODE DIFFERS {f} " + f"{'removed' if in_base else 'added'} file") + bad += 1 + continue old = git("show", f"{base}:{f}") new = open(f, encoding="utf-8").read() same = dump(old) == dump(new) + oo = oo_same(old, new) (c0, m0), (c1, m1) = counts(old), counts(new) print(f"{'SAME CODE' if same else 'CODE DIFFERS'} {f} " f"code {c0}->{c1} comment+doc {m0}->{m1} " - f"ratio {m0 / c0:.2f}->{m1 / c1:.2f}") - bad += not same - print("OK: no code change" if not bad else f"FAIL: {bad} file(s) differ") + f"ratio {ratio(m0, c0)}->{ratio(m1, c1)} " + f"-OO {'same' if oo else 'DIFFERS'}") + bad += (not same) + (not oo) + qo, qn = qualnames(old), qualnames(new) + for q in sorted(q for q in qo if qo[q] and not qn.get(q, True)): + print(f" docstring REMOVED: {f}:{q}") + for q in sorted(q for q in qn if qn[q] and not qo.get(q, True)): + print(f" docstring ADDED: {f}:{q}") + print("OK: no code change" if not bad else f"FAIL: {bad} problem(s)") return 1 if bad else 0