Coverage for src/workstack/core/shell_ops.py: 44%

27 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-19 09:31 -0400

1"""Shell detection and tool availability operations. 

2 

3This module provides abstraction over shell-specific operations like detecting 

4the current shell and checking if command-line tools are installed. This abstraction 

5enables dependency injection for testing without mock.patch. 

6""" 

7 

8import os 

9import shutil 

10from abc import ABC, abstractmethod 

11from pathlib import Path 

12 

13 

14class ShellOps(ABC): 

15 """Abstract interface for shell detection and tool availability checks. 

16 

17 This abstraction enables testing without mock.patch by making shell 

18 operations injectable dependencies. 

19 """ 

20 

21 @abstractmethod 

22 def detect_shell(self) -> tuple[str, Path] | None: 

23 """Detect current shell and return configuration file path. 

24 

25 Returns: 

26 Tuple of (shell_name, rc_file_path) or None if shell cannot be detected. 

27 

28 Supported shells: 

29 - bash: returns ("bash", ~/.bashrc) 

30 - zsh: returns ("zsh", ~/.zshrc) 

31 - fish: returns ("fish", ~/.config/fish/config.fish) 

32 

33 Example: 

34 >>> shell_ops = RealShellOps() 

35 >>> result = shell_ops.detect_shell() 

36 >>> if result: 

37 ... shell_name, rc_file = result 

38 ... print(f"Detected {shell_name} with rc file at {rc_file}") 

39 """ 

40 ... 

41 

42 @abstractmethod 

43 def check_tool_installed(self, tool_name: str) -> str | None: 

44 """Check if a command-line tool is installed and available in PATH. 

45 

46 Args: 

47 tool_name: Name of the tool to check (e.g., "gt", "git", "python") 

48 

49 Returns: 

50 Absolute path to the tool executable if found, None otherwise. 

51 

52 Example: 

53 >>> shell_ops = RealShellOps() 

54 >>> gt_path = shell_ops.check_tool_installed("gt") 

55 >>> if gt_path: 

56 ... print(f"Graphite found at {gt_path}") 

57 """ 

58 ... 

59 

60 

61class RealShellOps(ShellOps): 

62 """Production implementation using system environment and PATH.""" 

63 

64 def detect_shell(self) -> tuple[str, Path] | None: 

65 """Detect current shell from SHELL environment variable. 

66 

67 Implementation details: 

68 - Reads $SHELL environment variable 

69 - Extracts shell name from path (e.g., /bin/bash -> bash) 

70 - Maps to appropriate RC file location 

71 """ 

72 shell_path = os.environ.get("SHELL", "") 

73 if not shell_path: 

74 return None 

75 

76 shell_name = Path(shell_path).name 

77 

78 if shell_name == "bash": 

79 rc_file = Path.home() / ".bashrc" 

80 return ("bash", rc_file) 

81 if shell_name == "zsh": 

82 rc_file = Path.home() / ".zshrc" 

83 return ("zsh", rc_file) 

84 if shell_name == "fish": 

85 rc_file = Path.home() / ".config" / "fish" / "config.fish" 

86 return ("fish", rc_file) 

87 

88 return None 

89 

90 def check_tool_installed(self, tool_name: str) -> str | None: 

91 """Check if tool is in PATH using shutil.which.""" 

92 return shutil.which(tool_name)