Skip to content

Commit ec4752f

Browse files
committed
hooks(fix[show_hooks]): Store string hook values in returned dict
why: show_hooks() only stored values when val.isdigit() was True, silently dropping all string hook values (the common case for tmux hooks). what: - Fix parsing to split on first whitespace only (handles multi-word values) - Remove unused shlex import - Add docstring with Parameters, Returns, and Examples sections - Add parametrized test with NamedTuple for show_hooks string values
1 parent 79b16d6 commit ec4752f

File tree

2 files changed

+102
-8
lines changed

2 files changed

+102
-8
lines changed

src/libtmux/hooks.py

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
from __future__ import annotations
3131

3232
import logging
33-
import shlex
3433
import typing as t
3534
import warnings
3635

@@ -254,7 +253,37 @@ def show_hooks(
254253
scope: OptionScope | _DefaultOptionScope | None = DEFAULT_OPTION_SCOPE,
255254
ignore_errors: bool | None = None,
256255
) -> HookDict:
257-
"""Return a dict of hooks for the target."""
256+
"""Return a dict of hooks for the target.
257+
258+
Parameters
259+
----------
260+
global_ : bool, optional
261+
Pass ``-g`` flag for global hooks, default False.
262+
scope : OptionScope | _DefaultOptionScope | None, optional
263+
Hook scope (Server/Session/Window/Pane), defaults to object's scope.
264+
ignore_errors : bool, optional
265+
Suppress errors with ``-q`` flag.
266+
267+
Returns
268+
-------
269+
HookDict
270+
Dictionary mapping hook names to their values.
271+
272+
Examples
273+
--------
274+
>>> session.set_hook('session-renamed[0]', 'display-message "test"')
275+
Session($...)
276+
277+
>>> hooks = session.show_hooks()
278+
>>> isinstance(hooks, dict)
279+
True
280+
281+
>>> 'session-renamed[0]' in hooks
282+
True
283+
284+
>>> session.unset_hook('session-renamed')
285+
Session($...)
286+
"""
258287
if scope is DEFAULT_OPTION_SCOPE:
259288
scope = self.default_hook_scope
260289

@@ -283,16 +312,20 @@ def show_hooks(
283312
output = cmd.stdout
284313
hooks: HookDict = {}
285314
for item in output:
286-
try:
287-
key, val = shlex.split(item)
288-
except ValueError:
315+
# Split on first whitespace only to handle multi-word hook values
316+
parts = item.split(None, 1)
317+
if len(parts) == 2:
318+
key, val = parts
319+
elif len(parts) == 1:
320+
key, val = parts[0], None
321+
else:
289322
logger.warning(f"Error extracting hook: {item}")
290-
key, val = item, None
291-
assert isinstance(key, str)
292-
assert isinstance(val, str) or val is None
323+
continue
293324

294325
if isinstance(val, str) and val.isdigit():
295326
hooks[key] = int(val)
327+
elif isinstance(val, str):
328+
hooks[key] = val
296329

297330
return hooks
298331

tests/test_hooks.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,3 +712,64 @@ def test_set_hook_append_flag(server: Server) -> None:
712712

713713
# Cleanup
714714
session.unset_hook("session-renamed")
715+
716+
717+
# =============================================================================
718+
# show_hooks Tests
719+
# =============================================================================
720+
721+
722+
class ShowHooksTestCase(t.NamedTuple):
723+
"""Test case for show_hooks validation."""
724+
725+
test_id: str
726+
hook: str
727+
value: str
728+
expected_value: str
729+
expected_type: type
730+
min_version: str = "3.2"
731+
732+
733+
SHOW_HOOKS_TEST_CASES: list[ShowHooksTestCase] = [
734+
ShowHooksTestCase(
735+
test_id="string_hook_value",
736+
hook="session-renamed[0]",
737+
value='display-message "test"',
738+
expected_value="display-message test", # tmux strips quotes in output
739+
expected_type=str,
740+
),
741+
ShowHooksTestCase(
742+
test_id="multiple_hooks",
743+
hook="session-renamed[1]",
744+
value='display-message "another"',
745+
expected_value="display-message another", # tmux strips quotes in output
746+
expected_type=str,
747+
),
748+
]
749+
750+
751+
def _build_show_hooks_params() -> list[t.Any]:
752+
"""Build pytest params for show_hooks tests."""
753+
return [pytest.param(tc, id=tc.test_id) for tc in SHOW_HOOKS_TEST_CASES]
754+
755+
756+
@pytest.mark.parametrize("test_case", _build_show_hooks_params())
757+
def test_show_hooks_stores_string_values(
758+
server: Server,
759+
test_case: ShowHooksTestCase,
760+
) -> None:
761+
"""Test that show_hooks() correctly stores string hook values."""
762+
if not has_gte_version(test_case.min_version):
763+
pytest.skip(f"Requires tmux >= {test_case.min_version}")
764+
765+
session = server.new_session(session_name="test_show_hooks")
766+
767+
session.set_hook(test_case.hook, test_case.value)
768+
hooks = session.show_hooks()
769+
770+
assert test_case.hook in hooks
771+
assert isinstance(hooks[test_case.hook], test_case.expected_type)
772+
assert hooks[test_case.hook] == test_case.expected_value
773+
774+
# Cleanup
775+
session.unset_hook(test_case.hook.split("[")[0])

0 commit comments

Comments
 (0)