lsum 0.1.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.
- lsum-0.1.0/PKG-INFO +137 -0
- lsum-0.1.0/README.md +124 -0
- lsum-0.1.0/pyproject.toml +21 -0
- lsum-0.1.0/src/lsum/__init__.py +0 -0
- lsum-0.1.0/src/lsum/lib/__init__.py +0 -0
- lsum-0.1.0/src/lsum/lib/constants.py +39 -0
- lsum-0.1.0/src/lsum/lib/ls.py +620 -0
- lsum-0.1.0/src/lsum/lib/mime.py +27 -0
- lsum-0.1.0/src/lsum/lib/utils.py +19 -0
- lsum-0.1.0/src/lsum/main.py +146 -0
lsum-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lsum
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Directory summary tool
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: click>=8.3.2
|
|
8
|
+
Requires-Dist: flit-core>=3.12.0
|
|
9
|
+
Requires-Dist: pathspec>=0.12.1
|
|
10
|
+
Requires-Dist: python-magic>=0.4.27
|
|
11
|
+
Requires-Dist: rich>=15.0.0
|
|
12
|
+
|
|
13
|
+
# 📂 lsum
|
|
14
|
+
|
|
15
|
+
[](https://pypi.org/project/lsum/)
|
|
16
|
+
[](https://python.org)
|
|
17
|
+
[](https://opensource.org/licenses/MIT)
|
|
18
|
+
[](https://github.com/psf/black)
|
|
19
|
+
|
|
20
|
+
**lsum** (List Summary) is a high-performance, visually rich CLI directory analysis tool that transforms standard file listings into actionable intelligence.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
### ⚡ TL;DR
|
|
25
|
+
|
|
26
|
+
`lsum` is `ls` on steroids. It doesn't just list files; it **categorizes**, **counts**, and **visualizes** your directory's distribution by MIME types, extensions, and metadata—all while respecting your `.gitignore` rules.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
### 🤔 Why lsum?
|
|
31
|
+
|
|
32
|
+
Standard tools like `ls` or `tree` are great for finding files, but they fail to answer higher-level questions about your workspace. `lsum` was built to fill that gap:
|
|
33
|
+
|
|
34
|
+
* **Audit Your Assets:** Instantly see how many gigabytes of images vs. text files you have.
|
|
35
|
+
* **Visualize Structure:** Group files into elegant, color-coded panels based on their actual content (MIME) rather than just extensions.
|
|
36
|
+
* **Clean Analysis:** Use the `--gitignore` flag to strip out `node_modules`, build artifacts, and logs, focusing only on the code that matters.
|
|
37
|
+
* **Recursive Intelligence:** Understand the composition of entire project trees in a single, formatted view.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
### 🚀 Installation
|
|
42
|
+
|
|
43
|
+
#### 📦 From PyPI (Recommended)
|
|
44
|
+
|
|
45
|
+
Install using [uv](https://github.com/astral-sh/uv) for the best experience:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uv tool install lsum
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or with standard pip:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install lsum
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### 🛠️ Building From Source
|
|
58
|
+
|
|
59
|
+
Perfect for developers who want the latest features:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Clone the repository
|
|
63
|
+
git clone https://github.com/Debajyati/lsum.git
|
|
64
|
+
cd lsum
|
|
65
|
+
|
|
66
|
+
# Install dependencies and build
|
|
67
|
+
uv pip install -e .
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### 🛠️ Usage Examples
|
|
73
|
+
|
|
74
|
+
#### 1. Basic Listing
|
|
75
|
+
|
|
76
|
+
A clean, tabular view of your current directory:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
lsum
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
#### 2. Group by MIME Type (with Icons)
|
|
83
|
+
|
|
84
|
+
See your files grouped by their actual content type (e.g., Image, Video, Text):
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
lsum --group
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### 3. Respect Gitignore
|
|
91
|
+
|
|
92
|
+
Exclude build artifacts and ignored files for a "clean" summary:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
lsum . --gitignore --count
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### 4. Recursive Extension Summary
|
|
99
|
+
|
|
100
|
+
Analyze every file in your project tree, grouped by extension:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
lsum . --recursive --group-extension
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### 5. Advanced Sorting & Filtering
|
|
107
|
+
|
|
108
|
+
Find all `.txt` files and sort them by size:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
lsum --filter-extension .txt --sort size
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
### ⌨️ CLI Options
|
|
117
|
+
|
|
118
|
+
| Option | Shorthand | Description |
|
|
119
|
+
|:------------------- |:--------- |:---------------------------------------------------- |
|
|
120
|
+
| `--count` | `-c` | Count files/directories in groups or total. |
|
|
121
|
+
| `--group` | `-g` | Group files by MIME type. |
|
|
122
|
+
| `--group-extension` | `-ge` | Group files by file extension. |
|
|
123
|
+
| `--gitignore` | `-gi` | Respect `.gitignore` rules (excludes ignored files). |
|
|
124
|
+
| `--recursive` | `-r` | Perform operations on all subdirectories. |
|
|
125
|
+
| `--sort` | `-s` | Sort by `name`, `size`, or `date`. |
|
|
126
|
+
| `--filter` | `-f` | Filter by a specific MIME type (e.g., `image/jpeg`). |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
### 📄 License
|
|
131
|
+
|
|
132
|
+
Distributed under the MIT License. See `LICENSE` for more information.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
*Built with ❤️ using Python and Rich.*
|
|
137
|
+
|
lsum-0.1.0/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# 📂 lsum
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/lsum/)
|
|
4
|
+
[](https://python.org)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://github.com/psf/black)
|
|
7
|
+
|
|
8
|
+
**lsum** (List Summary) is a high-performance, visually rich CLI directory analysis tool that transforms standard file listings into actionable intelligence.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
### ⚡ TL;DR
|
|
13
|
+
|
|
14
|
+
`lsum` is `ls` on steroids. It doesn't just list files; it **categorizes**, **counts**, and **visualizes** your directory's distribution by MIME types, extensions, and metadata—all while respecting your `.gitignore` rules.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
### 🤔 Why lsum?
|
|
19
|
+
|
|
20
|
+
Standard tools like `ls` or `tree` are great for finding files, but they fail to answer higher-level questions about your workspace. `lsum` was built to fill that gap:
|
|
21
|
+
|
|
22
|
+
* **Audit Your Assets:** Instantly see how many gigabytes of images vs. text files you have.
|
|
23
|
+
* **Visualize Structure:** Group files into elegant, color-coded panels based on their actual content (MIME) rather than just extensions.
|
|
24
|
+
* **Clean Analysis:** Use the `--gitignore` flag to strip out `node_modules`, build artifacts, and logs, focusing only on the code that matters.
|
|
25
|
+
* **Recursive Intelligence:** Understand the composition of entire project trees in a single, formatted view.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
### 🚀 Installation
|
|
30
|
+
|
|
31
|
+
#### 📦 From PyPI (Recommended)
|
|
32
|
+
|
|
33
|
+
Install using [uv](https://github.com/astral-sh/uv) for the best experience:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
uv tool install lsum
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or with standard pip:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install lsum
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
#### 🛠️ Building From Source
|
|
46
|
+
|
|
47
|
+
Perfect for developers who want the latest features:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Clone the repository
|
|
51
|
+
git clone https://github.com/Debajyati/lsum.git
|
|
52
|
+
cd lsum
|
|
53
|
+
|
|
54
|
+
# Install dependencies and build
|
|
55
|
+
uv pip install -e .
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
### 🛠️ Usage Examples
|
|
61
|
+
|
|
62
|
+
#### 1. Basic Listing
|
|
63
|
+
|
|
64
|
+
A clean, tabular view of your current directory:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
lsum
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### 2. Group by MIME Type (with Icons)
|
|
71
|
+
|
|
72
|
+
See your files grouped by their actual content type (e.g., Image, Video, Text):
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
lsum --group
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### 3. Respect Gitignore
|
|
79
|
+
|
|
80
|
+
Exclude build artifacts and ignored files for a "clean" summary:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
lsum . --gitignore --count
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### 4. Recursive Extension Summary
|
|
87
|
+
|
|
88
|
+
Analyze every file in your project tree, grouped by extension:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
lsum . --recursive --group-extension
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### 5. Advanced Sorting & Filtering
|
|
95
|
+
|
|
96
|
+
Find all `.txt` files and sort them by size:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
lsum --filter-extension .txt --sort size
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
### ⌨️ CLI Options
|
|
105
|
+
|
|
106
|
+
| Option | Shorthand | Description |
|
|
107
|
+
|:------------------- |:--------- |:---------------------------------------------------- |
|
|
108
|
+
| `--count` | `-c` | Count files/directories in groups or total. |
|
|
109
|
+
| `--group` | `-g` | Group files by MIME type. |
|
|
110
|
+
| `--group-extension` | `-ge` | Group files by file extension. |
|
|
111
|
+
| `--gitignore` | `-gi` | Respect `.gitignore` rules (excludes ignored files). |
|
|
112
|
+
| `--recursive` | `-r` | Perform operations on all subdirectories. |
|
|
113
|
+
| `--sort` | `-s` | Sort by `name`, `size`, or `date`. |
|
|
114
|
+
| `--filter` | `-f` | Filter by a specific MIME type (e.g., `image/jpeg`). |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### 📄 License
|
|
119
|
+
|
|
120
|
+
Distributed under the MIT License. See `LICENSE` for more information.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
*Built with ❤️ using Python and Rich.*
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "lsum"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Directory summary tool"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"click>=8.3.2",
|
|
9
|
+
"flit-core>=3.12.0",
|
|
10
|
+
"pathspec>=0.12.1",
|
|
11
|
+
"python-magic>=0.4.27",
|
|
12
|
+
"rich>=15.0.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
lsum = "lsum.main:cli"
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["flit_core<4"]
|
|
20
|
+
build-backend = "flit_core.buildapi"
|
|
21
|
+
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
colormap = {
|
|
2
|
+
"pdf": "red",
|
|
3
|
+
"ppt": "bright_red",
|
|
4
|
+
"pptx": "bright_red",
|
|
5
|
+
"vnd.openxmlformats-officedocument.presentationml.presentation": "bright_red",
|
|
6
|
+
"image": "green",
|
|
7
|
+
"doc": "blue",
|
|
8
|
+
"vnd.openxmlformats-officedocument.wordprocessingml.document": "blue",
|
|
9
|
+
"x-script.python": "bright_cyan",
|
|
10
|
+
"json": "yellow",
|
|
11
|
+
"javascript": "yellow",
|
|
12
|
+
"audio": "cyan",
|
|
13
|
+
"video": "magenta",
|
|
14
|
+
"plain": "white",
|
|
15
|
+
"csv": "bright_green",
|
|
16
|
+
"vnd.openxmlformats-officedocument.spreadsheetml.sheet": "green",
|
|
17
|
+
"zip": "bright_blue",
|
|
18
|
+
"exe": "bright_red",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
MIME_TYPE_ICONS = {
|
|
22
|
+
"pdf": "📄",
|
|
23
|
+
"ppt": "📊",
|
|
24
|
+
"pptx": "📊",
|
|
25
|
+
"vnd.openxmlformats-officedocument.presentationml.presentation": "📊",
|
|
26
|
+
"image": "🖼️",
|
|
27
|
+
"doc": "📃",
|
|
28
|
+
"vnd.openxmlformats-officedocument.wordprocessingml.document": "📃",
|
|
29
|
+
"json": "🔧",
|
|
30
|
+
"audio": "🎵",
|
|
31
|
+
"video": "🎬",
|
|
32
|
+
"text": "📄",
|
|
33
|
+
"x-script.python": "🐍",
|
|
34
|
+
"javascript": "📜",
|
|
35
|
+
"csv": "📈",
|
|
36
|
+
"vnd.openxmlformats-officedocument.spreadsheetml.sheet": "📈",
|
|
37
|
+
"zip": "🗜️",
|
|
38
|
+
"exe": "⚙️",
|
|
39
|
+
}
|
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from collections import Counter
|
|
3
|
+
from .utils import assoclist, get_gitignore_matcher
|
|
4
|
+
from rich import print
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from rich.columns import Columns
|
|
8
|
+
from .mime import get_mime_type
|
|
9
|
+
from .constants import MIME_TYPE_ICONS, colormap
|
|
10
|
+
|
|
11
|
+
def count_files(path=".", gitignore=False):
|
|
12
|
+
try:
|
|
13
|
+
matcher = get_gitignore_matcher(path) if gitignore else None
|
|
14
|
+
dirs = 0
|
|
15
|
+
non_dirs = 0
|
|
16
|
+
with os.scandir(path) as files:
|
|
17
|
+
for file in files:
|
|
18
|
+
if matcher and matcher.match_file(file.name + ("/" if file.is_dir() else "")):
|
|
19
|
+
continue
|
|
20
|
+
if file.is_dir():
|
|
21
|
+
dirs += 1
|
|
22
|
+
else:
|
|
23
|
+
non_dirs += 1
|
|
24
|
+
print(f"Directories: {dirs}")
|
|
25
|
+
print(f"Files: {non_dirs}")
|
|
26
|
+
except FileNotFoundError:
|
|
27
|
+
print(
|
|
28
|
+
f"[bold yellow]Error:[/bold yellow] The directory '{path}' does not exist."
|
|
29
|
+
)
|
|
30
|
+
except PermissionError:
|
|
31
|
+
print(
|
|
32
|
+
f"[bold yellow]Error:[/bold yellow] You do not have permission to access '{path}'."
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def list_files(path=".", gitignore=False):
|
|
36
|
+
try:
|
|
37
|
+
matcher = get_gitignore_matcher(path) if gitignore else None
|
|
38
|
+
dirs = []
|
|
39
|
+
non_dirs = []
|
|
40
|
+
with os.scandir(path) as files:
|
|
41
|
+
for file in files:
|
|
42
|
+
if matcher and matcher.match_file(file.name + ("/" if file.is_dir() else "")):
|
|
43
|
+
continue
|
|
44
|
+
if file.is_dir():
|
|
45
|
+
dirs.append(file.name)
|
|
46
|
+
else:
|
|
47
|
+
non_dirs.append(file.name)
|
|
48
|
+
|
|
49
|
+
dirlen = len(dirs)
|
|
50
|
+
non_dirlen = len(non_dirs)
|
|
51
|
+
|
|
52
|
+
if dirlen < non_dirlen:
|
|
53
|
+
dirs = dirs + [""] * (non_dirlen - dirlen)
|
|
54
|
+
elif non_dirlen < dirlen:
|
|
55
|
+
non_dirs = non_dirs + [""] * (dirlen - non_dirlen)
|
|
56
|
+
|
|
57
|
+
files_assoclist = assoclist(dirs, non_dirs)
|
|
58
|
+
table = Table(
|
|
59
|
+
show_header=True,
|
|
60
|
+
header_style="bold magenta",
|
|
61
|
+
title=f"{path if path != '.' else 'CWD'} Listing",
|
|
62
|
+
title_style="bold underline magenta",
|
|
63
|
+
)
|
|
64
|
+
table.add_column("Index", style="dim", width=6, justify="right")
|
|
65
|
+
table.add_column("Directories", style="bold blue underline", justify="left")
|
|
66
|
+
table.add_column("Files", style="bold green underline", justify="left")
|
|
67
|
+
|
|
68
|
+
index = 1
|
|
69
|
+
for first, second in files_assoclist.__iter__():
|
|
70
|
+
table.add_row(
|
|
71
|
+
str(index), f"{first}/" if len(first) else "", f"{second}"
|
|
72
|
+
)
|
|
73
|
+
index += 1
|
|
74
|
+
print(table)
|
|
75
|
+
|
|
76
|
+
except FileNotFoundError:
|
|
77
|
+
print(
|
|
78
|
+
f"[bold yellow]Error:[/bold yellow] The directory '{path}' does not exist."
|
|
79
|
+
)
|
|
80
|
+
except PermissionError:
|
|
81
|
+
print(
|
|
82
|
+
f"[bold yellow]Error:[/bold yellow] You do not have permission to access '{path}'."
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def count_files_by_mime_type(path=".", gitignore=False):
|
|
86
|
+
try:
|
|
87
|
+
matcher = get_gitignore_matcher(path) if gitignore else None
|
|
88
|
+
mime_counts = Counter()
|
|
89
|
+
with os.scandir(path) as files:
|
|
90
|
+
for file in files:
|
|
91
|
+
if matcher and matcher.match_file(file.name + ("/" if file.is_dir() else "")):
|
|
92
|
+
continue
|
|
93
|
+
if file.is_file():
|
|
94
|
+
mime_type = get_mime_type(file.path) or "Unknown MIME Type"
|
|
95
|
+
mime_counts[mime_type] += 1
|
|
96
|
+
|
|
97
|
+
table = Table(
|
|
98
|
+
show_header=True,
|
|
99
|
+
header_style="bold magenta",
|
|
100
|
+
title=f"{path if path != '.' else 'CWD'} File Counts by MIME Type",
|
|
101
|
+
title_style="bold underline magenta",
|
|
102
|
+
)
|
|
103
|
+
table.add_column("MIME Type", style="bold red", justify="left")
|
|
104
|
+
table.add_column("Count", style="bold yellow", justify="right")
|
|
105
|
+
|
|
106
|
+
for mime, count in mime_counts.items():
|
|
107
|
+
table.add_row(mime, str(count))
|
|
108
|
+
|
|
109
|
+
print(table)
|
|
110
|
+
|
|
111
|
+
except FileNotFoundError:
|
|
112
|
+
print(
|
|
113
|
+
f"[bold yellow]Error:[/bold yellow] The directory '{path}' does not exist."
|
|
114
|
+
)
|
|
115
|
+
except PermissionError:
|
|
116
|
+
print(
|
|
117
|
+
f"[bold yellow]Error:[/bold yellow] You do not have permission to access '{path}'."
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# multibox layout with one box for each MIME type, and each box contains a list of files with that MIME type, and the box title is the MIME type and the count of files with that MIME type
|
|
121
|
+
# use colormap and MIME_TYPE_ICONS to color the box title and add an icon to the box title based on the MIME type, if the MIME type is not in the colormap or MIME_TYPE_ICONS, use a default color and icon
|
|
122
|
+
def group_files_by_mime_type(path=".", gitignore=False):
|
|
123
|
+
try:
|
|
124
|
+
matcher = get_gitignore_matcher(path) if gitignore else None
|
|
125
|
+
mime_groups = {}
|
|
126
|
+
with os.scandir(path) as entries:
|
|
127
|
+
for entry in entries:
|
|
128
|
+
if matcher and matcher.match_file(entry.name + ("/" if entry.is_dir() else "")):
|
|
129
|
+
continue
|
|
130
|
+
if entry.is_file():
|
|
131
|
+
mime_type = get_mime_type(entry.path) or "unknown/type"
|
|
132
|
+
mime_groups.setdefault(mime_type, []).append(entry.name)
|
|
133
|
+
|
|
134
|
+
panels = []
|
|
135
|
+
for mime, files in mime_groups.items():
|
|
136
|
+
# Get the prefix (e.g., 'image' from 'image/jpeg')
|
|
137
|
+
prefix, suffix = mime.split("/")[0], mime.split("/")[1]
|
|
138
|
+
color = colormap.get(prefix, colormap.get(suffix, "white"))
|
|
139
|
+
icon = MIME_TYPE_ICONS.get(suffix, MIME_TYPE_ICONS.get(prefix, "📁"))
|
|
140
|
+
|
|
141
|
+
# 1. Join all files into one string first so they don't overwrite each other
|
|
142
|
+
file_list_str = "\n".join([f"[{color}]{f}[/{color}]" for f in files])
|
|
143
|
+
|
|
144
|
+
# 2. Create a Panel (the "Box") for this MIME type
|
|
145
|
+
box_title = f"{icon} {mime} ({len(files)})"
|
|
146
|
+
panels.append(Panel(file_list_str, title=box_title, border_style=color, expand=False))
|
|
147
|
+
|
|
148
|
+
# 3. Use Columns to display boxes side-by-side (or wrapped)
|
|
149
|
+
print(Columns(panels))
|
|
150
|
+
|
|
151
|
+
except FileNotFoundError:
|
|
152
|
+
print(f"[bold red]Error:[/bold red] Directory '{path}' not found.")
|
|
153
|
+
except PermissionError:
|
|
154
|
+
print(f"[bold red]Error:[/bold red] Permission denied for '{path}'.")
|
|
155
|
+
|
|
156
|
+
def count_files_by_extension(path=".", gitignore=False):
|
|
157
|
+
try:
|
|
158
|
+
matcher = get_gitignore_matcher(path) if gitignore else None
|
|
159
|
+
extension_counts = Counter()
|
|
160
|
+
with os.scandir(path) as files:
|
|
161
|
+
for file in files:
|
|
162
|
+
if matcher and matcher.match_file(file.name + ("/" if file.is_dir() else "")):
|
|
163
|
+
continue
|
|
164
|
+
if file.is_file():
|
|
165
|
+
ext = os.path.splitext(file.name)[1].lower() or "No Extension"
|
|
166
|
+
extension_counts[ext] += 1
|
|
167
|
+
|
|
168
|
+
table = Table(
|
|
169
|
+
show_header=True,
|
|
170
|
+
header_style="bold magenta",
|
|
171
|
+
title=f"{path if path != '.' else 'CWD'} File Counts by Extension",
|
|
172
|
+
title_style="bold underline magenta",
|
|
173
|
+
)
|
|
174
|
+
table.add_column("Extension", style="bold red", justify="left")
|
|
175
|
+
table.add_column("Count", style="bold yellow", justify="right")
|
|
176
|
+
|
|
177
|
+
for ext, count in extension_counts.items():
|
|
178
|
+
table.add_row(ext, str(count))
|
|
179
|
+
|
|
180
|
+
print(table)
|
|
181
|
+
|
|
182
|
+
except FileNotFoundError:
|
|
183
|
+
print(
|
|
184
|
+
f"[bold yellow]Error:[/bold yellow] The directory '{path}' does not exist."
|
|
185
|
+
)
|
|
186
|
+
except PermissionError:
|
|
187
|
+
print(
|
|
188
|
+
f"[bold yellow]Error:[/bold yellow] You do not have permission to access '{path}'."
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def group_files_by_extension(path=".", gitignore=False):
|
|
192
|
+
try:
|
|
193
|
+
matcher = get_gitignore_matcher(path) if gitignore else None
|
|
194
|
+
extension_groups = {}
|
|
195
|
+
with os.scandir(path) as files:
|
|
196
|
+
for file in files:
|
|
197
|
+
if matcher and matcher.match_file(file.name + ("/" if file.is_dir() else "")):
|
|
198
|
+
continue
|
|
199
|
+
if file.is_file():
|
|
200
|
+
ext = os.path.splitext(file.name)[1].lower() or "No Extension"
|
|
201
|
+
extension_groups.setdefault(ext, []).append(file.path)
|
|
202
|
+
|
|
203
|
+
table = Table(
|
|
204
|
+
show_header=True,
|
|
205
|
+
header_style="bold magenta",
|
|
206
|
+
title=f"{path if path != '.' else 'CWD'} Files Grouped by Extension",
|
|
207
|
+
title_style="bold underline magenta",
|
|
208
|
+
)
|
|
209
|
+
table.add_column("Index", style="dim", width=6, justify="center")
|
|
210
|
+
table.add_column("Extension", style="bold red", justify="left")
|
|
211
|
+
table.add_column("Files", style="bold yellow", justify="left")
|
|
212
|
+
|
|
213
|
+
# when a new extension is encountered, print the first row with the extension with overline style, and subsequent rows with an empty extension column and no overline style
|
|
214
|
+
for ext, files in extension_groups.items():
|
|
215
|
+
first = True
|
|
216
|
+
index = 1
|
|
217
|
+
for file in files:
|
|
218
|
+
if first:
|
|
219
|
+
table.add_row(f"{index}",ext, file)
|
|
220
|
+
first = False
|
|
221
|
+
index += 1
|
|
222
|
+
else:
|
|
223
|
+
table.add_row(f"{index}","", file)
|
|
224
|
+
index += 1
|
|
225
|
+
table.add_row("") # add an empty row after each extension group for better readability
|
|
226
|
+
|
|
227
|
+
print(table)
|
|
228
|
+
|
|
229
|
+
except FileNotFoundError:
|
|
230
|
+
print(
|
|
231
|
+
f"[bold yellow]Error:[/bold yellow] The directory '{path}' does not exist."
|
|
232
|
+
)
|
|
233
|
+
except PermissionError:
|
|
234
|
+
print(
|
|
235
|
+
f"[bold yellow]Error:[/bold yellow] You do not have permission to access '{path}'."
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def recursive_list_files(path=".", gitignore=False):
|
|
239
|
+
try:
|
|
240
|
+
matcher = get_gitignore_matcher(path) if gitignore else None
|
|
241
|
+
table = Table(
|
|
242
|
+
show_header=True,
|
|
243
|
+
header_style="bold magenta",
|
|
244
|
+
title=f"{path if path != '.' else 'CWD'} Recursive Listing",
|
|
245
|
+
title_style="bold underline magenta",
|
|
246
|
+
)
|
|
247
|
+
table.add_column("Index", style="dim", width=6, justify="right")
|
|
248
|
+
table.add_column("Path", style="bold cyan", justify="left")
|
|
249
|
+
|
|
250
|
+
index = 1
|
|
251
|
+
for root, dirs, files in os.walk(path):
|
|
252
|
+
if matcher:
|
|
253
|
+
rel_root = os.path.relpath(root, path)
|
|
254
|
+
if rel_root == ".":
|
|
255
|
+
rel_root = ""
|
|
256
|
+
dirs[:] = [d for d in dirs if not matcher.match_file(os.path.join(rel_root, d) + "/")]
|
|
257
|
+
files = [f for f in files if not matcher.match_file(os.path.join(rel_root, f))]
|
|
258
|
+
|
|
259
|
+
for name in dirs + files:
|
|
260
|
+
full_path = os.path.join(root, name)
|
|
261
|
+
table.add_row(str(index), full_path)
|
|
262
|
+
index += 1
|
|
263
|
+
|
|
264
|
+
print(table)
|
|
265
|
+
|
|
266
|
+
except FileNotFoundError:
|
|
267
|
+
print(
|
|
268
|
+
f"[bold yellow]Error:[/bold yellow] The directory '{path}' does not exist."
|
|
269
|
+
)
|
|
270
|
+
except PermissionError:
|
|
271
|
+
print(
|
|
272
|
+
f"[bold yellow]Error:[/bold yellow] You do not have permission to access '{path}'."
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
def recursive_count_files(path=".", gitignore=False):
|
|
276
|
+
try:
|
|
277
|
+
matcher = get_gitignore_matcher(path) if gitignore else None
|
|
278
|
+
file_count = 0
|
|
279
|
+
dir_count = 0
|
|
280
|
+
for root, dirs, files in os.walk(path):
|
|
281
|
+
if matcher:
|
|
282
|
+
rel_root = os.path.relpath(root, path)
|
|
283
|
+
if rel_root == ".":
|
|
284
|
+
rel_root = ""
|
|
285
|
+
dirs[:] = [d for d in dirs if not matcher.match_file(os.path.join(rel_root, d) + "/")]
|
|
286
|
+
files = [f for f in files if not matcher.match_file(os.path.join(rel_root, f))]
|
|
287
|
+
|
|
288
|
+
file_count += len(files)
|
|
289
|
+
dir_count += len(dirs)
|
|
290
|
+
|
|
291
|
+
print(f"Total Directories: {dir_count}")
|
|
292
|
+
print(f"Total Files: {file_count}")
|
|
293
|
+
|
|
294
|
+
except FileNotFoundError:
|
|
295
|
+
print(
|
|
296
|
+
f"[bold yellow]Error:[/bold yellow] The directory '{path}' does not exist."
|
|
297
|
+
)
|
|
298
|
+
except PermissionError:
|
|
299
|
+
print(
|
|
300
|
+
f"[bold yellow]Error:[/bold yellow] You do not have permission to access '{path}'."
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
def recursive_count_files_by_mime_type(path=".", gitignore=False):
|
|
304
|
+
try:
|
|
305
|
+
matcher = get_gitignore_matcher(path) if gitignore else None
|
|
306
|
+
mime_counts = Counter()
|
|
307
|
+
for root, dirs, files in os.walk(path):
|
|
308
|
+
if matcher:
|
|
309
|
+
rel_root = os.path.relpath(root, path)
|
|
310
|
+
if rel_root == ".":
|
|
311
|
+
rel_root = ""
|
|
312
|
+
dirs[:] = [d for d in dirs if not matcher.match_file(os.path.join(rel_root, d) + "/")]
|
|
313
|
+
files = [f for f in files if not matcher.match_file(os.path.join(rel_root, f))]
|
|
314
|
+
|
|
315
|
+
for file in files:
|
|
316
|
+
full_path = os.path.join(root, file)
|
|
317
|
+
mime_type = get_mime_type(full_path) or "Unknown MIME Type"
|
|
318
|
+
mime_counts[mime_type] += 1
|
|
319
|
+
|
|
320
|
+
table = Table(
|
|
321
|
+
show_header=True,
|
|
322
|
+
header_style="bold magenta",
|
|
323
|
+
title=f"{path if path != '.' else 'CWD'} Recursive File Counts by MIME Type",
|
|
324
|
+
title_style="bold underline magenta",
|
|
325
|
+
)
|
|
326
|
+
table.add_column("MIME Type", style="bold red", justify="left")
|
|
327
|
+
table.add_column("Count", style="bold yellow", justify="right")
|
|
328
|
+
|
|
329
|
+
for mime, count in mime_counts.items():
|
|
330
|
+
table.add_row(mime, str(count))
|
|
331
|
+
|
|
332
|
+
print(table)
|
|
333
|
+
|
|
334
|
+
except FileNotFoundError:
|
|
335
|
+
print(
|
|
336
|
+
f"[bold yellow]Error:[/bold yellow] The directory '{path}' does not exist."
|
|
337
|
+
)
|
|
338
|
+
except PermissionError:
|
|
339
|
+
print(
|
|
340
|
+
f"[bold yellow]Error:[/bold yellow] You do not have permission to access '{path}'."
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
def recursive_group_files_by_mime_type(path=".", gitignore=False):
|
|
344
|
+
try:
|
|
345
|
+
matcher = get_gitignore_matcher(path) if gitignore else None
|
|
346
|
+
mime_groups = {}
|
|
347
|
+
for root, dirs, files in os.walk(path):
|
|
348
|
+
if matcher:
|
|
349
|
+
rel_root = os.path.relpath(root, path)
|
|
350
|
+
if rel_root == ".":
|
|
351
|
+
rel_root = ""
|
|
352
|
+
dirs[:] = [d for d in dirs if not matcher.match_file(os.path.join(rel_root, d) + "/")]
|
|
353
|
+
files = [f for f in files if not matcher.match_file(os.path.join(rel_root, f))]
|
|
354
|
+
|
|
355
|
+
for file in files:
|
|
356
|
+
full_path = os.path.join(root, file)
|
|
357
|
+
mime_type = get_mime_type(full_path) or "unknown/type"
|
|
358
|
+
mime_groups.setdefault(mime_type, []).append(full_path)
|
|
359
|
+
|
|
360
|
+
panels = []
|
|
361
|
+
for mime, files in mime_groups.items():
|
|
362
|
+
prefix, suffix = mime.split("/")[0], mime.split("/")[1]
|
|
363
|
+
color = colormap.get(prefix, colormap.get(suffix, "white"))
|
|
364
|
+
icon = MIME_TYPE_ICONS.get(suffix, MIME_TYPE_ICONS.get(prefix, "📁"))
|
|
365
|
+
file_list_str = "\n".join([f"[{color}]{f}[/{color}]" for f in files])
|
|
366
|
+
box_title = f"{icon} {mime} ({len(files)})"
|
|
367
|
+
panels.append(Panel(file_list_str, title=box_title, border_style=color, expand=False))
|
|
368
|
+
|
|
369
|
+
print(Columns(panels))
|
|
370
|
+
|
|
371
|
+
except FileNotFoundError:
|
|
372
|
+
print(f"[bold red]Error:[/bold red] Directory '{path}' not found.")
|
|
373
|
+
except PermissionError:
|
|
374
|
+
print(f"[bold red]Error:[/bold red] Permission denied for '{path}'.")
|
|
375
|
+
|
|
376
|
+
def recursive_count_files_by_extension(path=".", gitignore=False):
|
|
377
|
+
try:
|
|
378
|
+
matcher = get_gitignore_matcher(path) if gitignore else None
|
|
379
|
+
extension_counts = Counter()
|
|
380
|
+
for root, dirs, files in os.walk(path):
|
|
381
|
+
if matcher:
|
|
382
|
+
rel_root = os.path.relpath(root, path)
|
|
383
|
+
if rel_root == ".":
|
|
384
|
+
rel_root = ""
|
|
385
|
+
dirs[:] = [d for d in dirs if not matcher.match_file(os.path.join(rel_root, d) + "/")]
|
|
386
|
+
files = [f for f in files if not matcher.match_file(os.path.join(rel_root, f))]
|
|
387
|
+
|
|
388
|
+
for file in files:
|
|
389
|
+
ext = os.path.splitext(file)[1].lower() or "No Extension"
|
|
390
|
+
extension_counts[ext] += 1
|
|
391
|
+
|
|
392
|
+
table = Table(
|
|
393
|
+
show_header=True,
|
|
394
|
+
header_style="bold magenta",
|
|
395
|
+
title=f"{path if path != '.' else 'CWD'} Recursive File Counts by Extension",
|
|
396
|
+
title_style="bold underline magenta",
|
|
397
|
+
)
|
|
398
|
+
table.add_column("Extension", style="bold red", justify="left")
|
|
399
|
+
table.add_column("Count", style="bold yellow", justify="right")
|
|
400
|
+
|
|
401
|
+
for ext, count in extension_counts.items():
|
|
402
|
+
table.add_row(ext, str(count))
|
|
403
|
+
|
|
404
|
+
print(table)
|
|
405
|
+
|
|
406
|
+
except FileNotFoundError:
|
|
407
|
+
print(
|
|
408
|
+
f"[bold yellow]Error:[/bold yellow] The directory '{path}' does not exist."
|
|
409
|
+
)
|
|
410
|
+
except PermissionError:
|
|
411
|
+
print(
|
|
412
|
+
f"[bold yellow]Error:[/bold yellow] You do not have permission to access '{path}'."
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
def recursive_group_files_by_extension(path=".", gitignore=False):
|
|
416
|
+
try:
|
|
417
|
+
matcher = get_gitignore_matcher(path) if gitignore else None
|
|
418
|
+
extension_groups = {}
|
|
419
|
+
for root, dirs, files in os.walk(path):
|
|
420
|
+
if matcher:
|
|
421
|
+
rel_root = os.path.relpath(root, path)
|
|
422
|
+
if rel_root == ".":
|
|
423
|
+
rel_root = ""
|
|
424
|
+
dirs[:] = [d for d in dirs if not matcher.match_file(os.path.join(rel_root, d) + "/")]
|
|
425
|
+
files = [f for f in files if not matcher.match_file(os.path.join(rel_root, f))]
|
|
426
|
+
|
|
427
|
+
for file in files:
|
|
428
|
+
ext = os.path.splitext(file)[1].lower() or "No Extension"
|
|
429
|
+
full_path = os.path.join(root, file)
|
|
430
|
+
extension_groups.setdefault(ext, []).append(full_path)
|
|
431
|
+
|
|
432
|
+
table = Table(
|
|
433
|
+
show_header=True,
|
|
434
|
+
header_style="bold magenta",
|
|
435
|
+
title=f"{path if path != '.' else 'CWD'} Recursive Files Grouped by Extension",
|
|
436
|
+
title_style="bold underline magenta",
|
|
437
|
+
)
|
|
438
|
+
table.add_column("Index", style="dim", width=6, justify="center")
|
|
439
|
+
table.add_column("Extension", style="bold red", justify="left")
|
|
440
|
+
table.add_column("Files", style="bold yellow", justify="left")
|
|
441
|
+
|
|
442
|
+
for ext, files in extension_groups.items():
|
|
443
|
+
first = True
|
|
444
|
+
index = 1
|
|
445
|
+
for file in files:
|
|
446
|
+
if first:
|
|
447
|
+
table.add_row(f"{index}",ext, file)
|
|
448
|
+
first = False
|
|
449
|
+
index += 1
|
|
450
|
+
else:
|
|
451
|
+
table.add_row(f"{index}","", file)
|
|
452
|
+
index += 1
|
|
453
|
+
table.add_row("") # add an empty row after each extension group for better readability
|
|
454
|
+
|
|
455
|
+
print(table)
|
|
456
|
+
|
|
457
|
+
except FileNotFoundError:
|
|
458
|
+
print(
|
|
459
|
+
f"[bold yellow]Error:[/bold yellow] The directory '{path}' does not exist."
|
|
460
|
+
)
|
|
461
|
+
except PermissionError:
|
|
462
|
+
print(
|
|
463
|
+
f"[bold yellow]Error:[/bold yellow] You do not have permission to access '{path}'."
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
def filter_files_by_extension(path=".", extension=".txt", gitignore=False):
|
|
467
|
+
try:
|
|
468
|
+
matcher = get_gitignore_matcher(path) if gitignore else None
|
|
469
|
+
table = Table(
|
|
470
|
+
show_header=True,
|
|
471
|
+
header_style="bold magenta",
|
|
472
|
+
title=f"{path if path != '.' else 'CWD'} Files with Extension '{extension}'",
|
|
473
|
+
title_style="bold underline magenta",
|
|
474
|
+
)
|
|
475
|
+
table.add_column("Index", style="dim", width=6, justify="right")
|
|
476
|
+
table.add_column("File Name", style="bold green", justify="left")
|
|
477
|
+
|
|
478
|
+
index = 1
|
|
479
|
+
with os.scandir(path) as files:
|
|
480
|
+
for file in files:
|
|
481
|
+
if matcher and matcher.match_file(file.name + ("/" if file.is_dir() else "")):
|
|
482
|
+
continue
|
|
483
|
+
if file.is_file() and file.name.lower().endswith(extension.lower()):
|
|
484
|
+
table.add_row(str(index), file.name)
|
|
485
|
+
index += 1
|
|
486
|
+
|
|
487
|
+
print(table)
|
|
488
|
+
|
|
489
|
+
except FileNotFoundError:
|
|
490
|
+
print(
|
|
491
|
+
f"[bold yellow]Error:[/bold yellow] The directory '{path}' does not exist."
|
|
492
|
+
)
|
|
493
|
+
except PermissionError:
|
|
494
|
+
print(
|
|
495
|
+
f"[bold yellow]Error:[/bold yellow] You do not have permission to access '{path}'."
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
def filter_files_by_mime_type(path=".", mime_type="text/plain", gitignore=False):
|
|
499
|
+
try:
|
|
500
|
+
matcher = get_gitignore_matcher(path) if gitignore else None
|
|
501
|
+
table = Table(
|
|
502
|
+
show_header=True,
|
|
503
|
+
header_style="bold magenta",
|
|
504
|
+
title=f"{path if path != '.' else 'CWD'} Files with MIME Type '{mime_type}'",
|
|
505
|
+
title_style="bold underline magenta",
|
|
506
|
+
)
|
|
507
|
+
table.add_column("Index", style="dim", width=6, justify="right")
|
|
508
|
+
table.add_column("File Name", style="bold green", justify="left")
|
|
509
|
+
|
|
510
|
+
index = 1
|
|
511
|
+
with os.scandir(path) as files:
|
|
512
|
+
for file in files:
|
|
513
|
+
if matcher and matcher.match_file(file.name + ("/" if file.is_dir() else "")):
|
|
514
|
+
continue
|
|
515
|
+
if file.is_file():
|
|
516
|
+
file_mime_type = get_mime_type(file.path)
|
|
517
|
+
if file_mime_type == mime_type:
|
|
518
|
+
table.add_row(str(index), file.name)
|
|
519
|
+
index += 1
|
|
520
|
+
|
|
521
|
+
print(table)
|
|
522
|
+
|
|
523
|
+
except FileNotFoundError:
|
|
524
|
+
print(
|
|
525
|
+
f"[bold yellow]Error:[/bold yellow] The directory '{path}' does not exist."
|
|
526
|
+
)
|
|
527
|
+
except PermissionError:
|
|
528
|
+
print(
|
|
529
|
+
f"[bold yellow]Error:[/bold yellow] You do not have permission to access '{path}'."
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
def count_filter_files_by_mime_type(path=".", mime_type="text/plain", gitignore=False):
|
|
533
|
+
try:
|
|
534
|
+
matcher = get_gitignore_matcher(path) if gitignore else None
|
|
535
|
+
count = 0
|
|
536
|
+
with os.scandir(path) as files:
|
|
537
|
+
for file in files:
|
|
538
|
+
if matcher and matcher.match_file(file.name + ("/" if file.is_dir() else "")):
|
|
539
|
+
continue
|
|
540
|
+
if file.is_file():
|
|
541
|
+
file_mime_type = get_mime_type(file.path)
|
|
542
|
+
if file_mime_type == mime_type:
|
|
543
|
+
count += 1
|
|
544
|
+
|
|
545
|
+
print(f"Number of files with MIME type '{mime_type}': {count}")
|
|
546
|
+
|
|
547
|
+
except FileNotFoundError:
|
|
548
|
+
print(
|
|
549
|
+
f"[bold yellow]Error:[/bold yellow] The directory '{path}' does not exist."
|
|
550
|
+
)
|
|
551
|
+
except PermissionError:
|
|
552
|
+
print(
|
|
553
|
+
f"[bold yellow]Error:[/bold yellow] You do not have permission to access '{path}'."
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
def count_filter_files_by_extension(path=".", extension=".txt", gitignore=False):
|
|
557
|
+
try:
|
|
558
|
+
matcher = get_gitignore_matcher(path) if gitignore else None
|
|
559
|
+
count = 0
|
|
560
|
+
with os.scandir(path) as files:
|
|
561
|
+
for file in files:
|
|
562
|
+
if matcher and matcher.match_file(file.name + ("/" if file.is_dir() else "")):
|
|
563
|
+
continue
|
|
564
|
+
if file.is_file() and file.name.lower().endswith(extension.lower()):
|
|
565
|
+
count += 1
|
|
566
|
+
|
|
567
|
+
print(f"Number of files with extension '{extension}': {count}")
|
|
568
|
+
|
|
569
|
+
except FileNotFoundError:
|
|
570
|
+
print(
|
|
571
|
+
f"[bold yellow]Error:[/bold yellow] The directory '{path}' does not exist."
|
|
572
|
+
)
|
|
573
|
+
except PermissionError:
|
|
574
|
+
print(
|
|
575
|
+
f"[bold yellow]Error:[/bold yellow] You do not have permission to access '{path}'."
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
def sort_files(path=".", sort_by="name", gitignore=False):
|
|
579
|
+
try:
|
|
580
|
+
matcher = get_gitignore_matcher(path) if gitignore else None
|
|
581
|
+
files_list = []
|
|
582
|
+
with os.scandir(path) as files:
|
|
583
|
+
for file in files:
|
|
584
|
+
if matcher and matcher.match_file(file.name + ("/" if file.is_dir() else "")):
|
|
585
|
+
continue
|
|
586
|
+
if file.is_file():
|
|
587
|
+
if sort_by == "name":
|
|
588
|
+
files_list.append((file.name, file.path))
|
|
589
|
+
elif sort_by == "size":
|
|
590
|
+
files_list.append((file.stat().st_size, file.path))
|
|
591
|
+
elif sort_by == "date":
|
|
592
|
+
files_list.append((file.stat().st_mtime, file.path))
|
|
593
|
+
|
|
594
|
+
files_list.sort(key=lambda x: x[0])
|
|
595
|
+
|
|
596
|
+
table = Table(
|
|
597
|
+
show_header=True,
|
|
598
|
+
header_style="bold magenta",
|
|
599
|
+
title=f"{path if path != '.' else 'CWD'} Files Sorted by {sort_by.capitalize()}",
|
|
600
|
+
title_style="bold underline magenta",
|
|
601
|
+
)
|
|
602
|
+
table.add_column("Index", style="dim", width=6, justify="right")
|
|
603
|
+
table.add_column("File Name", style="bold green", justify="left")
|
|
604
|
+
|
|
605
|
+
index = 1
|
|
606
|
+
for _, file_path in files_list:
|
|
607
|
+
table.add_row(str(index), os.path.basename(file_path))
|
|
608
|
+
index += 1
|
|
609
|
+
|
|
610
|
+
print(table)
|
|
611
|
+
|
|
612
|
+
except FileNotFoundError:
|
|
613
|
+
print(
|
|
614
|
+
f"[bold yellow]Error:[/bold yellow] The directory '{path}' does not exist."
|
|
615
|
+
)
|
|
616
|
+
except PermissionError:
|
|
617
|
+
print(
|
|
618
|
+
f"[bold yellow]Error:[/bold yellow] You do not have permission to access '{path}'."
|
|
619
|
+
)
|
|
620
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import mimetypes
|
|
3
|
+
|
|
4
|
+
def get_mime_type(file_path:str):
|
|
5
|
+
"""
|
|
6
|
+
Detects the MIME type of a file.
|
|
7
|
+
- First tries python-magic (content-based detection)
|
|
8
|
+
- Falls back to mimetypes (extension-based detection)
|
|
9
|
+
"""
|
|
10
|
+
if not os.path.isfile(file_path):
|
|
11
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
12
|
+
|
|
13
|
+
# Try python-magic if available
|
|
14
|
+
try:
|
|
15
|
+
import magic
|
|
16
|
+
mime = magic.Magic(mime=True)
|
|
17
|
+
mime_type = mime.from_file(file_path)
|
|
18
|
+
if mime_type:
|
|
19
|
+
return mime_type
|
|
20
|
+
except ImportError:
|
|
21
|
+
print("python-magic not installed. Falling back to mimetypes.")
|
|
22
|
+
except Exception as e:
|
|
23
|
+
print(f"python-magic failed: {e}. Falling back to mimetypes.")
|
|
24
|
+
|
|
25
|
+
# Fallback: mimetypes (based on file extension)
|
|
26
|
+
mime_type, _ = mimetypes.guess_type(file_path)
|
|
27
|
+
return mime_type or "Unknown"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import Any, Tuple, Optional
|
|
2
|
+
import os
|
|
3
|
+
import pathspec
|
|
4
|
+
|
|
5
|
+
def assoclist(array1:list[Any], array2:list[Any]):
|
|
6
|
+
result:list[Tuple[Any,Any]] = []
|
|
7
|
+
|
|
8
|
+
length = min(len(array1),len(array2))
|
|
9
|
+
for i in range(length):
|
|
10
|
+
result.append((array1[i],array2[i]))
|
|
11
|
+
return result
|
|
12
|
+
|
|
13
|
+
def get_gitignore_matcher(path: str) -> Optional[pathspec.PathSpec]:
|
|
14
|
+
gitignore_path = os.path.join(path, ".gitignore")
|
|
15
|
+
if os.path.exists(gitignore_path):
|
|
16
|
+
with open(gitignore_path, "r") as f:
|
|
17
|
+
spec = pathspec.PathSpec.from_lines("gitwildmatch", f)
|
|
18
|
+
return spec
|
|
19
|
+
return None
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
from .lib.ls import (
|
|
3
|
+
count_files_by_extension,
|
|
4
|
+
count_files_by_mime_type,
|
|
5
|
+
group_files_by_mime_type,
|
|
6
|
+
list_files,
|
|
7
|
+
count_files,
|
|
8
|
+
group_files_by_extension,
|
|
9
|
+
recursive_count_files,
|
|
10
|
+
recursive_count_files_by_extension,
|
|
11
|
+
recursive_count_files_by_mime_type,
|
|
12
|
+
recursive_group_files_by_extension,
|
|
13
|
+
recursive_group_files_by_mime_type,
|
|
14
|
+
recursive_list_files,
|
|
15
|
+
filter_files_by_extension,
|
|
16
|
+
filter_files_by_mime_type,
|
|
17
|
+
count_filter_files_by_extension,
|
|
18
|
+
count_filter_files_by_mime_type,
|
|
19
|
+
sort_files,
|
|
20
|
+
)
|
|
21
|
+
import click
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@click.command()
|
|
25
|
+
@click.argument("path", default=".", required=False)
|
|
26
|
+
@click.option(
|
|
27
|
+
"--count",
|
|
28
|
+
"-c",
|
|
29
|
+
is_flag=True,
|
|
30
|
+
help="Count the number of files and directories in the specified path. Using it in conjunction with --group or --group-extension will count files within each group instead of the total count. Using it with --filter or --filter-extension will count only the files that match the specified filter criteria.",
|
|
31
|
+
)
|
|
32
|
+
@click.option("--group", "-g", is_flag=True, help="Group files by their MIME type.")
|
|
33
|
+
@click.option(
|
|
34
|
+
"--group-extension",
|
|
35
|
+
"-ge",
|
|
36
|
+
is_flag=True,
|
|
37
|
+
help="Group files by their file extension.",
|
|
38
|
+
)
|
|
39
|
+
@click.option(
|
|
40
|
+
"--group-by",
|
|
41
|
+
"-gb",
|
|
42
|
+
type=click.Choice(["mime", "extension"], case_sensitive=False),
|
|
43
|
+
help="Group files by MIME type or file extension.",
|
|
44
|
+
)
|
|
45
|
+
@click.option(
|
|
46
|
+
"--filter",
|
|
47
|
+
"-f",
|
|
48
|
+
type=str,
|
|
49
|
+
help="Filter files by a specific MIME type (e.g., 'image/jpeg').",
|
|
50
|
+
)
|
|
51
|
+
@click.option(
|
|
52
|
+
"--filter-extension",
|
|
53
|
+
"-fe",
|
|
54
|
+
type=str,
|
|
55
|
+
help="Filter files by a specific file extension (e.g., '.txt').",
|
|
56
|
+
)
|
|
57
|
+
@click.option(
|
|
58
|
+
"--sort",
|
|
59
|
+
"-s",
|
|
60
|
+
type=click.Choice(["name", "size", "date"], case_sensitive=False),
|
|
61
|
+
help="Sort files by name, size, or date.",
|
|
62
|
+
)
|
|
63
|
+
@click.option(
|
|
64
|
+
"--recursive",
|
|
65
|
+
"-r",
|
|
66
|
+
is_flag=True,
|
|
67
|
+
help="Recursively perform the specified operations on all subdirectories within the given path. Must be used in conjunction with other options to specify the desired operations (e.g., counting, grouping). Note: currently, filtering or sorting files is not supported in recursive mode.",
|
|
68
|
+
)
|
|
69
|
+
@click.option(
|
|
70
|
+
"--gitignore",
|
|
71
|
+
"-gi",
|
|
72
|
+
is_flag=True,
|
|
73
|
+
help="Respect the .gitignore file if present in the specified directory and exclude those files and directories mentioned in the file from the search space.",
|
|
74
|
+
)
|
|
75
|
+
def cli(
|
|
76
|
+
path,
|
|
77
|
+
count,
|
|
78
|
+
group,
|
|
79
|
+
group_extension,
|
|
80
|
+
group_by,
|
|
81
|
+
filter,
|
|
82
|
+
filter_extension,
|
|
83
|
+
sort,
|
|
84
|
+
recursive,
|
|
85
|
+
gitignore,
|
|
86
|
+
):
|
|
87
|
+
if recursive:
|
|
88
|
+
if count and group_by:
|
|
89
|
+
if group_by == "mime":
|
|
90
|
+
recursive_count_files_by_mime_type(path, gitignore=gitignore)
|
|
91
|
+
elif group_by == "extension":
|
|
92
|
+
recursive_count_files_by_extension(path, gitignore=gitignore)
|
|
93
|
+
elif count and not group_by and not group and not group_extension:
|
|
94
|
+
recursive_count_files(path, gitignore=gitignore)
|
|
95
|
+
elif count and group:
|
|
96
|
+
recursive_count_files_by_mime_type(path, gitignore=gitignore)
|
|
97
|
+
elif count and group_extension:
|
|
98
|
+
recursive_count_files_by_extension(path, gitignore=gitignore)
|
|
99
|
+
elif group_extension:
|
|
100
|
+
recursive_group_files_by_extension(path, gitignore=gitignore)
|
|
101
|
+
elif group:
|
|
102
|
+
recursive_group_files_by_mime_type(path, gitignore=gitignore)
|
|
103
|
+
elif group_by:
|
|
104
|
+
if group_by == "mime":
|
|
105
|
+
recursive_group_files_by_mime_type(path, gitignore=gitignore)
|
|
106
|
+
elif group_by == "extension":
|
|
107
|
+
recursive_group_files_by_extension(path, gitignore=gitignore)
|
|
108
|
+
else:
|
|
109
|
+
recursive_list_files(path, gitignore=gitignore)
|
|
110
|
+
else:
|
|
111
|
+
if count and group_by:
|
|
112
|
+
if group_by == "mime":
|
|
113
|
+
count_files_by_mime_type(path, gitignore=gitignore)
|
|
114
|
+
elif group_by == "extension":
|
|
115
|
+
count_files_by_extension(path, gitignore=gitignore)
|
|
116
|
+
elif count and not group_by and not group and not group_extension:
|
|
117
|
+
count_files(path, gitignore=gitignore)
|
|
118
|
+
elif count and group:
|
|
119
|
+
count_files_by_mime_type(path, gitignore=gitignore)
|
|
120
|
+
elif count and group_extension:
|
|
121
|
+
count_files_by_extension(path, gitignore=gitignore)
|
|
122
|
+
elif group_extension:
|
|
123
|
+
group_files_by_extension(path, gitignore=gitignore)
|
|
124
|
+
elif group:
|
|
125
|
+
group_files_by_mime_type(path, gitignore=gitignore)
|
|
126
|
+
elif group_by:
|
|
127
|
+
if group_by == "mime":
|
|
128
|
+
group_files_by_mime_type(path, gitignore=gitignore)
|
|
129
|
+
elif group_by == "extension":
|
|
130
|
+
group_files_by_extension(path, gitignore=gitignore)
|
|
131
|
+
elif count and filter:
|
|
132
|
+
count_filter_files_by_mime_type(path, filter, gitignore=gitignore)
|
|
133
|
+
elif count and filter_extension:
|
|
134
|
+
count_filter_files_by_extension(path, filter_extension, gitignore=gitignore)
|
|
135
|
+
elif filter_extension:
|
|
136
|
+
filter_files_by_extension(path, filter_extension, gitignore=gitignore)
|
|
137
|
+
elif filter:
|
|
138
|
+
filter_files_by_mime_type(path, filter, gitignore=gitignore)
|
|
139
|
+
elif sort:
|
|
140
|
+
sort_files(path, sort, gitignore=gitignore)
|
|
141
|
+
else:
|
|
142
|
+
list_files(path, gitignore=gitignore)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
if __name__ == "__main__":
|
|
146
|
+
cli()
|