mcpywrap 0.1.0__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.
- mcpywrap/__init__.py +4 -0
- mcpywrap/__main__.py +13 -0
- mcpywrap/builders/AddonsPack.py +226 -0
- mcpywrap/builders/__init__.py +10 -0
- mcpywrap/builders/file_handler.py +112 -0
- mcpywrap/builders/file_merge.py +49 -0
- mcpywrap/builders/project_builder.py +157 -0
- mcpywrap/builders/watcher.py +107 -0
- mcpywrap/cli.py +34 -0
- mcpywrap/commands/__init__.py +25 -0
- mcpywrap/commands/add_cmd.py +39 -0
- mcpywrap/commands/build_cmd.py +58 -0
- mcpywrap/commands/default_cmd.py +70 -0
- mcpywrap/commands/dev_cmd.py +111 -0
- mcpywrap/commands/init_cmd.py +202 -0
- mcpywrap/commands/mod_cmd.py +24 -0
- mcpywrap/commands/modsdk_cmd.py +65 -0
- mcpywrap/commands/publish_cmd.py +45 -0
- mcpywrap/commands/remove_cmd.py +42 -0
- mcpywrap/config.py +109 -0
- mcpywrap/custom_packaging.py +41 -0
- mcpywrap/minecraft/__init__.py +1 -0
- mcpywrap/minecraft/addons.py +157 -0
- mcpywrap/minecraft/config.py +6 -0
- mcpywrap/minecraft/mod_template.py +52 -0
- mcpywrap/minecraft/netease_modsdk.py +269 -0
- mcpywrap/minecraft/template/generate_mod_files.py +114 -0
- mcpywrap/minecraft/template/mod_template.py +459 -0
- mcpywrap/utils/__init__.py +6 -0
- mcpywrap/utils/print_guide.py +21 -0
- mcpywrap/utils/project_setup.py +112 -0
- mcpywrap/utils/py3to2_util.py +53 -0
- mcpywrap/utils/utils.py +34 -0
- mcpywrap-0.1.0.dist-info/METADATA +97 -0
- mcpywrap-0.1.0.dist-info/RECORD +38 -0
- mcpywrap-0.1.0.dist-info/WHEEL +5 -0
- mcpywrap-0.1.0.dist-info/entry_points.txt +3 -0
- mcpywrap-0.1.0.dist-info/top_level.txt +1 -0
mcpywrap/__init__.py
ADDED
mcpywrap/__main__.py
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import click
|
|
6
|
+
from .file_handler import ensure_dir, try_merge_file
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Python 包管理和其他应该忽略的文件和目录
|
|
10
|
+
EXCLUDED_PATTERNS = [
|
|
11
|
+
# Python 包管理
|
|
12
|
+
".egg-info",
|
|
13
|
+
"__pycache__",
|
|
14
|
+
".pyc",
|
|
15
|
+
".pyo",
|
|
16
|
+
".pyd",
|
|
17
|
+
".so",
|
|
18
|
+
".dll",
|
|
19
|
+
".eggs",
|
|
20
|
+
".pytest_cache",
|
|
21
|
+
".tox",
|
|
22
|
+
".coverage",
|
|
23
|
+
".coverage.*",
|
|
24
|
+
"htmlcov",
|
|
25
|
+
# 版本控制
|
|
26
|
+
".git",
|
|
27
|
+
".hg",
|
|
28
|
+
".svn",
|
|
29
|
+
".bzr",
|
|
30
|
+
# 其他临时文件
|
|
31
|
+
".DS_Store",
|
|
32
|
+
"Thumbs.db"
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
MANIFEST_FILES = [
|
|
36
|
+
"manifest.json",
|
|
37
|
+
"pack_manifest.json"
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AddonsPack(object):
|
|
42
|
+
|
|
43
|
+
pkg_name: str
|
|
44
|
+
path: str
|
|
45
|
+
is_origin: bool
|
|
46
|
+
behavior_pack_dir: str
|
|
47
|
+
resource_pack_dir: str
|
|
48
|
+
|
|
49
|
+
def __init__(self, pkg_name, path, is_origin=False):
|
|
50
|
+
self.pkg_name = pkg_name
|
|
51
|
+
self.path = path
|
|
52
|
+
self.is_origin = is_origin
|
|
53
|
+
self.behavior_pack_dir = None
|
|
54
|
+
self.resource_pack_dir = None
|
|
55
|
+
# 进入此目录,查找内部的行为包和资源包的路径
|
|
56
|
+
os.chdir(self.path)
|
|
57
|
+
for item in os.listdir(self.path):
|
|
58
|
+
item_path = os.path.join(self.path, item)
|
|
59
|
+
if os.path.isdir(item_path):
|
|
60
|
+
if item.startswith("behavior_pack") or item.startswith("BehaviorPack"):
|
|
61
|
+
self.behavior_pack_dir = item_path
|
|
62
|
+
elif item.startswith("resource_pack") or item.startswith("ResourcePack"):
|
|
63
|
+
self.resource_pack_dir = item_path
|
|
64
|
+
if not self.behavior_pack_dir:
|
|
65
|
+
self.behavior_pack_dir = os.path.join(self.path, "behavior_pack")
|
|
66
|
+
if not self.resource_pack_dir:
|
|
67
|
+
self.resource_pack_dir = os.path.join(self.path, "resource_pack")
|
|
68
|
+
|
|
69
|
+
def should_exclude(self, path):
|
|
70
|
+
"""判断文件或目录是否应该被排除"""
|
|
71
|
+
for pattern in EXCLUDED_PATTERNS:
|
|
72
|
+
if pattern in path:
|
|
73
|
+
return True
|
|
74
|
+
# 得到文件名
|
|
75
|
+
filename = os.path.basename(path)
|
|
76
|
+
if not self.is_origin and filename in MANIFEST_FILES:
|
|
77
|
+
return True
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
def copy_behavior_to(self, target_dir: str):
|
|
81
|
+
"""复制行为包和资源包到目标目录"""
|
|
82
|
+
if self.behavior_pack_dir:
|
|
83
|
+
target_path = os.path.join(target_dir, os.path.basename(self.behavior_pack_dir))
|
|
84
|
+
os.makedirs(target_path, exist_ok=True)
|
|
85
|
+
|
|
86
|
+
# 使用自定义复制函数而不是shutil.copytree
|
|
87
|
+
for root, dirs, files in os.walk(self.behavior_pack_dir):
|
|
88
|
+
# 过滤掉应该排除的目录
|
|
89
|
+
dirs[:] = [d for d in dirs if not self.should_exclude(os.path.join(root, d))]
|
|
90
|
+
|
|
91
|
+
# 计算相对路径
|
|
92
|
+
rel_path = os.path.relpath(root, self.behavior_pack_dir)
|
|
93
|
+
# 计算目标目录
|
|
94
|
+
target_root = os.path.join(target_path, rel_path) if rel_path != '.' else target_path
|
|
95
|
+
ensure_dir(target_root)
|
|
96
|
+
|
|
97
|
+
# 复制文件
|
|
98
|
+
for file in files:
|
|
99
|
+
src_file = os.path.join(root, file)
|
|
100
|
+
if not self.should_exclude(src_file):
|
|
101
|
+
dest_file = os.path.join(target_root, file)
|
|
102
|
+
# 如果是Python文件,检查并添加编码声明
|
|
103
|
+
if file.endswith('.py'):
|
|
104
|
+
self._copy_with_encoding_check(src_file, dest_file)
|
|
105
|
+
else:
|
|
106
|
+
shutil.copy2(src_file, dest_file)
|
|
107
|
+
|
|
108
|
+
def copy_resource_to(self, target_dir: str):
|
|
109
|
+
"""复制资源包到目标目录"""
|
|
110
|
+
if self.resource_pack_dir:
|
|
111
|
+
target_path = os.path.join(target_dir, os.path.basename(self.resource_pack_dir))
|
|
112
|
+
os.makedirs(target_path, exist_ok=True)
|
|
113
|
+
|
|
114
|
+
# 使用自定义复制函数而不是shutil.copytree
|
|
115
|
+
for root, dirs, files in os.walk(self.resource_pack_dir):
|
|
116
|
+
# 过滤掉应该排除的目录
|
|
117
|
+
dirs[:] = [d for d in dirs if not self.should_exclude(os.path.join(root, d))]
|
|
118
|
+
|
|
119
|
+
# 计算相对路径
|
|
120
|
+
rel_path = os.path.relpath(root, self.resource_pack_dir)
|
|
121
|
+
# 计算目标目录
|
|
122
|
+
target_root = os.path.join(target_path, rel_path) if rel_path != '.' else target_path
|
|
123
|
+
ensure_dir(target_root)
|
|
124
|
+
|
|
125
|
+
# 复制文件
|
|
126
|
+
for file in files:
|
|
127
|
+
src_file = os.path.join(root, file)
|
|
128
|
+
if not self.should_exclude(src_file):
|
|
129
|
+
dest_file = os.path.join(target_root, file)
|
|
130
|
+
# 如果是Python文件,检查并添加编码声明
|
|
131
|
+
if file.endswith('.py'):
|
|
132
|
+
self._copy_with_encoding_check(src_file, dest_file)
|
|
133
|
+
else:
|
|
134
|
+
shutil.copy2(src_file, dest_file)
|
|
135
|
+
|
|
136
|
+
def merge_behavior_into(self, target_behavior_dir: str):
|
|
137
|
+
"""合并行为包到目标行为包目录"""
|
|
138
|
+
if self.behavior_pack_dir:
|
|
139
|
+
for root, dirs, files in os.walk(self.behavior_pack_dir):
|
|
140
|
+
# 过滤掉应该排除的目录
|
|
141
|
+
dirs[:] = [d for d in dirs if not self.should_exclude(os.path.join(root, d))]
|
|
142
|
+
|
|
143
|
+
# 计算相对路径
|
|
144
|
+
rel_path = os.path.relpath(root, self.behavior_pack_dir)
|
|
145
|
+
# 计算目标目录
|
|
146
|
+
target_root = os.path.join(target_behavior_dir, rel_path) if rel_path != '.' else target_behavior_dir
|
|
147
|
+
ensure_dir(target_root)
|
|
148
|
+
|
|
149
|
+
# 复制文件
|
|
150
|
+
for file in files:
|
|
151
|
+
src_file = os.path.join(root, file)
|
|
152
|
+
if self.should_exclude(src_file):
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
dest_file = os.path.join(target_root, file)
|
|
156
|
+
|
|
157
|
+
if file.endswith('.py'): # 如果是Python文件,直接无脑覆盖
|
|
158
|
+
self._copy_with_encoding_check(src_file, dest_file)
|
|
159
|
+
elif os.path.exists(dest_file): # 如果文件已存在,尝试合并
|
|
160
|
+
suc, reason = try_merge_file(src_file, dest_file)
|
|
161
|
+
if not suc:
|
|
162
|
+
click.secho(f"❌ 未处理的文件冲突: {src_file} -> {dest_file} {reason}", fg="red")
|
|
163
|
+
else:
|
|
164
|
+
shutil.copy2(src_file, dest_file)
|
|
165
|
+
|
|
166
|
+
def merge_resource_into(self, target_resource_dir: str):
|
|
167
|
+
"""合并资源包到目标资源包目录"""
|
|
168
|
+
if self.resource_pack_dir:
|
|
169
|
+
for root, dirs, files in os.walk(self.resource_pack_dir):
|
|
170
|
+
# 过滤掉应该排除的目录
|
|
171
|
+
dirs[:] = [d for d in dirs if not self.should_exclude(os.path.join(root, d))]
|
|
172
|
+
|
|
173
|
+
# 计算相对路径
|
|
174
|
+
rel_path = os.path.relpath(root, self.resource_pack_dir)
|
|
175
|
+
# 计算目标目录
|
|
176
|
+
target_root = os.path.join(target_resource_dir, rel_path) if rel_path != '.' else target_resource_dir
|
|
177
|
+
ensure_dir(target_root)
|
|
178
|
+
|
|
179
|
+
# 复制文件
|
|
180
|
+
for file in files:
|
|
181
|
+
src_file = os.path.join(root, file)
|
|
182
|
+
if self.should_exclude(src_file):
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
dest_file = os.path.join(target_root, file)
|
|
186
|
+
# 处理文件冲突
|
|
187
|
+
if file.endswith('.py'):
|
|
188
|
+
self._copy_with_encoding_check(src_file, dest_file)
|
|
189
|
+
elif os.path.exists(dest_file):
|
|
190
|
+
suc, reason = try_merge_file(src_file, dest_file)
|
|
191
|
+
if not suc:
|
|
192
|
+
click.secho(f"⚠️ 警告: 文件合并异常 {src_file} -> {dest_file} {reason}", fg="yellow")
|
|
193
|
+
# 如果是Python文件,检查并添加编码声明
|
|
194
|
+
if file.endswith('.py'):
|
|
195
|
+
self._copy_with_encoding_check(src_file, dest_file)
|
|
196
|
+
else:
|
|
197
|
+
shutil.copy2(src_file, dest_file)
|
|
198
|
+
else:
|
|
199
|
+
shutil.copy2(src_file, dest_file)
|
|
200
|
+
|
|
201
|
+
def _copy_with_encoding_check(self, src_file, dest_file):
|
|
202
|
+
"""复制Python文件,并检查添加编码声明"""
|
|
203
|
+
try:
|
|
204
|
+
with open(src_file, 'r', encoding='utf-8') as f:
|
|
205
|
+
content = f.read()
|
|
206
|
+
|
|
207
|
+
# 检查是否有编码声明
|
|
208
|
+
has_coding = False
|
|
209
|
+
first_line = content.splitlines()[0] if content.splitlines() else ""
|
|
210
|
+
if "# -*- coding: utf-8 -*-" in first_line or "# coding: utf-8" in first_line:
|
|
211
|
+
has_coding = True
|
|
212
|
+
|
|
213
|
+
# 如果没有编码声明,则添加
|
|
214
|
+
if not has_coding:
|
|
215
|
+
content = "# -*- coding: utf-8 -*-\n" + content
|
|
216
|
+
|
|
217
|
+
# 写入目标文件
|
|
218
|
+
with open(dest_file, 'w', encoding='utf-8') as f:
|
|
219
|
+
f.write(content)
|
|
220
|
+
|
|
221
|
+
# 复制文件元数据
|
|
222
|
+
shutil.copystat(src_file, dest_file)
|
|
223
|
+
except Exception as e:
|
|
224
|
+
click.secho(f"⚠️ 添加编码声明时出错: {src_file} -> {dest_file}: {str(e)}", fg="yellow")
|
|
225
|
+
# 如果出错,则直接复制
|
|
226
|
+
shutil.copy2(src_file, dest_file)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
构建模块包,提供项目构建、文件处理和监控功能
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .project_builder import build_project
|
|
7
|
+
from .file_handler import process_file, is_python_file
|
|
8
|
+
from .watcher import FileWatcher
|
|
9
|
+
|
|
10
|
+
__all__ = ['build_project', 'process_file', 'is_python_file', 'FileWatcher']
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
文件处理模块 - 负责单个文件的复制和转换
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
|
|
9
|
+
from ..utils.py3to2_util import py3_to_2
|
|
10
|
+
from ..utils.utils import ensure_dir, run_command
|
|
11
|
+
from .file_merge import try_merge_file
|
|
12
|
+
|
|
13
|
+
def is_python_file(file_path):
|
|
14
|
+
"""判断是否为Python文件"""
|
|
15
|
+
return file_path.endswith('.py')
|
|
16
|
+
|
|
17
|
+
def copy_file(src_path, dest_path):
|
|
18
|
+
"""复制文件,确保目标目录存在"""
|
|
19
|
+
dest_dir = os.path.dirname(dest_path)
|
|
20
|
+
ensure_dir(dest_dir)
|
|
21
|
+
print(f"复制文件: {src_path} 到 {dest_path}")
|
|
22
|
+
shutil.copy2(src_path, dest_path)
|
|
23
|
+
return dest_path
|
|
24
|
+
|
|
25
|
+
def convert_py3_to_py2(file_path):
|
|
26
|
+
"""将某个Python文件转换为Python 2"""
|
|
27
|
+
try:
|
|
28
|
+
# 首先尝试使用直接的Python API调用
|
|
29
|
+
from lib3to2.main import main
|
|
30
|
+
# main函数接受包名和参数列表
|
|
31
|
+
# 第一个参数是包名 'lib3to2' (这是3to2所有修复器的位置)
|
|
32
|
+
# 第二个参数是命令行参数列表
|
|
33
|
+
exit_code = main('lib3to2.fixes', ['-w', '-n', '--no-diffs', file_path, '--nofix=metaclass'])
|
|
34
|
+
# exit_code = py3_to_2(file_path)
|
|
35
|
+
return exit_code == 0, "转换完成" if exit_code == 0 else f"转换失败,错误代码: {exit_code}"
|
|
36
|
+
except Exception as e:
|
|
37
|
+
# 如果直接调用失败,则尝试命令行方式(作为备选)
|
|
38
|
+
try:
|
|
39
|
+
# 方法1:直接命令行调用
|
|
40
|
+
success, output = run_command(["3to2", "-w", "-n", file_path])
|
|
41
|
+
if not success:
|
|
42
|
+
# 方法2:使用shell=True参数
|
|
43
|
+
success, output = run_command(["3to2", "-w", "-n", file_path], shell=True)
|
|
44
|
+
|
|
45
|
+
return success, output
|
|
46
|
+
except Exception as cmd_e:
|
|
47
|
+
return False, f"Python API调用失败: {str(e)}\n命令行调用也失败: {str(cmd_e)}"
|
|
48
|
+
|
|
49
|
+
def process_file(src_path, source_dir, target_dir, is_dependency=False, dependency_name=None):
|
|
50
|
+
"""处理单个文件(复制并根据文件类型处理)"""
|
|
51
|
+
# 计算相对路径和目标路径
|
|
52
|
+
rel_path = os.path.relpath(src_path, source_dir)
|
|
53
|
+
|
|
54
|
+
# 如果是依赖项,需要特殊处理目标路径
|
|
55
|
+
if is_dependency:
|
|
56
|
+
# 确定是属于行为包还是资源包
|
|
57
|
+
if "behavior_pack" in src_path.lower() or "behaviorpack" in src_path.lower():
|
|
58
|
+
# 在路径中定位behavior_pack部分
|
|
59
|
+
parts = src_path.split(os.sep)
|
|
60
|
+
for i, part in enumerate(parts):
|
|
61
|
+
if "behavior_pack" in part.lower() or "behaviorpack" in part.lower():
|
|
62
|
+
# 获取behavior_pack后的相对路径
|
|
63
|
+
sub_path = os.path.join(*parts[i+1:])
|
|
64
|
+
# 目标路径应该是behavior_pack目录
|
|
65
|
+
for item in os.listdir(target_dir):
|
|
66
|
+
if "behavior_pack" in item.lower() or "behaviorpack" in item.lower():
|
|
67
|
+
dest_path = os.path.join(target_dir, item, sub_path)
|
|
68
|
+
break
|
|
69
|
+
else:
|
|
70
|
+
# 如果找不到behavior_pack目录,创建一个
|
|
71
|
+
dest_path = os.path.join(target_dir, "behavior_pack", sub_path)
|
|
72
|
+
break
|
|
73
|
+
elif "resource_pack" in src_path.lower() or "resourcepack" in src_path.lower():
|
|
74
|
+
# 在路径中定位resource_pack部分
|
|
75
|
+
parts = src_path.split(os.sep)
|
|
76
|
+
for i, part in enumerate(parts):
|
|
77
|
+
if "resource_pack" in part.lower() or "resourcepack" in part.lower():
|
|
78
|
+
# 获取resource_pack后的相对路径
|
|
79
|
+
sub_path = os.path.join(*parts[i+1:])
|
|
80
|
+
# 目标路径应该是resource_pack目录
|
|
81
|
+
for item in os.listdir(target_dir):
|
|
82
|
+
if "resource_pack" in item.lower() or "resourcepack" in item.lower():
|
|
83
|
+
dest_path = os.path.join(target_dir, item, sub_path)
|
|
84
|
+
break
|
|
85
|
+
else:
|
|
86
|
+
# 如果找不到resource_pack目录,创建一个
|
|
87
|
+
dest_path = os.path.join(target_dir, "resource_pack", sub_path)
|
|
88
|
+
break
|
|
89
|
+
else:
|
|
90
|
+
# 如果不在行为包或资源包中,忽略该文件
|
|
91
|
+
return False, "不在行为包或资源包中的文件将被忽略", None
|
|
92
|
+
else:
|
|
93
|
+
dest_path = os.path.join(target_dir, rel_path)
|
|
94
|
+
|
|
95
|
+
# 检查目标文件是否已存在,如果存在且来自依赖,尝试合并
|
|
96
|
+
if is_dependency and os.path.exists(dest_path):
|
|
97
|
+
suc, reason = try_merge_file(src_path, dest_path)
|
|
98
|
+
if not suc:
|
|
99
|
+
# 如果合并失败或有冲突,直接返回
|
|
100
|
+
msg = f"文件合并失败或存在冲突" if reason is None else reason
|
|
101
|
+
return False, msg, dest_path
|
|
102
|
+
else:
|
|
103
|
+
# 复制文件
|
|
104
|
+
copy_file(src_path, dest_path)
|
|
105
|
+
|
|
106
|
+
# 如果是Python文件,进行转换
|
|
107
|
+
if is_python_file(src_path):
|
|
108
|
+
success, output = convert_py3_to_py2(dest_path)
|
|
109
|
+
return success, output, dest_path
|
|
110
|
+
|
|
111
|
+
# 如果是其他类型文件,直接返回成功
|
|
112
|
+
return True, "", dest_path
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def try_merge_file(source_file, target_file) -> tuple[bool, str]:
|
|
7
|
+
"""合并两个JSON文件的内容"""
|
|
8
|
+
# 如果是py文件,直接复制即可
|
|
9
|
+
if source_file.endswith('.py'):
|
|
10
|
+
# 直接复制
|
|
11
|
+
shutil.copy2(source_file, target_file)
|
|
12
|
+
return True, f"成功复制 {os.path.basename(source_file)}"
|
|
13
|
+
else:
|
|
14
|
+
# 读取源文件内容
|
|
15
|
+
with open(source_file, 'r', encoding='utf-8') as f:
|
|
16
|
+
source_content = f.read()
|
|
17
|
+
|
|
18
|
+
# 读取目标文件内容
|
|
19
|
+
with open(target_file, 'r', encoding='utf-8') as f:
|
|
20
|
+
target_content = f.read()
|
|
21
|
+
|
|
22
|
+
# 合并两个JSON对象
|
|
23
|
+
try:
|
|
24
|
+
source_json = eval(source_content)
|
|
25
|
+
target_json = eval(target_content)
|
|
26
|
+
merged_json = _merge_dicts(target_json, source_json)
|
|
27
|
+
|
|
28
|
+
# 写入合并后的内容到目标文件
|
|
29
|
+
with open(target_file, 'w', encoding='utf-8') as f:
|
|
30
|
+
f.write(str(merged_json))
|
|
31
|
+
|
|
32
|
+
return True, f"成功合并 {os.path.basename(source_file)} 到 {os.path.basename(target_file)}"
|
|
33
|
+
except Exception as e:
|
|
34
|
+
return False, f"合并失败: {str(e)}"
|
|
35
|
+
|
|
36
|
+
def _merge_dicts(dict1, dict2):
|
|
37
|
+
"""递归合并两个字典"""
|
|
38
|
+
result = dict1.copy()
|
|
39
|
+
for key, value in dict2.items():
|
|
40
|
+
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
|
41
|
+
# 递归合并嵌套字典
|
|
42
|
+
result[key] = _merge_dicts(result[key], value)
|
|
43
|
+
elif key in result and isinstance(result[key], list) and isinstance(value, list):
|
|
44
|
+
# 合并列表
|
|
45
|
+
result[key].extend(value)
|
|
46
|
+
else:
|
|
47
|
+
# 覆盖或添加新键
|
|
48
|
+
result[key] = value
|
|
49
|
+
return result
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
项目构建模块 - 负责整个项目的构建过程
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import sys
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import click
|
|
12
|
+
|
|
13
|
+
from ..utils.py3to2_util import py3_to_2
|
|
14
|
+
from ..utils.utils import run_command
|
|
15
|
+
from ..config import read_config, CONFIG_FILE
|
|
16
|
+
from .AddonsPack import AddonsPack
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def clear_directory(directory):
|
|
20
|
+
"""清空目录内容但保留目录本身"""
|
|
21
|
+
for item in os.listdir(directory):
|
|
22
|
+
item_path = os.path.join(directory, item)
|
|
23
|
+
if os.path.isdir(item_path):
|
|
24
|
+
shutil.rmtree(item_path)
|
|
25
|
+
else:
|
|
26
|
+
os.remove(item_path)
|
|
27
|
+
|
|
28
|
+
def convert_project_py3_to_py2(directory):
|
|
29
|
+
"""将整个项目中的Python文件转换为Python 2"""
|
|
30
|
+
try:
|
|
31
|
+
# 首先尝试使用直接的Python API调用
|
|
32
|
+
from lib3to2.main import main
|
|
33
|
+
# main函数接受包名和参数列表
|
|
34
|
+
# 第一个参数是包名 'lib3to2' (这是3to2所有修复器的位置)
|
|
35
|
+
# 第二个参数是命令行参数列表
|
|
36
|
+
exit_code = main('lib3to2.fixes', ['-w', '-n', '-j', '4', '--no-diffs', directory, '--nofix=metaclass'])
|
|
37
|
+
#exit_code = py3_to_2(directory)
|
|
38
|
+
return exit_code == 0, "转换完成" if exit_code == 0 else f"转换失败,错误代码: {exit_code}"
|
|
39
|
+
except Exception as e:
|
|
40
|
+
# 如果直接调用失败,则尝试命令行方式(作为备选)
|
|
41
|
+
try:
|
|
42
|
+
# 方法1:直接命令行调用
|
|
43
|
+
success, output = run_command(["3to2", "-w", "-n", directory])
|
|
44
|
+
if not success:
|
|
45
|
+
# 方法2:使用shell=True参数
|
|
46
|
+
success, output = run_command(["3to2", "-w", "-n", directory], shell=True)
|
|
47
|
+
|
|
48
|
+
return success, output
|
|
49
|
+
except Exception as cmd_e:
|
|
50
|
+
return False, f"Python API调用失败: {str(e)}\n命令行调用也失败: {str(cmd_e)}"
|
|
51
|
+
|
|
52
|
+
def find_mcpywrap_dependencies(dependencies: list[str]) -> dict[str, AddonsPack]:
|
|
53
|
+
"""
|
|
54
|
+
查找依赖包的真实路径,支持常规安装和 pip install -e(编辑安装)。
|
|
55
|
+
"""
|
|
56
|
+
# 记录依赖包的路径
|
|
57
|
+
dep_paths = {}
|
|
58
|
+
# 得到site-packages路径
|
|
59
|
+
for site_package_dir in __import__('site').getsitepackages():
|
|
60
|
+
site_packages = Path(site_package_dir)
|
|
61
|
+
for dist_info in site_packages.glob("*.dist-info"):
|
|
62
|
+
# 读取METADATA文件获取真实包名
|
|
63
|
+
metadata_path = dist_info / "METADATA"
|
|
64
|
+
if metadata_path.exists():
|
|
65
|
+
pkg_name = None
|
|
66
|
+
with open(metadata_path, 'r', encoding='utf-8') as f:
|
|
67
|
+
for line in f:
|
|
68
|
+
if line.startswith("Name:"):
|
|
69
|
+
pkg_name = line.split(":", 1)[1].strip()
|
|
70
|
+
break
|
|
71
|
+
|
|
72
|
+
if not pkg_name or pkg_name not in dependencies:
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
# 处理direct_url.json获取包路径
|
|
76
|
+
direct_url_path = dist_info / "direct_url.json"
|
|
77
|
+
if direct_url_path.exists():
|
|
78
|
+
with open(direct_url_path, 'r', encoding='utf-8') as f:
|
|
79
|
+
direct_url = json.load(f)
|
|
80
|
+
# 读取其中的url
|
|
81
|
+
if "url" in direct_url:
|
|
82
|
+
url = direct_url["url"]
|
|
83
|
+
# 处理file://开头的路径
|
|
84
|
+
if url.startswith("file:///"):
|
|
85
|
+
# 移除file:/// 前缀
|
|
86
|
+
if sys.platform == "win32":
|
|
87
|
+
# Windows 路径处理 (例如 file:///D:/path)
|
|
88
|
+
url = url[8:] # 去除 file:///
|
|
89
|
+
else:
|
|
90
|
+
url = "/" + url[8:] # 保留根目录斜杠
|
|
91
|
+
url = os.path.abspath(url)
|
|
92
|
+
# 兼容处理旧格式 file://
|
|
93
|
+
elif url.startswith("file://"):
|
|
94
|
+
url = url[7:]
|
|
95
|
+
# 对URL进行解码,处理%编码的特殊字符
|
|
96
|
+
from urllib.parse import unquote
|
|
97
|
+
url = unquote(url)
|
|
98
|
+
url = os.path.abspath(url)
|
|
99
|
+
|
|
100
|
+
# 确保路径格式一致
|
|
101
|
+
if sys.platform == "win32":
|
|
102
|
+
url = url.replace("\\", "/")
|
|
103
|
+
|
|
104
|
+
dep_paths[pkg_name] = AddonsPack(pkg_name, url)
|
|
105
|
+
else:
|
|
106
|
+
click.secho(f"⚠️ 警告: {pkg_name} 的direct_url.json中没有url字段", fg="yellow")
|
|
107
|
+
else:
|
|
108
|
+
click.secho(f"⚠️ 警告: {pkg_name} 没有找到direct_url.json文件", fg="yellow")
|
|
109
|
+
|
|
110
|
+
# 检查是否所有依赖都已找到
|
|
111
|
+
missing_deps = [dep for dep in dependencies if dep not in dep_paths]
|
|
112
|
+
if missing_deps:
|
|
113
|
+
click.secho(f"⚠️ 警告: 未找到以下依赖包: {', '.join(missing_deps)}", fg="yellow")
|
|
114
|
+
|
|
115
|
+
return dep_paths
|
|
116
|
+
|
|
117
|
+
def build_project(source_dir, target_dir):
|
|
118
|
+
"""
|
|
119
|
+
构建整个项目:
|
|
120
|
+
1. 复制所有项目文件
|
|
121
|
+
2. 复制并合并所有依赖项的文件
|
|
122
|
+
3. 转换所有Python文件
|
|
123
|
+
4. 报告冲突
|
|
124
|
+
"""
|
|
125
|
+
# 先清空
|
|
126
|
+
clear_directory(target_dir)
|
|
127
|
+
|
|
128
|
+
# 复制项目文件
|
|
129
|
+
config = read_config(os.path.join(source_dir, CONFIG_FILE))
|
|
130
|
+
project_name = config.get('project', {}).get('name', 'current_project')
|
|
131
|
+
origin_addons = AddonsPack(project_name, source_dir, is_origin=True)
|
|
132
|
+
|
|
133
|
+
# 复制基础
|
|
134
|
+
origin_addons.copy_behavior_to(target_dir)
|
|
135
|
+
origin_addons.copy_resource_to(target_dir)
|
|
136
|
+
|
|
137
|
+
target_addons = AddonsPack(project_name, target_dir)
|
|
138
|
+
|
|
139
|
+
# 查找并处理所有mcpywrap依赖
|
|
140
|
+
dependencies_list = config.get('project', {}).get('dependencies', [])
|
|
141
|
+
dependencies = find_mcpywrap_dependencies(dependencies_list)
|
|
142
|
+
click.secho(f"✅ 找到 {len(dependencies)} 个依赖包", fg="green")
|
|
143
|
+
for dep in dependencies:
|
|
144
|
+
click.secho(f" 📦 {dep} → {dependencies[dep].path}", fg="green")
|
|
145
|
+
for origin, dep in dependencies.items():
|
|
146
|
+
if origin not in dependencies:
|
|
147
|
+
click.secho(f"⚠️ 警告: 找不到依赖包 {origin}", fg="yellow")
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
for dep in dependencies:
|
|
151
|
+
dependencies[dep].merge_behavior_into(target_addons.behavior_pack_dir)
|
|
152
|
+
dependencies[dep].merge_resource_into(target_addons.resource_pack_dir)
|
|
153
|
+
|
|
154
|
+
# 转换Python文件
|
|
155
|
+
success, output = convert_project_py3_to_py2(target_dir)
|
|
156
|
+
|
|
157
|
+
return success, output
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
文件监控模块 - 负责监控文件变化并触发处理
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import time
|
|
8
|
+
from watchdog.observers import Observer
|
|
9
|
+
from watchdog.events import FileSystemEventHandler
|
|
10
|
+
from .file_handler import process_file, is_python_file
|
|
11
|
+
|
|
12
|
+
class FileChangeHandler(FileSystemEventHandler):
|
|
13
|
+
"""文件变化处理器"""
|
|
14
|
+
def __init__(self, source_dir, target_dir, callback=None, is_dependency=False, dependency_name=None):
|
|
15
|
+
self.source_dir = source_dir
|
|
16
|
+
self.target_dir = target_dir
|
|
17
|
+
self.last_event_time = 0
|
|
18
|
+
self.cooldown = 2 # 冷却时间(秒)
|
|
19
|
+
self.callback = callback
|
|
20
|
+
self.is_dependency = is_dependency # 是否是依赖项目
|
|
21
|
+
self.dependency_name = dependency_name # 依赖项目名称
|
|
22
|
+
|
|
23
|
+
def on_any_event(self, event):
|
|
24
|
+
# 检查路径是否有效
|
|
25
|
+
if not hasattr(event, 'src_path'):
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
src_path = event.src_path
|
|
29
|
+
|
|
30
|
+
# 忽略目录事件、隐藏文件和临时文件
|
|
31
|
+
if (event.is_directory or
|
|
32
|
+
os.path.basename(src_path).startswith('.') or
|
|
33
|
+
src_path.endswith('~') or
|
|
34
|
+
os.path.basename(src_path).startswith('.#') or
|
|
35
|
+
os.path.basename(src_path).endswith('.swp') or # vim临时文件
|
|
36
|
+
os.path.basename(src_path).endswith('.tmp')): # 其他临时文件
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
# 检查文件是否存在
|
|
40
|
+
if not os.path.exists(src_path):
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
current_time = time.time()
|
|
44
|
+
if current_time - self.last_event_time > self.cooldown:
|
|
45
|
+
self.last_event_time = current_time
|
|
46
|
+
|
|
47
|
+
# 处理文件变化
|
|
48
|
+
success, output, dest_path = process_file(
|
|
49
|
+
src_path,
|
|
50
|
+
self.source_dir,
|
|
51
|
+
self.target_dir,
|
|
52
|
+
is_dependency=self.is_dependency,
|
|
53
|
+
dependency_name=self.dependency_name
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# 如果有回调函数,调用它
|
|
57
|
+
if self.callback and dest_path: # 确保dest_path存在
|
|
58
|
+
is_py = is_python_file(src_path)
|
|
59
|
+
self.callback(src_path, dest_path, success, output, is_py, self.is_dependency, self.dependency_name)
|
|
60
|
+
|
|
61
|
+
class FileWatcher:
|
|
62
|
+
"""文件监控器"""
|
|
63
|
+
def __init__(self, source_dir, target_dir, callback=None, is_dependency=False, dependency_name=None):
|
|
64
|
+
self.source_dir = source_dir
|
|
65
|
+
self.target_dir = target_dir
|
|
66
|
+
self.observer = None
|
|
67
|
+
self.callback = callback
|
|
68
|
+
self.is_dependency = is_dependency
|
|
69
|
+
self.dependency_name = dependency_name
|
|
70
|
+
|
|
71
|
+
def start(self):
|
|
72
|
+
"""开始监控"""
|
|
73
|
+
event_handler = FileChangeHandler(
|
|
74
|
+
self.source_dir,
|
|
75
|
+
self.target_dir,
|
|
76
|
+
self.callback,
|
|
77
|
+
self.is_dependency,
|
|
78
|
+
self.dependency_name
|
|
79
|
+
)
|
|
80
|
+
self.observer = Observer()
|
|
81
|
+
self.observer.schedule(event_handler, path=self.source_dir, recursive=True)
|
|
82
|
+
self.observer.start()
|
|
83
|
+
|
|
84
|
+
def stop(self):
|
|
85
|
+
"""停止监控"""
|
|
86
|
+
if self.observer:
|
|
87
|
+
self.observer.stop()
|
|
88
|
+
self.observer.join()
|
|
89
|
+
|
|
90
|
+
class MultiWatcher:
|
|
91
|
+
"""多项目文件监控器,用于同时监控主项目和依赖项目"""
|
|
92
|
+
def __init__(self):
|
|
93
|
+
self.watchers = []
|
|
94
|
+
|
|
95
|
+
def add_watcher(self, watcher):
|
|
96
|
+
"""添加一个监视器"""
|
|
97
|
+
self.watchers.append(watcher)
|
|
98
|
+
|
|
99
|
+
def start_all(self):
|
|
100
|
+
"""启动所有监视器"""
|
|
101
|
+
for watcher in self.watchers:
|
|
102
|
+
watcher.start()
|
|
103
|
+
|
|
104
|
+
def stop_all(self):
|
|
105
|
+
"""停止所有监视器"""
|
|
106
|
+
for watcher in self.watchers:
|
|
107
|
+
watcher.stop()
|