84 lines
2.7 KiB
Python
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:]))
|