#!/usr/bin/env python3 """Improved Doxygen→Markdown extractor. Handles multi-line signatures.""" import re, os, glob def read_until_braces(text, start): """Read from start position until we find a complete function/struct/enum declaration. Returns (full_signature, end_position)""" pos = start result = [] paren_depth = 0 angle_depth = 0 found_open = False found_template = False while pos < len(text): ch = text[pos] result.append(ch) if ch == '(': paren_depth += 1 found_open = True elif ch == ')': paren_depth -= 1 elif ch == '<' and found_open: angle_depth += 1 elif ch == '>' and found_open: angle_depth -= 1 elif ch == '{' and found_open and paren_depth == 0 and angle_depth == 0: # Function body started break elif ch == ';' and found_open and paren_depth == 0 and angle_depth == 0: # Declaration ended break elif ch == '\n' and not found_open: # Too many newlines without finding a function if len(result) > 200: break pos += 1 return ''.join(result).strip(), pos + 1 def extract_blocks(text): """Extract /** */ blocks and the function/struct/enum that follows.""" results = [] for m in re.finditer(r'/\*\*\s*\n(.*?)\*/', text, re.DOTALL): doc_raw = m.group(1) after_start = m.end() # Skip whitespace, includes, comments while after_start < len(text): ch = text[after_start] if ch in ' \t\n\r': after_start += 1 continue line_start = after_start line_end = text.find('\n', line_start) if line_end < 0: line_end = len(text) line = text[line_start:line_end].strip() if line.startswith('#') or line.startswith('//') or line.startswith('using ') or line == '': after_start = line_end + 1 continue break if after_start >= len(text): continue # Read full signature (multi-line) sig, end_pos = read_until_braces(text, after_start) # Clean doc doc_lines = [] for line in doc_raw.split('\n'): line = line.strip() if line.startswith('*'): line = line[1:].strip() if line.startswith('@') or line: doc_lines.append(line) clean_doc = '\n'.join(doc_lines).strip() results.append((clean_doc, sig)) return results def to_md(doc, sig): """Convert doxygen doc + signature to markdown.""" brief, params, ret, notes, sees, warns = '', [], '', [], [], [] in_code = False for line in doc.split('\n'): line = line.strip() if line.startswith('@brief '): brief = line[7:] elif line.startswith('@param '): params.append(line[7:]) elif line.startswith('@return ') or line.startswith('@retval '): ret = line.split(' ', 1)[1] if ' ' in line else '' elif line.startswith('@note '): notes.append(f'> {line[6:]}') elif line.startswith('@warning '): warns.append(f'> 🔴 {line[9:]}') elif line.startswith('@see '): sees.append(line[5:].strip()) elif line.startswith('@code'): in_code = True elif line.startswith('@endcode'): in_code = False elif line.startswith('@ingroup') or line.startswith('@defgroup'): pass elif not in_code and line and not line.startswith('@') and not line.startswith('\\'): if not brief: brief = line if not brief: return '' md = f"#### {brief}\n\n" # Clean signature clean = sig.strip().rstrip(';') if clean and 'namespace' not in clean and clean != '};': # Truncate very long signatures if len(clean) > 200: # Find last complete parameter paren = clean.rfind(')') if paren > 50: clean = clean[:paren+1] md += f"```cpp\n{clean}\n```\n\n" if params: md += "| 参数 | 说明 |\n|------|------|\n" for p in params: sp = p.split(' ', 1) name = sp[0] desc = sp[1] if len(sp) > 1 else '' md += f"| `{name}` | {desc} |\n" md += "\n" if ret: md += f"**返回**: {ret}\n\n" for n in notes: md += f"{n}\n\n" for w in warns: md += f"{w}\n\n" if sees: md += f"**参见**: {', '.join(f'`{s}`' for s in sees)}\n\n" return md # Main all_headers = sorted(glob.glob('/ws/ViewDesignEngine/include/vde/**/*.h', recursive=True)) modules = {} for h in all_headers: mod = h.split('/include/vde/')[1].split('/')[0] modules.setdefault(mod, []).append(h) out = ['# ViewDesignEngine API 参考手册\n\n> 版本 3.2.0 | 76 个头文件 | 自动生成\n\n'] names = { 'foundation':'基础模块','core':'核心几何','curves':'曲线曲面','mesh':'网格处理', 'spatial':'空间索引','boolean':'布尔运算','collision':'碰撞检测','brep':'B-Rep建模', 'sdf':'SDF隐式建模','sketch':'草图约束','capi':'C API' } for mod in ['foundation','core','curves','mesh','spatial','boolean','collision','brep','sdf','sketch','capi']: if mod not in modules: continue out.append(f'## {names[mod]} (`vde::{mod}`)\n\n') for h in sorted(modules[mod]): with open(h) as f: content = f.read() blocks = extract_blocks(content) if not blocks: continue fname = os.path.basename(h) out.append(f'### {fname}\n\n') for doc, sig in blocks: if len(doc) < 15 and not sig.strip(): continue md = to_md(doc, sig) if md.strip(): out.append(md) out.append('\n') result = '\n'.join(out) # Remove empty ### headers result = re.sub(r'### \n\n+', '', result) with open('/ws/ViewDesignEngine/docs/API-REFERENCE.md', 'w') as f: f.write(result) print(f'Written: {len(result)} chars, {result.count(chr(10))} lines')