pyvegh 0.7.0__tar.gz → 0.9.0__tar.gz
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.
- {pyvegh-0.7.0 → pyvegh-0.9.0}/Cargo.lock +1 -1
- {pyvegh-0.7.0 → pyvegh-0.9.0}/Cargo.toml +1 -1
- {pyvegh-0.7.0 → pyvegh-0.9.0}/PKG-INFO +40 -25
- {pyvegh-0.7.0 → pyvegh-0.9.0}/README.md +39 -24
- {pyvegh-0.7.0 → pyvegh-0.9.0}/pyproject.toml +1 -1
- {pyvegh-0.7.0 → pyvegh-0.9.0}/python/vegh/__init__.py +3 -1
- pyvegh-0.9.0/python/vegh/analytics.py +281 -0
- pyvegh-0.9.0/python/vegh/cli.py +7 -0
- pyvegh-0.7.0/python/vegh/cli.py → pyvegh-0.9.0/python/vegh/cli_commands.py +226 -608
- pyvegh-0.9.0/python/vegh/cli_config.py +81 -0
- pyvegh-0.9.0/python/vegh/cli_helpers.py +124 -0
- pyvegh-0.9.0/python/vegh/cli_hooks.py +49 -0
- pyvegh-0.9.0/python/vegh/cli_main.py +71 -0
- pyvegh-0.9.0/python/vegh/cli_repo.py +90 -0
- pyvegh-0.9.0/python/vegh/config.jsonc +1116 -0
- pyvegh-0.9.0/python/vegh/jsonc.py +53 -0
- {pyvegh-0.7.0 → pyvegh-0.9.0}/src/core.rs +16 -12
- {pyvegh-0.7.0 → pyvegh-0.9.0}/src/lib.rs +68 -22
- {pyvegh-0.7.0 → pyvegh-0.9.0}/src/storage.rs +39 -37
- pyvegh-0.7.0/python/vegh/analytics.py +0 -621
- {pyvegh-0.7.0 → pyvegh-0.9.0}/.github/workflows/ci.yml +0 -0
- {pyvegh-0.7.0 → pyvegh-0.9.0}/.github/workflows/release.yml +0 -0
- {pyvegh-0.7.0 → pyvegh-0.9.0}/.github/workflows/rust-clippy.yml +0 -0
- {pyvegh-0.7.0 → pyvegh-0.9.0}/.gitignore +0 -0
- {pyvegh-0.7.0 → pyvegh-0.9.0}/LICENSE +0 -0
- {pyvegh-0.7.0 → pyvegh-0.9.0}/src/hash.rs +0 -0
- {pyvegh-0.7.0 → pyvegh-0.9.0}/tests/integration_test.sh +0 -0
- {pyvegh-0.7.0 → pyvegh-0.9.0}/tests/test_smoke.py +0 -0
- {pyvegh-0.7.0 → pyvegh-0.9.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyvegh
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Classifier: Programming Language :: Python :: 3.10
|
|
5
5
|
Classifier: Programming Language :: Python :: 3.11
|
|
6
6
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -49,13 +49,16 @@ It delivers the raw performance of Rust (Zstd multithreaded compression, Tar arc
|
|
|
49
49
|
## Installation
|
|
50
50
|
|
|
51
51
|
Install directly from PyPI:
|
|
52
|
-
```
|
|
52
|
+
```shell
|
|
53
53
|
pip install pyvegh
|
|
54
|
+
|
|
55
|
+
# Or via uv
|
|
56
|
+
uv pip install pyvegh
|
|
54
57
|
```
|
|
55
58
|
|
|
56
59
|
Or build from source (requires Rust):
|
|
57
60
|
|
|
58
|
-
```
|
|
61
|
+
```shell
|
|
59
62
|
maturin develop --release
|
|
60
63
|
```
|
|
61
64
|
|
|
@@ -67,7 +70,7 @@ PyVegh provides a powerful command-line interface via the `vegh` (or `pyvegh`) c
|
|
|
67
70
|
|
|
68
71
|
Set up your default server URL and Auth Token so you don't have to type them every time.
|
|
69
72
|
|
|
70
|
-
```
|
|
73
|
+
```shell
|
|
71
74
|
vegh config
|
|
72
75
|
# Or one-liner:
|
|
73
76
|
vegh config send --url https://api.teaserverse.online/test --auth YOUR_TOKEN
|
|
@@ -79,11 +82,21 @@ vegh config list
|
|
|
79
82
|
vegh config reset
|
|
80
83
|
```
|
|
81
84
|
|
|
85
|
+
**Advanced:** You can also configure custom `audit` patterns in `~/.vegh/config.json`:
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"audit": {
|
|
89
|
+
"patterns": ["custom_secret\\.key", ".*\\.private"],
|
|
90
|
+
"keywords": ["MY_API_KEY", "INTERNAL_TOKEN"]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
82
95
|
### 2\. Create Snapshot
|
|
83
96
|
|
|
84
97
|
Pack a directory into a highly compressed snapshot.
|
|
85
98
|
|
|
86
|
-
```
|
|
99
|
+
```shell
|
|
87
100
|
# Basic snapshot
|
|
88
101
|
vegh snap ./my-project --output backup.vegh
|
|
89
102
|
|
|
@@ -95,7 +108,7 @@ vegh snap ./my-project --dry-run
|
|
|
95
108
|
|
|
96
109
|
View the Analytics Dashboard to break down your project by language and lines of code.
|
|
97
110
|
|
|
98
|
-
```
|
|
111
|
+
```shell
|
|
99
112
|
vegh loc backup.vegh
|
|
100
113
|
|
|
101
114
|
# Show Source Lines of Code (SLOC) instead of total LOC
|
|
@@ -106,7 +119,7 @@ vegh loc backup.vegh --sloc
|
|
|
106
119
|
### 4\. Prompt
|
|
107
120
|
|
|
108
121
|
Generate a structured XML context of your codebase to feed directly into ChatGPT, Claude, or Gemini.
|
|
109
|
-
```
|
|
122
|
+
```shell
|
|
110
123
|
# Generate XML context to stdout
|
|
111
124
|
vegh prompt .
|
|
112
125
|
|
|
@@ -125,10 +138,13 @@ vegh prompt . --clean --output context.xml
|
|
|
125
138
|
|
|
126
139
|
Clean up old snapshots to free disk space.
|
|
127
140
|
|
|
128
|
-
```
|
|
141
|
+
```shell
|
|
129
142
|
# Keep only the 5 most recent snapshots in the current directory
|
|
130
143
|
vegh prune --keep 5
|
|
131
144
|
|
|
145
|
+
# Delete snapshots older than 30 days (but always keep the 5 most recent)
|
|
146
|
+
vegh prune --older-than 30 --keep 5
|
|
147
|
+
|
|
132
148
|
# Force clean without confirmation (useful for CI/CD)
|
|
133
149
|
vegh prune --keep 1 --force
|
|
134
150
|
```
|
|
@@ -137,7 +153,7 @@ vegh prune --keep 1 --force
|
|
|
137
153
|
|
|
138
154
|
Check file integrity (Blake3) and view embedded metadata.
|
|
139
155
|
|
|
140
|
-
```
|
|
156
|
+
```shell
|
|
141
157
|
vegh check backup.vegh
|
|
142
158
|
```
|
|
143
159
|
|
|
@@ -145,7 +161,7 @@ vegh check backup.vegh
|
|
|
145
161
|
|
|
146
162
|
Restore the snapshot to a target directory. Supports **Partial Restore**.
|
|
147
163
|
|
|
148
|
-
```
|
|
164
|
+
```shell
|
|
149
165
|
# Full restore
|
|
150
166
|
vegh restore backup.vegh ./restored-folder
|
|
151
167
|
|
|
@@ -160,7 +176,7 @@ vegh restore backup.vegh ./restored-folder --flatten
|
|
|
160
176
|
|
|
161
177
|
Inspect content without extracting.
|
|
162
178
|
|
|
163
|
-
```
|
|
179
|
+
```shell
|
|
164
180
|
# View a file's content inside the snapshot
|
|
165
181
|
vegh cat backup.vegh src/main.rs
|
|
166
182
|
|
|
@@ -168,6 +184,7 @@ vegh cat backup.vegh src/main.rs
|
|
|
168
184
|
vegh cat backup.vegh image.png --raw > extracted_image.png
|
|
169
185
|
|
|
170
186
|
# Compare snapshot with a directory
|
|
187
|
+
# (Automatically performs Blake3 Hash comparison if file sizes match)
|
|
171
188
|
vegh diff backup.vegh ./current-project
|
|
172
189
|
```
|
|
173
190
|
|
|
@@ -175,7 +192,7 @@ vegh diff backup.vegh ./current-project
|
|
|
175
192
|
|
|
176
193
|
Send the snapshot to a remote server. Supports **Chunked Uploads** for reliability.
|
|
177
194
|
|
|
178
|
-
```
|
|
195
|
+
```shell
|
|
179
196
|
# Auto-detects if chunking is needed, or force it:
|
|
180
197
|
vegh send backup.vegh --force-chunk
|
|
181
198
|
```
|
|
@@ -184,7 +201,7 @@ vegh send backup.vegh --force-chunk
|
|
|
184
201
|
|
|
185
202
|
Check your environment and installation health.
|
|
186
203
|
|
|
187
|
-
```
|
|
204
|
+
```shell
|
|
188
205
|
vegh doctor
|
|
189
206
|
```
|
|
190
207
|
|
|
@@ -194,21 +211,19 @@ Create a `.veghhooks.json` in your workspace.
|
|
|
194
211
|
|
|
195
212
|
```json
|
|
196
213
|
{
|
|
197
|
-
"
|
|
198
|
-
|
|
199
|
-
"echo '🚀 Pre-snap hook started...'",
|
|
200
|
-
"echo 'Preparing to backup...'",
|
|
201
|
-
"echo 'Backing up database...'",
|
|
202
|
-
"echo 'Backup completed.'"
|
|
203
|
-
],
|
|
204
|
-
"post": [
|
|
205
|
-
"echo '🎉 Snapshot completed!'",
|
|
206
|
-
"echo 'Cleaning up...'"
|
|
207
|
-
]
|
|
208
|
-
}
|
|
214
|
+
"pre": ["echo 'Checking...'", "ruff check -e"],
|
|
215
|
+
"post": ["echo 'Clean up...'"]
|
|
209
216
|
}
|
|
210
217
|
```
|
|
211
218
|
|
|
219
|
+
### 12\. Audit
|
|
220
|
+
|
|
221
|
+
Scan a snapshot for sensitive filenames and secrets.
|
|
222
|
+
|
|
223
|
+
```shell
|
|
224
|
+
vegh audit backup.vegh
|
|
225
|
+
```
|
|
226
|
+
|
|
212
227
|
## Library Usage
|
|
213
228
|
|
|
214
229
|
You can also use PyVegh as a library in your own Python scripts:
|
|
@@ -21,13 +21,16 @@ It delivers the raw performance of Rust (Zstd multithreaded compression, Tar arc
|
|
|
21
21
|
## Installation
|
|
22
22
|
|
|
23
23
|
Install directly from PyPI:
|
|
24
|
-
```
|
|
24
|
+
```shell
|
|
25
25
|
pip install pyvegh
|
|
26
|
+
|
|
27
|
+
# Or via uv
|
|
28
|
+
uv pip install pyvegh
|
|
26
29
|
```
|
|
27
30
|
|
|
28
31
|
Or build from source (requires Rust):
|
|
29
32
|
|
|
30
|
-
```
|
|
33
|
+
```shell
|
|
31
34
|
maturin develop --release
|
|
32
35
|
```
|
|
33
36
|
|
|
@@ -39,7 +42,7 @@ PyVegh provides a powerful command-line interface via the `vegh` (or `pyvegh`) c
|
|
|
39
42
|
|
|
40
43
|
Set up your default server URL and Auth Token so you don't have to type them every time.
|
|
41
44
|
|
|
42
|
-
```
|
|
45
|
+
```shell
|
|
43
46
|
vegh config
|
|
44
47
|
# Or one-liner:
|
|
45
48
|
vegh config send --url https://api.teaserverse.online/test --auth YOUR_TOKEN
|
|
@@ -51,11 +54,21 @@ vegh config list
|
|
|
51
54
|
vegh config reset
|
|
52
55
|
```
|
|
53
56
|
|
|
57
|
+
**Advanced:** You can also configure custom `audit` patterns in `~/.vegh/config.json`:
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"audit": {
|
|
61
|
+
"patterns": ["custom_secret\\.key", ".*\\.private"],
|
|
62
|
+
"keywords": ["MY_API_KEY", "INTERNAL_TOKEN"]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
54
67
|
### 2\. Create Snapshot
|
|
55
68
|
|
|
56
69
|
Pack a directory into a highly compressed snapshot.
|
|
57
70
|
|
|
58
|
-
```
|
|
71
|
+
```shell
|
|
59
72
|
# Basic snapshot
|
|
60
73
|
vegh snap ./my-project --output backup.vegh
|
|
61
74
|
|
|
@@ -67,7 +80,7 @@ vegh snap ./my-project --dry-run
|
|
|
67
80
|
|
|
68
81
|
View the Analytics Dashboard to break down your project by language and lines of code.
|
|
69
82
|
|
|
70
|
-
```
|
|
83
|
+
```shell
|
|
71
84
|
vegh loc backup.vegh
|
|
72
85
|
|
|
73
86
|
# Show Source Lines of Code (SLOC) instead of total LOC
|
|
@@ -78,7 +91,7 @@ vegh loc backup.vegh --sloc
|
|
|
78
91
|
### 4\. Prompt
|
|
79
92
|
|
|
80
93
|
Generate a structured XML context of your codebase to feed directly into ChatGPT, Claude, or Gemini.
|
|
81
|
-
```
|
|
94
|
+
```shell
|
|
82
95
|
# Generate XML context to stdout
|
|
83
96
|
vegh prompt .
|
|
84
97
|
|
|
@@ -97,10 +110,13 @@ vegh prompt . --clean --output context.xml
|
|
|
97
110
|
|
|
98
111
|
Clean up old snapshots to free disk space.
|
|
99
112
|
|
|
100
|
-
```
|
|
113
|
+
```shell
|
|
101
114
|
# Keep only the 5 most recent snapshots in the current directory
|
|
102
115
|
vegh prune --keep 5
|
|
103
116
|
|
|
117
|
+
# Delete snapshots older than 30 days (but always keep the 5 most recent)
|
|
118
|
+
vegh prune --older-than 30 --keep 5
|
|
119
|
+
|
|
104
120
|
# Force clean without confirmation (useful for CI/CD)
|
|
105
121
|
vegh prune --keep 1 --force
|
|
106
122
|
```
|
|
@@ -109,7 +125,7 @@ vegh prune --keep 1 --force
|
|
|
109
125
|
|
|
110
126
|
Check file integrity (Blake3) and view embedded metadata.
|
|
111
127
|
|
|
112
|
-
```
|
|
128
|
+
```shell
|
|
113
129
|
vegh check backup.vegh
|
|
114
130
|
```
|
|
115
131
|
|
|
@@ -117,7 +133,7 @@ vegh check backup.vegh
|
|
|
117
133
|
|
|
118
134
|
Restore the snapshot to a target directory. Supports **Partial Restore**.
|
|
119
135
|
|
|
120
|
-
```
|
|
136
|
+
```shell
|
|
121
137
|
# Full restore
|
|
122
138
|
vegh restore backup.vegh ./restored-folder
|
|
123
139
|
|
|
@@ -132,7 +148,7 @@ vegh restore backup.vegh ./restored-folder --flatten
|
|
|
132
148
|
|
|
133
149
|
Inspect content without extracting.
|
|
134
150
|
|
|
135
|
-
```
|
|
151
|
+
```shell
|
|
136
152
|
# View a file's content inside the snapshot
|
|
137
153
|
vegh cat backup.vegh src/main.rs
|
|
138
154
|
|
|
@@ -140,6 +156,7 @@ vegh cat backup.vegh src/main.rs
|
|
|
140
156
|
vegh cat backup.vegh image.png --raw > extracted_image.png
|
|
141
157
|
|
|
142
158
|
# Compare snapshot with a directory
|
|
159
|
+
# (Automatically performs Blake3 Hash comparison if file sizes match)
|
|
143
160
|
vegh diff backup.vegh ./current-project
|
|
144
161
|
```
|
|
145
162
|
|
|
@@ -147,7 +164,7 @@ vegh diff backup.vegh ./current-project
|
|
|
147
164
|
|
|
148
165
|
Send the snapshot to a remote server. Supports **Chunked Uploads** for reliability.
|
|
149
166
|
|
|
150
|
-
```
|
|
167
|
+
```shell
|
|
151
168
|
# Auto-detects if chunking is needed, or force it:
|
|
152
169
|
vegh send backup.vegh --force-chunk
|
|
153
170
|
```
|
|
@@ -156,7 +173,7 @@ vegh send backup.vegh --force-chunk
|
|
|
156
173
|
|
|
157
174
|
Check your environment and installation health.
|
|
158
175
|
|
|
159
|
-
```
|
|
176
|
+
```shell
|
|
160
177
|
vegh doctor
|
|
161
178
|
```
|
|
162
179
|
|
|
@@ -166,21 +183,19 @@ Create a `.veghhooks.json` in your workspace.
|
|
|
166
183
|
|
|
167
184
|
```json
|
|
168
185
|
{
|
|
169
|
-
"
|
|
170
|
-
|
|
171
|
-
"echo '🚀 Pre-snap hook started...'",
|
|
172
|
-
"echo 'Preparing to backup...'",
|
|
173
|
-
"echo 'Backing up database...'",
|
|
174
|
-
"echo 'Backup completed.'"
|
|
175
|
-
],
|
|
176
|
-
"post": [
|
|
177
|
-
"echo '🎉 Snapshot completed!'",
|
|
178
|
-
"echo 'Cleaning up...'"
|
|
179
|
-
]
|
|
180
|
-
}
|
|
186
|
+
"pre": ["echo 'Checking...'", "ruff check -e"],
|
|
187
|
+
"post": ["echo 'Clean up...'"]
|
|
181
188
|
}
|
|
182
189
|
```
|
|
183
190
|
|
|
191
|
+
### 12\. Audit
|
|
192
|
+
|
|
193
|
+
Scan a snapshot for sensitive filenames and secrets.
|
|
194
|
+
|
|
195
|
+
```shell
|
|
196
|
+
vegh audit backup.vegh
|
|
197
|
+
```
|
|
198
|
+
|
|
184
199
|
## Library Usage
|
|
185
200
|
|
|
186
201
|
You can also use PyVegh as a library in your own Python scripts:
|
|
@@ -9,9 +9,10 @@ from ._core import (
|
|
|
9
9
|
get_metadata,
|
|
10
10
|
count_locs,
|
|
11
11
|
scan_locs_dir,
|
|
12
|
+
read_snapshot_text,
|
|
12
13
|
)
|
|
13
14
|
|
|
14
|
-
__version__ = "0.
|
|
15
|
+
__version__ = "0.8.0"
|
|
15
16
|
__all__ = [
|
|
16
17
|
"create_snap",
|
|
17
18
|
"dry_run_snap",
|
|
@@ -20,5 +21,6 @@ __all__ = [
|
|
|
20
21
|
"get_metadata",
|
|
21
22
|
"count_locs",
|
|
22
23
|
"scan_locs_dir",
|
|
24
|
+
"read_snapshot_text",
|
|
23
25
|
"__version__",
|
|
24
26
|
]
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import List, Tuple, Dict
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.table import Table
|
|
5
|
+
from rich.panel import Panel
|
|
6
|
+
from rich.text import Text
|
|
7
|
+
from rich import box
|
|
8
|
+
from rich.layout import Layout
|
|
9
|
+
from rich.align import Align
|
|
10
|
+
|
|
11
|
+
from .jsonc import parse
|
|
12
|
+
|
|
13
|
+
# Load configuration from JSON
|
|
14
|
+
config_path = Path(__file__).parent / "config.jsonc"
|
|
15
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
16
|
+
config = parse(f.read())
|
|
17
|
+
|
|
18
|
+
LANG_MAP = {k: tuple(v) for k, v in config["LANG_MAP"].items()}
|
|
19
|
+
SLOC_IGNORE_EXTS = set(config["SLOC_IGNORE_EXTS"])
|
|
20
|
+
COMMENT_MAP = config["COMMENT_MAP"]
|
|
21
|
+
FILENAME_MAP = {k: tuple(v) for k, v in config["FILENAME_MAP"].items()}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ProjectStats:
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self.total_files = 0
|
|
27
|
+
self.total_loc = 0
|
|
28
|
+
self.lang_stats: Dict[str, Dict] = {}
|
|
29
|
+
|
|
30
|
+
def add_file(self, path_str: str, loc: int):
|
|
31
|
+
self.total_files += 1
|
|
32
|
+
self.total_loc += loc
|
|
33
|
+
|
|
34
|
+
path = Path(path_str)
|
|
35
|
+
# .lower() handles both .s and .S
|
|
36
|
+
ext = path.suffix.lower()
|
|
37
|
+
name = path.name.lower()
|
|
38
|
+
|
|
39
|
+
# Identify Language
|
|
40
|
+
lang, color = "Other", "white"
|
|
41
|
+
|
|
42
|
+
if name in FILENAME_MAP:
|
|
43
|
+
lang, color = FILENAME_MAP[name]
|
|
44
|
+
elif ext in LANG_MAP:
|
|
45
|
+
lang, color = LANG_MAP[ext]
|
|
46
|
+
|
|
47
|
+
# Update Stats
|
|
48
|
+
if lang not in self.lang_stats:
|
|
49
|
+
self.lang_stats[lang] = {"files": 0, "loc": 0, "color": color}
|
|
50
|
+
|
|
51
|
+
self.lang_stats[lang]["files"] += 1
|
|
52
|
+
self.lang_stats[lang]["loc"] += loc
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _make_bar(label: str, percent: float, color: str, width: int = 30) -> Text:
|
|
56
|
+
"""Manually renders a progress bar using unicode blocks."""
|
|
57
|
+
filled_len = int((percent / 100.0) * width)
|
|
58
|
+
unfilled_len = width - filled_len
|
|
59
|
+
|
|
60
|
+
bar_str = ("█" * filled_len) + ("░" * unfilled_len)
|
|
61
|
+
|
|
62
|
+
text = Text()
|
|
63
|
+
text.append(f"{label:<20}", style=f"bold {color}")
|
|
64
|
+
text.append(f"{bar_str} ", style=color)
|
|
65
|
+
text.append(f"{percent:>5.1f}%", style="bold white")
|
|
66
|
+
return text
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def scan_sloc(path: str) -> List[Tuple[str, int]]:
|
|
70
|
+
"""Scans a directory for SLOC, using core dry_run_snap for traversal."""
|
|
71
|
+
# We need to import dry_run_snap here or pass it in.
|
|
72
|
+
# To avoid circular imports, we'll try to import it inside the function
|
|
73
|
+
from ._core import dry_run_snap
|
|
74
|
+
|
|
75
|
+
results = []
|
|
76
|
+
|
|
77
|
+
# Use dry_run_snap to get the file list (respecting .gitignore/veghignore)
|
|
78
|
+
# dry_run_snap returns (path, size). We just want the path.
|
|
79
|
+
files = dry_run_snap(path)
|
|
80
|
+
|
|
81
|
+
base_path = Path(path)
|
|
82
|
+
|
|
83
|
+
for relative_path, _ in files:
|
|
84
|
+
full_path = base_path / relative_path
|
|
85
|
+
sloc = calculate_sloc(str(full_path))
|
|
86
|
+
results.append((relative_path, sloc))
|
|
87
|
+
|
|
88
|
+
return results
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def count_sloc_from_text(content: str, ext: str) -> int:
|
|
92
|
+
"""Core logic to count SLOC from a string content."""
|
|
93
|
+
if ext in SLOC_IGNORE_EXTS:
|
|
94
|
+
return 0
|
|
95
|
+
|
|
96
|
+
comment_prefix = COMMENT_MAP.get(ext)
|
|
97
|
+
count = 0
|
|
98
|
+
|
|
99
|
+
# Process line by line
|
|
100
|
+
for line in content.splitlines():
|
|
101
|
+
line = line.strip()
|
|
102
|
+
if not line:
|
|
103
|
+
continue
|
|
104
|
+
if comment_prefix and line.startswith(comment_prefix):
|
|
105
|
+
continue
|
|
106
|
+
count += 1
|
|
107
|
+
|
|
108
|
+
return count
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def calculate_sloc(file_path: str) -> int:
|
|
112
|
+
"""Calculates Source Lines of Code (SLOC) from a file on disk."""
|
|
113
|
+
path = Path(file_path)
|
|
114
|
+
ext = path.suffix.lower()
|
|
115
|
+
|
|
116
|
+
if ext in SLOC_IGNORE_EXTS:
|
|
117
|
+
return 0
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
# Check if file is binary
|
|
121
|
+
with open(file_path, "rb") as f:
|
|
122
|
+
chunk = f.read(512)
|
|
123
|
+
if b"\x00" in chunk:
|
|
124
|
+
return 0
|
|
125
|
+
|
|
126
|
+
# Read file with error handling
|
|
127
|
+
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
128
|
+
content = f.read()
|
|
129
|
+
return count_sloc_from_text(content, ext)
|
|
130
|
+
except Exception:
|
|
131
|
+
return 0
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def render_dashboard(
|
|
135
|
+
console: Console,
|
|
136
|
+
file_name: str,
|
|
137
|
+
raw_results: List[Tuple[str, int]],
|
|
138
|
+
metric_name: str = "LOC",
|
|
139
|
+
):
|
|
140
|
+
"""Draws the beautiful CodeTease Analytics Dashboard."""
|
|
141
|
+
|
|
142
|
+
# 1. Process Data
|
|
143
|
+
stats = ProjectStats()
|
|
144
|
+
for path, loc in raw_results:
|
|
145
|
+
if loc > 0:
|
|
146
|
+
stats.add_file(path, loc)
|
|
147
|
+
|
|
148
|
+
if stats.total_loc == 0:
|
|
149
|
+
console.print(
|
|
150
|
+
"[yellow]No code detected (or binary only). Is this a ghost project?[/yellow]"
|
|
151
|
+
)
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
sorted_langs = sorted(
|
|
155
|
+
stats.lang_stats.items(), key=lambda item: item[1]["loc"], reverse=True
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# 2. Build Layout
|
|
159
|
+
layout = Layout()
|
|
160
|
+
layout.split(
|
|
161
|
+
Layout(name="header", size=3),
|
|
162
|
+
Layout(name="body", ratio=1),
|
|
163
|
+
Layout(name="footer", size=3),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
layout["body"].split_row(
|
|
167
|
+
Layout(name="left", ratio=1), Layout(name="right", ratio=1)
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# --- Header ---
|
|
171
|
+
title_text = Text(
|
|
172
|
+
f"📊 Vegh Analytics ({metric_name}): {file_name}",
|
|
173
|
+
style="bold white on blue",
|
|
174
|
+
justify="center",
|
|
175
|
+
)
|
|
176
|
+
layout["header"].update(Panel(title_text, box=box.HEAVY))
|
|
177
|
+
|
|
178
|
+
# --- Left: Detailed Table ---
|
|
179
|
+
table = Table(box=box.SIMPLE_HEAD, expand=True)
|
|
180
|
+
table.add_column("Lang", style="bold")
|
|
181
|
+
table.add_column("Files", justify="right")
|
|
182
|
+
table.add_column(metric_name, justify="right", style="green")
|
|
183
|
+
table.add_column("%", justify="right")
|
|
184
|
+
|
|
185
|
+
for lang, data in sorted_langs:
|
|
186
|
+
percent = (data["loc"] / stats.total_loc) * 100
|
|
187
|
+
table.add_row(
|
|
188
|
+
f"[{data['color']}]{lang}[/{data['color']}]",
|
|
189
|
+
str(data["files"]),
|
|
190
|
+
f"{data['loc']:,}",
|
|
191
|
+
f"{percent:.1f}%",
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
layout["left"].update(
|
|
195
|
+
Panel(table, title="[bold]Breakdown[/bold]", border_style="cyan")
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# --- Right: Custom Manual Bar Chart ---
|
|
199
|
+
chart_content = Text()
|
|
200
|
+
|
|
201
|
+
# Take Top 12 languages
|
|
202
|
+
for i, (lang, data) in enumerate(sorted_langs[:12]):
|
|
203
|
+
percent = (data["loc"] / stats.total_loc) * 100
|
|
204
|
+
bar = _make_bar(lang, percent, data["color"])
|
|
205
|
+
chart_content.append(bar)
|
|
206
|
+
chart_content.append("\n")
|
|
207
|
+
|
|
208
|
+
if len(sorted_langs) > 12:
|
|
209
|
+
chart_content.append(
|
|
210
|
+
f"\n... and {len(sorted_langs) - 12} others", style="dim italic"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
layout["right"].update(
|
|
214
|
+
Panel(
|
|
215
|
+
Align.center(chart_content, vertical="middle"),
|
|
216
|
+
title="[bold]Distribution[/bold]",
|
|
217
|
+
border_style="green",
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# --- Footer: Summary & Fun Comment ---
|
|
222
|
+
if sorted_langs:
|
|
223
|
+
top_lang = sorted_langs[0][0]
|
|
224
|
+
else:
|
|
225
|
+
top_lang = "Other"
|
|
226
|
+
|
|
227
|
+
comment = "Code Hard, Play Hard! 🚀"
|
|
228
|
+
|
|
229
|
+
# Logic Fun Comment
|
|
230
|
+
if top_lang == "Rust":
|
|
231
|
+
comment = "Blazingly Fast! 🦀"
|
|
232
|
+
elif top_lang == "Python":
|
|
233
|
+
comment = "Snake Charmer! 🐍"
|
|
234
|
+
elif top_lang == "Haskell":
|
|
235
|
+
comment = "Purely Functional... and confusing! 😵💫"
|
|
236
|
+
elif top_lang == "Mojo":
|
|
237
|
+
comment = "AI Speedster! 🔥"
|
|
238
|
+
elif top_lang == "Solidity":
|
|
239
|
+
comment = "Wen Lambo? 🏎️"
|
|
240
|
+
elif top_lang == "Elixir":
|
|
241
|
+
comment = "Scalability God! 💜"
|
|
242
|
+
elif top_lang == "Astro":
|
|
243
|
+
comment = "To the stars! 🚀"
|
|
244
|
+
elif top_lang == "CSS":
|
|
245
|
+
comment = "Center a div? Good luck! 🎨"
|
|
246
|
+
elif "React" in top_lang:
|
|
247
|
+
comment = "Component Heaven! ⚛️"
|
|
248
|
+
elif top_lang in ["JavaScript", "TypeScript", "Vue", "Svelte"]:
|
|
249
|
+
comment = "Web Scale! 🌐"
|
|
250
|
+
elif top_lang in ["Assembly", "C", "C++"]:
|
|
251
|
+
comment = "Low Level Wizardry! 🧙♂️"
|
|
252
|
+
elif top_lang in ["FDON", "FWON", "BXSON"]:
|
|
253
|
+
comment = "Teasers! ⚡"
|
|
254
|
+
elif top_lang == "HTML":
|
|
255
|
+
comment = "How To Meet Ladies? 😉"
|
|
256
|
+
elif top_lang == "Go":
|
|
257
|
+
comment = "Gopher it! 🐹"
|
|
258
|
+
elif top_lang == "Java":
|
|
259
|
+
comment = "Enterprise Grade! ☕"
|
|
260
|
+
elif top_lang == "C#":
|
|
261
|
+
comment = "Microsoft Magic! 🪟"
|
|
262
|
+
elif top_lang == "PHP":
|
|
263
|
+
comment = "Elephant in the room! 🐘"
|
|
264
|
+
elif top_lang == "Swift":
|
|
265
|
+
comment = "Feeling Swift? 🍏"
|
|
266
|
+
elif top_lang == "Dart":
|
|
267
|
+
comment = "Fluttering away! 🐦"
|
|
268
|
+
elif top_lang == "SQL":
|
|
269
|
+
comment = "DROP TABLE production; 💀"
|
|
270
|
+
elif top_lang == "Terraform":
|
|
271
|
+
comment = "Infrastructure as Code! 🏗️"
|
|
272
|
+
elif top_lang == "Dockerfile":
|
|
273
|
+
comment = "Containerized! 🐳"
|
|
274
|
+
|
|
275
|
+
summary = f"[bold]Total {metric_name}:[/bold] [green]{stats.total_loc:,}[/green] | [bold]Analyzed Files:[/bold] {stats.total_files} | [italic]{comment}[/italic]"
|
|
276
|
+
|
|
277
|
+
layout["footer"].update(
|
|
278
|
+
Panel(Text.from_markup(summary, justify="center"), border_style="blue")
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
console.print(layout)
|