Skip to content

Commit 0e0b8bb

Browse files
committed
Server(feat[tmux_bin]): Add configurable tmux binary path
why: shutil.which("tmux") is hardcoded in tmux_cmd and Server. No way to use wemux, byobu, or a custom-built tmux. tmuxinator supports tmux_command config key including wemux module. what: - Add tmux_bin keyword arg to tmux_cmd.__init__ (backward compatible) - Add tmux_bin parameter to Server.__init__ with pathlib.Path support - Thread tmux_bin through Server.cmd(), Server.raise_if_dead(), and neo.fetch_objs() - Session/Window/Pane inherit via server.cmd() delegation - Add tests for default, custom path, and invalid path scenarios
1 parent abbefdf commit 0e0b8bb

4 files changed

Lines changed: 38 additions & 9 deletions

File tree

src/libtmux/common.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,12 +248,12 @@ class tmux_cmd:
248248
Renamed from ``tmux`` to ``tmux_cmd``.
249249
"""
250250

251-
def __init__(self, *args: t.Any) -> None:
252-
tmux_bin = shutil.which("tmux")
253-
if not tmux_bin:
251+
def __init__(self, *args: t.Any, tmux_bin: str | None = None) -> None:
252+
resolved = tmux_bin or shutil.which("tmux")
253+
if not resolved:
254254
raise exc.TmuxCommandNotFound
255255

256-
cmd = [tmux_bin]
256+
cmd = [resolved]
257257
cmd += args # add the command arguments to cmd
258258
cmd = [str(c) for c in cmd]
259259

src/libtmux/neo.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,10 @@ def fetch_objs(
319319
},
320320
)
321321

322-
proc = tmux_cmd(*tmux_cmds) # output
322+
proc = tmux_cmd(
323+
*tmux_cmds,
324+
tmux_bin=server.tmux_bin if server is not None else None,
325+
)
323326

324327
if proc.stderr:
325328
raise exc.LibTmuxException(proc.stderr)

src/libtmux/server.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ class Server(
127127
"""For option management."""
128128
default_hook_scope: OptionScope | None = OptionScope.Server
129129
"""For hook management."""
130+
tmux_bin: str | None = None
131+
"""Custom path to tmux binary. Falls back to ``shutil.which("tmux")``."""
130132

131133
def __init__(
132134
self,
@@ -136,9 +138,11 @@ def __init__(
136138
colors: int | None = None,
137139
on_init: t.Callable[[Server], None] | None = None,
138140
socket_name_factory: t.Callable[[], str] | None = None,
141+
tmux_bin: str | pathlib.Path | None = None,
139142
**kwargs: t.Any,
140143
) -> None:
141144
EnvironmentMixin.__init__(self, "-g")
145+
self.tmux_bin = str(tmux_bin) if tmux_bin else None
142146
self._windows: list[WindowDict] = []
143147
self._panes: list[PaneDict] = []
144148

@@ -220,8 +224,8 @@ def raise_if_dead(self) -> None:
220224
... print(type(e))
221225
<class 'subprocess.CalledProcessError'>
222226
"""
223-
tmux_bin = shutil.which("tmux")
224-
if tmux_bin is None:
227+
resolved = self.tmux_bin or shutil.which("tmux")
228+
if resolved is None:
225229
raise exc.TmuxCommandNotFound
226230

227231
cmd_args: list[str] = ["list-sessions"]
@@ -232,7 +236,7 @@ def raise_if_dead(self) -> None:
232236
if self.config_file:
233237
cmd_args.insert(0, f"-f{self.config_file}")
234238

235-
subprocess.check_call([tmux_bin, *cmd_args])
239+
subprocess.check_call([resolved, *cmd_args])
236240

237241
#
238242
# Command
@@ -308,7 +312,7 @@ def cmd(
308312

309313
cmd_args = ["-t", str(target), *args] if target is not None else [*args]
310314

311-
return tmux_cmd(*svr_args, *cmd_args)
315+
return tmux_cmd(*svr_args, *cmd_args, tmux_bin=self.tmux_bin)
312316

313317
@property
314318
def attached_sessions(self) -> list[Session]:

tests/test_server.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import logging
66
import os
77
import pathlib
8+
import shutil
89
import subprocess
910
import time
1011
import typing as t
@@ -414,3 +415,24 @@ def test_new_session_start_directory_pathlib(
414415
actual_path = str(pathlib.Path(active_pane.pane_current_path).resolve())
415416
expected_path = str(user_path.resolve())
416417
assert actual_path == expected_path
418+
419+
420+
def test_tmux_bin_default(server: Server) -> None:
421+
"""Default tmux_bin is None, falls back to shutil.which."""
422+
assert server.tmux_bin is None
423+
424+
425+
def test_tmux_bin_custom_path() -> None:
426+
"""Custom tmux_bin path is used for commands."""
427+
tmux_path = shutil.which("tmux")
428+
assert tmux_path is not None
429+
s = Server(tmux_bin=tmux_path)
430+
# Should work identically to default
431+
assert s.tmux_bin == tmux_path
432+
433+
434+
def test_tmux_bin_invalid_path() -> None:
435+
"""Invalid tmux_bin raises on command execution."""
436+
s = Server(tmux_bin="/nonexistent/tmux")
437+
with pytest.raises(FileNotFoundError):
438+
s.cmd("list-sessions")

0 commit comments

Comments
 (0)