@voodocs/cli 2.5.0 → 2.5.2
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 +26 -0
- package/lib/cli/__init__.py +7 -1
- package/lib/cli/analyze.py +156 -98
- package/lib/cli/convert.py +131 -0
- package/lib/darkarts/priority_analyzer/__init__.py +0 -0
- package/lib/darkarts/priority_analyzer/analyzer.py +301 -0
- package/lib/darkarts/priority_analyzer/complexity.py +271 -0
- package/lib/darkarts/priority_analyzer/dependencies.py +275 -0
- package/lib/darkarts/priority_analyzer/security.py +200 -0
- package/lib/darkarts/voodocs_lite_dict.py +216 -0
- package/lib/darkarts/voodocs_lite_dict_v2.py +198 -0
- package/lib/darkarts/voodocs_lite_parser.py +343 -0
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## [2.5.2] - 2024-12-24
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **CLI Registration**: Fixed analyze, convert, and companion commands not being registered
|
|
7
|
+
- Converted `analyze.py` from argparse to Click command
|
|
8
|
+
- Created `convert.py` as Click command for VooDocs Lite conversion
|
|
9
|
+
- Updated `lib/cli/__init__.py` to import and register all three commands
|
|
10
|
+
- Updated `__version__` from "2.1.3" to "2.5.2" in Python code
|
|
11
|
+
- v2.5.0 and v2.5.1 had files in package but commands weren't accessible via CLI
|
|
12
|
+
- All Priority Analyzer and VooDocs Lite features now fully functional
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## [2.5.1] - 2024-12-24
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- **Packaging Issue**: Fixed npm package to include missing files from v2.5.0
|
|
20
|
+
- Added `lib/darkarts/priority_analyzer/` directory
|
|
21
|
+
- Added `lib/darkarts/voodocs_lite_dict.py`
|
|
22
|
+
- Added `lib/darkarts/voodocs_lite_dict_v2.py`
|
|
23
|
+
- Added `lib/darkarts/voodocs_lite_parser.py`
|
|
24
|
+
- v2.5.0 was published with CHANGELOG updates but missing actual code files
|
|
25
|
+
- All Priority Analyzer and VooDocs Lite features now properly included
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
3
29
|
## [2.5.0] - 2024-12-24
|
|
4
30
|
|
|
5
31
|
### Added
|
package/lib/cli/__init__.py
CHANGED
|
@@ -16,7 +16,7 @@ This module provides the command-line interface for VooDocs.
|
|
|
16
16
|
import click
|
|
17
17
|
from typing import Optional
|
|
18
18
|
|
|
19
|
-
__version__ = "2.
|
|
19
|
+
__version__ = "2.5.2"
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
@click.group()
|
|
@@ -41,6 +41,9 @@ from .generate import generate
|
|
|
41
41
|
from .benchmark import benchmark
|
|
42
42
|
from .fix import fix
|
|
43
43
|
from .context import context
|
|
44
|
+
from .analyze import analyze
|
|
45
|
+
from .convert import convert
|
|
46
|
+
from .companion import companion
|
|
44
47
|
|
|
45
48
|
# Register commands
|
|
46
49
|
cli.add_command(init)
|
|
@@ -50,6 +53,9 @@ cli.add_command(generate)
|
|
|
50
53
|
cli.add_command(benchmark)
|
|
51
54
|
cli.add_command(fix)
|
|
52
55
|
cli.add_command(context)
|
|
56
|
+
cli.add_command(analyze)
|
|
57
|
+
cli.add_command(convert)
|
|
58
|
+
cli.add_command(companion)
|
|
53
59
|
|
|
54
60
|
|
|
55
61
|
def main():
|
package/lib/cli/analyze.py
CHANGED
|
@@ -1,29 +1,96 @@
|
|
|
1
|
+
"""@darkarts
|
|
2
|
+
⊢cli:analyze
|
|
3
|
+
∂{click,pathlib,darkarts.priority_analyzer}
|
|
4
|
+
⚠{python≥3.7,click≥8.0}
|
|
5
|
+
⊨{∀file→analyzed,∀score→calculated}
|
|
6
|
+
🔒{read-only:source-files}
|
|
7
|
+
⚡{O(n):files}
|
|
1
8
|
"""
|
|
2
|
-
CLI command for VooDocs Priority Analyzer
|
|
3
9
|
|
|
4
|
-
Usage: voodocs analyze [path] [options]
|
|
5
10
|
"""
|
|
11
|
+
VooDocs Analyze Command
|
|
6
12
|
|
|
13
|
+
Analyze files to identify which need VooDocs annotations most urgently.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import click
|
|
7
17
|
import os
|
|
8
18
|
import sys
|
|
19
|
+
import json
|
|
20
|
+
import csv
|
|
9
21
|
from pathlib import Path
|
|
10
22
|
|
|
11
23
|
# Add parent directory to path for imports
|
|
12
|
-
sys.path.insert(0,
|
|
24
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
25
|
+
from darkarts.priority_analyzer.analyzer import PriorityAnalyzer
|
|
13
26
|
|
|
14
|
-
from lib.darkarts.priority_analyzer.analyzer import PriorityAnalyzer
|
|
15
27
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
28
|
+
@click.command()
|
|
29
|
+
@click.argument(
|
|
30
|
+
'path',
|
|
31
|
+
type=click.Path(exists=True),
|
|
32
|
+
default='.',
|
|
33
|
+
required=False
|
|
34
|
+
)
|
|
35
|
+
@click.option(
|
|
36
|
+
'-r', '--recursive',
|
|
37
|
+
is_flag=True,
|
|
38
|
+
default=True,
|
|
39
|
+
help='Recursively analyze directories (default: true)'
|
|
40
|
+
)
|
|
41
|
+
@click.option(
|
|
42
|
+
'--top',
|
|
43
|
+
type=int,
|
|
44
|
+
default=10,
|
|
45
|
+
help='Show top N files (default: 10, 0 for all)'
|
|
46
|
+
)
|
|
47
|
+
@click.option(
|
|
48
|
+
'--priority',
|
|
49
|
+
type=click.Choice(['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'MINIMAL'], case_sensitive=False),
|
|
50
|
+
help='Filter by priority level'
|
|
51
|
+
)
|
|
52
|
+
@click.option(
|
|
53
|
+
'--min-score',
|
|
54
|
+
type=int,
|
|
55
|
+
help='Minimum priority score to show'
|
|
56
|
+
)
|
|
57
|
+
@click.option(
|
|
58
|
+
'--format',
|
|
59
|
+
'output_format',
|
|
60
|
+
type=click.Choice(['text', 'json', 'csv', 'markdown']),
|
|
61
|
+
default='text',
|
|
62
|
+
help='Output format (default: text)'
|
|
63
|
+
)
|
|
64
|
+
@click.option(
|
|
65
|
+
'--include-annotated',
|
|
66
|
+
is_flag=True,
|
|
67
|
+
help='Include fully annotated files'
|
|
68
|
+
)
|
|
69
|
+
@click.option(
|
|
70
|
+
'--exclude',
|
|
71
|
+
help='Comma-separated list of patterns to exclude'
|
|
72
|
+
)
|
|
73
|
+
def analyze(path, recursive, top, priority, min_score, output_format, include_annotated, exclude):
|
|
74
|
+
"""
|
|
75
|
+
Analyze files to identify which need VooDocs annotations most urgently.
|
|
76
|
+
|
|
77
|
+
Uses heuristics based on:
|
|
78
|
+
- File complexity (LOC, cyclomatic complexity, function count)
|
|
79
|
+
- Security keywords (auth, password, token, etc.)
|
|
80
|
+
- Import/dependency count
|
|
81
|
+
|
|
82
|
+
Examples:
|
|
83
|
+
voodocs analyze src/
|
|
84
|
+
voodocs analyze src/ --top 20
|
|
85
|
+
voodocs analyze src/ --priority critical
|
|
86
|
+
voodocs analyze src/ --format json
|
|
87
|
+
"""
|
|
21
88
|
path = os.path.abspath(path)
|
|
22
89
|
|
|
23
90
|
# Check if path exists
|
|
24
91
|
if not os.path.exists(path):
|
|
25
|
-
|
|
26
|
-
|
|
92
|
+
click.echo(f"❌ Error: Path not found: {path}", err=True)
|
|
93
|
+
sys.exit(1)
|
|
27
94
|
|
|
28
95
|
# Determine if path is file or directory
|
|
29
96
|
is_file = os.path.isfile(path)
|
|
@@ -36,38 +103,34 @@ def cmd_analyze(args):
|
|
|
36
103
|
if is_file:
|
|
37
104
|
scores = [analyzer.analyze_file(path)]
|
|
38
105
|
else:
|
|
39
|
-
recursive = getattr(args, 'recursive', True)
|
|
40
|
-
exclude = getattr(args, 'exclude', None)
|
|
41
106
|
exclude_patterns = exclude.split(',') if exclude else None
|
|
42
107
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
108
|
+
if output_format == 'text':
|
|
109
|
+
click.echo(f"🔍 Analyzing files in: {path}")
|
|
110
|
+
if recursive:
|
|
111
|
+
click.echo(" (recursive mode)")
|
|
112
|
+
click.echo()
|
|
47
113
|
|
|
48
114
|
scores = analyzer.analyze_directory(path, recursive, exclude_patterns)
|
|
49
115
|
|
|
50
116
|
# Filter by priority level if specified
|
|
51
|
-
if
|
|
52
|
-
priority_filter =
|
|
117
|
+
if priority:
|
|
118
|
+
priority_filter = priority.upper()
|
|
53
119
|
scores = [s for s in scores if s.priority_level == priority_filter]
|
|
54
120
|
|
|
55
121
|
# Filter by minimum score
|
|
56
|
-
if
|
|
57
|
-
scores = [s for s in scores if s.priority_score >=
|
|
122
|
+
if min_score:
|
|
123
|
+
scores = [s for s in scores if s.priority_score >= min_score]
|
|
58
124
|
|
|
59
125
|
# Filter out fully annotated files (unless --include-annotated)
|
|
60
|
-
if not
|
|
126
|
+
if not include_annotated:
|
|
61
127
|
scores = [s for s in scores if s.annotation_coverage < 1.0]
|
|
62
128
|
|
|
63
129
|
# Limit to top N
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
scores = scores[:top_n]
|
|
67
|
-
|
|
68
|
-
# Output format
|
|
69
|
-
output_format = getattr(args, 'format', 'text')
|
|
130
|
+
if top and top > 0:
|
|
131
|
+
scores = scores[:top]
|
|
70
132
|
|
|
133
|
+
# Output
|
|
71
134
|
if output_format == 'json':
|
|
72
135
|
_output_json(scores)
|
|
73
136
|
elif output_format == 'csv':
|
|
@@ -77,14 +140,14 @@ def cmd_analyze(args):
|
|
|
77
140
|
else:
|
|
78
141
|
_output_text(scores, path)
|
|
79
142
|
|
|
80
|
-
|
|
143
|
+
sys.exit(0)
|
|
81
144
|
|
|
82
145
|
|
|
83
146
|
def _output_text(scores, path):
|
|
84
147
|
"""Output results in human-readable text format."""
|
|
85
148
|
if not scores:
|
|
86
|
-
|
|
87
|
-
|
|
149
|
+
click.echo("✅ No files need annotations!")
|
|
150
|
+
click.echo(" All files are either fully annotated or below priority threshold.")
|
|
88
151
|
return
|
|
89
152
|
|
|
90
153
|
# Calculate statistics
|
|
@@ -101,34 +164,34 @@ def _output_text(scores, path):
|
|
|
101
164
|
coverage_pct = (annotated_count / total_files * 100) if total_files > 0 else 0
|
|
102
165
|
|
|
103
166
|
# Header
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
167
|
+
click.echo("━" * 70)
|
|
168
|
+
click.echo("VooDocs Priority Analysis")
|
|
169
|
+
click.echo("━" * 70)
|
|
170
|
+
click.echo()
|
|
171
|
+
click.echo(f"Project: {path}")
|
|
172
|
+
click.echo(f"Files analyzed: {total_files}")
|
|
173
|
+
click.echo(f"Annotations found: {annotated_count} ({coverage_pct:.0f}% coverage)")
|
|
174
|
+
click.echo()
|
|
112
175
|
|
|
113
176
|
# Priority distribution
|
|
114
|
-
|
|
177
|
+
click.echo("Priority Distribution:")
|
|
115
178
|
if priority_dist['CRITICAL'] > 0:
|
|
116
|
-
|
|
179
|
+
click.echo(f"🔴 CRITICAL (80-100): {priority_dist['CRITICAL']} files")
|
|
117
180
|
if priority_dist['HIGH'] > 0:
|
|
118
|
-
|
|
181
|
+
click.echo(f"🟠 HIGH (60-79): {priority_dist['HIGH']} files")
|
|
119
182
|
if priority_dist['MEDIUM'] > 0:
|
|
120
|
-
|
|
183
|
+
click.echo(f"🟡 MEDIUM (40-59): {priority_dist['MEDIUM']} files")
|
|
121
184
|
if priority_dist['LOW'] > 0:
|
|
122
|
-
|
|
185
|
+
click.echo(f"🟢 LOW (20-39): {priority_dist['LOW']} files")
|
|
123
186
|
if priority_dist['MINIMAL'] > 0:
|
|
124
|
-
|
|
125
|
-
|
|
187
|
+
click.echo(f"⚪ MINIMAL (0-19): {priority_dist['MINIMAL']} files")
|
|
188
|
+
click.echo()
|
|
126
189
|
|
|
127
190
|
# Top files
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
191
|
+
click.echo("━" * 70)
|
|
192
|
+
click.echo(f"Top {len(scores)} Files Needing Annotations")
|
|
193
|
+
click.echo("━" * 70)
|
|
194
|
+
click.echo()
|
|
132
195
|
|
|
133
196
|
for i, score in enumerate(scores, 1):
|
|
134
197
|
# Priority emoji
|
|
@@ -143,41 +206,39 @@ def _output_text(scores, path):
|
|
|
143
206
|
# Relative path
|
|
144
207
|
rel_path = os.path.relpath(score.filepath, path) if not os.path.isfile(path) else score.filepath
|
|
145
208
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
209
|
+
click.echo(f"{i}. {emoji} {rel_path} (Score: {score.priority_score:.0f})")
|
|
210
|
+
click.echo(f" Complexity: {score.complexity_score} Security: {score.security_score} Dependencies: {score.dependency_score} Annotations: {score.annotation_penalty}")
|
|
211
|
+
click.echo()
|
|
149
212
|
|
|
150
213
|
# Reasons
|
|
151
214
|
if score.reasons:
|
|
152
|
-
|
|
215
|
+
click.echo(" Reasons:")
|
|
153
216
|
for reason in score.reasons:
|
|
154
|
-
|
|
155
|
-
|
|
217
|
+
click.echo(f" • {reason}")
|
|
218
|
+
click.echo()
|
|
156
219
|
|
|
157
220
|
# Suggestions
|
|
158
221
|
if score.suggestions:
|
|
159
|
-
|
|
222
|
+
click.echo(" Suggestions:")
|
|
160
223
|
for suggestion in score.suggestions:
|
|
161
|
-
|
|
162
|
-
|
|
224
|
+
click.echo(f" ✓ {suggestion}")
|
|
225
|
+
click.echo()
|
|
163
226
|
|
|
164
227
|
# Footer
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
228
|
+
click.echo("━" * 70)
|
|
229
|
+
click.echo()
|
|
230
|
+
click.echo("💡 Tip: Start with CRITICAL files first. Use:")
|
|
168
231
|
if scores:
|
|
169
232
|
first_file = scores[0].filepath
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
233
|
+
click.echo(f" voodocs companion {first_file}")
|
|
234
|
+
click.echo()
|
|
235
|
+
click.echo(" Or convert to VooDocs Lite for faster annotation:")
|
|
236
|
+
click.echo(f" voodocs convert {path} --to lite")
|
|
237
|
+
click.echo()
|
|
175
238
|
|
|
176
239
|
|
|
177
240
|
def _output_json(scores):
|
|
178
241
|
"""Output results in JSON format."""
|
|
179
|
-
import json
|
|
180
|
-
|
|
181
242
|
data = []
|
|
182
243
|
for score in scores:
|
|
183
244
|
data.append({
|
|
@@ -197,14 +258,11 @@ def _output_json(scores):
|
|
|
197
258
|
'suggestions': score.suggestions
|
|
198
259
|
})
|
|
199
260
|
|
|
200
|
-
|
|
261
|
+
click.echo(json.dumps(data, indent=2))
|
|
201
262
|
|
|
202
263
|
|
|
203
264
|
def _output_csv(scores):
|
|
204
265
|
"""Output results in CSV format."""
|
|
205
|
-
import csv
|
|
206
|
-
import sys
|
|
207
|
-
|
|
208
266
|
writer = csv.writer(sys.stdout)
|
|
209
267
|
|
|
210
268
|
# Header
|
|
@@ -234,14 +292,14 @@ def _output_csv(scores):
|
|
|
234
292
|
|
|
235
293
|
def _output_markdown(scores):
|
|
236
294
|
"""Output results in Markdown format."""
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
295
|
+
click.echo("# VooDocs Priority Analysis")
|
|
296
|
+
click.echo()
|
|
297
|
+
click.echo("## Summary")
|
|
298
|
+
click.echo()
|
|
299
|
+
click.echo(f"- **Files analyzed:** {len(scores)}")
|
|
300
|
+
click.echo()
|
|
301
|
+
click.echo("## Top Files")
|
|
302
|
+
click.echo()
|
|
245
303
|
|
|
246
304
|
for i, score in enumerate(scores, 1):
|
|
247
305
|
emoji = {
|
|
@@ -252,26 +310,26 @@ def _output_markdown(scores):
|
|
|
252
310
|
'MINIMAL': '⚪'
|
|
253
311
|
}.get(score.priority_level, '⚪')
|
|
254
312
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
313
|
+
click.echo(f"### {i}. {emoji} {score.filepath}")
|
|
314
|
+
click.echo()
|
|
315
|
+
click.echo(f"**Score:** {score.priority_score:.0f} ({score.priority_level})")
|
|
316
|
+
click.echo()
|
|
317
|
+
click.echo(f"- Complexity: {score.complexity_score}")
|
|
318
|
+
click.echo(f"- Security: {score.security_score}")
|
|
319
|
+
click.echo(f"- Dependencies: {score.dependency_score}")
|
|
320
|
+
click.echo(f"- Annotation Coverage: {score.annotation_coverage * 100:.0f}%")
|
|
321
|
+
click.echo()
|
|
264
322
|
|
|
265
323
|
if score.reasons:
|
|
266
|
-
|
|
267
|
-
|
|
324
|
+
click.echo("**Reasons:**")
|
|
325
|
+
click.echo()
|
|
268
326
|
for reason in score.reasons:
|
|
269
|
-
|
|
270
|
-
|
|
327
|
+
click.echo(f"- {reason}")
|
|
328
|
+
click.echo()
|
|
271
329
|
|
|
272
330
|
if score.suggestions:
|
|
273
|
-
|
|
274
|
-
|
|
331
|
+
click.echo("**Suggestions:**")
|
|
332
|
+
click.echo()
|
|
275
333
|
for suggestion in score.suggestions:
|
|
276
|
-
|
|
277
|
-
|
|
334
|
+
click.echo(f"- {suggestion}")
|
|
335
|
+
click.echo()
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""@darkarts
|
|
2
|
+
⊢cli:convert
|
|
3
|
+
∂{click,pathlib,darkarts.voodocs_lite_parser}
|
|
4
|
+
⚠{python≥3.7,click≥8.0}
|
|
5
|
+
⊨{∀file→converted|skipped,∀error→reported}
|
|
6
|
+
🔒{read-only:source-files}
|
|
7
|
+
⚡{O(n):files}
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
VooDocs Convert Command
|
|
12
|
+
|
|
13
|
+
Convert VooDocs annotations between Standard and Lite formats.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import click
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
# Import VooDocs Lite parser
|
|
21
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
22
|
+
from darkarts.voodocs_lite_parser import VooDocsLiteParser
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@click.command()
|
|
26
|
+
@click.argument(
|
|
27
|
+
'paths',
|
|
28
|
+
nargs=-1,
|
|
29
|
+
type=click.Path(exists=True),
|
|
30
|
+
required=True
|
|
31
|
+
)
|
|
32
|
+
@click.option(
|
|
33
|
+
'--to',
|
|
34
|
+
'target_format',
|
|
35
|
+
type=click.Choice(['lite', 'standard']),
|
|
36
|
+
required=True,
|
|
37
|
+
help='Target format to convert to'
|
|
38
|
+
)
|
|
39
|
+
@click.option(
|
|
40
|
+
'-r', '--recursive',
|
|
41
|
+
is_flag=True,
|
|
42
|
+
help='Recursively process directories'
|
|
43
|
+
)
|
|
44
|
+
@click.option(
|
|
45
|
+
'--dry-run',
|
|
46
|
+
is_flag=True,
|
|
47
|
+
help='Show what would be converted without modifying files'
|
|
48
|
+
)
|
|
49
|
+
@click.option(
|
|
50
|
+
'-v', '--verbose',
|
|
51
|
+
is_flag=True,
|
|
52
|
+
help='Show detailed conversion information'
|
|
53
|
+
)
|
|
54
|
+
def convert(paths, target_format, recursive, dry_run, verbose):
|
|
55
|
+
"""
|
|
56
|
+
Convert VooDocs annotations between Standard and Lite formats.
|
|
57
|
+
|
|
58
|
+
Examples:
|
|
59
|
+
voodocs convert src/ --to lite
|
|
60
|
+
voodocs convert src/ --to standard --recursive
|
|
61
|
+
voodocs convert file.ts --to lite --dry-run
|
|
62
|
+
"""
|
|
63
|
+
parser = VooDocsLiteParser()
|
|
64
|
+
|
|
65
|
+
converted_count = 0
|
|
66
|
+
skipped_count = 0
|
|
67
|
+
error_count = 0
|
|
68
|
+
|
|
69
|
+
for path_str in paths:
|
|
70
|
+
path = Path(path_str)
|
|
71
|
+
|
|
72
|
+
if path.is_file():
|
|
73
|
+
files = [path]
|
|
74
|
+
elif path.is_dir():
|
|
75
|
+
if recursive:
|
|
76
|
+
files = list(path.rglob('*.ts')) + list(path.rglob('*.js')) + \
|
|
77
|
+
list(path.rglob('*.py')) + list(path.rglob('*.sol'))
|
|
78
|
+
else:
|
|
79
|
+
files = list(path.glob('*.ts')) + list(path.glob('*.js')) + \
|
|
80
|
+
list(path.glob('*.py')) + list(path.glob('*.sol'))
|
|
81
|
+
else:
|
|
82
|
+
click.echo(f"❌ Invalid path: {path}", err=True)
|
|
83
|
+
error_count += 1
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
for file_path in files:
|
|
87
|
+
try:
|
|
88
|
+
# Read file
|
|
89
|
+
content = file_path.read_text()
|
|
90
|
+
|
|
91
|
+
# Convert
|
|
92
|
+
if target_format == 'lite':
|
|
93
|
+
converted = parser.standard_to_lite(content)
|
|
94
|
+
else:
|
|
95
|
+
converted = parser.lite_to_standard(content)
|
|
96
|
+
|
|
97
|
+
# Check if anything changed
|
|
98
|
+
if converted == content:
|
|
99
|
+
if verbose:
|
|
100
|
+
click.echo(f"⏭️ Skipped (no changes): {file_path}")
|
|
101
|
+
skipped_count += 1
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
# Write or show
|
|
105
|
+
if dry_run:
|
|
106
|
+
click.echo(f"Would convert: {file_path}")
|
|
107
|
+
if verbose:
|
|
108
|
+
click.echo(f" {len(content)} → {len(converted)} chars")
|
|
109
|
+
else:
|
|
110
|
+
file_path.write_text(converted)
|
|
111
|
+
click.echo(f"✅ Converted: {file_path}")
|
|
112
|
+
|
|
113
|
+
converted_count += 1
|
|
114
|
+
|
|
115
|
+
except Exception as e:
|
|
116
|
+
click.echo(f"❌ Error converting {file_path}: {e}", err=True)
|
|
117
|
+
error_count += 1
|
|
118
|
+
|
|
119
|
+
# Summary
|
|
120
|
+
click.echo()
|
|
121
|
+
click.echo(f"Summary:")
|
|
122
|
+
click.echo(f" ✅ Converted: {converted_count}")
|
|
123
|
+
click.echo(f" ⏭️ Skipped: {skipped_count}")
|
|
124
|
+
if error_count > 0:
|
|
125
|
+
click.echo(f" ❌ Errors: {error_count}")
|
|
126
|
+
|
|
127
|
+
if dry_run:
|
|
128
|
+
click.echo()
|
|
129
|
+
click.echo("(Dry run - no files were modified)")
|
|
130
|
+
|
|
131
|
+
sys.exit(0 if error_count == 0 else 1)
|
|
File without changes
|