""" install_dependencies.py ======================== Installs all Python dependencies for the PST Email Extractor and verifies that pypff (libpff) is importable. Run once before first use: python install_dependencies.py Platform notes -------------- * Linux / macOS – pypff is usually available via pip (libpff-python) or a system package manager. * Windows – pypff typically requires a pre-built wheel or compiling libpff from source. See the README for details. """ import os import sys import subprocess import platform from importlib.util import find_spec # ── package lists ──────────────────────────────────────────────── # Packages that install cleanly via pip on all platforms PIP_PACKAGES: list[tuple[str, str]] = [ ("tqdm", "tqdm>=4.66"), # Progress bars ] # pypff can be installed with 'libpff-python' on Linux/macOS PYPFF_PIP_NAME = "libpff-python" # ── helpers ────────────────────────────────────────────────────── def run(cmd: list[str]) -> subprocess.CompletedProcess: """Print and run a command, returning the result without raising.""" print(f"\n>> {' '.join(cmd)}") return subprocess.run(cmd, check=False) def is_importable(module: str) -> bool: """Return True if *module* can be found in the current environment.""" return find_spec(module) is not None # ── steps ──────────────────────────────────────────────────────── def ensure_pip() -> None: """Bootstrap / upgrade pip, setuptools, and wheel.""" print("\n[1/3] Ensuring pip is up-to-date …") run([sys.executable, "-m", "ensurepip", "--upgrade"]) run([sys.executable, "-m", "pip", "install", "--upgrade", "pip", "setuptools", "wheel"]) def install_pip_packages() -> None: """Install the pip-only dependencies.""" print("\n[2/3] Installing pip packages …") specs = [spec for _, spec in PIP_PACKAGES] result = run([sys.executable, "-m", "pip", "install", "--upgrade"] + specs) if result.returncode != 0: print("\n❌ ERROR: Failed to install pip packages.") sys.exit(result.returncode) # Verify each import for module, spec in PIP_PACKAGES: ok = is_importable(module) status = "✓" if ok else "✗" print(f" {status} {spec} ({'ok' if ok else 'MISSING'})") def install_pypff() -> bool: """ Try to install pypff via pip (works on Linux/macOS). On Windows this usually fails; we give targeted guidance instead. """ print("\n[3/3] Installing pypff (libpff-python) …") if is_importable("pypff"): print(" ✓ pypff is already importable – skipping install.") return True result = run([sys.executable, "-m", "pip", "install", "--upgrade", PYPFF_PIP_NAME]) if result.returncode == 0 and is_importable("pypff"): print(" ✓ pypff installed and importable.") return True # ── second attempt: some distros ship it as 'pypff' directly ── result2 = run([sys.executable, "-m", "pip", "install", "--upgrade", "pypff"]) if result2.returncode == 0 and is_importable("pypff"): print(" ✓ pypff installed via 'pypff' package name.") return True print(" ✗ Could not install pypff automatically.") return False # ── platform guidance ───────────────────────────────────────────── def print_pypff_guidance() -> None: system = platform.system() py_ver = f"{sys.version_info.major}.{sys.version_info.minor}" print("\n" + "=" * 72) print("MANUAL INSTALLATION REQUIRED: pypff / libpff") print("=" * 72) if system == "Linux": print( "On Debian / Ubuntu:\n" " sudo apt-get install python3-libpff\n" " # or\n" " pip install libpff-python\n\n" "On Fedora / RHEL:\n" " sudo dnf install libpff-devel python3-libpff\n" ) elif system == "Darwin": print( "On macOS (Homebrew):\n" " brew install libpff\n" " pip install libpff-python\n" ) elif system == "Windows": print( f"On Windows (Python {py_ver}):\n" " Option 1 – Pre-built wheel (if available for your Python version):\n" " pip install libpff-python\n\n" " Option 2 – Build from source:\n" " https://github.com/libyal/libpff/blob/main/documentation/Building.md\n\n" " Option 3 – Use WSL (Windows Subsystem for Linux) and follow the Linux steps.\n" ) else: print( " Please visit https://github.com/libyal/libpff for build instructions\n" f" appropriate for your platform ({system}).\n" ) print("=" * 72) # ── main ────────────────────────────────────────────────────────── def main() -> None: print("PST Email Extractor – Dependency Installer") print("=" * 72) print(f" Python: {sys.version.replace(chr(10), ' ')}") print(f" Executable: {sys.executable}") print(f" Platform: {platform.platform()}") print(f" CWD: {os.getcwd()}") ensure_pip() install_pip_packages() has_pypff = install_pypff() # ── summary ────────────────────────────────────────────────── print("\n" + "=" * 72) print("Installation summary") print("=" * 72) for module, spec in PIP_PACKAGES: ok = is_importable(module) print(f" {'✓' if ok else '✗'} {spec}") print(f" {'✓' if has_pypff else '✗'} pypff (libpff)") print("=" * 72) if not has_pypff: print_pypff_guidance() sys.exit(2) print("\n✅ All dependencies are installed.") print(" You can now run: python Extract.py [directory]") if __name__ == "__main__": main()