bear-utils 0.0.1__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.
- bear_utils/__init__.py +51 -0
- bear_utils/__main__.py +14 -0
- bear_utils/_internal/__init__.py +0 -0
- bear_utils/_internal/_version.py +1 -0
- bear_utils/_internal/cli.py +119 -0
- bear_utils/_internal/debug.py +174 -0
- bear_utils/ai/__init__.py +30 -0
- bear_utils/ai/ai_helpers/__init__.py +136 -0
- bear_utils/ai/ai_helpers/_common.py +19 -0
- bear_utils/ai/ai_helpers/_config.py +24 -0
- bear_utils/ai/ai_helpers/_parsers.py +194 -0
- bear_utils/ai/ai_helpers/_types.py +15 -0
- bear_utils/cache/__init__.py +131 -0
- bear_utils/cli/__init__.py +22 -0
- bear_utils/cli/_args.py +12 -0
- bear_utils/cli/_get_version.py +207 -0
- bear_utils/cli/commands.py +105 -0
- bear_utils/cli/prompt_helpers.py +186 -0
- bear_utils/cli/shell/__init__.py +1 -0
- bear_utils/cli/shell/_base_command.py +81 -0
- bear_utils/cli/shell/_base_shell.py +430 -0
- bear_utils/cli/shell/_common.py +19 -0
- bear_utils/cli/typer_bridge.py +90 -0
- bear_utils/config/__init__.py +13 -0
- bear_utils/config/config_manager.py +229 -0
- bear_utils/config/dir_manager.py +69 -0
- bear_utils/config/settings_manager.py +179 -0
- bear_utils/constants/__init__.py +90 -0
- bear_utils/constants/_exceptions.py +8 -0
- bear_utils/constants/_exit_code.py +60 -0
- bear_utils/constants/_http_status_code.py +37 -0
- bear_utils/constants/_lazy_typing.py +15 -0
- bear_utils/constants/_meta.py +196 -0
- bear_utils/constants/date_related.py +25 -0
- bear_utils/constants/time_related.py +24 -0
- bear_utils/database/__init__.py +8 -0
- bear_utils/database/_db_manager.py +98 -0
- bear_utils/events/__init__.py +18 -0
- bear_utils/events/events_class.py +52 -0
- bear_utils/events/events_module.py +74 -0
- bear_utils/extras/__init__.py +28 -0
- bear_utils/extras/_async_helpers.py +67 -0
- bear_utils/extras/_tools.py +185 -0
- bear_utils/extras/_zapper.py +399 -0
- bear_utils/extras/platform_utils.py +57 -0
- bear_utils/extras/responses/__init__.py +5 -0
- bear_utils/extras/responses/function_response.py +451 -0
- bear_utils/extras/wrappers/__init__.py +1 -0
- bear_utils/extras/wrappers/add_methods.py +100 -0
- bear_utils/extras/wrappers/string_io.py +46 -0
- bear_utils/files/__init__.py +6 -0
- bear_utils/files/file_handlers/__init__.py +5 -0
- bear_utils/files/file_handlers/_base_file_handler.py +107 -0
- bear_utils/files/file_handlers/file_handler_factory.py +280 -0
- bear_utils/files/file_handlers/json_file_handler.py +71 -0
- bear_utils/files/file_handlers/log_file_handler.py +40 -0
- bear_utils/files/file_handlers/toml_file_handler.py +76 -0
- bear_utils/files/file_handlers/txt_file_handler.py +76 -0
- bear_utils/files/file_handlers/yaml_file_handler.py +64 -0
- bear_utils/files/ignore_parser.py +293 -0
- bear_utils/graphics/__init__.py +6 -0
- bear_utils/graphics/bear_gradient.py +145 -0
- bear_utils/graphics/font/__init__.py +13 -0
- bear_utils/graphics/font/_raw_block_letters.py +463 -0
- bear_utils/graphics/font/_theme.py +31 -0
- bear_utils/graphics/font/_utils.py +220 -0
- bear_utils/graphics/font/block_font.py +192 -0
- bear_utils/graphics/font/glitch_font.py +63 -0
- bear_utils/graphics/image_helpers.py +45 -0
- bear_utils/gui/__init__.py +8 -0
- bear_utils/gui/gui_tools/__init__.py +10 -0
- bear_utils/gui/gui_tools/_settings.py +36 -0
- bear_utils/gui/gui_tools/_types.py +12 -0
- bear_utils/gui/gui_tools/qt_app.py +150 -0
- bear_utils/gui/gui_tools/qt_color_picker.py +130 -0
- bear_utils/gui/gui_tools/qt_file_handler.py +130 -0
- bear_utils/gui/gui_tools/qt_input_dialog.py +303 -0
- bear_utils/logger_manager/__init__.py +109 -0
- bear_utils/logger_manager/_common.py +63 -0
- bear_utils/logger_manager/_console_junk.py +135 -0
- bear_utils/logger_manager/_log_level.py +50 -0
- bear_utils/logger_manager/_styles.py +95 -0
- bear_utils/logger_manager/logger_protocol.py +42 -0
- bear_utils/logger_manager/loggers/__init__.py +1 -0
- bear_utils/logger_manager/loggers/_console.py +223 -0
- bear_utils/logger_manager/loggers/_level_sin.py +61 -0
- bear_utils/logger_manager/loggers/_logger.py +19 -0
- bear_utils/logger_manager/loggers/base_logger.py +244 -0
- bear_utils/logger_manager/loggers/base_logger.pyi +51 -0
- bear_utils/logger_manager/loggers/basic_logger/__init__.py +5 -0
- bear_utils/logger_manager/loggers/basic_logger/logger.py +80 -0
- bear_utils/logger_manager/loggers/basic_logger/logger.pyi +19 -0
- bear_utils/logger_manager/loggers/buffer_logger.py +57 -0
- bear_utils/logger_manager/loggers/console_logger.py +278 -0
- bear_utils/logger_manager/loggers/console_logger.pyi +50 -0
- bear_utils/logger_manager/loggers/fastapi_logger.py +333 -0
- bear_utils/logger_manager/loggers/file_logger.py +151 -0
- bear_utils/logger_manager/loggers/simple_logger.py +98 -0
- bear_utils/logger_manager/loggers/sub_logger.py +105 -0
- bear_utils/logger_manager/loggers/sub_logger.pyi +23 -0
- bear_utils/monitoring/__init__.py +13 -0
- bear_utils/monitoring/_common.py +28 -0
- bear_utils/monitoring/host_monitor.py +346 -0
- bear_utils/time/__init__.py +59 -0
- bear_utils-0.0.1.dist-info/METADATA +305 -0
- bear_utils-0.0.1.dist-info/RECORD +107 -0
- bear_utils-0.0.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
import asyncio
|
2
|
+
from asyncio import AbstractEventLoop, Task
|
3
|
+
from collections.abc import Callable
|
4
|
+
from contextlib import suppress
|
5
|
+
import inspect
|
6
|
+
|
7
|
+
from pydantic import BaseModel, Field
|
8
|
+
|
9
|
+
|
10
|
+
class AsyncResponseModel(BaseModel):
|
11
|
+
"""A model to handle asynchronous operations with a function and its arguments."""
|
12
|
+
|
13
|
+
loop: AbstractEventLoop | None = Field(default=None, description="The event loop to run the function in.")
|
14
|
+
task: Task | None = Field(default=None, description="The task created for the asynchronous function.")
|
15
|
+
before_loop: bool = Field(default=False, description="If the function was called from a running loop.")
|
16
|
+
|
17
|
+
model_config = {"arbitrary_types_allowed": True}
|
18
|
+
|
19
|
+
def conditional_run(self) -> None:
|
20
|
+
"""Run the event loop until the task is complete if not in a running loop."""
|
21
|
+
if self.loop and self.task and not self.before_loop:
|
22
|
+
self.loop.run_until_complete(self.task)
|
23
|
+
|
24
|
+
|
25
|
+
def is_async_function(func: Callable) -> bool:
|
26
|
+
"""Check if a function is asynchronous.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
func (Callable): The function/method to check.
|
30
|
+
|
31
|
+
Returns:
|
32
|
+
bool: True if the function is asynchronous, False otherwise.
|
33
|
+
"""
|
34
|
+
return inspect.iscoroutinefunction(func) or inspect.isasyncgenfunction(func) or inspect.isasyncgen(func)
|
35
|
+
|
36
|
+
|
37
|
+
def in_async_loop() -> bool:
|
38
|
+
"""Check if the current context is already in an async loop.
|
39
|
+
|
40
|
+
Returns:
|
41
|
+
bool: True if an async loop is running, False otherwise.
|
42
|
+
"""
|
43
|
+
loop: AbstractEventLoop | None = None
|
44
|
+
with suppress(RuntimeError):
|
45
|
+
loop = asyncio.get_running_loop()
|
46
|
+
return loop.is_running() if loop else False
|
47
|
+
|
48
|
+
|
49
|
+
def gimmie_async_loop() -> AbstractEventLoop:
|
50
|
+
"""Get the current event loop, creating one if it doesn't exist."""
|
51
|
+
if in_async_loop():
|
52
|
+
return asyncio.get_event_loop()
|
53
|
+
loop: AbstractEventLoop = asyncio.new_event_loop()
|
54
|
+
asyncio.set_event_loop(loop)
|
55
|
+
return loop
|
56
|
+
|
57
|
+
|
58
|
+
def create_async_task(
|
59
|
+
func: Callable,
|
60
|
+
*args,
|
61
|
+
**kwargs,
|
62
|
+
) -> AsyncResponseModel:
|
63
|
+
"""Create an asyncio task for a given function."""
|
64
|
+
before_loop: bool = in_async_loop()
|
65
|
+
loop: AbstractEventLoop = gimmie_async_loop()
|
66
|
+
task = loop.create_task(func(*args, **kwargs))
|
67
|
+
return AsyncResponseModel(loop=loop, task=task, before_loop=before_loop)
|
@@ -0,0 +1,185 @@
|
|
1
|
+
import asyncio
|
2
|
+
from asyncio.subprocess import PIPE
|
3
|
+
from collections import deque
|
4
|
+
import shutil
|
5
|
+
from typing import TYPE_CHECKING
|
6
|
+
|
7
|
+
from bear_utils.cli.shell._base_command import BaseShellCommand as ShellCommand
|
8
|
+
from bear_utils.cli.shell._base_shell import AsyncShellSession
|
9
|
+
from bear_utils.extras.platform_utils import OS, get_platform
|
10
|
+
from bear_utils.graphics.font._utils import ascii_header
|
11
|
+
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
from subprocess import CompletedProcess
|
14
|
+
|
15
|
+
|
16
|
+
class ClipboardManager:
|
17
|
+
"""A class to manage clipboard operations such as copying, pasting, and clearing.
|
18
|
+
|
19
|
+
This class provides methods to interact with the system clipboard.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(self, maxlen: int = 10) -> None:
|
23
|
+
"""Initialize the ClipboardManager with a maximum history length."""
|
24
|
+
self.clipboard_history: deque[str] = deque(maxlen=maxlen)
|
25
|
+
self.shell = AsyncShellSession(env={"LANG": "en_US.UTF-8"}, verbose=False)
|
26
|
+
self._copy: ShellCommand[str]
|
27
|
+
self._paste: ShellCommand[str]
|
28
|
+
|
29
|
+
platform: OS = get_platform()
|
30
|
+
match platform:
|
31
|
+
case OS.DARWIN:
|
32
|
+
self._copy = ShellCommand.adhoc(name="pbcopy")
|
33
|
+
self._paste = ShellCommand.adhoc(name="pbpaste")
|
34
|
+
case OS.LINUX:
|
35
|
+
if shutil.which(cmd="wl-copy") and shutil.which(cmd="wl-paste"):
|
36
|
+
self._copy = ShellCommand.adhoc(name="wl-copy")
|
37
|
+
self._paste = ShellCommand.adhoc(name="wl-paste")
|
38
|
+
elif shutil.which(cmd="xclip"):
|
39
|
+
self._copy = ShellCommand.adhoc(name="xclip").sub("-selection", "clipboard")
|
40
|
+
self._paste = ShellCommand.adhoc(name="xclip").sub("-selection", "clipboard").value("-o")
|
41
|
+
else:
|
42
|
+
raise RuntimeError("No clipboard command found on Linux")
|
43
|
+
case OS.WINDOWS:
|
44
|
+
self._copy = ShellCommand.adhoc(name="clip")
|
45
|
+
self._paste = ShellCommand.adhoc(name="powershell").sub("Get-Clipboard")
|
46
|
+
case _:
|
47
|
+
raise RuntimeError(f"Unsupported platform: {platform}")
|
48
|
+
|
49
|
+
def _copy_cmd(self) -> ShellCommand[str]:
|
50
|
+
"""Get the copy command based on the platform."""
|
51
|
+
return self._copy
|
52
|
+
|
53
|
+
def _paste_cmd(self) -> ShellCommand[str]:
|
54
|
+
"""Get the paste command based on the platform."""
|
55
|
+
return self._paste
|
56
|
+
|
57
|
+
def get_history(self) -> deque:
|
58
|
+
"""Get the clipboard history.
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
deque: The history of clipboard entries.
|
62
|
+
"""
|
63
|
+
return self.clipboard_history
|
64
|
+
|
65
|
+
async def copy(self, output: str) -> int:
|
66
|
+
"""A function that copies the output to the clipboard.
|
67
|
+
|
68
|
+
Args:
|
69
|
+
output (str): The output to copy to the clipboard.
|
70
|
+
|
71
|
+
Returns:
|
72
|
+
int: The return code of the command.
|
73
|
+
"""
|
74
|
+
await self.shell.run(cmd=self._copy, stdin=PIPE)
|
75
|
+
result: CompletedProcess[str] = await self.shell.communicate(stdin=output)
|
76
|
+
if result.returncode == 0:
|
77
|
+
self.clipboard_history.append(output) # Only append to history if the copy was successful
|
78
|
+
return result.returncode
|
79
|
+
|
80
|
+
async def paste(self) -> str:
|
81
|
+
"""Paste the output from the clipboard.
|
82
|
+
|
83
|
+
Returns:
|
84
|
+
str: The content of the clipboard.
|
85
|
+
|
86
|
+
Raises:
|
87
|
+
RuntimeError: If the paste command fails.
|
88
|
+
"""
|
89
|
+
try:
|
90
|
+
await self.shell.run(cmd=self._paste)
|
91
|
+
result: CompletedProcess[str] = await self.shell.communicate()
|
92
|
+
except Exception as e:
|
93
|
+
raise RuntimeError(f"Error pasting from clipboard: {e}") from e
|
94
|
+
if result.returncode != 0:
|
95
|
+
raise RuntimeError(f"{self._paste.cmd} failed with return code {result.returncode}")
|
96
|
+
return result.stdout
|
97
|
+
|
98
|
+
async def clear(self) -> int:
|
99
|
+
"""A function that clears the clipboard.
|
100
|
+
|
101
|
+
Returns:
|
102
|
+
int: The return code of the command.
|
103
|
+
"""
|
104
|
+
return await self.copy(output="")
|
105
|
+
|
106
|
+
|
107
|
+
def copy_to_clipboard(output: str) -> int:
|
108
|
+
"""Copy the output to the clipboard.
|
109
|
+
|
110
|
+
Args:
|
111
|
+
output (str): The output to copy to the clipboard.
|
112
|
+
|
113
|
+
Returns:
|
114
|
+
int: The return code of the command.
|
115
|
+
"""
|
116
|
+
clipboard_manager = ClipboardManager()
|
117
|
+
loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
|
118
|
+
return loop.run_until_complete(future=clipboard_manager.copy(output))
|
119
|
+
|
120
|
+
|
121
|
+
async def copy_to_clipboard_async(output: str) -> int:
|
122
|
+
"""Asynchronously copy the output to the clipboard.
|
123
|
+
|
124
|
+
Args:
|
125
|
+
output (str): The output to copy to the clipboard.
|
126
|
+
|
127
|
+
Returns:
|
128
|
+
int: The return code of the command.
|
129
|
+
"""
|
130
|
+
clipboard_manager = ClipboardManager()
|
131
|
+
return await clipboard_manager.copy(output=output)
|
132
|
+
|
133
|
+
|
134
|
+
def paste_from_clipboard() -> str:
|
135
|
+
"""Paste the output from the clipboard.
|
136
|
+
|
137
|
+
Returns:
|
138
|
+
str: The content of the clipboard.
|
139
|
+
"""
|
140
|
+
clipboard_manager = ClipboardManager()
|
141
|
+
loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
|
142
|
+
return loop.run_until_complete(future=clipboard_manager.paste())
|
143
|
+
|
144
|
+
|
145
|
+
async def paste_from_clipboard_async() -> str:
|
146
|
+
"""Asynchronously paste the output from the clipboard.
|
147
|
+
|
148
|
+
Returns:
|
149
|
+
str: The content of the clipboard.
|
150
|
+
"""
|
151
|
+
clipboard_manager = ClipboardManager()
|
152
|
+
return await clipboard_manager.paste()
|
153
|
+
|
154
|
+
|
155
|
+
def clear_clipboard() -> int:
|
156
|
+
"""Clear the clipboard.
|
157
|
+
|
158
|
+
Returns:
|
159
|
+
int: The return code of the command.
|
160
|
+
"""
|
161
|
+
clipboard_manager = ClipboardManager()
|
162
|
+
loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
|
163
|
+
return loop.run_until_complete(clipboard_manager.clear())
|
164
|
+
|
165
|
+
|
166
|
+
async def clear_clipboard_async() -> int:
|
167
|
+
"""Asynchronously clear the clipboard.
|
168
|
+
|
169
|
+
Returns:
|
170
|
+
int: The return code of the command.
|
171
|
+
"""
|
172
|
+
clipboard_manager = ClipboardManager()
|
173
|
+
return await clipboard_manager.clear()
|
174
|
+
|
175
|
+
|
176
|
+
__all__ = [
|
177
|
+
"ClipboardManager",
|
178
|
+
"ascii_header",
|
179
|
+
"clear_clipboard",
|
180
|
+
"clear_clipboard_async",
|
181
|
+
"copy_to_clipboard",
|
182
|
+
"copy_to_clipboard_async",
|
183
|
+
"paste_from_clipboard",
|
184
|
+
"paste_from_clipboard_async",
|
185
|
+
]
|
@@ -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)
|