libra-boot-low 1.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- generator.py +930 -0
- libra_boot_low-1.1.1.dist-info/METADATA +180 -0
- libra_boot_low-1.1.1.dist-info/RECORD +25 -0
- libra_boot_low-1.1.1.dist-info/WHEEL +5 -0
- libra_boot_low-1.1.1.dist-info/entry_points.txt +2 -0
- libra_boot_low-1.1.1.dist-info/licenses/LICENSE +21 -0
- libra_boot_low-1.1.1.dist-info/top_level.txt +2 -0
- module/__init__.py +19 -0
- module/analyze_optional_context.py +311 -0
- module/config_loader.py +686 -0
- module/field_builder.py +569 -0
- module/gen_master_detail.py +314 -0
- module/gen_master_detail_file.py +391 -0
- module/gen_master_detail_modules.py +382 -0
- module/gen_master_detail_modules_file.py +428 -0
- module/gen_master_details_tabs.py +403 -0
- module/gen_master_details_tabs_dialog.py +403 -0
- module/gen_master_details_tabs_dialog_file.py +413 -0
- module/gen_master_details_tabs_file.py +414 -0
- module/gen_multi_table.py +265 -0
- module/gen_multi_table_with_file.py +275 -0
- module/gen_single.py +90 -0
- module/gen_single_with_file.py +91 -0
- module/master_detail_utils.py +280 -0
- module/template_renderer.py +527 -0
generator.py
ADDED
|
@@ -0,0 +1,930 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# Libra Boot Low 代码生成器 — 统一入口(索引 + 生成 + 报告)
|
|
5
|
+
#
|
|
6
|
+
# 【设计思路】
|
|
7
|
+
# 本工具整合三大功能:
|
|
8
|
+
# 1. 索引更新 → 自动扫描 config/ 目录,更新 config/index.json
|
|
9
|
+
# 2. 代码生成 → 根据 YAML 配置 + Jinja2 模板生成后端/前端代码
|
|
10
|
+
# 3. 报告输出 → 生成运行报告到 output/gen_{timestamp}.log
|
|
11
|
+
#
|
|
12
|
+
# 【支持的生成模式(10 种)】
|
|
13
|
+
# - single : 单表(无附件),标准 6 接口
|
|
14
|
+
# - single-with-file : 单表 + 附件从表(1:N),9 接口
|
|
15
|
+
# - master-detail : 主表 + 明细从表(1:N,无附件),主6 + 明细6 接口
|
|
16
|
+
# - master-detail-with-file : 主表 + 明细从表 + 明细附件从表,主6 + 明细9 接口
|
|
17
|
+
# - master-detail-modules : 主表 + N 个明细从表(无附件),主6 + 明细6×N 接口
|
|
18
|
+
# - master-detail-modules-with-file : 主表 + N 个明细从表,各自可选附件,主6 + 明细6/9×N 接口
|
|
19
|
+
# - master-details-tabs : 主表(无新增)+ N 明细(Tab 内嵌,无附件),主6 + 明细6×N
|
|
20
|
+
# - master-details-tabs-with-file : 主表(无新增)+ N 明细(Tab 内嵌,各自可选附件)
|
|
21
|
+
# - multi-table : 多平级子表(无 FK,各自独立 CRUD),6×N 接口
|
|
22
|
+
# - multi-table-with-file : 多平级子表,每张表含附件从表,9×N 接口
|
|
23
|
+
#
|
|
24
|
+
# 【用法】
|
|
25
|
+
# python generator.py <config.yml> [output_dir]
|
|
26
|
+
# python generator.py --update-index # 仅更新索引
|
|
27
|
+
# python generator.py config/test/t1000.yml # 生成指定模块
|
|
28
|
+
#
|
|
29
|
+
# 【报告输出】
|
|
30
|
+
# output/gen_{timestamp}.txt (生成报告)
|
|
31
|
+
# output/log_{timestamp}.txt (控制台日志)
|
|
32
|
+
# =============================================================================
|
|
33
|
+
import os
|
|
34
|
+
import sys
|
|
35
|
+
import yaml
|
|
36
|
+
import json
|
|
37
|
+
import argparse
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
from datetime import datetime
|
|
40
|
+
|
|
41
|
+
# =============================================================================
|
|
42
|
+
# 路径常量定义
|
|
43
|
+
# =============================================================================
|
|
44
|
+
SCRIPT_DIR = Path(__file__).parent.resolve()
|
|
45
|
+
CONFIG_DIR = SCRIPT_DIR / 'config'
|
|
46
|
+
TEMPLATE_DIR = SCRIPT_DIR / 'templates'
|
|
47
|
+
OUTPUT_DIR = SCRIPT_DIR / 'output'
|
|
48
|
+
|
|
49
|
+
# 添加 lib 到路径
|
|
50
|
+
sys.path.insert(0, str(SCRIPT_DIR))
|
|
51
|
+
|
|
52
|
+
from module.config_loader import ConfigLoader
|
|
53
|
+
from module.field_builder import FieldBuilder
|
|
54
|
+
from module.template_renderer import TemplateRenderer
|
|
55
|
+
from module import template_renderer as _tr
|
|
56
|
+
|
|
57
|
+
# 导入各模式生成器
|
|
58
|
+
from module.gen_single import generate as gen_single
|
|
59
|
+
from module.gen_single_with_file import generate as gen_single_with_file
|
|
60
|
+
from module.gen_master_detail import generate as gen_master_detail
|
|
61
|
+
from module.gen_master_detail_file import generate as gen_master_detail_file
|
|
62
|
+
from module.gen_master_detail_modules import generate as gen_master_detail_modules
|
|
63
|
+
from module.gen_master_detail_modules_file import generate as gen_master_detail_modules_file
|
|
64
|
+
from module.gen_master_details_tabs import generate as gen_master_details_tabs
|
|
65
|
+
from module.gen_master_details_tabs_file import generate as gen_master_details_tabs_file
|
|
66
|
+
from module.gen_master_details_tabs_dialog import generate as gen_master_details_tabs_dialog
|
|
67
|
+
from module.gen_master_details_tabs_dialog_file import generate as gen_master_details_tabs_dialog_file
|
|
68
|
+
from module.gen_multi_table import generate as gen_multi_table
|
|
69
|
+
from module.gen_multi_table_with_file import generate as gen_multi_table_with_file
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# =============================================================================
|
|
73
|
+
# 模式映射表 — 将 mode 字符串映射到对应的生成函数
|
|
74
|
+
# =============================================================================
|
|
75
|
+
MODE_GENERATORS = {
|
|
76
|
+
'single': gen_single,
|
|
77
|
+
'single-with-file': gen_single_with_file,
|
|
78
|
+
'master-detail': gen_master_detail,
|
|
79
|
+
'master-detail-with-file': gen_master_detail_file,
|
|
80
|
+
'master-detail-modules': gen_master_detail_modules,
|
|
81
|
+
'master-detail-modules-with-file': gen_master_detail_modules_file,
|
|
82
|
+
'master-details-tabs': gen_master_details_tabs,
|
|
83
|
+
'master-details-tabs-with-file': gen_master_details_tabs_file,
|
|
84
|
+
'master-details-tabs-dialog': gen_master_details_tabs_dialog,
|
|
85
|
+
'master-details-tabs-dialog-with-file': gen_master_details_tabs_dialog_file,
|
|
86
|
+
'multi-table': gen_multi_table,
|
|
87
|
+
'multi-table-with-file': gen_multi_table_with_file,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# 模式分组(用于确定参数名称)
|
|
91
|
+
SINGLE_MODES = ['single', 'single-with-file']
|
|
92
|
+
MASTER_DETAIL_MODES = ['master-detail', 'master-detail-with-file', 'master-detail-modules', 'master-detail-modules-with-file', 'master-details-tabs', 'master-details-tabs-with-file', 'master-details-tabs-dialog', 'master-details-tabs-dialog-with-file']
|
|
93
|
+
MULTI_TABLE_MODES = ['multi-table', 'multi-table-with-file']
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ConfigIndexer:
|
|
97
|
+
"""
|
|
98
|
+
配置索引器。
|
|
99
|
+
|
|
100
|
+
扫描 config/ 目录下所有 YAML 配置文件,生成 index.json 索引。
|
|
101
|
+
|
|
102
|
+
Attributes:
|
|
103
|
+
config_dir (Path): 配置目录路径
|
|
104
|
+
|
|
105
|
+
Example:
|
|
106
|
+
>>> indexer = ConfigIndexer()
|
|
107
|
+
>>> indexer.generate_index()
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def __init__(self, config_dir=None):
|
|
111
|
+
"""
|
|
112
|
+
初始化索引器。
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
config_dir: 配置目录路径(默认为 script/config/)
|
|
116
|
+
"""
|
|
117
|
+
if config_dir is None:
|
|
118
|
+
self.config_dir = CONFIG_DIR
|
|
119
|
+
else:
|
|
120
|
+
self.config_dir = Path(config_dir)
|
|
121
|
+
|
|
122
|
+
def scan_configs(self):
|
|
123
|
+
"""
|
|
124
|
+
扫描所有 YAML 配置文件。
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
list: 配置信息列表
|
|
128
|
+
"""
|
|
129
|
+
configs = []
|
|
130
|
+
|
|
131
|
+
# 遍历所有域目录
|
|
132
|
+
for domain_dir in sorted(self.config_dir.iterdir()):
|
|
133
|
+
if not domain_dir.is_dir() or domain_dir.name.startswith('.'):
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
domain = domain_dir.name
|
|
137
|
+
|
|
138
|
+
# 遍历该域下的所有 YAML 文件
|
|
139
|
+
for yml_file in sorted(domain_dir.glob('*.yml')):
|
|
140
|
+
config_info = self._parse_config(yml_file, domain)
|
|
141
|
+
if config_info:
|
|
142
|
+
configs.append(config_info)
|
|
143
|
+
|
|
144
|
+
return configs
|
|
145
|
+
|
|
146
|
+
def _parse_config(self, yml_file, domain):
|
|
147
|
+
"""
|
|
148
|
+
解析单个配置文件。
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
yml_file: YAML 文件路径
|
|
152
|
+
domain: 业务域代码
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
dict: 配置信息字典
|
|
156
|
+
"""
|
|
157
|
+
try:
|
|
158
|
+
with open(yml_file, 'r', encoding='utf-8') as f:
|
|
159
|
+
data = yaml.safe_load(f)
|
|
160
|
+
|
|
161
|
+
# 提取模块信息
|
|
162
|
+
modules = data.get('modules', [])
|
|
163
|
+
if not modules:
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
module = modules[0] # 取第一个模块
|
|
167
|
+
mode = module.get('mode', 'single')
|
|
168
|
+
code = module.get('code', '')
|
|
169
|
+
name = module.get('name', '')
|
|
170
|
+
description = module.get('description', '')
|
|
171
|
+
fields = module.get('fields', [])
|
|
172
|
+
|
|
173
|
+
# 构建配置信息
|
|
174
|
+
config_info = {
|
|
175
|
+
'file': str(yml_file.relative_to(self.config_dir)),
|
|
176
|
+
'mode': mode,
|
|
177
|
+
'domain': domain,
|
|
178
|
+
'code': code,
|
|
179
|
+
'name': name,
|
|
180
|
+
'description': description,
|
|
181
|
+
'field_count': len(fields),
|
|
182
|
+
'last_modified': datetime.fromtimestamp(yml_file.stat().st_mtime).isoformat()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# 根据 mode 添加额外信息
|
|
186
|
+
if mode in ['master-detail', 'master-detail-with-file']:
|
|
187
|
+
config_info['has_master'] = 'master' in module
|
|
188
|
+
config_info['has_detail'] = 'detail' in module
|
|
189
|
+
if config_info['has_master']:
|
|
190
|
+
config_info['master_code'] = module.get('master', {}).get('code', '')
|
|
191
|
+
if config_info['has_detail']:
|
|
192
|
+
config_info['detail_code'] = module.get('detail', {}).get('code', '')
|
|
193
|
+
|
|
194
|
+
elif mode in ['master-detail-modules', 'master-detail-modules-with-file']:
|
|
195
|
+
config_info['has_master'] = 'master' in module
|
|
196
|
+
detail_modules = module.get('detail_modules', []) or []
|
|
197
|
+
config_info['detail_module_count'] = len(detail_modules)
|
|
198
|
+
config_info['detail_module_codes'] = [dm.get('code', '') for dm in detail_modules]
|
|
199
|
+
if config_info['has_master']:
|
|
200
|
+
config_info['master_code'] = module.get('master', {}).get('code', '')
|
|
201
|
+
|
|
202
|
+
elif mode in ['multi-table', 'multi-table-with-file']:
|
|
203
|
+
sub_tables = module.get('sub_tables', [])
|
|
204
|
+
config_info['sub_table_count'] = len(sub_tables)
|
|
205
|
+
config_info['sub_table_codes'] = [st.get('code', '') for st in sub_tables]
|
|
206
|
+
|
|
207
|
+
return config_info
|
|
208
|
+
|
|
209
|
+
except Exception as e:
|
|
210
|
+
print(f"⚠️ 解析失败 {yml_file}: {e}")
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
def generate_index(self, output_file=None):
|
|
214
|
+
"""
|
|
215
|
+
生成索引文件。
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
output_file: 输出文件路径(默认为 config/index.json)
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
dict: 索引数据
|
|
222
|
+
"""
|
|
223
|
+
if output_file is None:
|
|
224
|
+
output_file = self.config_dir / 'index.json'
|
|
225
|
+
|
|
226
|
+
# 扫描所有配置
|
|
227
|
+
print(f"正在扫描 {self.config_dir}...")
|
|
228
|
+
configs = self.scan_configs()
|
|
229
|
+
|
|
230
|
+
# 构建索引数据
|
|
231
|
+
index_data = {
|
|
232
|
+
'last_updated': datetime.now().isoformat(),
|
|
233
|
+
'total_configs': len(configs),
|
|
234
|
+
'domains': {},
|
|
235
|
+
'configs': configs
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
# 按域分组统计
|
|
239
|
+
for config in configs:
|
|
240
|
+
domain = config['domain']
|
|
241
|
+
if domain not in index_data['domains']:
|
|
242
|
+
index_data['domains'][domain] = {
|
|
243
|
+
'domain': domain,
|
|
244
|
+
'count': 0,
|
|
245
|
+
'modules': []
|
|
246
|
+
}
|
|
247
|
+
index_data['domains'][domain]['count'] += 1
|
|
248
|
+
index_data['domains'][domain]['modules'].append({
|
|
249
|
+
'code': config['code'],
|
|
250
|
+
'name': config['name'],
|
|
251
|
+
'mode': config['mode']
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
# 写入 JSON 文件
|
|
255
|
+
with open(output_file, 'w', encoding='utf-8') as f:
|
|
256
|
+
json.dump(index_data, f, ensure_ascii=False, indent=2)
|
|
257
|
+
|
|
258
|
+
print(f"\n✅ 发现 {len(configs)} 个配置文件:")
|
|
259
|
+
for config in configs:
|
|
260
|
+
print(f" - {config['file']}")
|
|
261
|
+
print(f" └─ 模式: {config['mode']} | {config['domain']}/{config['code']} - {config['name']}")
|
|
262
|
+
|
|
263
|
+
print(f"\n✅ 索引已生成: {output_file}")
|
|
264
|
+
|
|
265
|
+
return index_data
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class ReportWriter:
|
|
269
|
+
"""
|
|
270
|
+
报告写入器。
|
|
271
|
+
|
|
272
|
+
将生成结果拆分为两个文件:
|
|
273
|
+
- gen_{timestamp}.txt: 生成报告(结构化的生成结果)
|
|
274
|
+
- log_{timestamp}.txt: 控制台输出(运行时日志)
|
|
275
|
+
|
|
276
|
+
Attributes:
|
|
277
|
+
report_dir (Path): 报告输出目录
|
|
278
|
+
report_file (Path): 生成报告文件路径 (gen_*.txt)
|
|
279
|
+
log_file (Path): 控制台日志文件路径 (log_*.txt)
|
|
280
|
+
start_time (datetime): 开始时间
|
|
281
|
+
records (list): 生成记录列表
|
|
282
|
+
log_lines (list): 控制台日志行列表
|
|
283
|
+
|
|
284
|
+
Example:
|
|
285
|
+
>>> report = ReportWriter()
|
|
286
|
+
>>> with report.capture():
|
|
287
|
+
... print("正在处理模块 simple...")
|
|
288
|
+
>>> report.add_record('simple', True, '生成成功')
|
|
289
|
+
>>> report.write()
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
class _Tee:
|
|
293
|
+
"""同时输出到控制台和日志列表的 Tee 类。"""
|
|
294
|
+
def __init__(self, log_lines, original_stdout):
|
|
295
|
+
self.log_lines = log_lines
|
|
296
|
+
self.original_stdout = original_stdout
|
|
297
|
+
|
|
298
|
+
def write(self, message):
|
|
299
|
+
# 输出到控制台
|
|
300
|
+
self.original_stdout.write(message)
|
|
301
|
+
# 同时捕获到日志列表(去除空行)
|
|
302
|
+
stripped = message.rstrip('\n\r')
|
|
303
|
+
if stripped:
|
|
304
|
+
timestamp = datetime.now().strftime('%H:%M:%S')
|
|
305
|
+
self.log_lines.append(f"[{timestamp}] {stripped}")
|
|
306
|
+
|
|
307
|
+
def flush(self):
|
|
308
|
+
self.original_stdout.flush()
|
|
309
|
+
|
|
310
|
+
def __init__(self, output_dir=None, batch_timestamp=None, dry_run=False):
|
|
311
|
+
"""
|
|
312
|
+
初始化报告写入器。
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
output_dir: 输出目录(默认为 script/output/)
|
|
316
|
+
batch_timestamp: 批量模式时间戳(用于多个模块共享同一日志文件)
|
|
317
|
+
dry_run: 预览模式——不创建目录、不写任何文件
|
|
318
|
+
"""
|
|
319
|
+
self.dry_run = dry_run
|
|
320
|
+
if output_dir is None:
|
|
321
|
+
self.report_dir = OUTPUT_DIR
|
|
322
|
+
else:
|
|
323
|
+
self.report_dir = Path(output_dir)
|
|
324
|
+
|
|
325
|
+
if not dry_run:
|
|
326
|
+
self.report_dir.mkdir(parents=True, exist_ok=True)
|
|
327
|
+
|
|
328
|
+
# 批量模式使用共享时间戳,否则生成新的
|
|
329
|
+
if batch_timestamp:
|
|
330
|
+
self.timestamp = batch_timestamp
|
|
331
|
+
else:
|
|
332
|
+
self.timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
333
|
+
|
|
334
|
+
# 拆分为两个文件:生成报告 + 控制台日志
|
|
335
|
+
self.report_file = self.report_dir / f'gen_{self.timestamp}.txt'
|
|
336
|
+
self.log_file = self.report_dir / f'log_{self.timestamp}.txt'
|
|
337
|
+
self.start_time = datetime.now()
|
|
338
|
+
self.records = []
|
|
339
|
+
self.log_lines = [] # 控制台日志行
|
|
340
|
+
|
|
341
|
+
# stdout 捕获状态
|
|
342
|
+
self._original_stdout = None
|
|
343
|
+
self._tee = None
|
|
344
|
+
|
|
345
|
+
# 标记是否为批量模式(追加而非覆盖)
|
|
346
|
+
self.is_batch_mode = batch_timestamp is not None
|
|
347
|
+
|
|
348
|
+
def capture_start(self):
|
|
349
|
+
"""开始捕获 stdout。"""
|
|
350
|
+
import sys
|
|
351
|
+
self._original_stdout = sys.stdout
|
|
352
|
+
self._tee = self._Tee(self.log_lines, self._original_stdout)
|
|
353
|
+
sys.stdout = self._tee
|
|
354
|
+
|
|
355
|
+
def capture_end(self):
|
|
356
|
+
"""停止捕获 stdout。"""
|
|
357
|
+
import sys
|
|
358
|
+
if self._original_stdout:
|
|
359
|
+
sys.stdout = self._original_stdout
|
|
360
|
+
self._original_stdout = None
|
|
361
|
+
self._tee = None
|
|
362
|
+
|
|
363
|
+
def capture(self):
|
|
364
|
+
"""返回一个 context manager 用于捕获 stdout。"""
|
|
365
|
+
return self._CaptureContext(self)
|
|
366
|
+
|
|
367
|
+
class _CaptureContext:
|
|
368
|
+
"""stdout 捕获的 context manager。"""
|
|
369
|
+
def __init__(self, report_writer):
|
|
370
|
+
self.report_writer = report_writer
|
|
371
|
+
|
|
372
|
+
def __enter__(self):
|
|
373
|
+
self.report_writer.capture_start()
|
|
374
|
+
return self
|
|
375
|
+
|
|
376
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
377
|
+
self.report_writer.capture_end()
|
|
378
|
+
return False
|
|
379
|
+
|
|
380
|
+
def add_record(self, module_code: str, success: bool, message: str, config_file: str = ''):
|
|
381
|
+
"""
|
|
382
|
+
添加生成记录。
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
module_code: 模块代码
|
|
386
|
+
success: 是否成功
|
|
387
|
+
message: 消息内容
|
|
388
|
+
config_file: 配置文件路径
|
|
389
|
+
"""
|
|
390
|
+
self.records.append({
|
|
391
|
+
'module': module_code,
|
|
392
|
+
'success': success,
|
|
393
|
+
'message': message,
|
|
394
|
+
'config': config_file,
|
|
395
|
+
'time': datetime.now().isoformat()
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
def log(self, message: str):
|
|
399
|
+
"""
|
|
400
|
+
添加控制台日志行。
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
message: 日志消息
|
|
404
|
+
"""
|
|
405
|
+
timestamp = datetime.now().strftime('%H:%M:%S')
|
|
406
|
+
self.log_lines.append(f"[{timestamp}] {message}")
|
|
407
|
+
|
|
408
|
+
def write(self, project_info: dict = None, summary: dict = None, is_final: bool = True):
|
|
409
|
+
"""
|
|
410
|
+
写入报告文件和日志文件。
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
project_info: 项目信息
|
|
414
|
+
summary: 汇总信息 {'total': N, 'success': N, 'failed': N}
|
|
415
|
+
is_final: 是否为最后一次写入(批量模式下控制是否写入汇总)
|
|
416
|
+
"""
|
|
417
|
+
end_time = datetime.now()
|
|
418
|
+
duration = (end_time - self.start_time).total_seconds()
|
|
419
|
+
|
|
420
|
+
# 批量模式:追加记录,非批量模式:完整报告
|
|
421
|
+
if self.is_batch_mode:
|
|
422
|
+
self._write_batch_record(project_info, summary)
|
|
423
|
+
else:
|
|
424
|
+
self._write_full_report(end_time, duration, project_info, summary)
|
|
425
|
+
|
|
426
|
+
# 写入控制台日志文件
|
|
427
|
+
self._write_log_file(end_time, duration)
|
|
428
|
+
|
|
429
|
+
def _write_log_file(self, end_time, duration):
|
|
430
|
+
"""
|
|
431
|
+
写入控制台日志文件 (log_{timestamp}.txt)。
|
|
432
|
+
批量模式下使用追加模式,非批量模式使用覆盖模式。
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
end_time: 结束时间
|
|
436
|
+
duration: 运行时长
|
|
437
|
+
"""
|
|
438
|
+
if self.is_batch_mode:
|
|
439
|
+
# 批量模式:追加日志
|
|
440
|
+
self._write_batch_log(end_time, duration)
|
|
441
|
+
else:
|
|
442
|
+
# 非批量模式:完整日志(覆盖)
|
|
443
|
+
self._write_full_log(end_time, duration)
|
|
444
|
+
|
|
445
|
+
def _write_batch_log(self, end_time, duration):
|
|
446
|
+
"""
|
|
447
|
+
批量模式:追加日志到文件。
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
end_time: 结束时间
|
|
451
|
+
duration: 运行时长
|
|
452
|
+
"""
|
|
453
|
+
# 如果文件不存在,先写入头部(使用追加模式)
|
|
454
|
+
if not self.log_file.exists():
|
|
455
|
+
header = []
|
|
456
|
+
header.append("=" * 60)
|
|
457
|
+
header.append("Libra Boot Low 代码生成器 - 批量生成日志")
|
|
458
|
+
header.append("=" * 60)
|
|
459
|
+
header.append("")
|
|
460
|
+
header.append(f"批量开始时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
461
|
+
header.append(f"日志文件: {self.log_file}")
|
|
462
|
+
header.append("")
|
|
463
|
+
header.append("-" * 40)
|
|
464
|
+
header.append("运行日志")
|
|
465
|
+
header.append("-" * 40)
|
|
466
|
+
with open(self.log_file, 'a', encoding='utf-8') as f:
|
|
467
|
+
f.write('\n'.join(header) + '\n')
|
|
468
|
+
|
|
469
|
+
# 追加当前模块日志(使用追加模式)
|
|
470
|
+
with open(self.log_file, 'a', encoding='utf-8') as f:
|
|
471
|
+
if self.log_lines:
|
|
472
|
+
for line in self.log_lines:
|
|
473
|
+
f.write(f"{line}\n")
|
|
474
|
+
f.write("") # 空行分隔
|
|
475
|
+
|
|
476
|
+
print(f"📋 控制台日志: {self.log_file}")
|
|
477
|
+
|
|
478
|
+
def _write_full_log(self, end_time, duration):
|
|
479
|
+
"""
|
|
480
|
+
非批量模式:写入完整日志(覆盖)。
|
|
481
|
+
|
|
482
|
+
Args:
|
|
483
|
+
end_time: 结束时间
|
|
484
|
+
duration: 运行时长
|
|
485
|
+
"""
|
|
486
|
+
lines = []
|
|
487
|
+
lines.append("=" * 60)
|
|
488
|
+
lines.append("Libra Boot Low 代码生成器 - 控制台日志")
|
|
489
|
+
lines.append("=" * 60)
|
|
490
|
+
lines.append("")
|
|
491
|
+
lines.append(f"开始时间: {self.start_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
492
|
+
lines.append(f"结束时间: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
493
|
+
lines.append(f"运行时长: {duration:.2f} 秒")
|
|
494
|
+
lines.append(f"日志文件: {self.log_file}")
|
|
495
|
+
lines.append("")
|
|
496
|
+
lines.append("-" * 40)
|
|
497
|
+
lines.append("运行日志")
|
|
498
|
+
lines.append("-" * 40)
|
|
499
|
+
|
|
500
|
+
if self.log_lines:
|
|
501
|
+
for line in self.log_lines:
|
|
502
|
+
lines.append(line)
|
|
503
|
+
else:
|
|
504
|
+
lines.append("(无运行日志)")
|
|
505
|
+
|
|
506
|
+
lines.append("")
|
|
507
|
+
lines.append("=" * 60)
|
|
508
|
+
lines.append("日志结束")
|
|
509
|
+
lines.append("=" * 60)
|
|
510
|
+
|
|
511
|
+
with open(self.log_file, 'w', encoding='utf-8') as f:
|
|
512
|
+
f.write('\n'.join(lines))
|
|
513
|
+
|
|
514
|
+
print(f"📋 控制台日志: {self.log_file}")
|
|
515
|
+
|
|
516
|
+
def _write_batch_record(self, project_info: dict = None, summary: dict = None):
|
|
517
|
+
"""
|
|
518
|
+
批量模式:追加单条记录到日志文件。
|
|
519
|
+
|
|
520
|
+
Args:
|
|
521
|
+
project_info: 项目信息
|
|
522
|
+
summary: 汇总信息(仅最后一个模块时使用)
|
|
523
|
+
"""
|
|
524
|
+
# 如果文件不存在,先写入头部
|
|
525
|
+
if not self.report_file.exists():
|
|
526
|
+
header = []
|
|
527
|
+
header.append("=" * 60)
|
|
528
|
+
header.append("Libra Boot Low 代码生成器 - 批量生成报告")
|
|
529
|
+
header.append("=" * 60)
|
|
530
|
+
header.append("")
|
|
531
|
+
header.append(f"批量开始时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
532
|
+
header.append(f"报告文件: {self.report_file}")
|
|
533
|
+
header.append("")
|
|
534
|
+
header.append("-" * 40)
|
|
535
|
+
header.append("生成记录")
|
|
536
|
+
header.append("-" * 40)
|
|
537
|
+
with open(self.report_file, 'w', encoding='utf-8') as f:
|
|
538
|
+
f.write('\n'.join(header) + '\n')
|
|
539
|
+
|
|
540
|
+
# 追加当前模块记录
|
|
541
|
+
with open(self.report_file, 'a', encoding='utf-8') as f:
|
|
542
|
+
for record in self.records:
|
|
543
|
+
status = "✅ 成功" if record['success'] else "❌ 失败"
|
|
544
|
+
f.write(f" [{status}] {record['module']}\n")
|
|
545
|
+
if record['config']:
|
|
546
|
+
f.write(f" 配置: {record['config']}\n")
|
|
547
|
+
f.write(f" 消息: {record['message']}\n")
|
|
548
|
+
f.write(f" 时间: {record['time']}\n\n")
|
|
549
|
+
|
|
550
|
+
print(f" 📄 记录已追加: {self.report_file}")
|
|
551
|
+
|
|
552
|
+
def _write_full_report(self, end_time, duration, project_info: dict = None, summary: dict = None):
|
|
553
|
+
"""
|
|
554
|
+
非批量模式:写入完整报告。
|
|
555
|
+
|
|
556
|
+
Args:
|
|
557
|
+
end_time: 结束时间
|
|
558
|
+
duration: 运行时长
|
|
559
|
+
project_info: 项目信息
|
|
560
|
+
summary: 汇总信息
|
|
561
|
+
"""
|
|
562
|
+
lines = []
|
|
563
|
+
lines.append("=" * 60)
|
|
564
|
+
lines.append("Libra Boot Low 代码生成器 - 运行报告")
|
|
565
|
+
lines.append("=" * 60)
|
|
566
|
+
lines.append("")
|
|
567
|
+
lines.append(f"开始时间: {self.start_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
568
|
+
lines.append(f"结束时间: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
569
|
+
lines.append(f"运行时长: {duration:.2f} 秒")
|
|
570
|
+
lines.append(f"报告文件: {self.report_file}")
|
|
571
|
+
lines.append("")
|
|
572
|
+
|
|
573
|
+
if project_info:
|
|
574
|
+
lines.append("-" * 40)
|
|
575
|
+
lines.append("项目信息")
|
|
576
|
+
lines.append("-" * 40)
|
|
577
|
+
lines.append(f" 名称: {project_info.get('name', 'Unknown')}")
|
|
578
|
+
lines.append(f" 代码: {project_info.get('code', 'Unknown')}")
|
|
579
|
+
lines.append(f" 版本: {project_info.get('version', 'Unknown')}")
|
|
580
|
+
lines.append("")
|
|
581
|
+
|
|
582
|
+
if summary:
|
|
583
|
+
lines.append("-" * 40)
|
|
584
|
+
lines.append("生成汇总")
|
|
585
|
+
lines.append("-" * 40)
|
|
586
|
+
lines.append(f" 总计: {summary.get('total', 0)}")
|
|
587
|
+
lines.append(f" 成功: {summary.get('success', 0)}")
|
|
588
|
+
lines.append(f" 失败: {summary.get('failed', 0)}")
|
|
589
|
+
lines.append("")
|
|
590
|
+
|
|
591
|
+
if self.records:
|
|
592
|
+
lines.append("-" * 40)
|
|
593
|
+
lines.append("生成记录")
|
|
594
|
+
lines.append("-" * 40)
|
|
595
|
+
for record in self.records:
|
|
596
|
+
status = "✅ 成功" if record['success'] else "❌ 失败"
|
|
597
|
+
lines.append(f" [{status}] {record['module']}")
|
|
598
|
+
if record['config']:
|
|
599
|
+
lines.append(f" 配置: {record['config']}")
|
|
600
|
+
lines.append(f" 消息: {record['message']}")
|
|
601
|
+
lines.append("")
|
|
602
|
+
|
|
603
|
+
lines.append("=" * 60)
|
|
604
|
+
lines.append("报告结束")
|
|
605
|
+
lines.append("=" * 60)
|
|
606
|
+
|
|
607
|
+
with open(self.report_file, 'w', encoding='utf-8') as f:
|
|
608
|
+
f.write('\n'.join(lines))
|
|
609
|
+
|
|
610
|
+
print(f"\n📄 生成报告: {self.report_file}")
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
class Generator:
|
|
614
|
+
"""
|
|
615
|
+
代码生成器入口路由器。
|
|
616
|
+
|
|
617
|
+
负责协调 ConfigLoader、FieldBuilder 和各模式生成模块,
|
|
618
|
+
根据 mode 参数路由到对应的 module/gen_*.py 模块执行生成逻辑。
|
|
619
|
+
|
|
620
|
+
Attributes:
|
|
621
|
+
config_path (str): 配置文件路径
|
|
622
|
+
output_dir (Path): 输出目录路径
|
|
623
|
+
script_dir (Path): 脚本所在目录
|
|
624
|
+
template_dir (Path): 模板目录路径
|
|
625
|
+
config_loader (ConfigLoader): 配置加载器实例
|
|
626
|
+
field_builder (FieldBuilder): 字段构建器实例
|
|
627
|
+
config (dict): 加载后的配置数据
|
|
628
|
+
project_info (dict): 项目信息字典
|
|
629
|
+
report (ReportWriter): 报告写入器实例
|
|
630
|
+
|
|
631
|
+
Example:
|
|
632
|
+
>>> generator = Generator('config/test/t1000.yml', '../../out')
|
|
633
|
+
>>> generator.validate()
|
|
634
|
+
True
|
|
635
|
+
>>> generator.generate()
|
|
636
|
+
"""
|
|
637
|
+
|
|
638
|
+
def __init__(self, config_path: str, output_dir: str, batch_timestamp: str = None, dry_run: bool = False):
|
|
639
|
+
"""
|
|
640
|
+
初始化代码生成器。
|
|
641
|
+
|
|
642
|
+
Args:
|
|
643
|
+
config_path (str): YAML 配置文件的路径
|
|
644
|
+
output_dir (str): 生成代码的输出目录路径
|
|
645
|
+
batch_timestamp (str, optional): 批量模式时间戳(用于共享日志文件)
|
|
646
|
+
"""
|
|
647
|
+
self.dry_run = dry_run
|
|
648
|
+
self.config_path = config_path
|
|
649
|
+
self.output_dir = Path(output_dir)
|
|
650
|
+
self.batch_timestamp = batch_timestamp
|
|
651
|
+
# 使用模块级常量
|
|
652
|
+
self.script_dir = SCRIPT_DIR
|
|
653
|
+
self.template_dir = TEMPLATE_DIR
|
|
654
|
+
|
|
655
|
+
self.config_loader = ConfigLoader(config_path)
|
|
656
|
+
self.field_builder = FieldBuilder()
|
|
657
|
+
|
|
658
|
+
self.config = self.config_loader.load()
|
|
659
|
+
self.project_info = self.config_loader.get_project_info()
|
|
660
|
+
|
|
661
|
+
# 初始化报告写入器(批量模式下共享时间戳)
|
|
662
|
+
self.report = ReportWriter(batch_timestamp=batch_timestamp, dry_run=dry_run)
|
|
663
|
+
|
|
664
|
+
def validate(self) -> bool:
|
|
665
|
+
"""
|
|
666
|
+
验证配置文件的合法性。
|
|
667
|
+
|
|
668
|
+
调用 ConfigLoader 的 validate 方法检查配置文件的结构和内容,
|
|
669
|
+
包括必需字段、字段类型、枚举值等。
|
|
670
|
+
|
|
671
|
+
Returns:
|
|
672
|
+
bool: 验证通过返回 True,否则返回 False
|
|
673
|
+
"""
|
|
674
|
+
valid, errors = self.config_loader.validate()
|
|
675
|
+
if not valid:
|
|
676
|
+
print("❌ 配置验证失败:")
|
|
677
|
+
for error in errors:
|
|
678
|
+
print(f" - {error}")
|
|
679
|
+
return False
|
|
680
|
+
print("✅ 配置验证通过")
|
|
681
|
+
return True
|
|
682
|
+
|
|
683
|
+
def list_modules(self):
|
|
684
|
+
"""
|
|
685
|
+
列出配置文件中定义的所有模块。
|
|
686
|
+
|
|
687
|
+
以表格形式输出模块索引、名称、代码和生成模式。
|
|
688
|
+
"""
|
|
689
|
+
print(f"\n📋 配置文件: {self.config_path}")
|
|
690
|
+
print(f"📦 项目: {self.project_info.get('name', 'Unknown')}\n")
|
|
691
|
+
print("模块列表:")
|
|
692
|
+
for i, module in enumerate(self.config_loader.get_modules(), 1):
|
|
693
|
+
mode = module.get('mode', 'single')
|
|
694
|
+
print(f" {i}. {module.get('name')} ({module.get('code')}) [{mode}]")
|
|
695
|
+
|
|
696
|
+
def generate(self, module_code: str = None, mode_override: str = None):
|
|
697
|
+
"""
|
|
698
|
+
生成代码的主入口方法。
|
|
699
|
+
|
|
700
|
+
遍历配置文件中的所有模块,根据 module_code 过滤(可选),
|
|
701
|
+
调用对应的生成器函数生成代码,最后写入清单文件和报告。
|
|
702
|
+
|
|
703
|
+
Args:
|
|
704
|
+
module_code (str, optional): 仅生成指定 code 的模块。
|
|
705
|
+
mode_override (str, optional): 覆盖 YAML 配置中的生成模式。
|
|
706
|
+
"""
|
|
707
|
+
modules = self.config_loader.get_modules()
|
|
708
|
+
generated = []
|
|
709
|
+
failed = []
|
|
710
|
+
|
|
711
|
+
# DRY-RUN:通知渲染器只预览不落盘
|
|
712
|
+
_tr.DRY_RUN = self.dry_run
|
|
713
|
+
|
|
714
|
+
# 开始捕获 stdout(所有 print 输出)
|
|
715
|
+
self.report.capture_start()
|
|
716
|
+
|
|
717
|
+
try:
|
|
718
|
+
for module in modules:
|
|
719
|
+
if module_code and module.get('code') != module_code:
|
|
720
|
+
continue
|
|
721
|
+
|
|
722
|
+
if mode_override:
|
|
723
|
+
module = dict(module)
|
|
724
|
+
module['mode'] = mode_override
|
|
725
|
+
|
|
726
|
+
try:
|
|
727
|
+
self._generate_module(module)
|
|
728
|
+
generated.append(module.get('code'))
|
|
729
|
+
self.report.add_record(
|
|
730
|
+
module.get('code'),
|
|
731
|
+
True,
|
|
732
|
+
f"生成成功 - {module.get('name')}",
|
|
733
|
+
self.config_path
|
|
734
|
+
)
|
|
735
|
+
except Exception as e:
|
|
736
|
+
failed.append(module.get('code'))
|
|
737
|
+
self.report.add_record(
|
|
738
|
+
module.get('code'),
|
|
739
|
+
False,
|
|
740
|
+
f"生成失败: {str(e)}",
|
|
741
|
+
self.config_path
|
|
742
|
+
)
|
|
743
|
+
print(f"❌ 模块 '{module.get('name')}' 生成失败: {e}")
|
|
744
|
+
import traceback
|
|
745
|
+
traceback.print_exc()
|
|
746
|
+
|
|
747
|
+
if self.dry_run:
|
|
748
|
+
print("\n👁 [DRY-RUN] 预览模式:未写入任何文件(含清单)")
|
|
749
|
+
else:
|
|
750
|
+
self._write_manifest(generated)
|
|
751
|
+
|
|
752
|
+
finally:
|
|
753
|
+
# 停止捕获 stdout(无论成功或失败都要执行)
|
|
754
|
+
self.report.capture_end()
|
|
755
|
+
|
|
756
|
+
# 写入报告(此时 stdout 已恢复正常;DRY-RUN 只回放不落盘)
|
|
757
|
+
if not self.dry_run:
|
|
758
|
+
self.report.write(
|
|
759
|
+
project_info=self.project_info,
|
|
760
|
+
summary={
|
|
761
|
+
'total': len(generated) + len(failed),
|
|
762
|
+
'success': len(generated),
|
|
763
|
+
'failed': len(failed)
|
|
764
|
+
}
|
|
765
|
+
)
|
|
766
|
+
|
|
767
|
+
print(f"\n{'=' * 60}")
|
|
768
|
+
print(f"✅ 生成完成!")
|
|
769
|
+
print(f"📦 共生成 {len(generated)} 个模块: {', '.join(generated)}")
|
|
770
|
+
if failed:
|
|
771
|
+
print(f"❌ 失败 {len(failed)} 个模块: {', '.join(failed)}")
|
|
772
|
+
print(f"📂 输出目录: {self.output_dir.absolute()}")
|
|
773
|
+
print(f"📄 清单文件: {self.output_dir / '.manifest.json'}")
|
|
774
|
+
print(f"{'=' * 60}")
|
|
775
|
+
|
|
776
|
+
def _generate_module(self, module: dict):
|
|
777
|
+
"""
|
|
778
|
+
根据 mode 路由到对应的生成器模块。
|
|
779
|
+
|
|
780
|
+
Args:
|
|
781
|
+
module (dict): 模块配置字典
|
|
782
|
+
|
|
783
|
+
Raises:
|
|
784
|
+
ValueError: 当 mode 不在支持的模式列表中时抛出
|
|
785
|
+
"""
|
|
786
|
+
mode = module.get('mode', 'single')
|
|
787
|
+
|
|
788
|
+
# 检查 mode 是否支持
|
|
789
|
+
if mode not in MODE_GENERATORS:
|
|
790
|
+
raise ValueError(f"不支持的生成模式: {mode}")
|
|
791
|
+
|
|
792
|
+
# 获取对应的生成函数
|
|
793
|
+
generator_func = MODE_GENERATORS[mode]
|
|
794
|
+
|
|
795
|
+
# 根据模式类型使用不同的参数名
|
|
796
|
+
if mode in SINGLE_MODES:
|
|
797
|
+
generator_func(
|
|
798
|
+
config_loader=self.config_loader,
|
|
799
|
+
field_builder=self.field_builder,
|
|
800
|
+
renderer_class=TemplateRenderer,
|
|
801
|
+
template_dir=self.template_dir,
|
|
802
|
+
output_dir=self.output_dir,
|
|
803
|
+
project_info=self.project_info,
|
|
804
|
+
module=module,
|
|
805
|
+
)
|
|
806
|
+
else:
|
|
807
|
+
generator_func(
|
|
808
|
+
config_loader=self.config_loader,
|
|
809
|
+
field_builder=self.field_builder,
|
|
810
|
+
renderer_class=TemplateRenderer,
|
|
811
|
+
template_dir=self.template_dir,
|
|
812
|
+
output_dir=self.output_dir,
|
|
813
|
+
project_info=self.project_info,
|
|
814
|
+
group_module=module,
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
def _write_manifest(self, generated: list):
|
|
818
|
+
"""
|
|
819
|
+
写入清单文件。
|
|
820
|
+
|
|
821
|
+
Args:
|
|
822
|
+
generated (list): 已成功生成的模块 code 列表
|
|
823
|
+
"""
|
|
824
|
+
manifest = {
|
|
825
|
+
'generated_at': datetime.now().isoformat(),
|
|
826
|
+
'config_file': str(self.config_path),
|
|
827
|
+
'project': self.project_info,
|
|
828
|
+
'modules': generated,
|
|
829
|
+
'total': len(generated),
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
manifest_path = self.output_dir / '.manifest.json'
|
|
833
|
+
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
834
|
+
|
|
835
|
+
with open(manifest_path, 'w', encoding='utf-8') as f:
|
|
836
|
+
json.dump(manifest, f, ensure_ascii=False, indent=2)
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
def main():
|
|
840
|
+
"""
|
|
841
|
+
CLI 入口函数。
|
|
842
|
+
|
|
843
|
+
命令行参数:
|
|
844
|
+
config: 配置文件路径(必需)
|
|
845
|
+
output: 输出目录(可选,默认 ../../out)
|
|
846
|
+
--update-index: 仅更新配置索引
|
|
847
|
+
--module, -m: 仅生成指定模块
|
|
848
|
+
--mode: 覆盖 YAML 中的生成模式
|
|
849
|
+
--validate, -v: 仅验证配置文件
|
|
850
|
+
--list, -l: 列出配置中的所有模块
|
|
851
|
+
--batch-timestamp: 批量模式时间戳(用于多个模块共享同一日志文件)
|
|
852
|
+
"""
|
|
853
|
+
parser = argparse.ArgumentParser(
|
|
854
|
+
description='Libra Boot Low 项目级代码生成器(索引 + 生成 + 报告)',
|
|
855
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
856
|
+
epilog="""
|
|
857
|
+
示例:
|
|
858
|
+
%(prog)s --update-index # 更新配置索引
|
|
859
|
+
%(prog)s config/test/t1000.yml # 生成指定模块
|
|
860
|
+
%(prog)s config/test/t1000.yml --mode single-with-file # 覆盖生成模式
|
|
861
|
+
%(prog)s my-config.yml --validate # 仅验证配置
|
|
862
|
+
%(prog)s my-config.yml --list # 列出模块
|
|
863
|
+
%(prog)s config/test/t1000.yml --batch-timestamp 20260101_120000 # 批量模式
|
|
864
|
+
"""
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
parser.add_argument('config', nargs='?', help='配置文件路径 (YAML)')
|
|
868
|
+
parser.add_argument('output', nargs='?', default='../../out', help='输出目录')
|
|
869
|
+
parser.add_argument('--update-index', '-u', action='store_true', help='更新配置索引')
|
|
870
|
+
parser.add_argument('--module', '-m', help='仅生成指定模块')
|
|
871
|
+
parser.add_argument('--mode', help='覆盖 YAML 中的生成模式')
|
|
872
|
+
parser.add_argument('--validate', '-v', action='store_true', help='仅验证配置文件')
|
|
873
|
+
parser.add_argument('--list', '-l', action='store_true', help='列出配置中的所有模块')
|
|
874
|
+
parser.add_argument('--batch-timestamp', help='批量模式时间戳(共享日志文件)')
|
|
875
|
+
parser.add_argument('--version', action='version', version='%(prog)s 1.1.1', help='显示版本号并退出')
|
|
876
|
+
parser.add_argument('--list-modes', action='store_true', help='列出全部支持的生成模式')
|
|
877
|
+
parser.add_argument('--dry-run', action='store_true', help='仅预览将生成的文件列表,不写入磁盘')
|
|
878
|
+
|
|
879
|
+
args = parser.parse_args()
|
|
880
|
+
if args.list_modes:
|
|
881
|
+
print("支持的生成模式:")
|
|
882
|
+
print(" single 单表 CRUD")
|
|
883
|
+
print(" single-with-file 单表 + 附件上传")
|
|
884
|
+
print(" multi-table 多表关联")
|
|
885
|
+
print(" multi-table-with-file 多表 + 附件")
|
|
886
|
+
print(" master-detail 主从表(一对多)")
|
|
887
|
+
print(" master-detail-with-file 主从表 + 附件")
|
|
888
|
+
print(" master-detail-modules 主从表(模块化)")
|
|
889
|
+
print(" master-detail-modules-with-file 主从表模块化 + 附件")
|
|
890
|
+
print(" master-details-tabs 主多明细(页签)")
|
|
891
|
+
print(" master-details-tabs-with-file 主多明细页签 + 附件")
|
|
892
|
+
print(" master-details-tabs-dialog 主多明细(页签弹窗)")
|
|
893
|
+
print(" master-details-tabs-dialog-with-file 主多明细页签弹窗 + 附件")
|
|
894
|
+
return
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
# 仅更新索引
|
|
898
|
+
if args.update_index:
|
|
899
|
+
indexer = ConfigIndexer()
|
|
900
|
+
indexer.generate_index()
|
|
901
|
+
sys.exit(0)
|
|
902
|
+
|
|
903
|
+
# 必须提供配置文件
|
|
904
|
+
if not args.config:
|
|
905
|
+
parser.print_help()
|
|
906
|
+
sys.exit(1)
|
|
907
|
+
|
|
908
|
+
if not os.path.exists(args.config):
|
|
909
|
+
print(f"❌ 配置文件不存在: {args.config}")
|
|
910
|
+
sys.exit(1)
|
|
911
|
+
|
|
912
|
+
generator = Generator(args.config, args.output, batch_timestamp=args.batch_timestamp, dry_run=args.dry_run)
|
|
913
|
+
|
|
914
|
+
if args.validate:
|
|
915
|
+
success = generator.validate()
|
|
916
|
+
sys.exit(0 if success else 1)
|
|
917
|
+
|
|
918
|
+
if args.list:
|
|
919
|
+
generator.list_modules()
|
|
920
|
+
sys.exit(0)
|
|
921
|
+
|
|
922
|
+
if not generator.validate():
|
|
923
|
+
sys.exit(1)
|
|
924
|
+
|
|
925
|
+
generator.generate(args.module, args.mode)
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
if __name__ == '__main__':
|
|
930
|
+
main()
|