Skip to content

Commit 0ccacec

Browse files
committed
feat(options): Add OptionsMixin
1 parent 2e5d723 commit 0ccacec

File tree

1 file changed

+272
-0
lines changed

1 file changed

+272
-0
lines changed

src/libtmux/options.py

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
"""Helpers for tmux options."""
22
import logging
3+
import shlex
34
import typing as t
45

6+
from libtmux.common import CmdMixin
7+
from libtmux.constants import (
8+
DEFAULT_SCOPE,
9+
OPTION_SCOPE_FLAG_MAP,
10+
OptionScope,
11+
_DefaultScope,
12+
)
13+
514
from . import exc
615

16+
if t.TYPE_CHECKING:
17+
from typing_extensions import Self
18+
19+
OptionDict = t.Dict[str, t.Any]
20+
721
logger = logging.getLogger(__name__)
822

923

@@ -42,3 +56,261 @@ def handle_option_error(error: str) -> t.Type[exc.OptionError]:
4256
raise exc.AmbiguousOption(error)
4357
else:
4458
raise exc.OptionError(error) # Raise generic option error
59+
60+
61+
class OptionMixin(CmdMixin):
62+
"""Mixin for manager session and server level environment variables in tmux."""
63+
64+
default_scope: t.Optional[OptionScope]
65+
66+
def __init__(self, default_scope: OptionScope) -> None:
67+
self.default_scope = default_scope
68+
69+
def set_option(
70+
self,
71+
option: str,
72+
value: t.Union[int, str],
73+
_format: t.Optional[bool] = None,
74+
unset: t.Optional[bool] = None,
75+
unset_panes: t.Optional[bool] = None,
76+
prevent_overwrite: t.Optional[bool] = None,
77+
ignore_errors: t.Optional[bool] = None,
78+
append: t.Optional[bool] = None,
79+
g: t.Optional[bool] = None,
80+
scope: t.Optional[t.Union[OptionScope, _DefaultScope]] = DEFAULT_SCOPE,
81+
) -> "Self":
82+
"""Set option for tmux window.
83+
84+
Wraps ``$ tmux set-option <option> <value>``.
85+
86+
Parameters
87+
----------
88+
option : str
89+
option to set, e.g. 'aggressive-resize'
90+
value : str
91+
window option value. True/False will turn in 'on' and 'off',
92+
also accepts string of 'on' or 'off' directly.
93+
94+
Raises
95+
------
96+
:exc:`exc.OptionError`, :exc:`exc.UnknownOption`,
97+
:exc:`exc.InvalidOption`, :exc:`exc.AmbiguousOption`
98+
"""
99+
if scope is DEFAULT_SCOPE:
100+
scope = self.default_scope
101+
102+
flags: t.List[str] = []
103+
if isinstance(value, bool) and value:
104+
value = "on"
105+
elif isinstance(value, bool) and not value:
106+
value = "off"
107+
108+
if unset is not None and unset:
109+
assert isinstance(unset, bool)
110+
flags.append("-u")
111+
112+
if unset_panes is not None and unset_panes:
113+
assert isinstance(unset_panes, bool)
114+
flags.append("-U")
115+
116+
if _format is not None and _format:
117+
assert isinstance(_format, bool)
118+
flags.append("-F")
119+
120+
if prevent_overwrite is not None and prevent_overwrite:
121+
assert isinstance(prevent_overwrite, bool)
122+
flags.append("-o")
123+
124+
if ignore_errors is not None and ignore_errors:
125+
assert isinstance(ignore_errors, bool)
126+
flags.append("-q")
127+
128+
if append is not None and append:
129+
assert isinstance(append, bool)
130+
flags.append("-a")
131+
132+
if g is not None and g:
133+
assert isinstance(g, bool)
134+
flags.append("-g")
135+
136+
if scope is not None and not isinstance(scope, _DefaultScope):
137+
assert scope in OPTION_SCOPE_FLAG_MAP
138+
flags.append(
139+
OPTION_SCOPE_FLAG_MAP[scope],
140+
)
141+
142+
cmd = self.cmd(
143+
"set-option",
144+
*flags,
145+
option,
146+
value,
147+
)
148+
149+
if isinstance(cmd.stderr, list) and len(cmd.stderr):
150+
handle_option_error(cmd.stderr[0])
151+
152+
return self
153+
154+
@t.overload
155+
def show_options(
156+
self,
157+
g: t.Optional[bool],
158+
scope: t.Optional[t.Union[OptionScope, _DefaultScope]],
159+
ignore_errors: t.Optional[bool],
160+
include_hooks: t.Optional[bool],
161+
include_parents: t.Optional[bool],
162+
values_only: t.Literal[True],
163+
) -> t.List[str]:
164+
...
165+
166+
@t.overload
167+
def show_options(
168+
self,
169+
g: t.Optional[bool],
170+
scope: t.Optional[t.Union[OptionScope, _DefaultScope]],
171+
ignore_errors: t.Optional[bool],
172+
include_hooks: t.Optional[bool],
173+
include_parents: t.Optional[bool],
174+
values_only: t.Literal[None] = None,
175+
) -> "OptionDict":
176+
...
177+
178+
@t.overload
179+
def show_options(
180+
self,
181+
g: t.Optional[bool] = None,
182+
scope: t.Optional[t.Union[OptionScope, _DefaultScope]] = DEFAULT_SCOPE,
183+
ignore_errors: t.Optional[bool] = None,
184+
include_hooks: t.Optional[bool] = None,
185+
include_parents: t.Optional[bool] = None,
186+
values_only: t.Literal[False] = False,
187+
) -> "OptionDict":
188+
...
189+
190+
def show_options(
191+
self,
192+
g: t.Optional[bool] = False,
193+
scope: t.Optional[t.Union[OptionScope, _DefaultScope]] = DEFAULT_SCOPE,
194+
ignore_errors: t.Optional[bool] = None,
195+
include_hooks: t.Optional[bool] = None,
196+
include_parents: t.Optional[bool] = None,
197+
values_only: t.Optional[bool] = False,
198+
) -> t.Union["OptionDict", t.List[str]]:
199+
"""Return a dict of options for the window.
200+
201+
Parameters
202+
----------
203+
g : str, optional
204+
Pass ``-g`` flag for global variable, default False.
205+
"""
206+
if scope is DEFAULT_SCOPE:
207+
scope = self.default_scope
208+
209+
tmux_args: t.Tuple[str, ...] = ()
210+
211+
if g:
212+
tmux_args += ("-g",)
213+
214+
if scope is not None and not isinstance(scope, _DefaultScope):
215+
assert scope in OPTION_SCOPE_FLAG_MAP
216+
tmux_args += (OPTION_SCOPE_FLAG_MAP[scope],)
217+
218+
if include_parents is not None and include_parents:
219+
tmux_args += ("-A",)
220+
221+
if include_hooks is not None and include_hooks:
222+
tmux_args += ("-H",)
223+
224+
if values_only is not None and values_only:
225+
tmux_args += ("-v",)
226+
227+
if ignore_errors is not None and ignore_errors:
228+
assert isinstance(ignore_errors, bool)
229+
tmux_args += ("-q",)
230+
231+
cmd = self.cmd("show-options", *tmux_args)
232+
233+
output = cmd.stdout
234+
235+
# The shlex.split function splits the args at spaces, while also
236+
# retaining quoted sub-strings.
237+
# shlex.split('this is "a test"') => ['this', 'is', 'a test']
238+
239+
window_options: "OptionDict" = {}
240+
for item in output:
241+
try:
242+
key, val = shlex.split(item)
243+
except ValueError:
244+
logger.exception(f"Error extracting option: {item}")
245+
assert isinstance(key, str)
246+
assert isinstance(val, str)
247+
248+
if isinstance(val, str) and val.isdigit():
249+
window_options[key] = int(val)
250+
251+
return window_options
252+
253+
def show_option(
254+
self,
255+
option: str,
256+
g: bool = False,
257+
scope: t.Optional[t.Union[OptionScope, _DefaultScope]] = DEFAULT_SCOPE,
258+
ignore_errors: t.Optional[bool] = None,
259+
include_hooks: t.Optional[bool] = None,
260+
include_parents: t.Optional[bool] = None,
261+
) -> t.Optional[t.Union[str, int]]:
262+
"""Return option value for the target window.
263+
264+
todo: test and return True/False for on/off string
265+
266+
Parameters
267+
----------
268+
option : str
269+
g : bool, optional
270+
Pass ``-g`` flag, global. Default False.
271+
272+
Raises
273+
------
274+
:exc:`exc.OptionError`, :exc:`exc.UnknownOption`,
275+
:exc:`exc.InvalidOption`, :exc:`exc.AmbiguousOption`
276+
"""
277+
if scope is DEFAULT_SCOPE:
278+
scope = self.default_scope
279+
280+
tmux_args: t.Tuple[t.Union[str, int], ...] = ()
281+
282+
if g:
283+
tmux_args += ("-g",)
284+
285+
if scope is not None and not isinstance(scope, _DefaultScope):
286+
assert scope in OPTION_SCOPE_FLAG_MAP
287+
tmux_args += (OPTION_SCOPE_FLAG_MAP[scope],)
288+
289+
if ignore_errors is not None and ignore_errors:
290+
tmux_args += ("-q",)
291+
292+
if include_parents is not None and include_parents:
293+
tmux_args += ("-A",)
294+
295+
if include_hooks is not None and include_hooks:
296+
tmux_args += ("-H",)
297+
298+
tmux_args += (option,)
299+
300+
cmd = self.cmd("show-options", *tmux_args)
301+
302+
if len(cmd.stderr):
303+
handle_option_error(cmd.stderr[0])
304+
305+
window_options_output = cmd.stdout
306+
307+
if not len(window_options_output):
308+
return None
309+
310+
value_raw = next(shlex.split(item) for item in window_options_output)
311+
312+
value: t.Union[str, int] = (
313+
int(value_raw[1]) if value_raw[1].isdigit() else value_raw[1]
314+
)
315+
316+
return value

0 commit comments

Comments
 (0)