MAINT-20 part 1: same-code and ratio script
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
8afadbdd9a
commit
f31307105a
@@ -0,0 +1,83 @@
|
||||
#!/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:]))
|
||||
Reference in New Issue
Block a user