pyscreeps-arena 0.5.7.6__tar.gz → 0.5.9b1__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.
Files changed (57) hide show
  1. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/PKG-INFO +1 -1
  2. pyscreeps_arena-0.5.9b1/pyscreeps_arena/afters/__init__.py +6 -0
  3. pyscreeps_arena-0.5.9b1/pyscreeps_arena/afters/after_config.py +84 -0
  4. pyscreeps_arena-0.5.9b1/pyscreeps_arena/afters/after_custom.py +87 -0
  5. pyscreeps_arena-0.5.9b1/pyscreeps_arena/afters/after_empty.py +34 -0
  6. pyscreeps_arena-0.5.9b1/pyscreeps_arena/afters/after_prefab.py +67 -0
  7. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/compiler.py +155 -31
  8. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/core/const.py +1 -1
  9. pyscreeps_arena-0.5.9b1/pyscreeps_arena/project.7z +0 -0
  10. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/project_ui.py +115 -2
  11. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena.egg-info/PKG-INFO +1 -1
  12. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena.egg-info/SOURCES.txt +5 -0
  13. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/setup.py +1 -1
  14. pyscreeps_arena-0.5.7.6/pyscreeps_arena/project.7z +0 -0
  15. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/__init__.py +0 -0
  16. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/build.py +0 -0
  17. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/core/__init__.py +0 -0
  18. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/core/basic.py +0 -0
  19. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/core/config.py +0 -0
  20. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/core/core.py +0 -0
  21. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/core/main.py +0 -0
  22. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/core/utils.py +0 -0
  23. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/localization.py +0 -0
  24. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/P2PY.py +0 -0
  25. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/__init__.py +0 -0
  26. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/creeplogic_edit.py +0 -0
  27. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/map_render.py +0 -0
  28. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/mapviewer.py +0 -0
  29. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qcreeplogic/__init__.py +0 -0
  30. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qcreeplogic/model.py +0 -0
  31. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qcreeplogic/qcreeplogic.py +0 -0
  32. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qmapker/__init__.py +0 -0
  33. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qmapker/qmapmarker.py +0 -0
  34. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qmapker/qvariable.py +0 -0
  35. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qmapker/test_compact_variable.py +0 -0
  36. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qmapker/test_qmapmarker.py +0 -0
  37. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qmapker/test_qvariable.py +0 -0
  38. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qmapker/to_code.py +0 -0
  39. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qmapv/__init__.py +0 -0
  40. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qmapv/qcinfo.py +0 -0
  41. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qmapv/qco.py +0 -0
  42. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qmapv/qmapv.py +0 -0
  43. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qmapv/test_array_drag.py +0 -0
  44. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qmapv/test_drag.py +0 -0
  45. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qmapv/test_qcinfo.py +0 -0
  46. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qmapv/test_qco_drag.py +0 -0
  47. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qmapv/test_qmapv.py +0 -0
  48. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qmapv/test_simple_array.py +0 -0
  49. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qrecipe/__init__.py +0 -0
  50. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qrecipe/model.py +0 -0
  51. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/qrecipe/qrecipe.py +0 -0
  52. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena/ui/rs_icon.py +0 -0
  53. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena.egg-info/dependency_links.txt +0 -0
  54. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena.egg-info/entry_points.txt +0 -0
  55. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena.egg-info/requires.txt +0 -0
  56. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/pyscreeps_arena.egg-info/top_level.txt +0 -0
  57. {pyscreeps_arena-0.5.7.6 → pyscreeps_arena-0.5.9b1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyscreeps-arena
3
- Version: 0.5.7.6
3
+ Version: 0.5.9b1
4
4
  Summary: Python api|interface to play game: Screeps: Arena.
5
5
  Author-email: 2229066748@qq.com
6
6
  Maintainer: Eagle'sBaby
@@ -0,0 +1,6 @@
1
+ from pyscreeps_arena.afters.after_empty import ToEmptyAfter
2
+ from pyscreeps_arena.afters.after_prefab import ToPrefabAfter
3
+ from pyscreeps_arena.afters.after_custom import ToCustomAfter
4
+ from pyscreeps_arena.afters.after_config import ToConfigAfter
5
+
6
+
@@ -0,0 +1,84 @@
1
+ import os
2
+ import re
3
+ import sys
4
+
5
+ def ToConfigAfter(path='.', language='cn', arena='gray', level='basic'):
6
+ # 验证参数是否在范围内
7
+ valid_languages = ['cn', 'en']
8
+ valid_arenas = ['green', 'blue', 'red', 'gray']
9
+ valid_levels = ['basic', 'advanced']
10
+
11
+ if language not in valid_languages:
12
+ print(f"错误: language 参数必须是 {valid_languages} 中的一个")
13
+ return False
14
+
15
+ if arena not in valid_arenas:
16
+ print(f"错误: arena 参数必须是 {valid_arenas} 中的一个")
17
+ return False
18
+
19
+ if level not in valid_levels:
20
+ print(f"错误: level 参数必须是 {valid_levels} 中的一个")
21
+ return False
22
+
23
+ # 检查 build.py 文件是否存在
24
+ build_py_path = os.path.join(path, 'build.py')
25
+ if not os.path.exists(build_py_path):
26
+ print("build.py 文件不存在,跳过修改操作")
27
+ return False
28
+
29
+ # 读取文件内容
30
+ with open(build_py_path, 'r', encoding='utf-8') as f:
31
+ content = f.read()
32
+
33
+ # 使用正则表达式替换配置语句
34
+ # 替换 language 配置
35
+ content = re.sub(
36
+ r'config\.language\s*=\s*[\'"]([^\'"]*)[\'"]\s*#\s*\'en\'\s+or\s+\'cn\'',
37
+ f"config.language = '{language}' # 'en' or 'cn'",
38
+ content
39
+ )
40
+
41
+ # 替换 arena 配置
42
+ content = re.sub(
43
+ r'config\.arena\s*=\s*[\'"]([^\'"]*)[\'"]\s*#\s*\'green\',\s*\'blue\',\s*\'red\',\s*\'gray\'',
44
+ f"config.arena = '{arena}' # 'green', 'blue', 'red', 'gray'",
45
+ content
46
+ )
47
+
48
+ # 替换 level 配置
49
+ content = re.sub(
50
+ r'config\.level\s*=\s*[\'"]([^\'"]*)[\'"]\s*#\s*\'basic\',\s*\'advanced\'',
51
+ f"config.level = '{level}' # 'basic', 'advanced'",
52
+ content
53
+ )
54
+
55
+ # 写回文件
56
+ with open(build_py_path, 'w', encoding='utf-8') as f:
57
+ f.write(content)
58
+
59
+ print(f"已更新 build.py 配置:")
60
+ print(f" language = {language}")
61
+ print(f" arena = {arena}")
62
+ print(f" level = {level}")
63
+ return True
64
+
65
+ if __name__ == "__main__":
66
+ # 解析命令行参数
67
+ # 默认值
68
+ path = '.'
69
+ language = 'cn'
70
+ arena = 'gray'
71
+ level = 'basic'
72
+
73
+ # 处理命令行参数
74
+ if len(sys.argv) > 1:
75
+ path = sys.argv[1]
76
+ if len(sys.argv) > 2:
77
+ language = sys.argv[2]
78
+ if len(sys.argv) > 3:
79
+ arena = sys.argv[3]
80
+ if len(sys.argv) > 4:
81
+ level = sys.argv[4]
82
+
83
+ # 执行更新
84
+ ToConfigAfter(path, language, arena, level)
@@ -0,0 +1,87 @@
1
+ import os
2
+ import re
3
+
4
+
5
+ def ToCustomAfter(path='.'):
6
+ # 检查 prefab 目录是否存在
7
+ prefab_dir = os.path.join(path, 'src', 'prefab')
8
+ if not os.path.exists(prefab_dir):
9
+ print("src/prefab 目录不存在,跳过处理")
10
+ return
11
+
12
+ # 创建 src/custom 目录
13
+ custom_dir = os.path.join(path, 'src', 'custom')
14
+ if not os.path.exists(custom_dir):
15
+ os.makedirs(custom_dir)
16
+ print("已创建 src/custom 目录")
17
+ else:
18
+ print("src/custom 目录已存在,跳过创建操作")
19
+
20
+ # 遍历 prefab 目录中的非 _ 开头的 .py 文件
21
+ for file_name in os.listdir(prefab_dir):
22
+ if file_name.startswith('_') or not file_name.endswith('.py'):
23
+ continue
24
+
25
+ file_path = os.path.join(prefab_dir, file_name)
26
+ print(f"处理文件: {file_path}")
27
+
28
+ # 读取文件内容
29
+ with open(file_path, 'r', encoding='utf-8') as f:
30
+ content = f.read()
31
+
32
+ # 检查是否存在 """$TEMPLATE...""" 的内容
33
+ template_match = re.search(r'"""(\$TEMPLATE.*?)"""', content, re.DOTALL)
34
+ if template_match:
35
+ template_content = template_match.group(1)
36
+ # 移除开头的 $TEMPLATE 标记
37
+ template_content = re.sub(r'^\s*\$TEMPLATE\s*', '', template_content)
38
+
39
+ # 在 custom 目录下创建同名文件
40
+ custom_file_path = os.path.join(custom_dir, file_name)
41
+ with open(custom_file_path, 'w', encoding='utf-8') as f:
42
+ f.write(template_content)
43
+ print(f"已在 custom 目录下创建 {file_name} 文件")
44
+ else:
45
+ print(f"{file_name} 中未找到模板内容,跳过")
46
+
47
+ # 检查并修改 src/main.py 文件,在导入语句末尾添加 from custom import *
48
+ main_py_path = os.path.join(path, 'src', 'main.py')
49
+ if not os.path.exists(main_py_path):
50
+ print("src/main.py 文件不存在,跳过导入语句添加操作")
51
+ return
52
+
53
+ # 读取文件内容
54
+ with open(main_py_path, 'r') as f:
55
+ lines = f.readlines()
56
+
57
+ # 检查是否存在 from custom import * 语句
58
+ has_custom_import = False
59
+ import_end_line = 0
60
+
61
+ for i, line in enumerate(lines):
62
+ if re.search(r'from\s+custom\s+import\s+\*', line):
63
+ has_custom_import = True
64
+ break
65
+ # 检查是否为导入语句
66
+ if re.match(r'^(from|import)\s+', line.strip()):
67
+ import_end_line = i + 1 # 记录导入语句的结束位置(下一行)
68
+
69
+ if not has_custom_import:
70
+ # 在导入语句的末尾插入 from custom import * 语句
71
+ if import_end_line > 0:
72
+ # 在导入语句末尾插入
73
+ lines.insert(import_end_line, '\nfrom custom import *\n')
74
+ else:
75
+ # 如果没有导入语句,在文件开头插入
76
+ lines.insert(0, 'from custom import *\n')
77
+
78
+ # 写回文件
79
+ with open(main_py_path, 'w') as f:
80
+ f.writelines(lines)
81
+ print("已在 src/main.py 中导入语句末尾追加 from custom import * 语句")
82
+ else:
83
+ print("src/main.py 中已存在 from custom import * 语句,跳过添加操作")
84
+
85
+
86
+ if __name__ == "__main__":
87
+ ToCustomAfter('.')
@@ -0,0 +1,34 @@
1
+ import os
2
+ import re
3
+
4
+ def ToEmptyAfter(path='.'):
5
+ # 删除 src/basic.py 文件
6
+ basic_path = os.path.join(path, 'src', 'basic.py')
7
+ if os.path.exists(basic_path):
8
+ os.remove(basic_path)
9
+ print("已删除 src/basic.py")
10
+ else:
11
+ print("src/basic.py 不存在,跳过删除操作")
12
+
13
+ # 修改 src/main.py 文件,将 from basic import * 改为 from builtin import *
14
+ main_path = os.path.join(path, 'src', 'main.py')
15
+ if os.path.exists(main_path):
16
+ with open(main_path, 'r') as f:
17
+ content = f.read()
18
+
19
+ # 使用正则表达式替换,处理各种可能的空白字符
20
+ modified_content = re.sub(r'from\s+basic\s+import\s+\*', 'from builtin import *', content)
21
+
22
+ # 检查是否发生了替换
23
+ if modified_content != content:
24
+ with open(main_path, 'w') as f:
25
+ f.write(modified_content)
26
+ print("已修改 src/main.py,将 from basic import * 改为 from builtin import *")
27
+ else:
28
+ print("src/main.py 中未找到 from basic import * 语句,跳过修改操作")
29
+ else:
30
+ print("src/main.py 不存在,跳过修改操作")
31
+
32
+ if __name__ == "__main__":
33
+ ToEmptyAfter('.')
34
+
@@ -0,0 +1,67 @@
1
+ import os
2
+ import shutil
3
+ import re
4
+
5
+ def ToPrefabAfter(path='.'):
6
+ # 检查 docs/prefab 目录是否存在
7
+ source_dir = os.path.join(path, 'docs', 'prefab')
8
+ if not os.path.exists(source_dir):
9
+ print("docs/prefab 目录不存在,跳过移动操作")
10
+ return
11
+
12
+ # 检查 src 目录是否存在
13
+ src_dir = os.path.join(path, 'src')
14
+ if not os.path.exists(src_dir):
15
+ os.makedirs(src_dir)
16
+ print("已创建 src 目录")
17
+
18
+ # 移动 docs/prefab 到 src/prefab
19
+ dest_dir = os.path.join(src_dir, 'prefab')
20
+ if os.path.exists(dest_dir):
21
+ # 如果目标目录已存在,先删除
22
+ shutil.rmtree(dest_dir)
23
+ print("已删除现有 src/prefab 目录")
24
+
25
+ shutil.move(source_dir, dest_dir)
26
+ print("已将 docs/prefab 移动到 src/prefab")
27
+
28
+ # 检查并修改 src/main.py 文件
29
+ main_py_path = os.path.join(src_dir, 'main.py')
30
+ if not os.path.exists(main_py_path):
31
+ print("src/main.py 文件不存在,跳过导入语句添加操作")
32
+ return
33
+
34
+ # 读取文件内容
35
+ with open(main_py_path, 'r') as f:
36
+ lines = f.readlines()
37
+
38
+ # 检查是否存在 from prefab import * 语句
39
+ has_prefab_import = False
40
+ import_end_line = 0
41
+
42
+ for i, line in enumerate(lines):
43
+ if re.search(r'from\s+prefab\s+import\s+\*', line):
44
+ has_prefab_import = True
45
+ break
46
+ # 检查是否为导入语句
47
+ if re.match(r'^(from|import)\s+', line.strip()):
48
+ import_end_line = i + 1 # 记录导入语句的结束位置(下一行)
49
+
50
+ if not has_prefab_import:
51
+ # 在导入语句的末尾插入 from prefab import * 语句
52
+ if import_end_line > 0:
53
+ # 在导入语句末尾插入
54
+ lines.insert(import_end_line, 'from prefab import *\n')
55
+ else:
56
+ # 如果没有导入语句,在文件开头插入
57
+ lines.insert(0, 'from prefab import *\n')
58
+
59
+ # 写回文件
60
+ with open(main_py_path, 'w') as f:
61
+ f.writelines(lines)
62
+ print("已在 src/main.py 中导入语句末尾追加 from prefab import * 语句")
63
+ else:
64
+ print("src/main.py 中已存在 from prefab import * 语句,跳过添加操作")
65
+
66
+ if __name__ == "__main__":
67
+ ToPrefabAfter('.')
@@ -8,6 +8,7 @@ import shutil
8
8
  import chardet
9
9
  import subprocess
10
10
  import pyperclip
11
+ import datetime
11
12
  from colorama import Fore
12
13
  from typing import List, Optional, Tuple, Union
13
14
 
@@ -17,6 +18,10 @@ python_version_info = sys.version_info
17
18
  python_version_info = f"{python_version_info.major}.{python_version_info.minor}.{python_version_info.micro}"
18
19
 
19
20
 
21
+ class MatchCaseError(Exception):
22
+ pass
23
+
24
+
20
25
  def replace_src_prefix(file_list):
21
26
  """
22
27
  将列表中以'./src.'开头的字符串替换为'./'
@@ -28,16 +33,17 @@ def replace_src_prefix(file_list):
28
33
  替换后的新列表
29
34
  """
30
35
  _ = []
31
-
36
+
32
37
  for item in file_list:
33
38
  if isinstance(item, str) and item.startswith('./src.'):
34
39
  _new = item.replace('./src.', './', 1)
35
40
  if _new in file_list:
36
41
  continue
37
42
  _.append(item)
38
-
43
+
39
44
  return _
40
45
 
46
+
41
47
  # def InsertPragmaBefore(content:str) -> str:
42
48
  # """
43
49
  # 在content的开头插入__pragma__('noalias', 'undefined')等内容 |
@@ -110,7 +116,7 @@ export var loop = function () {
110
116
  if (get._SCH_FLAG) sch.handle();
111
117
  stepCost = get.cpu_us() - timeLine;
112
118
  std.show_usage ();
113
- print("knowCost:", knowCost, "monitorCost:", monitorCost, "stepCost:", stepCost);
119
+ print("knowCost:", knowCost, "monitorCost:", monitorCost, "stepCost:", stepCost);
114
120
  if (know.draw) know.draw();
115
121
  };
116
122
  """
@@ -160,8 +166,8 @@ class GameConstructionBoost{{
160
166
  }};
161
167
  import {{ Portal as GamePortal}} from 'arena/season_{config.season}/{const.ARENA_GREEN}/{"advanced" if config.level in ["advance", "advanced"] else "basic"}/prototypes';
162
168
  """,
163
- # import {Portal} from 'arena/season_1/portal_exploration/basic/prototypes';
164
-
169
+ # import {Portal} from 'arena/season_1/portal_exploration/basic/prototypes';
170
+
165
171
  const.ARENA_BLUE: lambda: f"""
166
172
  const ARENA_COLOR_TYPE = "BLUE";
167
173
  const GameScoreCollector = GameStructureSpawn;
@@ -415,8 +421,7 @@ class Compiler_Utils(Compiler_Const):
415
421
  py_files.append(item)
416
422
  elif os.path.isdir(_path):
417
423
  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
-
424
+ core.warn(f'Compiler.expand_folder_imports', core.lformat(LOC_DIR_UNDER_NONINIT_DIR, [item, rel]), end='', head='\n', ln=config.language)
420
425
 
421
426
  # 为每个 .py 文件生成导入语句
422
427
  if py_files:
@@ -436,7 +441,6 @@ class Compiler_Utils(Compiler_Const):
436
441
  with open(fpath, 'w', encoding='utf-8') as f:
437
442
  f.write(new_content)
438
443
 
439
-
440
444
  def find_chain_import(self, fpath: str, search_dirs: list[str], project_path: str = None, records: dict[str, None] = None) -> list[str]:
441
445
  r"""
442
446
  查找文件中的所有import语句,并返回所有import的文件路径 | find all import statements in a file and return the paths of all imported files
@@ -572,13 +576,13 @@ class Compiler_Utils(Compiler_Const):
572
576
  return imps
573
577
 
574
578
  @staticmethod
575
- def relist_pyimports_to_jsimports(base_dir:str, pyimps:list[str]) -> list[str]:
579
+ def relist_pyimports_to_jsimports(base_dir: str, pyimps: list[str]) -> list[str]:
576
580
  """
577
581
  将python的imports路径列表转换为js的imports路径列表 | convert a list of python imports paths to a list of js imports paths
578
582
  """
579
583
  jsimps = []
580
584
  for pyimp in pyimps:
581
- rel_path_nodes:list[str] = os.path.relpath(pyimp, base_dir).replace('\\', '/').split('/')
585
+ rel_path_nodes: list[str] = os.path.relpath(pyimp, base_dir).replace('\\', '/').split('/')
582
586
  if rel_path_nodes[-1] == '__init__.py':
583
587
  rel_path_nodes.pop()
584
588
  else:
@@ -644,7 +648,83 @@ class Compiler_Utils(Compiler_Const):
644
648
  result = '\n'.join(calls_to_add) + '\n' + result
645
649
 
646
650
  return result
647
-
651
+
652
+ @staticmethod
653
+ def stage_called_replace(caller_name: str, content: str) -> str:
654
+ """
655
+ 移除 '@<caller_name>(...)' 装饰器行,并在文末添加对应的 _<caller_name>Login 调用。
656
+
657
+ 对于类方法: _<caller_name>Login("ClassName", "method_name", a, b, ...)
658
+ 对于普通函数: _<caller_name>Login("", "function_name", a, b, ...)
659
+ """
660
+ calls_to_add = []
661
+ deletions = []
662
+
663
+ # 1. 收集所有类定义的位置和缩进
664
+ class_pattern = re.compile(r'^(\s*)class\s+(\w+)', re.MULTILINE)
665
+ classes = [(m.start(), len(m.group(1)), m.group(2))
666
+ for m in class_pattern.finditer(content)]
667
+
668
+ # 2. 查找所有 @<caller_name>(...) 装饰器(支持多行参数)
669
+ decorator_pattern = re.compile(
670
+ r'^\s*@\s*' + re.escape(caller_name) + r'\s*\((.*?)\)\s*$\n?',
671
+ re.MULTILINE | re.DOTALL
672
+ )
673
+
674
+ for dec_match in decorator_pattern.finditer(content):
675
+ dec_start = dec_match.start()
676
+ dec_end = dec_match.end()
677
+ # 提取装饰器的参数
678
+ params_str = dec_match.group(1).strip()
679
+
680
+ # 查找接下来的函数定义(跳过可能的空行)
681
+ after_decorator = content[dec_end:]
682
+ func_match = re.search(r'^(\s*)def\s+([^\s\(]+)', after_decorator, re.MULTILINE)
683
+
684
+ if not func_match:
685
+ continue
686
+
687
+ func_indent_len = len(func_match.group(1))
688
+ func_name = func_match.group(2)
689
+
690
+ # 3. 确定类名:查找装饰器前最近的、缩进小于函数缩进的类
691
+ class_name = ""
692
+ for cls_pos, cls_indent_len, cls_name in reversed(classes):
693
+ if cls_pos < dec_match.start() and func_indent_len > cls_indent_len:
694
+ class_name = cls_name
695
+ break
696
+
697
+ # 4. 处理参数,保持参数的格式
698
+ # 移除参数中的换行和多余空格,保持参数列表的格式
699
+ params = []
700
+ if params_str:
701
+ # 简单处理参数,保持引号内的内容不变
702
+ # 这里可以根据需要进行更复杂的参数解析
703
+ params = [p.strip() for p in params_str.split(',') if p.strip()]
704
+
705
+ # 构建参数部分的字符串
706
+ params_part = ""
707
+ if params:
708
+ params_part = ", " + ", ".join(params)
709
+
710
+ # 5. 记录删除位置和调用信息
711
+ deletions.append((dec_start, dec_end))
712
+ calls_to_add.append(f'_{caller_name}Login("{class_name}", "{func_name}"{params_part})')
713
+
714
+ # 6. 应用删除(倒序避免位置偏移)
715
+ if not deletions:
716
+ return content
717
+
718
+ result = content
719
+ for start, end in sorted(deletions, key=lambda x: x[0], reverse=True):
720
+ result = result[:start] + result[end:]
721
+
722
+ # 7. 在文末添加调用
723
+ if calls_to_add:
724
+ result = '\n'.join(calls_to_add) + '\n' + result
725
+
726
+ return result
727
+
648
728
  @staticmethod
649
729
  def process_mate_code(code):
650
730
  # 用于存储匹配到的信息
@@ -678,12 +758,11 @@ class Compiler_Utils(Compiler_Const):
678
758
  for variable_name, class_name in mate_assignments:
679
759
  output_string = f"# > insert Object.defineProperty ({class_name}, '{variable_name}', property.call ({class_name}, {class_name}.{variable_name}._MateGet_, {class_name}.{variable_name}._MateSet_));"
680
760
  output_strings.append(output_string)
681
-
682
- return code + '\n'.join(output_strings)
683
761
 
762
+ return code + '\n'.join(output_strings)
684
763
 
685
764
  @staticmethod
686
- def remove_long_docstring(content:str) -> str:
765
+ def remove_long_docstring(content: str) -> str:
687
766
  """
688
767
  移除长注释 | remove long docstring
689
768
  """
@@ -1075,14 +1154,16 @@ class Compiler(CompilerBase):
1075
1154
  with open(fpath, 'w', encoding='utf-8') as f:
1076
1155
  f.write(new_content)
1077
1156
 
1078
- # ------------------------------------ 自定义:调用stage_recursive_replace ------------------------------------ #
1157
+ # ------------------------------------ 自定义:调用stage_recursive_replace和stage_called_replace ------------------------------------ #
1079
1158
  for fpath in py_fpath:
1080
1159
  content = self.auto_read(fpath)
1081
1160
  content = self.stage_recursive_replace(content) # 调用stage_recursive_replace
1161
+ # 调用stage_called_replace处理四个装饰器
1162
+ for caller in ['behavior', 'sequence', 'selector', 'parallel']:
1163
+ content = self.stage_called_replace(caller, content)
1082
1164
  with open(fpath, 'w', encoding='utf-8') as f:
1083
1165
  f.write(content)
1084
1166
 
1085
-
1086
1167
  core.lprint(GREEN.format('[2/6]'), LOC_DONE, " ", LOC_PREPROCESSING_FINISH, sep="", head="\r", ln=config.language)
1087
1168
  return _imports, _js_imports, _pre_sort_, _pre_define_, _js_replace_
1088
1169
 
@@ -1153,7 +1234,7 @@ class Compiler(CompilerBase):
1153
1234
 
1154
1235
  content = self.auto_read(self.target_js)
1155
1236
  if modules is None: modules = []
1156
- new_modules, new_content = [],""
1237
+ new_modules, new_content = [], ""
1157
1238
  for line in content.split('\n'):
1158
1239
  m = re.search(self.JS_IMPORT_PAT, line)
1159
1240
  if not m:
@@ -1199,7 +1280,7 @@ class Compiler(CompilerBase):
1199
1280
  """
1200
1281
  return re.sub(r'import[^\n]*\n', '', raw)
1201
1282
 
1202
- def generate_total_js(self, usr_modules, t_imps: list[str], f_sorts, f_replaces, g_replaces) -> str:
1283
+ def generate_total_js(self, usr_modules, t_imps: list[str], f_sorts, f_replaces, g_replaces, min_js_files=None) -> str:
1203
1284
  """
1204
1285
  生成总的main.js
1205
1286
  按照如下顺序组合:
@@ -1213,12 +1294,30 @@ class Compiler(CompilerBase):
1213
1294
  :param f_sorts: dict{module_name: sort_priority}
1214
1295
  :param f_replaces: dict{module_name: dict{old: new}}
1215
1296
  :param g_replaces: dict{old: new}
1297
+ :param min_js_files: list[str] # .min.js文件路径列表
1216
1298
  :return: str
1217
1299
  """
1218
- arena_name = const.ARENA_NAMES.get(config.arena, const.ARENA_NAMES['green']) # like green -> spawn_and_swamp
1300
+ arena_name = const.ARENA_NAMES.get(config.arena, const.ARENA_NAMES["green"]) # like green -> spawn_and_swamp
1219
1301
  self.TOTAL_INSERT_AT_HEAD += self.ARENA_IMPORTS_GETTER[arena_name]() # add arena imports
1220
- total_js = f"const __VERSION__ = '{const.VERSION}';\nconst __PYTHON_VERSION__ = '{python_version_info}';" + self.TOTAL_INSERT_AT_HEAD + f"\nexport var LANGUAGE = '{config.language}';\n"
1221
- total_js += f"const __AUTHOR__ = '{const.AUTHOR}';\nconst __AUTHOR_CN__ = '{const.BILIBILI_NAME}';"
1302
+ current_time = datetime.datetime.now()
1303
+ timestamp_ms = int(current_time.timestamp() * 1000)
1304
+ timestring = current_time.strftime("%Y-%m-%d %H:%M")
1305
+
1306
+ total_js = f"""const __VERSION__ = '{const.VERSION}';
1307
+ const __PYTHON_VERSION__ = '{python_version_info}';""" + self.TOTAL_INSERT_AT_HEAD + f"""
1308
+ export var LANGUAGE = '{config.language}';
1309
+ """
1310
+
1311
+ total_js += f"export var TIMESTAMP = {timestamp_ms};\n"
1312
+ total_js += f"export var TIMESTRING = '{timestring}';\n"
1313
+ total_js += f"""const __AUTHOR__ = '{const.AUTHOR}';
1314
+ const __AUTHOR_CN__ = '{const.BILIBILI_NAME}';"""
1315
+
1316
+ if min_js_files:
1317
+ for min_js_path in min_js_files:
1318
+ min_js_filename = os.path.basename(min_js_path)
1319
+ total_js += f"\nimport \"./{min_js_filename}\";"
1320
+ total_js += "\n"
1222
1321
 
1223
1322
  core.lprint(WAIT, LOC_GENERATING_TOTAL_MAIN_JS, end="", ln=config.language)
1224
1323
 
@@ -1285,19 +1384,29 @@ class Compiler(CompilerBase):
1285
1384
  def find_add_pure_js_files(self, sorts, modules):
1286
1385
  """
1287
1386
  找到所有的纯js文件,并添加到modules中
1387
+ 忽略.min.js文件,这些文件会被单独处理
1288
1388
  :param sorts:
1289
1389
  :param modules:
1290
- :return:
1390
+ :return: list 返回所有.min.js文件的列表
1291
1391
  """
1392
+ min_js_files = []
1292
1393
  for root, dirs, files in os.walk(self.lib_dir):
1293
1394
  for file in files:
1294
1395
  if file.endswith('.js') and file not in modules:
1295
- fpath = str(os.path.join(root, file))
1296
- fname = file.replace('\\', '/')
1297
- # copy file to target
1298
- shutil.copy(fpath, os.path.join(self.target_dir, fname))
1299
- sorts[fname] = self.__parse_js_file_sort(fpath)
1300
- modules.append("./" + fname)
1396
+ # 如果是.min.js文件,不拷贝到target,而是记录到单独列表
1397
+ if file.endswith('.min.js'):
1398
+ fpath = str(os.path.join(root, file))
1399
+ min_js_files.append(fpath)
1400
+ else:
1401
+ # 普通js文件,按原逻辑处理
1402
+ fpath = str(os.path.join(root, file))
1403
+ fname = file.replace('\\', '/')
1404
+ # copy file to target
1405
+ shutil.copy(fpath, os.path.join(self.target_dir, fname))
1406
+ sorts[fname] = self.__parse_js_file_sort(fpath)
1407
+ modules.append("./" + fname)
1408
+
1409
+ return min_js_files
1301
1410
 
1302
1411
  def compile(self, paste=False):
1303
1412
  """
@@ -1308,8 +1417,7 @@ class Compiler(CompilerBase):
1308
1417
  imps, jimps, sorts, defs, reps = self.pre_compile()
1309
1418
  self.transcrypt_cmd()
1310
1419
  imports, modules = self.analyze_rebuild_main_js(defs, jimps)
1311
- self.find_add_pure_js_files(sorts, modules)
1312
- total_js = imports + "\n" + self.generate_total_js(replace_src_prefix(modules), imps, sorts, self.FILE_STRONG_REPLACE, reps)
1420
+ min_js_files = self.find_add_pure_js_files(sorts, modules)
1313
1421
 
1314
1422
  core.lprint(WAIT, LOC_EXPORTING_TOTAL_MAIN_JS, end="", ln=config.language)
1315
1423
 
@@ -1320,17 +1428,33 @@ class Compiler(CompilerBase):
1320
1428
  if not mjs_path.endswith('js'):
1321
1429
  mjs_path = os.path.join(mjs_path, 'main.mjs')
1322
1430
 
1431
+ # 获取目标目录路径
1432
+ dir_path = os.path.dirname(mjs_path)
1433
+ build_dir_path = os.path.dirname(build_main_mjs)
1434
+
1435
+ # 生成total_js,传入.min.js文件列表
1436
+ total_js = imports + "\n" + self.generate_total_js(
1437
+ replace_src_prefix(modules), imps, sorts, self.FILE_STRONG_REPLACE, reps, min_js_files
1438
+ )
1439
+
1323
1440
  # write main.mjs
1324
1441
  with open(build_main_mjs, 'w', encoding='utf-8') as f:
1325
1442
  f.write(total_js)
1326
1443
 
1327
1444
  # export main.mjs
1328
- dir_path = os.path.dirname(mjs_path)
1329
1445
  if not os.path.exists(dir_path):
1330
1446
  core.error('Compiler.compile', core.lformat(LOC_EXPORT_DIR_PATH_NOT_EXISTS, [dir_path]), head='\n', ln=config.language)
1331
1447
  with open(mjs_path, 'w', encoding='utf-8') as f:
1332
1448
  f.write(total_js)
1333
1449
 
1450
+ # 复制.min.js文件到目标目录
1451
+ for min_js_path in min_js_files:
1452
+ min_js_filename = os.path.basename(min_js_path)
1453
+ # 复制到build目录
1454
+ shutil.copy(min_js_path, os.path.join(build_dir_path, min_js_filename))
1455
+ # 复制到最终导出目录
1456
+ shutil.copy(min_js_path, os.path.join(dir_path, min_js_filename))
1457
+
1334
1458
  core.lprint(GREEN.format('[6/6]'), LOC_DONE, " ", LOC_EXPORTING_TOTAL_MAIN_JS_FINISH, sep="", head="\r", ln=config.language)
1335
1459
 
1336
1460
  if mjs_path != build_main_mjs:
@@ -9,7 +9,7 @@
9
9
  #
10
10
  import re
11
11
 
12
- VERSION = "0.5.7.6"
12
+ VERSION = "0.5.9b"
13
13
  AUTHOR = "●ω<🤍♪"
14
14
  STEAM_ID = "1029562896"
15
15
  GITHUB_NAME = "EagleBaby"
@@ -7,12 +7,14 @@ import os
7
7
  from pathlib import Path
8
8
  from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
9
9
  QHBoxLayout, QLabel, QLineEdit, QPushButton,
10
- QFileDialog, QMessageBox)
10
+ QFileDialog, QMessageBox, QComboBox)
11
11
  from PyQt6.QtCore import Qt
