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.
Files changed (29) hide show
  1. {pyvegh-0.7.0 → pyvegh-0.9.0}/Cargo.lock +1 -1
  2. {pyvegh-0.7.0 → pyvegh-0.9.0}/Cargo.toml +1 -1
  3. {pyvegh-0.7.0 → pyvegh-0.9.0}/PKG-INFO +40 -25
  4. {pyvegh-0.7.0 → pyvegh-0.9.0}/README.md +39 -24
  5. {pyvegh-0.7.0 → pyvegh-0.9.0}/pyproject.toml +1 -1
  6. {pyvegh-0.7.0 → pyvegh-0.9.0}/python/vegh/__init__.py +3 -1
  7. pyvegh-0.9.0/python/vegh/analytics.py +281 -0
  8. pyvegh-0.9.0/python/vegh/cli.py +7 -0
  9. pyvegh-0.7.0/python/vegh/cli.py → pyvegh-0.9.0/python/vegh/cli_commands.py +226 -608
  10. pyvegh-0.9.0/python/vegh/cli_config.py +81 -0
  11. pyvegh-0.9.0/python/vegh/cli_helpers.py +124 -0
  12. pyvegh-0.9.0/python/vegh/cli_hooks.py +49 -0
  13. pyvegh-0.9.0/python/vegh/cli_main.py +71 -0
  14. pyvegh-0.9.0/python/vegh/cli_repo.py +90 -0
  15. pyvegh-0.9.0/python/vegh/config.jsonc +1116 -0
  16. pyvegh-0.9.0/python/vegh/jsonc.py +53 -0
  17. {pyvegh-0.7.0 → pyvegh-0.9.0}/src/core.rs +16 -12
  18. {pyvegh-0.7.0 → pyvegh-0.9.0}/src/lib.rs +68 -22
  19. {pyvegh-0.7.0 → pyvegh-0.9.0}/src/storage.rs +39 -37
  20. pyvegh-0.7.0/python/vegh/analytics.py +0 -621
  21. {pyvegh-0.7.0 → pyvegh-0.9.0}/.github/workflows/ci.yml +0 -0
  22. {pyvegh-0.7.0 → pyvegh-0.9.0}/.github/workflows/release.yml +0 -0
  23. {pyvegh-0.7.0 → pyvegh-0.9.0}/.github/workflows/rust-clippy.yml +0 -0
  24. {pyvegh-0.7.0 → pyvegh-0.9.0}/.gitignore +0 -0
  25. {pyvegh-0.7.0 → pyvegh-0.9.0}/LICENSE +0 -0
  26. {pyvegh-0.7.0 → pyvegh-0.9.0}/src/hash.rs +0 -0
  27. {pyvegh-0.7.0 → pyvegh-0.9.0}/tests/integration_test.sh +0 -0
  28. {pyvegh-0.7.0 → pyvegh-0.9.0}/tests/test_smoke.py +0 -0
  29. {pyvegh-0.7.0 → pyvegh-0.9.0}/uv.lock +0 -0
@@ -540,7 +540,7 @@ dependencies = [
540
540
 
541
541
  [[package]]
542
542
  name = "pyvegh"
543
- version = "0.7.0"
543
+ version = "0.9.0"
544
544
  dependencies = [
545
545
  "anyhow",
546
546
  "bincode",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "pyvegh"
3
- version = "0.7.0"
3
+ version = "0.9.0"
4
4
  edition = "2024"
5
5
  authors = ["CodeTease"]
6
6
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyvegh
3
- Version: 0.7.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
- ```bash
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
- ```bash
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
- ```bash
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
- ```bash
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
- ```bash
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
- ```bash
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
- ```bash
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
- ```bash
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
- ```bash
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
- ```bash
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
- ```bash
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
- ```bash
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
- "hooks": {
198
- "pre": [
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
- ```bash
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
- ```bash
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
- ```bash
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
- ```bash
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
- ```bash
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
- ```bash
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
- ```bash
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
- ```bash
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
- ```bash
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
- ```bash
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
- ```bash
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
- ```bash
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
- "hooks": {
170
- "pre": [
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:
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "pyvegh"
7
- version = "0.7.0"
7
+ version = "0.9.0"
8
8
  description = "Python bindings for Vegh - The Snapshot Tool."
9
9
  authors = [{name = "CodeTease"}]
10
10
  readme = "README.md"
@@ -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.7.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)
@@ -0,0 +1,7 @@
1
+ from .cli_main import app
2
+
3
+ # noqa: F401 to expose app at package level
4
+ from . import cli_commands # noqa: F401
5
+
6
+ if __name__ == "__main__":
7
+ app()