hammad-python 0.0.10__py3-none-any.whl → 0.0.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hammad/__init__.py +64 -10
- hammad/based/__init__.py +52 -0
- hammad/based/fields.py +546 -0
- hammad/based/model.py +968 -0
- hammad/based/utils.py +455 -0
- hammad/cache/__init__.py +30 -0
- hammad/{cache.py → cache/_cache.py} +83 -12
- hammad/cli/__init__.py +25 -0
- hammad/cli/plugins/__init__.py +786 -0
- hammad/cli/styles/__init__.py +5 -0
- hammad/cli/styles/animations.py +548 -0
- hammad/cli/styles/settings.py +135 -0
- hammad/cli/styles/types.py +358 -0
- hammad/cli/styles/utils.py +480 -0
- hammad/data/__init__.py +51 -0
- hammad/data/collections/__init__.py +32 -0
- hammad/data/collections/base_collection.py +58 -0
- hammad/data/collections/collection.py +227 -0
- hammad/data/collections/searchable_collection.py +556 -0
- hammad/data/collections/vector_collection.py +497 -0
- hammad/data/databases/__init__.py +21 -0
- hammad/data/databases/database.py +551 -0
- hammad/data/types/__init__.py +33 -0
- hammad/data/types/files/__init__.py +1 -0
- hammad/data/types/files/audio.py +81 -0
- hammad/data/types/files/configuration.py +475 -0
- hammad/data/types/files/document.py +195 -0
- hammad/data/types/files/file.py +358 -0
- hammad/data/types/files/image.py +80 -0
- hammad/json/__init__.py +21 -0
- hammad/{utils/json → json}/converters.py +4 -1
- hammad/logging/__init__.py +27 -0
- hammad/logging/decorators.py +432 -0
- hammad/logging/logger.py +534 -0
- hammad/pydantic/__init__.py +43 -0
- hammad/{utils/pydantic → pydantic}/converters.py +2 -1
- hammad/pydantic/models/__init__.py +28 -0
- hammad/pydantic/models/arbitrary_model.py +46 -0
- hammad/pydantic/models/cacheable_model.py +79 -0
- hammad/pydantic/models/fast_model.py +318 -0
- hammad/pydantic/models/function_model.py +176 -0
- hammad/pydantic/models/subscriptable_model.py +63 -0
- hammad/text/__init__.py +37 -0
- hammad/text/text.py +1068 -0
- hammad/text/utils/__init__.py +1 -0
- hammad/{utils/text → text/utils}/converters.py +2 -2
- hammad/text/utils/markdown/__init__.py +1 -0
- hammad/{utils → text/utils}/markdown/converters.py +3 -3
- hammad/{utils → text/utils}/markdown/formatting.py +1 -1
- hammad/{utils/typing/utils.py → typing/__init__.py} +75 -2
- hammad/web/__init__.py +42 -0
- hammad/web/http/__init__.py +1 -0
- hammad/web/http/client.py +944 -0
- hammad/web/openapi/client.py +740 -0
- hammad/web/search/__init__.py +1 -0
- hammad/web/search/client.py +936 -0
- hammad/web/utils.py +463 -0
- hammad/yaml/__init__.py +30 -0
- hammad/yaml/converters.py +19 -0
- {hammad_python-0.0.10.dist-info → hammad_python-0.0.11.dist-info}/METADATA +14 -8
- hammad_python-0.0.11.dist-info/RECORD +65 -0
- hammad/database.py +0 -447
- hammad/logger.py +0 -273
- hammad/types/color.py +0 -951
- hammad/utils/json/__init__.py +0 -0
- hammad/utils/markdown/__init__.py +0 -0
- hammad/utils/pydantic/__init__.py +0 -0
- hammad/utils/text/__init__.py +0 -0
- hammad/utils/typing/__init__.py +0 -0
- hammad_python-0.0.10.dist-info/RECORD +0 -22
- /hammad/{types/__init__.py → py.typed} +0 -0
- /hammad/{utils → web/openapi}/__init__.py +0 -0
- {hammad_python-0.0.10.dist-info → hammad_python-0.0.11.dist-info}/WHEEL +0 -0
- {hammad_python-0.0.10.dist-info → hammad_python-0.0.11.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,548 @@
|
|
1
|
+
"""hammad.cli.styles.animations"""
|
2
|
+
|
3
|
+
import time
|
4
|
+
import math
|
5
|
+
import random
|
6
|
+
import threading
|
7
|
+
from dataclasses import dataclass, field
|
8
|
+
from typing import Literal, Optional, List, overload, TYPE_CHECKING
|
9
|
+
|
10
|
+
if TYPE_CHECKING:
|
11
|
+
from rich import get_console
|
12
|
+
from rich.console import Console, ConsoleOptions, RenderResult, RenderableType
|
13
|
+
from rich.live import Live
|
14
|
+
from rich.text import Text
|
15
|
+
from rich.panel import Panel
|
16
|
+
|
17
|
+
from .types import (
|
18
|
+
CLIStyleColorName,
|
19
|
+
)
|
20
|
+
|
21
|
+
|
22
|
+
def _get_rich_animation_classes():
|
23
|
+
"""Lazy import for rich classes used in animations"""
|
24
|
+
from rich import get_console
|
25
|
+
from rich.console import Console, ConsoleOptions, RenderResult, RenderableType
|
26
|
+
from rich.live import Live
|
27
|
+
from rich.text import Text
|
28
|
+
from rich.panel import Panel
|
29
|
+
|
30
|
+
return {
|
31
|
+
"get_console": get_console,
|
32
|
+
"Console": Console,
|
33
|
+
"ConsoleOptions": ConsoleOptions,
|
34
|
+
"RenderResult": RenderResult,
|
35
|
+
"RenderableType": RenderableType,
|
36
|
+
"Live": Live,
|
37
|
+
"Text": Text,
|
38
|
+
"Panel": Panel,
|
39
|
+
}
|
40
|
+
|
41
|
+
|
42
|
+
__all__ = (
|
43
|
+
"CLIAnimation",
|
44
|
+
"CLIAnimationState",
|
45
|
+
"CLIFlashingAnimation",
|
46
|
+
"CLIPulsingAnimation",
|
47
|
+
"CLIShakingAnimation",
|
48
|
+
"CLITypingAnimation",
|
49
|
+
"CLISpinningAnimation",
|
50
|
+
"CLIRainbowAnimation",
|
51
|
+
"animate_flashing",
|
52
|
+
"animate_pulsing",
|
53
|
+
"animate_shaking",
|
54
|
+
"animate_spinning",
|
55
|
+
"animate_rainbow",
|
56
|
+
"animate_typing",
|
57
|
+
)
|
58
|
+
|
59
|
+
|
60
|
+
@dataclass
|
61
|
+
class CLIAnimationState:
|
62
|
+
"""Internal class used to track the current state of an
|
63
|
+
animation."""
|
64
|
+
|
65
|
+
start_time: float = field(default_factory=time.time)
|
66
|
+
frame: int = 0
|
67
|
+
last_update: float | None = field(default_factory=time.time)
|
68
|
+
|
69
|
+
|
70
|
+
@dataclass
|
71
|
+
class CLIAnimation:
|
72
|
+
"""Base class for all animations within the `hammad` package,
|
73
|
+
this is used to integrate with rich's `__rich_console__` protocol."""
|
74
|
+
|
75
|
+
def __init__(
|
76
|
+
self,
|
77
|
+
# The object that this animation is being applied to.
|
78
|
+
renderable,
|
79
|
+
duration: Optional[float] = None,
|
80
|
+
) -> None:
|
81
|
+
self.renderable = renderable
|
82
|
+
"""The object that this animation is being applied to."""
|
83
|
+
self.duration = duration or 2.0
|
84
|
+
"""The duration of the animation in seconds (defaults to 2.0 seconds)."""
|
85
|
+
# Set last_update to None to ensure the animation is classified as
|
86
|
+
# the first update on init.
|
87
|
+
self.state = CLIAnimationState(last_update=None)
|
88
|
+
"""The current state of the animation."""
|
89
|
+
|
90
|
+
rich_classes = _get_rich_animation_classes()
|
91
|
+
self.rich_console = rich_classes["get_console"]()
|
92
|
+
"""The rich console responsible for rendering the animation."""
|
93
|
+
self._animation_thread: threading.Thread | None = None
|
94
|
+
"""The thread responsible for running the animation."""
|
95
|
+
self._stop_animation = False
|
96
|
+
"""Flag used to stop the animation."""
|
97
|
+
|
98
|
+
def __rich_console__(
|
99
|
+
self,
|
100
|
+
console,
|
101
|
+
options,
|
102
|
+
):
|
103
|
+
"""Rich will call this automatically when rendering."""
|
104
|
+
if not self.is_complete:
|
105
|
+
console.force_terminal = True
|
106
|
+
if console.is_terminal:
|
107
|
+
# force referesh
|
108
|
+
console._is_alt_screen = False
|
109
|
+
|
110
|
+
current_time = time.time()
|
111
|
+
self.state.frame += 1
|
112
|
+
self.state.last_update = current_time
|
113
|
+
|
114
|
+
yield from self.apply(console, options)
|
115
|
+
|
116
|
+
def apply(self, console, options):
|
117
|
+
"""Used by subclasses to apply the animation."""
|
118
|
+
yield self.renderable
|
119
|
+
|
120
|
+
@property
|
121
|
+
def time_elapsed(self) -> float:
|
122
|
+
"""Time elapsed since the animation started."""
|
123
|
+
return time.time() - self.state.start_time
|
124
|
+
|
125
|
+
@property
|
126
|
+
def is_complete(self) -> bool:
|
127
|
+
"""Check if the animation is complete."""
|
128
|
+
if self.duration is None:
|
129
|
+
return False
|
130
|
+
return self.time_elapsed >= self.duration
|
131
|
+
|
132
|
+
def animate(
|
133
|
+
self,
|
134
|
+
duration: Optional[float] = None,
|
135
|
+
refresh_rate: int = 20,
|
136
|
+
) -> None:
|
137
|
+
"""Animate this effect for the specified duration using Live."""
|
138
|
+
animate_duration = duration or self.duration or 3.0
|
139
|
+
rich_classes = _get_rich_animation_classes()
|
140
|
+
Console = rich_classes["Console"]
|
141
|
+
Live = rich_classes["Live"]
|
142
|
+
console = Console()
|
143
|
+
|
144
|
+
with Live(
|
145
|
+
self, console=console, refresh_per_second=refresh_rate, transient=True
|
146
|
+
) as live:
|
147
|
+
start = time.time()
|
148
|
+
while time.time() - start < animate_duration:
|
149
|
+
time.sleep(0.05)
|
150
|
+
|
151
|
+
|
152
|
+
class CLIFlashingAnimation(CLIAnimation):
|
153
|
+
"""Makes any renderable flash/blink."""
|
154
|
+
|
155
|
+
def __init__(
|
156
|
+
self,
|
157
|
+
renderable,
|
158
|
+
speed: float = 0.5,
|
159
|
+
colors: Optional[List[CLIStyleColorName]] = None,
|
160
|
+
duration: Optional[float] = None,
|
161
|
+
):
|
162
|
+
super().__init__(renderable, duration)
|
163
|
+
self.speed = speed
|
164
|
+
self.colors = colors or [
|
165
|
+
"bright_white",
|
166
|
+
"bright_yellow",
|
167
|
+
"bright_cyan",
|
168
|
+
"bright_magenta",
|
169
|
+
]
|
170
|
+
|
171
|
+
def apply(self, console, options):
|
172
|
+
rich_classes = _get_rich_animation_classes()
|
173
|
+
Text = rich_classes["Text"]
|
174
|
+
|
175
|
+
# Calculate which color to use based on time
|
176
|
+
color_index = int(self.time_elapsed / self.speed) % len(self.colors)
|
177
|
+
color = self.colors[color_index]
|
178
|
+
|
179
|
+
# Apply color to the renderable
|
180
|
+
if isinstance(self.renderable, str):
|
181
|
+
yield Text(self.renderable, style=color)
|
182
|
+
else:
|
183
|
+
# Wrap any renderable in the flash color
|
184
|
+
yield Text.from_markup(f"[{color}]{self.renderable}[/{color}]")
|
185
|
+
|
186
|
+
|
187
|
+
class CLIPulsingAnimation(CLIAnimation):
|
188
|
+
"""Makes any renderable pulse/breathe."""
|
189
|
+
|
190
|
+
def __init__(
|
191
|
+
self,
|
192
|
+
renderable: "RenderableType",
|
193
|
+
speed: float = 2.0,
|
194
|
+
min_opacity: float = 0.3,
|
195
|
+
max_opacity: float = 1.0,
|
196
|
+
color: "CLIStyleColorName" = "white",
|
197
|
+
duration: Optional[float] = None,
|
198
|
+
):
|
199
|
+
super().__init__(renderable, duration)
|
200
|
+
self.speed = speed
|
201
|
+
self.min_opacity = min_opacity
|
202
|
+
self.max_opacity = max_opacity
|
203
|
+
self.color = color
|
204
|
+
|
205
|
+
def apply(self, console: "Console", options: "ConsoleOptions") -> "RenderResult":
|
206
|
+
# Calculate opacity using sine wave
|
207
|
+
opacity = self.min_opacity + (self.max_opacity - self.min_opacity) * (
|
208
|
+
0.5 + 0.5 * math.sin(self.time_elapsed * self.speed)
|
209
|
+
)
|
210
|
+
|
211
|
+
# Convert opacity to RGB values for fading effect
|
212
|
+
rgb_value = int(opacity * 255)
|
213
|
+
fade_color = f"rgb({rgb_value},{rgb_value},{rgb_value})"
|
214
|
+
|
215
|
+
if isinstance(self.renderable, str):
|
216
|
+
yield Text(self.renderable, style=fade_color)
|
217
|
+
else:
|
218
|
+
# For Panel and other renderables, we need to use opacity styling
|
219
|
+
if isinstance(self.renderable, Panel):
|
220
|
+
# Create a new panel with modified style
|
221
|
+
new_panel = Panel(
|
222
|
+
self.renderable.renderable,
|
223
|
+
title=self.renderable.title,
|
224
|
+
title_align=self.renderable.title_align,
|
225
|
+
subtitle=self.renderable.subtitle,
|
226
|
+
subtitle_align=self.renderable.subtitle_align,
|
227
|
+
box=self.renderable.box,
|
228
|
+
style=fade_color,
|
229
|
+
border_style=fade_color,
|
230
|
+
expand=self.renderable.expand,
|
231
|
+
padding=self.renderable.padding,
|
232
|
+
width=self.renderable.width,
|
233
|
+
height=self.renderable.height,
|
234
|
+
)
|
235
|
+
yield new_panel
|
236
|
+
else:
|
237
|
+
# For other renderables, wrap in a panel with the fade effect
|
238
|
+
yield Panel(self.renderable, style=fade_color, border_style=fade_color)
|
239
|
+
|
240
|
+
|
241
|
+
class CLIShakingAnimation(CLIAnimation):
|
242
|
+
"""Makes text shake/jitter."""
|
243
|
+
|
244
|
+
def __init__(
|
245
|
+
self,
|
246
|
+
renderable: "RenderableType",
|
247
|
+
intensity: int = 1,
|
248
|
+
speed: float = 0.1,
|
249
|
+
duration: Optional[float] = None,
|
250
|
+
):
|
251
|
+
super().__init__(renderable, duration)
|
252
|
+
self.intensity = intensity
|
253
|
+
self.speed = speed
|
254
|
+
self.last_shake = 0
|
255
|
+
|
256
|
+
def apply(self, console: "Console", options: "ConsoleOptions") -> "RenderResult":
|
257
|
+
if self.time_elapsed - self.last_shake > self.speed:
|
258
|
+
self.last_shake = self.time_elapsed
|
259
|
+
|
260
|
+
# Add random spaces for shake effect
|
261
|
+
shake = " " * random.randint(0, self.intensity)
|
262
|
+
|
263
|
+
if isinstance(self.renderable, str):
|
264
|
+
yield Text(shake + self.renderable)
|
265
|
+
else:
|
266
|
+
yield Text(shake) + self.renderable
|
267
|
+
else:
|
268
|
+
# Keep previous position
|
269
|
+
yield self.renderable
|
270
|
+
|
271
|
+
|
272
|
+
class CLITypingAnimation(CLIAnimation):
|
273
|
+
"""Typewriter effect."""
|
274
|
+
|
275
|
+
def __init__(
|
276
|
+
self, text: str, speed: float = 0.05, duration: Optional[float] = None
|
277
|
+
):
|
278
|
+
super().__init__(text, duration)
|
279
|
+
self.text = text
|
280
|
+
self.speed = speed
|
281
|
+
|
282
|
+
def apply(self, console: "Console", options: "ConsoleOptions") -> "RenderResult":
|
283
|
+
# Calculate how many characters to show
|
284
|
+
chars_to_show = int(self.time_elapsed / self.speed)
|
285
|
+
chars_to_show = min(chars_to_show, len(self.text))
|
286
|
+
|
287
|
+
yield Text(
|
288
|
+
self.text[:chars_to_show] + "█"
|
289
|
+
if chars_to_show < len(self.text)
|
290
|
+
else self.text
|
291
|
+
)
|
292
|
+
|
293
|
+
|
294
|
+
class CLISpinningAnimation(CLIAnimation):
|
295
|
+
"""Spinner effect for any renderable."""
|
296
|
+
|
297
|
+
def __init__(
|
298
|
+
self,
|
299
|
+
renderable: "RenderableType",
|
300
|
+
frames: Optional[List[str]] = None,
|
301
|
+
speed: float = 0.1,
|
302
|
+
prefix: bool = True,
|
303
|
+
duration: Optional[float] = None,
|
304
|
+
):
|
305
|
+
super().__init__(renderable, duration)
|
306
|
+
self.frames = frames or ["⋅", "•", "●", "◉", "●", "•"]
|
307
|
+
self.speed = speed
|
308
|
+
self.prefix = prefix
|
309
|
+
|
310
|
+
def apply(self, console: "Console", options: "ConsoleOptions") -> "RenderResult":
|
311
|
+
frame_index = int(self.time_elapsed / self.speed) % len(self.frames)
|
312
|
+
spinner = self.frames[frame_index]
|
313
|
+
|
314
|
+
if isinstance(self.renderable, str):
|
315
|
+
if self.prefix:
|
316
|
+
yield Text(f"{spinner} {self.renderable}")
|
317
|
+
else:
|
318
|
+
yield Text(f"{self.renderable} {spinner}")
|
319
|
+
else:
|
320
|
+
if self.prefix:
|
321
|
+
yield Text(f"{spinner} ") + self.renderable
|
322
|
+
else:
|
323
|
+
yield self.renderable + Text(f" {spinner}")
|
324
|
+
|
325
|
+
|
326
|
+
RainbowPreset = Literal["classic", "bright", "pastel", "neon"]
|
327
|
+
|
328
|
+
RAINBOW_PRESETS = {
|
329
|
+
"classic": ["red", "yellow", "green", "cyan", "blue", "magenta"],
|
330
|
+
"bright": [
|
331
|
+
"bright_red",
|
332
|
+
"bright_yellow",
|
333
|
+
"bright_green",
|
334
|
+
"bright_cyan",
|
335
|
+
"bright_blue",
|
336
|
+
"bright_magenta",
|
337
|
+
],
|
338
|
+
"pastel": [
|
339
|
+
"light_pink3",
|
340
|
+
"khaki1",
|
341
|
+
"light_green",
|
342
|
+
"light_cyan1",
|
343
|
+
"light_blue",
|
344
|
+
"plum2",
|
345
|
+
],
|
346
|
+
"neon": ["hot_pink", "yellow1", "green1", "cyan1", "blue1", "magenta1"],
|
347
|
+
}
|
348
|
+
|
349
|
+
|
350
|
+
class CLIRainbowAnimation(CLIAnimation):
|
351
|
+
"""Rainbow color cycling effect."""
|
352
|
+
|
353
|
+
def __init__(
|
354
|
+
self,
|
355
|
+
renderable: "RenderableType",
|
356
|
+
speed: float = 0.5,
|
357
|
+
colors: "RainbowPreset | List[CLIStyleColorName] | None" = None,
|
358
|
+
duration: Optional[float] = None,
|
359
|
+
):
|
360
|
+
super().__init__(renderable, duration)
|
361
|
+
self.speed = speed
|
362
|
+
|
363
|
+
# Handle color selection
|
364
|
+
if colors is None:
|
365
|
+
colors = "classic"
|
366
|
+
|
367
|
+
if isinstance(colors, str) and colors in RAINBOW_PRESETS:
|
368
|
+
self.colors = RAINBOW_PRESETS[colors]
|
369
|
+
elif isinstance(colors, list):
|
370
|
+
self.colors = colors
|
371
|
+
else:
|
372
|
+
self.colors = RAINBOW_PRESETS["classic"]
|
373
|
+
|
374
|
+
def apply(self, console: "Console", options: "ConsoleOptions") -> "RenderResult":
|
375
|
+
if isinstance(self.renderable, str):
|
376
|
+
# Apply rainbow to each character
|
377
|
+
result = _get_rich_animation_classes()["Text"]()
|
378
|
+
for i, char in enumerate(self.renderable):
|
379
|
+
color_offset = int(
|
380
|
+
(self.time_elapsed / self.speed + i) % len(self.colors)
|
381
|
+
)
|
382
|
+
color = self.colors[color_offset]
|
383
|
+
result.append(char, style=color)
|
384
|
+
yield result
|
385
|
+
else:
|
386
|
+
# Cycle through colors for the whole renderable
|
387
|
+
color_index = int(self.time_elapsed / self.speed) % len(self.colors)
|
388
|
+
yield Text.from_markup(
|
389
|
+
f"[{self.colors[color_index]}]{self.renderable}[/{self.colors[color_index]}]"
|
390
|
+
)
|
391
|
+
|
392
|
+
|
393
|
+
def animate_flashing(
|
394
|
+
renderable: "RenderableType",
|
395
|
+
duration: Optional[float] = None,
|
396
|
+
speed: float = 0.5,
|
397
|
+
on_color: CLIStyleColorName = "white",
|
398
|
+
off_color: CLIStyleColorName = "dim white",
|
399
|
+
) -> None:
|
400
|
+
"""Create and run a flashing animation on any renderable.
|
401
|
+
|
402
|
+
Args:
|
403
|
+
renderable: The object to animate (text, panel, etc.)
|
404
|
+
duration: Duration of the animation in seconds (defaults to 2.0)
|
405
|
+
speed: Speed of the flashing effect (defaults to 0.5)
|
406
|
+
on_color: Color when flashing "on" (defaults to "white")
|
407
|
+
off_color: Color when flashing "off" (defaults to "dim white")
|
408
|
+
|
409
|
+
Examples:
|
410
|
+
>>> animate_flashing("Alert!", duration=3.0, speed=0.3)
|
411
|
+
>>> animate_flashing(Panel("Warning"), on_color="red", off_color="dark_red")
|
412
|
+
"""
|
413
|
+
animation = CLIFlashingAnimation(
|
414
|
+
renderable,
|
415
|
+
duration=duration,
|
416
|
+
speed=speed,
|
417
|
+
on_color=on_color,
|
418
|
+
off_color=off_color,
|
419
|
+
)
|
420
|
+
animation.animate()
|
421
|
+
|
422
|
+
|
423
|
+
def animate_pulsing(
|
424
|
+
renderable: "RenderableType",
|
425
|
+
duration: Optional[float] = None,
|
426
|
+
speed: float = 1.0,
|
427
|
+
min_opacity: float = 0.3,
|
428
|
+
max_opacity: float = 1.0,
|
429
|
+
) -> None:
|
430
|
+
"""Create and run a pulsing animation on any renderable.
|
431
|
+
|
432
|
+
Args:
|
433
|
+
renderable: The object to animate (text, panel, etc.)
|
434
|
+
duration: Duration of the animation in seconds (defaults to 2.0)
|
435
|
+
speed: Speed of the pulsing effect (defaults to 1.0)
|
436
|
+
min_opacity: Minimum opacity during pulse (defaults to 0.3)
|
437
|
+
max_opacity: Maximum opacity during pulse (defaults to 1.0)
|
438
|
+
|
439
|
+
Examples:
|
440
|
+
>>> animate_pulsing("Loading...", duration=5.0, speed=2.0)
|
441
|
+
>>> animate_pulsing(Panel("Status"), min_opacity=0.1, max_opacity=0.9)
|
442
|
+
"""
|
443
|
+
animation = CLIPulsingAnimation(
|
444
|
+
renderable,
|
445
|
+
duration=duration,
|
446
|
+
speed=speed,
|
447
|
+
min_opacity=min_opacity,
|
448
|
+
max_opacity=max_opacity,
|
449
|
+
)
|
450
|
+
animation.animate()
|
451
|
+
|
452
|
+
|
453
|
+
def animate_shaking(
|
454
|
+
renderable: "RenderableType",
|
455
|
+
duration: Optional[float] = None,
|
456
|
+
intensity: int = 2,
|
457
|
+
speed: float = 10.0,
|
458
|
+
) -> None:
|
459
|
+
"""Create and run a shaking animation on any renderable.
|
460
|
+
|
461
|
+
Args:
|
462
|
+
renderable: The object to animate (text, panel, etc.)
|
463
|
+
duration: Duration of the animation in seconds (defaults to 2.0)
|
464
|
+
intensity: Intensity of the shake effect (defaults to 2)
|
465
|
+
speed: Speed of the shaking motion (defaults to 10.0)
|
466
|
+
|
467
|
+
Examples:
|
468
|
+
>>> animate_shaking("Error!", duration=1.5, intensity=3)
|
469
|
+
>>> animate_shaking(Panel("Critical Alert"), speed=15.0)
|
470
|
+
"""
|
471
|
+
animation = CLIShakingAnimation(
|
472
|
+
renderable, duration=duration, intensity=intensity, speed=speed
|
473
|
+
)
|
474
|
+
animation.animate()
|
475
|
+
|
476
|
+
|
477
|
+
def animate_spinning(
|
478
|
+
renderable: "RenderableType",
|
479
|
+
duration: Optional[float] = None,
|
480
|
+
frames: Optional[List[str]] = None,
|
481
|
+
speed: float = 0.1,
|
482
|
+
prefix: bool = True,
|
483
|
+
) -> None:
|
484
|
+
"""Create and run a spinning animation on any renderable.
|
485
|
+
|
486
|
+
Args:
|
487
|
+
renderable: The object to animate (text, panel, etc.)
|
488
|
+
duration: Duration of the animation in seconds (defaults to 2.0)
|
489
|
+
frames: List of spinner frames (defaults to ["⋅", "•", "●", "◉", "●", "•"])
|
490
|
+
speed: Speed between frame changes (defaults to 0.1)
|
491
|
+
prefix: Whether to show spinner before text (defaults to True)
|
492
|
+
|
493
|
+
Examples:
|
494
|
+
>>> animate_spinning("Processing...", duration=10.0, speed=0.2)
|
495
|
+
>>> animate_spinning("Done", frames=["◐", "◓", "◑", "◒"], prefix=False)
|
496
|
+
"""
|
497
|
+
animation = CLISpinningAnimation(
|
498
|
+
renderable, duration=duration, frames=frames, speed=speed, prefix=prefix
|
499
|
+
)
|
500
|
+
animation.animate()
|
501
|
+
|
502
|
+
|
503
|
+
def animate_rainbow(
|
504
|
+
renderable: "RenderableType", duration: Optional[float] = None, speed: float = 0.5
|
505
|
+
) -> None:
|
506
|
+
"""Create and run a rainbow animation on any renderable.
|
507
|
+
|
508
|
+
Args:
|
509
|
+
renderable: The object to animate (text, panel, etc.)
|
510
|
+
duration: Duration of the animation in seconds (defaults to 2.0)
|
511
|
+
speed: Speed of the color cycling effect (defaults to 0.5)
|
512
|
+
|
513
|
+
Examples:
|
514
|
+
>>> animate_rainbow("Colorful Text!", duration=4.0, speed=1.0)
|
515
|
+
>>> animate_rainbow(Panel("Rainbow Panel"), speed=0.3)
|
516
|
+
"""
|
517
|
+
animation = CLIRainbowAnimation(renderable, duration=duration, speed=speed)
|
518
|
+
animation.animate()
|
519
|
+
|
520
|
+
|
521
|
+
def animate_typing(
|
522
|
+
text: str,
|
523
|
+
duration: Optional[float] = None,
|
524
|
+
typing_speed: float = 0.05,
|
525
|
+
cursor: str = "▌",
|
526
|
+
show_cursor: bool = True,
|
527
|
+
) -> None:
|
528
|
+
"""Create and run a typewriter animation.
|
529
|
+
|
530
|
+
Args:
|
531
|
+
text: The text to type out
|
532
|
+
duration: Duration of the animation in seconds (defaults to 2.0)
|
533
|
+
typing_speed: Speed between character reveals (defaults to 0.05)
|
534
|
+
cursor: Cursor character to show (defaults to "▌")
|
535
|
+
show_cursor: Whether to show the typing cursor (defaults to True)
|
536
|
+
|
537
|
+
Examples:
|
538
|
+
>>> animate_typing("Hello, World!", typing_speed=0.1)
|
539
|
+
>>> animate_typing("Fast typing", duration=1.0, cursor="|", show_cursor=False)
|
540
|
+
"""
|
541
|
+
animation = CLITypingAnimation(
|
542
|
+
text,
|
543
|
+
duration=duration,
|
544
|
+
typing_speed=typing_speed,
|
545
|
+
cursor=cursor,
|
546
|
+
show_cursor=show_cursor,
|
547
|
+
)
|
548
|
+
animation.animate()
|
@@ -0,0 +1,135 @@
|
|
1
|
+
"""hammad.cli.styles.settings"""
|
2
|
+
|
3
|
+
from typing import Any
|
4
|
+
from typing_extensions import TypedDict, NotRequired
|
5
|
+
|
6
|
+
from .types import (
|
7
|
+
CLIStyleBoxName,
|
8
|
+
CLIStyleJustifyMethod,
|
9
|
+
CLIStyleOverflowMethod,
|
10
|
+
CLIStyleVerticalOverflowMethod,
|
11
|
+
)
|
12
|
+
|
13
|
+
__all__ = (
|
14
|
+
"CLIStyleRenderableSettings",
|
15
|
+
"CLIStyleBackgroundSettings",
|
16
|
+
"CLIStyleLiveSettings",
|
17
|
+
)
|
18
|
+
|
19
|
+
|
20
|
+
class CLIStyleRenderableSettings(TypedDict, total=False):
|
21
|
+
"""Extended dictionary definition of settings that can be
|
22
|
+
applied to style various renderable content. These settings
|
23
|
+
extend the settings within `rich.text.Text` and
|
24
|
+
`rich.style.Style`.
|
25
|
+
|
26
|
+
When using various stylable modules in the `hammad` package,
|
27
|
+
you can either define the `style` parameter with a rich string
|
28
|
+
tag with a color / style name. or apply the `style_settings`
|
29
|
+
parameter with these settings."""
|
30
|
+
|
31
|
+
# rich.text
|
32
|
+
|
33
|
+
justify: NotRequired[CLIStyleJustifyMethod]
|
34
|
+
"""The justification of the renderable output or content."""
|
35
|
+
overflow: NotRequired[CLIStyleOverflowMethod | int]
|
36
|
+
"""The overflow method of the renderable output or content."""
|
37
|
+
no_wrap: NotRequired[bool]
|
38
|
+
"""Whether the renderable output or content should be wrapped."""
|
39
|
+
end: NotRequired[str]
|
40
|
+
"""The end character of the renderable output or content."""
|
41
|
+
tab_size: NotRequired[int]
|
42
|
+
"""The tab size of the renderable output or content."""
|
43
|
+
spans: NotRequired[list[Any]]
|
44
|
+
"""The spans of the renderable output or content."""
|
45
|
+
|
46
|
+
# rich.style
|
47
|
+
|
48
|
+
bold: NotRequired[bool]
|
49
|
+
"""Whether the renderable output or content should be bold."""
|
50
|
+
dim: NotRequired[bool]
|
51
|
+
"""Whether the renderable output or content should be dimmed."""
|
52
|
+
italic: NotRequired[bool]
|
53
|
+
"""Whether the renderable output or content should be italicized."""
|
54
|
+
underline: NotRequired[bool]
|
55
|
+
"""Whether the renderable output or content should be underlined."""
|
56
|
+
blink: NotRequired[bool]
|
57
|
+
"""Whether the renderable output or content should blink."""
|
58
|
+
blink2: NotRequired[bool]
|
59
|
+
"""Whether the renderable output or content should blink twice."""
|
60
|
+
reverse: NotRequired[bool]
|
61
|
+
"""Whether the renderable output or content should be reversed."""
|
62
|
+
conceal: NotRequired[bool]
|
63
|
+
"""Whether the renderable output or content should be concealed."""
|
64
|
+
strike: NotRequired[bool]
|
65
|
+
"""Whether the renderable output or content should be struck through."""
|
66
|
+
underline2: NotRequired[bool]
|
67
|
+
"""Whether the renderable output or content should be underlined twice."""
|
68
|
+
frame: NotRequired[bool]
|
69
|
+
"""Whether the renderable output or content should be framed."""
|
70
|
+
encircle: NotRequired[bool]
|
71
|
+
"""Whether the renderable output or content should be encircled."""
|
72
|
+
overline: NotRequired[bool]
|
73
|
+
"""Whether the renderable output or content should be overlined."""
|
74
|
+
link: NotRequired[str]
|
75
|
+
"""The link to be applied to the renderable output or content."""
|
76
|
+
|
77
|
+
|
78
|
+
class CLIStyleBackgroundSettings(TypedDict, total=False):
|
79
|
+
"""Extended dictionary definition of settings that can be
|
80
|
+
applied to style various background content. These settings
|
81
|
+
extend the settings within `rich.box.Box` and `rich.panel.Panel`.
|
82
|
+
|
83
|
+
When using various stylable modules in the `hammad` package,
|
84
|
+
you can either define the `bg` parameter with a rich string
|
85
|
+
tag with a color / style name. or apply the `bg_settings`
|
86
|
+
parameter with these settings."""
|
87
|
+
|
88
|
+
box: NotRequired[CLIStyleBoxName]
|
89
|
+
"""The box style to be applied to the background."""
|
90
|
+
title: NotRequired[str]
|
91
|
+
"""The title of the background."""
|
92
|
+
subtitle: NotRequired[str]
|
93
|
+
"""The subtitle of the background."""
|
94
|
+
title_align: NotRequired[CLIStyleJustifyMethod]
|
95
|
+
"""The alignment of the title."""
|
96
|
+
subtitle_align: NotRequired[CLIStyleJustifyMethod]
|
97
|
+
"""The alignment of the subtitle."""
|
98
|
+
safe_box: NotRequired[bool]
|
99
|
+
"""Whether the box should be safe."""
|
100
|
+
expand: NotRequired[bool]
|
101
|
+
"""Whether the box should be expanded."""
|
102
|
+
style: NotRequired[CLIStyleRenderableSettings]
|
103
|
+
"""The style of the background."""
|
104
|
+
border_style: NotRequired[CLIStyleRenderableSettings]
|
105
|
+
"""The style of the border."""
|
106
|
+
width: NotRequired[int]
|
107
|
+
"""The width of the background."""
|
108
|
+
height: NotRequired[int]
|
109
|
+
"""The height of the background."""
|
110
|
+
padding: NotRequired[int]
|
111
|
+
"""The padding of the background."""
|
112
|
+
highlight: NotRequired[bool]
|
113
|
+
"""Whether the background should be highlighted."""
|
114
|
+
|
115
|
+
|
116
|
+
class CLIStyleLiveSettings(TypedDict, total=False):
|
117
|
+
"""Dictionary definition of settings for content rendered
|
118
|
+
with `rich.live.Live`."""
|
119
|
+
|
120
|
+
screen: NotRequired[bool]
|
121
|
+
"""Whether the live renderable should be displayed in a screen."""
|
122
|
+
duration: NotRequired[float]
|
123
|
+
"""The duration of the live renderable."""
|
124
|
+
refresh_rate: NotRequired[int]
|
125
|
+
"""The refresh rate of the live renderable."""
|
126
|
+
auto_refresh: NotRequired[bool]
|
127
|
+
"""Whether the live renderable should be automatically refreshed."""
|
128
|
+
transient: NotRequired[bool]
|
129
|
+
"""Whether the live renderable should be transient."""
|
130
|
+
redirect_stdout: NotRequired[bool]
|
131
|
+
"""Whether the live renderable should redirect stdout."""
|
132
|
+
redirect_stderr: NotRequired[bool]
|
133
|
+
"""Whether the live renderable should redirect stderr."""
|
134
|
+
vertical_overflow: NotRequired[CLIStyleVerticalOverflowMethod]
|
135
|
+
"""The vertical overflow method of the live renderable."""
|