|
9 | 9 |
|
10 | 10 | import logging |
11 | 11 | import re |
12 | | -import shutil |
13 | | -import subprocess |
14 | 12 | import sys |
15 | 13 | import typing as t |
16 | 14 |
|
| 15 | +from libtmux.engines import CommandResult, SubprocessEngine, TmuxEngine |
| 16 | + |
17 | 17 | from . import exc |
18 | 18 | from ._compat import LooseVersion |
19 | 19 |
|
@@ -190,8 +190,23 @@ def getenv(self, name: str) -> str | bool | None: |
190 | 190 | return opts_dict.get(name) |
191 | 191 |
|
192 | 192 |
|
| 193 | +_default_engine: TmuxEngine = SubprocessEngine() |
| 194 | + |
| 195 | + |
| 196 | +def _apply_result(target: tmux_cmd | None, result: CommandResult) -> tmux_cmd: |
| 197 | + cmd_obj: tmux_cmd = object.__new__(tmux_cmd) if target is None else target |
| 198 | + |
| 199 | + cmd_obj.cmd = list(result.cmd) |
| 200 | + cmd_obj.stdout = result.stdout |
| 201 | + cmd_obj.stderr = result.stderr |
| 202 | + cmd_obj.returncode = result.returncode |
| 203 | + cmd_obj.process = result.process |
| 204 | + |
| 205 | + return cmd_obj |
| 206 | + |
| 207 | + |
193 | 208 | class tmux_cmd: |
194 | | - """Run any :term:`tmux(1)` command through :py:mod:`subprocess`. |
| 209 | + """Run any :term:`tmux(1)` command through the configured engine. |
195 | 210 |
|
196 | 211 | Examples |
197 | 212 | -------- |
@@ -219,52 +234,50 @@ class tmux_cmd: |
219 | 234 | Renamed from ``tmux`` to ``tmux_cmd``. |
220 | 235 | """ |
221 | 236 |
|
| 237 | + cmd: list[str] |
| 238 | + stdout: list[str] |
| 239 | + stderr: list[str] |
| 240 | + returncode: int |
| 241 | + process: object | None |
| 242 | + |
222 | 243 | def __init__(self, *args: t.Any) -> None: |
223 | | - tmux_bin = shutil.which("tmux") |
224 | | - if not tmux_bin: |
225 | | - raise exc.TmuxCommandNotFound |
226 | | - |
227 | | - cmd = [tmux_bin] |
228 | | - cmd += args # add the command arguments to cmd |
229 | | - cmd = [str(c) for c in cmd] |
230 | | - |
231 | | - self.cmd = cmd |
232 | | - |
233 | | - try: |
234 | | - self.process = subprocess.Popen( |
235 | | - cmd, |
236 | | - stdout=subprocess.PIPE, |
237 | | - stderr=subprocess.PIPE, |
238 | | - text=True, |
239 | | - errors="backslashreplace", |
240 | | - ) |
241 | | - stdout, stderr = self.process.communicate() |
242 | | - returncode = self.process.returncode |
243 | | - except Exception: |
244 | | - logger.exception(f"Exception for {subprocess.list2cmdline(cmd)}") |
245 | | - raise |
246 | | - |
247 | | - self.returncode = returncode |
248 | | - |
249 | | - stdout_split = stdout.split("\n") |
250 | | - # remove trailing newlines from stdout |
251 | | - while stdout_split and stdout_split[-1] == "": |
252 | | - stdout_split.pop() |
253 | | - |
254 | | - stderr_split = stderr.split("\n") |
255 | | - self.stderr = list(filter(None, stderr_split)) # filter empty values |
256 | | - |
257 | | - if "has-session" in cmd and len(self.stderr) and not stdout_split: |
258 | | - self.stdout = [self.stderr[0]] |
259 | | - else: |
260 | | - self.stdout = stdout_split |
261 | | - |
262 | | - logger.debug( |
263 | | - "self.stdout for {cmd}: {stdout}".format( |
264 | | - cmd=" ".join(cmd), |
265 | | - stdout=self.stdout, |
266 | | - ), |
267 | | - ) |
| 244 | + result = _default_engine.run(*args) |
| 245 | + _apply_result(self, result) |
| 246 | + |
| 247 | + @classmethod |
| 248 | + def from_result(cls, result: CommandResult) -> tmux_cmd: |
| 249 | + """Create a :class:`tmux_cmd` instance from a raw result.""" |
| 250 | + return _apply_result(None, result) |
| 251 | + |
| 252 | + |
| 253 | +def get_default_engine() -> TmuxEngine: |
| 254 | + """Return the process-based default engine.""" |
| 255 | + return _default_engine |
| 256 | + |
| 257 | + |
| 258 | +def set_default_engine(engine: TmuxEngine) -> None: |
| 259 | + """Override the process-based default engine. |
| 260 | +
|
| 261 | + Parameters |
| 262 | + ---------- |
| 263 | + engine : TmuxEngine |
| 264 | + Engine implementation that should back :class:`tmux_cmd` and helper functions. |
| 265 | + """ |
| 266 | + global _default_engine |
| 267 | + _default_engine = engine |
| 268 | + |
| 269 | + |
| 270 | +def run_tmux_command(engine: TmuxEngine, *args: t.Any) -> tmux_cmd: |
| 271 | + """Execute a tmux command using the provided engine. |
| 272 | +
|
| 273 | + Falls back to the global default engine when ``engine`` matches the default, |
| 274 | + preserving legacy monkeypatching that targets :class:`tmux_cmd` directly. |
| 275 | + """ |
| 276 | + if engine is _default_engine: |
| 277 | + return tmux_cmd(*args) |
| 278 | + |
| 279 | + result = engine.run(*args) |
| 280 | + return tmux_cmd.from_result(result) |
268 | 281 |
|
269 | 282 |
|
270 | 283 | def get_version() -> LooseVersion: |
|
0 commit comments