bear-utils 0.9.0__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.
- bear_utils/_internal/_version.py +1 -0
- bear_utils/_internal/cli.py +55 -9
- bear_utils/_internal/debug.py +19 -4
- bear_utils/cli/_get_version.py +207 -0
- bear_utils/constants/__init__.py +6 -2
- bear_utils/constants/_exit_code.py +1 -1
- bear_utils/constants/_http_status_code.py +1 -1
- bear_utils/constants/_meta.py +107 -16
- bear_utils/extras/__init__.py +6 -0
- bear_utils/extras/_tools.py +11 -119
- bear_utils/extras/_zapper.py +399 -0
- bear_utils/graphics/font/_utils.py +188 -0
- bear_utils/graphics/font/block_font.py +68 -7
- bear_utils/logger_manager/_log_level.py +14 -14
- {bear_utils-0.9.0.dist-info → bear_utils-0.9.3.dist-info}/METADATA +3 -2
- {bear_utils-0.9.0.dist-info → bear_utils-0.9.3.dist-info}/RECORD +17 -13
- {bear_utils-0.9.0.dist-info → bear_utils-0.9.3.dist-info}/WHEEL +0 -0
@@ -0,0 +1 @@
|
|
1
|
+
version = "0.9.3"
|
bear_utils/_internal/cli.py
CHANGED
@@ -15,6 +15,9 @@ import sys
|
|
15
15
|
from typing import Any
|
16
16
|
|
17
17
|
from bear_utils._internal import debug
|
18
|
+
from bear_utils._internal._version import version as _version
|
19
|
+
from bear_utils.cli._get_version import VALID_BUMP_TYPES, cli_bump
|
20
|
+
from bear_utils.constants import ExitCode
|
18
21
|
|
19
22
|
|
20
23
|
class _DebugInfo(Action):
|
@@ -23,7 +26,7 @@ class _DebugInfo(Action):
|
|
23
26
|
|
24
27
|
def __call__(self, *_: Any, **__: Any) -> None:
|
25
28
|
debug._print_debug_info()
|
26
|
-
sys.exit(
|
29
|
+
sys.exit(ExitCode.SUCCESS)
|
27
30
|
|
28
31
|
|
29
32
|
class _About(Action):
|
@@ -32,20 +35,52 @@ class _About(Action):
|
|
32
35
|
|
33
36
|
def __call__(self, *_: Any, **__: Any) -> None:
|
34
37
|
print(debug._get_package_info())
|
35
|
-
sys.exit(
|
38
|
+
sys.exit(ExitCode.SUCCESS)
|
39
|
+
|
40
|
+
|
41
|
+
class _Version(Action):
|
42
|
+
def __init__(self, nargs: int | str | None = 0, **kwargs: Any) -> None:
|
43
|
+
super().__init__(nargs=nargs, **kwargs)
|
44
|
+
|
45
|
+
def __call__(self, *_: Any, **__: Any) -> None:
|
46
|
+
version: str = f"{debug._get_name()} v{_version}"
|
47
|
+
print(version)
|
48
|
+
sys.exit(ExitCode.SUCCESS)
|
49
|
+
|
50
|
+
|
51
|
+
def get_version() -> ExitCode:
|
52
|
+
"""CLI command to get the version of the package."""
|
53
|
+
print(_version)
|
54
|
+
return ExitCode.SUCCESS
|
55
|
+
|
56
|
+
|
57
|
+
def bump_version(args: list[str] | None = None) -> ExitCode:
|
58
|
+
"""CLI command to bump the version of the package."""
|
59
|
+
parser = ArgumentParser(description="Bump the version of the package.")
|
60
|
+
parser.add_argument(
|
61
|
+
"bump_type",
|
62
|
+
type=str,
|
63
|
+
choices=VALID_BUMP_TYPES,
|
64
|
+
help=f"Type of version bump: {', '.join(VALID_BUMP_TYPES)}",
|
65
|
+
)
|
66
|
+
_args: Namespace = parser.parse_args(args or sys.argv[1:])
|
67
|
+
return cli_bump([_args.bump_type, debug.__PACKAGE_NAME__, _version])
|
36
68
|
|
37
69
|
|
38
70
|
def get_parser() -> ArgumentParser:
|
39
|
-
name
|
40
|
-
version: str = f"{name} v{debug._get_version()}"
|
71
|
+
name = debug._get_name()
|
41
72
|
parser = ArgumentParser(description=name.capitalize(), prog=name, exit_on_error=False)
|
42
|
-
parser.add_argument("-V", "--version", action=
|
73
|
+
parser.add_argument("-V", "--version", action=_Version, help="Print the version of the package")
|
74
|
+
subparser = parser.add_subparsers(dest="command", required=False, help="Available commands")
|
75
|
+
subparser.add_parser("get-version", help="Get the current version of the package")
|
76
|
+
bump = subparser.add_parser("bump-version", help="Bump the version of the package")
|
77
|
+
bump.add_argument("bump_type", type=str, choices=VALID_BUMP_TYPES, help="major, minor, or patch")
|
43
78
|
parser.add_argument("--about", action=_About, help="Print information about the package")
|
44
79
|
parser.add_argument("--debug_info", action=_DebugInfo, help="Print debug information")
|
45
80
|
return parser
|
46
81
|
|
47
82
|
|
48
|
-
def main(args: list[str] | None = None) ->
|
83
|
+
def main(args: list[str] | None = None) -> ExitCode:
|
49
84
|
"""Main entry point for the CLI.
|
50
85
|
|
51
86
|
This function is called when the CLI is executed. It can be used to
|
@@ -62,11 +97,22 @@ def main(args: list[str] | None = None) -> int:
|
|
62
97
|
try:
|
63
98
|
parser: ArgumentParser = get_parser()
|
64
99
|
opts: Namespace = parser.parse_args(args)
|
65
|
-
|
100
|
+
command = opts.command
|
101
|
+
if command is None:
|
102
|
+
parser.print_help()
|
103
|
+
return ExitCode.SUCCESS
|
104
|
+
if command == "get-version":
|
105
|
+
return get_version()
|
106
|
+
if command == "bump-version":
|
107
|
+
if not hasattr(opts, "bump_type"):
|
108
|
+
print("Error: 'bump-version' command requires a 'bump_type' argument.", file=sys.stderr)
|
109
|
+
return ExitCode.FAILURE
|
110
|
+
bump_type = opts.bump_type
|
111
|
+
return bump_version([bump_type])
|
66
112
|
except Exception as e:
|
67
113
|
print(f"Error initializing CLI: {e}", file=sys.stderr)
|
68
|
-
return
|
69
|
-
return
|
114
|
+
return ExitCode.FAILURE
|
115
|
+
return ExitCode.SUCCESS
|
70
116
|
|
71
117
|
|
72
118
|
if __name__ == "__main__":
|
bear_utils/_internal/debug.py
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from dataclasses import dataclass
|
3
|
+
from dataclasses import dataclass, field
|
4
4
|
import importlib.metadata
|
5
5
|
from importlib.metadata import PackageNotFoundError, metadata, version
|
6
6
|
import os
|
7
7
|
import platform
|
8
8
|
import sys
|
9
9
|
|
10
|
+
from bear_utils._internal._version import version as _version
|
11
|
+
from bear_utils.cli._get_version import Version
|
12
|
+
|
10
13
|
__PACKAGE_NAME__ = "bear-utils"
|
11
14
|
|
12
15
|
|
@@ -16,11 +19,22 @@ class _Package:
|
|
16
19
|
|
17
20
|
name: str = __PACKAGE_NAME__
|
18
21
|
"""Package name."""
|
19
|
-
version: str =
|
22
|
+
version: str = _version
|
20
23
|
"""Package version."""
|
24
|
+
_version: Version = field(default_factory=lambda: Version.from_string(_version))
|
21
25
|
description: str = "No description available."
|
22
26
|
"""Package description."""
|
23
27
|
|
28
|
+
def __post_init__(self) -> None:
|
29
|
+
"""Post-initialization to ensure version is a string."""
|
30
|
+
if not isinstance(self.version, str) or "0.0.0" in self.version:
|
31
|
+
self.version = version(self.name) if self.name else "0.0.0"
|
32
|
+
if not self.description:
|
33
|
+
try:
|
34
|
+
self.description = metadata(self.name)["Summary"]
|
35
|
+
except PackageNotFoundError:
|
36
|
+
self.description = "No description available."
|
37
|
+
|
24
38
|
def __str__(self) -> str:
|
25
39
|
"""String representation of the package information."""
|
26
40
|
return f"{self.name} v{self.version}: {self.description}"
|
@@ -69,7 +83,7 @@ def _get_package_info(dist: str = __PACKAGE_NAME__) -> _Package:
|
|
69
83
|
try:
|
70
84
|
return _Package(
|
71
85
|
name=dist,
|
72
|
-
version=version(dist),
|
86
|
+
version=_version or version(dist),
|
73
87
|
description=metadata(dist)["Summary"],
|
74
88
|
)
|
75
89
|
except PackageNotFoundError:
|
@@ -156,4 +170,5 @@ def _print_debug_info() -> None:
|
|
156
170
|
|
157
171
|
|
158
172
|
if __name__ == "__main__":
|
159
|
-
_print_debug_info()
|
173
|
+
# _print_debug_info()
|
174
|
+
print(_get_package_info())
|
@@ -0,0 +1,207 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from argparse import ArgumentParser, Namespace
|
4
|
+
from contextlib import redirect_stdout
|
5
|
+
from importlib.metadata import PackageNotFoundError, version
|
6
|
+
from io import StringIO
|
7
|
+
import sys
|
8
|
+
from typing import Literal, Self
|
9
|
+
|
10
|
+
from pydantic import BaseModel
|
11
|
+
|
12
|
+
from bear_utils.constants import ExitCode
|
13
|
+
from bear_utils.constants._meta import IntValue as Value, RichIntEnum
|
14
|
+
from bear_utils.extras import zap_as
|
15
|
+
|
16
|
+
|
17
|
+
class VerParts(RichIntEnum):
|
18
|
+
"""Enumeration for version parts."""
|
19
|
+
|
20
|
+
MAJOR = Value(0, "major")
|
21
|
+
MINOR = Value(1, "minor")
|
22
|
+
PATCH = Value(2, "patch")
|
23
|
+
|
24
|
+
@classmethod
|
25
|
+
def choices(cls) -> list[str]:
|
26
|
+
"""Return a list of valid version parts."""
|
27
|
+
return [version_part.text for version_part in cls]
|
28
|
+
|
29
|
+
@classmethod
|
30
|
+
def parts(cls) -> int:
|
31
|
+
"""Return the total number of version parts."""
|
32
|
+
return len(cls.choices())
|
33
|
+
|
34
|
+
|
35
|
+
VALID_BUMP_TYPES: list[str] = VerParts.choices()
|
36
|
+
ALL_PARTS: int = VerParts.parts()
|
37
|
+
|
38
|
+
|
39
|
+
class Version(BaseModel):
|
40
|
+
"""Model to represent a version string."""
|
41
|
+
|
42
|
+
major: int = 0
|
43
|
+
"""Major version number."""
|
44
|
+
minor: int = 0
|
45
|
+
"""Minor version number."""
|
46
|
+
patch: int = 0
|
47
|
+
"""Patch version number."""
|
48
|
+
|
49
|
+
@classmethod
|
50
|
+
def from_string(cls, version_str: str) -> Self:
|
51
|
+
"""Create a Version instance from a version string.
|
52
|
+
|
53
|
+
Args:
|
54
|
+
version_str: A version string in the format "major.minor.patch".
|
55
|
+
|
56
|
+
Returns:
|
57
|
+
A Version instance.
|
58
|
+
|
59
|
+
Raises:
|
60
|
+
ValueError: If the version string is not in the correct format.
|
61
|
+
"""
|
62
|
+
try:
|
63
|
+
major, minor, patch = zap_as("-+", version_str, 3, replace=".", func=int)
|
64
|
+
return cls(major=int(major), minor=int(minor), patch=int(patch))
|
65
|
+
except ValueError as e:
|
66
|
+
raise ValueError(
|
67
|
+
f"Invalid version string format: {version_str}. Expected integers for major, minor, and patch."
|
68
|
+
) from e
|
69
|
+
|
70
|
+
def increment(self, attr_name: str) -> None:
|
71
|
+
"""Increment the specified part of the version."""
|
72
|
+
setattr(self, attr_name, getattr(self, attr_name) + 1)
|
73
|
+
|
74
|
+
@property
|
75
|
+
def version_string(self) -> str:
|
76
|
+
"""Return the version as a string in the format "major.minor.patch".
|
77
|
+
|
78
|
+
Returns:
|
79
|
+
A string representation of the version.
|
80
|
+
"""
|
81
|
+
return f"{self.major}.{self.minor}.{self.patch}"
|
82
|
+
|
83
|
+
def default(self, part: str) -> None:
|
84
|
+
"""Clear the specified part of the version.
|
85
|
+
|
86
|
+
Args:
|
87
|
+
part: The part of the version to clear.
|
88
|
+
"""
|
89
|
+
if hasattr(self, part):
|
90
|
+
setattr(self, part, 0)
|
91
|
+
|
92
|
+
def new_version(self, bump_type: str) -> Version:
|
93
|
+
"""Return a new version string based on the bump type."""
|
94
|
+
bump_part: VerParts = VerParts.get(bump_type, default=VerParts.PATCH)
|
95
|
+
self.increment(bump_part.text)
|
96
|
+
for part in VerParts:
|
97
|
+
if part.value > bump_part.value:
|
98
|
+
self.default(part.text)
|
99
|
+
return self
|
100
|
+
|
101
|
+
@classmethod
|
102
|
+
def from_func(cls, package_name: str) -> Self:
|
103
|
+
"""Create a Version instance from the current package version.
|
104
|
+
|
105
|
+
Returns:
|
106
|
+
A Version instance with the current package version.
|
107
|
+
|
108
|
+
Raises:
|
109
|
+
PackageNotFoundError: If the package is not found.
|
110
|
+
"""
|
111
|
+
try:
|
112
|
+
current_version = version(package_name)
|
113
|
+
return cls.from_string(current_version)
|
114
|
+
except PackageNotFoundError as e:
|
115
|
+
raise PackageNotFoundError(f"Package '{package_name}' not found: {e}") from e
|
116
|
+
|
117
|
+
|
118
|
+
def _bump_version(version: str, bump_type: Literal["major", "minor", "patch"]) -> Version:
|
119
|
+
"""Bump the version based on the specified type.
|
120
|
+
|
121
|
+
Args:
|
122
|
+
version: The current version string (e.g., "1.2.3").
|
123
|
+
bump_type: The type of bump ("major", "minor", or "patch").
|
124
|
+
|
125
|
+
Returns:
|
126
|
+
The new version string.
|
127
|
+
|
128
|
+
Raises:
|
129
|
+
ValueError: If the version format is invalid or bump_type is unsupported.
|
130
|
+
"""
|
131
|
+
ver: Version = Version.from_string(version)
|
132
|
+
return ver.new_version(bump_type)
|
133
|
+
|
134
|
+
|
135
|
+
def _get_version(package_name: str) -> str:
|
136
|
+
"""Get the version of the specified package.
|
137
|
+
|
138
|
+
Args:
|
139
|
+
package_name: The name of the package to get the version for.
|
140
|
+
|
141
|
+
Returns:
|
142
|
+
A Version instance representing the current version of the package.
|
143
|
+
|
144
|
+
Raises:
|
145
|
+
PackageNotFoundError: If the package is not found.
|
146
|
+
"""
|
147
|
+
record = StringIO()
|
148
|
+
with redirect_stdout(record):
|
149
|
+
cli_get_version([package_name])
|
150
|
+
return record.getvalue().strip()
|
151
|
+
|
152
|
+
|
153
|
+
def cli_get_version(args: list[str] | None = None) -> ExitCode:
|
154
|
+
"""Get the version of the current package.
|
155
|
+
|
156
|
+
Returns:
|
157
|
+
The version of the package.
|
158
|
+
"""
|
159
|
+
if args is None:
|
160
|
+
args = sys.argv[1:]
|
161
|
+
parser = ArgumentParser(description="Get the version of the package.")
|
162
|
+
parser.add_argument("package_name", nargs="?", type=str, help="Name of the package to get the version for.")
|
163
|
+
arguments: Namespace = parser.parse_args(args)
|
164
|
+
if not arguments.package_name:
|
165
|
+
print("No package name provided. Please specify a package name.")
|
166
|
+
return ExitCode.FAILURE
|
167
|
+
package_name: str = arguments.package_name
|
168
|
+
try:
|
169
|
+
current_version = version(package_name)
|
170
|
+
print(current_version)
|
171
|
+
except PackageNotFoundError:
|
172
|
+
print(f"Package '{package_name}' not found.")
|
173
|
+
return ExitCode.FAILURE
|
174
|
+
return ExitCode.SUCCESS
|
175
|
+
|
176
|
+
|
177
|
+
def cli_bump(args: list[str] | None = None) -> ExitCode:
|
178
|
+
if args is None:
|
179
|
+
args = sys.argv[1:]
|
180
|
+
parser = ArgumentParser(description="Bump the version of the package.")
|
181
|
+
parser.add_argument("bump_type", type=str, choices=VALID_BUMP_TYPES, default="patch")
|
182
|
+
parser.add_argument("package_name", nargs="?", type=str, help="Name of the package to bump the version for.")
|
183
|
+
parser.add_argument("current_version", type=str, help="Current version of the package.")
|
184
|
+
arguments: Namespace = parser.parse_args(args)
|
185
|
+
bump_type: Literal["major", "minor", "patch"] = arguments.bump_type
|
186
|
+
if not arguments.package_name:
|
187
|
+
print("No package name provided.")
|
188
|
+
return ExitCode.FAILURE
|
189
|
+
package_name: str = arguments.package_name
|
190
|
+
if bump_type not in VALID_BUMP_TYPES:
|
191
|
+
print(f"Invalid argument '{bump_type}'. Use one of: {', '.join(VALID_BUMP_TYPES)}.")
|
192
|
+
return ExitCode.FAILURE
|
193
|
+
current_version: str = arguments.current_version or _get_version(package_name)
|
194
|
+
try:
|
195
|
+
new_version: Version = _bump_version(version=current_version, bump_type=bump_type)
|
196
|
+
print(new_version.version_string)
|
197
|
+
return ExitCode.SUCCESS
|
198
|
+
except ValueError as e:
|
199
|
+
print(f"Error: {e}")
|
200
|
+
return ExitCode.FAILURE
|
201
|
+
except Exception as e:
|
202
|
+
print(f"Unexpected error: {e}")
|
203
|
+
return ExitCode.FAILURE
|
204
|
+
|
205
|
+
|
206
|
+
if __name__ == "__main__":
|
207
|
+
cli_bump(["patch", "bear-utils", "0.9.2-fart.build-alpha"])
|
bear_utils/constants/__init__.py
CHANGED
@@ -9,6 +9,7 @@ from bear_utils.constants._exit_code import (
|
|
9
9
|
COMMAND_NOT_FOUND,
|
10
10
|
EXIT_STATUS_OUT_OF_RANGE,
|
11
11
|
FAIL,
|
12
|
+
FAILURE,
|
12
13
|
INVALID_ARGUMENT_TO_EXIT,
|
13
14
|
MISUSE_OF_SHELL_COMMAND,
|
14
15
|
PROCESS_KILLED_BY_SIGKILL,
|
@@ -28,7 +29,7 @@ from bear_utils.constants._http_status_code import (
|
|
28
29
|
UNAUTHORIZED,
|
29
30
|
HTTPStatusCode,
|
30
31
|
)
|
31
|
-
from bear_utils.constants._meta import NullFile, RichIntEnum,
|
32
|
+
from bear_utils.constants._meta import IntValue, NullFile, RichIntEnum, RichStrEnum, StrValue
|
32
33
|
|
33
34
|
VIDEO_EXTS = [".mp4", ".mov", ".avi", ".mkv"]
|
34
35
|
"""Extensions for video files."""
|
@@ -58,6 +59,7 @@ __all__ = [
|
|
58
59
|
"CONFLICT",
|
59
60
|
"EXIT_STATUS_OUT_OF_RANGE",
|
60
61
|
"FAIL",
|
62
|
+
"FAILURE",
|
61
63
|
"FILE_EXTS",
|
62
64
|
"FORBIDDEN",
|
63
65
|
"GLOBAL_VENV",
|
@@ -80,7 +82,9 @@ __all__ = [
|
|
80
82
|
"VIDEO_EXTS",
|
81
83
|
"ExitCode",
|
82
84
|
"HTTPStatusCode",
|
85
|
+
"IntValue",
|
83
86
|
"NullFile",
|
84
87
|
"RichIntEnum",
|
85
|
-
"
|
88
|
+
"RichStrEnum",
|
89
|
+
"StrValue",
|
86
90
|
]
|
bear_utils/constants/_meta.py
CHANGED
@@ -1,27 +1,101 @@
|
|
1
|
+
from contextlib import suppress
|
1
2
|
from dataclasses import dataclass
|
2
|
-
from enum import IntEnum
|
3
|
-
from typing import Any, Self, TextIO
|
3
|
+
from enum import IntEnum, StrEnum
|
4
|
+
from typing import Any, Self, TextIO, overload
|
4
5
|
|
5
6
|
|
6
7
|
@dataclass(frozen=True)
|
7
|
-
class
|
8
|
-
"""A frozen dataclass for holding constant values."""
|
8
|
+
class IntValue:
|
9
|
+
"""A frozen dataclass for holding constant integer values."""
|
9
10
|
|
10
11
|
value: int
|
11
12
|
text: str
|
13
|
+
default: int = 0
|
14
|
+
|
15
|
+
|
16
|
+
@dataclass(frozen=True)
|
17
|
+
class StrValue:
|
18
|
+
"""A frozen dataclass for holding constant string values."""
|
19
|
+
|
20
|
+
value: str
|
21
|
+
text: str
|
22
|
+
default: str = ""
|
23
|
+
|
24
|
+
|
25
|
+
class RichStrEnum(StrEnum):
|
26
|
+
"""Base class for StrEnums with rich metadata."""
|
27
|
+
|
28
|
+
text: str
|
29
|
+
default: str
|
30
|
+
|
31
|
+
def __new__(cls, value: StrValue) -> Self:
|
32
|
+
text: str = value.text
|
33
|
+
obj: Self = str.__new__(cls, value.value)
|
34
|
+
obj._value_ = value.value
|
35
|
+
obj.text = text
|
36
|
+
obj.default = value.default
|
37
|
+
return obj
|
38
|
+
|
39
|
+
@classmethod
|
40
|
+
def keys(cls) -> list[str]:
|
41
|
+
"""Return a list of all enum member names."""
|
42
|
+
return [item.name for item in cls]
|
43
|
+
|
44
|
+
@overload
|
45
|
+
@classmethod
|
46
|
+
def get(cls, value: str | Self, default: Self) -> Self: ...
|
47
|
+
|
48
|
+
@overload
|
49
|
+
@classmethod
|
50
|
+
def get(cls, value: str | Self, default: None = None) -> None: ...
|
51
|
+
|
52
|
+
@classmethod
|
53
|
+
def get(cls, value: str | Self, default: Self | None = None) -> Self | None:
|
54
|
+
"""Try to get an enum member by its value or name."""
|
55
|
+
if isinstance(value, cls):
|
56
|
+
return value
|
57
|
+
with suppress(ValueError):
|
58
|
+
if isinstance(value, str):
|
59
|
+
return cls.from_text(value)
|
60
|
+
return default
|
61
|
+
|
62
|
+
@classmethod
|
63
|
+
def from_text(cls, text: str) -> Self:
|
64
|
+
"""Convert a string text to its corresponding enum member."""
|
65
|
+
for item in cls:
|
66
|
+
if item.text == text:
|
67
|
+
return item
|
68
|
+
raise ValueError(f"Text {text} not found in {cls.__name__}")
|
69
|
+
|
70
|
+
@classmethod
|
71
|
+
def from_name(cls, name: str) -> Self:
|
72
|
+
"""Convert a string name to its corresponding enum member."""
|
73
|
+
try:
|
74
|
+
return cls[name.upper()]
|
75
|
+
except KeyError as e:
|
76
|
+
raise ValueError(f"Name {name} not found in {cls.__name__}") from e
|
77
|
+
|
78
|
+
def __str__(self) -> str:
|
79
|
+
"""Return a string representation of the enum."""
|
80
|
+
return self.value
|
81
|
+
|
82
|
+
def str(self) -> str:
|
83
|
+
"""Return the string value of the enum."""
|
84
|
+
return self.value
|
12
85
|
|
13
86
|
|
14
87
|
class RichIntEnum(IntEnum):
|
15
88
|
"""Base class for IntEnums with rich metadata."""
|
16
89
|
|
17
90
|
text: str
|
91
|
+
default: int
|
18
92
|
|
19
|
-
def __new__(cls, value:
|
20
|
-
_value: int = value.value
|
93
|
+
def __new__(cls, value: IntValue) -> Self:
|
21
94
|
text: str = value.text
|
22
|
-
obj: Self = int.__new__(cls,
|
23
|
-
obj._value_ =
|
95
|
+
obj: Self = int.__new__(cls, value.value)
|
96
|
+
obj._value_ = value.value
|
24
97
|
obj.text = text
|
98
|
+
obj.default = value.default
|
25
99
|
return obj
|
26
100
|
|
27
101
|
def __int__(self) -> int:
|
@@ -29,18 +103,33 @@ class RichIntEnum(IntEnum):
|
|
29
103
|
return self.value
|
30
104
|
|
31
105
|
def __str__(self) -> str:
|
106
|
+
"""Return a string representation of the enum."""
|
32
107
|
return f"{self.name} ({self.value}): {self.text}"
|
33
108
|
|
34
109
|
@classmethod
|
35
|
-
def
|
110
|
+
def keys(cls) -> list[str]:
|
111
|
+
"""Return a list of all enum member names."""
|
112
|
+
return [item.name for item in cls]
|
113
|
+
|
114
|
+
@overload
|
115
|
+
@classmethod
|
116
|
+
def get(cls, value: str | int | Self, default: Self) -> Self: ...
|
117
|
+
|
118
|
+
@overload
|
119
|
+
@classmethod
|
120
|
+
def get(cls, value: str | int | Self, default: None = None) -> None: ...
|
121
|
+
|
122
|
+
@classmethod
|
123
|
+
def get(cls, value: str | int | Self | Any, default: Self | None = None) -> Self | None:
|
36
124
|
"""Try to get an enum member by its value, name, or text."""
|
37
125
|
if isinstance(value, cls):
|
38
126
|
return value
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
127
|
+
with suppress(ValueError):
|
128
|
+
if isinstance(value, int):
|
129
|
+
return cls.from_int(value)
|
130
|
+
if isinstance(value, str):
|
131
|
+
return cls.from_name(value)
|
132
|
+
return default
|
44
133
|
|
45
134
|
@classmethod
|
46
135
|
def from_name(cls, name: str) -> Self:
|
@@ -52,6 +141,7 @@ class RichIntEnum(IntEnum):
|
|
52
141
|
|
53
142
|
@classmethod
|
54
143
|
def from_int(cls, code: int) -> Self:
|
144
|
+
"""Convert an integer to its corresponding enum member."""
|
55
145
|
for item in cls:
|
56
146
|
if item.value == code:
|
57
147
|
return item
|
@@ -67,11 +157,11 @@ class RichIntEnum(IntEnum):
|
|
67
157
|
|
68
158
|
|
69
159
|
class MockTextIO(TextIO):
|
70
|
-
"""A mock TextIO class that
|
160
|
+
"""A mock TextIO class that captures written output for testing purposes."""
|
71
161
|
|
72
162
|
def __init__(self) -> None:
|
73
163
|
"""Initialize the mock TextIO."""
|
74
|
-
self._buffer = []
|
164
|
+
self._buffer: list[str] = []
|
75
165
|
|
76
166
|
def write(self, _s: str, *_) -> None: # type: ignore[override]
|
77
167
|
"""Mock write method that appends to the buffer."""
|
@@ -101,6 +191,7 @@ class NullFile(TextIO):
|
|
101
191
|
"""Flush the null file (no operation)."""
|
102
192
|
|
103
193
|
def __enter__(self) -> Self:
|
194
|
+
"""Enter context manager and return self."""
|
104
195
|
return self
|
105
196
|
|
106
197
|
def __exit__(self, *_: object) -> None:
|
bear_utils/extras/__init__.py
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
from singleton_base import SingletonBase
|
4
4
|
|
5
5
|
from ._tools import ClipboardManager, ascii_header, clear_clipboard, copy_to_clipboard, paste_from_clipboard
|
6
|
+
from ._zapper import zap, zap_as, zap_as_multi, zap_get, zap_multi
|
6
7
|
from .platform_utils import OS, get_platform, is_linux, is_macos, is_windows
|
7
8
|
from .wrappers.add_methods import add_comparison_methods
|
8
9
|
|
@@ -19,4 +20,9 @@ __all__ = [
|
|
19
20
|
"is_macos",
|
20
21
|
"is_windows",
|
21
22
|
"paste_from_clipboard",
|
23
|
+
"zap",
|
24
|
+
"zap_as",
|
25
|
+
"zap_as_multi",
|
26
|
+
"zap_get",
|
27
|
+
"zap_multi",
|
22
28
|
]
|