"""Detect orphan templates (never rendered) and orphan static assets.""" from __future__ import annotations import os import re # Walk up until we find the project root (robust to the tool living in a # nested folder such as docs/tools/). _here = os.path.dirname(os.path.abspath(__file__)) while _here != os.path.dirname(_here) and not os.path.isdir(os.path.join(_here, 'app')): _here = os.path.dirname(_here) REPO = _here APP = os.path.join(REPO, 'app') TPL = os.path.join(APP, 'templates') def read(p): return open(p, encoding='utf-8', errors='replace').read() # gather all render_template('x.html') calls rendered = set() for root, dirs, files in os.walk(APP): dirs[:] = [d for d in dirs if d not in ('__pycache__', 'templates')] for f in files: if f.endswith('.py'): s = read(os.path.join(root, f)) rendered |= set(re.findall(r"render_template\(\s*['\"]([^'\"]+)['\"]", s)) # render_template with variable name - note it rendered |= set(re.findall(r"render_template\(\s*([a-z_]+)", s)) - {'f'} # gather extends/include/import references refs = set() for root, dirs, files in os.walk(TPL): for f in files: if f.endswith('.html'): s = read(os.path.join(root, f)) for pat in (r"{%\s*extends\s*['\"]([^'\"]+)['\"]", r"{%\s*include\s*['\"]([^'\"]+)['\"]", r"{%\s*import\s*['\"]([^'\"]+)['\"]", r"render_template\(\s*['\"]([^'\"]+)['\"]"): refs |= set(re.findall(pat, s)) all_tpl = [] for root, dirs, files in os.walk(TPL): for f in files: if f.endswith('.html'): all_tpl.append(os.path.relpath(os.path.join(root, f), TPL)) referenced = rendered | refs orphans = [] for t in sorted(all_tpl): base = os.path.basename(t) if t in referenced or base in referenced: continue # macro/library files are referenced dynamically sometimes orphans.append(t) print(f'templates total : {len(all_tpl)}') print(f'referenced : {len([t for t in all_tpl if t not in orphans])}') print(f'ORPHAN templates : {len(orphans)}') print() for o in orphans: lines = len(read(os.path.join(TPL, o)).splitlines()) print(f' ✗ {o:<58} {lines} lines')