pyscreeps-arena 0.5.1.0__tar.gz → 0.5.7.6__tar.gz
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.
- {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/PKG-INFO +2 -1
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/__init__.py +88 -0
- {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/compiler.py +380 -35
- {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/core/const.py +1 -1
- {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/localization.py +10 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/project.7z +0 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/P2PY.py +108 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/__init__.py +12 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/creeplogic_edit.py +14 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/map_render.py +705 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/mapviewer.py +14 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/project_ui.py +215 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qcreeplogic/__init__.py +3 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qcreeplogic/model.py +72 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qcreeplogic/qcreeplogic.py +770 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapker/__init__.py +1 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapker/qmapmarker.py +339 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapker/qvariable.py +303 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapker/test_compact_variable.py +61 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapker/test_qmapmarker.py +71 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapker/test_qvariable.py +49 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapker/to_code.py +100 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/__init__.py +3 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/qcinfo.py +567 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/qco.py +441 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/qmapv.py +728 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/test_array_drag.py +191 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/test_drag.py +107 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/test_qcinfo.py +169 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/test_qco_drag.py +7 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/test_qmapv.py +224 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/test_simple_array.py +303 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qrecipe/__init__.py +1 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qrecipe/model.py +434 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qrecipe/qrecipe.py +914 -0
- pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/rs_icon.py +43 -0
- {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena.egg-info/PKG-INFO +2 -1
- pyscreeps_arena-0.5.7.6/pyscreeps_arena.egg-info/SOURCES.txt +49 -0
- {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena.egg-info/entry_points.txt +1 -0
- {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena.egg-info/requires.txt +1 -0
- {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/setup.py +3 -1
- pyscreeps_arena-0.5.1.0/pyscreeps_arena/__init__.py +0 -32
- pyscreeps_arena-0.5.1.0/pyscreeps_arena/project.7z +0 -0
- pyscreeps_arena-0.5.1.0/pyscreeps_arena.egg-info/SOURCES.txt +0 -19
- {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/build.py +0 -0
- {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/core/__init__.py +0 -0
- {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/core/basic.py +0 -0
- {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/core/config.py +0 -0
- {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/core/core.py +0 -0
- {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/core/main.py +0 -0
- {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/core/utils.py +0 -0
- {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena.egg-info/dependency_links.txt +0 -0
- {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena.egg-info/top_level.txt +0 -0
- {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyscreeps-arena
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.7.6
|
|
4
4
|
Summary: Python api|interface to play game: Screeps: Arena.
|
|
5
5
|
Author-email: 2229066748@qq.com
|
|
6
6
|
Maintainer: Eagle'sBaby
|
|
@@ -16,6 +16,7 @@ Requires-Dist: colorama
|
|
|
16
16
|
Requires-Dist: py7zr
|
|
17
17
|
Requires-Dist: chardet
|
|
18
18
|
Requires-Dist: Transcrypt==3.9.1
|
|
19
|
+
Requires-Dist: PyQt6
|
|
19
20
|
Requires-Dist: mkdocs
|
|
20
21
|
Requires-Dist: mkdocstrings[python]
|
|
21
22
|
Requires-Dist: mkdocs-material
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import shutil
|
|
4
|
+
import py7zr
|
|
5
|
+
from pyscreeps_arena.core import const, config
|
|
6
|
+
from pyscreeps_arena.ui.mapviewer import run_mapviewer
|
|
7
|
+
from pyscreeps_arena.ui.project_ui import run_project_creator
|
|
8
|
+
from pyscreeps_arena.ui.creeplogic_edit import run_creeplogic_edit
|
|
9
|
+
|
|
10
|
+
def CMD_NewProject():
|
|
11
|
+
"""
|
|
12
|
+
cmd:
|
|
13
|
+
pyscreeps-arena [project_path]
|
|
14
|
+
arena [project_path]
|
|
15
|
+
|
|
16
|
+
* 复制"src" "game" "build.py" 到指定目录
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
if len(sys.argv) < 2:
|
|
22
|
+
print("Usage: pyarena new [project_path]\n# or\narena new [project_path]")
|
|
23
|
+
return
|
|
24
|
+
project_path = sys.argv[1]
|
|
25
|
+
if not os.path.exists(project_path):
|
|
26
|
+
os.makedirs(project_path)
|
|
27
|
+
this_path = os.path.dirname(os.path.abspath(__file__))
|
|
28
|
+
extract_7z(os.path.join(this_path, 'project.7z'), project_path)
|
|
29
|
+
print("Project created at", project_path)
|
|
30
|
+
|
|
31
|
+
def CMD_OpenUI():
|
|
32
|
+
"""
|
|
33
|
+
cmd:
|
|
34
|
+
psaui 无参数,启用project ui
|
|
35
|
+
psaui -c/-e 启用creeplogic edit
|
|
36
|
+
psaui -m [-c/-e] 启用mapviewer. 默认cn,可以指定-c/-e
|
|
37
|
+
psaui -h 显示帮助信息
|
|
38
|
+
|
|
39
|
+
* 打开UI界面
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
# 显示帮助信息
|
|
46
|
+
if len(sys.argv) > 1 and sys.argv[1] == '-h':
|
|
47
|
+
print("Usage:")
|
|
48
|
+
print(" psaui 启用project ui")
|
|
49
|
+
print(" psaui -c/-e 启用creeplogic edit (-c: 中文, -e: 英文)")
|
|
50
|
+
print(" psaui -m [-c/-e] 启用mapviewer (-c: 中文, -e: 英文, 默认: 中文)")
|
|
51
|
+
print(" psaui -h 显示帮助信息")
|
|
52
|
+
print(" --------------------------------------------------------------")
|
|
53
|
+
print(" psaui run `project ui`")
|
|
54
|
+
print(" psaui -c/-e run `creeplogic edit` (-c: chinese, -e: english)")
|
|
55
|
+
print(" psaui -m [-c/-e] run `mapviewer` (-c: chinese, -e: english, default: chinese)")
|
|
56
|
+
print(" psaui -h Show this help message")
|
|
57
|
+
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
# 检查是否使用mapviewer
|
|
61
|
+
if len(sys.argv) > 1 and sys.argv[1] == '-m':
|
|
62
|
+
# 检查语言参数
|
|
63
|
+
if len(sys.argv) > 2 and sys.argv[2] == '-e':
|
|
64
|
+
from pyscreeps_arena.core import config
|
|
65
|
+
config.language = 'en'
|
|
66
|
+
run_mapviewer()
|
|
67
|
+
# 检查是否使用creeplogic edit
|
|
68
|
+
elif len(sys.argv) > 1 and sys.argv[1] == '-c':
|
|
69
|
+
run_creeplogic_edit()
|
|
70
|
+
elif len(sys.argv) > 1 and sys.argv[1] == '-e':
|
|
71
|
+
from pyscreeps_arena.core import config
|
|
72
|
+
config.language = 'en'
|
|
73
|
+
run_creeplogic_edit()
|
|
74
|
+
# 默认启用project ui
|
|
75
|
+
else:
|
|
76
|
+
run_project_creator()
|
|
77
|
+
except ImportError as e:
|
|
78
|
+
print(f"错误: 无法导入UI模块 - {e}")
|
|
79
|
+
print("请确保已安装PyQt6: pip install PyQt6")
|
|
80
|
+
except Exception as e:
|
|
81
|
+
print(f"错误: 打开UI界面失败 - {e}")
|
|
82
|
+
|
|
83
|
+
def extract_7z(file_path, output_dir):
|
|
84
|
+
with py7zr.SevenZipFile(file_path, mode='r') as archive:
|
|
85
|
+
archive.extractall(path=output_dir)
|
|
86
|
+
|
|
87
|
+
if __name__ == '__main__':
|
|
88
|
+
CMD_OpenUI()
|
|
@@ -9,6 +9,7 @@ import chardet
|
|
|
9
9
|
import subprocess
|
|
10
10
|
import pyperclip
|
|
11
11
|
from colorama import Fore
|
|
12
|
+
from typing import List, Optional, Tuple, Union
|
|
12
13
|
|
|
13
14
|
WAIT = Fore.YELLOW + ">>>" + Fore.RESET
|
|
14
15
|
GREEN = Fore.GREEN + "{}" + Fore.RESET
|
|
@@ -16,6 +17,27 @@ python_version_info = sys.version_info
|
|
|
16
17
|
python_version_info = f"{python_version_info.major}.{python_version_info.minor}.{python_version_info.micro}"
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
def replace_src_prefix(file_list):
|
|
21
|
+
"""
|
|
22
|
+
将列表中以'./src.'开头的字符串替换为'./'
|
|
23
|
+
|
|
24
|
+
参数:
|
|
25
|
+
file_list: 字符串列表
|
|
26
|
+
|
|
27
|
+
返回:
|
|
28
|
+
替换后的新列表
|
|
29
|
+
"""
|
|
30
|
+
_ = []
|
|
31
|
+
|
|
32
|
+
for item in file_list:
|
|
33
|
+
if isinstance(item, str) and item.startswith('./src.'):
|
|
34
|
+
_new = item.replace('./src.', './', 1)
|
|
35
|
+
if _new in file_list:
|
|
36
|
+
continue
|
|
37
|
+
_.append(item)
|
|
38
|
+
|
|
39
|
+
return _
|
|
40
|
+
|
|
19
41
|
# def InsertPragmaBefore(content:str) -> str:
|
|
20
42
|
# """
|
|
21
43
|
# 在content的开头插入__pragma__('noalias', 'undefined')等内容 |
|
|
@@ -60,7 +82,7 @@ var monitor = Monitor(1);
|
|
|
60
82
|
know.now = 0;
|
|
61
83
|
|
|
62
84
|
StageMachineLogicMeta.__types__ = []; // 清空js首次构造时引入的数据
|
|
63
|
-
|
|
85
|
+
__init_before_k__();
|
|
64
86
|
let knowCost = 0;
|
|
65
87
|
let monitorCost = 0;
|
|
66
88
|
let stepCost = 0;
|
|
@@ -71,24 +93,25 @@ export var loop = function () {
|
|
|
71
93
|
timeLine = get.cpu_us();
|
|
72
94
|
know.handle();
|
|
73
95
|
knowCost = get.cpu_us() - timeLine;
|
|
74
|
-
|
|
75
|
-
std.show_welcome();
|
|
76
|
-
init (know);
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
96
|
+
|
|
80
97
|
timeLine = get.cpu_us();
|
|
81
98
|
monitor.handle();
|
|
82
99
|
monitorCost = get.cpu_us() - timeLine;
|
|
83
|
-
for (const creep of
|
|
100
|
+
for (const creep of know.creeps){
|
|
84
101
|
creep.handle();
|
|
85
102
|
}
|
|
103
|
+
if (know.now === 1) {
|
|
104
|
+
std.show_welcome();
|
|
105
|
+
init (know);
|
|
106
|
+
|
|
107
|
+
}
|
|
86
108
|
step (know);
|
|
87
109
|
timeLine = get.cpu_us();
|
|
88
|
-
sch.handle();
|
|
110
|
+
if (get._SCH_FLAG) sch.handle();
|
|
89
111
|
stepCost = get.cpu_us() - timeLine;
|
|
90
112
|
std.show_usage ();
|
|
91
113
|
print("knowCost:", knowCost, "monitorCost:", monitorCost, "stepCost:", stepCost);
|
|
114
|
+
if (know.draw) know.draw();
|
|
92
115
|
};
|
|
93
116
|
"""
|
|
94
117
|
|
|
@@ -126,7 +149,7 @@ export var loop = function () {
|
|
|
126
149
|
|
|
127
150
|
ARENA_IMPORTS_GETTER = {
|
|
128
151
|
const.ARENA_GREEN: lambda: f"""
|
|
129
|
-
const ARENA_COLOR_TYPE = "
|
|
152
|
+
const ARENA_COLOR_TYPE = "GREEN";
|
|
130
153
|
class GameAreaEffect{{
|
|
131
154
|
constructor(){{
|
|
132
155
|
}}
|
|
@@ -136,8 +159,9 @@ class GameConstructionBoost{{
|
|
|
136
159
|
}}
|
|
137
160
|
}};
|
|
138
161
|
import {{ Portal as GamePortal}} from 'arena/season_{config.season}/{const.ARENA_GREEN}/{"advanced" if config.level in ["advance", "advanced"] else "basic"}/prototypes';
|
|
139
|
-
|
|
140
162
|
""",
|
|
163
|
+
# import {Portal} from 'arena/season_1/portal_exploration/basic/prototypes';
|
|
164
|
+
|
|
141
165
|
const.ARENA_BLUE: lambda: f"""
|
|
142
166
|
const ARENA_COLOR_TYPE = "BLUE";
|
|
143
167
|
const GameScoreCollector = GameStructureSpawn;
|
|
@@ -178,11 +202,6 @@ class GameConstructionBoost{{
|
|
|
178
202
|
constructor(){{
|
|
179
203
|
}}
|
|
180
204
|
}};
|
|
181
|
-
let GameFlag = GameStructureSpawn;
|
|
182
|
-
import * as GAME_PROTO_MODULE from 'game/prototypes';
|
|
183
|
-
if (GAME_PROTO_MODULE.Flag){{
|
|
184
|
-
GameFlag = GAME_PROTO_MODULE.Flag;
|
|
185
|
-
}}
|
|
186
205
|
""",
|
|
187
206
|
}
|
|
188
207
|
|
|
@@ -212,12 +231,16 @@ class Compiler_Utils(Compiler_Const):
|
|
|
212
231
|
return f.read()
|
|
213
232
|
except UnicodeDecodeError:
|
|
214
233
|
# 如果使用检测到的编码读取失败,尝试使用chardet检测编码
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
234
|
+
try:
|
|
235
|
+
with open(fpath, 'rb') as f: # 以二进制模式打开文件
|
|
236
|
+
raw_data = f.read() # 读取文件的原始数据
|
|
237
|
+
result = chardet.detect(raw_data) # 使用chardet检测编码
|
|
238
|
+
encoding = result['encoding'] # 获取检测到的编码
|
|
239
|
+
with open(fpath, 'r', encoding=encoding) as f: # 使用检测到的编码打开文件
|
|
240
|
+
return f.read()
|
|
241
|
+
except UnicodeDecodeError as e:
|
|
242
|
+
core.error('Compiler_Utils.auto_read', core.lformat(LOC_FILE_READ_FAILED, [fpath, "UnicodeError", e]), end='', head='\n', ln=config.language)
|
|
243
|
+
quit(-1)
|
|
221
244
|
|
|
222
245
|
def copy_to(self) -> list:
|
|
223
246
|
"""
|
|
@@ -318,6 +341,102 @@ class Compiler_Utils(Compiler_Const):
|
|
|
318
341
|
|
|
319
342
|
return '\n'.join(result) # 将处理后的所有代码行连接成一个字符串,并返回最终结果 | join all processed lines into a string and return
|
|
320
343
|
|
|
344
|
+
def expand_folder_imports(self, fpath: str, project_path: str = None):
|
|
345
|
+
"""
|
|
346
|
+
扩展文件夹导入语句:将 `from folder import *` 替换为 `from folder.module import *`
|
|
347
|
+
仅在文件夹没有 __init__.py 时执行此操作
|
|
348
|
+
|
|
349
|
+
:param fpath: 要处理的文件路径
|
|
350
|
+
:param project_path: 项目根路径,用于解析相对导入,默认为 None(使用文件所在目录)
|
|
351
|
+
"""
|
|
352
|
+
if not os.path.exists(fpath):
|
|
353
|
+
return
|
|
354
|
+
|
|
355
|
+
content = self.auto_read(fpath)
|
|
356
|
+
lines = content.split('\n')
|
|
357
|
+
new_lines = []
|
|
358
|
+
changed = False
|
|
359
|
+
|
|
360
|
+
for line in lines:
|
|
361
|
+
m = self.PY_IMPORT_PAT.match(line)
|
|
362
|
+
if not m:
|
|
363
|
+
new_lines.append(line)
|
|
364
|
+
continue
|
|
365
|
+
|
|
366
|
+
original_target = m.group(1)
|
|
367
|
+
target = original_target
|
|
368
|
+
target_path = project_path or os.path.dirname(fpath)
|
|
369
|
+
|
|
370
|
+
# 处理相对路径(向前定位)
|
|
371
|
+
if target.startswith('.'):
|
|
372
|
+
target_path = os.path.dirname(fpath)
|
|
373
|
+
count = 0
|
|
374
|
+
for c in target:
|
|
375
|
+
if c == '.':
|
|
376
|
+
count += 1
|
|
377
|
+
else:
|
|
378
|
+
break
|
|
379
|
+
|
|
380
|
+
# 向上移动目录
|
|
381
|
+
if count > 1:
|
|
382
|
+
for _ in range(count - 1):
|
|
383
|
+
target_path = os.path.dirname(target_path)
|
|
384
|
+
|
|
385
|
+
# 移除开头的点
|
|
386
|
+
target = target[count:]
|
|
387
|
+
|
|
388
|
+
# 如果 target 为空,跳过
|
|
389
|
+
if not target:
|
|
390
|
+
new_lines.append(line)
|
|
391
|
+
continue
|
|
392
|
+
|
|
393
|
+
# 向后定位,构建完整路径
|
|
394
|
+
temp_target = target
|
|
395
|
+
while (_idx := temp_target.find('.')) != -1:
|
|
396
|
+
part = temp_target[:_idx]
|
|
397
|
+
target_path = os.path.join(target_path, part)
|
|
398
|
+
temp_target = temp_target[_idx + 1:]
|
|
399
|
+
|
|
400
|
+
# 最终的文件夹路径
|
|
401
|
+
final_dir_path = os.path.join(target_path, temp_target) if temp_target else target_path
|
|
402
|
+
|
|
403
|
+
# 检查是否是文件夹且没有 __init__.py
|
|
404
|
+
if os.path.isdir(final_dir_path):
|
|
405
|
+
init_path = os.path.join(final_dir_path, '__init__.py')
|
|
406
|
+
if not os.path.exists(init_path):
|
|
407
|
+
# 找到所有 .py 文件(排除 __init__.py)| 如果包含子目录,产生一个警告
|
|
408
|
+
# try:
|
|
409
|
+
# py_files = [f for f in os.listdir(final_dir_path) if f.endswith('.py') and f != '__init__.py']
|
|
410
|
+
# except (FileNotFoundError, PermissionError):
|
|
411
|
+
py_files = []
|
|
412
|
+
for item in os.listdir(final_dir_path):
|
|
413
|
+
_path = os.path.join(final_dir_path, item)
|
|
414
|
+
if os.path.isfile(_path) and item.endswith('.py') and item != '__init__.py':
|
|
415
|
+
py_files.append(item)
|
|
416
|
+
elif os.path.isdir(_path):
|
|
417
|
+
rel = os.path.relpath(final_dir_path, project_path)
|
|
418
|
+
core.warn(f'Compiler.expand_folder_imports', core.lformat(LOC_DIR_UNDER_NONINIT_DIR, [item, rel]), end='', head='\n', ln=config.language )
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
# 为每个 .py 文件生成导入语句
|
|
422
|
+
if py_files:
|
|
423
|
+
for py_file in py_files:
|
|
424
|
+
module_name = py_file[:-3]
|
|
425
|
+
new_import = f"from {original_target}.{module_name} import *"
|
|
426
|
+
new_lines.append(new_import)
|
|
427
|
+
changed = True
|
|
428
|
+
continue
|
|
429
|
+
|
|
430
|
+
# 保留原行
|
|
431
|
+
new_lines.append(line)
|
|
432
|
+
|
|
433
|
+
# 如果文件有修改,写回
|
|
434
|
+
if changed:
|
|
435
|
+
new_content = '\n'.join(new_lines)
|
|
436
|
+
with open(fpath, 'w', encoding='utf-8') as f:
|
|
437
|
+
f.write(new_content)
|
|
438
|
+
|
|
439
|
+
|
|
321
440
|
def find_chain_import(self, fpath: str, search_dirs: list[str], project_path: str = None, records: dict[str, None] = None) -> list[str]:
|
|
322
441
|
r"""
|
|
323
442
|
查找文件中的所有import语句,并返回所有import的文件路径 | find all import statements in a file and return the paths of all imported files
|
|
@@ -468,15 +587,63 @@ class Compiler_Utils(Compiler_Const):
|
|
|
468
587
|
return jsimps
|
|
469
588
|
|
|
470
589
|
# ---------- 自定义函数 ---------- #
|
|
590
|
+
|
|
471
591
|
@staticmethod
|
|
472
|
-
def stage_recursive_replace(content:str) -> str:
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
Replace '@recursive' with '@recursive(<fname>)', where <fname> is the name of the decorated function.
|
|
592
|
+
def stage_recursive_replace(content: str) -> str:
|
|
593
|
+
"""
|
|
594
|
+
移除 '@recursive' 装饰器行,并在文末添加对应的 _recursiveLogin 调用。
|
|
476
595
|
|
|
477
|
-
|
|
596
|
+
对于类方法: _recursiveLogin("ClassName", "method_name")
|
|
597
|
+
对于普通函数: _recursiveLogin("", "function_name")
|
|
478
598
|
"""
|
|
479
|
-
|
|
599
|
+
calls_to_add = []
|
|
600
|
+
deletions = []
|
|
601
|
+
|
|
602
|
+
# 1. 收集所有类定义的位置和缩进
|
|
603
|
+
class_pattern = re.compile(r'^(\s*)class\s+(\w+)', re.MULTILINE)
|
|
604
|
+
classes = [(m.start(), len(m.group(1)), m.group(2))
|
|
605
|
+
for m in class_pattern.finditer(content)]
|
|
606
|
+
|
|
607
|
+
# 2. 查找所有 @recursive 装饰器
|
|
608
|
+
decorator_pattern = re.compile(r'^\s*@\s*recursive\s*$\n?', re.MULTILINE)
|
|
609
|
+
|
|
610
|
+
for dec_match in decorator_pattern.finditer(content):
|
|
611
|
+
dec_end = dec_match.end()
|
|
612
|
+
|
|
613
|
+
# 查找接下来的函数定义(跳过可能的空行)
|
|
614
|
+
after_decorator = content[dec_end:]
|
|
615
|
+
func_match = re.search(r'^(\s*)def\s+([^\s\(]+)', after_decorator, re.MULTILINE)
|
|
616
|
+
|
|
617
|
+
if not func_match:
|
|
618
|
+
continue
|
|
619
|
+
|
|
620
|
+
func_indent_len = len(func_match.group(1))
|
|
621
|
+
func_name = func_match.group(2)
|
|
622
|
+
|
|
623
|
+
# 3. 确定类名:查找装饰器前最近的、缩进小于函数缩进的类
|
|
624
|
+
class_name = ""
|
|
625
|
+
for cls_pos, cls_indent_len, cls_name in reversed(classes):
|
|
626
|
+
if cls_pos < dec_match.start() and func_indent_len > cls_indent_len:
|
|
627
|
+
class_name = cls_name
|
|
628
|
+
break
|
|
629
|
+
|
|
630
|
+
# 4. 记录删除位置和调用信息
|
|
631
|
+
deletions.append((dec_match.start(), dec_end))
|
|
632
|
+
calls_to_add.append(f'_recursiveLogin("{class_name}", "{func_name}")')
|
|
633
|
+
|
|
634
|
+
# 5. 应用删除(倒序避免位置偏移)
|
|
635
|
+
if not deletions:
|
|
636
|
+
return content
|
|
637
|
+
|
|
638
|
+
result = content
|
|
639
|
+
for start, end in sorted(deletions, key=lambda x: x[0], reverse=True):
|
|
640
|
+
result = result[:start] + result[end:]
|
|
641
|
+
|
|
642
|
+
# 6. 在文末添加调用
|
|
643
|
+
if calls_to_add:
|
|
644
|
+
result = '\n'.join(calls_to_add) + '\n' + result
|
|
645
|
+
|
|
646
|
+
return result
|
|
480
647
|
|
|
481
648
|
@staticmethod
|
|
482
649
|
def process_mate_code(code):
|
|
@@ -524,6 +691,167 @@ class Compiler_Utils(Compiler_Const):
|
|
|
524
691
|
code = re.sub(r"'''[^']*'''", '', code)
|
|
525
692
|
return code
|
|
526
693
|
|
|
694
|
+
@classmethod
|
|
695
|
+
def _collect_logical_line(cls, lines: List[str], start_idx: int) -> Tuple[str, int]:
|
|
696
|
+
"""收集从start_idx开始的逻辑行(处理多行语句,直到遇到:结尾)"""
|
|
697
|
+
if start_idx >= len(lines):
|
|
698
|
+
return "", start_idx
|
|
699
|
+
|
|
700
|
+
parts = [lines[start_idx].rstrip()]
|
|
701
|
+
i = start_idx
|
|
702
|
+
|
|
703
|
+
# 持续收集直到找到以:结尾的行
|
|
704
|
+
while i < len(lines) and not parts[-1].endswith(':'):
|
|
705
|
+
i += 1
|
|
706
|
+
if i < len(lines):
|
|
707
|
+
parts.append(lines[i].rstrip())
|
|
708
|
+
|
|
709
|
+
return " ".join(parts), i
|
|
710
|
+
|
|
711
|
+
@classmethod
|
|
712
|
+
def _convert_block(cls, lines: List[str], match_counter: Optional[List[int]] = None) -> List[str]:
|
|
713
|
+
if match_counter is None:
|
|
714
|
+
match_counter = [0]
|
|
715
|
+
|
|
716
|
+
result = []
|
|
717
|
+
i = 0
|
|
718
|
+
|
|
719
|
+
while i < len(lines):
|
|
720
|
+
line = lines[i].rstrip()
|
|
721
|
+
|
|
722
|
+
# 检测match语句(支持多行)
|
|
723
|
+
if re.match(r'^\s*match\s+', line):
|
|
724
|
+
full_match, end_idx = cls._collect_logical_line(lines, i)
|
|
725
|
+
|
|
726
|
+
match_stmt = re.match(r'^(\s*)match\s+(.+?)\s*:', full_match)
|
|
727
|
+
if not match_stmt:
|
|
728
|
+
result.append(line)
|
|
729
|
+
i += 1
|
|
730
|
+
continue
|
|
731
|
+
|
|
732
|
+
indent = match_stmt.group(1)
|
|
733
|
+
subject = match_stmt.group(2).strip()
|
|
734
|
+
i = end_idx + 1 # 跳过match语句
|
|
735
|
+
|
|
736
|
+
# 生成临时变量
|
|
737
|
+
var_name = f"__MATCH_{match_counter[0]}__"
|
|
738
|
+
match_counter[0] += 1
|
|
739
|
+
result.append(f"{indent}{var_name} = {subject}")
|
|
740
|
+
|
|
741
|
+
# 解析case语句
|
|
742
|
+
cases: List[Tuple[str, List[str]]] = []
|
|
743
|
+
case_indent = None
|
|
744
|
+
|
|
745
|
+
while i < len(lines):
|
|
746
|
+
case_line = lines[i].rstrip()
|
|
747
|
+
|
|
748
|
+
# 缩进检查 - case必须比match缩进更多
|
|
749
|
+
if not case_line.startswith(indent + ' ') and case_line.strip():
|
|
750
|
+
if re.match(r'^\s*case\s+', case_line):
|
|
751
|
+
raise MatchCaseError(f"第 {i + 1} 行: case 缩进必须大于 match")
|
|
752
|
+
break
|
|
753
|
+
|
|
754
|
+
# 检测case语句(不再使用_collect_logical_line,而是单独处理每一行)
|
|
755
|
+
case_match = re.match(r'^(\s+)case\s+(.+?)\s*:', case_line)
|
|
756
|
+
if case_match:
|
|
757
|
+
curr_case_indent = case_match.group(1)
|
|
758
|
+
case_val = case_match.group(2).strip()
|
|
759
|
+
|
|
760
|
+
# 验证缩进 - 允许不同的case缩进(用于嵌套)
|
|
761
|
+
if len(curr_case_indent) <= len(indent):
|
|
762
|
+
raise MatchCaseError(f"第 {i + 1} 行: case 缩进必须大于 match")
|
|
763
|
+
|
|
764
|
+
# 不再强制要求所有case缩进一致,允许嵌套情况下的不同缩进
|
|
765
|
+
if case_indent is None:
|
|
766
|
+
case_indent = curr_case_indent
|
|
767
|
+
|
|
768
|
+
# 提取内联代码(如果有)
|
|
769
|
+
inline_code = ""
|
|
770
|
+
if ':' in case_line:
|
|
771
|
+
after_colon = case_line.split(':', 1)[1].strip()
|
|
772
|
+
if after_colon:
|
|
773
|
+
inline_code = after_colon
|
|
774
|
+
|
|
775
|
+
i += 1
|
|
776
|
+
|
|
777
|
+
# 收集case块
|
|
778
|
+
block_lines = []
|
|
779
|
+
if inline_code:
|
|
780
|
+
block_lines.append(f"{curr_case_indent} {inline_code}")
|
|
781
|
+
|
|
782
|
+
while i < len(lines):
|
|
783
|
+
block_line = lines[i].rstrip()
|
|
784
|
+
if not block_line.strip():
|
|
785
|
+
block_lines.append(block_line)
|
|
786
|
+
i += 1
|
|
787
|
+
continue
|
|
788
|
+
|
|
789
|
+
# 检查是否是下一个case或者缩进回到当前match级别
|
|
790
|
+
if re.match(r'^\s*case\s+', block_line):
|
|
791
|
+
# 检查这个case是否属于当前match还是父级match
|
|
792
|
+
next_case_indent = re.match(r'^\s*', block_line).group(0)
|
|
793
|
+
if len(next_case_indent) <= len(indent):
|
|
794
|
+
# 属于父级match,退出当前match的处理
|
|
795
|
+
break
|
|
796
|
+
# 仍然属于当前match,继续收集
|
|
797
|
+
if block_line.startswith(curr_case_indent + ' '):
|
|
798
|
+
block_lines.append(block_line)
|
|
799
|
+
i += 1
|
|
800
|
+
continue
|
|
801
|
+
else:
|
|
802
|
+
break
|
|
803
|
+
|
|
804
|
+
if block_line.startswith(indent) and not block_line.startswith(curr_case_indent):
|
|
805
|
+
break
|
|
806
|
+
|
|
807
|
+
if block_line.startswith(curr_case_indent + ' '):
|
|
808
|
+
block_lines.append(block_line)
|
|
809
|
+
i += 1
|
|
810
|
+
continue
|
|
811
|
+
|
|
812
|
+
break
|
|
813
|
+
|
|
814
|
+
cases.append((case_val, block_lines))
|
|
815
|
+
else:
|
|
816
|
+
break
|
|
817
|
+
|
|
818
|
+
# 验证case
|
|
819
|
+
seen = set()
|
|
820
|
+
for idx, (val, _) in enumerate(cases):
|
|
821
|
+
if val == '_':
|
|
822
|
+
if idx != len(cases) - 1:
|
|
823
|
+
raise MatchCaseError(f"第 {i + 1} 行附近: case _ 必须在最后")
|
|
824
|
+
else:
|
|
825
|
+
if val in seen:
|
|
826
|
+
raise MatchCaseError(f"第 {i + 1} 行附近: 重复的 case 值 '{val}'")
|
|
827
|
+
seen.add(val)
|
|
828
|
+
|
|
829
|
+
# 生成if/elif/else
|
|
830
|
+
for idx, (case_val, blk_lines) in enumerate(cases):
|
|
831
|
+
keyword = "else" if case_val == '_' else ("if" if idx == 0 else "elif")
|
|
832
|
+
if keyword == "else":
|
|
833
|
+
result.append(f"{indent}else:")
|
|
834
|
+
else:
|
|
835
|
+
result.append(f"{indent}{keyword} {var_name} == {case_val}:")
|
|
836
|
+
|
|
837
|
+
if blk_lines:
|
|
838
|
+
# 递归处理block_lines,以支持嵌套match
|
|
839
|
+
converted_blk_lines = cls._convert_block(blk_lines, match_counter)
|
|
840
|
+
result.extend(converted_blk_lines)
|
|
841
|
+
|
|
842
|
+
continue
|
|
843
|
+
|
|
844
|
+
result.append(line)
|
|
845
|
+
i += 1
|
|
846
|
+
|
|
847
|
+
return result
|
|
848
|
+
|
|
849
|
+
@classmethod
|
|
850
|
+
def convert_match_to_if(cls, code: str) -> str:
|
|
851
|
+
lines = code.split('\n')
|
|
852
|
+
converted_lines = cls._convert_block(lines, [0])
|
|
853
|
+
return '\n'.join(converted_lines)
|
|
854
|
+
|
|
527
855
|
|
|
528
856
|
class CompilerBase(Compiler_Utils):
|
|
529
857
|
|
|
@@ -626,6 +954,8 @@ class Compiler(CompilerBase):
|
|
|
626
954
|
if m and (not m.group(2) or m.group(2)[0] != '*'):
|
|
627
955
|
core.error('Compiler.pre_compile', core.lformat(LOC_IMPORT_STAR2_ERROR, [m.group(1), m.group(2), m.group(1)]), head='\n', ln=config.language)
|
|
628
956
|
|
|
957
|
+
self.expand_folder_imports(fpath, self.build_dir)
|
|
958
|
+
|
|
629
959
|
# -------------------------------- EXPAND IMPORT * -------------------------------- #
|
|
630
960
|
_imports = self.find_chain_import(self.target_py, [os.path.dirname(self.src_dir), self.src_dir])
|
|
631
961
|
_js_imports = self.relist_pyimports_to_jsimports(self.build_dir, _imports)
|
|
@@ -664,10 +994,11 @@ class Compiler(CompilerBase):
|
|
|
664
994
|
else:
|
|
665
995
|
_pre_sort_[fname] = 65535
|
|
666
996
|
|
|
667
|
-
# ------------------------------------
|
|
997
|
+
# ------------------------------------ 自定义:mate & match ------------------------------------ #
|
|
668
998
|
for fpath in py_fpath:
|
|
669
999
|
content = self.auto_read(fpath)
|
|
670
1000
|
content = self.process_mate_code(content) # 调用process_mate_code
|
|
1001
|
+
content = self.convert_match_to_if(content) # 调用convert_match_to_if
|
|
671
1002
|
with open(fpath, 'w', encoding='utf-8') as f:
|
|
672
1003
|
f.write(content)
|
|
673
1004
|
|
|
@@ -756,9 +1087,9 @@ class Compiler(CompilerBase):
|
|
|
756
1087
|
return _imports, _js_imports, _pre_sort_, _pre_define_, _js_replace_
|
|
757
1088
|
|
|
758
1089
|
def transcrypt_cmd(self):
|
|
759
|
-
# 执行cmd命令: transcrypt -b -m -n -s -e 6 target | execute cmd: transcrypt -b -m -n -s -e 6 target
|
|
1090
|
+
# 执行cmd命令: python -m transcrypt -b -m -n -s -e 6 target | execute cmd: python -m transcrypt -b -m -n -s -e 6 target
|
|
760
1091
|
# 并获取cmd得到的输出 | and get the output of the cmd
|
|
761
|
-
cmd = 'transcrypt -b -m -n -s -e 6 %s' % self.target_py
|
|
1092
|
+
cmd = 'python -m transcrypt -b -m -n -s -e 6 %s' % self.target_py
|
|
762
1093
|
core.lprint(WAIT, core.lformat(LOC_TRANSCRYPTING, [cmd]), end="", ln=config.language)
|
|
763
1094
|
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
|
|
764
1095
|
stdout, stderr = p.communicate()
|
|
@@ -978,7 +1309,7 @@ class Compiler(CompilerBase):
|
|
|
978
1309
|
self.transcrypt_cmd()
|
|
979
1310
|
imports, modules = self.analyze_rebuild_main_js(defs, jimps)
|
|
980
1311
|
self.find_add_pure_js_files(sorts, modules)
|
|
981
|
-
total_js = imports + "\n" + self.generate_total_js(modules, imps, sorts, self.FILE_STRONG_REPLACE, reps)
|
|
1312
|
+
total_js = imports + "\n" + self.generate_total_js(replace_src_prefix(modules), imps, sorts, self.FILE_STRONG_REPLACE, reps)
|
|
982
1313
|
|
|
983
1314
|
core.lprint(WAIT, LOC_EXPORTING_TOTAL_MAIN_JS, end="", ln=config.language)
|
|
984
1315
|
|
|
@@ -1053,6 +1384,20 @@ class Compiler(CompilerBase):
|
|
|
1053
1384
|
|
|
1054
1385
|
|
|
1055
1386
|
if __name__ == '__main__':
|
|
1056
|
-
compiler = Compiler('src', 'library', 'build')
|
|
1057
|
-
compiler.compile()
|
|
1058
|
-
compiler.clean()
|
|
1387
|
+
# compiler = Compiler('src', 'library', 'build')
|
|
1388
|
+
# compiler.compile()
|
|
1389
|
+
# compiler.clean()
|
|
1390
|
+
test = """
|
|
1391
|
+
|
|
1392
|
+
def patrolling(self, c: Creep):
|
|
1393
|
+
e = self.center.nearest(k.civilian.enemies, 5)
|
|
1394
|
+
if e:
|
|
1395
|
+
match c.test(e,
|
|
1396
|
+
int(c.hpPer <= 0.9) * 2,
|
|
1397
|
+
int(c.info.melee) * -3
|
|
1398
|
+
):
|
|
1399
|
+
case True: c.move(e, SWAMP_MOTION)
|
|
1400
|
+
case False: c.move(self.bpos, SWAMP_MOTION)
|
|
1401
|
+
|
|
1402
|
+
"""
|
|
1403
|
+
print(f"res=\n{Compiler.convert_match_to_if(test)}")
|
|
@@ -17,6 +17,10 @@ LOC_FILE_NOT_EXISTS = {
|
|
|
17
17
|
'en': "{} File not exists: {}. You can ignore if it's a not used file.",
|
|
18
18
|
'cn': "{} 文件不存在: {}. 如果它是一个未使用的文件,您可以忽略它。",
|
|
19
19
|
}
|
|
20
|
+
LOC_FILE_READ_FAILED = {
|
|
21
|
+
'en': "Failed to read file: {}({})\nDetails:\n{}",
|
|
22
|
+
'cn': "读取文件失败: {}({})\n详细信息:\n{}",
|
|
23
|
+
}
|
|
20
24
|
LOC_PREPROCESSING = {
|
|
21
25
|
'en': "Preprocessing ...",
|
|
22
26
|
'cn': "预处理中 ...",
|
|
@@ -135,6 +139,12 @@ LOC_NOT_SUPPORT_PYFILE_INIT = {
|
|
|
135
139
|
'cn': '不支持 __init__.py!请删除它(初始化文件)并使用 from directory.xxx import xxxx 代替之。',
|
|
136
140
|
}
|
|
137
141
|
|
|
142
|
+
# V0.5.4
|
|
143
|
+
LOC_DIR_UNDER_NONINIT_DIR = {
|
|
144
|
+
'en': "Directory [{}] is located under [{}] without __init__.py, therefore it is ignored.",
|
|
145
|
+
'cn': "目录 [{}] 位于 `无__init__.py`的目录[{}]下,因此被忽略。",
|
|
146
|
+
}
|
|
147
|
+
|
|
138
148
|
if __name__ == '__main__':
|
|
139
149
|
lprint(TEMPLATE)
|
|
140
150
|
|
|
Binary file
|