bear-utils 0.8.25__py3-none-any.whl → 0.8.27__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 +10 -4
- bear_utils/cli/__init__.py +5 -0
- bear_utils/cli/_args.py +12 -0
- bear_utils/cli/shell/_base_shell.py +4 -17
- bear_utils/constants/__init__.py +52 -1
- bear_utils/constants/_exit_code.py +60 -0
- bear_utils/constants/_http_status_code.py +37 -0
- bear_utils/constants/_meta.py +107 -0
- bear_utils/extras/__init__.py +2 -2
- bear_utils/extras/_tools.py +101 -33
- bear_utils/extras/responses/__init__.py +2 -6
- bear_utils/extras/responses/function_response.py +0 -4
- bear_utils/graphics/font/__init__.py +11 -0
- bear_utils/graphics/font/_raw_block_letters.py +463 -0
- bear_utils/graphics/font/_theme.py +11 -0
- bear_utils/graphics/font/block_font.py +150 -0
- bear_utils/graphics/font/glitch_font.py +63 -0
- bear_utils/logger_manager/__init__.py +15 -3
- bear_utils/logger_manager/_log_level.py +12 -88
- bear_utils/logger_manager/logger_protocol.py +22 -25
- bear_utils/logger_manager/loggers/_console.py +204 -0
- bear_utils/logger_manager/loggers/_logger.py +19 -0
- bear_utils/logger_manager/loggers/base_logger.py +7 -7
- bear_utils/logger_manager/loggers/fastapi_logger.py +235 -110
- bear_utils/logger_manager/loggers/simple_logger.py +39 -24
- {bear_utils-0.8.25.dist-info → bear_utils-0.8.27.dist-info}/METADATA +3 -3
- {bear_utils-0.8.25.dist-info → bear_utils-0.8.27.dist-info}/RECORD +28 -18
- bear_utils/constants/server.py +0 -16
- {bear_utils-0.8.25.dist-info → bear_utils-0.8.27.dist-info}/WHEEL +0 -0
bear_utils/__init__.py
CHANGED
@@ -2,11 +2,12 @@
|
|
2
2
|
|
3
3
|
from bear_utils.cache import CacheWrapper, cache, cache_factory
|
4
4
|
from bear_utils.config.settings_manager import SettingsManager, get_settings_manager
|
5
|
+
from bear_utils.constants import DEVNULL, STDERR, STDOUT, ExitCode, HTTPStatusCode
|
5
6
|
from bear_utils.database import DatabaseManager
|
6
7
|
from bear_utils.events import Events
|
7
|
-
from bear_utils.extras.responses import
|
8
|
+
from bear_utils.extras.responses import FunctionResponse
|
8
9
|
from bear_utils.files.file_handlers.file_handler_factory import FileHandlerFactory
|
9
|
-
from bear_utils.logger_manager import BaseLogger, BufferLogger, ConsoleLogger, FileLogger
|
10
|
+
from bear_utils.logger_manager import BaseLogger, BufferLogger, ConsoleLogger, FileLogger, LoggingClient, LoggingServer
|
10
11
|
from bear_utils.logger_manager._common import VERBOSE_CONSOLE_FORMAT
|
11
12
|
from bear_utils.logger_manager._styles import VERBOSE
|
12
13
|
from bear_utils.time import (
|
@@ -21,8 +22,9 @@ from bear_utils.time import (
|
|
21
22
|
__all__ = [
|
22
23
|
"DATE_FORMAT",
|
23
24
|
"DATE_TIME_FORMAT",
|
24
|
-
"
|
25
|
-
"
|
25
|
+
"DEVNULL",
|
26
|
+
"STDERR",
|
27
|
+
"STDOUT",
|
26
28
|
"VERBOSE",
|
27
29
|
"VERBOSE_CONSOLE_FORMAT",
|
28
30
|
"BaseLogger",
|
@@ -32,9 +34,13 @@ __all__ = [
|
|
32
34
|
"DatabaseManager",
|
33
35
|
"EpochTimestamp",
|
34
36
|
"Events",
|
37
|
+
"ExitCode",
|
35
38
|
"FileHandlerFactory",
|
36
39
|
"FileLogger",
|
37
40
|
"FunctionResponse",
|
41
|
+
"HTTPStatusCode",
|
42
|
+
"LoggingClient",
|
43
|
+
"LoggingServer",
|
38
44
|
"SettingsManager",
|
39
45
|
"TimeTools",
|
40
46
|
"cache",
|
bear_utils/cli/__init__.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
"""A set of command-line interface (CLI) utilities for bear_utils."""
|
2
2
|
|
3
|
+
from ._args import FAILURE, SUCCESS, ExitCode, args_process
|
3
4
|
from .commands import GitCommand, MaskShellCommand, OPShellCommand, UVShellCommand
|
4
5
|
from .shell._base_command import BaseShellCommand
|
5
6
|
from .shell._base_shell import SimpleShellSession, shell_session
|
@@ -7,11 +8,15 @@ from .shell._common import DEFAULT_SHELL
|
|
7
8
|
|
8
9
|
__all__ = [
|
9
10
|
"DEFAULT_SHELL",
|
11
|
+
"FAILURE",
|
12
|
+
"SUCCESS",
|
10
13
|
"BaseShellCommand",
|
14
|
+
"ExitCode",
|
11
15
|
"GitCommand",
|
12
16
|
"MaskShellCommand",
|
13
17
|
"OPShellCommand",
|
14
18
|
"SimpleShellSession",
|
15
19
|
"UVShellCommand",
|
20
|
+
"args_process",
|
16
21
|
"shell_session",
|
17
22
|
]
|
bear_utils/cli/_args.py
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
import sys
|
2
|
+
|
3
|
+
from bear_utils.constants._exit_code import FAILURE, SUCCESS, ExitCode
|
4
|
+
|
5
|
+
|
6
|
+
def args_process(args: list[str] | None = None) -> tuple[list[str], ExitCode]:
|
7
|
+
args = sys.argv[1:] if args is None else args
|
8
|
+
|
9
|
+
if not args:
|
10
|
+
return [], FAILURE
|
11
|
+
|
12
|
+
return args, SUCCESS
|
@@ -13,26 +13,13 @@ import subprocess
|
|
13
13
|
from subprocess import CompletedProcess
|
14
14
|
from typing import Self, override
|
15
15
|
|
16
|
+
from bear_utils.constants import ExitCode
|
16
17
|
from bear_utils.logger_manager import VERBOSE, BaseLogger, SubConsoleLogger
|
17
18
|
from bear_utils.logger_manager.logger_protocol import LoggerProtocol
|
18
19
|
|
19
20
|
from ._base_command import BaseShellCommand
|
20
21
|
from ._common import DEFAULT_SHELL
|
21
22
|
|
22
|
-
EXIT_CODES: dict[int, str] = {
|
23
|
-
0: "Success",
|
24
|
-
1: "General error",
|
25
|
-
2: "Misuse of shell command",
|
26
|
-
126: "Command invoked cannot execute",
|
27
|
-
127: "Command not found",
|
28
|
-
128: "Invalid argument to exit",
|
29
|
-
130: "Script terminated by Control-C",
|
30
|
-
137: "Process killed by SIGKILL (9)",
|
31
|
-
139: "Segmentation fault (core dumped)",
|
32
|
-
143: "Process terminated by SIGTERM (15)",
|
33
|
-
255: "Exit status out of range",
|
34
|
-
}
|
35
|
-
|
36
23
|
|
37
24
|
class FancyCompletedProcess(CompletedProcess[str]):
|
38
25
|
def __init__(self, args: list[str], returncode: int, stdout: str | None = None, stderr: str | None = None) -> None:
|
@@ -53,7 +40,7 @@ class FancyCompletedProcess(CompletedProcess[str]):
|
|
53
40
|
@property
|
54
41
|
def exit_message(self) -> str:
|
55
42
|
"""Get a human-readable message for the exit code"""
|
56
|
-
return
|
43
|
+
return ExitCode.from_int(self.returncode).text
|
57
44
|
|
58
45
|
|
59
46
|
class CommandList(deque[CompletedProcess[str]]):
|
@@ -149,7 +136,7 @@ class SimpleShellSession:
|
|
149
136
|
|
150
137
|
def _run(self, command: str) -> CompletedProcess[str]:
|
151
138
|
"""Internal method to run the accumulated command"""
|
152
|
-
self.logger.
|
139
|
+
self.logger.debug(f"Executing: {command}")
|
153
140
|
self.next_cmd()
|
154
141
|
|
155
142
|
if self.use_shell:
|
@@ -311,7 +298,7 @@ class AsyncShellSession(SimpleShellSession):
|
|
311
298
|
@override
|
312
299
|
async def _run(self, command: str, **kwargs) -> Process: # type: ignore[override]
|
313
300
|
"""Run the command using Popen for better control"""
|
314
|
-
self.logger.
|
301
|
+
self.logger.debug(f"Executing: {command}")
|
315
302
|
self.next_cmd()
|
316
303
|
|
317
304
|
if self.use_shell:
|
bear_utils/constants/__init__.py
CHANGED
@@ -1,8 +1,34 @@
|
|
1
1
|
"""Constants Module for Bear Utils."""
|
2
2
|
|
3
3
|
from pathlib import Path
|
4
|
+
import sys
|
5
|
+
from typing import TextIO
|
4
6
|
|
5
|
-
from .
|
7
|
+
from bear_utils.constants._exit_code import (
|
8
|
+
COMMAND_CANNOT_EXECUTE,
|
9
|
+
COMMAND_NOT_FOUND,
|
10
|
+
EXIT_STATUS_OUT_OF_RANGE,
|
11
|
+
FAIL,
|
12
|
+
INVALID_ARGUMENT_TO_EXIT,
|
13
|
+
MISUSE_OF_SHELL_COMMAND,
|
14
|
+
PROCESS_KILLED_BY_SIGKILL,
|
15
|
+
PROCESS_TERMINATED_BY_SIGTERM,
|
16
|
+
SCRIPT_TERMINATED_BY_CONTROL_C,
|
17
|
+
SEGMENTATION_FAULT,
|
18
|
+
SUCCESS,
|
19
|
+
ExitCode,
|
20
|
+
)
|
21
|
+
from bear_utils.constants._http_status_code import (
|
22
|
+
BAD_REQUEST,
|
23
|
+
CONFLICT,
|
24
|
+
FORBIDDEN,
|
25
|
+
PAGE_NOT_FOUND,
|
26
|
+
SERVER_ERROR,
|
27
|
+
SERVER_OK,
|
28
|
+
UNAUTHORIZED,
|
29
|
+
HTTPStatusCode,
|
30
|
+
)
|
31
|
+
from bear_utils.constants._meta import NullFile, RichIntEnum, Value
|
6
32
|
|
7
33
|
VIDEO_EXTS = [".mp4", ".mov", ".avi", ".mkv"]
|
8
34
|
"""Extensions for video files."""
|
@@ -18,18 +44,43 @@ PATH_TO_PICTURES = Path.home() / "Pictures"
|
|
18
44
|
GLOBAL_VENV = Path.home() / ".global_venv"
|
19
45
|
"""Path to the global virtual environment."""
|
20
46
|
|
47
|
+
STDOUT: TextIO = sys.stdout
|
48
|
+
"""Standard output stream."""
|
49
|
+
STDERR: TextIO = sys.stderr
|
50
|
+
"""Standard error stream."""
|
51
|
+
DEVNULL: TextIO = NullFile()
|
52
|
+
"""A null file that discards all writes."""
|
53
|
+
|
21
54
|
__all__ = [
|
22
55
|
"BAD_REQUEST",
|
56
|
+
"COMMAND_CANNOT_EXECUTE",
|
57
|
+
"COMMAND_NOT_FOUND",
|
23
58
|
"CONFLICT",
|
59
|
+
"EXIT_STATUS_OUT_OF_RANGE",
|
60
|
+
"FAIL",
|
24
61
|
"FILE_EXTS",
|
25
62
|
"FORBIDDEN",
|
26
63
|
"GLOBAL_VENV",
|
27
64
|
"IMAGE_EXTS",
|
65
|
+
"INVALID_ARGUMENT_TO_EXIT",
|
66
|
+
"MISUSE_OF_SHELL_COMMAND",
|
28
67
|
"PAGE_NOT_FOUND",
|
29
68
|
"PATH_TO_DOWNLOADS",
|
30
69
|
"PATH_TO_PICTURES",
|
70
|
+
"PROCESS_KILLED_BY_SIGKILL",
|
71
|
+
"PROCESS_TERMINATED_BY_SIGTERM",
|
72
|
+
"SCRIPT_TERMINATED_BY_CONTROL_C",
|
73
|
+
"SEGMENTATION_FAULT",
|
31
74
|
"SERVER_ERROR",
|
32
75
|
"SERVER_OK",
|
76
|
+
"STDERR",
|
77
|
+
"STDOUT",
|
78
|
+
"SUCCESS",
|
33
79
|
"UNAUTHORIZED",
|
34
80
|
"VIDEO_EXTS",
|
81
|
+
"ExitCode",
|
82
|
+
"HTTPStatusCode",
|
83
|
+
"NullFile",
|
84
|
+
"RichIntEnum",
|
85
|
+
"Value",
|
35
86
|
]
|
@@ -0,0 +1,60 @@
|
|
1
|
+
from bear_utils.constants._meta import RichIntEnum, Value
|
2
|
+
|
3
|
+
|
4
|
+
class ExitCode(RichIntEnum):
|
5
|
+
"""An enumeration of common exit codes used in shell commands."""
|
6
|
+
|
7
|
+
SUCCESS = Value(0, "Success")
|
8
|
+
FAILURE = Value(1, "General error")
|
9
|
+
MISUSE_OF_SHELL_COMMAND = Value(2, "Misuse of shell command")
|
10
|
+
COMMAND_CANNOT_EXECUTE = Value(126, "Command invoked cannot execute")
|
11
|
+
COMMAND_NOT_FOUND = Value(127, "Command not found")
|
12
|
+
INVALID_ARGUMENT_TO_EXIT = Value(128, "Invalid argument to exit")
|
13
|
+
SCRIPT_TERMINATED_BY_CONTROL_C = Value(130, "Script terminated by Control-C")
|
14
|
+
PROCESS_KILLED_BY_SIGKILL = Value(137, "Process killed by SIGKILL (9)")
|
15
|
+
SEGMENTATION_FAULT = Value(139, "Segmentation fault (core dumped)")
|
16
|
+
PROCESS_TERMINATED_BY_SIGTERM = Value(143, "Process terminated by SIGTERM (15)")
|
17
|
+
EXIT_STATUS_OUT_OF_RANGE = Value(255, "Exit status out of range")
|
18
|
+
|
19
|
+
|
20
|
+
SUCCESS = ExitCode.SUCCESS
|
21
|
+
"""An exit code indicating success."""
|
22
|
+
FAIL = ExitCode.FAILURE
|
23
|
+
"""Deprecated alias for ExitCode.FAILURE."""
|
24
|
+
FAILURE = ExitCode.FAILURE
|
25
|
+
"""An exit code indicating a general error."""
|
26
|
+
MISUSE_OF_SHELL_COMMAND = ExitCode.MISUSE_OF_SHELL_COMMAND
|
27
|
+
"""An exit code indicating misuse of a shell command."""
|
28
|
+
COMMAND_CANNOT_EXECUTE = ExitCode.COMMAND_CANNOT_EXECUTE
|
29
|
+
"""An exit code indicating that the command invoked cannot execute."""
|
30
|
+
COMMAND_NOT_FOUND = ExitCode.COMMAND_NOT_FOUND
|
31
|
+
"""An exit code indicating that the command was not found."""
|
32
|
+
INVALID_ARGUMENT_TO_EXIT = ExitCode.INVALID_ARGUMENT_TO_EXIT
|
33
|
+
"""An exit code indicating an invalid argument to exit."""
|
34
|
+
SCRIPT_TERMINATED_BY_CONTROL_C = ExitCode.SCRIPT_TERMINATED_BY_CONTROL_C
|
35
|
+
"""An exit code indicating that the script was terminated by Control-C."""
|
36
|
+
PROCESS_KILLED_BY_SIGKILL = ExitCode.PROCESS_KILLED_BY_SIGKILL
|
37
|
+
"""An exit code indicating that the process was killed by SIGKILL (9)."""
|
38
|
+
SEGMENTATION_FAULT = ExitCode.SEGMENTATION_FAULT
|
39
|
+
"""An exit code indicating a segmentation fault (core dumped)."""
|
40
|
+
PROCESS_TERMINATED_BY_SIGTERM = ExitCode.PROCESS_TERMINATED_BY_SIGTERM
|
41
|
+
"""An exit code indicating that the process was terminated by SIGTERM (15)."""
|
42
|
+
EXIT_STATUS_OUT_OF_RANGE = ExitCode.EXIT_STATUS_OUT_OF_RANGE
|
43
|
+
"""An exit code indicating that the exit status is out of range."""
|
44
|
+
|
45
|
+
|
46
|
+
__all__ = [
|
47
|
+
"COMMAND_CANNOT_EXECUTE",
|
48
|
+
"COMMAND_NOT_FOUND",
|
49
|
+
"EXIT_STATUS_OUT_OF_RANGE",
|
50
|
+
"FAIL",
|
51
|
+
"FAILURE",
|
52
|
+
"INVALID_ARGUMENT_TO_EXIT",
|
53
|
+
"MISUSE_OF_SHELL_COMMAND",
|
54
|
+
"PROCESS_KILLED_BY_SIGKILL",
|
55
|
+
"PROCESS_TERMINATED_BY_SIGTERM",
|
56
|
+
"SCRIPT_TERMINATED_BY_CONTROL_C",
|
57
|
+
"SEGMENTATION_FAULT",
|
58
|
+
"SUCCESS",
|
59
|
+
"ExitCode",
|
60
|
+
]
|
@@ -0,0 +1,37 @@
|
|
1
|
+
"""HTTP status codes."""
|
2
|
+
|
3
|
+
from bear_utils.constants._meta import RichIntEnum, Value
|
4
|
+
|
5
|
+
|
6
|
+
class HTTPStatusCode(RichIntEnum):
|
7
|
+
"""An enumeration of common HTTP status codes."""
|
8
|
+
|
9
|
+
SERVER_ERROR = Value(500, "Internal Server Error")
|
10
|
+
SERVER_OK = Value(200, "OK")
|
11
|
+
PAGE_NOT_FOUND = Value(404, "Not Found")
|
12
|
+
BAD_REQUEST = Value(400, "Bad Request")
|
13
|
+
UNPROCESSABLE_CONTENT = Value(422, "Unprocessable Content")
|
14
|
+
UNAUTHORIZED = Value(401, "Unauthorized")
|
15
|
+
FORBIDDEN = Value(403, "Forbidden")
|
16
|
+
CONFLICT = Value(409, "Conflict")
|
17
|
+
METHOD_NOT_ALLOWED = Value(405, "Method Not Allowed")
|
18
|
+
|
19
|
+
|
20
|
+
SERVER_ERROR = HTTPStatusCode.SERVER_ERROR
|
21
|
+
"""Internal Server Error"""
|
22
|
+
SERVER_OK = HTTPStatusCode.SERVER_OK
|
23
|
+
"""OK"""
|
24
|
+
PAGE_NOT_FOUND = HTTPStatusCode.PAGE_NOT_FOUND
|
25
|
+
"""Not Found"""
|
26
|
+
BAD_REQUEST = HTTPStatusCode.BAD_REQUEST
|
27
|
+
"""Bad Request"""
|
28
|
+
UNPROCESSABLE_CONTENT = HTTPStatusCode.UNPROCESSABLE_CONTENT
|
29
|
+
"""Unprocessable Content"""
|
30
|
+
UNAUTHORIZED = HTTPStatusCode.UNAUTHORIZED
|
31
|
+
"""Unauthorized"""
|
32
|
+
FORBIDDEN = HTTPStatusCode.FORBIDDEN
|
33
|
+
"""Forbidden"""
|
34
|
+
CONFLICT = HTTPStatusCode.CONFLICT
|
35
|
+
"""Conflict"""
|
36
|
+
METHOD_NOT_ALLOWED = HTTPStatusCode.METHOD_NOT_ALLOWED
|
37
|
+
"""Method Not Allowed"""
|
@@ -0,0 +1,107 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from enum import IntEnum
|
3
|
+
from typing import Any, Self, TextIO
|
4
|
+
|
5
|
+
|
6
|
+
@dataclass(frozen=True)
|
7
|
+
class Value:
|
8
|
+
"""A frozen dataclass for holding constant values."""
|
9
|
+
|
10
|
+
value: int
|
11
|
+
text: str
|
12
|
+
|
13
|
+
|
14
|
+
class RichIntEnum(IntEnum):
|
15
|
+
"""Base class for IntEnums with rich metadata."""
|
16
|
+
|
17
|
+
text: str
|
18
|
+
|
19
|
+
def __new__(cls, value: Value) -> Self:
|
20
|
+
_value: int = value.value
|
21
|
+
text: str = value.text
|
22
|
+
obj: Self = int.__new__(cls, _value)
|
23
|
+
obj._value_ = _value
|
24
|
+
obj.text = text
|
25
|
+
return obj
|
26
|
+
|
27
|
+
def __int__(self) -> int:
|
28
|
+
"""Return the integer value of the enum."""
|
29
|
+
return self.value
|
30
|
+
|
31
|
+
def __str__(self) -> str:
|
32
|
+
return f"{self.name} ({self.value}): {self.text}"
|
33
|
+
|
34
|
+
@classmethod
|
35
|
+
def get(cls, value: Any) -> Self:
|
36
|
+
"""Try to get an enum member by its value, name, or text."""
|
37
|
+
if isinstance(value, cls):
|
38
|
+
return value
|
39
|
+
if isinstance(value, int):
|
40
|
+
return cls.from_int(value)
|
41
|
+
if isinstance(value, str):
|
42
|
+
return cls.from_name(value)
|
43
|
+
raise ValueError(f"Cannot convert {value} to {cls.__name__}")
|
44
|
+
|
45
|
+
@classmethod
|
46
|
+
def from_name(cls, name: str) -> Self:
|
47
|
+
"""Convert a string name to its corresponding enum member."""
|
48
|
+
try:
|
49
|
+
return cls[name.upper()]
|
50
|
+
except KeyError as e:
|
51
|
+
raise ValueError(f"Name {name} not found in {cls.__name__}") from e
|
52
|
+
|
53
|
+
@classmethod
|
54
|
+
def from_int(cls, code: int) -> Self:
|
55
|
+
for item in cls:
|
56
|
+
if item.value == code:
|
57
|
+
return item
|
58
|
+
raise ValueError(f"Value {code} not found in {cls.__name__}")
|
59
|
+
|
60
|
+
@classmethod
|
61
|
+
def int_to_text(cls, code: int) -> str:
|
62
|
+
"""Convert an integer to its text representation."""
|
63
|
+
try:
|
64
|
+
return cls.from_int(code).text
|
65
|
+
except ValueError:
|
66
|
+
return "Unknown value"
|
67
|
+
|
68
|
+
|
69
|
+
class MockTextIO(TextIO):
|
70
|
+
"""A mock TextIO class that does nothing."""
|
71
|
+
|
72
|
+
def __init__(self) -> None:
|
73
|
+
"""Initialize the mock TextIO."""
|
74
|
+
self._buffer = []
|
75
|
+
|
76
|
+
def write(self, _s: str, *_) -> None: # type: ignore[override]
|
77
|
+
"""Mock write method that appends to the buffer."""
|
78
|
+
if _s == "\n":
|
79
|
+
return
|
80
|
+
self._buffer.append(_s)
|
81
|
+
|
82
|
+
def output_buffer(self) -> list[str]:
|
83
|
+
"""Get the output buffer."""
|
84
|
+
return self._buffer
|
85
|
+
|
86
|
+
def clear(self) -> None:
|
87
|
+
"""Clear the output buffer."""
|
88
|
+
self._buffer.clear()
|
89
|
+
|
90
|
+
def flush(self) -> None:
|
91
|
+
"""Mock flush method that does nothing."""
|
92
|
+
|
93
|
+
|
94
|
+
class NullFile(TextIO):
|
95
|
+
"""A class that acts as a null file, discarding all writes."""
|
96
|
+
|
97
|
+
def write(self, _s: str, *_: Any) -> None: # type: ignore[override]
|
98
|
+
"""Discard the string written to this null file."""
|
99
|
+
|
100
|
+
def flush(self) -> None:
|
101
|
+
"""Flush the null file (no operation)."""
|
102
|
+
|
103
|
+
def __enter__(self) -> Self:
|
104
|
+
return self
|
105
|
+
|
106
|
+
def __exit__(self, *_: object) -> None:
|
107
|
+
"""Exit context manager (no operation)."""
|
bear_utils/extras/__init__.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
from singleton_base import SingletonBase
|
4
4
|
|
5
|
-
from ._tools import ClipboardManager, clear_clipboard, copy_to_clipboard,
|
5
|
+
from ._tools import ClipboardManager, ascii_header, clear_clipboard, copy_to_clipboard, paste_from_clipboard
|
6
6
|
from .platform_utils import OS, get_platform, is_linux, is_macos, is_windows
|
7
7
|
from .wrappers.add_methods import add_comparison_methods
|
8
8
|
|
@@ -11,9 +11,9 @@ __all__ = [
|
|
11
11
|
"ClipboardManager",
|
12
12
|
"SingletonBase",
|
13
13
|
"add_comparison_methods",
|
14
|
+
"ascii_header",
|
14
15
|
"clear_clipboard",
|
15
16
|
"copy_to_clipboard",
|
16
|
-
"fmt_header",
|
17
17
|
"get_platform",
|
18
18
|
"is_linux",
|
19
19
|
"is_macos",
|
bear_utils/extras/_tools.py
CHANGED
@@ -5,42 +5,16 @@ from functools import cached_property
|
|
5
5
|
import shutil
|
6
6
|
from typing import TYPE_CHECKING
|
7
7
|
|
8
|
+
from rich.console import Console
|
9
|
+
|
8
10
|
from bear_utils.cli.shell._base_command import BaseShellCommand as ShellCommand
|
9
11
|
from bear_utils.cli.shell._base_shell import AsyncShellSession
|
10
12
|
from bear_utils.extras.platform_utils import OS, get_platform
|
11
|
-
from bear_utils.logger_manager.loggers.base_logger import BaseLogger
|
12
13
|
|
13
14
|
if TYPE_CHECKING:
|
14
15
|
from subprocess import CompletedProcess
|
15
16
|
|
16
17
|
|
17
|
-
class TextHelper:
|
18
|
-
@cached_property
|
19
|
-
def local_console(self) -> BaseLogger:
|
20
|
-
from bear_utils.logger_manager import BaseLogger # noqa: PLC0415
|
21
|
-
|
22
|
-
init: bool = not BaseLogger.has_instance()
|
23
|
-
return BaseLogger.get_instance(init=init)
|
24
|
-
|
25
|
-
def print_header(
|
26
|
-
self,
|
27
|
-
title: str,
|
28
|
-
sep: str = "#",
|
29
|
-
length: int = 60,
|
30
|
-
s1: str = "bold red",
|
31
|
-
s2: str = "bold blue",
|
32
|
-
return_txt: bool = False,
|
33
|
-
) -> str:
|
34
|
-
"""Generate a header string"""
|
35
|
-
# FIXME: There are probably better ways to do this, but this is OK.
|
36
|
-
fill: str = sep * length
|
37
|
-
title = f" {title} ".center(length, sep).replace(title, f"[{s1}]{title}[/{s1}]")
|
38
|
-
output_text: str = f"\n{fill}\n{title}\n{fill}\n"
|
39
|
-
if not return_txt:
|
40
|
-
self.local_console.print(output_text, style=s2)
|
41
|
-
return output_text
|
42
|
-
|
43
|
-
|
44
18
|
class ClipboardManager:
|
45
19
|
"""A class to manage clipboard operations such as copying, pasting, and clearing.
|
46
20
|
|
@@ -201,9 +175,71 @@ async def clear_clipboard_async() -> int:
|
|
201
175
|
return await clipboard_manager.clear()
|
202
176
|
|
203
177
|
|
204
|
-
|
178
|
+
class TextHelper:
|
179
|
+
@cached_property
|
180
|
+
def local_console(self) -> Console:
|
181
|
+
return Console()
|
182
|
+
|
183
|
+
def print_header(
|
184
|
+
self,
|
185
|
+
title: str,
|
186
|
+
top_sep: str = "#",
|
187
|
+
left_sep: str = ">",
|
188
|
+
right_sep: str = "<",
|
189
|
+
bottom_sep: str = "#",
|
190
|
+
length: int = 60,
|
191
|
+
s1: str = "bold red",
|
192
|
+
s2: str = "bold blue",
|
193
|
+
return_txt: bool = False,
|
194
|
+
) -> str:
|
195
|
+
"""Generate a header string with customizable separators for each line.
|
196
|
+
|
197
|
+
Args:
|
198
|
+
title: The title text to display
|
199
|
+
top_sep: Character(s) for the top separator line
|
200
|
+
left_sep: Character(s) for the left side of title line
|
201
|
+
right_sep: Character(s) for the right side of title line
|
202
|
+
bottom_sep: Character(s) for the bottom separator line
|
203
|
+
length: Total width of each line
|
204
|
+
s1: Style for the title text
|
205
|
+
s2: Style for the entire header block
|
206
|
+
return_txt: If True, return the text instead of printing
|
207
|
+
"""
|
208
|
+
# Top line: all top_sep characters
|
209
|
+
top_line: str = top_sep * length
|
210
|
+
|
211
|
+
# Bottom line: all bottom_sep characters
|
212
|
+
bottom_line: str = bottom_sep * length
|
213
|
+
|
214
|
+
# Title line: left_sep chars + title + right_sep chars
|
215
|
+
title_with_spaces = f" {title} "
|
216
|
+
styled_title = f"[{s1}]{title}[/{s1}]"
|
217
|
+
|
218
|
+
# Calculate padding needed on each side
|
219
|
+
title_length = len(title_with_spaces)
|
220
|
+
remaining_space = length - title_length
|
221
|
+
left_padding = remaining_space // 2
|
222
|
+
right_padding = remaining_space - left_padding
|
223
|
+
|
224
|
+
# Build the title line with different left and right separators
|
225
|
+
title_line = (
|
226
|
+
(left_sep * left_padding) + title_with_spaces.replace(title, styled_title) + (right_sep * right_padding)
|
227
|
+
)
|
228
|
+
|
229
|
+
# Assemble the complete header
|
230
|
+
output_text: str = f"\n{top_line}\n{title_line}\n{bottom_line}\n"
|
231
|
+
|
232
|
+
if not return_txt:
|
233
|
+
self.local_console.print(output_text, style=s2)
|
234
|
+
return output_text
|
235
|
+
|
236
|
+
|
237
|
+
def ascii_header(
|
205
238
|
title: str,
|
206
|
-
|
239
|
+
top_sep: str = "#",
|
240
|
+
left_sep: str = ">",
|
241
|
+
right_sep: str = "<",
|
242
|
+
bottom_sep: str = "#",
|
207
243
|
length: int = 60,
|
208
244
|
style1: str = "bold red",
|
209
245
|
style2: str = "bold blue",
|
@@ -213,13 +249,45 @@ def fmt_header(
|
|
213
249
|
|
214
250
|
Args:
|
215
251
|
title (str): The title to display in the header.
|
216
|
-
|
252
|
+
top_sep (str): The character to use for the top separator line. Defaults to '#'.
|
253
|
+
left_sep (str): The character to use for the left side of title line. Defaults to '>'.
|
254
|
+
right_sep (str): The character to use for the right side of title line. Defaults to '<'.
|
255
|
+
bottom_sep (str): The character to use for the bottom separator line. Defaults to '#'.
|
217
256
|
length (int): The total length of the header line. Defaults to 60.
|
218
257
|
style1 (str): The style for the title text. Defaults to 'bold red'.
|
219
258
|
style2 (str): The style for the separator text. Defaults to 'bold blue'.
|
259
|
+
print_out (bool): Whether to print the header or just return it. Defaults to True.
|
220
260
|
"""
|
221
261
|
text_helper = TextHelper()
|
222
262
|
if print_out:
|
223
|
-
text_helper.print_header(
|
263
|
+
text_helper.print_header(
|
264
|
+
title=title,
|
265
|
+
top_sep=top_sep,
|
266
|
+
left_sep=left_sep,
|
267
|
+
right_sep=right_sep,
|
268
|
+
bottom_sep=bottom_sep,
|
269
|
+
length=length,
|
270
|
+
s1=style1,
|
271
|
+
s2=style2,
|
272
|
+
return_txt=False,
|
273
|
+
)
|
224
274
|
return ""
|
225
|
-
return text_helper.print_header(
|
275
|
+
return text_helper.print_header(
|
276
|
+
title=title,
|
277
|
+
top_sep=top_sep,
|
278
|
+
left_sep=left_sep,
|
279
|
+
right_sep=right_sep,
|
280
|
+
bottom_sep=bottom_sep,
|
281
|
+
length=length,
|
282
|
+
s1=style1,
|
283
|
+
s2=style2,
|
284
|
+
return_txt=True,
|
285
|
+
)
|
286
|
+
|
287
|
+
|
288
|
+
if __name__ == "__main__":
|
289
|
+
# Example usage of the TextHelper
|
290
|
+
text_helper = TextHelper()
|
291
|
+
text_helper.print_header("My Title", top_sep="#", bottom_sep="#")
|
292
|
+
text_helper.print_header("My Title", top_sep="=", left_sep=">", right_sep="<", bottom_sep="=")
|
293
|
+
text_helper.print_header("My Title", top_sep="-", left_sep="[", right_sep="]", bottom_sep="-")
|
@@ -1,9 +1,5 @@
|
|
1
1
|
"""A module for handling responses for functions, methods, and classes in Bear Utils."""
|
2
2
|
|
3
|
-
from .function_response import
|
3
|
+
from .function_response import FunctionResponse, fail, success
|
4
4
|
|
5
|
-
__all__ = [
|
6
|
-
"FAILURE",
|
7
|
-
"SUCCESS",
|
8
|
-
"FunctionResponse",
|
9
|
-
]
|
5
|
+
__all__ = ["FunctionResponse", "fail", "success"]
|
@@ -18,10 +18,6 @@ if TYPE_CHECKING:
|
|
18
18
|
from collections.abc import Callable
|
19
19
|
|
20
20
|
|
21
|
-
SUCCESS: list[str] = ["name", "success", "number_of_tasks"]
|
22
|
-
FAILURE: list[str] = ["name", "number_of_tasks"]
|
23
|
-
|
24
|
-
|
25
21
|
class FunctionResponse(BaseModel):
|
26
22
|
"""A class to represent the response of a function call, including success status, content, and error messages."""
|
27
23
|
|
@@ -0,0 +1,11 @@
|
|
1
|
+
"""A set of command-line interface (CLI) utilities for creating font outputs."""
|
2
|
+
|
3
|
+
from .block_font import BLOCK_LETTERS, char_to_block, print_block_font, word_to_block
|
4
|
+
|
5
|
+
__all__ = [
|
6
|
+
"BLOCK_LETTERS",
|
7
|
+
"char_to_block",
|
8
|
+
"char_to_block",
|
9
|
+
"print_block_font",
|
10
|
+
"word_to_block",
|
11
|
+
]
|