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 ADDED
@@ -0,0 +1,4 @@
1
+ # -*- coding: utf-8 -*-
2
+ """mcpywrap - 将 Python 3 代码转换为 Python 2 代码的工具"""
3
+
4
+ __version__ = '0.1.0'
mcpywrap/__main__.py ADDED
@@ -0,0 +1,13 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ mcpywrap 命令行工具的入口点
4
+ """
5
+
6
+ from .cli import cli
7
+
8
+ def main():
9
+ """主函数,作为 CLI 入口点"""
10
+ cli()
11
+
12
+ if __name__ == "__main__":
13
+ main()
@@ -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()