Files
goodwe-addon/goodwe_controller/tools/maint20_same_code.py
T
2026-11-01 01:13:48 +01:00

84 lines
2.7 KiB
Python

#!/usr/bin/env python3
"""MAINT-20: prove a comment/docstring cut changed no code, and show the ratio.
python3 goodwe_controller/tools/maint20_same_code.py [BASE_REF] [FILE ...]
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.
"""
import ast
import io
import subprocess
import sys
import tokenize
SCOPES = (ast.Module, ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)
def docstring(n):
if isinstance(n, SCOPES) and n.body:
e = n.body[0]
if (isinstance(e, ast.Expr) and isinstance(e.value, ast.Constant)
and isinstance(e.value.value, str)):
return e
return None
def dump(src):
tree = ast.parse(src)
for n in ast.walk(tree):
if docstring(n) is not None:
n.body = n.body[1:] or [ast.Pass()]
return ast.dump(tree)
def counts(src):
doc = set()
for n in ast.walk(ast.parse(src)):
e = docstring(n)
if e is not None:
doc.update(range(e.lineno, e.end_lineno + 1))
skip = (tokenize.NL, tokenize.NEWLINE, tokenize.INDENT, tokenize.DEDENT,
tokenize.ENDMARKER, tokenize.COMMENT)
code, com = set(), set()
for tk in tokenize.generate_tokens(io.StringIO(src).readline):
if tk.type == tokenize.COMMENT:
com.add(tk.start[0])
elif tk.type not in skip:
code.update(range(tk.start[0], tk.end[0] + 1))
code -= doc
return len(code), len(doc) + len(com - code - doc)
def git(*a):
return subprocess.run(["git", *a], check=True, capture_output=True,
text=True).stdout
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()}")
for f in files:
old = git("show", f"{base}:{f}")
new = open(f, encoding="utf-8").read()
same = dump(old) == dump(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")
return 1 if bad else 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))