6
6
7
7
from __future__ import annotations
8
8
9
+ import time
9
10
import typing as t
10
11
from dataclasses import dataclass
11
- from typing import Callable , TypeVar
12
+ from typing import (
13
+ TypeVar ,
14
+ )
12
15
13
- from libtmux .test .retry import retry_until
16
+ from libtmux .exc import LibTmuxException
17
+ from libtmux .test .retry import WaitTimeout , retry_until
14
18
15
19
if t .TYPE_CHECKING :
16
20
from libtmux .pane import Pane
17
21
18
22
T = TypeVar ("T" )
19
23
20
24
25
+ class WaiterError (LibTmuxException ):
26
+ """Base exception for waiter errors."""
27
+
28
+
29
+ class WaiterTimeoutError (WaiterError ):
30
+ """Exception raised when waiting for content times out."""
31
+
32
+
33
+ class WaiterContentError (WaiterError ):
34
+ """Exception raised when there's an error getting or checking content."""
35
+
36
+
21
37
@dataclass
22
38
class WaitResult (t .Generic [T ]):
23
39
"""Result of a wait operation."""
@@ -43,112 +59,101 @@ def __init__(self, pane: Pane, timeout: float = 2.0) -> None:
43
59
self .pane = pane
44
60
self .timeout = timeout
45
61
46
- def wait_for_content (
62
+ def _check_content (
47
63
self ,
48
- predicate : Callable [[str ], bool ],
49
- * ,
50
- timeout : float | None = None ,
51
- error_message : str | None = None ,
52
- ) -> WaitResult [str ]:
53
- """Wait for pane content to match predicate.
64
+ predicate : t .Callable [[str ], bool ],
65
+ result : WaitResult ,
66
+ ) -> bool :
67
+ """Check pane content against predicate.
54
68
55
69
Parameters
56
70
----------
57
71
predicate : Callable[[str], bool]
58
72
Function that takes pane content as string and returns bool
59
- timeout : float | None, optional
60
- Timeout in seconds, by default None (uses instance timeout)
61
- error_message : str | None, optional
62
- Custom error message if timeout occurs, by default None
73
+ result : WaitResult
74
+ Result object to store content if predicate matches
63
75
64
76
Returns
65
77
-------
66
- WaitResult[str]
67
- Result containing success status and pane content if successful
78
+ bool
79
+ True if predicate matches, False otherwise
80
+
81
+ Raises
82
+ ------
83
+ WaiterContentError
84
+ If there's an error capturing pane content
68
85
"""
69
- timeout = timeout or self .timeout
70
- result = WaitResult [str ](success = False )
71
-
72
- def check_content () -> bool :
73
- try :
74
- content = "\n " .join (self .pane .capture_pane ())
75
- if predicate (content ):
76
- result .success = True
77
- result .value = content
78
- return True
79
- else :
80
- return False
81
- except Exception as e :
82
- result .error = e
83
- return False
86
+ try :
87
+ content = "\n " .join (self .pane .capture_pane ())
88
+ if predicate (content ):
89
+ result .value = content
90
+ return True
91
+ return False
92
+ except Exception as e :
93
+ error = WaiterContentError ("Error capturing pane content" )
94
+ error .__cause__ = e
95
+ raise error from e
84
96
97
+ def wait_for_content (
98
+ self ,
99
+ predicate : t .Callable [[str ], bool ],
100
+ timeout_seconds : float | None = None ,
101
+ interval_seconds : float | None = None ,
102
+ error_message : str | None = None ,
103
+ ) -> WaitResult :
104
+ """Wait for content in the pane to match a predicate."""
105
+ result = WaitResult (success = False , value = None , error = None )
85
106
try :
86
- success = retry_until (check_content , timeout , raises = False )
107
+ # Give the shell a moment to be ready
108
+ time .sleep (0.1 )
109
+ success = retry_until (
110
+ lambda : self ._check_content (predicate , result ),
111
+ seconds = timeout_seconds or self .timeout ,
112
+ interval = interval_seconds ,
113
+ raises = True ,
114
+ )
115
+ result .success = success
87
116
if not success :
88
- result .error = Exception (
117
+ result .error = WaiterTimeoutError (
89
118
error_message or "Timed out waiting for content" ,
90
119
)
91
- except Exception as e :
120
+ except WaitTimeout as e :
121
+ result .error = WaiterTimeoutError (error_message or str (e ))
122
+ result .success = False
123
+ except WaiterContentError as e :
92
124
result .error = e
93
- if error_message :
94
- result .error = Exception (error_message )
95
-
125
+ result .success = False
126
+ except Exception as e :
127
+ if isinstance (e , (WaiterTimeoutError , WaiterContentError )):
128
+ result .error = e
129
+ else :
130
+ result .error = WaiterContentError ("Error capturing pane content" )
131
+ result .error .__cause__ = e
132
+ result .success = False
96
133
return result
97
134
98
135
def wait_for_prompt (
99
136
self ,
100
137
prompt : str ,
101
- * ,
102
- timeout : float | None = None ,
138
+ timeout_seconds : float | None = None ,
103
139
error_message : str | None = None ,
104
- ) -> WaitResult [str ]:
105
- """Wait for specific prompt to appear in pane.
106
-
107
- Parameters
108
- ----------
109
- prompt : str
110
- The prompt text to wait for
111
- timeout : float | None, optional
112
- Timeout in seconds, by default None (uses instance timeout)
113
- error_message : str | None, optional
114
- Custom error message if timeout occurs, by default None
115
-
116
- Returns
117
- -------
118
- WaitResult[str]
119
- Result containing success status and pane content if successful
120
- """
140
+ ) -> WaitResult :
141
+ """Wait for a specific prompt to appear in the pane."""
121
142
return self .wait_for_content (
122
143
lambda content : prompt in content and len (content .strip ()) > 0 ,
123
- timeout = timeout ,
144
+ timeout_seconds = timeout_seconds ,
124
145
error_message = error_message or f"Prompt '{ prompt } ' not found in pane" ,
125
146
)
126
147
127
148
def wait_for_text (
128
149
self ,
129
150
text : str ,
130
- * ,
131
- timeout : float | None = None ,
151
+ timeout_seconds : float | None = None ,
132
152
error_message : str | None = None ,
133
- ) -> WaitResult [str ]:
134
- """Wait for specific text to appear in pane.
135
-
136
- Parameters
137
- ----------
138
- text : str
139
- The text to wait for
140
- timeout : float | None, optional
141
- Timeout in seconds, by default None (uses instance timeout)
142
- error_message : str | None, optional
143
- Custom error message if timeout occurs, by default None
144
-
145
- Returns
146
- -------
147
- WaitResult[str]
148
- Result containing success status and pane content if successful
149
- """
153
+ ) -> WaitResult :
154
+ """Wait for specific text to appear in the pane."""
150
155
return self .wait_for_content (
151
156
lambda content : text in content ,
152
- timeout = timeout ,
157
+ timeout_seconds = timeout_seconds ,
153
158
error_message = error_message or f"Text '{ text } ' not found in pane" ,
154
159
)
0 commit comments