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
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
R2MO SQL to Protobuf Converter (V2 - Enhanced Java Property & Constraints)
|
|
6
|
+
从 Flyway SQL 脚本生成 Protobuf 文件
|
|
7
|
+
更新内容:
|
|
8
|
+
1. 【修复】中文注释提取 BUG:正确解析「javaProperty」- 描述格式
|
|
9
|
+
2. 【强化】Java 属性名保留:从 COMMENT 的「」中提取并附加到注释 [Java:xxx]
|
|
10
|
+
3. 【强化】约束规范化:必填、唯一、主键、默认值、最大长度等统一格式
|
|
11
|
+
4. 【强化】仅扫描 src/main/resources 下的 flyway/MYSQL(排除 target/classes)
|
|
12
|
+
5. 【优化】逐行解析,支持多行注释过滤和行尾 -- 补充描述
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import sys
|
|
18
|
+
import argparse
|
|
19
|
+
|
|
20
|
+
# ================= 1. 配置与映射表 =================
|
|
21
|
+
|
|
22
|
+
# SQL 类型映射到 Proto 类型
|
|
23
|
+
SQL_TYPE_MAPPING = {
|
|
24
|
+
'VARCHAR': 'string', 'CHAR': 'string', 'TEXT': 'string', 'LONGTEXT': 'string',
|
|
25
|
+
'TINYTEXT': 'string', 'MEDIUMTEXT': 'string',
|
|
26
|
+
'INT': 'int32', 'INTEGER': 'int32', 'TINYINT': 'int32', 'SMALLINT': 'int32',
|
|
27
|
+
'BIGINT': 'int64', 'LONG': 'int64',
|
|
28
|
+
'FLOAT': 'float', 'DOUBLE': 'double', 'DECIMAL': 'string',
|
|
29
|
+
'DATETIME': 'string', 'TIMESTAMP': 'string', 'DATE': 'string', 'TIME': 'string',
|
|
30
|
+
'BIT': 'bool', 'BOOLEAN': 'bool',
|
|
31
|
+
'BLOB': 'bytes', 'LONGBLOB': 'bytes', 'MEDIUMBLOB': 'bytes', 'TINYBLOB': 'bytes'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# ================= 2. 辅助函数 =================
|
|
35
|
+
|
|
36
|
+
def camel_to_snake(name):
|
|
37
|
+
"""驼峰转蛇形"""
|
|
38
|
+
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
|
|
39
|
+
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
|
40
|
+
|
|
41
|
+
def snake_to_pascal(name):
|
|
42
|
+
"""蛇形转帕斯卡"""
|
|
43
|
+
return ''.join(word.capitalize() for word in name.split('_'))
|
|
44
|
+
|
|
45
|
+
def _snake_to_camel(name):
|
|
46
|
+
"""蛇形转驼峰:tenant_id -> tenantId"""
|
|
47
|
+
parts = name.lower().split('_')
|
|
48
|
+
return parts[0] + ''.join(p.capitalize() for p in parts[1:])
|
|
49
|
+
|
|
50
|
+
def extract_sql_type(type_def):
|
|
51
|
+
"""
|
|
52
|
+
从 SQL 类型定义中提取基础类型
|
|
53
|
+
例如: VARCHAR(255) -> VARCHAR, INT(11) -> INT
|
|
54
|
+
"""
|
|
55
|
+
match = re.match(r'([A-Z]+)', type_def.upper())
|
|
56
|
+
return match.group(1) if match else 'VARCHAR'
|
|
57
|
+
|
|
58
|
+
def extract_comment_parts(comment):
|
|
59
|
+
"""
|
|
60
|
+
从 COMMENT 提取:Java属性名(「xxx」)、中文描述(- 后面的内容)
|
|
61
|
+
格式: '「fieldName」- 描述',支持全角「」与半角
|
|
62
|
+
返回: (java_prop, desc)
|
|
63
|
+
"""
|
|
64
|
+
java_prop = ''
|
|
65
|
+
desc = ''
|
|
66
|
+
if not comment:
|
|
67
|
+
return java_prop, desc
|
|
68
|
+
# 全角括号 U+300C/U+300D 或 半角
|
|
69
|
+
java_prop_m = re.search(r'[「\u300c]([^」\u300d]+)[」\u300d]', comment)
|
|
70
|
+
if java_prop_m:
|
|
71
|
+
java_prop = java_prop_m.group(1).strip()
|
|
72
|
+
# 描述:」或)后面 -/— 之后到结尾,去尾逗号/空格
|
|
73
|
+
desc_m = re.search(r'[」\u300d]\s*[-—]\s*(.+)', comment)
|
|
74
|
+
if desc_m:
|
|
75
|
+
desc = desc_m.group(1).strip().rstrip(',').strip()
|
|
76
|
+
if not desc:
|
|
77
|
+
chinese = re.findall(r'[\u4e00-\u9fff]+', comment)
|
|
78
|
+
desc = ''.join(chinese) if chinese else ''
|
|
79
|
+
return java_prop, desc
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _extract_field_block(sql_content):
|
|
83
|
+
"""提取 CREATE TABLE 后括号内的字段定义块(含括号匹配)。"""
|
|
84
|
+
start = re.search(r'CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`\']?\w+[`\']?\s*\(', sql_content, re.IGNORECASE)
|
|
85
|
+
if not start:
|
|
86
|
+
return None
|
|
87
|
+
i = start.end() - 1 # 指向 '('
|
|
88
|
+
depth = 0
|
|
89
|
+
begin = None
|
|
90
|
+
for j in range(i, len(sql_content)):
|
|
91
|
+
c = sql_content[j]
|
|
92
|
+
if c == '(':
|
|
93
|
+
depth += 1
|
|
94
|
+
if begin is None:
|
|
95
|
+
begin = j + 1
|
|
96
|
+
elif c == ')':
|
|
97
|
+
depth -= 1
|
|
98
|
+
if depth == 0:
|
|
99
|
+
return sql_content[begin:j]
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def parse_create_table(sql_content):
|
|
104
|
+
"""
|
|
105
|
+
解析 CREATE TABLE 语句(逐行解析字段,支持 COMMENT 与约束)
|
|
106
|
+
返回: {
|
|
107
|
+
'table_name': '表名',
|
|
108
|
+
'fields': [{'name': '字段名', 'type': 'proto类型', 'comment': '注释(含Java属性名)'}, ...]
|
|
109
|
+
}
|
|
110
|
+
"""
|
|
111
|
+
table_match = re.search(r'CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`\']?(\w+)[`\']?', sql_content, re.IGNORECASE)
|
|
112
|
+
if not table_match:
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
table_name = table_match.group(1)
|
|
116
|
+
field_block = _extract_field_block(sql_content)
|
|
117
|
+
if not field_block:
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
# 去掉多行注释 /* ... */,避免干扰行解析
|
|
121
|
+
field_block = re.sub(r'/\*.*?\*/', '', field_block, flags=re.DOTALL)
|
|
122
|
+
|
|
123
|
+
fields = []
|
|
124
|
+
for raw_line in field_block.split('\n'):
|
|
125
|
+
line = raw_line.strip()
|
|
126
|
+
if not line or line.startswith('--'):
|
|
127
|
+
continue
|
|
128
|
+
# 跳过索引定义行
|
|
129
|
+
if re.match(r'^(PRIMARY\s+KEY|UNIQUE\s+KEY|KEY|INDEX)\s', line, re.IGNORECASE):
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
# 必须包含反引号字段名和 COMMENT
|
|
133
|
+
if '`' not in line or 'COMMENT' not in line.upper():
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
# 字段名: 第一个 `name`
|
|
137
|
+
name_m = re.search(r'`(\w+)`', line)
|
|
138
|
+
if not name_m:
|
|
139
|
+
continue
|
|
140
|
+
field_name = name_m.group(1).lower()
|
|
141
|
+
|
|
142
|
+
# 类型: 紧接在 `name` 后的 WORD 或 WORD(NUM) 或 WORD(NUM,NUM)
|
|
143
|
+
type_m = re.search(r'`\w+`\s+(\w+)(?:\s*\(\s*\d+(?:\s*,\s*\d+)?\s*\))?', line)
|
|
144
|
+
if not type_m:
|
|
145
|
+
continue
|
|
146
|
+
sql_type_def = type_m.group(1).upper()
|
|
147
|
+
base_sql_type = extract_sql_type(sql_type_def)
|
|
148
|
+
proto_type = SQL_TYPE_MAPPING.get(base_sql_type, 'string')
|
|
149
|
+
|
|
150
|
+
# COMMENT 内容:支持 '...' 或 "...",含全角引号
|
|
151
|
+
comment_text = ''
|
|
152
|
+
m = re.search(r"COMMENT\s+['\"]([^'\"]*)['\"]", line, re.IGNORECASE)
|
|
153
|
+
if not m:
|
|
154
|
+
m = re.search(r'COMMENT\s+[\u2018\u2019\u201c\u201d]([^\u2018\u2019\u201c\u201d]*)[\u2018\u2019\u201c\u201d]', line, re.IGNORECASE)
|
|
155
|
+
if m:
|
|
156
|
+
comment_text = m.group(1).strip()
|
|
157
|
+
|
|
158
|
+
# Java 属性名 + 中文描述(「xxx」- 描述)
|
|
159
|
+
java_prop, desc = extract_comment_parts(comment_text)
|
|
160
|
+
if not java_prop and field_name:
|
|
161
|
+
java_prop = _snake_to_camel(field_name)
|
|
162
|
+
if '--' in line:
|
|
163
|
+
trail = line.split('--', 1)[1].strip()
|
|
164
|
+
trail_cn = re.findall(r'[\u4e00-\u9fff]+', trail)
|
|
165
|
+
if trail_cn:
|
|
166
|
+
desc = (desc + ' ' + ''.join(trail_cn)) if desc else ''.join(trail_cn)
|
|
167
|
+
|
|
168
|
+
# 约束
|
|
169
|
+
constraints = []
|
|
170
|
+
if re.search(r'\bNOT\s+NULL\b', line, re.IGNORECASE):
|
|
171
|
+
constraints.append('必填')
|
|
172
|
+
if re.search(r'\bUNIQUE\b', line, re.IGNORECASE):
|
|
173
|
+
constraints.append('唯一')
|
|
174
|
+
if re.search(r'\bPRIMARY\s+KEY\b', line, re.IGNORECASE):
|
|
175
|
+
constraints.append('主键')
|
|
176
|
+
default_m = re.search(r'\bDEFAULT\s+([^\s,]+)', line, re.IGNORECASE)
|
|
177
|
+
if default_m:
|
|
178
|
+
v = default_m.group(1).strip()
|
|
179
|
+
if v.upper() not in ('NULL', 'CURRENT_TIMESTAMP'):
|
|
180
|
+
constraints.append(f"默认:{v}")
|
|
181
|
+
len_m = re.search(r'\w+\((\d+)(?:,\s*\d+)?\)', line)
|
|
182
|
+
if len_m and base_sql_type in ('VARCHAR', 'CHAR'):
|
|
183
|
+
constraints.append(f"最大长度:{len_m.group(1)}")
|
|
184
|
+
|
|
185
|
+
# 最终注释:[Java:xxx] 描述 (约束)
|
|
186
|
+
parts = []
|
|
187
|
+
if java_prop:
|
|
188
|
+
parts.append(f"[Java:{java_prop}]")
|
|
189
|
+
if desc:
|
|
190
|
+
parts.append(desc)
|
|
191
|
+
if constraints:
|
|
192
|
+
parts.append('(' + ', '.join(constraints) + ')')
|
|
193
|
+
final_comment = ' '.join(parts) if parts else ''
|
|
194
|
+
|
|
195
|
+
fields.append({
|
|
196
|
+
'name': field_name,
|
|
197
|
+
'type': proto_type,
|
|
198
|
+
'comment': final_comment
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
'table_name': table_name,
|
|
203
|
+
'fields': fields
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
def generate_proto_from_table(table_info, java_package="domain"):
|
|
207
|
+
"""
|
|
208
|
+
从表信息生成 Proto 文件内容。
|
|
209
|
+
java_package: 与 jOOQ 生成的 domain pojos 包一致,用于 option java_package 和 Generated from 注释。
|
|
210
|
+
"""
|
|
211
|
+
table_name = table_info['table_name']
|
|
212
|
+
fields = table_info['fields']
|
|
213
|
+
message_name = snake_to_pascal(table_name)
|
|
214
|
+
|
|
215
|
+
lines = [
|
|
216
|
+
'syntax = "proto3";',
|
|
217
|
+
'package domain;',
|
|
218
|
+
'',
|
|
219
|
+
f'// Generated from {java_package}.{message_name}',
|
|
220
|
+
f'option java_package = "{java_package}";',
|
|
221
|
+
'option java_multiple_files = true;',
|
|
222
|
+
'',
|
|
223
|
+
f'message {message_name} {{'
|
|
224
|
+
]
|
|
225
|
+
|
|
226
|
+
for idx, field in enumerate(fields, 1):
|
|
227
|
+
comment_suffix = f' // {field["comment"]}' if field["comment"] else ''
|
|
228
|
+
lines.append(f' {field["type"]} {field["name"]} = {idx};{comment_suffix}')
|
|
229
|
+
|
|
230
|
+
lines.append('}')
|
|
231
|
+
|
|
232
|
+
return "\n".join(lines)
|
|
233
|
+
|
|
234
|
+
def find_flyway_dirs(base_dir):
|
|
235
|
+
"""
|
|
236
|
+
仅查找 src/main/resources 下的 flyway/MYSQL 目录(排除 target/classes 等编译输出)
|
|
237
|
+
"""
|
|
238
|
+
flyway_dirs = []
|
|
239
|
+
resources_part = os.path.join('src', 'main', 'resources')
|
|
240
|
+
for root, dirs, files in os.walk(base_dir):
|
|
241
|
+
norm_root = os.path.normpath(root)
|
|
242
|
+
if resources_part not in norm_root:
|
|
243
|
+
continue
|
|
244
|
+
if 'flyway' in norm_root.lower() and 'mysql' in norm_root.lower():
|
|
245
|
+
flyway_dirs.append(root)
|
|
246
|
+
return flyway_dirs
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _discover_java_package(project_root):
|
|
250
|
+
"""
|
|
251
|
+
从 -domain 项目中发现 jOOQ 生成的 pojos 包,作为 java_package。
|
|
252
|
+
一般在 -domain/src/main/java/.../domain/.../pojos 或 .../domain/tables/pojos 下。
|
|
253
|
+
返回: 如 "com.formaltech.apps.takeout.domain" 或 "io.zerows.extension.module.ambient.domain.tables.pojos"
|
|
254
|
+
"""
|
|
255
|
+
root = os.path.normpath(project_root)
|
|
256
|
+
# 确定 -domain 模块目录
|
|
257
|
+
domain_dir = None
|
|
258
|
+
if os.path.basename(root).endswith('-domain'):
|
|
259
|
+
domain_dir = root
|
|
260
|
+
else:
|
|
261
|
+
for name in os.listdir(root):
|
|
262
|
+
if name.endswith('-domain') and os.path.isdir(os.path.join(root, name)):
|
|
263
|
+
domain_dir = os.path.join(root, name)
|
|
264
|
+
break
|
|
265
|
+
if not domain_dir or not os.path.isdir(domain_dir):
|
|
266
|
+
return 'domain'
|
|
267
|
+
java_src = os.path.join(domain_dir, 'src', 'main', 'java')
|
|
268
|
+
if not os.path.isdir(java_src):
|
|
269
|
+
return 'domain'
|
|
270
|
+
# 查找包含 pojos 的目录下任意 .java,读取 package
|
|
271
|
+
for dirpath, dirnames, filenames in os.walk(java_src):
|
|
272
|
+
if 'pojos' not in os.path.basename(dirpath).lower():
|
|
273
|
+
continue
|
|
274
|
+
for f in filenames:
|
|
275
|
+
if not f.endswith('.java'):
|
|
276
|
+
continue
|
|
277
|
+
path = os.path.join(dirpath, f)
|
|
278
|
+
try:
|
|
279
|
+
with open(path, 'r', encoding='utf-8') as fp:
|
|
280
|
+
for line in fp:
|
|
281
|
+
m = re.match(r'\s*package\s+([\w.]+)\s*;', line)
|
|
282
|
+
if m:
|
|
283
|
+
return m.group(1).strip()
|
|
284
|
+
except Exception:
|
|
285
|
+
continue
|
|
286
|
+
return 'domain'
|
|
287
|
+
|
|
288
|
+
# ================= 3. 主程序 =================
|
|
289
|
+
|
|
290
|
+
def main():
|
|
291
|
+
parser = argparse.ArgumentParser(description='R2MO SQL-to-Proto (V1 - Flyway Mode)')
|
|
292
|
+
parser.add_argument('-i', '--input', help='指定输入目录(默认当前目录)')
|
|
293
|
+
parser.add_argument('-o', '--output', help='指定输出目录')
|
|
294
|
+
args = parser.parse_args()
|
|
295
|
+
|
|
296
|
+
input_dir = os.path.abspath(args.input) if args.input else os.getcwd()
|
|
297
|
+
|
|
298
|
+
if not os.path.exists(input_dir):
|
|
299
|
+
print(f"❌ 输入目录不存在: {input_dir}")
|
|
300
|
+
sys.exit(1)
|
|
301
|
+
|
|
302
|
+
# 查找所有 flyway 目录
|
|
303
|
+
print(f"🚀 R2MO Proto Generator (V2 - Flyway SQL Mode)")
|
|
304
|
+
print(f" Scanning: {input_dir}")
|
|
305
|
+
print("-" * 40)
|
|
306
|
+
|
|
307
|
+
flyway_dirs = find_flyway_dirs(input_dir)
|
|
308
|
+
|
|
309
|
+
if not flyway_dirs:
|
|
310
|
+
print(f"❌ 未找到 flyway/MYSQL 目录")
|
|
311
|
+
sys.exit(1)
|
|
312
|
+
|
|
313
|
+
print(f"✓ 找到 {len(flyway_dirs)} 个 Flyway 目录")
|
|
314
|
+
for d in flyway_dirs:
|
|
315
|
+
print(f" - {d}")
|
|
316
|
+
print("-" * 40)
|
|
317
|
+
|
|
318
|
+
# 确定输出目录
|
|
319
|
+
if args.output:
|
|
320
|
+
output_dir = os.path.abspath(args.output)
|
|
321
|
+
else:
|
|
322
|
+
# 默认输出到第一个 flyway 目录的同级 proto 目录
|
|
323
|
+
output_dir = os.path.join(os.path.dirname(flyway_dirs[0]), 'proto')
|
|
324
|
+
|
|
325
|
+
if not os.path.exists(output_dir):
|
|
326
|
+
os.makedirs(output_dir)
|
|
327
|
+
|
|
328
|
+
print(f" Out: {output_dir}")
|
|
329
|
+
print("-" * 40)
|
|
330
|
+
|
|
331
|
+
# 从 -domain 项目中发现 jOOQ pojos 包,作为 java_package
|
|
332
|
+
java_package = _discover_java_package(input_dir)
|
|
333
|
+
print(f" Java package: {java_package}")
|
|
334
|
+
print("-" * 40)
|
|
335
|
+
|
|
336
|
+
count = 0
|
|
337
|
+
for flyway_dir in flyway_dirs:
|
|
338
|
+
for filename in os.listdir(flyway_dir):
|
|
339
|
+
if filename.endswith('.sql'):
|
|
340
|
+
sql_file = os.path.join(flyway_dir, filename)
|
|
341
|
+
try:
|
|
342
|
+
with open(sql_file, 'r', encoding='utf-8') as f:
|
|
343
|
+
sql_content = f.read()
|
|
344
|
+
|
|
345
|
+
table_info = parse_create_table(sql_content)
|
|
346
|
+
|
|
347
|
+
if table_info and table_info['fields']:
|
|
348
|
+
proto_content = generate_proto_from_table(table_info, java_package=java_package)
|
|
349
|
+
|
|
350
|
+
# 输出文件名:表名的蛇形形式
|
|
351
|
+
proto_filename = f"{table_info['table_name'].lower()}.proto"
|
|
352
|
+
proto_file = os.path.join(output_dir, proto_filename)
|
|
353
|
+
|
|
354
|
+
with open(proto_file, 'w', encoding='utf-8') as f:
|
|
355
|
+
f.write(proto_content)
|
|
356
|
+
|
|
357
|
+
count += 1
|
|
358
|
+
print(f"✅ {table_info['table_name']} -> {proto_filename} ({len(table_info['fields'])} fields)")
|
|
359
|
+
else:
|
|
360
|
+
print(f"⚠️ Skip {filename}: 未找到有效的 CREATE TABLE")
|
|
361
|
+
|
|
362
|
+
except Exception as e:
|
|
363
|
+
print(f"⚠️ Skip {filename}: {e}")
|
|
364
|
+
|
|
365
|
+
print("-" * 40)
|
|
366
|
+
print(f"🎉 处理完成: {count} 个文件")
|
|
367
|
+
|
|
368
|
+
if __name__ == "__main__":
|
|
369
|
+
main()
|