pyvegh 0.8.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.8.0 → pyvegh-0.9.0}/Cargo.lock +1 -1
- {pyvegh-0.8.0 → pyvegh-0.9.0}/Cargo.toml +1 -1
- {pyvegh-0.8.0 → pyvegh-0.9.0}/PKG-INFO +23 -1
- {pyvegh-0.8.0 → pyvegh-0.9.0}/README.md +22 -0
- {pyvegh-0.8.0 → pyvegh-0.9.0}/pyproject.toml +1 -1
- {pyvegh-0.8.0 → pyvegh-0.9.0}/python/vegh/analytics.py +2 -2
- {pyvegh-0.8.0 → pyvegh-0.9.0}/python/vegh/cli.py +2 -1
- {pyvegh-0.8.0 → pyvegh-0.9.0}/python/vegh/cli_commands.py +112 -28
- {pyvegh-0.8.0 → pyvegh-0.9.0}/python/vegh/cli_helpers.py +2 -2
- {pyvegh-0.8.0 → pyvegh-0.9.0}/python/vegh/cli_hooks.py +3 -2
- {pyvegh-0.8.0 → pyvegh-0.9.0}/python/vegh/cli_main.py +4 -31
- {pyvegh-0.8.0 → pyvegh-0.9.0}/python/vegh/jsonc.py +1 -1
- {pyvegh-0.8.0 → pyvegh-0.9.0}/src/core.rs +5 -3
- {pyvegh-0.8.0 → pyvegh-0.9.0}/src/lib.rs +20 -3
- {pyvegh-0.8.0 → pyvegh-0.9.0}/.github/workflows/ci.yml +0 -0
- {pyvegh-0.8.0 → pyvegh-0.9.0}/.github/workflows/release.yml +0 -0
- {pyvegh-0.8.0 → pyvegh-0.9.0}/.github/workflows/rust-clippy.yml +0 -0
- {pyvegh-0.8.0 → pyvegh-0.9.0}/.gitignore +0 -0
- {pyvegh-0.8.0 → pyvegh-0.9.0}/LICENSE +0 -0
- {pyvegh-0.8.0 → pyvegh-0.9.0}/python/vegh/__init__.py +0 -0
- {pyvegh-0.8.0 → pyvegh-0.9.0}/python/vegh/cli_config.py +0 -0
- {pyvegh-0.8.0 → pyvegh-0.9.0}/python/vegh/cli_repo.py +0 -0
- {pyvegh-0.8.0 → pyvegh-0.9.0}/python/vegh/config.jsonc +0 -0
- {pyvegh-0.8.0 → pyvegh-0.9.0}/src/hash.rs +0 -0
- {pyvegh-0.8.0 → pyvegh-0.9.0}/src/storage.rs +0 -0
- {pyvegh-0.8.0 → pyvegh-0.9.0}/tests/integration_test.sh +0 -0
- {pyvegh-0.8.0 → pyvegh-0.9.0}/tests/test_smoke.py +0 -0
- {pyvegh-0.8.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
|
|
@@ -82,6 +82,16 @@ vegh config list
|
|
|
82
82
|
vegh config reset
|
|
83
83
|
```
|
|
84
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
|
+
|
|
85
95
|
### 2\. Create Snapshot
|
|
86
96
|
|
|
87
97
|
Pack a directory into a highly compressed snapshot.
|
|
@@ -132,6 +142,9 @@ Clean up old snapshots to free disk space.
|
|
|
132
142
|
# Keep only the 5 most recent snapshots in the current directory
|
|
133
143
|
vegh prune --keep 5
|
|
134
144
|
|
|
145
|
+
# Delete snapshots older than 30 days (but always keep the 5 most recent)
|
|
146
|
+
vegh prune --older-than 30 --keep 5
|
|
147
|
+
|
|
135
148
|
# Force clean without confirmation (useful for CI/CD)
|
|
136
149
|
vegh prune --keep 1 --force
|
|
137
150
|
```
|
|
@@ -171,6 +184,7 @@ vegh cat backup.vegh src/main.rs
|
|
|
171
184
|
vegh cat backup.vegh image.png --raw > extracted_image.png
|
|
172
185
|
|
|
173
186
|
# Compare snapshot with a directory
|
|
187
|
+
# (Automatically performs Blake3 Hash comparison if file sizes match)
|
|
174
188
|
vegh diff backup.vegh ./current-project
|
|
175
189
|
```
|
|
176
190
|
|
|
@@ -202,6 +216,14 @@ Create a `.veghhooks.json` in your workspace.
|
|
|
202
216
|
}
|
|
203
217
|
```
|
|
204
218
|
|
|
219
|
+
### 12\. Audit
|
|
220
|
+
|
|
221
|
+
Scan a snapshot for sensitive filenames and secrets.
|
|
222
|
+
|
|
223
|
+
```shell
|
|
224
|
+
vegh audit backup.vegh
|
|
225
|
+
```
|
|
226
|
+
|
|
205
227
|
## Library Usage
|
|
206
228
|
|
|
207
229
|
You can also use PyVegh as a library in your own Python scripts:
|
|
@@ -54,6 +54,16 @@ vegh config list
|
|
|
54
54
|
vegh config reset
|
|
55
55
|
```
|
|
56
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
|
+
|
|
57
67
|
### 2\. Create Snapshot
|
|
58
68
|
|
|
59
69
|
Pack a directory into a highly compressed snapshot.
|
|
@@ -104,6 +114,9 @@ Clean up old snapshots to free disk space.
|
|
|
104
114
|
# Keep only the 5 most recent snapshots in the current directory
|
|
105
115
|
vegh prune --keep 5
|
|
106
116
|
|
|
117
|
+
# Delete snapshots older than 30 days (but always keep the 5 most recent)
|
|
118
|
+
vegh prune --older-than 30 --keep 5
|
|
119
|
+
|
|
107
120
|
# Force clean without confirmation (useful for CI/CD)
|
|
108
121
|
vegh prune --keep 1 --force
|
|
109
122
|
```
|
|
@@ -143,6 +156,7 @@ vegh cat backup.vegh src/main.rs
|
|
|
143
156
|
vegh cat backup.vegh image.png --raw > extracted_image.png
|
|
144
157
|
|
|
145
158
|
# Compare snapshot with a directory
|
|
159
|
+
# (Automatically performs Blake3 Hash comparison if file sizes match)
|
|
146
160
|
vegh diff backup.vegh ./current-project
|
|
147
161
|
```
|
|
148
162
|
|
|
@@ -174,6 +188,14 @@ Create a `.veghhooks.json` in your workspace.
|
|
|
174
188
|
}
|
|
175
189
|
```
|
|
176
190
|
|
|
191
|
+
### 12\. Audit
|
|
192
|
+
|
|
193
|
+
Scan a snapshot for sensitive filenames and secrets.
|
|
194
|
+
|
|
195
|
+
```shell
|
|
196
|
+
vegh audit backup.vegh
|
|
197
|
+
```
|
|
198
|
+
|
|
177
199
|
## Library Usage
|
|
178
200
|
|
|
179
201
|
You can also use PyVegh as a library in your own Python scripts:
|
|
@@ -120,9 +120,9 @@ def calculate_sloc(file_path: str) -> int:
|
|
|
120
120
|
# Check if file is binary
|
|
121
121
|
with open(file_path, "rb") as f:
|
|
122
122
|
chunk = f.read(512)
|
|
123
|
-
if b
|
|
123
|
+
if b"\x00" in chunk:
|
|
124
124
|
return 0
|
|
125
|
-
|
|
125
|
+
|
|
126
126
|
# Read file with error handling
|
|
127
127
|
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
128
128
|
content = f.read()
|
|
@@ -14,7 +14,8 @@ from rich.table import Table
|
|
|
14
14
|
from rich.panel import Panel
|
|
15
15
|
from rich.prompt import Prompt, Confirm
|
|
16
16
|
|
|
17
|
-
from .cli_main import app
|
|
17
|
+
from .cli_main import app
|
|
18
|
+
from ._core import create_snap, dry_run_snap
|
|
18
19
|
from .cli_helpers import (
|
|
19
20
|
console,
|
|
20
21
|
format_bytes,
|
|
@@ -44,6 +45,7 @@ from ._core import (
|
|
|
44
45
|
get_context_xml,
|
|
45
46
|
search_snap,
|
|
46
47
|
read_snapshot_text,
|
|
48
|
+
hash_file,
|
|
47
49
|
)
|
|
48
50
|
from .analytics import render_dashboard, scan_sloc, calculate_sloc, count_sloc_from_text
|
|
49
51
|
|
|
@@ -56,6 +58,9 @@ def prune(
|
|
|
56
58
|
keep: int = typer.Option(
|
|
57
59
|
5, "--keep", "-k", help="Number of recent snapshots to keep"
|
|
58
60
|
),
|
|
61
|
+
older_than: Optional[int] = typer.Option(
|
|
62
|
+
None, "--older-than", help="Delete snapshots older than X days"
|
|
63
|
+
),
|
|
59
64
|
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
|
|
60
65
|
):
|
|
61
66
|
"""Clean up old snapshots, keeping only the most recent ones."""
|
|
@@ -67,18 +72,36 @@ def prune(
|
|
|
67
72
|
target_dir.glob("*.vegh"), key=lambda f: f.stat().st_mtime, reverse=True
|
|
68
73
|
)
|
|
69
74
|
|
|
70
|
-
|
|
75
|
+
delete_list = []
|
|
76
|
+
|
|
77
|
+
if older_than is not None:
|
|
78
|
+
cutoff = time.time() - (older_than * 86400)
|
|
79
|
+
# Identify files older than cutoff
|
|
80
|
+
time_candidates = [s for s in snapshots if s.stat().st_mtime < cutoff]
|
|
81
|
+
|
|
82
|
+
# Ensure we keep at least 'keep' snapshots (the most recent ones)
|
|
83
|
+
safe_set = set(snapshots[:keep])
|
|
84
|
+
delete_list = [s for s in time_candidates if s not in safe_set]
|
|
85
|
+
|
|
71
86
|
console.print(
|
|
72
|
-
f"[
|
|
87
|
+
f"[cyan]Policy: Delete older than {older_than} days (except top {keep}).[/cyan]"
|
|
73
88
|
)
|
|
74
|
-
|
|
89
|
+
else:
|
|
90
|
+
if len(snapshots) <= keep:
|
|
91
|
+
console.print(
|
|
92
|
+
f"[green]No cleanup needed. Found {len(snapshots)} snapshots (Keep: {keep}).[/green]"
|
|
93
|
+
)
|
|
94
|
+
return
|
|
75
95
|
|
|
76
|
-
|
|
77
|
-
delete_list = snapshots[keep:]
|
|
96
|
+
delete_list = snapshots[keep:]
|
|
78
97
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
98
|
+
console.print(
|
|
99
|
+
f"[bold cyan]Found {len(snapshots)} snapshots. Keeping {keep} most recent.[/bold cyan]"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if not delete_list:
|
|
103
|
+
console.print("[green]No snapshots match deletion criteria.[/green]")
|
|
104
|
+
return
|
|
82
105
|
|
|
83
106
|
table = Table(title="Snapshots to Delete")
|
|
84
107
|
table.add_column("File", style="red")
|
|
@@ -336,15 +359,20 @@ def diff(
|
|
|
336
359
|
repo_path, source_name = ensure_repo(repo, branch, offline)
|
|
337
360
|
source_name = f"Repo: {source_name}"
|
|
338
361
|
snap_list = dry_run_snap(str(repo_path))
|
|
339
|
-
snap_map = {
|
|
362
|
+
snap_map = {
|
|
363
|
+
Path(p).as_posix(): {"size": s, "hash": None} for p, s in snap_list
|
|
364
|
+
}
|
|
340
365
|
elif file:
|
|
341
366
|
if not file.exists():
|
|
342
367
|
console.print(f"[red]File '{file}' not found.[/red]")
|
|
343
368
|
raise typer.Exit(1)
|
|
344
369
|
source_name = f"Snap: {file.name}"
|
|
345
370
|
snap_files = list_files_details(str(file))
|
|
371
|
+
# list_files_details now returns (path, size, hash)
|
|
346
372
|
snap_map = {
|
|
347
|
-
Path(p).as_posix(): s
|
|
373
|
+
Path(p).as_posix(): {"size": s, "hash": h}
|
|
374
|
+
for p, s, h in snap_files
|
|
375
|
+
if p != ".vegh.json"
|
|
348
376
|
}
|
|
349
377
|
else:
|
|
350
378
|
console.print(
|
|
@@ -355,11 +383,15 @@ def diff(
|
|
|
355
383
|
if target_is_snap:
|
|
356
384
|
target_files = list_files_details(str(target))
|
|
357
385
|
local_files = {
|
|
358
|
-
Path(p).as_posix(): s
|
|
386
|
+
Path(p).as_posix(): {"size": s, "hash": h}
|
|
387
|
+
for p, s, h in target_files
|
|
388
|
+
if p != ".vegh.json"
|
|
359
389
|
}
|
|
360
390
|
else:
|
|
361
391
|
local_list = dry_run_snap(str(target))
|
|
362
|
-
local_files = {
|
|
392
|
+
local_files = {
|
|
393
|
+
Path(p).as_posix(): {"size": s, "hash": None} for p, s in local_list
|
|
394
|
+
}
|
|
363
395
|
except Exception as e:
|
|
364
396
|
console.print(f"[red]Error:[/red] {e}")
|
|
365
397
|
raise typer.Exit(1)
|
|
@@ -376,11 +408,48 @@ def diff(
|
|
|
376
408
|
in_loc = path in local_files
|
|
377
409
|
|
|
378
410
|
if in_src and in_loc:
|
|
379
|
-
|
|
411
|
+
src_info = snap_map[path]
|
|
412
|
+
loc_info = local_files[path]
|
|
413
|
+
src_size = src_info["size"]
|
|
414
|
+
loc_size = loc_info["size"]
|
|
415
|
+
|
|
416
|
+
modified = False
|
|
417
|
+
details = ""
|
|
418
|
+
|
|
419
|
+
if src_size != loc_size:
|
|
420
|
+
modified = True
|
|
421
|
+
details = f"Size: {format_bytes(src_size)} -> {format_bytes(loc_size)}"
|
|
422
|
+
else:
|
|
423
|
+
# Same size, check content via Hash
|
|
424
|
+
src_hash = src_info.get("hash")
|
|
425
|
+
|
|
426
|
+
if src_hash: # Only if source is snapshot (or has hash)
|
|
427
|
+
loc_hash = loc_info.get("hash")
|
|
428
|
+
|
|
429
|
+
if loc_hash:
|
|
430
|
+
# Target is also snapshot, compare hashes directly
|
|
431
|
+
if src_hash != loc_hash:
|
|
432
|
+
modified = True
|
|
433
|
+
details = "Content Changed (Hash mismatch)"
|
|
434
|
+
elif not target_is_snap:
|
|
435
|
+
# Target is local directory, compute hash on demand
|
|
436
|
+
try:
|
|
437
|
+
full_local_path = target / path
|
|
438
|
+
if full_local_path.exists():
|
|
439
|
+
computed = hash_file(str(full_local_path))
|
|
440
|
+
if computed != src_hash:
|
|
441
|
+
modified = True
|
|
442
|
+
details = "Content Changed (Hash mismatch)"
|
|
443
|
+
except Exception:
|
|
444
|
+
# If hashing fails (permissions etc), assume unmodified or warn?
|
|
445
|
+
# For now, if we can't read it, we rely on size (which matched).
|
|
446
|
+
pass
|
|
447
|
+
|
|
448
|
+
if modified:
|
|
380
449
|
table.add_row(
|
|
381
450
|
path,
|
|
382
451
|
"[yellow]MODIFIED[/yellow]",
|
|
383
|
-
|
|
452
|
+
details,
|
|
384
453
|
)
|
|
385
454
|
changes = True
|
|
386
455
|
elif in_src and not in_loc:
|
|
@@ -417,6 +486,24 @@ def audit(
|
|
|
417
486
|
|
|
418
487
|
console.print(f"[bold cyan]Auditing {file.name}...[/bold cyan]")
|
|
419
488
|
|
|
489
|
+
# Load custom audit config
|
|
490
|
+
cfg = load_config()
|
|
491
|
+
audit_cfg = cfg.get("audit", {})
|
|
492
|
+
custom_patterns = audit_cfg.get("patterns", [])
|
|
493
|
+
custom_keywords = audit_cfg.get("keywords", [])
|
|
494
|
+
|
|
495
|
+
final_patterns = SENSITIVE_PATTERNS + custom_patterns
|
|
496
|
+
|
|
497
|
+
secret_keywords = [
|
|
498
|
+
"PASSWORD",
|
|
499
|
+
"SECRET_KEY",
|
|
500
|
+
"TOKEN",
|
|
501
|
+
"API_KEY",
|
|
502
|
+
"ACCESS_KEY",
|
|
503
|
+
"PRIVATE_KEY",
|
|
504
|
+
]
|
|
505
|
+
final_keywords = secret_keywords + custom_keywords
|
|
506
|
+
|
|
420
507
|
risks = []
|
|
421
508
|
|
|
422
509
|
try:
|
|
@@ -424,9 +511,14 @@ def audit(
|
|
|
424
511
|
|
|
425
512
|
# 1. Filename Scan
|
|
426
513
|
for path in files:
|
|
427
|
-
for pattern in
|
|
428
|
-
|
|
429
|
-
|
|
514
|
+
for pattern in final_patterns:
|
|
515
|
+
try:
|
|
516
|
+
if re.search(pattern, path, re.IGNORECASE):
|
|
517
|
+
risks.append((path, "Filename Match", f"Pattern: {pattern}"))
|
|
518
|
+
except re.error:
|
|
519
|
+
console.print(
|
|
520
|
+
f"[yellow]Warning: Invalid regex pattern '{pattern}' in config[/yellow]"
|
|
521
|
+
)
|
|
430
522
|
|
|
431
523
|
# 2. Content Scan (Config files only)
|
|
432
524
|
# Scan for common secrets inside textual config files
|
|
@@ -440,14 +532,6 @@ def audit(
|
|
|
440
532
|
".ini",
|
|
441
533
|
".xml",
|
|
442
534
|
}
|
|
443
|
-
secret_keywords = [
|
|
444
|
-
"PASSWORD",
|
|
445
|
-
"SECRET_KEY",
|
|
446
|
-
"TOKEN",
|
|
447
|
-
"API_KEY",
|
|
448
|
-
"ACCESS_KEY",
|
|
449
|
-
"PRIVATE_KEY",
|
|
450
|
-
]
|
|
451
535
|
|
|
452
536
|
for path in files:
|
|
453
537
|
p = Path(path)
|
|
@@ -457,7 +541,7 @@ def audit(
|
|
|
457
541
|
content_bytes = cat_file(str(file), path)
|
|
458
542
|
try:
|
|
459
543
|
content = bytes(content_bytes).decode("utf-8")
|
|
460
|
-
for keyword in
|
|
544
|
+
for keyword in final_keywords:
|
|
461
545
|
if keyword in content:
|
|
462
546
|
risks.append(
|
|
463
547
|
(path, "Content Match", f"Found keyword: {keyword}")
|
|
@@ -504,7 +588,7 @@ def doctor(
|
|
|
504
588
|
console.print("Config: [dim]Not configured[/dim]")
|
|
505
589
|
|
|
506
590
|
try:
|
|
507
|
-
from . import _core
|
|
591
|
+
from . import _core # noqa: F401
|
|
508
592
|
|
|
509
593
|
console.print("Rust Core: [green]Loaded[/green]")
|
|
510
594
|
except ImportError:
|
|
@@ -35,7 +35,7 @@ def load_config() -> Dict:
|
|
|
35
35
|
try:
|
|
36
36
|
with open(CONFIG_FILE, "r") as f:
|
|
37
37
|
return json.load(f)
|
|
38
|
-
except:
|
|
38
|
+
except Exception:
|
|
39
39
|
pass
|
|
40
40
|
return {}
|
|
41
41
|
|
|
@@ -65,7 +65,7 @@ def get_dir_size(path: Path) -> int:
|
|
|
65
65
|
for entry in path.rglob("*"):
|
|
66
66
|
if entry.is_file():
|
|
67
67
|
total += entry.stat().st_size
|
|
68
|
-
except:
|
|
68
|
+
except Exception:
|
|
69
69
|
pass
|
|
70
70
|
return total
|
|
71
71
|
|
|
@@ -22,8 +22,9 @@ def execute_hooks(commands: List[str], hook_name: str) -> bool:
|
|
|
22
22
|
return True
|
|
23
23
|
|
|
24
24
|
# Just a friendly warning for the unsuspecting user
|
|
25
|
-
console.print(
|
|
26
|
-
|
|
25
|
+
console.print(
|
|
26
|
+
f"[bold yellow]⚠ Running {hook_name} hooks from project config...[/bold yellow]"
|
|
27
|
+
)
|
|
27
28
|
|
|
28
29
|
console.print(f"[bold magenta]>>> HOOK: {hook_name}[/bold magenta]")
|
|
29
30
|
env = os.environ.copy()
|
|
@@ -3,6 +3,9 @@ from rich.console import Console
|
|
|
3
3
|
|
|
4
4
|
import typer
|
|
5
5
|
|
|
6
|
+
# Add sub-apps
|
|
7
|
+
from .cli_config import config_app
|
|
8
|
+
|
|
6
9
|
# Try to import package version metadata (Modern Pythonic way)
|
|
7
10
|
try:
|
|
8
11
|
from importlib.metadata import version as get_package_version, PackageNotFoundError
|
|
@@ -13,38 +16,11 @@ except ImportError:
|
|
|
13
16
|
|
|
14
17
|
# Import core functionality
|
|
15
18
|
try:
|
|
16
|
-
from .
|
|
17
|
-
create_snap,
|
|
18
|
-
dry_run_snap,
|
|
19
|
-
restore_snap,
|
|
20
|
-
check_integrity,
|
|
21
|
-
list_files,
|
|
22
|
-
get_metadata,
|
|
23
|
-
count_locs,
|
|
24
|
-
scan_locs_dir,
|
|
25
|
-
cat_file,
|
|
26
|
-
list_files_details,
|
|
27
|
-
get_context_xml,
|
|
28
|
-
search_snap,
|
|
29
|
-
read_snapshot_text,
|
|
30
|
-
)
|
|
19
|
+
from . import _core # noqa: F401
|
|
31
20
|
except ImportError:
|
|
32
21
|
print("Error: Rust core missing. Run 'maturin develop'!")
|
|
33
22
|
exit(1)
|
|
34
23
|
|
|
35
|
-
# Import Analytics module
|
|
36
|
-
try:
|
|
37
|
-
from .analytics import (
|
|
38
|
-
render_dashboard,
|
|
39
|
-
scan_sloc,
|
|
40
|
-
calculate_sloc,
|
|
41
|
-
count_sloc_from_text,
|
|
42
|
-
)
|
|
43
|
-
except ImportError:
|
|
44
|
-
render_dashboard = None
|
|
45
|
-
scan_sloc = None
|
|
46
|
-
calculate_sloc = None
|
|
47
|
-
|
|
48
24
|
# Define context settings to enable '-h' alongside '--help'
|
|
49
25
|
CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}
|
|
50
26
|
|
|
@@ -92,7 +68,4 @@ def main(
|
|
|
92
68
|
pass
|
|
93
69
|
|
|
94
70
|
|
|
95
|
-
# Add sub-apps
|
|
96
|
-
from .cli_config import config_app
|
|
97
|
-
|
|
98
71
|
app.add_typer(config_app, name="config")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# A simple JSONC parser, written to avoid extra dependencies.
|
|
2
2
|
# It removes comments from JSONC strings and parses the result as JSON.
|
|
3
|
-
# Use internally only, so we probably don't need advance parsing features.
|
|
3
|
+
# Use internally only, so we probably don't need advance parsing features.
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
import re
|
|
@@ -39,7 +39,7 @@ pub struct VeghMetadata {
|
|
|
39
39
|
|
|
40
40
|
// Pipeline Messages
|
|
41
41
|
enum WorkerResult {
|
|
42
|
-
Processed(ProcessedMessage),
|
|
42
|
+
Processed(Box<ProcessedMessage>),
|
|
43
43
|
Error(String),
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -66,6 +66,7 @@ enum DataAction {
|
|
|
66
66
|
|
|
67
67
|
// --- Main Packing Logic ---
|
|
68
68
|
|
|
69
|
+
#[allow(clippy::too_many_arguments)]
|
|
69
70
|
pub fn create_snap_logic(
|
|
70
71
|
source: &Path,
|
|
71
72
|
output: &Path,
|
|
@@ -355,7 +356,7 @@ pub fn create_snap_logic(
|
|
|
355
356
|
|
|
356
357
|
match process_res {
|
|
357
358
|
Ok(msg) => {
|
|
358
|
-
let _ = tx.send(WorkerResult::Processed(msg));
|
|
359
|
+
let _ = tx.send(WorkerResult::Processed(Box::new(msg)));
|
|
359
360
|
}
|
|
360
361
|
Err(e) => {
|
|
361
362
|
let _ = tx.send(WorkerResult::Error(e.to_string()));
|
|
@@ -384,7 +385,8 @@ pub fn create_snap_logic(
|
|
|
384
385
|
eprintln!("Error: {}", e);
|
|
385
386
|
}
|
|
386
387
|
}
|
|
387
|
-
WorkerResult::Processed(
|
|
388
|
+
WorkerResult::Processed(pm_box) => {
|
|
389
|
+
let pm = *pm_box;
|
|
388
390
|
if pm.is_cached_hit {
|
|
389
391
|
cache_hit_count += 1;
|
|
390
392
|
}
|
|
@@ -119,6 +119,7 @@ fn read_snapshot_text(file_path: String) -> PyResult<Vec<(String, String)>> {
|
|
|
119
119
|
|
|
120
120
|
#[pyfunction]
|
|
121
121
|
#[pyo3(signature = (source, output, level=3, comment=None, include=None, exclude=None, no_cache=false, verbose=true))]
|
|
122
|
+
#[allow(clippy::too_many_arguments)]
|
|
122
123
|
fn create_snap(
|
|
123
124
|
source: String,
|
|
124
125
|
output: String,
|
|
@@ -528,7 +529,7 @@ fn scan_locs_dir(source: String, exclude: Option<Vec<String>>) -> PyResult<Vec<(
|
|
|
528
529
|
}
|
|
529
530
|
|
|
530
531
|
#[pyfunction]
|
|
531
|
-
fn list_files_details(file_path: String) -> PyResult<Vec<(String, u64)>> {
|
|
532
|
+
fn list_files_details(file_path: String) -> PyResult<Vec<(String, u64, String)>> {
|
|
532
533
|
let file = File::open(&file_path).map_err(|e| PyIOError::new_err(e.to_string()))?;
|
|
533
534
|
let decoder = zstd::stream::read::Decoder::new(file).unwrap();
|
|
534
535
|
let mut archive = tar::Archive::new(decoder);
|
|
@@ -548,7 +549,7 @@ fn list_files_details(file_path: String) -> PyResult<Vec<(String, u64)>> {
|
|
|
548
549
|
return Ok(manifest
|
|
549
550
|
.entries
|
|
550
551
|
.into_iter()
|
|
551
|
-
.map(|en| (en.path, en.size))
|
|
552
|
+
.map(|en| (en.path, en.size, en.hash))
|
|
552
553
|
.collect());
|
|
553
554
|
}
|
|
554
555
|
}
|
|
@@ -557,13 +558,28 @@ fn list_files_details(file_path: String) -> PyResult<Vec<(String, u64)>> {
|
|
|
557
558
|
&& path_str != ".vegh.json"
|
|
558
559
|
&& path_str != "manifest.json"
|
|
559
560
|
{
|
|
560
|
-
results.push((path_str, size));
|
|
561
|
+
results.push((path_str, size, String::new()));
|
|
561
562
|
}
|
|
562
563
|
}
|
|
563
564
|
}
|
|
564
565
|
Ok(results)
|
|
565
566
|
}
|
|
566
567
|
|
|
568
|
+
#[pyfunction]
|
|
569
|
+
fn hash_file(file_path: String) -> PyResult<String> {
|
|
570
|
+
let file = File::open(&file_path).map_err(|e| PyIOError::new_err(e.to_string()))?;
|
|
571
|
+
let mut hasher = blake3::Hasher::new();
|
|
572
|
+
|
|
573
|
+
if let Ok(mmap) = unsafe { memmap2::MmapOptions::new().map(&file) } {
|
|
574
|
+
hasher.update_rayon(&mmap);
|
|
575
|
+
} else {
|
|
576
|
+
let mut f = File::open(&file_path).map_err(|e| PyIOError::new_err(e.to_string()))?;
|
|
577
|
+
std::io::copy(&mut f, &mut hasher).map_err(|e| PyIOError::new_err(e.to_string()))?;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
Ok(hasher.finalize().to_hex().to_string())
|
|
581
|
+
}
|
|
582
|
+
|
|
567
583
|
#[pymodule]
|
|
568
584
|
#[pyo3(name = "_core")]
|
|
569
585
|
fn pyvegh_core(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
@@ -580,5 +596,6 @@ fn pyvegh_core(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
|
580
596
|
m.add_function(wrap_pyfunction!(search_snap, m)?)?;
|
|
581
597
|
m.add_function(wrap_pyfunction!(count_locs, m)?)?;
|
|
582
598
|
m.add_function(wrap_pyfunction!(read_snapshot_text, m)?)?;
|
|
599
|
+
m.add_function(wrap_pyfunction!(hash_file, m)?)?;
|
|
583
600
|
Ok(())
|
|
584
601
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|