@voodocs/cli 0.4.2 → 1.0.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/CHANGELOG.md +312 -0
- package/lib/cli/__init__.py +53 -0
- package/lib/cli/benchmark.py +311 -0
- package/lib/cli/fix.py +244 -0
- package/lib/cli/generate.py +310 -0
- package/lib/cli/test_cli.py +215 -0
- package/lib/cli/validate.py +364 -0
- package/lib/darkarts/__init__.py +11 -5
- package/lib/darkarts/annotations/__init__.py +11 -3
- package/lib/darkarts/annotations/darkarts_parser.py +1 -1
- package/lib/darkarts/annotations/types.py +16 -3
- package/lib/darkarts/cli_darkarts.py +1 -1
- package/lib/darkarts/context/__init__.py +11 -3
- package/lib/darkarts/context/ai_integrations.py +7 -21
- package/lib/darkarts/context/commands.py +1 -1
- package/lib/darkarts/context/diagram.py +8 -22
- package/lib/darkarts/context/models.py +7 -22
- package/lib/darkarts/context/module_utils.py +1 -1
- package/lib/darkarts/context/ui.py +1 -1
- package/lib/darkarts/context/validation.py +1 -1
- package/lib/darkarts/context/yaml_utils.py +8 -23
- package/lib/darkarts/core/__init__.py +12 -2
- package/lib/darkarts/core/interface.py +16 -2
- package/lib/darkarts/core/loader.py +17 -2
- package/lib/darkarts/core/plugin.py +16 -3
- package/lib/darkarts/core/registry.py +17 -2
- package/lib/darkarts/exceptions.py +17 -3
- package/lib/darkarts/plugins/voodocs/__init__.py +12 -2
- package/lib/darkarts/plugins/voodocs/ai_native_plugin.py +16 -5
- package/lib/darkarts/plugins/voodocs/annotation_validator.py +16 -3
- package/lib/darkarts/plugins/voodocs/api_spec_generator.py +16 -3
- package/lib/darkarts/plugins/voodocs/documentation_generator.py +16 -3
- package/lib/darkarts/plugins/voodocs/html_exporter.py +16 -3
- package/lib/darkarts/plugins/voodocs/instruction_generator.py +1 -1
- package/lib/darkarts/plugins/voodocs/pdf_exporter.py +16 -3
- package/lib/darkarts/plugins/voodocs/test_generator.py +16 -3
- package/lib/darkarts/telemetry.py +16 -3
- package/lib/darkarts/validation/README.md +147 -0
- package/lib/darkarts/validation/__init__.py +91 -0
- package/lib/darkarts/validation/autofix.py +297 -0
- package/lib/darkarts/validation/benchmark.py +426 -0
- package/lib/darkarts/validation/benchmark_wrapper.py +22 -0
- package/lib/darkarts/validation/config.py +257 -0
- package/lib/darkarts/validation/performance.py +412 -0
- package/lib/darkarts/validation/performance_wrapper.py +37 -0
- package/lib/darkarts/validation/semantic.py +461 -0
- package/lib/darkarts/validation/semantic_wrapper.py +77 -0
- package/lib/darkarts/validation/test_validation.py +160 -0
- package/lib/darkarts/validation/types.py +97 -0
- package/lib/darkarts/validation/watch.py +239 -0
- package/package.json +19 -6
- package/voodocs_cli.py +28 -0
- package/cli.py +0 -1646
- package/lib/darkarts/cli.py +0 -128
package/lib/cli/fix.py
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""@darkarts
|
|
2
|
+
⊢cli:fix
|
|
3
|
+
∂{click,pathlib,typing,sys}
|
|
4
|
+
⚠{python≥3.7,click≥8.0}
|
|
5
|
+
⊨{∀fix→executed,∀backup→created}
|
|
6
|
+
🔒{write:files,read:files}
|
|
7
|
+
⚡{O(n*m)|n=files,m=file-size}
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
VooDocs CLI - Fix Command
|
|
12
|
+
|
|
13
|
+
Automatically fixes validation issues in @darkarts annotations.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
import click
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import List, Dict, Any
|
|
20
|
+
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path as PathLib
|
|
23
|
+
sys.path.insert(0, str(PathLib(__file__).parent.parent))
|
|
24
|
+
from darkarts.validation.autofix import AutoFixer
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@click.command()
|
|
28
|
+
@click.argument('path', type=click.Path(exists=True))
|
|
29
|
+
@click.option('-r', '--recursive', is_flag=True, help='Recursively fix all files')
|
|
30
|
+
@click.option('--dry-run', is_flag=True, help='Preview changes without applying')
|
|
31
|
+
@click.option('--no-backup', is_flag=True, help='Skip creating backup files')
|
|
32
|
+
@click.option('--deps-only', is_flag=True, help='Only fix dependency issues')
|
|
33
|
+
@click.option('--perf-only', is_flag=True, help='Only fix performance issues')
|
|
34
|
+
@click.option('--exclude', multiple=True, help='Exclude patterns (can be used multiple times)')
|
|
35
|
+
@click.option('--format', type=click.Choice(['text', 'json']), default='text', help='Output format')
|
|
36
|
+
@click.option('--strict', is_flag=True, help='Exit with error code if fixes needed')
|
|
37
|
+
def fix(
|
|
38
|
+
path: str,
|
|
39
|
+
recursive: bool,
|
|
40
|
+
dry_run: bool,
|
|
41
|
+
no_backup: bool,
|
|
42
|
+
deps_only: bool,
|
|
43
|
+
perf_only: bool,
|
|
44
|
+
exclude: tuple,
|
|
45
|
+
format: str,
|
|
46
|
+
strict: bool
|
|
47
|
+
):
|
|
48
|
+
"""
|
|
49
|
+
Automatically fix validation issues in @darkarts annotations.
|
|
50
|
+
|
|
51
|
+
Fixes include:
|
|
52
|
+
- Update ∂{} to match actual imports
|
|
53
|
+
- Update ⚡{} to match detected complexity
|
|
54
|
+
- Add missing annotation sections
|
|
55
|
+
|
|
56
|
+
Examples:
|
|
57
|
+
|
|
58
|
+
# Preview fixes for a file
|
|
59
|
+
voodocs fix myfile.py --dry-run
|
|
60
|
+
|
|
61
|
+
# Apply fixes to a file
|
|
62
|
+
voodocs fix myfile.py
|
|
63
|
+
|
|
64
|
+
# Fix all files in directory (with backup)
|
|
65
|
+
voodocs fix lib/ -r
|
|
66
|
+
|
|
67
|
+
# Fix without backup
|
|
68
|
+
voodocs fix lib/ -r --no-backup
|
|
69
|
+
|
|
70
|
+
# Only fix dependencies
|
|
71
|
+
voodocs fix lib/ -r --deps-only
|
|
72
|
+
|
|
73
|
+
# Only fix performance claims
|
|
74
|
+
voodocs fix lib/ -r --perf-only
|
|
75
|
+
|
|
76
|
+
# JSON output
|
|
77
|
+
voodocs fix lib/ -r --format json
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
if format == 'text':
|
|
81
|
+
click.echo(f"Fixing: {path}")
|
|
82
|
+
if dry_run:
|
|
83
|
+
click.secho("(Dry run - no changes will be made)", fg='yellow')
|
|
84
|
+
click.echo()
|
|
85
|
+
|
|
86
|
+
# Collect files to fix
|
|
87
|
+
path_obj = Path(path)
|
|
88
|
+
files_to_fix: List[Path] = []
|
|
89
|
+
|
|
90
|
+
if path_obj.is_file():
|
|
91
|
+
files_to_fix = [path_obj]
|
|
92
|
+
elif path_obj.is_dir():
|
|
93
|
+
pattern = "**/*.py" if recursive else "*.py"
|
|
94
|
+
files_to_fix = [
|
|
95
|
+
f for f in path_obj.glob(pattern)
|
|
96
|
+
if f.is_file() and not _should_exclude(f, exclude)
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
if not files_to_fix:
|
|
100
|
+
if format == 'text':
|
|
101
|
+
click.secho("No Python files found to fix.", fg='yellow')
|
|
102
|
+
else:
|
|
103
|
+
click.echo('{"files": [], "fixed": 0, "errors": 0}')
|
|
104
|
+
sys.exit(0)
|
|
105
|
+
|
|
106
|
+
# Initialize auto-fix
|
|
107
|
+
autofix = AutoFixer(dry_run=dry_run)
|
|
108
|
+
|
|
109
|
+
# Track results
|
|
110
|
+
results = {
|
|
111
|
+
'files': [],
|
|
112
|
+
'fixed': 0,
|
|
113
|
+
'errors': 0,
|
|
114
|
+
'skipped': 0
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# Process each file
|
|
118
|
+
for file_path in files_to_fix:
|
|
119
|
+
try:
|
|
120
|
+
# Create backup if needed
|
|
121
|
+
if not dry_run and not no_backup:
|
|
122
|
+
try:
|
|
123
|
+
autofix.create_backup(file_path)
|
|
124
|
+
except Exception as e:
|
|
125
|
+
if format == 'text':
|
|
126
|
+
click.secho(f"⚠️ Warning: Could not create backup for {file_path}: {e}", fg='yellow')
|
|
127
|
+
|
|
128
|
+
# Run auto-fix
|
|
129
|
+
fix_result = autofix.fix_file(file_path)
|
|
130
|
+
|
|
131
|
+
# Track result
|
|
132
|
+
changes = []
|
|
133
|
+
if fix_result.success:
|
|
134
|
+
if fix_result.original_deps != fix_result.fixed_deps:
|
|
135
|
+
changes.append(f"Updated ∂{{}} from {fix_result.original_deps} to {fix_result.fixed_deps}")
|
|
136
|
+
|
|
137
|
+
file_result = {
|
|
138
|
+
'file': str(file_path),
|
|
139
|
+
'fixed': fix_result.success and len(changes) > 0,
|
|
140
|
+
'changes': changes,
|
|
141
|
+
'error': fix_result.error if not fix_result.success else None
|
|
142
|
+
}
|
|
143
|
+
results['files'].append(file_result)
|
|
144
|
+
|
|
145
|
+
if file_result['fixed']:
|
|
146
|
+
results['fixed'] += 1
|
|
147
|
+
elif file_result['error']:
|
|
148
|
+
results['errors'] += 1
|
|
149
|
+
else:
|
|
150
|
+
results['skipped'] += 1
|
|
151
|
+
|
|
152
|
+
# Display result (text mode)
|
|
153
|
+
if format == 'text':
|
|
154
|
+
_display_file_result(file_path, file_result, dry_run)
|
|
155
|
+
|
|
156
|
+
except Exception as e:
|
|
157
|
+
results['errors'] += 1
|
|
158
|
+
results['files'].append({
|
|
159
|
+
'file': str(file_path),
|
|
160
|
+
'fixed': False,
|
|
161
|
+
'error': str(e)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
if format == 'text':
|
|
165
|
+
click.secho(f"❌ {file_path}", fg='red')
|
|
166
|
+
click.secho(f" Error: {e}", fg='red')
|
|
167
|
+
|
|
168
|
+
# Display summary
|
|
169
|
+
if format == 'text':
|
|
170
|
+
_display_summary(results, dry_run, strict)
|
|
171
|
+
else:
|
|
172
|
+
import json
|
|
173
|
+
click.echo(json.dumps(results, indent=2))
|
|
174
|
+
|
|
175
|
+
# Exit code
|
|
176
|
+
if strict and results['fixed'] > 0:
|
|
177
|
+
sys.exit(1)
|
|
178
|
+
elif results['errors'] > 0:
|
|
179
|
+
sys.exit(1)
|
|
180
|
+
else:
|
|
181
|
+
sys.exit(0)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _should_exclude(file_path: Path, exclude_patterns: tuple) -> bool:
|
|
185
|
+
"""Check if file should be excluded based on patterns."""
|
|
186
|
+
file_str = str(file_path)
|
|
187
|
+
for pattern in exclude_patterns:
|
|
188
|
+
if pattern in file_str:
|
|
189
|
+
return True
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _display_file_result(file_path: Path, result: Dict[str, Any], dry_run: bool):
|
|
194
|
+
"""Display result for a single file."""
|
|
195
|
+
if result.get('error'):
|
|
196
|
+
click.secho(f"❌ {file_path}", fg='red')
|
|
197
|
+
click.secho(f" Error: {result['error']}", fg='red')
|
|
198
|
+
elif result.get('fixed'):
|
|
199
|
+
action = "Would fix" if dry_run else "Fixed"
|
|
200
|
+
click.secho(f"✅ {file_path}", fg='green')
|
|
201
|
+
changes = result.get('changes', [])
|
|
202
|
+
for change in changes:
|
|
203
|
+
click.echo(f" • {change}")
|
|
204
|
+
else:
|
|
205
|
+
click.secho(f"⚪ {file_path}", fg='white')
|
|
206
|
+
click.echo(f" No fixes needed")
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _display_summary(results: Dict[str, Any], dry_run: bool, strict: bool):
|
|
210
|
+
"""Display summary of fix results."""
|
|
211
|
+
total = len(results['files'])
|
|
212
|
+
fixed = results['fixed']
|
|
213
|
+
errors = results['errors']
|
|
214
|
+
skipped = results['skipped']
|
|
215
|
+
|
|
216
|
+
click.echo()
|
|
217
|
+
click.echo("━" * 60)
|
|
218
|
+
click.echo(f"Total files: {total}")
|
|
219
|
+
|
|
220
|
+
if dry_run:
|
|
221
|
+
click.secho(f"Would fix: {fixed}", fg='yellow')
|
|
222
|
+
else:
|
|
223
|
+
click.secho(f"Fixed: {fixed}", fg='green' if fixed > 0 else 'white')
|
|
224
|
+
|
|
225
|
+
click.echo(f"No changes needed: {skipped}")
|
|
226
|
+
|
|
227
|
+
if errors > 0:
|
|
228
|
+
click.secho(f"Errors: {errors}", fg='red')
|
|
229
|
+
|
|
230
|
+
click.echo("━" * 60)
|
|
231
|
+
|
|
232
|
+
if dry_run and fixed > 0:
|
|
233
|
+
click.echo()
|
|
234
|
+
click.secho("💡 Run without --dry-run to apply fixes", fg='cyan')
|
|
235
|
+
elif not dry_run and fixed > 0:
|
|
236
|
+
click.echo()
|
|
237
|
+
click.secho("✅ All fixes applied!", fg='green')
|
|
238
|
+
elif fixed == 0 and errors == 0:
|
|
239
|
+
click.echo()
|
|
240
|
+
click.secho("✅ All files are already valid!", fg='green')
|
|
241
|
+
|
|
242
|
+
if strict and fixed > 0:
|
|
243
|
+
click.echo()
|
|
244
|
+
click.secho("⚠️ Strict mode: Exiting with error code (fixes were needed)", fg='yellow')
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""@darkarts
|
|
2
|
+
⊢cli:generate
|
|
3
|
+
∂{click,pathlib,typing,sys}
|
|
4
|
+
⚠{python≥3.7,click≥8.0}
|
|
5
|
+
⊨{∀generation→executed,∀validation→passed-if-enabled}
|
|
6
|
+
🔒{read:files,write:documentation}
|
|
7
|
+
⚡{O(n*m)|n=files,m=file-size}
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
VooDocs CLI - Generate Command
|
|
12
|
+
|
|
13
|
+
Generates documentation from @darkarts annotations.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
import click
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import List
|
|
20
|
+
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path as PathLib
|
|
23
|
+
sys.path.insert(0, str(PathLib(__file__).parent.parent))
|
|
24
|
+
from darkarts.validation.semantic_wrapper import SemanticValidator
|
|
25
|
+
from darkarts.validation.performance_wrapper import PerformanceTracker
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@click.command()
|
|
29
|
+
@click.argument('source', type=click.Path(exists=True))
|
|
30
|
+
@click.argument('output', type=click.Path())
|
|
31
|
+
@click.option('-r', '--recursive', is_flag=True, help='Recursively process all files')
|
|
32
|
+
@click.option('--validate', is_flag=True, help='Validate annotations before generating')
|
|
33
|
+
@click.option('--strict', is_flag=True, help='Fail if validation fails')
|
|
34
|
+
@click.option('--format', type=click.Choice(['markdown', 'html', 'json']), default='markdown', help='Output format')
|
|
35
|
+
@click.option('--include-private', is_flag=True, help='Include private members in documentation')
|
|
36
|
+
def generate(
|
|
37
|
+
source: str,
|
|
38
|
+
output: str,
|
|
39
|
+
recursive: bool,
|
|
40
|
+
validate: bool,
|
|
41
|
+
strict: bool,
|
|
42
|
+
format: str,
|
|
43
|
+
include_private: bool
|
|
44
|
+
):
|
|
45
|
+
"""
|
|
46
|
+
Generate documentation from @darkarts annotations.
|
|
47
|
+
|
|
48
|
+
Reads @darkarts annotations from Python files and generates
|
|
49
|
+
comprehensive documentation in various formats.
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
|
|
53
|
+
# Generate markdown docs
|
|
54
|
+
voodocs generate lib/ docs/
|
|
55
|
+
|
|
56
|
+
# Generate with validation
|
|
57
|
+
voodocs generate lib/ docs/ --validate
|
|
58
|
+
|
|
59
|
+
# Strict mode (fail if validation fails)
|
|
60
|
+
voodocs generate lib/ docs/ --validate --strict
|
|
61
|
+
|
|
62
|
+
# HTML format
|
|
63
|
+
voodocs generate lib/ docs/ --format html
|
|
64
|
+
|
|
65
|
+
# Recursive processing
|
|
66
|
+
voodocs generate lib/ docs/ -r
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
click.echo(f"Generating documentation from: {source}")
|
|
70
|
+
click.echo(f"Output directory: {output}")
|
|
71
|
+
click.echo(f"Format: {format}")
|
|
72
|
+
click.echo()
|
|
73
|
+
|
|
74
|
+
# Collect source files
|
|
75
|
+
source_path = Path(source)
|
|
76
|
+
files_to_process: List[Path] = []
|
|
77
|
+
|
|
78
|
+
if source_path.is_file():
|
|
79
|
+
files_to_process = [source_path]
|
|
80
|
+
elif source_path.is_dir():
|
|
81
|
+
pattern = "**/*.py" if recursive else "*.py"
|
|
82
|
+
files_to_process = [f for f in source_path.glob(pattern) if f.is_file()]
|
|
83
|
+
|
|
84
|
+
if not files_to_process:
|
|
85
|
+
click.secho("No Python files found to process.", fg='yellow')
|
|
86
|
+
sys.exit(1)
|
|
87
|
+
|
|
88
|
+
click.echo(f"Found {len(files_to_process)} files to process")
|
|
89
|
+
|
|
90
|
+
# Validate if requested
|
|
91
|
+
if validate:
|
|
92
|
+
click.echo()
|
|
93
|
+
click.secho("Validating annotations...", fg='cyan')
|
|
94
|
+
|
|
95
|
+
validator = SemanticValidator()
|
|
96
|
+
tracker = PerformanceTracker()
|
|
97
|
+
|
|
98
|
+
validation_failed = False
|
|
99
|
+
for file_path in files_to_process:
|
|
100
|
+
try:
|
|
101
|
+
# Semantic validation
|
|
102
|
+
sem_result = validator.validate_file(file_path)
|
|
103
|
+
|
|
104
|
+
# Performance validation
|
|
105
|
+
perf_result = tracker.analyze_file(file_path)
|
|
106
|
+
|
|
107
|
+
if not (sem_result and perf_result):
|
|
108
|
+
validation_failed = True
|
|
109
|
+
click.secho(f"❌ {file_path}", fg='red')
|
|
110
|
+
except Exception as e:
|
|
111
|
+
validation_failed = True
|
|
112
|
+
click.secho(f"❌ {file_path}: {e}", fg='red')
|
|
113
|
+
|
|
114
|
+
if validation_failed:
|
|
115
|
+
click.echo()
|
|
116
|
+
click.secho("⚠️ Validation failed!", fg='red')
|
|
117
|
+
if strict:
|
|
118
|
+
click.echo("Strict mode enabled - aborting documentation generation")
|
|
119
|
+
sys.exit(1)
|
|
120
|
+
else:
|
|
121
|
+
click.echo("Continuing with documentation generation (use --strict to abort on validation failure)")
|
|
122
|
+
else:
|
|
123
|
+
click.echo()
|
|
124
|
+
click.secho("✅ All annotations validated successfully!", fg='green')
|
|
125
|
+
|
|
126
|
+
# Create output directory
|
|
127
|
+
output_path = Path(output)
|
|
128
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
129
|
+
|
|
130
|
+
# Generate documentation
|
|
131
|
+
click.echo()
|
|
132
|
+
click.secho("Generating documentation...", fg='cyan')
|
|
133
|
+
|
|
134
|
+
generated_files = []
|
|
135
|
+
for file_path in files_to_process:
|
|
136
|
+
try:
|
|
137
|
+
# Generate documentation for this file
|
|
138
|
+
doc_content = _generate_doc_for_file(file_path, format, include_private)
|
|
139
|
+
|
|
140
|
+
# Determine output filename
|
|
141
|
+
if source_path.is_dir():
|
|
142
|
+
relative_path = file_path.relative_to(source_path)
|
|
143
|
+
output_filename = relative_path.with_suffix(f".{_get_extension(format)}")
|
|
144
|
+
else:
|
|
145
|
+
output_filename = Path(file_path.stem + f".{_get_extension(format)}")
|
|
146
|
+
output_file = output_path / output_filename
|
|
147
|
+
|
|
148
|
+
# Create parent directories if needed
|
|
149
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
150
|
+
|
|
151
|
+
# Write documentation
|
|
152
|
+
output_file.write_text(doc_content, encoding='utf-8')
|
|
153
|
+
generated_files.append(output_file)
|
|
154
|
+
|
|
155
|
+
click.echo(f"✅ {file_path} → {output_file}")
|
|
156
|
+
|
|
157
|
+
except Exception as e:
|
|
158
|
+
click.secho(f"❌ {file_path}: {e}", fg='red')
|
|
159
|
+
|
|
160
|
+
# Summary
|
|
161
|
+
click.echo()
|
|
162
|
+
click.echo("━" * 60)
|
|
163
|
+
click.echo(f"Total files processed: {len(files_to_process)}")
|
|
164
|
+
click.secho(f"Documentation generated: {len(generated_files)}", fg='green')
|
|
165
|
+
click.echo(f"Output directory: {output_path}")
|
|
166
|
+
click.echo("━" * 60)
|
|
167
|
+
|
|
168
|
+
if len(generated_files) == len(files_to_process):
|
|
169
|
+
click.echo()
|
|
170
|
+
click.secho("✅ Documentation generation complete!", fg='green')
|
|
171
|
+
sys.exit(0)
|
|
172
|
+
else:
|
|
173
|
+
click.echo()
|
|
174
|
+
click.secho("⚠️ Some files failed to generate documentation", fg='yellow')
|
|
175
|
+
sys.exit(1)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _generate_doc_for_file(file_path: Path, format: str, include_private: bool) -> str:
|
|
179
|
+
"""Generate documentation for a single file."""
|
|
180
|
+
content = file_path.read_text(encoding='utf-8')
|
|
181
|
+
|
|
182
|
+
# Extract @darkarts annotation
|
|
183
|
+
annotation = _extract_annotation(content)
|
|
184
|
+
|
|
185
|
+
if not annotation:
|
|
186
|
+
return f"# {file_path.name}\n\nNo @darkarts annotation found.\n"
|
|
187
|
+
|
|
188
|
+
# Parse annotation sections
|
|
189
|
+
module_id = _extract_section(annotation, '⊢')
|
|
190
|
+
dependencies = _extract_section(annotation, '∂')
|
|
191
|
+
assumptions = _extract_section(annotation, '⚠')
|
|
192
|
+
invariants = _extract_section(annotation, '⊨')
|
|
193
|
+
security = _extract_section(annotation, '🔒')
|
|
194
|
+
performance = _extract_section(annotation, '⚡')
|
|
195
|
+
|
|
196
|
+
# Generate documentation based on format
|
|
197
|
+
if format == 'markdown':
|
|
198
|
+
return _generate_markdown(file_path, module_id, dependencies, assumptions, invariants, security, performance)
|
|
199
|
+
elif format == 'html':
|
|
200
|
+
return _generate_html(file_path, module_id, dependencies, assumptions, invariants, security, performance)
|
|
201
|
+
elif format == 'json':
|
|
202
|
+
import json
|
|
203
|
+
return json.dumps({
|
|
204
|
+
'file': str(file_path),
|
|
205
|
+
'module': module_id,
|
|
206
|
+
'dependencies': dependencies,
|
|
207
|
+
'assumptions': assumptions,
|
|
208
|
+
'invariants': invariants,
|
|
209
|
+
'security': security,
|
|
210
|
+
'performance': performance
|
|
211
|
+
}, indent=2)
|
|
212
|
+
else:
|
|
213
|
+
return ""
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _extract_annotation(content: str) -> str:
|
|
217
|
+
"""Extract @darkarts annotation from file content."""
|
|
218
|
+
if '"""@darkarts' in content:
|
|
219
|
+
start = content.index('"""@darkarts')
|
|
220
|
+
end = content.index('"""', start + 3) + 3
|
|
221
|
+
return content[start:end]
|
|
222
|
+
return ""
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _extract_section(annotation: str, symbol: str) -> str:
|
|
226
|
+
"""Extract a specific section from annotation."""
|
|
227
|
+
lines = annotation.split('\n')
|
|
228
|
+
for line in lines:
|
|
229
|
+
if line.strip().startswith(symbol):
|
|
230
|
+
return line.strip()[len(symbol):].strip('{}')
|
|
231
|
+
return ""
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _generate_markdown(file_path, module_id, dependencies, assumptions, invariants, security, performance) -> str:
|
|
235
|
+
"""Generate Markdown documentation."""
|
|
236
|
+
md = f"# {file_path.name}\n\n"
|
|
237
|
+
|
|
238
|
+
if module_id:
|
|
239
|
+
md += f"**Module:** `{module_id}`\n\n"
|
|
240
|
+
|
|
241
|
+
if dependencies:
|
|
242
|
+
md += f"## Dependencies\n\n`{dependencies}`\n\n"
|
|
243
|
+
|
|
244
|
+
if assumptions:
|
|
245
|
+
md += f"## Assumptions\n\n{assumptions}\n\n"
|
|
246
|
+
|
|
247
|
+
if invariants:
|
|
248
|
+
md += f"## Invariants\n\n{invariants}\n\n"
|
|
249
|
+
|
|
250
|
+
if security:
|
|
251
|
+
md += f"## Security\n\n{security}\n\n"
|
|
252
|
+
|
|
253
|
+
if performance:
|
|
254
|
+
md += f"## Performance\n\n**Complexity:** `{performance}`\n\n"
|
|
255
|
+
|
|
256
|
+
return md
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _generate_html(file_path, module_id, dependencies, assumptions, invariants, security, performance) -> str:
|
|
260
|
+
"""Generate HTML documentation."""
|
|
261
|
+
html = f"""<!DOCTYPE html>
|
|
262
|
+
<html>
|
|
263
|
+
<head>
|
|
264
|
+
<title>{file_path.name}</title>
|
|
265
|
+
<style>
|
|
266
|
+
body {{ font-family: Arial, sans-serif; margin: 40px; }}
|
|
267
|
+
h1 {{ color: #333; }}
|
|
268
|
+
h2 {{ color: #666; margin-top: 30px; }}
|
|
269
|
+
code {{ background: #f4f4f4; padding: 2px 6px; border-radius: 3px; }}
|
|
270
|
+
.section {{ margin: 20px 0; }}
|
|
271
|
+
</style>
|
|
272
|
+
</head>
|
|
273
|
+
<body>
|
|
274
|
+
<h1>{file_path.name}</h1>
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
if module_id:
|
|
278
|
+
html += f"<div class='section'><strong>Module:</strong> <code>{module_id}</code></div>"
|
|
279
|
+
|
|
280
|
+
if dependencies:
|
|
281
|
+
html += f"<h2>Dependencies</h2><div class='section'><code>{dependencies}</code></div>"
|
|
282
|
+
|
|
283
|
+
if assumptions:
|
|
284
|
+
html += f"<h2>Assumptions</h2><div class='section'>{assumptions}</div>"
|
|
285
|
+
|
|
286
|
+
if invariants:
|
|
287
|
+
html += f"<h2>Invariants</h2><div class='section'>{invariants}</div>"
|
|
288
|
+
|
|
289
|
+
if security:
|
|
290
|
+
html += f"<h2>Security</h2><div class='section'>{security}</div>"
|
|
291
|
+
|
|
292
|
+
if performance:
|
|
293
|
+
html += f"<h2>Performance</h2><div class='section'><strong>Complexity:</strong> <code>{performance}</code></div>"
|
|
294
|
+
|
|
295
|
+
html += """
|
|
296
|
+
</body>
|
|
297
|
+
</html>
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
return html
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _get_extension(format: str) -> str:
|
|
304
|
+
"""Get file extension for format."""
|
|
305
|
+
extensions = {
|
|
306
|
+
'markdown': 'md',
|
|
307
|
+
'html': 'html',
|
|
308
|
+
'json': 'json'
|
|
309
|
+
}
|
|
310
|
+
return extensions.get(format, 'txt')
|