"""Ticket #18: offline naming-sweep — no old-name strings on user-visible surfaces. The fork takes its own name everywhere a user can see it. Anything still spelling the old name must be on the allowlist below: - upstream provenance (``VocaHQ/vocalinux`` URLs), - the retained internal Python module name (``src/vocalinux`` paths, ``vocalinux.*`` imports/attributes, ``-m vocalinux.main``), - retained internal class names (``VocalinuxEngine*``, ``VocalinuxDBusService``), - one-time-legacy import sources (old config/dictionary/bus literals), - generated or historical files (``uv.lock``, ``requirements/*.txt``, ``CHANGELOG.md``), - the rest of ``web/`` (owned by ticket #24; only ``page.tsx`` and ``package.json`` belong to this ticket). """ import os import re REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) OLD_NAME = re.compile(r"vocalinux", re.IGNORECASE) SKIP_DIRS = {".git", "__pycache__", ".venv", "venv", "node_modules", ".next", "build", "dist"} SKIP_SUFFIXES = (".egg-info",) SKIP_FILES = {"uv.lock", "CHANGELOG.md"} SKIP_PREFIXES = ("requirements/",) BINARY_EXTS = ( ".wav", ".mp3", ".ogg", ".png", ".svg", ".ico", ".icns", ".bin", ".onnx", ".pt", ".zip", ".tar", ".gz", ".whl", ".AppImage", ".zsync", ".pyc", ) #: web/ belongs to ticket #24 except the two rename-inventory files. WEB_OWNED = {"web/src/app/page.tsx", "web/package.json"} def _allowed(path: str, line: str) -> bool: """Return True when an old-name match is an allowlisted surface.""" low = line.lower() if "vocahq/" in low or "jatinkrmalik/vocalinux" in low: return True if "vocalinux" in low and "voculinux" in low: # Migration-compat shims that name both installs at once. return True if "/{name}/vocalinux" in line: # Upstream repo-name template (retained provenance). return True if path == "snap/local/check_runtime.py" and '"vocalinux"' in line: # Smoke imports resolve the retained import package. return True if re.search(r'vocalinux\.["a-z_]', line): return True if line.strip().rstrip(",").lstrip("/ ") == '"vocalinux"': # Retained module-dir path segment element in tests and tooling # (single-line and os.path.join element forms). return True if 'site / "vocalinux"' in line or '"platform/vocalinux"' in line: # Fixture module trees named after the retained import package. return True if '"vocalinux/' in line: # Fixture module file paths under the retained package name. return True if "/vocalinux/" in line or "src/vocalinux" in line: return True if "-m vocalinux" in line or "import vocalinux" in line: return True if "VocalinuxEngine" in line or "VocalinuxDBusService" in line: return True if "cmdline" in line: return True if " - vocalinux" in line: # Log logger names keep the retained module path. return True if path in ( "src/vocalinux/legacy_import.py", "tests/test_legacy_import.py", ): return True if "LEGACY" in line or "legacy" in line: return True if "~/.config/vocalinux" in line or '".config" / "vocalinux"' in line: # Documented one-time-import source (ticket #18). return True if "from vocalinux" in line: return True if '"src"' in line and '"vocalinux"' in line: # Retained module-dir path segments in tests and tooling. return True if path == "tests/test_rename_sweep.py": return True if path == "pyproject.toml" and re.search(r'"vocalinux[."\'\]]', line): return True if path == "pyproject.toml" and "vocalinux.version" in line: return True if 'PKG_NAME="vocalinux"' in line: # Installer probes the retained import package, not the distribution. return True if '== "vocalinux"' in line or '.startswith("vocalinux' in line: # Retained module identity comparisons in test isolation helpers. return True return False def _iter_files(): for root, dirs, files in os.walk(REPO_ROOT): dirs[:] = [d for d in sorted(dirs) if d not in SKIP_DIRS] for name in sorted(files): if name in SKIP_FILES or name.endswith(BINARY_EXTS): continue full = os.path.join(root, name) rel = os.path.relpath(full, REPO_ROOT) if rel.startswith(SKIP_PREFIXES): continue if rel.startswith("web/") and rel not in WEB_OWNED: continue yield rel, full def test_naming_sweep_no_old_name_outside_allowlist() -> None: """Every old-name hit must be an allowlisted retained surface.""" offenders: list[str] = [] for rel, full in _iter_files(): if any(part.endswith(SKIP_SUFFIXES) for part in rel.split("/")): continue try: with open(full, encoding="utf-8") as handle: text = handle.read() except (UnicodeDecodeError, ValueError): continue for number, line in enumerate(text.splitlines(), 1): if OLD_NAME.search(line) and not _allowed(rel, line): offenders.append(f"{rel}:{number}:{line.strip()[:160]}") assert ( offenders == [] ), f"{len(offenders)} old-name string(s) outside the allowlist:\n" + "\n".join(offenders[:60]) def test_owned_web_files_carry_new_name() -> None: """The two web rename surfaces name the fork (URLs aside).""" for rel in sorted(WEB_OWNED): with open(os.path.join(REPO_ROOT, rel), encoding="utf-8") as handle: text = handle.read() assert "Voculinux" in text or "voculinux" in text, rel