momo-ai 1.0.21 → 1.0.22
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.
- package/.claude/skills/r2mo-rad-lain/SKILL.md +63 -374
- package/.trae/skills/algorithmic-art/LICENSE.txt +202 -0
- package/.trae/skills/algorithmic-art/SKILL.md +405 -0
- package/.trae/skills/algorithmic-art/templates/generator_template.js +223 -0
- package/.trae/skills/algorithmic-art/templates/viewer.html +599 -0
- package/.trae/skills/doc-coauthoring/SKILL.md +375 -0
- package/.trae/skills/frontend-design/LICENSE.txt +177 -0
- package/.trae/skills/frontend-design/SKILL.md +42 -0
- package/.trae/skills/r2mo-rad-lain/SKILL.md +101 -0
- package/README.md +9 -32
- package/docs/images/r2mo-lain.png +0 -0
- package/package.json +11 -11
- package/skills/r2mo-rad-domain/SKILL.md +70 -0
- package/src/_skill/repositories.json +9 -3
- package/src/_template/LAIN/.obsidian/app.json +1 -0
- package/src/_template/LAIN/.obsidian/appearance.json +10 -0
- package/src/_template/LAIN/.obsidian/community-plugins.json +7 -0
- package/src/_template/LAIN/.obsidian/core-plugins.json +33 -0
- package/src/_template/LAIN/.obsidian/plugins/dataview/main.js +20876 -0
- package/src/_template/LAIN/.obsidian/plugins/dataview/manifest.json +11 -0
- package/src/_template/LAIN/.obsidian/plugins/dataview/styles.css +141 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/data.json +815 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/main.js +10 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/manifest.json +12 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/styles.css +1 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/main.js +153 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/manifest.json +11 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/styles.css +1 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/main.js +7732 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/manifest.json +10 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/styles.css +38 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/main.js +504 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/manifest.json +12 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/styles.css +1 -0
- package/src/_template/LAIN/.obsidian/snippets/body-font.css +27 -0
- package/src/_template/LAIN/.obsidian/themes/Primary/manifest.json +9 -0
- package/src/_template/LAIN/.obsidian/themes/Primary/theme.css +3878 -0
- package/src/_template/LAIN/.obsidian/themes/Retro Windows/manifest.json +7 -0
- package/src/_template/LAIN/.obsidian/themes/Retro Windows/theme.css +582 -0
- package/src/_template/LAIN/.obsidian/themes/RetroOS 98/manifest.json +9 -0
- package/src/_template/LAIN/.obsidian/themes/RetroOS 98/theme.css +2566 -0
- package/src/_template/LAIN/.obsidian/types.json +28 -0
- package/src/_template/LAIN/.obsidian/workspace.json +184 -0
- package/src/_template/LAIN/AGENTS.md +170 -16
- package/src/_template/R2MO/domain-enhance.md +10 -0
- package/src/commander/app.json +13 -0
- package/src/commander/apply.json +13 -0
- package/src/commander/ask.json +6 -0
- package/src/commander/docs.json +13 -0
- package/src/commander/domain.json +19 -0
- package/src/commander/init.json +1 -1
- package/src/commander/mmr0.json +6 -0
- package/src/commander/mmr2.json +6 -0
- package/src/executor/executeApp.js +133 -0
- package/src/executor/{executeSkills.js → executeApply.js} +166 -302
- package/src/executor/executeAsk.js +274 -0
- package/src/executor/executeDocs.js +498 -0
- package/src/executor/executeDomain.js +293 -0
- package/src/executor/executeInit.js +159 -383
- package/src/executor/executeMcp.js +74 -1
- package/src/executor/executeMmr0.js +488 -0
- package/src/executor/executeMmr2.js +880 -0
- package/src/executor/index.js +15 -3
- package/src/python/r2mo_proto.py +418 -0
- package/src/python/r2mo_proto_database.py +369 -0
- package/src/python/r2mo_proto_domain.py +458 -0
- package/src/utils/momo-menu.js +43 -13
- package/.claude/skills/r2mo-rad-lain/PROMPT.md +0 -281
- package/.claude/skills/r2mo-rad-lain/README.md +0 -192
- package/.claude/skills/r2mo-rad-lain/examples/argument-parsing.js +0 -154
- package/.claude/skills/r2mo-rad-lain/examples/file-operations.js +0 -182
- package/.claude/skills/r2mo-rad-lain/file-utils-api.md +0 -281
- package/.claude/skills/r2mo-rad-lain/menu-api.md +0 -187
- package/.claude/skills/r2mo-rad-lain/scripts/file-utils.js +0 -223
- package/.claude/skills/r2mo-rad-lain/scripts/menu.js +0 -289
- package/.claude/skills/r2mo-rad-lain/scripts/yaml-parser.js +0 -209
- package/.claude/skills/r2mo-rad-lain/templates/command.json.template +0 -13
- package/.claude/skills/r2mo-rad-lain/templates/executor.js.template +0 -32
- package/.claude/skills/r2mo-rad-lain/templates/interactive-menu.js.template +0 -221
- package/src/_template/LAIN/.momo/advanced/actor.md +0 -42
- package/src/_template/LAIN/.momo/advanced/refer.json +0 -46
- package/src/_template/LAIN/.momo/scripts/submodule-clean.sh +0 -56
- package/src/_template/LAIN/changes/proposal.md +0 -39
- package/src/_template/LAIN/changes/tasks/task-detail.md +0 -45
- package/src/_template/LAIN/changes/tasks.md +0 -49
- package/src/_template/LAIN/execute/admin-n-f-dashboard.md +0 -53
- package/src/_template/LAIN/execute/admin-n-f-form.md +0 -51
- package/src/_template/LAIN/execute/admin-n-f-home.md +0 -49
- package/src/_template/LAIN/execute/admin-n-f-list.md +0 -52
- package/src/_template/LAIN/execute/admin-n-f-login.md +0 -56
- package/src/_template/LAIN/specification/project-model.md +0 -13
- package/src/_template/LAIN/specification/project.md +0 -73
- package/src/_template/LAIN/specification/requirement.md +0 -25
- package/src/commander/skills.json +0 -20
package/src/executor/index.js
CHANGED
|
@@ -2,15 +2,27 @@ const executeHelp = require('./executeHelp');
|
|
|
2
2
|
const executeInit = require('./executeInit');
|
|
3
3
|
const executeEnv = require('./executeEnv');
|
|
4
4
|
const executeOpen = require('./executeOpen');
|
|
5
|
-
const executeSkills = require('./executeSkills');
|
|
6
5
|
const executeMcp = require('./executeMcp');
|
|
6
|
+
const executeApp = require('./executeApp');
|
|
7
|
+
const executeApply = require('./executeApply');
|
|
8
|
+
const executeDomain = require('./executeDomain');
|
|
9
|
+
const executeAsk = require('./executeAsk');
|
|
10
|
+
const executeMmr2 = require('./executeMmr2');
|
|
11
|
+
const executeMmr0 = require('./executeMmr0');
|
|
12
|
+
const executeDocs = require('./executeDocs');
|
|
7
13
|
|
|
8
14
|
const exported = {
|
|
9
15
|
executeHelp,
|
|
10
16
|
executeInit,
|
|
11
17
|
executeEnv,
|
|
12
18
|
executeOpen,
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
executeMcp,
|
|
20
|
+
executeApp,
|
|
21
|
+
executeApply,
|
|
22
|
+
executeDomain,
|
|
23
|
+
executeAsk,
|
|
24
|
+
executeMmr2,
|
|
25
|
+
executeMmr0,
|
|
26
|
+
executeDocs
|
|
15
27
|
};
|
|
16
28
|
module.exports = exported;
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
R2MO Java to Protobuf Converter (V8 - Enhanced Comments & Constraints)
|
|
6
|
+
更新内容:
|
|
7
|
+
1. 【新增】从 @Schema(description) 中提取中文注释
|
|
8
|
+
2. 【新增】约束信息增强:长度、必填、数值范围等
|
|
9
|
+
3. 【优化】注释优先级:Schema description > JavaDoc > 行尾注释
|
|
10
|
+
4. 保留所有 V7 特性(自动导入、Enum 排序、BaseEntity 补全、注解提取、路径推导)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
import argparse
|
|
17
|
+
import xml.etree.ElementTree as ET
|
|
18
|
+
|
|
19
|
+
# ================= 1. 配置与映射表 =================
|
|
20
|
+
|
|
21
|
+
BASE_ENTITY_FIELDS = [
|
|
22
|
+
{'name': 'key', 'type': 'string', 'label': '', 'comment': '主键'},
|
|
23
|
+
{'name': 'active', 'type': 'bool', 'label': '', 'comment': '是否启用'},
|
|
24
|
+
{'name': 'sigma', 'type': 'string', 'label': '', 'comment': '统一标识'},
|
|
25
|
+
{'name': 'metadata', 'type': 'string', 'label': '', 'comment': '附加配置'},
|
|
26
|
+
{'name': 'language', 'type': 'string', 'label': '', 'comment': '语言'},
|
|
27
|
+
{'name': 'created_at', 'type': 'string', 'label': '', 'comment': '创建时间'},
|
|
28
|
+
{'name': 'created_by', 'type': 'string', 'label': '', 'comment': '创建人'},
|
|
29
|
+
{'name': 'updated_at', 'type': 'string', 'label': '', 'comment': '更新时间'},
|
|
30
|
+
{'name': 'updated_by', 'type': 'string', 'label': '', 'comment': '更新人'},
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
# 基础类型映射
|
|
34
|
+
TYPE_MAPPING = {
|
|
35
|
+
'String': 'string', 'Integer': 'int32', 'int': 'int32',
|
|
36
|
+
'Long': 'int64', 'long': 'int64', 'Boolean': 'bool', 'boolean': 'bool',
|
|
37
|
+
'Double': 'double', 'double': 'double', 'Float': 'float', 'float': 'float',
|
|
38
|
+
'BigDecimal': 'string', 'LocalDateTime': 'string', 'LocalDate': 'string',
|
|
39
|
+
'LocalTime': 'string', 'Date': 'string', 'UUID': 'string',
|
|
40
|
+
'JsonObject': 'string', 'JsonArray': 'string'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
CONSTRAINT_MAPPING = {
|
|
44
|
+
'NotNull': '必填', 'NotBlank': '必填', 'NotEmpty': '必填',
|
|
45
|
+
'Deprecated': '已废弃', 'Email': '邮箱格式', 'Phone': '手机号格式'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# ================= 2. 辅助函数 =================
|
|
49
|
+
|
|
50
|
+
def get_project_name():
|
|
51
|
+
if os.path.exists('pom.xml'):
|
|
52
|
+
try:
|
|
53
|
+
tree = ET.parse('pom.xml')
|
|
54
|
+
root = tree.getroot()
|
|
55
|
+
ns = re.match(r'\{.*\}', root.tag)
|
|
56
|
+
ns_map = { 'mvn': ns.group(0).strip('{}') } if ns else {}
|
|
57
|
+
if ns:
|
|
58
|
+
aid = root.find(f"{{{ns_map['mvn']}}}artifactId")
|
|
59
|
+
else:
|
|
60
|
+
aid = root.find('artifactId')
|
|
61
|
+
if aid is not None: return aid.text
|
|
62
|
+
except Exception: pass
|
|
63
|
+
return os.path.basename(os.getcwd())
|
|
64
|
+
|
|
65
|
+
def camel_to_snake(name):
|
|
66
|
+
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
|
|
67
|
+
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
|
68
|
+
|
|
69
|
+
def get_package_name(content):
|
|
70
|
+
match = re.search(r'package\s+([\w\.]+);', content)
|
|
71
|
+
return match.group(1) if match else "domain"
|
|
72
|
+
|
|
73
|
+
def extract_constraints(lines):
|
|
74
|
+
"""
|
|
75
|
+
从注解中提取约束信息,包括:
|
|
76
|
+
1. 长度约束 (@Size, @Length)
|
|
77
|
+
2. 数值约束 (@Max, @Min)
|
|
78
|
+
3. 验证约束 (@NotNull, @NotBlank, @Email 等)
|
|
79
|
+
4. Swagger 描述 (@Schema)
|
|
80
|
+
"""
|
|
81
|
+
constraints = []
|
|
82
|
+
block = " ".join(lines)
|
|
83
|
+
|
|
84
|
+
# 提取 @Size / @Length 约束
|
|
85
|
+
size = re.search(r'@(?:Size|Length)\((.*?)\)', block)
|
|
86
|
+
if size:
|
|
87
|
+
size_str = size.group(1).replace(' ','').replace('"','')
|
|
88
|
+
constraints.append(f"长度[{size_str}]")
|
|
89
|
+
|
|
90
|
+
# 提取 @Max / @Min 约束
|
|
91
|
+
for k in ['Max', 'Min']:
|
|
92
|
+
val = re.search(fr'@{k}\(?\s*(?:value\s*=\s*)?(\d+)\s*\)?', block)
|
|
93
|
+
if val: constraints.append(f"{k}:{val.group(1)}")
|
|
94
|
+
|
|
95
|
+
# 提取验证约束
|
|
96
|
+
for k, v in CONSTRAINT_MAPPING.items():
|
|
97
|
+
if f'@{k}' in block: constraints.append(v)
|
|
98
|
+
|
|
99
|
+
# 提取 @Schema 中的 description (中文注释)
|
|
100
|
+
schema_desc = re.search(r'@Schema\([^)]*description\s*=\s*"([^"]+)"', block)
|
|
101
|
+
if schema_desc:
|
|
102
|
+
desc_text = schema_desc.group(1)
|
|
103
|
+
# 如果包含中文,优先使用
|
|
104
|
+
if re.search(r'[\u4e00-\u9fff]', desc_text):
|
|
105
|
+
return constraints, desc_text
|
|
106
|
+
|
|
107
|
+
return constraints, None
|
|
108
|
+
|
|
109
|
+
# ================= 3. 解析逻辑 =================
|
|
110
|
+
|
|
111
|
+
def parse_java_enum(content, class_name):
|
|
112
|
+
start_match = re.search(r'public\s+enum\s+\w+\s*\{', content)
|
|
113
|
+
if not start_match: return []
|
|
114
|
+
|
|
115
|
+
start_idx = start_match.end()
|
|
116
|
+
end_idx = content.find(';', start_idx)
|
|
117
|
+
if end_idx == -1: end_idx = content.rfind('}')
|
|
118
|
+
|
|
119
|
+
body = content[start_idx:end_idx]
|
|
120
|
+
body = re.sub(r'//.*', '', body)
|
|
121
|
+
body = re.sub(r'/\*.*?\*/', '', body, flags=re.DOTALL)
|
|
122
|
+
|
|
123
|
+
raw_items = body.split(',')
|
|
124
|
+
enum_items = []
|
|
125
|
+
auto_idx = 0
|
|
126
|
+
|
|
127
|
+
for item in raw_items:
|
|
128
|
+
item = item.strip()
|
|
129
|
+
if not item: continue
|
|
130
|
+
|
|
131
|
+
name_match = re.match(r'([A-Z0-9_]+)', item)
|
|
132
|
+
if not name_match: continue
|
|
133
|
+
name = name_match.group(1)
|
|
134
|
+
|
|
135
|
+
val_match = re.search(r'\(\s*(\d+)', item)
|
|
136
|
+
if val_match:
|
|
137
|
+
val = int(val_match.group(1))
|
|
138
|
+
else:
|
|
139
|
+
val = auto_idx
|
|
140
|
+
auto_idx += 1
|
|
141
|
+
|
|
142
|
+
enum_items.append({'name': name, 'value': val})
|
|
143
|
+
|
|
144
|
+
enum_items.sort(key=lambda x: x['value'])
|
|
145
|
+
if not enum_items or enum_items[0]['value'] != 0:
|
|
146
|
+
prefix = camel_to_snake(class_name).upper()
|
|
147
|
+
enum_items.insert(0, {'name': f"{prefix}_UNSPECIFIED", 'value': 0})
|
|
148
|
+
|
|
149
|
+
return enum_items
|
|
150
|
+
|
|
151
|
+
def parse_java_class(content):
|
|
152
|
+
"""
|
|
153
|
+
解析 Class,返回 (字段列表, 依赖导入集合)
|
|
154
|
+
支持两种模式:
|
|
155
|
+
1. 字段声明上的注解和注释
|
|
156
|
+
2. Getter 方法中的 JavaDoc 注释
|
|
157
|
+
"""
|
|
158
|
+
fields = []
|
|
159
|
+
imports = set() # 收集需要 import 的文件名
|
|
160
|
+
|
|
161
|
+
if 'extends BaseEntity' in content:
|
|
162
|
+
fields.extend(BASE_ENTITY_FIELDS)
|
|
163
|
+
|
|
164
|
+
lines = content.split('\n')
|
|
165
|
+
buf_anno, buf_doc = [], ""
|
|
166
|
+
|
|
167
|
+
field_pat = re.compile(r'private\s+([\w<>?]+)\s+(\w+)\s*;')
|
|
168
|
+
json_pat = re.compile(r'@JsonProperty\("([^"]+)"\)')
|
|
169
|
+
# 匹配 Getter 方法的 JavaDoc: /** Getter for <code>ZDB.TABLE.FIELD</code>. 「fieldName」- 描述 */
|
|
170
|
+
getter_doc_pat = re.compile(r'/\*\*\s*Getter for.*?[「」].*?[\u4e00-\u9fff]+.*?\*/', re.DOTALL)
|
|
171
|
+
|
|
172
|
+
# 首先尝试从字段声明处理(带注解的方式)
|
|
173
|
+
for i, line in enumerate(lines):
|
|
174
|
+
line_stripped = line.strip()
|
|
175
|
+
if not line_stripped: continue
|
|
176
|
+
|
|
177
|
+
if line_stripped.startswith('/**'):
|
|
178
|
+
buf_doc = re.sub(r'/\*\*|\*/|\*', '', line_stripped).strip()
|
|
179
|
+
continue
|
|
180
|
+
if line_stripped.startswith('*'):
|
|
181
|
+
clean = line_stripped.replace('*', '').strip()
|
|
182
|
+
if clean and clean != '/': buf_doc += " " + clean
|
|
183
|
+
continue
|
|
184
|
+
if line_stripped.startswith('@'):
|
|
185
|
+
buf_anno.append(line_stripped)
|
|
186
|
+
continue
|
|
187
|
+
if 'static final' in line_stripped:
|
|
188
|
+
buf_anno, buf_doc = [], ""
|
|
189
|
+
continue
|
|
190
|
+
|
|
191
|
+
match = field_pat.search(line)
|
|
192
|
+
if match:
|
|
193
|
+
j_type, raw_name = match.group(1), match.group(2)
|
|
194
|
+
|
|
195
|
+
# 1. 命名
|
|
196
|
+
final_name = camel_to_snake(raw_name)
|
|
197
|
+
for anno in buf_anno:
|
|
198
|
+
jp = json_pat.search(anno)
|
|
199
|
+
if jp: final_name = jp.group(1); break
|
|
200
|
+
|
|
201
|
+
# 2. 类型映射与依赖分析
|
|
202
|
+
p_type, label, target_type_for_import = _map_java_type(j_type)
|
|
203
|
+
|
|
204
|
+
# 【新增】依赖收集逻辑
|
|
205
|
+
if target_type_for_import:
|
|
206
|
+
import_file = f"{camel_to_snake(target_type_for_import)}.proto"
|
|
207
|
+
imports.add(import_file)
|
|
208
|
+
|
|
209
|
+
# 3. 注释和约束提取
|
|
210
|
+
constraints, schema_desc = extract_constraints(buf_anno)
|
|
211
|
+
|
|
212
|
+
# 提取行尾注释
|
|
213
|
+
eol_cmt = lines[i].split('//')[1].strip() if '//' in lines[i] else ""
|
|
214
|
+
|
|
215
|
+
# 优先级:Schema description (中文) > JavaDoc > 行尾注释
|
|
216
|
+
if schema_desc:
|
|
217
|
+
if constraints:
|
|
218
|
+
final_cmt = f"{schema_desc} [{', '.join(constraints)}]"
|
|
219
|
+
else:
|
|
220
|
+
final_cmt = schema_desc
|
|
221
|
+
else:
|
|
222
|
+
con_str = f"[{', '.join(constraints)}]" if constraints else ""
|
|
223
|
+
parts = [p for p in [con_str, eol_cmt, buf_doc] if p]
|
|
224
|
+
final_cmt = " ".join(parts)
|
|
225
|
+
|
|
226
|
+
fields.append({'name': final_name, 'type': p_type, 'label': label, 'comment': final_cmt})
|
|
227
|
+
buf_anno, buf_doc = [], ""
|
|
228
|
+
|
|
229
|
+
# 如果字段列表为空(可能是 jOOQ 风格),尝试从 Getter 方法提取
|
|
230
|
+
if len(fields) <= len(BASE_ENTITY_FIELDS):
|
|
231
|
+
fields_from_getters = _extract_fields_from_getters(content)
|
|
232
|
+
if fields_from_getters:
|
|
233
|
+
# 清空字段,重新从 getter 提取
|
|
234
|
+
fields = []
|
|
235
|
+
if 'extends BaseEntity' in content or 'implements VertxPojo' in content:
|
|
236
|
+
fields.extend(BASE_ENTITY_FIELDS)
|
|
237
|
+
fields.extend(fields_from_getters)
|
|
238
|
+
|
|
239
|
+
return fields, imports
|
|
240
|
+
|
|
241
|
+
def _map_java_type(j_type):
|
|
242
|
+
"""映射 Java 类型到 Proto 类型"""
|
|
243
|
+
p_type = j_type
|
|
244
|
+
label = ""
|
|
245
|
+
target_type_for_import = None
|
|
246
|
+
|
|
247
|
+
if j_type.startswith("List<"):
|
|
248
|
+
label = "repeated "
|
|
249
|
+
inner = re.search(r'List<(\w+)>', j_type)
|
|
250
|
+
if inner:
|
|
251
|
+
inner_type = inner.group(1)
|
|
252
|
+
if inner_type in TYPE_MAPPING:
|
|
253
|
+
p_type = TYPE_MAPPING[inner_type]
|
|
254
|
+
else:
|
|
255
|
+
p_type = inner_type
|
|
256
|
+
target_type_for_import = inner_type
|
|
257
|
+
else:
|
|
258
|
+
if j_type in TYPE_MAPPING:
|
|
259
|
+
p_type = TYPE_MAPPING[j_type]
|
|
260
|
+
else:
|
|
261
|
+
p_type = j_type
|
|
262
|
+
target_type_for_import = j_type
|
|
263
|
+
|
|
264
|
+
return p_type, label, target_type_for_import
|
|
265
|
+
|
|
266
|
+
def _extract_fields_from_getters(content):
|
|
267
|
+
"""
|
|
268
|
+
从 Getter 方法中提取字段信息(用于 jOOQ 生成的代码)
|
|
269
|
+
匹配格式: /** Getter for <code>ZDB.TABLE.FIELD</code>. 「fieldName」- 描述 */
|
|
270
|
+
"""
|
|
271
|
+
fields = []
|
|
272
|
+
|
|
273
|
+
# 匹配 getter 方法及其 JavaDoc
|
|
274
|
+
getter_pattern = re.compile(
|
|
275
|
+
r'/\*\*\s*Getter for[^*]*?\*/' # JavaDoc
|
|
276
|
+
r'\s*@Override\s*' # @Override
|
|
277
|
+
r'public\s+([\w<>?]+)\s+get(\w+)\(\)', # 方法签名
|
|
278
|
+
re.DOTALL
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
for match in getter_pattern.finditer(content):
|
|
282
|
+
javadoc = match.group(0)
|
|
283
|
+
j_type = match.group(1)
|
|
284
|
+
field_name_camel = match.group(2)
|
|
285
|
+
|
|
286
|
+
# 提取中文注释:「fieldName」- 描述
|
|
287
|
+
chinese_match = re.search(r'[「」]([^」]+)[」][\s\-—]*([^*\n]+)', javadoc)
|
|
288
|
+
if chinese_match:
|
|
289
|
+
# field_label = chinese_match.group(1) # 如 "id"
|
|
290
|
+
description = chinese_match.group(2).strip() # 如 "主键"
|
|
291
|
+
else:
|
|
292
|
+
# 回退:提取任何中文内容
|
|
293
|
+
chinese_text = re.findall(r'[\u4e00-\u9fff]+', javadoc)
|
|
294
|
+
description = ''.join(chinese_text) if chinese_text else ''
|
|
295
|
+
|
|
296
|
+
# 字段名转换为 snake_case
|
|
297
|
+
field_name = camel_to_snake(field_name_camel[0].lower() + field_name_camel[1:])
|
|
298
|
+
|
|
299
|
+
# 类型映射
|
|
300
|
+
p_type, label, _ = _map_java_type(j_type)
|
|
301
|
+
|
|
302
|
+
fields.append({
|
|
303
|
+
'name': field_name,
|
|
304
|
+
'type': p_type,
|
|
305
|
+
'label': label,
|
|
306
|
+
'comment': description
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
return fields
|
|
310
|
+
|
|
311
|
+
def generate_proto(name, pkg, content, ftype):
|
|
312
|
+
lines = [
|
|
313
|
+
'syntax = "proto3";',
|
|
314
|
+
'package domain;', ''
|
|
315
|
+
]
|
|
316
|
+
|
|
317
|
+
# 只有 class 类型才需要 import
|
|
318
|
+
imports = set()
|
|
319
|
+
fields = []
|
|
320
|
+
enum_items = []
|
|
321
|
+
|
|
322
|
+
if ftype == 'enum':
|
|
323
|
+
enum_items = parse_java_enum(content, name)
|
|
324
|
+
if not enum_items: return None
|
|
325
|
+
else:
|
|
326
|
+
# 解析字段并获取依赖
|
|
327
|
+
fields, imports = parse_java_class(content)
|
|
328
|
+
if not fields: return None
|
|
329
|
+
|
|
330
|
+
# 【新增】生成 import 语句
|
|
331
|
+
# 排除自己引用自己
|
|
332
|
+
self_import = f"{camel_to_snake(name)}.proto"
|
|
333
|
+
if self_import in imports:
|
|
334
|
+
imports.remove(self_import)
|
|
335
|
+
|
|
336
|
+
if imports:
|
|
337
|
+
sorted_imports = sorted(list(imports))
|
|
338
|
+
for imp in sorted_imports:
|
|
339
|
+
lines.append(f'import "{imp}";')
|
|
340
|
+
lines.append('') # 空行分隔
|
|
341
|
+
|
|
342
|
+
lines.append(f'// Generated from {pkg}.{name}')
|
|
343
|
+
lines.append(f'option java_package = "{pkg}";')
|
|
344
|
+
lines.append('option java_multiple_files = true;')
|
|
345
|
+
lines.append('')
|
|
346
|
+
|
|
347
|
+
if ftype == 'enum':
|
|
348
|
+
lines.append(f'enum {name} {{')
|
|
349
|
+
for i in enum_items: lines.append(f' {i["name"]} = {i["value"]};')
|
|
350
|
+
lines.append('}')
|
|
351
|
+
else:
|
|
352
|
+
lines.append(f'message {name} {{')
|
|
353
|
+
for idx, f in enumerate(fields, 1):
|
|
354
|
+
suf = f' // {f["comment"]}' if f["comment"] else ''
|
|
355
|
+
lines.append(f' {f["label"]}{f["type"]} {f["name"]} = {idx};{suf}')
|
|
356
|
+
lines.append('}')
|
|
357
|
+
|
|
358
|
+
return "\n".join(lines)
|
|
359
|
+
|
|
360
|
+
# ================= 4. 主程序 =================
|
|
361
|
+
|
|
362
|
+
def main():
|
|
363
|
+
parser = argparse.ArgumentParser(description='R2MO Java-to-Proto (V8)')
|
|
364
|
+
parser.add_argument('-i', '--input', help='指定输入目录')
|
|
365
|
+
parser.add_argument('-o', '--output', help='指定输出目录')
|
|
366
|
+
args = parser.parse_args()
|
|
367
|
+
|
|
368
|
+
cwd = os.getcwd()
|
|
369
|
+
project_name = get_project_name()
|
|
370
|
+
|
|
371
|
+
if args.input: input_dir = os.path.abspath(args.input)
|
|
372
|
+
else: input_dir = os.path.join(cwd, f"{project_name}-domain")
|
|
373
|
+
|
|
374
|
+
if args.output: output_dir = os.path.abspath(args.output)
|
|
375
|
+
else: output_dir = os.path.join(cwd, f"{project_name}-ui", ".r2mo", "domain")
|
|
376
|
+
|
|
377
|
+
if not os.path.exists(input_dir):
|
|
378
|
+
if f"{project_name}-domain" in cwd: input_dir = cwd
|
|
379
|
+
else:
|
|
380
|
+
print(f"❌ 输入目录不存在: {input_dir}"); sys.exit(1)
|
|
381
|
+
|
|
382
|
+
if not os.path.exists(output_dir): os.makedirs(output_dir)
|
|
383
|
+
|
|
384
|
+
print(f"🚀 R2MO Proto Generator (V8)")
|
|
385
|
+
print(f" In: {input_dir}")
|
|
386
|
+
print(f" Out: {output_dir}")
|
|
387
|
+
print("-" * 40)
|
|
388
|
+
|
|
389
|
+
count = 0
|
|
390
|
+
for root, dirs, files in os.walk(input_dir):
|
|
391
|
+
for f in files:
|
|
392
|
+
if f.endswith(".java") and f != "BaseEntity.java":
|
|
393
|
+
path = os.path.join(root, f)
|
|
394
|
+
try:
|
|
395
|
+
with open(path, 'r', encoding='utf-8') as fh: content = fh.read()
|
|
396
|
+
|
|
397
|
+
is_enum = 'public enum' in content
|
|
398
|
+
match = re.search(r'public\s+(class|enum)\s+(\w+)', content)
|
|
399
|
+
|
|
400
|
+
if match:
|
|
401
|
+
name = match.group(2)
|
|
402
|
+
pkg = get_package_name(content)
|
|
403
|
+
ftype = 'enum' if is_enum else 'class'
|
|
404
|
+
|
|
405
|
+
proto = generate_proto(name, pkg, content, ftype)
|
|
406
|
+
if proto:
|
|
407
|
+
out_name = f"{camel_to_snake(name)}.proto"
|
|
408
|
+
with open(os.path.join(output_dir, out_name), 'w') as fh: fh.write(proto)
|
|
409
|
+
count += 1
|
|
410
|
+
print(f"✅ {name} -> {out_name}")
|
|
411
|
+
except Exception as e:
|
|
412
|
+
print(f"⚠️ Skip {f}: {e}")
|
|
413
|
+
|
|
414
|
+
print("-" * 40)
|
|
415
|
+
print(f"🎉 处理完成: {count} 个文件")
|
|
416
|
+
|
|
417
|
+
if __name__ == "__main__":
|
|
418
|
+
main()
|