66import secrets
77import threading
88import time
9+ from typing import Any
10+
11+ from rich .console import Group
12+ from rich .panel import Panel
13+ from rich .table import Table
14+ from rich .text import Text
915
1016import cmd2
1117from cmd2 import (
1218 Color ,
1319 stylize ,
1420)
1521
16- ALERTS = [
17- "Watch as this application prints alerts and updates the prompt" ,
18- "This will only happen when the prompt is present" ,
19- "Notice how it doesn't interfere with your typing or cursor location" ,
20- "Go ahead and type some stuff and move the cursor throughout the line" ,
21- "Keep typing..." ,
22- "Move that cursor..." ,
23- "Pretty seamless, eh?" ,
24- "Feedback can also be given in the window title. Notice the alert count up there?" ,
25- "You can stop and start the alerts by typing stop_alerts and start_alerts" ,
26- "This demo will now continue to print alerts at random intervals" ,
27- ]
22+
23+ def get_alerts () -> list [tuple [Any , bool ]]:
24+ """Return a list of (alert_msg, soft_wrap) tuples."""
25+ table = Table ("Command" , "Description" , title = "Quick Help" )
26+ table .add_row ("start_alerts" , "Start the async alert generator" )
27+ table .add_row ("stop_alerts" , "Stop the async alert generator" )
28+ table .add_row ("help" , "Show help menu" )
29+
30+ return [
31+ (Text ("Watch as this application prints alerts asynchronously!" , style = "bold bright_cyan" ), True ),
32+ ("Notice how alerts don't interfere with your typing or cursor location." , True ),
33+ (
34+ Panel (
35+ "This message is wrapped in a Rich Panel!" ,
36+ title = "System Alert" ,
37+ border_style = "bright_blue" ,
38+ expand = False ,
39+ ),
40+ False ,
41+ ),
42+ (table , False ),
43+ ("You can stop and start the alerts by typing stop_alerts and start_alerts." , True ),
44+ ]
2845
2946
3047class AlerterApp (cmd2 .Cmd ):
@@ -40,7 +57,6 @@ def __init__(self) -> None:
4057 self ._stop_event = threading .Event ()
4158 self ._add_alert_thread = threading .Thread ()
4259 self ._alert_count = 0
43- self ._next_alert_time = 0.0
4460
4561 # Create some hooks to handle the starting and stopping of our thread
4662 self .register_preloop_hook (self ._preloop_hook )
@@ -78,56 +94,6 @@ def do_stop_alerts(self, _: cmd2.Statement) -> None:
7894 else :
7995 print ("The alert thread is already stopped" )
8096
81- def _get_alerts (self ) -> list [str ]:
82- """Reports alerts
83- :return: the list of alerts.
84- """
85- cur_time = time .monotonic ()
86- if cur_time < self ._next_alert_time :
87- return []
88-
89- alerts = []
90-
91- if self ._alert_count < len (ALERTS ):
92- alerts .append (ALERTS [self ._alert_count ])
93- self ._alert_count += 1
94- self ._next_alert_time = cur_time + 4
95-
96- else :
97- rand_num = self ._secure_generator .randint (1 , 20 )
98- if rand_num > 2 :
99- return []
100-
101- for _ in range (rand_num ):
102- self ._alert_count += 1
103- alerts .append (f"Alert { self ._alert_count } " )
104-
105- self ._next_alert_time = 0
106-
107- return alerts
108-
109- def _build_alert_str (self ) -> str :
110- """Combines alerts into one string that can be printed to the terminal
111- :return: the alert string.
112- """
113- alert_str = ""
114- alerts = self ._get_alerts ()
115-
116- longest_alert = max (ALERTS , key = len )
117- num_asterisks = len (longest_alert ) + 8
118-
119- for i , cur_alert in enumerate (alerts ):
120- # Use padding to center the alert
121- padding = " " * int ((num_asterisks - len (cur_alert )) / 2 )
122-
123- if i > 0 :
124- alert_str += "\n "
125- alert_str += "*" * num_asterisks + "\n "
126- alert_str += padding + cur_alert + padding + "\n "
127- alert_str += "*" * num_asterisks + "\n "
128-
129- return alert_str
130-
13197 def _build_colored_prompt (self ) -> str :
13298 """Randomly builds a colored prompt
13399 :return: the new prompt.
@@ -152,26 +118,45 @@ def _build_colored_prompt(self) -> str:
152118 def _add_alerts_func (self ) -> None :
153119 """Prints alerts and updates the prompt any time the prompt is showing."""
154120 self ._alert_count = 0
155- self ._next_alert_time = 0
121+
122+ alerts = get_alerts ()
123+ alert_index = 0
124+ last_alert_time = 0.0
156125
157126 while not self ._stop_event .is_set ():
158- # Get any alerts that need to be printed
159- alert_str = self ._build_alert_str ()
127+ cur_time = time .monotonic ()
128+ alert_msg = None
129+ soft_wrap = True
130+
131+ # Trigger the next alert every 4 seconds
132+ if cur_time - last_alert_time >= 4.0 :
133+ alert_msg , soft_wrap = alerts [alert_index ]
134+ alert_index = (alert_index + 1 ) % len (alerts )
135+ self ._alert_count += 1
136+ last_alert_time = cur_time
160137
161- # Build a new prompt
138+ # Build a new prompt (color changes randomly)
162139 new_prompt = self ._build_colored_prompt ()
163140
164- # Check if we have alerts to print
165- if alert_str :
166- self .add_alert (msg = alert_str , prompt = new_prompt )
141+ # Check if we have an alert to print
142+ if alert_msg is not None :
143+ # Wrap the alert message and an empty string in a Rich Group.
144+ # This is a clean way to append a blank line after any RenderableType
145+ # (strings, panels, tables, etc.) for visual separation.
146+ self .add_alert (
147+ msg = Group (alert_msg , "" ),
148+ soft_wrap = soft_wrap ,
149+ prompt = new_prompt ,
150+ )
151+
167152 new_title = f"Alerts Printed: { self ._alert_count } "
168153 self .set_window_title (new_title )
169154
170- # Otherwise check if the prompt needs to be updated or refreshed
155+ # Otherwise, check if the prompt needs to be updated or refreshed
171156 elif self .prompt != new_prompt :
172157 self .add_alert (prompt = new_prompt )
173158
174- self ._stop_event .wait (0.5 )
159+ self ._stop_event .wait (1.0 )
175160
176161
177162if __name__ == "__main__" :
0 commit comments