12
12
  from PyQt6.QtGui import QFont
13
13
  from PyQt6.QtGui import QIcon
14
14
  from pyscreeps_arena.core import const
15
15
  from pyscreeps_arena.ui.rs_icon import get_pixmap
16
+ from pyscreeps_arena.afters import ToConfigAfter, ToEmptyAfter, ToPrefabAfter, ToCustomAfter
17
+ from PyQt6.QtWidgets import QRadioButton, QGroupBox
16
18
 
17
19
 
18
20
 
@@ -27,7 +29,7 @@ class ProjectCreatorUI(QMainWindow):
27
29
  """初始化UI界面"""
28
30
  self.setWindowTitle("PyScreeps Arena - 项目创建器")
29
31
  self.setWindowIcon(QIcon(get_pixmap()))
30
- self.setFixedSize(500, 350)
32
+ self.setFixedSize(500, 580)
31
33
 
32
34
  # 主窗口部件
33
35
  central_widget = QWidget()
@@ -85,6 +87,7 @@ class ProjectCreatorUI(QMainWindow):
85
87
  self._name_input = QLineEdit()
86
88
  self._name_input.setPlaceholderText("输入项目名称...")
87
89
  self._name_input.textChanged.connect(self._on_name_changed)
90
+ self._name_input.returnPressed.connect(self._create_project)
88
91
  name_layout.addWidget(name_label)
