-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexecutor.py
More file actions
70 lines (64 loc) · 2.27 KB
/
executor.py
File metadata and controls
70 lines (64 loc) · 2.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
"""
executor.py — Run shell commands and capture output
"""
import subprocess
import time
from dataclasses import dataclass
from typing import Optional
# Commands that modify shell state and cannot work in a subprocess
SHELL_STATE_COMMANDS = ("cd ", "export ", "source ", ". ", "pushd ", "popd", "cd\n", "cd")
@dataclass
class ExecutionResult:
command: str
stdout: str
stderr: str
exit_code: int
elapsed_seconds: float
class Executor:
def run(self, command: str, timeout: int = 30) -> ExecutionResult:
"""
Run command via subprocess with shell=True.
Captures stdout and stderr separately.
On timeout: kills process, sets exit_code=-1.
Never raises on command failure — always returns ExecutionResult.
"""
start = time.monotonic()
try:
proc = subprocess.run(
command,
shell=True,
capture_output=True,
text=True,
timeout=timeout,
)
elapsed = time.monotonic() - start
return ExecutionResult(
command=command,
stdout=proc.stdout,
stderr=proc.stderr,
exit_code=proc.returncode,
elapsed_seconds=elapsed,
)
except subprocess.TimeoutExpired:
elapsed = time.monotonic() - start
return ExecutionResult(
command=command,
stdout="",
stderr=f"Command timed out after {timeout}s",
exit_code=-1,
elapsed_seconds=elapsed,
)
def warn_if_shell_state(self, command: str) -> Optional[str]:
"""
Return a warning string if the command modifies shell state
(e.g. cd, export, source) which cannot take effect in a subprocess.
Returns None if no warning needed.
"""
stripped = command.strip()
for prefix in SHELL_STATE_COMMANDS:
if stripped == prefix.strip() or stripped.startswith(prefix):
return (
f"Warning: '{stripped.split()[0]}' affects shell state and will NOT "
f"change your current session. Run it directly in your shell instead."
)
return None