import pathlib, re, textwrap
RE_ITEM = re.compile(
r'(?m)^(\s*)(pub\s+(?:async\s+)?(?:const\s+)?(?:fn|struct|enum|trait|type|union|mod)\s+)',
)
RE_FIELD = re.compile(
r'(?m)^(\s*)(pub\s+[\w_]+\s*:|[\w_]+\s*:\s*[\w<>]+,?|[\w_]+\s*\{\s*[\w_]+\s*:\s*[\w<>]+)',
)
RE_TRAIT_METHOD = re.compile(
r'(?m)^(\s*)((?:async\s+)?fn\s+[\w_]+)',
)
RE_IMPL_FN = re.compile(
r'(?m)^(\s*)((?:#\[[^\]]*\]\s*)*pub\s+(?:async\s+)?(?:const\s+)?fn\s+)',
)
def has_doc_comment(lines, line_idx):
if line_idx == 0:
return False
i = line_idx - 1
while i >= 0 and lines[i].strip() == '':
i -= 1
if i < 0:
return False
prev_line = lines[i].strip()
return prev_line.startswith('///') or prev_line.startswith('//!')
def ensure_doc_toplevel(match: re.Match[str]) -> str:
indent, decl = match.groups()
return f'{indent}/// TODO: document this\n{indent}{decl}'
def ensure_doc_field(match: re.Match[str]) -> str:
indent, decl = match.groups()
return f'{indent}/// TODO: document this\n{indent}{decl}'
def ensure_doc_impl_fn(match: re.Match[str]) -> str:
indent, decl = match.groups()
return f'{indent}/// TODO: document this\n{indent}{decl}'
def ensure_doc_trait_method(match: re.Match[str]) -> str:
indent, decl = match.groups()
return f'{indent}/// TODO: document this\n{indent}{decl}'
def process_variants_in_enum(content: str) -> str:
lines = content.splitlines()
result_lines = []
in_enum = False
enum_indent = 0
for i, line in enumerate(lines):
stripped = line.strip()
if re.match(r'pub\s+enum\s+', stripped) or re.match(r'enum\s+', stripped):
in_enum = True
enum_indent = len(line) - len(line.lstrip())
result_lines.append(line)
continue
if in_enum and stripped == '}' and len(line) - len(line.lstrip()) == enum_indent:
in_enum = False
result_lines.append(line)
continue
if in_enum and stripped and not stripped.startswith('//') and not stripped.startswith('#['):
variant_match = re.match(r'^(\s*)([A-Z][A-Za-z0-9_]*)', line)
if variant_match and not has_doc_comment(lines, i):
indent = variant_match.group(1)
result_lines.append(f'{indent}/// TODO: document this')
result_lines.append(line)
continue
result_lines.append(line)
return '\n'.join(result_lines)
def process_trait_methods(content: str) -> str:
lines = content.splitlines()
result_lines = []
in_trait = False
trait_indent = 0
for i, line in enumerate(lines):
stripped = line.strip()
if re.match(r'pub\s+trait\s+', stripped) or re.match(r'trait\s+', stripped):
in_trait = True
trait_indent = len(line) - len(line.lstrip())
result_lines.append(line)
continue
if in_trait and stripped == '}' and len(line) - len(line.lstrip()) == trait_indent:
in_trait = False
result_lines.append(line)
continue
if in_trait and (re.match(r'^\s*async\s+fn\s+', line) or re.match(r'^\s*fn\s+', line)):
if not has_doc_comment(lines, i):
indent = re.match(r'^(\s*)', line).group(1)
result_lines.append(f'{indent}/// TODO: document this')
result_lines.append(line)
continue
result_lines.append(line)
return '\n'.join(result_lines)
def process_struct_fields(content: str) -> str:
lines = content.splitlines()
result_lines = []
for i, line in enumerate(lines):
if re.match(r'^\s+[\w_]+\s*:\s*[\w<>(),\s]+[,}]?\s*$', line) or \
re.match(r'^\s+pub\s+[\w_]+\s*:\s*[\w<>(),\s]+[,}]?\s*$', line):
if not has_doc_comment(lines, i):
indent = re.match(r'^(\s*)', line).group(1)
result_lines.append(f'{indent}/// TODO: document this')
result_lines.append(line)
continue
result_lines.append(line)
return '\n'.join(result_lines)
for path in pathlib.Path("src").rglob("*.rs"):
txt = path.read_text()
if "target/" in str(path):
continue
original_txt = txt
txt = RE_ITEM.sub(ensure_doc_toplevel, txt)
txt = RE_FIELD.sub(ensure_doc_field, txt)
txt = RE_IMPL_FN.sub(ensure_doc_impl_fn, txt)
txt = process_variants_in_enum(txt)
txt = process_trait_methods(txt)
txt = process_struct_fields(txt)
if txt != original_txt:
path.write_text(txt)
print(f"Added stub docs to {path}")