89
92
  name_layout.addWidget(self._name_input)
90
93
  input_layout.addLayout(name_layout)
@@ -113,6 +116,86 @@ class ProjectCreatorUI(QMainWindow):
113
116
  input_layout.addLayout(path_layout)
114
117
  layout.addWidget(input_widget)
115
118
 
119
+ # 分隔线
120
+ separator2 = QLabel("─" * 50)
121
+ separator2.setAlignment(Qt.AlignmentFlag.AlignCenter)
122
+ separator2.setStyleSheet("color: #ccc;")
123
+ layout.addWidget(separator2)
124
+
125
+ # 配置选项区域(左右布局)
126
+ config_container = QWidget()
127
+ config_container_layout = QHBoxLayout(config_container)
128
+ config_container_layout.setSpacing(20)
129
+
130
+ # 左侧:语言、竞技场、难度配置
131
+ left_config_widget = QWidget()
132
+ left_config_layout = QVBoxLayout(left_config_widget)
133
+ left_config_layout.setSpacing(10)
134
+
135
+ # 语言选择
136
+ lang_layout = QHBoxLayout()
137
+ lang_label = QLabel("语 言:")
138
+ lang_label.setFixedWidth(80)
139
+ self._lang_combo = QComboBox()
140
+ self._lang_combo.addItems(["中文 (cn)", "英文 (en)"])
141
+ self._lang_combo.setCurrentIndex(0)
142
+ lang_layout.addWidget(lang_label)
143
+ lang_layout.addWidget(self._lang_combo)
144
+ left_config_layout.addLayout(lang_layout)
145
+
146
+ # 竞技场选择
147
+ arena_layout = QHBoxLayout()
148
+ arena_label = QLabel("竞技场:")
149
+ arena_label.setFixedWidth(80)
150
+ self._arena_combo = QComboBox()
151
+ self._arena_combo.addItems(["灰色 (gray)", "绿色 (green)", "蓝色 (blue)", "红色 (red)"])
152
+ self._arena_combo.setCurrentIndex(0)
153
+ arena_layout.addWidget(arena_label)
154
+ arena_layout.addWidget(self._arena_combo)
155
+ left_config_layout.addLayout(arena_layout)
156
+
157
+ # 难度级别选择
158
+ level_layout = QHBoxLayout()
159
+ level_label = QLabel("难 度:")
160
+ level_label.setFixedWidth(80)
161
+ self._level_combo = QComboBox()
162
+ self._level_combo.addItems(["基础 (basic)", "高级 (advanced)"])
163
+ self._level_combo.setCurrentIndex(0)
164
+ level_layout.addWidget(level_label)
165
+ level_layout.addWidget(self._level_combo)
166
+ left_config_layout.addLayout(level_layout)
167
+
168
+ config_container_layout.addWidget(left_config_widget)
169
+
170
+ # 右侧:模式选择(单选框)
171
+ right_config_widget = QWidget()
172
+ right_config_layout = QVBoxLayout(right_config_widget)
173
+ right_config_layout.setSpacing(8)
174
+
175
+ # 模式选择标题
176
+ # mode_title = QLabel("模式选择")
177
+ # mode_title.setFont(QFont("Arial", 10, QFont.Weight.Bold))
178
+ # right_config_layout.addWidget(mode_title)
179
+
180
+ # 单选按钮
181
+ self._empty_radio = QRadioButton("空白 (Empty)")
182
+ self._basic_radio = QRadioButton("基础 (Basic)")
183
+ self._prefab_radio = QRadioButton("预设 (Prefab)")
184
+ self._pi_radio = QRadioButton("预设继承 (P&&I)")
185
+
186
+ # 默认选择基础模式
187
+ self._basic_radio.setChecked(True)
188
+
189
+ # 添加到布局
190
+ right_config_layout.addWidget(self._empty_radio)
191
+ right_config_layout.addWidget(self._basic_radio)
192
+ right_config_layout.addWidget(self._prefab_radio)
193
+ right_config_layout.addWidget(self._pi_radio)
194
+
195
+ config_container_layout.addWidget(right_config_widget)
196
+
197
+ layout.addWidget(config_container)
198
+
116
199
  # 按钮区域
