bear-utils 0.9.2__py3-none-any.whl → 0.9.3__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.
@@ -0,0 +1,399 @@
1
+ from collections.abc import Callable
2
+ import re
3
+ from typing import Any, Literal, Self
4
+
5
+
6
+ class Zapper[T]:
7
+ """A class to remove specified symbols from a source string."""
8
+
9
+ def __init__(
10
+ self,
11
+ str_input: str | tuple[str, ...],
12
+ src: str,
13
+ replace: str = "",
14
+ expected_unpack: int = 0,
15
+ sep: str = ".",
16
+ atomic: bool = False,
17
+ unpack_strict: bool = True,
18
+ regex: bool = False,
19
+ filter_start: bool = False, # Will filter out any separators at the start of the string
20
+ filter_end: bool = False, # Will filter out any separators at the end of the string
21
+ sorting: Literal["length", "order"] = "length",
22
+ func: Callable[[str], T] = str,
23
+ ) -> None:
24
+ """Initialize the Zapper with symbols, source string, and optional parameters."""
25
+ self.src: str = src
26
+ self.replace: str = replace
27
+ self.expected_unpack: int = expected_unpack
28
+ self.sep: str = sep
29
+ self.func: Callable[[str], T] = func
30
+ self.atomic: bool = atomic
31
+ self.unpack_strict: bool = unpack_strict
32
+ self.regex_enabled: bool = regex
33
+ self.filter_start: bool = filter_start
34
+ self.filter_end: bool = filter_end
35
+ self.sorting: Literal["length", "order"] = sorting
36
+ self.input: list[str] = self._process_input(str_input)
37
+ self.dst: str = ""
38
+ self.result: tuple[Any, ...] | None = None
39
+
40
+ @staticmethod
41
+ def _get_chars(str_input: str) -> list[str]:
42
+ """Get a list of characters from the input string."""
43
+ return [char for char in str_input if char if char != ""]
44
+
45
+ @staticmethod
46
+ def _get_strs(str_input: tuple[str, ...]) -> list[str]:
47
+ """Get a list of strings from the input tuple."""
48
+ return [item for item in str_input if item if item != ""]
49
+
50
+ @staticmethod
51
+ def _de_dupe(strings: list[str], atomic: bool, sorting: Literal["length", "order"]) -> list[str]:
52
+ """Remove duplicates from the input list based on the sorting method."""
53
+ if atomic and sorting == "length":
54
+ return sorted(set(strings), key=lambda x: len(x), reverse=True)
55
+ seen = set()
56
+ ordered_result = []
57
+ for item in strings:
58
+ if item not in seen:
59
+ seen.add(item)
60
+ ordered_result.append(item)
61
+ return ordered_result
62
+
63
+ def _process_input(self, str_input: str | tuple[str, ...]) -> list[str]:
64
+ """Process the input symbols based on whether they are multi-input or single."""
65
+ is_tuple = isinstance(str_input, tuple)
66
+ is_string = isinstance(str_input, str)
67
+ if str_input == "":
68
+ return []
69
+ strings_to_replace = []
70
+ if is_string and not self.atomic:
71
+ # If a single string is passed, treat each char as a string to replace
72
+ chars: list[str] = self._get_chars(str_input)
73
+ strings_to_replace.extend(chars)
74
+ elif is_string and self.atomic:
75
+ # If a single string is passed and self.atomic is True, treat it as a single string to replace
76
+ strings_to_replace.append(str_input)
77
+ elif is_tuple and not self.atomic:
78
+ # If a tuple is passed while atomic is false, treat each char as a string to replace
79
+ for item in str_input:
80
+ chars = self._get_chars(item)
81
+ strings_to_replace.extend(chars)
82
+ elif is_tuple and self.atomic:
83
+ # If a tuple is passed and atomic is True, treat each string as a thing to replace
84
+ strings: list[str] = self._get_strs(str_input)
85
+ strings_to_replace.extend(strings)
86
+ return self._de_dupe(strings_to_replace, self.atomic, self.sorting)
87
+
88
+ # region Zap Related
89
+
90
+ @staticmethod
91
+ def _re_sub(src: str, pattern: str, replacement: str) -> str:
92
+ """Perform a regex substitution on the source string."""
93
+ return re.sub(pattern, replacement, src)
94
+
95
+ @property
96
+ def value(self) -> str:
97
+ """Return the modified source string."""
98
+ return self.dst
99
+
100
+ def _zap(self) -> str:
101
+ """Remove specified symbols from the source string."""
102
+ temp_str = self.src
103
+ for to_replace in self.input:
104
+ if self.regex_enabled:
105
+ temp_str = self._re_sub(temp_str, to_replace, self.replace)
106
+ else:
107
+ temp_str = temp_str.replace(to_replace, self.replace)
108
+ if self.filter_start and temp_str.startswith(self.sep):
109
+ temp_str = temp_str[len(self.sep) :]
110
+ if self.filter_end and temp_str.endswith(self.sep):
111
+ temp_str = temp_str[: -len(self.sep)]
112
+ if self.unpack_strict:
113
+ temp_str = temp_str.replace(self.sep * 2, self.sep) # Remove double separators
114
+ self.dst = temp_str
115
+ return self.dst
116
+
117
+ def zap(self) -> Self:
118
+ """Remove specified symbols from the source string."""
119
+ self.dst = self._zap()
120
+ return self
121
+
122
+ # endregion
123
+ # region Zap Get Related
124
+
125
+ def _zap_get(self) -> tuple[str, ...]:
126
+ """Remove specified symbols and return a tuple of unpacked values."""
127
+ result: list[str] = self._zap().split(self.sep)[: self.expected_unpack]
128
+ if self.unpack_strict and len(result) != self.expected_unpack:
129
+ raise ValueError(f"Expected {self.expected_unpack} items, got {len(result)}: {result}")
130
+ self.result = tuple(result)
131
+ return self.result
132
+
133
+ def zap_get(self) -> Self:
134
+ """Remove specified symbols and return a tuple of unpacked values."""
135
+ self.result = self._zap_get()
136
+ if self.unpack_strict and len(self.result) != self.expected_unpack:
137
+ raise ValueError(f"Expected {self.expected_unpack} items, got {len(self.result)}: {self.result}")
138
+ return self
139
+
140
+ @property
141
+ def unpacked(self) -> tuple[Any, ...]:
142
+ """Return the unpacked values as a tuple."""
143
+ if isinstance(self.result, tuple):
144
+ return self.result
145
+ raise ValueError("Result is not unpacked yet. Call zap_get() first.")
146
+
147
+ # endregion
148
+ # region Zap As Related
149
+
150
+ def _zap_as(self) -> tuple[T, ...]:
151
+ """Convert the result in self.result to the specified type."""
152
+ temp_list: list[Any] = []
153
+ if self.result is None:
154
+ raise ValueError("No result to convert. Call zap_get() first.")
155
+ for item in self.result:
156
+ temp_list.append(self.func(item))
157
+ self.result = tuple(temp_list)
158
+ return self.result
159
+
160
+ def zap_as(self) -> Self:
161
+ """Convert the result in self.result to the specified type."""
162
+ if not self.result:
163
+ raise ValueError("No result to convert. Call zap_get() first.")
164
+ self._zap_as()
165
+ return self
166
+
167
+ # endregion
168
+
169
+
170
+ def zap_multi(
171
+ *sym: str,
172
+ src: str,
173
+ replace: str = "",
174
+ atomic: bool = True,
175
+ ) -> str:
176
+ """Remove specified symbols from the source string.
177
+
178
+ Args:
179
+ *sym: A variable number of strings containing symbols to remove from src (e.g., "?!" or "!?")
180
+ src (str): The source string from which to remove the symbols
181
+ replace (str, optional): The string to replace the removed symbols with (default is an empty string).
182
+
183
+ Returns:
184
+ str: The modified source string with specified symbols removed.
185
+
186
+ Example:
187
+ >>> zap_multi("!?*", "Hello!? World! *", "")
188
+ 'Hello World '
189
+ >>> zap_multi("!?*", "Hello!? World! *", "-")
190
+ 'Hello- World- -'
191
+ """
192
+ zapper = Zapper(sym, src, replace, atomic=atomic)
193
+ return zapper.zap().value
194
+
195
+
196
+ def zap(sym: str, src: str, replace: str = "") -> str:
197
+ """Remove specified symbols from the source string.
198
+
199
+ Args:
200
+ sym (str): A string containing symbols to remove from src (e.g., "?!" or "!?")
201
+ src (str): The source string from which to remove the symbols
202
+ replace (str, optional): The string to replace the removed symbols with (default is an empty string).
203
+
204
+ Returns:
205
+ str: The modified source string with specified symbols removed.
206
+
207
+ Example:
208
+ >>> zap("!?*", "Hello!? World! *", "")
209
+ 'Hello World '
210
+ >>> zap("!?*", "Hello!? World! *", "-")
211
+ 'Hello- World- -'
212
+ """
213
+ zapper = Zapper(sym, src, replace, unpack_strict=False)
214
+ return zapper.zap().value
215
+
216
+
217
+ def zap_get_multi(
218
+ *sym: str,
219
+ src: str,
220
+ unpack_val: int,
221
+ replace: str = "",
222
+ sep: str = ".",
223
+ ) -> tuple[str, ...]:
224
+ """Remove specified symbols from the source string and return a tuple of unpacked values.
225
+
226
+ Args:
227
+ *sym: A variable number of strings containing symbols to remove from src (e.g., "?!" or "!?")
228
+ src (str): The source string from which to remove the symbols
229
+ unpack_val (int): The expected number of items to unpack from the result
230
+ replace (str, optional): The string to replace the removed symbols with (default is an empty string).
231
+ sep (str, optional): The separator used to split the modified source string (default is ".").
232
+
233
+ Returns:
234
+ tuple[str, ...]: A tuple of unpacked values from the modified source string.
235
+
236
+ Raises:
237
+ ValueError: If the number of items in the result does not match unpack_val.
238
+ """
239
+ zapper = Zapper(sym, src, replace, expected_unpack=unpack_val, sep=sep, unpack_strict=True)
240
+ try:
241
+ return zapper.zap_get().unpacked
242
+ except Exception as e:
243
+ raise ValueError(f"Error unpacking values: {e}") from e
244
+
245
+
246
+ def zap_get(sym: str, src: str, unpack_val: int, replace: str = "", sep: str = ".") -> tuple[str, ...]:
247
+ """Remove specified symbols from the source string and return a tuple of unpacked values.
248
+
249
+ Args:
250
+ sym (str): A string containing symbols to remove from src (e.g., "?!" or "!?")
251
+ src (str): The source string from which to remove the symbols
252
+ unpack_val (int): The expected number of items to unpack from the result
253
+ replace (str, optional): The string to replace the removed symbols with (default is an empty string).
254
+ sep (str, optional): The separator used to split the modified source string (default is ".").
255
+
256
+ Returns:
257
+ tuple[str, ...]: A tuple of unpacked values from the modified source string.
258
+
259
+ Raises:
260
+ ValueError: If the number of items in the result does not match unpack_val.
261
+ """
262
+ zapper = Zapper(sym, src, replace, expected_unpack=unpack_val, sep=sep, unpack_strict=True)
263
+ try:
264
+ return zapper.zap_get().unpacked
265
+ except Exception as e:
266
+ raise ValueError(f"Error unpacking values: {e}") from e
267
+
268
+
269
+ def zap_take(sym: str, src: str, unpack_val: int, replace: str = "", sep: str = ".") -> tuple[str, ...]:
270
+ """Remove specified symbols from the source string and return a tuple of unpacked values.
271
+
272
+ This function is similar to zap_get but does not raise an error if the number of items does not match unpack_val.
273
+ Instead, it returns as many items as possible.
274
+
275
+ Args:
276
+ sym (str): A string containing symbols to remove from src (e.g., "?!" or "!?")
277
+ src (str): The source string from which to remove the symbols
278
+ unpack_val (int): The expected number of items to unpack from the result
279
+ replace (str, optional): The string to replace the removed symbols with (default is an empty string).
280
+ sep (str, optional): The separator used to split the modified source string (default is ".").
281
+
282
+ Returns:
283
+ tuple[str, ...]: A tuple of unpacked values from the modified source string.
284
+ """
285
+ zapper = Zapper(sym, src, replace, expected_unpack=unpack_val, sep=sep, unpack_strict=False)
286
+ return zapper.zap_get().unpacked
287
+
288
+
289
+ def zap_as_multi[T](
290
+ *sym,
291
+ src: str,
292
+ unpack_val: int,
293
+ replace: str = "",
294
+ sep: str | None = None,
295
+ func: Callable[[str], T] = str,
296
+ strict: bool = True,
297
+ regex: bool = False,
298
+ atomic: bool = True,
299
+ filter_start: bool = False, # Will filter out any separators at the start of the string
300
+ filter_end: bool = False, # Will filter out any separators at the end of the string
301
+ sorting: Literal["length", "order"] = "length", # Will sort the input symbols by length in descending order
302
+ ) -> tuple[T, ...]:
303
+ """Remove specified symbols from the source string, unpack the result, and convert it to a specified type.
304
+
305
+ Args:
306
+ *sym: A variable number of strings containing symbols to remove from src (e.g., "?!" or "!?")
307
+ src (str): The source string from which to remove the symbols
308
+ unpack_val (int): The expected number of items to unpack from the result
309
+ replace (str, optional): The string to replace the removed symbols with (default is an empty string).
310
+ sep (str, optional): The separator used to split the modified source string (default is ".").
311
+ func (type, optional): The type of the result to cast/convert to (default is str).
312
+
313
+ Returns:
314
+ Zapper: An instance of the Zapper class configured with the provided parameters.
315
+ """
316
+ if sep is None:
317
+ sep = replace
318
+
319
+ zapper: Zapper[T] = Zapper(
320
+ str_input=sym,
321
+ src=src,
322
+ replace=replace,
323
+ expected_unpack=unpack_val,
324
+ sep=sep,
325
+ func=func,
326
+ unpack_strict=strict,
327
+ regex=regex,
328
+ atomic=atomic,
329
+ filter_start=filter_start,
330
+ filter_end=filter_end,
331
+ sorting=sorting,
332
+ )
333
+ try:
334
+ return zapper.zap_get().zap_as().unpacked
335
+ except Exception as e:
336
+ raise ValueError(f"Error converting values: {e}") from e
337
+
338
+
339
+ def zap_as[T](
340
+ sym: str,
341
+ src: str,
342
+ unpack_val: int,
343
+ replace: str = "",
344
+ sep: str | None = None,
345
+ func: Callable[[str], T] = str,
346
+ strict: bool = True,
347
+ regex: bool = False,
348
+ filter_start: bool = False, # Will filter out any separators at the start of the string
349
+ filter_end: bool = False, # Will filter out any separators at the end of the string
350
+ atomic: bool = False, # If True, treat the input symbols as a single string to replace
351
+ sorting: Literal["length", "order"] = "length", # length ordering or by the order of input
352
+ ) -> tuple[T, ...]:
353
+ """Remove specified symbols from the source string, unpack the result, and convert it to a specified type.
354
+
355
+ Args:
356
+ sym (str): A string containing symbols to remove from src (e.g., "?!" or "!?")
357
+ src (str): The source string from which to remove the symbols
358
+ unpack_val (int): The expected number of items to unpack from the result
359
+ replace (str, optional): The string to replace the removed symbols with (default is an empty string).
360
+ sep (str, optional): The separator used to split the modified source string (default is ".").
361
+ func (type, optional): The type of the result to cast/convert to (default is str).
362
+
363
+ Returns:
364
+ Zapper: An instance of the Zapper class configured with the provided parameters.
365
+ """
366
+ if sep is None:
367
+ sep = replace
368
+
369
+ zapper: Zapper[T] = Zapper(
370
+ str_input=sym,
371
+ src=src,
372
+ replace=replace,
373
+ expected_unpack=unpack_val,
374
+ sep=sep,
375
+ func=func,
376
+ unpack_strict=strict,
377
+ regex=regex,
378
+ filter_start=filter_start,
379
+ filter_end=filter_end,
380
+ atomic=atomic,
381
+ sorting=sorting,
382
+ )
383
+ try:
384
+ return zapper.zap_get().zap_as().unpacked
385
+ except Exception as e:
386
+ raise ValueError(f"Error converting values: {e}") from e
387
+
388
+
389
+ if __name__ == "__main__":
390
+ # # Example usage
391
+ # result = zap_as_multi("()", "(", ")", src="(a)(b)(c)", unpack_val=3, replace="|", atomic=False)
392
+ # print(result)
393
+ # assert result == ("a", "b", "c")
394
+
395
+ version_string = "v1.2.3-beta+build"
396
+ result = zap_as_multi(
397
+ "v", "-", "+", src=version_string, unpack_val=3, replace=".", func=int, strict=True, filter_start=True
398
+ )
399
+ print(result)
@@ -0,0 +1,188 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from dataclasses import dataclass
5
+ from io import StringIO
6
+ import re
7
+ from typing import Any, Self
8
+
9
+ from rich.align import Align
10
+ from rich.panel import Panel
11
+ from rich.text import Text
12
+
13
+ from bear_utils.graphics.font._theme import CyberTheme
14
+ from bear_utils.graphics.font.block_font import Style
15
+ from bear_utils.logger_manager import LogConsole as Console
16
+
17
+
18
+ @dataclass
19
+ class HeaderConfig:
20
+ """Configuration for header styling."""
21
+
22
+ top_sep: str = "#"
23
+ left_sep: str = ">"
24
+ right_sep: str = "<"
25
+ bottom_sep: str = "#"
26
+ length: int = 60
27
+ title_style: str = "bold red" # s1
28
+ border_style: str = "bold blue" # s2 - top/bottom lines
29
+ separator_style: str = "bold green" # s3 - left/right separators
30
+ overall_style: str = "bold yellow" # s4
31
+ center_align: bool = True
32
+ return_txt: bool = False
33
+ use_panel: bool = False # New option!
34
+
35
+
36
+ class TextHelper:
37
+ def _create_separator_line(self, char: str, length: int, style: str) -> Text:
38
+ """Create a styled separator line."""
39
+ return Text(char * length, style=style)
40
+
41
+ def _create_title_line_manual(self, title: str, cfg: HeaderConfig) -> Text:
42
+ """Create title line with manual separator padding."""
43
+ title_with_spaces = f" {title} "
44
+ title_length = len(title_with_spaces)
45
+ remaining_space = cfg.length - title_length
46
+ left_padding = remaining_space // 2
47
+ right_padding = remaining_space - left_padding
48
+ title_line = Text()
49
+ title_line.append(cfg.left_sep * left_padding, style=cfg.separator_style)
50
+ title_line.append(f" {title} ", style=cfg.title_style)
51
+ title_line.append(cfg.right_sep * right_padding, style=cfg.separator_style)
52
+ return title_line
53
+
54
+ def _create_title_line_rich(self, title: str, cfg: HeaderConfig) -> Text:
55
+ """Create title line using Rich's alignment."""
56
+ styled_title = Text(f" {title} ", style=cfg.title_style)
57
+ title_line = Text()
58
+ title_line.append(cfg.left_sep, style=cfg.separator_style)
59
+ title_line.append(styled_title)
60
+ title_line.append(cfg.right_sep, style=cfg.separator_style)
61
+ return Text.from_markup(str(Align.center(title_line, width=cfg.length)))
62
+
63
+ def _create_panel_header(self, title: str, cfg: HeaderConfig) -> Panel:
64
+ """Create header using Rich Panel."""
65
+ return Panel(
66
+ f"[{cfg.title_style}]{title}[/{cfg.title_style}]",
67
+ width=cfg.length,
68
+ border_style=cfg.border_style,
69
+ expand=False,
70
+ )
71
+
72
+ def _create_manual_header(self, title: str, cfg: HeaderConfig) -> list[Text]:
73
+ """Create header using manual separator lines."""
74
+ top_line = self._create_separator_line(cfg.top_sep, cfg.length, cfg.border_style)
75
+ bottom_line = self._create_separator_line(cfg.bottom_sep, cfg.length, cfg.border_style)
76
+ title_line = self._create_title_line_manual(title, cfg)
77
+
78
+ return [top_line, title_line, bottom_line]
79
+
80
+ def print_header(self, title: str, config: HeaderConfig | None = None, **kwargs) -> str:
81
+ """Generate a header string with customizable separators and styling.
82
+
83
+ Args:
84
+ title: The title text to display
85
+ config: HeaderConfig object, or None to use defaults
86
+ **kwargs: Override any config values (top_sep, left_sep, etc.)
87
+ """
88
+ local_console = Console()
89
+ cfg = config or HeaderConfig()
90
+ for key, value in kwargs.items():
91
+ if hasattr(cfg, key):
92
+ setattr(cfg, key, value)
93
+
94
+ if cfg.use_panel:
95
+ panel: Panel = self._create_panel_header(title, cfg)
96
+ output: Align | Panel = Align.center(panel) if cfg.center_align else panel
97
+
98
+ if not cfg.return_txt:
99
+ local_console.print(output, style=cfg.overall_style)
100
+
101
+ temp_console = Console(file=StringIO(), width=cfg.length)
102
+ temp_console.print(output, style=cfg.overall_style)
103
+ return temp_console.file.getvalue()
104
+
105
+ header_lines = self._create_manual_header(title, cfg)
106
+
107
+ if cfg.center_align:
108
+ header_lines = [Align.center(line) for line in header_lines]
109
+
110
+ if not cfg.return_txt:
111
+ local_console.print() # Leading newline
112
+ for line in header_lines:
113
+ local_console.print(line, style=cfg.overall_style)
114
+ local_console.print() # Trailing newline
115
+ output_lines: list[str] = [str(line) for line in header_lines]
116
+ return "\n" + "\n".join(output_lines) + "\n"
117
+
118
+ def quick_header(self, title: str, style: str = "cyberpunk") -> str:
119
+ """Quick header with predefined styles."""
120
+ styles = {
121
+ "cyberpunk": HeaderConfig(
122
+ top_sep=str(Style.SOLID),
123
+ left_sep=str(Style.RIGHT_ARROWS),
124
+ right_sep=str(Style.LEFT_ARROWS),
125
+ bottom_sep=str(Style.SOLID),
126
+ title_style="bold bright_magenta",
127
+ border_style="bright_cyan",
128
+ separator_style="bright_green",
129
+ overall_style="",
130
+ use_panel=False,
131
+ ),
132
+ "panel": HeaderConfig(title_style="bold bright_magenta", border_style="bright_cyan", use_panel=True),
133
+ "classic": HeaderConfig(), # Uses defaults
134
+ "minimal": HeaderConfig(top_sep="─", left_sep="", right_sep="", bottom_sep="─", separator_style="dim"),
135
+ }
136
+
137
+ config = styles.get(style, HeaderConfig())
138
+ return self.print_header(title, config)
139
+
140
+
141
+ def ascii_header(title: str, print_out: bool = True, **kwargs) -> str:
142
+ """Generate a header string for visual tests.
143
+
144
+ Args:
145
+ title: The title to display
146
+ print_out: Whether to print or return the header
147
+ **kwargs: Any HeaderConfig parameters (top_sep, length, etc.)
148
+ """
149
+ config = HeaderConfig(return_txt=not print_out, **kwargs)
150
+ text_helper = TextHelper()
151
+ result = text_helper.print_header(title, config)
152
+ return "" if print_out else result
153
+
154
+
155
+ if __name__ == "__main__":
156
+ top = Style.SOLID.text
157
+ bottom = Style.SOLID.text
158
+ left = Style.RIGHT_ARROWS.text
159
+ right = Style.LEFT_ARROWS.text
160
+ ascii_header(
161
+ "Welcome to Bear Utils",
162
+ top_sep=top,
163
+ bottom_sep=bottom,
164
+ left_sep=left,
165
+ right_sep=right,
166
+ title_style=CyberTheme.primary,
167
+ separator_style=CyberTheme.system,
168
+ border_style=CyberTheme.neon_cyan,
169
+ print_out=True,
170
+ )
171
+
172
+ from pyfiglet import figlet_format
173
+
174
+ ANSI_SHADOW = "ansi_shadow"
175
+ BLOODY = "bloody"
176
+ BANNER_3_D = "banner3-D"
177
+ POISON = "poison"
178
+ ALPHA = "alpha"
179
+ DOOM = "doom"
180
+ DOT_MATRIX = "dotmatrix"
181
+ JAZMINE = "jazmine"
182
+ RAMMSTEIN = "rammstein"
183
+ REVERSE = "reverse"
184
+ DIAGONAL_3D = "3d_diagonal"
185
+ GHOST = "ghost"
186
+
187
+ text = figlet_format("BANKER", font="computer")
188
+ print(text)
@@ -3,6 +3,7 @@
3
3
  from rich.align import Align
