Skip to content

Commit 10c51c5

Browse files
committed
feat(options): Add OptionsMixin
1 parent b53c82e commit 10c51c5

File tree

1 file changed

+277
-0
lines changed

1 file changed

+277
-0
lines changed

src/libtmux/options.py

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
"""Helpers for tmux options."""
2+
import logging
3+
import shlex
4+
import typing as t
5+
6+
from libtmux.common import CmdMixin, handle_option_error
7+
from libtmux.constants import (
8+
DEFAULT_SCOPE,
9+
OPTION_SCOPE_FLAG_MAP,
10+
OptionScope,
11+
_DefaultScope,
12+
)
13+
14+
if t.TYPE_CHECKING:
15+
from typing_extensions import Self
16+
17+
OptionDict = t.Dict[str, t.Any]
18+
19+
logger = logging.getLogger(__name__)
20+
21+
22+
class OptionMixin(CmdMixin):
23+
"""Mixin for manager session and server level environment variables in tmux."""
24+
25+
default_scope: t.Optional[OptionScope]
26+
27+
def __init__(self, default_scope: OptionScope) -> None:
28+
self.default_scope = default_scope
29+
30+
def set_option(
31+
self,
32+
option: str,
33+
value: t.Union[int, str],
34+
_format: t.Optional[bool] = None,
35+
unset: t.Optional[bool] = None,
36+
unset_panes: t.Optional[bool] = None,
37+
prevent_overwrite: t.Optional[bool] = None,
38+
ignore_errors: t.Optional[bool] = None,
39+
append: t.Optional[bool] = None,
40+
g: t.Optional[bool] = None,
41+
scope: t.Optional[t.Union[OptionScope, _DefaultScope]] = DEFAULT_SCOPE,
42+
) -> "Self":
43+
"""Set option for tmux window.
44+
45+
Wraps ``$ tmux set-option <option> <value>``.
46+
47+
Parameters
48+
----------
49+
option : str
50+
option to set, e.g. 'aggressive-resize'
51+
value : str
52+
window option value. True/False will turn in 'on' and 'off',
53+
also accepts string of 'on' or 'off' directly.
54+
55+
Raises
56+
------
57+
:exc:`exc.OptionError`, :exc:`exc.UnknownOption`,
58+
:exc:`exc.InvalidOption`, :exc:`exc.AmbiguousOption`
59+
"""
60+
if scope is DEFAULT_SCOPE:
61+
scope = self.default_scope
62+
63+
flags: t.List[str] = []
64+
if isinstance(value, bool) and value:
65+
value = "on"
66+
elif isinstance(value, bool) and not value:
67+
value = "off"
68+
69+
if unset is not None and unset:
70+
assert isinstance(unset, bool)
71+
flags.append("-u")
72+
73+
if unset_panes is not None and unset_panes:
74+
assert isinstance(unset_panes, bool)
75+
flags.append("-U")
76+
77+
if _format is not None and _format:
78+
assert isinstance(_format, bool)
79+
flags.append("-F")
80+
81+
if prevent_overwrite is not None and prevent_overwrite:
82+
assert isinstance(prevent_overwrite, bool)
83+
flags.append("-o")
84+
85+
if ignore_errors is not None and ignore_errors:
86+
assert isinstance(ignore_errors, bool)
87+
flags.append("-q")
88+
89+
if append is not None and append:
90+
assert isinstance(append, bool)
91+
flags.append("-a")
92+
93+
if g is not None and g:
94+
assert isinstance(g, bool)
95+
flags.append("-g")
96+
97+
if scope is not None and not isinstance(scope, _DefaultScope):
98+
assert scope in OPTION_SCOPE_FLAG_MAP
99+
flags.append(
100+
OPTION_SCOPE_FLAG_MAP[scope],
101+
)
102+
103+
cmd = self.cmd(
104+
"set-option",
105+
*flags,
106+
option,
107+
value,
108+
)
109+
110+
if isinstance(cmd.stderr, list) and len(cmd.stderr):
111+
handle_option_error(cmd.stderr[0])
112+
113+
return self
114+
115+
@t.overload
116+
def show_options(
117+
self,
118+
g: t.Optional[bool],
119+
scope: t.Optional[t.Union[OptionScope, _DefaultScope]],
120+
ignore_errors: t.Optional[bool],
121+
include_hooks: t.Optional[bool],
122+
include_parents: t.Optional[bool],
123+
values_only: t.Literal[True],
124+
) -> t.List[str]:
125+
...
126+
127+
@t.overload
128+
def show_options(
129+
self,
130+
g: t.Optional[bool],
131+
scope: t.Optional[t.Union[OptionScope, _DefaultScope]],
132+
ignore_errors: t.Optional[bool],
133+
include_hooks: t.Optional[bool],
134+
include_parents: t.Optional[bool],
135+
values_only: t.Literal[None] = None,
136+
) -> "OptionDict":
137+
...
138+
139+
@t.overload
140+
def show_options(
141+
self,
142+
g: t.Optional[bool] = None,
143+
scope: t.Optional[t.Union[OptionScope, _DefaultScope]] = DEFAULT_SCOPE,
144+
ignore_errors: t.Optional[bool] = None,
145+
include_hooks: t.Optional[bool] = None,
146+
include_parents: t.Optional[bool] = None,
147+
values_only: t.Literal[False] = False,
148+
) -> "OptionDict":
149+
...
150+
151+
def show_options(
152+
self,
153+
g: t.Optional[bool] = False,
154+
scope: t.Optional[t.Union[OptionScope, _DefaultScope]] = DEFAULT_SCOPE,
155+
ignore_errors: t.Optional[bool] = None,
156+
include_hooks: t.Optional[bool] = None,
157+
include_parents: t.Optional[bool] = None,
158+
values_only: t.Optional[bool] = False,
159+
) -> t.Union["OptionDict", t.List[str]]:
160+
"""Return a dict of options for the window.
161+
162+
Parameters
163+
----------
164+
g : str, optional
165+
Pass ``-g`` flag for global variable, default False.
166+
"""
167+
if scope is DEFAULT_SCOPE:
168+
scope = self.default_scope
169+
170+
tmux_args: t.Tuple[str, ...] = ()
171+
172+
if g:
173+
tmux_args += ("-g",)
174+
175+
if scope is not None and not isinstance(scope, _DefaultScope):
176+
assert scope in OPTION_SCOPE_FLAG_MAP
177+
tmux_args += (OPTION_SCOPE_FLAG_MAP[scope],)
178+
179+
if include_parents is not None and include_parents:
180+
tmux_args += ("-A",)
181+
182+
if include_hooks is not None and include_hooks:
183+
tmux_args += ("-H",)
184+
185+
if values_only is not None and values_only:
186+
tmux_args += ("-v",)
187+
188+
if ignore_errors is not None and ignore_errors:
189+
assert isinstance(ignore_errors, bool)
190+
tmux_args += ("-q",)
191+
192+
cmd = self.cmd("show-options", *tmux_args)
193+
194+
output = cmd.stdout
195+
196+
# The shlex.split function splits the args at spaces, while also
197+
# retaining quoted sub-strings.
198+
# shlex.split('this is "a test"') => ['this', 'is', 'a test']
199+
200+
window_options: "OptionDict" = {}
201+
for item in output:
202+
try:
203+
key, val = shlex.split(item)
204+
except ValueError:
205+
logger.exception(f"Error extracting option: {item}")
206+
assert isinstance(key, str)
207+
assert isinstance(val, str)
208+
209+
if isinstance(val, str) and val.isdigit():
210+
window_options[key] = int(val)
211+
212+
return window_options
213+
214+
def show_option(
215+
self,
216+
option: str,
217+
g: bool = False,
218+
scope: t.Optional[t.Union[OptionScope, _DefaultScope]] = DEFAULT_SCOPE,
219+
ignore_errors: t.Optional[bool] = None,
220+
include_hooks: t.Optional[bool] = None,
221+
include_parents: t.Optional[bool] = None,
222+
) -> t.Optional[t.Union[str, int]]:
223+
"""Return option value for the target window.
224+
225+
todo: test and return True/False for on/off string
226+
227+
Parameters
228+
----------
229+
option : str
230+
g : bool, optional
231+
Pass ``-g`` flag, global. Default False.
232+
233+
Raises
234+
------
235+
:exc:`exc.OptionError`, :exc:`exc.UnknownOption`,
236+
:exc:`exc.InvalidOption`, :exc:`exc.AmbiguousOption`
237+
"""
238+
if scope is DEFAULT_SCOPE:
239+
scope = self.default_scope
240+
241+
tmux_args: t.Tuple[t.Union[str, int], ...] = ()
242+
243+
if g:
244+
tmux_args += ("-g",)
245+
246+
if scope is not None and not isinstance(scope, _DefaultScope):
247+
assert scope in OPTION_SCOPE_FLAG_MAP
248+
tmux_args += (OPTION_SCOPE_FLAG_MAP[scope],)
249+
250+
if ignore_errors is not None and ignore_errors:
251+
tmux_args += ("-q",)
252+
253+
if include_parents is not None and include_parents:
254+
tmux_args += ("-A",)
255+
256+
if include_hooks is not None and include_hooks:
257+
tmux_args += ("-H",)
258+
259+
tmux_args += (option,)
260+
261+
cmd = self.cmd("show-options", *tmux_args)
262+
263+
if len(cmd.stderr):
264+
handle_option_error(cmd.stderr[0])
265+
266+
window_options_output = cmd.stdout
267+
268+
if not len(window_options_output):
269+
return None
270+
271+
value_raw = next(shlex.split(item) for item in window_options_output)
272+
273+
value: t.Union[str, int] = (
274+
int(value_raw[1]) if value_raw[1].isdigit() else value_raw[1]
275+
)
276+
277+
return value

0 commit comments

Comments
 (0)