@voodocs/cli 0.1.0
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/LICENSE +37 -0
- package/README.md +153 -0
- package/USAGE.md +314 -0
- package/cli.py +1340 -0
- package/examples/.cursorrules +437 -0
- package/examples/instructions/.claude/instructions.md +372 -0
- package/examples/instructions/.cursorrules +437 -0
- package/examples/instructions/.windsurfrules +437 -0
- package/examples/instructions/VOODOCS_INSTRUCTIONS.md +437 -0
- package/examples/math_example.py +41 -0
- package/examples/phase2_test.py +24 -0
- package/examples/test_compound_conditions.py +40 -0
- package/examples/test_math_example.py +186 -0
- package/lib/darkarts/README.md +115 -0
- package/lib/darkarts/__init__.py +16 -0
- package/lib/darkarts/annotations/__init__.py +34 -0
- package/lib/darkarts/annotations/parser.py +618 -0
- package/lib/darkarts/annotations/types.py +181 -0
- package/lib/darkarts/cli.py +128 -0
- package/lib/darkarts/core/__init__.py +32 -0
- package/lib/darkarts/core/interface.py +256 -0
- package/lib/darkarts/core/loader.py +231 -0
- package/lib/darkarts/core/plugin.py +215 -0
- package/lib/darkarts/core/registry.py +146 -0
- package/lib/darkarts/exceptions.py +51 -0
- package/lib/darkarts/parsers/typescript/dist/cli.d.ts +9 -0
- package/lib/darkarts/parsers/typescript/dist/cli.d.ts.map +1 -0
- package/lib/darkarts/parsers/typescript/dist/cli.js +69 -0
- package/lib/darkarts/parsers/typescript/dist/cli.js.map +1 -0
- package/lib/darkarts/parsers/typescript/dist/parser.d.ts +111 -0
- package/lib/darkarts/parsers/typescript/dist/parser.d.ts.map +1 -0
- package/lib/darkarts/parsers/typescript/dist/parser.js +365 -0
- package/lib/darkarts/parsers/typescript/dist/parser.js.map +1 -0
- package/lib/darkarts/parsers/typescript/package-lock.json +51 -0
- package/lib/darkarts/parsers/typescript/package.json +19 -0
- package/lib/darkarts/parsers/typescript/src/cli.ts +41 -0
- package/lib/darkarts/parsers/typescript/src/parser.ts +408 -0
- package/lib/darkarts/parsers/typescript/tsconfig.json +19 -0
- package/lib/darkarts/plugins/voodocs/__init__.py +379 -0
- package/lib/darkarts/plugins/voodocs/ai_native_plugin.py +151 -0
- package/lib/darkarts/plugins/voodocs/annotation_validator.py +280 -0
- package/lib/darkarts/plugins/voodocs/api_spec_generator.py +486 -0
- package/lib/darkarts/plugins/voodocs/documentation_generator.py +610 -0
- package/lib/darkarts/plugins/voodocs/html_exporter.py +260 -0
- package/lib/darkarts/plugins/voodocs/instruction_generator.py +706 -0
- package/lib/darkarts/plugins/voodocs/pdf_exporter.py +66 -0
- package/lib/darkarts/plugins/voodocs/test_generator.py +636 -0
- package/package.json +70 -0
- package/requirements.txt +13 -0
- package/templates/ci/github-actions.yml +73 -0
- package/templates/ci/gitlab-ci.yml +35 -0
- package/templates/ci/pre-commit-hook.sh +26 -0
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
"""
|
|
2
|
+
VooDocs Documentation Generator
|
|
3
|
+
|
|
4
|
+
Translates @voodocs annotations (using the DarkArts language) into human-readable documentation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List, Optional, Dict
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
from darkarts.annotations.types import (
|
|
12
|
+
ParsedAnnotations,
|
|
13
|
+
ModuleAnnotation,
|
|
14
|
+
ClassAnnotation,
|
|
15
|
+
FunctionAnnotation,
|
|
16
|
+
ComplexityAnnotation,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DocumentationGenerator:
|
|
21
|
+
"""Generates human-readable documentation from @voodocs annotations."""
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
self.output_dir = Path("docs")
|
|
25
|
+
self._init_translation_tables()
|
|
26
|
+
|
|
27
|
+
def _init_translation_tables(self):
|
|
28
|
+
"""Initialize mathematical symbol translation tables."""
|
|
29
|
+
# Mathematical symbols to natural language
|
|
30
|
+
self.math_symbols = {
|
|
31
|
+
'∀': 'for all',
|
|
32
|
+
'∃': 'there exists',
|
|
33
|
+
'∈': 'in',
|
|
34
|
+
'∉': 'not in',
|
|
35
|
+
'⊆': 'subset of',
|
|
36
|
+
'⊂': 'proper subset of',
|
|
37
|
+
'∪': 'union',
|
|
38
|
+
'∩': 'intersection',
|
|
39
|
+
'∅': 'empty set',
|
|
40
|
+
'≥': 'greater than or equal to',
|
|
41
|
+
'≤': 'less than or equal to',
|
|
42
|
+
'≠': 'not equal to',
|
|
43
|
+
'≈': 'approximately equal to',
|
|
44
|
+
'⟹': 'implies',
|
|
45
|
+
'⟺': 'if and only if',
|
|
46
|
+
'⇒': 'implies',
|
|
47
|
+
'⇔': 'if and only if',
|
|
48
|
+
'→': 'leads to',
|
|
49
|
+
'∧': 'and',
|
|
50
|
+
'∨': 'or',
|
|
51
|
+
'¬': 'not',
|
|
52
|
+
'ℝ': 'real numbers',
|
|
53
|
+
'ℝ⁺': 'positive real numbers',
|
|
54
|
+
'ℝ⁻': 'negative real numbers',
|
|
55
|
+
'ℤ': 'integers',
|
|
56
|
+
'ℤ⁺': 'positive integers',
|
|
57
|
+
'ℕ': 'natural numbers',
|
|
58
|
+
'ℚ': 'rational numbers',
|
|
59
|
+
'ℂ': 'complex numbers',
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Logical operators
|
|
63
|
+
self.logical_ops = {
|
|
64
|
+
'∧': 'and',
|
|
65
|
+
'∨': 'or',
|
|
66
|
+
'¬': 'not',
|
|
67
|
+
'⟹': 'implies that',
|
|
68
|
+
'⟺': 'if and only if',
|
|
69
|
+
'⇒': 'implies that',
|
|
70
|
+
'⇔': 'if and only if',
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
def generate(self, parsed: ParsedAnnotations, output_file: Optional[str] = None) -> str:
|
|
74
|
+
"""Generate Markdown documentation from parsed annotations."""
|
|
75
|
+
if not parsed.has_annotations():
|
|
76
|
+
return self._generate_basic_docs(parsed)
|
|
77
|
+
|
|
78
|
+
sections = []
|
|
79
|
+
|
|
80
|
+
# Title
|
|
81
|
+
module_name = parsed.module.name.replace("_", " ").title()
|
|
82
|
+
sections.append(f"# {module_name}\n")
|
|
83
|
+
|
|
84
|
+
# Badges
|
|
85
|
+
badges = self._generate_badges(parsed)
|
|
86
|
+
if badges:
|
|
87
|
+
sections.append(badges)
|
|
88
|
+
|
|
89
|
+
# Table of Contents
|
|
90
|
+
toc = self._generate_table_of_contents(parsed)
|
|
91
|
+
if toc:
|
|
92
|
+
sections.append(toc)
|
|
93
|
+
|
|
94
|
+
# Module overview
|
|
95
|
+
if parsed.module.module_purpose or parsed.module.dependencies or parsed.module.assumptions:
|
|
96
|
+
sections.append(self._generate_module_section(parsed.module))
|
|
97
|
+
|
|
98
|
+
# Classes
|
|
99
|
+
if parsed.module.classes:
|
|
100
|
+
sections.append(self._generate_classes_section(parsed.module.classes))
|
|
101
|
+
|
|
102
|
+
# Functions
|
|
103
|
+
functions = parsed.get_all_functions()
|
|
104
|
+
if functions:
|
|
105
|
+
sections.append(self._generate_functions_section(functions))
|
|
106
|
+
|
|
107
|
+
# Combine all sections
|
|
108
|
+
documentation = "\n".join(sections)
|
|
109
|
+
|
|
110
|
+
# Write to file if specified
|
|
111
|
+
if output_file:
|
|
112
|
+
output_path = Path(output_file)
|
|
113
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
114
|
+
output_path.write_text(documentation, encoding='utf-8')
|
|
115
|
+
|
|
116
|
+
return documentation
|
|
117
|
+
|
|
118
|
+
def _generate_basic_docs(self, parsed: ParsedAnnotations) -> str:
|
|
119
|
+
"""Generate basic documentation for code without @voodocs annotations."""
|
|
120
|
+
sections = []
|
|
121
|
+
|
|
122
|
+
module_name = parsed.module.name.replace("_", " ").title()
|
|
123
|
+
sections.append(f"# {module_name}\n")
|
|
124
|
+
sections.append("*No @voodocs annotations found in this module.*\n")
|
|
125
|
+
sections.append("Consider adding @voodocs annotations to enable automatic documentation generation.\n")
|
|
126
|
+
|
|
127
|
+
return "\n".join(sections)
|
|
128
|
+
|
|
129
|
+
def _generate_module_section(self, module: ModuleAnnotation) -> str:
|
|
130
|
+
"""Generate module overview section."""
|
|
131
|
+
lines = ["## Overview\n"]
|
|
132
|
+
|
|
133
|
+
if module.module_purpose:
|
|
134
|
+
lines.append(f"{module.module_purpose}\n")
|
|
135
|
+
|
|
136
|
+
if module.dependencies:
|
|
137
|
+
lines.append("### Dependencies\n")
|
|
138
|
+
for dep in module.dependencies:
|
|
139
|
+
lines.append(f"- {dep}")
|
|
140
|
+
lines.append("")
|
|
141
|
+
|
|
142
|
+
if module.assumptions:
|
|
143
|
+
lines.append("### Assumptions\n")
|
|
144
|
+
lines.append("This module makes the following assumptions:\n")
|
|
145
|
+
for assumption in module.assumptions:
|
|
146
|
+
lines.append(f"- {assumption}")
|
|
147
|
+
lines.append("")
|
|
148
|
+
|
|
149
|
+
if hasattr(module, 'security_model') and module.security_model:
|
|
150
|
+
lines.append("### Security Model\n")
|
|
151
|
+
lines.append(f"{module.security_model}\n")
|
|
152
|
+
|
|
153
|
+
return "\n".join(lines)
|
|
154
|
+
|
|
155
|
+
def _generate_classes_section(self, classes: List[ClassAnnotation]) -> str:
|
|
156
|
+
"""Generate classes section."""
|
|
157
|
+
lines = ["## Classes\n"]
|
|
158
|
+
|
|
159
|
+
for cls in classes:
|
|
160
|
+
lines.append(f"### `{cls.name}`\n")
|
|
161
|
+
|
|
162
|
+
if cls.class_invariants:
|
|
163
|
+
lines.append("**Class Invariants**\n")
|
|
164
|
+
lines.append("The following properties must always hold true for instances of this class:\n")
|
|
165
|
+
for inv in cls.class_invariants:
|
|
166
|
+
readable_inv = self._translate_to_natural_language(inv)
|
|
167
|
+
lines.append(f"- {readable_inv}")
|
|
168
|
+
lines.append("")
|
|
169
|
+
|
|
170
|
+
if cls.state_transitions:
|
|
171
|
+
lines.append("**State Transitions**\n")
|
|
172
|
+
lines.append("Valid state changes for this class:\n")
|
|
173
|
+
for st in cls.state_transitions:
|
|
174
|
+
if hasattr(st, 'from_state'):
|
|
175
|
+
lines.append(f"- **{st.from_state}** → **{st.to_state}**: {st.condition}")
|
|
176
|
+
else:
|
|
177
|
+
# Parse state transition string
|
|
178
|
+
st_text = self._format_state_transition(str(st))
|
|
179
|
+
lines.append(f"- {st_text}")
|
|
180
|
+
lines.append("")
|
|
181
|
+
|
|
182
|
+
if hasattr(cls, 'thread_safety') and cls.thread_safety:
|
|
183
|
+
lines.append(f"**Thread Safety**: {cls.thread_safety}\n")
|
|
184
|
+
|
|
185
|
+
# Methods
|
|
186
|
+
if cls.methods:
|
|
187
|
+
lines.append("#### Methods\n")
|
|
188
|
+
for method in cls.methods:
|
|
189
|
+
lines.append(self._generate_function_doc(method, is_method=True))
|
|
190
|
+
|
|
191
|
+
return "\n".join(lines)
|
|
192
|
+
|
|
193
|
+
def _generate_functions_section(self, functions: List[FunctionAnnotation]) -> str:
|
|
194
|
+
"""Generate functions section."""
|
|
195
|
+
lines = ["## Functions\n"]
|
|
196
|
+
|
|
197
|
+
for func in functions:
|
|
198
|
+
lines.append(self._generate_function_doc(func, is_method=False))
|
|
199
|
+
|
|
200
|
+
return "\n".join(lines)
|
|
201
|
+
|
|
202
|
+
def _generate_function_doc(self, func: FunctionAnnotation, is_method: bool = False) -> str:
|
|
203
|
+
"""Generate documentation for a single function."""
|
|
204
|
+
lines = []
|
|
205
|
+
|
|
206
|
+
# Function signature
|
|
207
|
+
prefix = "####" if is_method else "###"
|
|
208
|
+
lines.append(f"{prefix} `{func.name}()`\n")
|
|
209
|
+
|
|
210
|
+
# Purpose/solve
|
|
211
|
+
if func.solve:
|
|
212
|
+
lines.append(f"{func.solve}\n")
|
|
213
|
+
|
|
214
|
+
# Preconditions
|
|
215
|
+
if func.preconditions:
|
|
216
|
+
lines.append("**Requirements**\n")
|
|
217
|
+
lines.append("The following conditions must be met before calling this function:\n")
|
|
218
|
+
for pre in func.preconditions:
|
|
219
|
+
readable_pre = self._translate_to_natural_language(pre)
|
|
220
|
+
lines.append(f"- {readable_pre}")
|
|
221
|
+
lines.append("")
|
|
222
|
+
|
|
223
|
+
# Postconditions
|
|
224
|
+
if func.postconditions:
|
|
225
|
+
lines.append("**Guarantees**\n")
|
|
226
|
+
lines.append("After successful execution, the following conditions will be true:\n")
|
|
227
|
+
for post in func.postconditions:
|
|
228
|
+
readable_post = self._translate_to_natural_language(post)
|
|
229
|
+
lines.append(f"- {readable_post}")
|
|
230
|
+
lines.append("")
|
|
231
|
+
|
|
232
|
+
# Invariants
|
|
233
|
+
if func.invariants:
|
|
234
|
+
lines.append("**Invariants**\n")
|
|
235
|
+
lines.append("The following properties remain unchanged throughout execution:\n")
|
|
236
|
+
for inv in func.invariants:
|
|
237
|
+
readable_inv = self._translate_to_natural_language(inv)
|
|
238
|
+
lines.append(f"- {readable_inv}")
|
|
239
|
+
lines.append("")
|
|
240
|
+
|
|
241
|
+
# Side effects
|
|
242
|
+
if hasattr(func, 'side_effects') and func.side_effects:
|
|
243
|
+
lines.append("**Side Effects**\n")
|
|
244
|
+
for effect in func.side_effects:
|
|
245
|
+
lines.append(f"- {effect}")
|
|
246
|
+
lines.append("")
|
|
247
|
+
|
|
248
|
+
# Security implications
|
|
249
|
+
if hasattr(func, 'security_implications') and func.security_implications:
|
|
250
|
+
lines.append("**⚠️ Security Considerations**\n")
|
|
251
|
+
for implication in func.security_implications:
|
|
252
|
+
lines.append(f"- {implication}")
|
|
253
|
+
lines.append("")
|
|
254
|
+
|
|
255
|
+
# Error handling
|
|
256
|
+
if hasattr(func, 'error_handling') and func.error_handling:
|
|
257
|
+
lines.append(f"**Error Handling**: {func.error_handling}\n")
|
|
258
|
+
|
|
259
|
+
# Optimization
|
|
260
|
+
if func.optimize:
|
|
261
|
+
readable_opt = self._translate_to_natural_language(func.optimize)
|
|
262
|
+
lines.append(f"**Optimization Goal**: {readable_opt}\n")
|
|
263
|
+
|
|
264
|
+
# Complexity
|
|
265
|
+
if func.complexity:
|
|
266
|
+
complexity_text = self._format_complexity(func.complexity)
|
|
267
|
+
lines.append(f"**Performance**: {complexity_text}\n")
|
|
268
|
+
|
|
269
|
+
# Error cases
|
|
270
|
+
if func.error_cases:
|
|
271
|
+
lines.append("**Error Cases**\n")
|
|
272
|
+
for error in func.error_cases:
|
|
273
|
+
if hasattr(error, 'condition'):
|
|
274
|
+
lines.append(f"- **{error.error_type}**: {error.condition}")
|
|
275
|
+
else:
|
|
276
|
+
lines.append(f"- {error}")
|
|
277
|
+
lines.append("")
|
|
278
|
+
|
|
279
|
+
# Usage examples (generated from preconditions)
|
|
280
|
+
usage_example = self._generate_usage_example(func)
|
|
281
|
+
if usage_example:
|
|
282
|
+
lines.append("**Usage Example**\n")
|
|
283
|
+
lines.append("```python")
|
|
284
|
+
lines.append(usage_example)
|
|
285
|
+
lines.append("```\n")
|
|
286
|
+
|
|
287
|
+
return "\n".join(lines)
|
|
288
|
+
|
|
289
|
+
def _translate_to_natural_language(self, expression: str) -> str:
|
|
290
|
+
"""
|
|
291
|
+
Translate mathematical notation to natural language.
|
|
292
|
+
|
|
293
|
+
This method converts DarkArts mathematical expressions into readable English.
|
|
294
|
+
"""
|
|
295
|
+
text = expression.strip()
|
|
296
|
+
|
|
297
|
+
# Handle quantifiers (∀, ∃) specially
|
|
298
|
+
text = self._translate_quantifiers(text)
|
|
299
|
+
|
|
300
|
+
# Handle logical operators
|
|
301
|
+
text = self._translate_logical_operators(text)
|
|
302
|
+
|
|
303
|
+
# Handle mathematical symbols
|
|
304
|
+
for symbol, replacement in self.math_symbols.items():
|
|
305
|
+
if symbol in text:
|
|
306
|
+
text = text.replace(symbol, replacement)
|
|
307
|
+
|
|
308
|
+
# Clean up extra spaces
|
|
309
|
+
text = re.sub(r'\s+', ' ', text)
|
|
310
|
+
|
|
311
|
+
return text
|
|
312
|
+
|
|
313
|
+
def _translate_quantifiers(self, text: str) -> str:
|
|
314
|
+
"""Translate quantifiers (∀, ∃) into natural language."""
|
|
315
|
+
# Pattern: ∀ x ∈ S: P(x)
|
|
316
|
+
# Translation: For all x in S, P(x) holds
|
|
317
|
+
|
|
318
|
+
# Universal quantifier
|
|
319
|
+
text = re.sub(
|
|
320
|
+
r'∀\s*(\w+)\s*∈\s*([^:]+):\s*(.+)',
|
|
321
|
+
r'For all \1 in \2, \3',
|
|
322
|
+
text
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
# Existential quantifier
|
|
326
|
+
text = re.sub(
|
|
327
|
+
r'∃\s*(\w+)\s*∈\s*([^:]+):\s*(.+)',
|
|
328
|
+
r'There exists \1 in \2 such that \3',
|
|
329
|
+
text
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
return text
|
|
333
|
+
|
|
334
|
+
def _translate_logical_operators(self, text: str) -> str:
|
|
335
|
+
"""Translate logical operators into natural language."""
|
|
336
|
+
# Handle compound expressions
|
|
337
|
+
# ⇔ (if and only if)
|
|
338
|
+
text = re.sub(r'\s*⇔\s*', ' if and only if ', text)
|
|
339
|
+
text = re.sub(r'\s*⟺\s*', ' if and only if ', text)
|
|
340
|
+
|
|
341
|
+
# ⇒ (implies)
|
|
342
|
+
text = re.sub(r'\s*⇒\s*', ' implies ', text)
|
|
343
|
+
text = re.sub(r'\s*⟹\s*', ' implies ', text)
|
|
344
|
+
|
|
345
|
+
# ∧ (and)
|
|
346
|
+
text = re.sub(r'\s*∧\s*', ' and ', text)
|
|
347
|
+
|
|
348
|
+
# ∨ (or)
|
|
349
|
+
text = re.sub(r'\s*∨\s*', ' or ', text)
|
|
350
|
+
|
|
351
|
+
# ¬ (not)
|
|
352
|
+
text = re.sub(r'¬\s*', 'not ', text)
|
|
353
|
+
|
|
354
|
+
return text
|
|
355
|
+
|
|
356
|
+
def _format_state_transition(self, transition: str) -> str:
|
|
357
|
+
"""Format a state transition string for better readability."""
|
|
358
|
+
# Pattern: STATE1 → STATE2: condition
|
|
359
|
+
match = re.match(r'(.+?)\s*→\s*(.+?):\s*(.+)', transition)
|
|
360
|
+
if match:
|
|
361
|
+
from_state, to_state, condition = match.groups()
|
|
362
|
+
return f"**{from_state.strip()}** → **{to_state.strip()}**: {condition.strip()}"
|
|
363
|
+
return transition
|
|
364
|
+
|
|
365
|
+
def _format_complexity(self, complexity: ComplexityAnnotation) -> str:
|
|
366
|
+
"""Format complexity annotation into readable text."""
|
|
367
|
+
parts = []
|
|
368
|
+
|
|
369
|
+
if complexity.time:
|
|
370
|
+
parts.append(f"Time complexity is {complexity.time}")
|
|
371
|
+
|
|
372
|
+
if complexity.space:
|
|
373
|
+
parts.append(f"space complexity is {complexity.space}")
|
|
374
|
+
|
|
375
|
+
if parts:
|
|
376
|
+
return ", ".join(parts) + "."
|
|
377
|
+
|
|
378
|
+
return "Complexity not specified."
|
|
379
|
+
|
|
380
|
+
def generate_api_reference(self, parsed: ParsedAnnotations, output_file: Optional[str] = None) -> str:
|
|
381
|
+
"""Generate API reference documentation."""
|
|
382
|
+
sections = []
|
|
383
|
+
|
|
384
|
+
# Title
|
|
385
|
+
module_name = parsed.module.name.replace("_", " ").title()
|
|
386
|
+
sections.append(f"# {module_name} API Reference\n")
|
|
387
|
+
|
|
388
|
+
# Table of contents
|
|
389
|
+
sections.append("## Table of Contents\n")
|
|
390
|
+
|
|
391
|
+
if parsed.module.classes:
|
|
392
|
+
sections.append("### Classes\n")
|
|
393
|
+
for cls in parsed.module.classes:
|
|
394
|
+
sections.append(f"- [{cls.name}](#{cls.name.lower()})")
|
|
395
|
+
sections.append("")
|
|
396
|
+
|
|
397
|
+
functions = parsed.get_all_functions()
|
|
398
|
+
if functions:
|
|
399
|
+
sections.append("### Functions\n")
|
|
400
|
+
for func in functions:
|
|
401
|
+
sections.append(f"- [{func.name}](#{func.name.lower()})")
|
|
402
|
+
sections.append("")
|
|
403
|
+
|
|
404
|
+
# Detailed API docs
|
|
405
|
+
sections.append("---\n")
|
|
406
|
+
|
|
407
|
+
# Classes
|
|
408
|
+
if parsed.module.classes:
|
|
409
|
+
for cls in parsed.module.classes:
|
|
410
|
+
sections.append(f"## {cls.name}\n")
|
|
411
|
+
sections.append(f"*Defined at line {cls.line_number}*\n")
|
|
412
|
+
|
|
413
|
+
if cls.class_invariants:
|
|
414
|
+
sections.append("### Class Invariants\n")
|
|
415
|
+
sections.append("```")
|
|
416
|
+
for inv in cls.class_invariants:
|
|
417
|
+
sections.append(inv)
|
|
418
|
+
sections.append("```\n")
|
|
419
|
+
|
|
420
|
+
if cls.methods:
|
|
421
|
+
sections.append("### Methods\n")
|
|
422
|
+
for method in cls.methods:
|
|
423
|
+
sections.append(f"#### `{method.name}()`\n")
|
|
424
|
+
sections.append(f"*Defined at line {method.line_number}*\n")
|
|
425
|
+
|
|
426
|
+
if method.solve:
|
|
427
|
+
sections.append(f"**Purpose:** {method.solve}\n")
|
|
428
|
+
|
|
429
|
+
if method.preconditions:
|
|
430
|
+
sections.append("**Preconditions:**")
|
|
431
|
+
sections.append("```")
|
|
432
|
+
for pre in method.preconditions:
|
|
433
|
+
sections.append(pre)
|
|
434
|
+
sections.append("```\n")
|
|
435
|
+
|
|
436
|
+
if method.postconditions:
|
|
437
|
+
sections.append("**Postconditions:**")
|
|
438
|
+
sections.append("```")
|
|
439
|
+
for post in method.postconditions:
|
|
440
|
+
sections.append(post)
|
|
441
|
+
sections.append("```\n")
|
|
442
|
+
|
|
443
|
+
# Functions
|
|
444
|
+
if functions:
|
|
445
|
+
sections.append("## Functions\n")
|
|
446
|
+
for func in functions:
|
|
447
|
+
sections.append(f"### `{func.name}()`\n")
|
|
448
|
+
sections.append(f"*Defined at line {func.line_number}*\n")
|
|
449
|
+
|
|
450
|
+
if func.solve:
|
|
451
|
+
sections.append(f"**Purpose:** {func.solve}\n")
|
|
452
|
+
|
|
453
|
+
if func.preconditions:
|
|
454
|
+
sections.append("**Preconditions:**")
|
|
455
|
+
sections.append("```")
|
|
456
|
+
for pre in func.preconditions:
|
|
457
|
+
sections.append(pre)
|
|
458
|
+
sections.append("```\n")
|
|
459
|
+
|
|
460
|
+
if func.postconditions:
|
|
461
|
+
sections.append("**Postconditions:**")
|
|
462
|
+
sections.append("```")
|
|
463
|
+
for post in func.postconditions:
|
|
464
|
+
sections.append(post)
|
|
465
|
+
sections.append("```\n")
|
|
466
|
+
|
|
467
|
+
if func.complexity:
|
|
468
|
+
complexity_text = self._format_complexity(func.complexity)
|
|
469
|
+
sections.append(f"**Performance:** {complexity_text}\n")
|
|
470
|
+
|
|
471
|
+
documentation = "\n".join(sections)
|
|
472
|
+
|
|
473
|
+
if output_file:
|
|
474
|
+
output_path = Path(output_file)
|
|
475
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
476
|
+
output_path.write_text(documentation, encoding='utf-8')
|
|
477
|
+
|
|
478
|
+
return documentation
|
|
479
|
+
|
|
480
|
+
def _generate_usage_example(self, func: FunctionAnnotation) -> Optional[str]:
|
|
481
|
+
"""Generate a usage example from function annotations."""
|
|
482
|
+
if not func.preconditions:
|
|
483
|
+
return None
|
|
484
|
+
|
|
485
|
+
# Try to infer example values from preconditions
|
|
486
|
+
example_params = self._infer_example_params(func.preconditions)
|
|
487
|
+
|
|
488
|
+
if not example_params:
|
|
489
|
+
return None
|
|
490
|
+
|
|
491
|
+
# Build example code
|
|
492
|
+
lines = []
|
|
493
|
+
lines.append(f"# Example usage of {func.name}()")
|
|
494
|
+
|
|
495
|
+
# Add variable assignments
|
|
496
|
+
for param, value in example_params.items():
|
|
497
|
+
lines.append(f"{param} = {value}")
|
|
498
|
+
|
|
499
|
+
# Add function call
|
|
500
|
+
param_list = ", ".join(example_params.keys())
|
|
501
|
+
lines.append(f"result = {func.name}({param_list})")
|
|
502
|
+
|
|
503
|
+
# Add postcondition check if available
|
|
504
|
+
if func.postconditions:
|
|
505
|
+
lines.append("")
|
|
506
|
+
lines.append("# Postconditions are guaranteed:")
|
|
507
|
+
for post in func.postconditions[:2]: # Show first 2 postconditions
|
|
508
|
+
readable_post = self._translate_to_natural_language(post)
|
|
509
|
+
lines.append(f"# - {readable_post}")
|
|
510
|
+
|
|
511
|
+
return "\n".join(lines)
|
|
512
|
+
|
|
513
|
+
def _infer_example_params(self, preconditions: List[str]) -> Dict[str, str]:
|
|
514
|
+
"""Infer example parameter values from preconditions."""
|
|
515
|
+
params = {}
|
|
516
|
+
|
|
517
|
+
for pre in preconditions:
|
|
518
|
+
# Pattern: "x > 0" → x = 1
|
|
519
|
+
if match := re.match(r'(\w+)\s*>\s*(\d+)', pre):
|
|
520
|
+
var_name = match.group(1)
|
|
521
|
+
min_val = int(match.group(2))
|
|
522
|
+
params[var_name] = str(min_val + 1)
|
|
523
|
+
|
|
524
|
+
# Pattern: "x >= 0" → x = 0
|
|
525
|
+
elif match := re.match(r'(\w+)\s*>=\s*(\d+)', pre):
|
|
526
|
+
var_name = match.group(1)
|
|
527
|
+
min_val = int(match.group(2))
|
|
528
|
+
params[var_name] = str(min_val)
|
|
529
|
+
|
|
530
|
+
# Pattern: "x < 100" → x = 50
|
|
531
|
+
elif match := re.match(r'(\w+)\s*<\s*(\d+)', pre):
|
|
532
|
+
var_name = match.group(1)
|
|
533
|
+
max_val = int(match.group(2))
|
|
534
|
+
if var_name in params:
|
|
535
|
+
# Combine with existing constraint
|
|
536
|
+
current = int(params[var_name])
|
|
537
|
+
params[var_name] = str(min(current, max_val - 1))
|
|
538
|
+
else:
|
|
539
|
+
params[var_name] = str(max_val // 2)
|
|
540
|
+
|
|
541
|
+
# Pattern: "x in [1, 2, 3]" → x = 1
|
|
542
|
+
elif match := re.match(r'(\w+)\s+in\s+\[(.+?)\]', pre):
|
|
543
|
+
var_name = match.group(1)
|
|
544
|
+
values = match.group(2).split(',')
|
|
545
|
+
params[var_name] = values[0].strip()
|
|
546
|
+
|
|
547
|
+
# Pattern: "x is string" → x = "example"
|
|
548
|
+
elif match := re.search(r'(\w+).*(?:is|must be).*string', pre, re.IGNORECASE):
|
|
549
|
+
var_name = match.group(1)
|
|
550
|
+
params[var_name] = '"example"'
|
|
551
|
+
|
|
552
|
+
# Pattern: "x is list" → x = [1, 2, 3]
|
|
553
|
+
elif match := re.search(r'(\w+).*(?:is|must be).*list', pre, re.IGNORECASE):
|
|
554
|
+
var_name = match.group(1)
|
|
555
|
+
params[var_name] = '[1, 2, 3]'
|
|
556
|
+
|
|
557
|
+
# Pattern: "x is boolean" → x = True
|
|
558
|
+
elif match := re.search(r'(\w+).*(?:is|must be).*(?:bool|boolean)', pre, re.IGNORECASE):
|
|
559
|
+
var_name = match.group(1)
|
|
560
|
+
params[var_name] = 'True'
|
|
561
|
+
|
|
562
|
+
return params
|
|
563
|
+
|
|
564
|
+
def _generate_badges(self, parsed: ParsedAnnotations) -> str:
|
|
565
|
+
"""Generate status badges for the documentation."""
|
|
566
|
+
badges = []
|
|
567
|
+
|
|
568
|
+
# Documentation coverage badge
|
|
569
|
+
total_items = len(parsed.module.classes) + len(parsed.get_all_functions())
|
|
570
|
+
annotated_items = sum(1 for _ in parsed.module.classes if _.invariants or _.state_transitions)
|
|
571
|
+
annotated_items += sum(1 for f in parsed.get_all_functions() if f.preconditions or f.postconditions)
|
|
572
|
+
|
|
573
|
+
if total_items > 0:
|
|
574
|
+
coverage = int((annotated_items / total_items) * 100)
|
|
575
|
+
color = "green" if coverage >= 80 else "yellow" if coverage >= 50 else "red"
|
|
576
|
+
badges.append(f"")
|
|
577
|
+
|
|
578
|
+
# VooDocs badge
|
|
579
|
+
badges.append("")
|
|
580
|
+
|
|
581
|
+
# Language badge
|
|
582
|
+
lang = parsed.language.value if hasattr(parsed.language, 'value') else str(parsed.language)
|
|
583
|
+
badges.append(f"")
|
|
584
|
+
|
|
585
|
+
return " ".join(badges) + "\\n" if badges else ""
|
|
586
|
+
|
|
587
|
+
def _generate_table_of_contents(self, parsed: ParsedAnnotations) -> str:
|
|
588
|
+
"""Generate table of contents for the documentation."""
|
|
589
|
+
lines = []
|
|
590
|
+
lines.append("## Table of Contents\\n")
|
|
591
|
+
|
|
592
|
+
# Module overview
|
|
593
|
+
if parsed.module.module_purpose or parsed.module.dependencies or parsed.module.assumptions:
|
|
594
|
+
lines.append("- [Overview](#overview)")
|
|
595
|
+
|
|
596
|
+
# Classes
|
|
597
|
+
if parsed.module.classes:
|
|
598
|
+
lines.append("- [Classes](#classes)")
|
|
599
|
+
for cls in parsed.module.classes:
|
|
600
|
+
lines.append(f" - [{cls.name}](#{cls.name.lower()})")
|
|
601
|
+
|
|
602
|
+
# Functions
|
|
603
|
+
functions = parsed.get_all_functions()
|
|
604
|
+
if functions:
|
|
605
|
+
lines.append("- [Functions](#functions)")
|
|
606
|
+
for func in functions:
|
|
607
|
+
lines.append(f" - [{func.name}](#{func.name.lower()})")
|
|
608
|
+
|
|
609
|
+
lines.append("")
|
|
610
|
+
return "\\n".join(lines)
|