4
4
  from rich.console import Console
5
5
 
6
+ from bear_utils.constants import RichStrEnum, StrValue
6
7
  from bear_utils.graphics.font._raw_block_letters import (
7
8
  ASTERISK,
8
9
  AT,
@@ -112,6 +113,37 @@ BLOCK_LETTERS: dict[str, list[str]] = {
112
113
  "$": DOLLAR,
113
114
  }
114
115
 
116
+
117
+ class Style(RichStrEnum):
118
+ """Enumeration for block font styles."""
119
+
120
+ SOLID = StrValue("solid", "█")
121
+ HOLLOW = StrValue("hollow", "░")
122
+ PIPES = StrValue("pipes", "|")
123
+ OUTLINE = StrValue("outline", "■")
124
+ DASHED = StrValue("dashed", "─")
125
+ DOTTED = StrValue("dotted", "·")
126
+ ZIGZAG = StrValue("zigzag", "╱") # noqa: RUF001
127
+ CROSSED = StrValue("crossed", "╳") # noqa: RUF001
128
+ FANCY = StrValue("fancy", "◆")
129
+ RIGHT_ARROWS = StrValue("right_arrows", "▶")
130
+ LEFT_ARROWS = StrValue("left_arrows", "◀")
131
+ STARS = StrValue("stars", "★")
132
+
133
+
134
+ OG_CHAR = Style.SOLID
135
+
136
+
137
+ def apply_block_style(block_rows: list[str], style: str = "solid") -> list[str]:
138
+ """Replace block characters with different symbols."""
139
+ try:
140
+ new_char: Style = Style.get(value=style, default=OG_CHAR)
141
+ return [row.replace(OG_CHAR.text, new_char.text) for row in block_rows]
142
+ except (KeyError, AttributeError) as e:
143
+ available = ", ".join(Style._value2member_map_.keys())
144
+ raise ValueError(f"Invalid style: {style}. Available styles: {available}") from e
145
+
146
+
115
147
  console = Console()
116
148
 
117
149
 
@@ -135,10 +167,19 @@ def _word_to_block(word: str) -> list[str]:
135
167
  return rows
136
168
 
137
169
 
138
- def word_to_block(word: str) -> str:
139
- """Convert a word to its block font representation as a single string."""
170
+ def word_to_block(word: str, font: str = "solid") -> str:
171
+ """Convert a word to its block font representation as a single string.
172
+
173
+ Args:
174
+ word (str): The word to convert.
175
+ font (str): The style of the block font. Defaults to "solid".
176
+
177
+ Returns:
178
+ str: The block font representation of the word.
179
+ """
140
180
  block_rows = _word_to_block(word)
141
- return "\n".join(block_rows)
181
+ styled_rows = apply_block_style(block_rows, font)
182
+ return "\n".join(styled_rows)
142
183
 
143
184
 
144
185
  def print_block_font(text: str, color: str = Theme.neon_green) -> None:
@@ -149,11 +190,22 @@ def print_block_font(text: str, color: str = Theme.neon_green) -> None:
149
190
  console.print(Align.center(f"[{color}]{row}[/{color}]"))
150
191
 
151
192
 
193
+ def show_off_styles(word: str, style: str = Theme.primary) -> None:
194
+ """Display all block styles by using an example word"""
195
+ console.print("Available block styles:")
196
+
197
+ for symbol in Style:
198
+ styled_word = word_to_block(word, font=symbol.name.lower())
199
+
200
+ console.print()
201
+ console.print(Align.center(f"[{Theme.system}]{symbol.name.title()} Style:[/]"))
202
+ console.print(Align.center(f"[{style}]{styled_word}[/]"))
203
+ console.print()
204
+
205
+
152
206
  __all__ = ["BLOCK_LETTERS", "char_to_block", "word_to_block"]
153
207
  # fmt: on
154
208
 
155
209
  if __name__ == "__main__":
156
- WORD = "BANKER"
157
- BANKER = word_to_block(WORD)
158
-
159
- console.print(Align.center(f"[{Theme.error}]{BANKER}[/{Theme.error}]"))
210
+ WORD = "CLAUDE"
211
+ show_off_styles(WORD)