"""Shell detection utilities for completion generation. This module provides functionality to detect the current shell type by inspecting environment variables. This is useful for dynamically generating appropriate completion scripts for different shell environments. """ import os import subprocess from pathlib import Path from typing import Literal class ShellDetectionError(Exception): """Raised when the shell type cannot be detected.""" def _extract_shell_name(shell_string: str) -> Literal["zsh", "bash", "fish"] | None: """Extract shell name from a string (path or process name). Parameters ---------- shell_string : str String that may contain a shell name (e.g., "/bin/bash", "zsh", "-bash"). Returns ------- Literal["zsh", "bash", "fish"] | None The detected shell type, or None if not recognized. """ shell_lower = shell_string.lower() if "zsh" in shell_lower: return "zsh" elif "bash" in shell_lower: return "bash" elif "fish" in shell_lower: return "fish" return None def detect_shell() -> Literal["zsh", "bash", "fish"]: """Detect the current shell type using multiple detection methods. Returns ------- Literal["zsh", "bash", "fish"] The detected shell type. Raises ------ ShellDetectionError If the shell type cannot be determined from any detection method. Examples -------- >>> shell = detect_shell() # doctest: +SKIP >>> print(f"Detected shell: {shell}") # doctest: +SKIP Detected shell: bash """ if os.environ.get("ZSH_VERSION"): return "zsh" elif os.environ.get("BASH_VERSION"): return "bash" elif os.environ.get("FISH_VERSION"): return "fish" try: ppid = os.getppid() result = subprocess.run( ["ps", "-p", str(ppid), "-o", "comm="], capture_output=True, text=True, timeout=1, ) if result.returncode == 0 and result.stdout: parent_process = result.stdout.strip() shell = _extract_shell_name(parent_process) if shell: return shell except (subprocess.SubprocessError, FileNotFoundError, OSError): pass shell_path = os.environ.get("SHELL", "") if shell_path: shell_name = Path(shell_path).name shell = _extract_shell_name(shell_name) if shell: return shell raise ShellDetectionError("Unable to detect shell type.")