bitool 0.1.2__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.
Files changed (51) hide show
  1. bitool/__init__.py +27 -0
  2. bitool/cmd/__init__.py +65 -0
  3. bitool/cmd/_base.py +105 -0
  4. bitool/cmd/_condition.py +60 -0
  5. bitool/cmd/_scheduler.py +548 -0
  6. bitool/cmd/env.py +454 -0
  7. bitool/cmd/git.py +123 -0
  8. bitool/cmd/io.py +248 -0
  9. bitool/cmd/pdf.py +385 -0
  10. bitool/cmd/run.py +300 -0
  11. bitool/cmd/toml.py +237 -0
  12. bitool/cmd/version.py +630 -0
  13. bitool/consts.py +14 -0
  14. bitool/core/__init__.py +7 -0
  15. bitool/core/app.py +142 -0
  16. bitool/core/commands.py +194 -0
  17. bitool/core/config.py +647 -0
  18. bitool/core/env.py +18 -0
  19. bitool/core/logger.py +237 -0
  20. bitool/core/plugin.py +117 -0
  21. bitool/core/workspace.py +76 -0
  22. bitool/models/__init__.py +3 -0
  23. bitool/models/version.py +173 -0
  24. bitool/scripts/__init__.py +1 -0
  25. bitool/scripts/bumpversion.py +189 -0
  26. bitool/scripts/clearscreen.py +37 -0
  27. bitool/scripts/envpy.py +161 -0
  28. bitool/scripts/envrs.py +119 -0
  29. bitool/scripts/filedate.py +246 -0
  30. bitool/scripts/filelevel.py +191 -0
  31. bitool/scripts/gittool.py +178 -0
  32. bitool/scripts/img2pdf.py +151 -0
  33. bitool/scripts/pdf2img.py +139 -0
  34. bitool/scripts/piptool.py +130 -0
  35. bitool/scripts/pymake.py +345 -0
  36. bitool/scripts/sshcopyid.py +491 -0
  37. bitool/scripts/taskkill.py +366 -0
  38. bitool/scripts/which.py +227 -0
  39. bitool/types.py +7 -0
  40. bitool/utils/__init__.py +9 -0
  41. bitool/utils/cli_parser.py +412 -0
  42. bitool/utils/executor.py +881 -0
  43. bitool/utils/profiler.py +369 -0
  44. bitool/utils/task.py +133 -0
  45. bitool/utils/task_group.py +668 -0
  46. bitool/utils/tests/__init__.py +0 -0
  47. bitool/utils/tests/test_profiler.py +487 -0
  48. bitool-0.1.2.dist-info/METADATA +154 -0
  49. bitool-0.1.2.dist-info/RECORD +51 -0
  50. bitool-0.1.2.dist-info/WHEEL +4 -0
  51. bitool-0.1.2.dist-info/entry_points.txt +15 -0
