-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathfix_docs_readmes.py
More file actions
164 lines (146 loc) Β· 6.08 KB
/
fix_docs_readmes.py
File metadata and controls
164 lines (146 loc) Β· 6.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#!/usr/bin/env python3
"""Add Testing, Installation, and code blocks to remaining docs/modules READMEs and SPECs."""
import ast
import logging
import os
import sys
logger = logging.getLogger(__name__)
REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
SRC = os.path.join(REPO, "src", "codomyrmex")
DOCS = os.path.join(REPO, "docs", "modules")
def get_exports(mod_name):
"""Get classes and functions from src."""
init = os.path.join(SRC, mod_name, "__init__.py")
classes, functions = [], []
if not os.path.exists(init):
return classes, functions
try:
tree = ast.parse(open(init).read())
for node in tree.body:
if isinstance(node, ast.ClassDef):
doc = ast.get_docstring(node) or ""
classes.append((node.name, doc.split("\n")[0] if doc else ""))
elif isinstance(node, ast.FunctionDef) and not node.name.startswith("_"):
doc = ast.get_docstring(node) or ""
functions.append((node.name, doc.split("\n")[0] if doc else ""))
except Exception as e:
logger.debug("Could not parse __init__.py for %s: %s", mod_name, e)
if not classes and not functions:
for f in sorted(os.listdir(os.path.join(SRC, mod_name))):
if not f.endswith(".py") or f == "__init__.py": continue
try:
sub = ast.parse(open(os.path.join(SRC, mod_name, f)).read())
for node in sub.body:
if isinstance(node, ast.ClassDef):
doc = ast.get_docstring(node) or ""
classes.append((node.name, doc.split("\n")[0] if doc else ""))
elif isinstance(node, ast.FunctionDef) and not node.name.startswith("_"):
doc = ast.get_docstring(node) or ""
functions.append((node.name, doc.split("\n")[0] if doc else ""))
except Exception as e:
logger.debug("Could not parse %s: %s", f, e)
if len(classes) >= 3: break
return classes, functions
def fix_readme(mod_name):
"""Add missing sections to docs/modules README."""
readme = os.path.join(DOCS, mod_name, "README.md")
if not os.path.exists(readme):
return False
with open(readme) as f:
content = f.read()
original = content
classes, functions = get_exports(mod_name)
# Add Installation
if "install" not in content.lower() and "pip" not in content.lower() and "setup" not in content.lower():
install = "\n## Installation\n\n```bash\npip install codomyrmex\n```\n"
for anchor in ["## API", "## Key", "## Quick", "## Test", "## Related", "## Navigation", "## References"]:
if anchor in content:
content = content.replace(anchor, install + "\n" + anchor)
break
else:
content = content.rstrip() + "\n" + install
# Add Testing
if "test" not in content.lower():
testing = f"\n## Testing\n\n```bash\nuv run python -m pytest src/codomyrmex/tests/ -k {mod_name} -v\n```\n"
for anchor in ["## Related", "## Navigation", "## References"]:
if anchor in content:
content = content.replace(anchor, testing + "\n" + anchor)
break
else:
content = content.rstrip() + "\n" + testing
# Add code block if missing
if "```" not in content:
code = "\n## Quick Start\n\n```python\n"
if classes:
code += f"from codomyrmex.{mod_name} import {classes[0][0]}\n\n"
code += f"{classes[0][0].lower()} = {classes[0][0]}()\n"
elif functions:
code += f"from codomyrmex.{mod_name} import {functions[0][0]}\n\n"
code += f"result = {functions[0][0]}()\n"
else:
code += f"import codomyrmex.{mod_name}\n"
code += "```\n"
for anchor in ["## Test", "## Related", "## Navigation", "## References"]:
if anchor in content:
content = content.replace(anchor, code + "\n" + anchor)
break
else:
content = content.rstrip() + "\n" + code
if content != original:
with open(readme, "w") as f:
f.write(content)
return True
return False
def fix_spec(mod_name):
"""Add code block to docs/modules SPEC if missing."""
spec = os.path.join(DOCS, mod_name, "SPEC.md")
if not os.path.exists(spec):
return False
with open(spec) as f:
content = f.read()
if "```" in content:
return False
classes, functions = get_exports(mod_name)
code = "\n## API Usage\n\n```python\n"
if classes:
code += f"from codomyrmex.{mod_name} import {classes[0][0]}\n"
elif functions:
code += f"from codomyrmex.{mod_name} import {functions[0][0]}\n"
else:
code += f"import codomyrmex.{mod_name}\n"
code += "```\n"
content = content.rstrip() + "\n" + code
with open(spec, "w") as f:
f.write(content)
return True
def main():
# Auto-injected: Load configuration
from pathlib import Path
import yaml
config_path = Path(__file__).resolve().parent.parent.parent / "config" / "documentation" / "config.yaml"
config_data = {}
if config_path.exists():
with open(config_path, "r") as f:
config_data = yaml.safe_load(f) or {}
print(f"Loaded config from config/documentation/config.yaml")
modules = sorted(d for d in os.listdir(DOCS) if os.path.isdir(os.path.join(DOCS, d)))
readme_fixed = spec_fixed = 0
for mod in modules:
if fix_readme(mod):
readme_fixed += 1
sys.stdout.write("R")
else:
sys.stdout.write(".")
sys.stdout.flush()
print(f"\nβ
docs README.md fixed: {readme_fixed}")
for mod in modules:
if fix_spec(mod):
spec_fixed += 1
sys.stdout.write("S")
else:
sys.stdout.write(".")
sys.stdout.flush()
print(f"\nβ
docs SPEC.md fixed: {spec_fixed}")
print(f"π Total: {readme_fixed + spec_fixed}")
if __name__ == "__main__":
main()