117
200
  button_layout = QHBoxLayout()
118
201
  button_layout.addStretch()
@@ -122,6 +205,7 @@ class ProjectCreatorUI(QMainWindow):
122
205
  self._create_btn.setFixedSize(100, 35)
123
206
  self._create_btn.clicked.connect(self._create_project)
124
207
  self._create_btn.setEnabled(False)
208
+ self._create_btn.setDefault(True)
125
209
  button_layout.addWidget(self._create_btn)
126
210
 
127
211
  # 取消按钮
@@ -201,6 +285,35 @@ class ProjectCreatorUI(QMainWindow):
201
285
  archive.extractall(path=target_path)
202
286
 
203
287
  print(f"[DEBUG] 项目模板已解压到: {target_path}") # 调试输出
288
+
289
+ # 获取用户选择的配置
290
+ lang_text = self._lang_combo.currentText()
291
+ lang = lang_text.split('(')[1].strip(')')
292
+
293
+ arena_text = self._arena_combo.currentText()
294
+ arena = arena_text.split('(')[1].strip(')')
295
+
296
+ level_text = self._level_combo.currentText()
297
+ level = level_text.split('(')[1].strip(')')
298
+
299
+ # 调用配置方法
300
+ ToConfigAfter(path=target_path, language=lang, arena=arena, level=level)
301
+ print(f"[DEBUG] 项目配置已更新: language={lang}, arena={arena}, level={level}") # 调试输出
302
+
303
+ # 根据选择的模式执行相应的操作
304
+ if self._empty_radio.isChecked():
305
+ print(f"[DEBUG] 执行空白模式配置")
306
+ ToEmptyAfter(path=target_path)
307
+ elif self._basic_radio.isChecked():
308
+ print(f"[DEBUG] 执行基础模式配置(不操作)")
309
+ # 基础模式不执行任何操作
310
+ elif self._prefab_radio.isChecked():
311
+ print(f"[DEBUG] 执行预设模式配置")
312
+ ToPrefabAfter(path=target_path)
313
+ elif self._pi_radio.isChecked():
314
+ print(f"[DEBUG] 执行预设继承模式配置")
315
+ ToPrefabAfter(path=target_path)
316
+ ToCustomAfter(path=target_path)
204
317
 
