#!/usr/bin/env python3 """Scan tools/*.py for DRY smells: repeated sum(1 for ...)/list-comprehension patterns, duplicated function bodies, and other refactor candidates.""" import ast, os, hashlib, collections, sys REPO = "/root/troupe-standup" TOOLS = os.path.join(REPO, "tools") def scan_file(path): src = open(path, encoding="utf-8").read() lines = src.splitlines() info = collections.Counter() # sum(1 for ... patterns - potential DRY info["sum_gen"] = sum(1 for l in lines if "sum(1 for" in l) # len([...]) vs len([...]) patterns info["len_listcomp"] = sum(1 for l in lines if "len([" in l or "len([" in l) # list comprehensions that filter info["listcomp_filter"] = sum(1 for l in lines if "if r[" in l) # count() calls on lists info["count_method"] = sum(1 for l in lines if ".count(" in l) # manual counting loops info["manual_count"] = sum(1 for l in lines if "+= 1" in l) # Find repeated string literals / patterns # Look for duplicated .strip() calls, split() calls etc. # Check for duplicate function bodies using AST try: tree = ast.parse(src) funcs = [(node.name, node.body) for node in ast.walk(tree) if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))] # hash each function body body_hashes = {} for name, body in funcs: h = hashlib.sha256(ast.unparse(body).encode()).hexdigest()[:12] body_hashes.setdefault(h, []).append(name) dup_funcs = {h: names for h, names in body_hashes.items() if len(names) > 1} except Exception as e: dup_funcs = {} funcs = [] # Find repeated string patterns (lines that appear 3+ times) line_counts = collections.Counter(l.strip() for l in lines if l.strip()) repeated_lines = [(l, c) for l, c in line_counts.items() if c >= 3 and len(l) > 15] return info, dup_funcs, repeated_lines, len(lines), funcs for f in sorted(os.listdir(TOOLS)): if not f.endswith(".py") or f.startswith("_"): continue path = os.path.join(TOOLS, f) info, dup_funcs, repeated, nlines, funcs = scan_file(path) if info["sum_gen"] >= 2 or dup_funcs or repeated: print(f"\n=== {f} ({nlines} lines, {len(funcs)} funcs) ===") if info["sum_gen"]: print(f" sum(1 for ...) x{info['sum_gen']}") if info["len_listcomp"]: print(f" len([...]) x{info['len_listcomp']}") if info["listcomp_filter"]: print(f" listcomp with if-filter x{info['listcomp_filter']}") if info["count_method"]: print(f" .count() calls x{info['count_method']}") if info["manual_count"]: print(f" manual +=1 count x{info['manual_count']}") if dup_funcs: print(f" DUPLICATE function bodies: {dup_funcs}") for l, c in repeated[:5]: print(f" repeated x{c}: {l[:90]}")