Coverage for src/system/commands.py: 100.00%
5 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-08 13:49 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-08 13:49 +0000
1#!/usr/bin/env python3
3# Standard libraries
4from pathlib import Path
5from shutil import which
6import subprocess
7from typing import List, Optional
9# Commands
10class Commands:
12 # Exec
13 @staticmethod
14 def exec(binary: str, arguments: List[str]) -> int: # pragma: no cover
16 # Run system command
17 try:
18 process = subprocess.run(
19 args=[binary] + arguments,
20 cwd=None,
21 check=False,
22 shell=False,
23 )
24 return process.returncode
25 except subprocess.CalledProcessError as e:
26 print(f"Error executing command: {e}")
27 return 1
29 # Exists
30 @staticmethod
31 def exists(binary: str) -> bool: # pragma: no cover
33 # Check system binary exists
34 path: Optional[str] = which(binary)
35 if path is None:
36 return False
38 # Check mise shims resolve to an active tool version
39 if Path(path).resolve().name == 'mise' and Path(path).name != 'mise':
40 try:
41 return subprocess.run(
42 args=['mise', 'which', binary],
43 check=False,
44 shell=False,
45 stdout=subprocess.DEVNULL,
46 stderr=subprocess.DEVNULL,
47 ).returncode == 0
48 except OSError:
49 return False
51 # Result
52 return True
54 # Grep
55 @staticmethod
56 def grep(file: Path, string: str) -> bool: # pragma: no cover
58 # Ignore missing file
59 if not file.exists():
60 return False
62 # Check file contains string
63 with open(
64 file,
65 encoding='utf8',
66 mode='r',
67 ) as f:
68 for line in f.readlines():
70 # Find string in line
71 if string in line:
72 return True
74 # Fallback
75 return False # pragma: no cover
77 # Mise
78 @staticmethod
79 def mise(arguments: List[str], check: bool = True) -> bool: # pragma: no cover
81 # Run mise
82 return Commands.run(
83 'mise',
84 arguments,
85 check,
86 )
88 # Output
89 @staticmethod
90 def output(
91 binary: str,
92 arguments: List[str],
93 quiet: bool = False,
94 errors: bool = False,
95 ) -> str: # pragma: no cover
97 # Variables
98 stderr: Optional[int] = None
100 # Redirect errors
101 if errors:
102 stderr = subprocess.STDOUT
103 elif quiet:
104 stderr = subprocess.DEVNULL
106 # Get system output
107 try:
108 return subprocess.check_output(
109 args=[binary] + arguments,
110 cwd=None,
111 shell=False,
112 stderr=stderr,
113 ).strip().decode()
114 except subprocess.CalledProcessError as err:
115 return str(err.output.decode())
116 except FileNotFoundError:
117 return ''
119 # Pip
120 @staticmethod
121 def pip(arguments: List[str]) -> bool: # pragma: no cover
123 # Run with pipx
124 if Commands.exists('pipx'):
125 return Commands.run(
126 'pipx',
127 arguments,
128 )
130 # Run with pip # pragma: no cover
131 return Commands.run(
132 'sudo',
133 ['pip'] + arguments,
134 )
136 # Run
137 @staticmethod
138 def run(binary: str, arguments: List[str],
139 check: bool = True) -> bool: # pragma: no cover
141 # Variables
142 stderr: Optional[int] = None if check else subprocess.DEVNULL
143 stdout: Optional[int] = None if check else subprocess.DEVNULL
145 # Run system command
146 try:
147 process = subprocess.run(
148 args=[binary] + arguments,
149 cwd=None,
150 check=check,
151 shell=False,
152 stderr=stderr,
153 stdout=stdout,
154 )
155 return process.returncode == 0
156 except subprocess.CalledProcessError as e:
157 if check:
158 print(f"Error executing command: {e}")
159 return False