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.
- bitool/__init__.py +27 -0
- bitool/cmd/__init__.py +65 -0
- bitool/cmd/_base.py +105 -0
- bitool/cmd/_condition.py +60 -0
- bitool/cmd/_scheduler.py +548 -0
- bitool/cmd/env.py +454 -0
- bitool/cmd/git.py +123 -0
- bitool/cmd/io.py +248 -0
- bitool/cmd/pdf.py +385 -0
- bitool/cmd/run.py +300 -0
- bitool/cmd/toml.py +237 -0
- bitool/cmd/version.py +630 -0
- bitool/consts.py +14 -0
- bitool/core/__init__.py +7 -0
- bitool/core/app.py +142 -0
- bitool/core/commands.py +194 -0
- bitool/core/config.py +647 -0
- bitool/core/env.py +18 -0
- bitool/core/logger.py +237 -0
- bitool/core/plugin.py +117 -0
- bitool/core/workspace.py +76 -0
- bitool/models/__init__.py +3 -0
- bitool/models/version.py +173 -0
- bitool/scripts/__init__.py +1 -0
- bitool/scripts/bumpversion.py +189 -0
- bitool/scripts/clearscreen.py +37 -0
- bitool/scripts/envpy.py +161 -0
- bitool/scripts/envrs.py +119 -0
- bitool/scripts/filedate.py +246 -0
- bitool/scripts/filelevel.py +191 -0
- bitool/scripts/gittool.py +178 -0
- bitool/scripts/img2pdf.py +151 -0
- bitool/scripts/pdf2img.py +139 -0
- bitool/scripts/piptool.py +130 -0
- bitool/scripts/pymake.py +345 -0
- bitool/scripts/sshcopyid.py +491 -0
- bitool/scripts/taskkill.py +366 -0
- bitool/scripts/which.py +227 -0
- bitool/types.py +7 -0
- bitool/utils/__init__.py +9 -0
- bitool/utils/cli_parser.py +412 -0
- bitool/utils/executor.py +881 -0
- bitool/utils/profiler.py +369 -0
- bitool/utils/task.py +133 -0
- bitool/utils/task_group.py +668 -0
- bitool/utils/tests/__init__.py +0 -0
- bitool/utils/tests/test_profiler.py +487 -0
- bitool-0.1.2.dist-info/METADATA +154 -0
- bitool-0.1.2.dist-info/RECORD +51 -0
- bitool-0.1.2.dist-info/WHEEL +4 -0
- bitool-0.1.2.dist-info/entry_points.txt +15 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""图片转PDF脚本.
|
|
2
|
+
|
|
3
|
+
将多张图片合并成一个PDF文件, 支持多种图片格式和页面设置.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from bitool.cmd import CommandScheduler, ImagesToPdfCommand
|
|
13
|
+
from bitool.core import ConfigMixin, logger
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ImageToPdfConfig(ConfigMixin):
|
|
17
|
+
"""图片转PDF配置类."""
|
|
18
|
+
|
|
19
|
+
FORMAT: str = "pdf"
|
|
20
|
+
PAGE_SIZE: str = "auto"
|
|
21
|
+
VALID_IMAGE_FORMATS: set[str] = {"png", "jpg", "jpeg", "bmp", "gif", "tiff", "tif"} # noqa: RUF012
|
|
22
|
+
VALID_OUTPUT_FORMATS: set[str] = {"pdf"} # noqa: RUF012
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
conf = ImageToPdfConfig()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def create_scheduler(
|
|
29
|
+
image_paths: list[Path], output_path: Path, page_size: str = "auto"
|
|
30
|
+
) -> CommandScheduler:
|
|
31
|
+
"""创建图片转PDF命令调度器.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
image_paths: 输入图片文件路径列表
|
|
35
|
+
output_path: 输出PDF文件路径
|
|
36
|
+
page_size: 页面大小设置
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
命令调度器实例
|
|
40
|
+
"""
|
|
41
|
+
return CommandScheduler(
|
|
42
|
+
commands=[
|
|
43
|
+
ImagesToPdfCommand(
|
|
44
|
+
image_paths=image_paths, output_path=output_path, page_size=page_size
|
|
45
|
+
)
|
|
46
|
+
]
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def parse_args() -> argparse.Namespace:
|
|
51
|
+
"""解析命令行参数.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
解析后的参数命名空间
|
|
55
|
+
"""
|
|
56
|
+
parser = argparse.ArgumentParser(description="将图片转换为PDF")
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
"input_paths",
|
|
59
|
+
type=str,
|
|
60
|
+
nargs="*",
|
|
61
|
+
default=None,
|
|
62
|
+
help="输入图片文件路径(可选, 默认搜索当前目录)",
|
|
63
|
+
)
|
|
64
|
+
parser.add_argument(
|
|
65
|
+
"output_path", type=str, nargs="?", default=None, help="输出PDF文件路径"
|
|
66
|
+
)
|
|
67
|
+
parser.add_argument(
|
|
68
|
+
"--page-size",
|
|
69
|
+
type=str,
|
|
70
|
+
default=conf.PAGE_SIZE,
|
|
71
|
+
help="页面大小 (auto, A4, letter等)",
|
|
72
|
+
)
|
|
73
|
+
return parser.parse_args()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def main() -> None:
|
|
77
|
+
"""图片转PDF主函数."""
|
|
78
|
+
args = parse_args()
|
|
79
|
+
|
|
80
|
+
# 验证输入路径
|
|
81
|
+
if args.input_paths:
|
|
82
|
+
input_paths = [Path(p) for p in args.input_paths]
|
|
83
|
+
else:
|
|
84
|
+
# 自动搜索当前目录下的图片文件
|
|
85
|
+
current_dir = Path.cwd()
|
|
86
|
+
input_paths = []
|
|
87
|
+
for ext in conf.VALID_IMAGE_FORMATS:
|
|
88
|
+
input_paths.extend(current_dir.glob(f"*.{ext}"))
|
|
89
|
+
input_paths.extend(current_dir.glob(f"*.{ext.upper()}"))
|
|
90
|
+
|
|
91
|
+
if not input_paths:
|
|
92
|
+
logger.error(f"在当前目录 {current_dir} 中未找到图片文件")
|
|
93
|
+
sys.exit(1)
|
|
94
|
+
|
|
95
|
+
# 去重并排序
|
|
96
|
+
input_paths = sorted(set(input_paths))
|
|
97
|
+
logger.info(f"在当前目录 {current_dir} 中找到 {len(input_paths)} 个图片文件")
|
|
98
|
+
logger.info(f"将处理: {', '.join([str(p.name) for p in input_paths])}")
|
|
99
|
+
|
|
100
|
+
for input_path in input_paths:
|
|
101
|
+
if not input_path.exists():
|
|
102
|
+
logger.error(f"未找到输入文件: {input_path}")
|
|
103
|
+
sys.exit(1)
|
|
104
|
+
# 验证图片格式
|
|
105
|
+
if input_path.suffix.lower().lstrip(".") not in conf.VALID_IMAGE_FORMATS:
|
|
106
|
+
logger.error(
|
|
107
|
+
f"不支持的图片格式: {input_path.suffix}, 请选择 {conf.VALID_IMAGE_FORMATS}"
|
|
108
|
+
)
|
|
109
|
+
sys.exit(1)
|
|
110
|
+
|
|
111
|
+
# 确定输出路径
|
|
112
|
+
output_path = Path(args.output_path) if args.output_path else None
|
|
113
|
+
if output_path is None:
|
|
114
|
+
# 默认输出到第一个图片所在目录, 命名为 output.pdf
|
|
115
|
+
output_path = input_paths[0].parent / "output.pdf"
|
|
116
|
+
|
|
117
|
+
# 确保输出目录存在
|
|
118
|
+
if not output_path.parent.exists():
|
|
119
|
+
output_path.parent.mkdir(parents=True)
|
|
120
|
+
logger.info(f"创建输出目录: {output_path.parent}")
|
|
121
|
+
|
|
122
|
+
# 验证输出格式
|
|
123
|
+
if output_path.suffix.lower().lstrip(".") not in conf.VALID_OUTPUT_FORMATS:
|
|
124
|
+
logger.error(
|
|
125
|
+
f"不支持的输出格式: {output_path.suffix}, 请选择 {conf.VALID_OUTPUT_FORMATS}"
|
|
126
|
+
)
|
|
127
|
+
sys.exit(1)
|
|
128
|
+
|
|
129
|
+
logger.info(
|
|
130
|
+
f"开始转换: {len(input_paths)} 张图片 -> {output_path} (页面大小={args.page_size})"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
scheduler = create_scheduler(
|
|
135
|
+
image_paths=input_paths, output_path=output_path, page_size=args.page_size
|
|
136
|
+
)
|
|
137
|
+
success = scheduler.run()
|
|
138
|
+
|
|
139
|
+
if success:
|
|
140
|
+
logger.info("图片转PDF完成")
|
|
141
|
+
sys.exit(0)
|
|
142
|
+
else:
|
|
143
|
+
logger.error("图片转PDF失败")
|
|
144
|
+
sys.exit(1)
|
|
145
|
+
|
|
146
|
+
except KeyboardInterrupt:
|
|
147
|
+
logger.warning("用户中断操作")
|
|
148
|
+
sys.exit(130)
|
|
149
|
+
except Exception as e: # noqa: BLE001
|
|
150
|
+
logger.error(f"转换过程发生错误: {e}")
|
|
151
|
+
sys.exit(1)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""PDF转图片脚本.
|
|
2
|
+
|
|
3
|
+
将PDF文件的每一页转换为图片格式, 支持多种输出格式和分辨率设置.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from bitool.cmd import CommandScheduler, PdfToImagesCommand
|
|
13
|
+
from bitool.core import ConfigMixin, logger
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PdfToImageConfig(ConfigMixin):
|
|
17
|
+
"""PDF转图片配置类."""
|
|
18
|
+
|
|
19
|
+
DPI: int = 150
|
|
20
|
+
DPI_RANGE: tuple[int, int] = (72, 300)
|
|
21
|
+
FORMAT: str = "png"
|
|
22
|
+
QUALITY: int = 90
|
|
23
|
+
QUALITY_RANGE: tuple[int, int] = (1, 100)
|
|
24
|
+
VALID_FORMATS: set[str] = {"png", "jpg", "jpeg"} # noqa: RUF012
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
conf = PdfToImageConfig()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def create_scheduler(
|
|
31
|
+
input_path: Path, output_dir: Path, dpi: int, fmt: str = "png"
|
|
32
|
+
) -> CommandScheduler:
|
|
33
|
+
"""创建PDF转图片命令调度器.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
input_path: 输入PDF文件路径
|
|
37
|
+
output_dir: 输出图片目录
|
|
38
|
+
dpi: 图片分辨率
|
|
39
|
+
fmt: 图片格式
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
命令调度器实例
|
|
43
|
+
"""
|
|
44
|
+
return CommandScheduler(
|
|
45
|
+
commands=[
|
|
46
|
+
PdfToImagesCommand(
|
|
47
|
+
input_path=input_path, output_dir=output_dir, dpi=dpi, fmt=fmt
|
|
48
|
+
)
|
|
49
|
+
]
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def parse_args() -> argparse.Namespace:
|
|
54
|
+
parser = argparse.ArgumentParser(description="将PDF转换为图片")
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
"input_path",
|
|
57
|
+
type=str,
|
|
58
|
+
nargs="?",
|
|
59
|
+
default=None,
|
|
60
|
+
help="输入PDF文件路径(可选, 默认搜索当前目录)",
|
|
61
|
+
)
|
|
62
|
+
parser.add_argument(
|
|
63
|
+
"output_dir", type=str, nargs="?", default=None, help="输出目录"
|
|
64
|
+
)
|
|
65
|
+
parser.add_argument("--format", type=str, default=conf.FORMAT, help="输出图片格式")
|
|
66
|
+
parser.add_argument("--dpi", type=int, default=conf.DPI, help="图片分辨率(DPI)")
|
|
67
|
+
parser.add_argument("--quality", type=int, default=conf.QUALITY, help="图片质量")
|
|
68
|
+
return parser.parse_args()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def main() -> None:
|
|
72
|
+
"""PDF转图片主函数."""
|
|
73
|
+
args = parse_args()
|
|
74
|
+
|
|
75
|
+
# 验证输入路径
|
|
76
|
+
input_path = Path(args.input_path) if args.input_path else Path.cwd()
|
|
77
|
+
|
|
78
|
+
# 如果输入路径是目录, 搜索该目录下的所有PDF文件
|
|
79
|
+
if input_path.is_dir():
|
|
80
|
+
pdf_files = list(input_path.glob("*.pdf")) + list(input_path.glob("*.PDF"))
|
|
81
|
+
if not pdf_files:
|
|
82
|
+
logger.error(f"在目录 {input_path} 中未找到PDF文件")
|
|
83
|
+
sys.exit(1)
|
|
84
|
+
logger.info(f"在目录 {input_path} 中找到 {len(pdf_files)} 个PDF文件")
|
|
85
|
+
# 处理第一个找到的PDF文件
|
|
86
|
+
input_path = pdf_files[0]
|
|
87
|
+
logger.info(f"将处理: {input_path}")
|
|
88
|
+
|
|
89
|
+
if not input_path.exists():
|
|
90
|
+
logger.error(f"未找到输入路径: {input_path}")
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
|
|
93
|
+
# 确定输出目录
|
|
94
|
+
output_dir = Path(args.output_dir) if args.output_dir else None
|
|
95
|
+
if output_dir is None:
|
|
96
|
+
output_dir = input_path.parent / "converted_images"
|
|
97
|
+
|
|
98
|
+
if not output_dir.exists():
|
|
99
|
+
output_dir.mkdir(parents=True)
|
|
100
|
+
logger.info(f"创建输出目录: {output_dir}")
|
|
101
|
+
|
|
102
|
+
# 验证参数范围
|
|
103
|
+
if not conf.QUALITY_RANGE[0] <= args.quality <= conf.QUALITY_RANGE[1]:
|
|
104
|
+
logger.error(
|
|
105
|
+
f"图片质量必须在 {conf.QUALITY_RANGE[0]} 到 {conf.QUALITY_RANGE[1]} 之间"
|
|
106
|
+
)
|
|
107
|
+
sys.exit(1)
|
|
108
|
+
|
|
109
|
+
if not conf.DPI_RANGE[0] <= args.dpi <= conf.DPI_RANGE[1]:
|
|
110
|
+
logger.error(f"DPI必须在 {conf.DPI_RANGE[0]} 到 {conf.DPI_RANGE[1]} 之间")
|
|
111
|
+
sys.exit(1)
|
|
112
|
+
|
|
113
|
+
if args.format not in conf.VALID_FORMATS:
|
|
114
|
+
logger.error(f"无效的图片格式, 请选择 {conf.VALID_FORMATS}")
|
|
115
|
+
sys.exit(1)
|
|
116
|
+
|
|
117
|
+
logger.info(
|
|
118
|
+
f"开始转换: {input_path} -> {output_dir} (DPI={args.dpi}, 格式={args.format})"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
scheduler = create_scheduler(
|
|
123
|
+
input_path=input_path, output_dir=output_dir, dpi=args.dpi, fmt=args.format
|
|
124
|
+
)
|
|
125
|
+
success = scheduler.run()
|
|
126
|
+
|
|
127
|
+
if success:
|
|
128
|
+
logger.info("PDF转图片完成")
|
|
129
|
+
sys.exit(0)
|
|
130
|
+
else:
|
|
131
|
+
logger.error("PDF转图片失败")
|
|
132
|
+
sys.exit(1)
|
|
133
|
+
|
|
134
|
+
except KeyboardInterrupt:
|
|
135
|
+
logger.warning("用户中断操作")
|
|
136
|
+
sys.exit(130)
|
|
137
|
+
except Exception as e: # noqa: BLE001
|
|
138
|
+
logger.error(f"转换过程发生错误: {e}")
|
|
139
|
+
sys.exit(1)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""pip工具模块。
|
|
2
|
+
|
|
3
|
+
提供pip包管理操作的封装,支持安装、重新安装和下载功能。
|
|
4
|
+
支持在线和离线两种模式。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
from typing import Final
|
|
11
|
+
|
|
12
|
+
from ..cmd import CommandScheduler, RunCommand, RunShellCommand
|
|
13
|
+
from ..core import ConfigMixin
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PipToolConfig(ConfigMixin):
|
|
17
|
+
"""pip工具配置类。"""
|
|
18
|
+
|
|
19
|
+
TRUSTED_HOST: Final[list[str]] = [
|
|
20
|
+
"--trusted-host",
|
|
21
|
+
"mirrors.aliyun.com",
|
|
22
|
+
"--index-url",
|
|
23
|
+
"http://mirrors.aliyun.com/pypi/simple/",
|
|
24
|
+
]
|
|
25
|
+
PACKAGE_DIR: Final[str] = "packages"
|
|
26
|
+
REQUIREMENTS_FILE: Final[str] = "requirements.txt"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
conf = PipToolConfig()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def create_scheduler_map(
|
|
33
|
+
libnames: list[str] | None = None,
|
|
34
|
+
*,
|
|
35
|
+
use_requirements: bool = False,
|
|
36
|
+
offline: bool = False,
|
|
37
|
+
) -> dict[str, CommandScheduler]:
|
|
38
|
+
"""创建pip操作的命令调度器映射。
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
libnames: 要安装的库名称列表。
|
|
42
|
+
use_requirements: 是否使用requirements文件。
|
|
43
|
+
offline: 是否使用离线模式。
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
命令调度器的字典映射。
|
|
47
|
+
"""
|
|
48
|
+
# 修复类型注解冲突:使用新变量名
|
|
49
|
+
pkg_names: list[str] = [] if libnames is None else libnames
|
|
50
|
+
options: list[str] = (
|
|
51
|
+
["--no-index", "--find-links", "."] if offline else conf.TRUSTED_HOST.copy()
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if use_requirements:
|
|
55
|
+
options.extend(["-r", conf.REQUIREMENTS_FILE])
|
|
56
|
+
else:
|
|
57
|
+
options.extend(pkg_names)
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
# 安装
|
|
61
|
+
"i": CommandScheduler(commands=[RunCommand(cmd=["pip", "install", *options])]),
|
|
62
|
+
# 重新安装
|
|
63
|
+
"r": CommandScheduler(
|
|
64
|
+
commands=[
|
|
65
|
+
RunCommand(cmd=["pip", "uninstall", "-y", *pkg_names]),
|
|
66
|
+
RunCommand(cmd=["pip", "install", *options]),
|
|
67
|
+
]
|
|
68
|
+
),
|
|
69
|
+
# 下载
|
|
70
|
+
"d": CommandScheduler(
|
|
71
|
+
commands=[
|
|
72
|
+
RunCommand(cmd=["pip", "download", *options, "-d", conf.PACKAGE_DIR])
|
|
73
|
+
]
|
|
74
|
+
),
|
|
75
|
+
# 冻结依赖
|
|
76
|
+
"f": CommandScheduler(
|
|
77
|
+
commands=[
|
|
78
|
+
RunShellCommand(
|
|
79
|
+
shell_cmd=f"pip freeze --exclude-editable > {conf.REQUIREMENTS_FILE}"
|
|
80
|
+
)
|
|
81
|
+
]
|
|
82
|
+
),
|
|
83
|
+
# 卸载
|
|
84
|
+
"u": CommandScheduler(
|
|
85
|
+
commands=[RunCommand(cmd=["pip", "uninstall", "-y", *pkg_names])]
|
|
86
|
+
),
|
|
87
|
+
# 升级pip
|
|
88
|
+
"up": CommandScheduler(
|
|
89
|
+
commands=[
|
|
90
|
+
RunCommand(cmd=["python", "-m", "pip", "install", "--upgrade", "pip"])
|
|
91
|
+
]
|
|
92
|
+
),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
OPTIONS: list[str] = list(create_scheduler_map().keys())
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def parse_args() -> argparse.Namespace:
|
|
100
|
+
"""解析命令行参数。
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
解析后的参数命名空间。
|
|
104
|
+
"""
|
|
105
|
+
parser = argparse.ArgumentParser(description="pip安装工具")
|
|
106
|
+
parser.add_argument("action", choices=OPTIONS, help="要执行的操作")
|
|
107
|
+
parser.add_argument("libnames", nargs="*", help="要安装的库名称")
|
|
108
|
+
parser.add_argument("--offline", "-o", action="store_true", help="离线模式")
|
|
109
|
+
parser.add_argument(
|
|
110
|
+
"--requirements", "-r", action="store_true", help="使用requirements文件"
|
|
111
|
+
)
|
|
112
|
+
return parser.parse_args()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def main() -> None:
|
|
116
|
+
"""主函数,执行pip工具命令。"""
|
|
117
|
+
args = parse_args()
|
|
118
|
+
|
|
119
|
+
# 验证参数:使用requirements模式时不需要指定库名
|
|
120
|
+
if args.action in ["i", "r", "d"] and not args.libnames and not args.requirements:
|
|
121
|
+
raise ValueError("请指定要安装的库名称,或使用 --requirements 参数")
|
|
122
|
+
|
|
123
|
+
scheduler_map: dict[str, CommandScheduler] = create_scheduler_map(
|
|
124
|
+
args.libnames, offline=args.offline, use_requirements=args.requirements
|
|
125
|
+
)
|
|
126
|
+
scheduler: CommandScheduler | None = scheduler_map.get(args.action)
|
|
127
|
+
if not scheduler:
|
|
128
|
+
raise ValueError(f"无效的操作: {args.action}")
|
|
129
|
+
|
|
130
|
+
scheduler.run()
|