102 lines
3.2 KiB
Python
102 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Fail if a client-specific term appears inside a product (community_*) module.
|
|
|
|
Deployment modules (matching *_deployment) are exempt, since client identity
|
|
is expected to live there. Run from the repo root:
|
|
|
|
python scripts/check_brand_leak.py
|
|
"""
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent if Path(__file__).name != '<stdin>' else Path('/mnt/extra-addons').parent
|
|
ADDONS_DIR = Path('/mnt/extra-addons') if Path('/mnt/extra-addons').exists() else (REPO_ROOT / "addons")
|
|
|
|
BANNED_WORDS = [
|
|
"tncsc",
|
|
"tamil nadu",
|
|
"tamil nadu cultural society",
|
|
]
|
|
|
|
BANNED_PATTERNS = [
|
|
re.compile(r"\b" + re.escape(word).replace(r"\ ", r"\s+") + r"\b", re.IGNORECASE)
|
|
for word in BANNED_WORDS
|
|
]
|
|
|
|
# Any email/domain that identifies the specific client deployment.
|
|
BANNED_DOMAIN_PATTERN = re.compile(r"\btncsc\.(ca|org|com)\b", re.IGNORECASE)
|
|
|
|
SCAN_EXTENSIONS = {".py", ".xml", ".csv", ".js", ".rst", ".md", ".html", ".txt"}
|
|
|
|
# Never scan the plan itself, or this checker's own word list.
|
|
EXCLUDE_FILENAMES = {"check_brand_leak.py"}
|
|
|
|
|
|
def is_deployment_module(module_dir: Path) -> bool:
|
|
return module_dir.name.endswith("_deployment")
|
|
|
|
|
|
def is_product_module(module_dir: Path) -> bool:
|
|
return module_dir.is_dir() and not module_dir.name.startswith(".")
|
|
|
|
|
|
def iter_product_files():
|
|
if not ADDONS_DIR.exists():
|
|
return
|
|
for module_dir in sorted(ADDONS_DIR.iterdir()):
|
|
if not is_product_module(module_dir) or is_deployment_module(module_dir):
|
|
continue
|
|
for path in module_dir.rglob("*"):
|
|
if not path.is_file():
|
|
continue
|
|
if path.name in EXCLUDE_FILENAMES:
|
|
continue
|
|
if path.suffix.lower() not in SCAN_EXTENSIONS:
|
|
continue
|
|
yield path
|
|
|
|
|
|
def scan_file(path: Path):
|
|
"""Return a list of (line_no, line_text, matched_term) violations."""
|
|
violations = []
|
|
try:
|
|
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
except OSError:
|
|
return violations
|
|
|
|
for line_no, line in enumerate(text.splitlines(), start=1):
|
|
for pattern in BANNED_PATTERNS:
|
|
if pattern.search(line):
|
|
violations.append((line_no, line.strip(), pattern.pattern))
|
|
if BANNED_DOMAIN_PATTERN.search(line):
|
|
violations.append((line_no, line.strip(), BANNED_DOMAIN_PATTERN.pattern))
|
|
return violations
|
|
|
|
|
|
def main() -> int:
|
|
if not ADDONS_DIR.exists():
|
|
print(f"No addons/ directory found at {ADDONS_DIR}; nothing to check.")
|
|
return 0
|
|
|
|
any_violation = False
|
|
for path in iter_product_files():
|
|
for line_no, line_text, term in scan_file(path):
|
|
any_violation = True
|
|
rel_path = path.relative_to(REPO_ROOT)
|
|
print(f"BRAND LEAK: {rel_path}:{line_no}: matched /{term}/ -> {line_text}")
|
|
|
|
if any_violation:
|
|
print(
|
|
"\nOne or more community_* (product) modules reference client-specific "
|
|
"identity. Move this content into the appropriate *_deployment module."
|
|
)
|
|
return 1
|
|
|
|
print("Brand-leak check passed: no client-specific terms found in product modules.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|