bitool/core/logger.py ADDED
@@ -0,0 +1,237 @@
1
+ """Bitool 的日志模块.
2
+
3
+ 本模块提供一个简单的、类似 loguru 的日志系统,具有彩色输出、文件轮转和易于使用的 API.
4
+
5
+ 快速开始
6
+ --------
7
+ >>> from bitool.core import logger
8
+ >>> logger.info("Hello, World!") # 彩色输出到控制台和文件
9
+ >>> logger.debug("看不到调试信息")
10
+ >>> logger.setLevel(logger.DEBUG)
11
+ >>> logger.debug("现在可以看到调试信息了")
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import contextlib
17
+ import logging
18
+ import logging.handlers
19
+ import platform
20
+ import sys
21
+ import threading
22
+ from pathlib import Path
23
+ from typing import ClassVar, Final
24
+
25
+ __all__ = ["logger"]
26
+
27
+ # 默认日志格式
28
+ _DEFAULT_CONSOLE_FORMAT: Final[str] = "%(asctime)s | %(levelname)-8s | %(message)s"
29
+ _DEFAULT_FILE_FORMAT: Final[str] = (
30
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
31
+ )
32
+ _DEFAULT_CONSOLE_DATEFMT: Final[str] = "%H:%M:%S"
33
+ _DEFAULT_FILE_DATEFMT: Final[str] = "%Y-%m-%d %H:%M:%S"
34
+
35
+ # 默认日志路径
36
+ _DEFAULT_LOG_DIR: Final[Path] = Path.home() / ".bitool" / "logs"
37
+ _DEFAULT_LOG_FILE: Final[Path] = _DEFAULT_LOG_DIR / "bitool.log"
38
+ _DEFAULT_MAX_BYTES: Final[int] = 10 * 1024 * 1024 # 10MB
39
+ _DEFAULT_BACKUP_COUNT: Final[int] = 5
40
+
41
+
42
+ class _ColoredFormatter(logging.Formatter):
43
+ """自定义格式化器,为日志消息添加 ANSI 颜色."""
44
+
45
+ COLORS: ClassVar[dict[str, str]] = {
46
+ "DEBUG": "\033[36m", # 青色
47
+ "INFO": "\033[32m", # 绿色
48
+ "WARNING": "\033[33m", # 黄色
49
+ "ERROR": "\033[31m", # 红色
50
+ "CRITICAL": "\033[41m", # 红底白字
51
+ "RESET": "\033[0m", # 重置颜色
52
+ }
53
+
54
+ def __init__(self, fmt: str | None = None, datefmt: str | None = None) -> None:
55
+ """初始化格式化器,检测是否支持 ANSI 颜色."""
56
+ super().__init__(fmt, datefmt)
57
+ # 检测是否启用彩色输出
58
+ self._enable_colors = self._check_color_support()
59
+
60
+ @staticmethod
61
+ def _check_color_support() -> bool:
62
+ """检查当前环境是否支持 ANSI 颜色."""
63
+ # Windows 10 及以上版本支持 ANSI 转义序列
64
+ if platform.system() == "Windows":
65
+ try:
66
+ # 获取 Windows 版本号
67
+ version = platform.version()
68
+ # Windows 版本号格式为 "major.minor.build"
69
+ major_version = int(version.split(".")[0])
70
+ # Windows 10 的版本号为 10, Windows 7 为 6
71
+
72
+ except (ValueError, IndexError):
73
+ # 如果无法解析版本号,保守起见禁用颜色
74
+ return False
75
+ else:
76
+ return major_version >= 10
77
+
78
+ # 非 Windows 系统通常支持 ANSI 颜色
79
+ return True
80
+
81
+ def format(self, record: logging.LogRecord) -> str:
82
+ """使用适当的颜色格式化日志记录."""
83
+ message = super().format(record)
84
+ if not self._enable_colors:
85
+ return message
86
+ log_color = self.COLORS.get(record.levelname, self.COLORS["RESET"])
87
+ return f"{log_color}{message}{self.COLORS['RESET']}"
88
+
89
+
90
+ class _Logger:
91
+ """一个简单的、类似 loguru 的日志器.
92
+
93
+ 提供干净、最小化的 API,用于带有自动控制台颜色和文件轮转的日志记录。
94
+
95
+ 示例
96
+ --------
97
+ >>> from bitool.core import logger
98
+ >>> logger.info("Hello, World!")
99
+ >>> logger.debug("Debug message") # 仅在启用调试时显示
100
+ >>> logger.error("Error occurred")
101
+ """
102
+
103
+ _instance: ClassVar[logging.Logger | None] = None
104
+ _setup_lock: ClassVar[threading.Lock] = threading.Lock()
105
+ _is_setup: ClassVar[bool] = False
106
+
107
+ # 日志级别常量 (与 Python logging 模块兼容)
108
+ DEBUG: Final[int] = logging.DEBUG
109
+ INFO: Final[int] = logging.INFO
110
+ WARNING: Final[int] = logging.WARNING
111
+ ERROR: Final[int] = logging.ERROR
112
+ CRITICAL: Final[int] = logging.CRITICAL
113
+
114
+ @classmethod
115
+ def _setup(
116
+ cls,
117
+ name: str = "bitool",
118
+ level: int = logging.INFO,
119
+ console_format: str | None = None,
120
+ file_format: str | None = None,
121
+ ) -> logging.Logger:
122
+ """设置带有控制台和文件处理器的日志器.
123
+
124
+ 此方法应在应用程序启动时调用一次。
125
+ 后续调用将返回现有的日志器实例。
126
+
127
+ 参数
128
+ ----------
129
+ name : str
130
+ 日志器名称。默认为 "bitool"
131
+ level : int
132
+ 最低日志级别。默认为 INFO
133
+ 使用 logger.DEBUG 获取更详细的输出
134
+ console_format : str, optional
135
+ 控制台日志格式。默认为 "%(asctime)s | %(levelname)-8s | %(message)s"
136
+ file_format : str, optional
137
+ 文件日志格式。默认为 "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
138
+
139
+ 返回
140
+ -------
141
+ logging.Logger
142
+ 配置好的带有控制台和文件处理器的日志器
143
+ """
144
+ # 线程安全的单例初始化
145
+ with cls._setup_lock:
146
+ if cls._is_setup and cls._instance is not None:
147
+ return cls._instance
148
+
149
+ # 确保日志目录存在
150
+ with contextlib.suppress(OSError):
151
+ _DEFAULT_LOG_DIR.mkdir(parents=True, exist_ok=True)
152
+
153
+ # 创建日志器
154
+ logger_instance = logging.getLogger(name)
155
+ logger_instance.setLevel(logging.DEBUG) # 捕获所有级别,在处理器级别过滤
156
+
157
+ # 控制台处理器
158
+ console_handler = logging.StreamHandler(sys.stdout)
159
+ console_handler.setLevel(level)
160
+ console_fmt = console_format if console_format else _DEFAULT_CONSOLE_FORMAT
161
+ console_formatter = _ColoredFormatter(
162
+ console_fmt, datefmt=_DEFAULT_CONSOLE_DATEFMT
163
+ )
164
+ console_handler.setFormatter(console_formatter)
165
+
166
+ # 文件处理器 - 捕获所有 DEBUG+ 日志并带轮转功能
167
+ file_handler = logging.handlers.RotatingFileHandler(
168
+ _DEFAULT_LOG_FILE,
169
+ maxBytes=_DEFAULT_MAX_BYTES,
170
+ backupCount=_DEFAULT_BACKUP_COUNT,
171
+ encoding="utf-8",
172
+ )
173
+ file_handler.setLevel(logging.DEBUG)
174
+ file_fmt = file_format if file_format else _DEFAULT_FILE_FORMAT
175
+ file_formatter = logging.Formatter(file_fmt, datefmt=_DEFAULT_FILE_DATEFMT)
176
+ file_handler.setFormatter(file_formatter)
177
+
178
+ # 添加处理器
179
+ logger_instance.addHandler(console_handler)
180
+ logger_instance.addHandler(file_handler)
181
+
182
+ # 防止多次添加处理器
183
+ logger_instance.propagate = False
184
+
185
+ cls._instance = logger_instance
186
+ cls._is_setup = True
187
+ return cls._instance
188
+
189
+ @classmethod
190
+ def _get_logger(cls, name: str = "bitool") -> logging.Logger:
191
+ """获取日志器实例.
192
+
193
+ 如果日志器尚未设置,此方法将使用默认设置进行设置。
194
+ 如需自定义配置,请显式调用 setup()。
195
+
196
+ 参数
197
+ ----------
198
+ name : str
199
+ 日志器名称。默认为 "bitool"
200
+
201
+ 返回
202
+ -------
203
+ logging.Logger
204
+ 配置好的日志器实例
205
+ """
206
+ if cls._instance is None:
207
+ return cls._setup(name)
208
+ return cls._instance
209
+
210
+ @classmethod
211
+ def setLevel(cls, level: int) -> None:
212
+ """设置日志级别,同时更新 Logger 和所有 Handler 的级别.
213
+
214
+ 解决 Python logging 的两级过滤问题:
215
+ - Logger 级别:决定哪些消息进入日志系统
216
+ - Handler 级别:决定哪些消息实际输出
217
+
218
+ 参数
219
+ ----------
220
+ level : int
221
+ 日志级别,如 logging.DEBUG, logging.INFO 等
222
+
223
+ 示例
224
+ --------
225
+ >>> from bitool.core import logger
226
+ >>> logger.setLevel(logger.DEBUG) # 启用调试日志
227
+ >>> logger.debug("调试信息") # 现在会正常输出
228
+ """
229
+ if cls._instance is not None:
230
+ logging.Logger.setLevel(cls._instance, level)
231
+ for handler in cls._instance.handlers:
232
+ handler.setLevel(level)
233
+
234
+
235
+ # 预配置的日志器实例,便于导入
236
+ logger: Final[logging.Logger] = _Logger._get_logger()
237
+ logger.setLevel = _Logger.setLevel # type: ignore
bitool/core/plugin.py ADDED
@@ -0,0 +1,117 @@
1
+ """插件系统基础框架."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.util
6
+ from abc import ABC, abstractmethod
7
+ from dataclasses import dataclass, field
8
+ from pathlib import Path
9
+ from typing import Any, Callable
10
+
11
+
12
+ @dataclass
13
+ class PluginMetadata:
14
+ """插件元数据."""
15
+
16
+ name: str
17
+ version: str = "0.1.0"
18
+ description: str = ""
19
+ author: str = ""
20
+
21
+
22
+ class Plugin(ABC):
23
+ """插件基类."""
24
+
25
+ @property
26
+ @abstractmethod
27
+ def metadata(self) -> PluginMetadata:
28
+ """返回插件元数据."""
29
+ ...
30
+
31
+ @abstractmethod
32
+ def register(self, registry: PluginRegistry) -> None:
33
+ """注册插件提供的命令和能力."""
34
+ ...
35
+
36
+ def initialize(self, context: PluginContext) -> None: # noqa: B027
37
+ """插件初始化钩子(可选覆盖)."""
38
+ # 默认实现:无需初始化操作
39
+
40
+ def shutdown(self) -> None: # noqa: B027
41
+ """插件关闭钩子(可选覆盖)."""
42
+ # 默认实现:无需清理操作
43
+
44
+
45
+ @dataclass
46
+ class PluginContext:
47
+ """插件上下文,提供插件运行所需的环境."""
48
+
49
+ workspace: Any = None
50
+ config: Any = None
51
+ registry: Any = None
52
+
53
+
54
+ @dataclass
55
+ class PluginRegistry:
56
+ """插件注册表."""
57
+
58
+ _plugins: dict[str, Plugin] = field(default_factory=dict)
59
+ _commands: dict[str, Callable[..., Any]] = field(default_factory=dict)
60
+
61
+ def register_plugin(self, plugin: Plugin) -> None:
62
+ """注册插件."""
63
+ meta = plugin.metadata
64
+ if meta.name in self._plugins:
65
+ raise ValueError(f"插件 {meta.name} 已注册")
66
+ self._plugins[meta.name] = plugin
67
+
68
+ def register_command(self, name: str, handler: Callable[..., Any]) -> None:
69
+ """注册命令."""
70
+ if name in self._commands:
71
+ raise ValueError(f"命令 {name} 已注册")
72
+ self._commands[name] = handler
73
+
74
+ def get_plugin(self, name: str) -> Plugin | None:
75
+ """获取插件."""
76
+ return self._plugins.get(name)
77
+
78
+ def get_command(self, name: str) -> Callable[..., Any] | None:
79
+ """获取命令处理器."""
80
+ return self._commands.get(name)
81
+
82
+ def list_plugins(self) -> list[str]:
83
+ """列出所有已注册的插件."""
84
+ return list(self._plugins.keys())
85
+
86
+ def list_commands(self) -> list[str]:
87
+ """列出所有已注册的命令."""
88
+ return list(self._commands.keys())
89
+
90
+ def load_plugins_from_directory(self, directory: str | Path) -> None:
91
+ """从目录加载插件."""
92
+ directory = Path(directory).expanduser()
93
+ if not directory.exists():
94
+ return
95
+
96
+ for plugin_file in directory.glob("*.py"):
97
+ self._load_plugin_module(plugin_file)
98
+
99
+ def _load_plugin_module(self, plugin_file: Path) -> None:
100
+ """加载单个插件模块."""
101
+ import sys
102
+
103
+ spec = importlib.util.spec_from_file_location(plugin_file.stem, plugin_file)
104
+ if spec is None or spec.loader is None:
105
+ return
106
+
107
+ module = importlib.util.module_from_spec(spec)
108
+ sys.modules[plugin_file.stem] = module
109
+ spec.loader.exec_module(module)
110
+
111
+ # 查找 Plugin 子类并实例化
112
+ for attr_name in dir(module):
113
+ attr = getattr(module, attr_name)
114
+ if isinstance(attr, type) and issubclass(attr, Plugin) and attr is not Plugin:
115
+ plugin = attr()
116
+ self.register_plugin(plugin)
117
+ plugin.register(self)
@@ -0,0 +1,76 @@
1
+ """工作空间管理模块."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+
8
+ class WorkspaceManager:
9
+ """工作空间管理器."""
10
+
11
+ def __init__(self, root: str | Path | None = None) -> None:
12
+ """初始化工作空间管理器.
13
+
14
+ Args:
15
+ root: 工作空间根路径
16
+ """
17
+ self._root = Path(root) if root else Path.cwd()
18
+ self._variables: dict[str, str] = {}
19
+
20
+ @property
21
+ def root(self) -> str:
22
+ """获取工作空间根路径."""
23
+ return str(self._root)
24
+
25
+ @root.setter
26
+ def root(self, value: str | Path) -> None:
27
+ """设置工作空间根路径."""
28
+ self._root = Path(value)
29
+
30
+ def set_var(self, name: str, value: str) -> None:
31
+ """设置工作空间变量.
32
+
33
+ Args:
34
+ name: 变量名
35
+ value: 变量值
36
+ """
37
+ self._variables[name] = value
38
+
39
+ def get_var(self, name: str, default: str = "") -> str:
40
+ """获取工作空间变量.
41
+
42
+ Args:
43
+ name: 变量名
44
+ default: 默认值
45
+
46
+ Returns:
47
+ 变量值
48
+ """
49
+ return self._variables.get(name, default)
50
+
51
+ def resolve_path(self, relative_path: str | Path) -> Path:
52
+ """解析相对于工作空间根目录的路径.
53
+
54
+ Args:
55
+ relative_path: 相对路径
56
+
57
+ Returns:
58
+ 绝对路径
59
+ """
60
+ path = Path(relative_path)
61
+ if path.is_absolute():
62
+ return path
63
+ return self._root / path
64
+
65
+ def ensure_dir(self, relative_path: str | Path) -> Path:
66
+ """确保目录存在.
67
+
68
+ Args:
69
+ relative_path: 相对路径
70
+
71
+ Returns:
72
+ 绝对路径
73
+ """
74
+ path = self.resolve_path(relative_path)
75
+ path.mkdir(parents=True, exist_ok=True)
76
+ return path
@@ -0,0 +1,3 @@
1
+ from .version import Version, VersionError, VersionPart
2
+
3
+ __all__ = ["Version", "VersionError", "VersionPart"]
@@ -0,0 +1,173 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+ from enum import Enum
6
+ from functools import lru_cache
7
+ from re import Pattern
8
+ from typing import Final
9
+
10
+ VERSION_PATTERN: Final[Pattern[str]] = re.compile(
11
+ r"(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?"
12
+ )
13
+
14
+
15
+ class VersionPart(Enum):
16
+ """可更新的版本号部分."""
17
+
18
+ MAJOR = "major"
19
+ MINOR = "minor"
20
+ PATCH = "patch"
21
+
22
+
23
+ class VersionError(Exception):
24
+ """版本号相关异常."""
25
+
26
+
27
+ @dataclass
28
+ class Version:
29
+ """语义化版本号, 遵循 SemVer 2.0.0 规范.
30
+
31
+ Attributes
32
+ ----------
33
+ major: 主版本号 (破坏性变更时递增)
34
+ minor: 次版本号 (向后兼容的功能新增时递增)
35
+ patch: 补丁号 (向后兼容的 bug 修复时递增)
36
+ prerelease: 预发布标识 (如 "alpha", "beta.1", "rc")
37
+ buildmetadata: 构建元数据 (不参与版本比较)
38
+ """
39
+
40
+ major: int
41
+ minor: int
42
+ patch: int
43
+ prerelease: str | None = None
44
+ buildmetadata: str | None = None
45
+
46
+ def __str__(self) -> str:
47
+ """返回版本号的字符串表示."""
48
+ version = f"{self.major}.{self.minor}.{self.patch}"
49
+ if self.prerelease:
50
+ version += f"-{self.prerelease}"
51
+ if self.buildmetadata:
52
+ version += f"+{self.buildmetadata}"
53
+ return version
54
+
55
+ @classmethod
56
+ @lru_cache(maxsize=128)
57
+ def parse(cls, version_string: str) -> Version:
58
+ """解析版本号字符串为 Version 对象.
59
+
60
+ Parameters
61
+ ----------
62
+ version_string : str
63
+ 版本号字符串
64
+
65
+ Returns
66
+ -------
67
+ Version
68
+ 解析后的 Version 对象
69
+
70
+ Raises
71
+ ------
72
+ VersionError
73
+ 版本号格式无效时抛出
74
+ """
75
+ if not version_string:
76
+ msg = "版本号字符串不能为空"
77
+ raise VersionError(msg)
78
+
79
+ match = VERSION_PATTERN.match(version_string)
80
+ if not match:
81
+ msg = f"无效的版本号格式: {version_string}"
82
+ raise VersionError(msg)
83
+
84
+ return cls(
85
+ major=int(match.group("major")),
86
+ minor=int(match.group("minor")),
87
+ patch=int(match.group("patch")),
88
+ prerelease=match.group("prerelease"),
89
+ buildmetadata=match.group("buildmetadata"),
90
+ )
91
+
92
+ def bump(
93
+ self,
94
+ part: VersionPart,
95
+ *,
96
+ reset_prerelease: bool = True,
97
+ prerelease: str | None = None,
98
+ ) -> Version:
99
+ """返回递增指定部分后的新版本号.
100
+
101
+ Parameters
102
+ ----------
103
+ part : VersionPart
104
+ 要递增的版本号部分 (major, minor, patch)
105
+ reset_prerelease : bool
106
+ 是否重置预发布标识, 默认是
107
+ prerelease : str | None
108
+ 可选的预发布标识, 默认 None
109
+
110
+ Returns
111
+ -------
112
+ Version
113
+ 递增后的新版本号
114
+ """
115
+ # 确定新的预发布标识值
116
+ new_prerelease = None
117
+ if prerelease is not None:
118
+ new_prerelease = prerelease
119
+ elif not reset_prerelease:
120
+ new_prerelease = self.prerelease
121
+
122
+ if part == VersionPart.MAJOR:
123
+ return Version(
124
+ major=self.major + 1,
125
+ minor=0,
126
+ patch=0,
127
+ prerelease=new_prerelease,
128
+ buildmetadata=None, # 总是重置构建元数据
129
+ )
130
+ if part == VersionPart.MINOR:
131
+ return Version(
132
+ major=self.major,
133
+ minor=self.minor + 1,
134
+ patch=0,
135
+ prerelease=new_prerelease,
136
+ buildmetadata=None,
137
+ )
138
+ if part == VersionPart.PATCH:
139
+ return Version(
140
+ major=self.major,
141
+ minor=self.minor,
142
+ patch=self.patch + 1,
143
+ prerelease=new_prerelease,
144
+ buildmetadata=None,
145
+ )
146
+
147
+ msg = f"不支持的版本号部分: {part}"
148
+ raise VersionError(msg)
149
+
150
+ def set_prerelease(self, prerelease: str) -> Version:
151
+ """返回设置预发布标识后的新版本号.
152
+
153
+ Parameters
154
+ ----------
155
+ prerelease : str
156
+ 预发布标识
157
+
158
+ Returns
159
+ -------
160
+ Version
161
+ 设置预发布标识后的新版本号
162
+ """
163
+ if not prerelease:
164
+ msg = "预发布标识不能为空"
165
+ raise VersionError(msg)
166
+
167
+ return Version(
168
+ major=self.major,
169
+ minor=self.minor,
170
+ patch=self.patch,
171
+ prerelease=prerelease,
172
+ buildmetadata=None,
173
+ )
@@ -0,0 +1 @@
1
+ """Scripts 工具模块."""