205
318
 
206
319
  def run_project_creator():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyscreeps-arena
3
- Version: 0.5.7.6
3
+ Version: 0.5.9b1
4
4
  Summary: Python api|interface to play game: Screeps: Arena.
5
5
  Author-email: 2229066748@qq.com
6
6
  Maintainer: Eagle'sBaby
@@ -10,6 +10,11 @@ pyscreeps_arena.egg-info/dependency_links.txt
10
10
  pyscreeps_arena.egg-info/entry_points.txt
11
11
  pyscreeps_arena.egg-info/requires.txt
12
12
  pyscreeps_arena.egg-info/top_level.txt
13
+ pyscreeps_arena/afters/__init__.py
14
+ pyscreeps_arena/afters/after_config.py
15
+ pyscreeps_arena/afters/after_custom.py
16
+ pyscreeps_arena/afters/after_empty.py
17
+ pyscreeps_arena/afters/after_prefab.py
13
18
  pyscreeps_arena/core/__init__.py
14
19
  pyscreeps_arena/core/basic.py
15
20
  pyscreeps_arena/core/config.py
@@ -7,7 +7,7 @@ with open(r"T:\New_PC\Import_Project\uploads\pyscreeps-arena_upload\pyscreeps-ar
7
7
  long_description = f.read()
8
8
  setup(
9
9
  name='pyscreeps-arena',
10
- version='0.5.7.6',
10
+ version='0.5.9b1',
11
11
  description='Python api|interface to play game: Screeps: Arena.',
12
12
  long_description=long_description,
13
13
  long_description_content_type='text/markdown',