filetree-cli 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.
- filetree_cli-0.1.0/.gitignore +2 -0
- filetree_cli-0.1.0/LICENSE +21 -0
- filetree_cli-0.1.0/PKG-INFO +170 -0
- filetree_cli-0.1.0/README.md +142 -0
- filetree_cli-0.1.0/SKILL.md +96 -0
- filetree_cli-0.1.0/filetree/__init__.py +3 -0
- filetree_cli-0.1.0/filetree/cli.py +271 -0
- filetree_cli-0.1.0/pyproject.toml +44 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marcus
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: filetree-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generate ASCII or Markdown directory trees with smart filtering
|
|
5
|
+
Project-URL: Homepage, https://github.com/marcusbuildsthings-droid/filetree
|
|
6
|
+
Project-URL: Repository, https://github.com/marcusbuildsthings-droid/filetree
|
|
7
|
+
Project-URL: Issues, https://github.com/marcusbuildsthings-droid/filetree/issues
|
|
8
|
+
Author-email: Marcus <marcus.builds.things@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ascii,cli,directory,file,markdown,structure,tree
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
24
|
+
Classifier: Topic :: Utilities
|
|
25
|
+
Requires-Python: >=3.8
|
|
26
|
+
Requires-Dist: click>=8.0.0
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# filetree
|
|
30
|
+
|
|
31
|
+
Generate ASCII or Markdown directory trees with smart filtering.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install filetree-cli
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Basic usage - current directory
|
|
43
|
+
filetree
|
|
44
|
+
|
|
45
|
+
# Specific path with depth limit
|
|
46
|
+
filetree ~/projects -d 2
|
|
47
|
+
|
|
48
|
+
# Markdown output for documentation
|
|
49
|
+
filetree . --md
|
|
50
|
+
|
|
51
|
+
# Ignore additional patterns
|
|
52
|
+
filetree . -I "*.log" -I "tmp"
|
|
53
|
+
|
|
54
|
+
# Show only directories
|
|
55
|
+
filetree . -D
|
|
56
|
+
|
|
57
|
+
# Show hidden files with sizes and stats
|
|
58
|
+
filetree . -a --size --stats
|
|
59
|
+
|
|
60
|
+
# JSON output for programmatic use
|
|
61
|
+
filetree . --json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Features
|
|
65
|
+
|
|
66
|
+
- **Smart defaults**: Ignores common cruft (.git, node_modules, __pycache__, etc.)
|
|
67
|
+
- **Depth limiting**: Control how deep the tree goes
|
|
68
|
+
- **Pattern ignoring**: Glob patterns to exclude files/directories
|
|
69
|
+
- **Multiple formats**: ASCII (default), Markdown, or JSON
|
|
70
|
+
- **Size display**: Optional file size annotations
|
|
71
|
+
- **Statistics**: File/directory counts and total size
|
|
72
|
+
- **Hidden files**: Toggle visibility of dotfiles
|
|
73
|
+
|
|
74
|
+
## Default Ignored Patterns
|
|
75
|
+
|
|
76
|
+
These patterns are ignored by default (use `--no-default-ignore` to include them):
|
|
77
|
+
|
|
78
|
+
- `.git`, `.svn`, `.hg`
|
|
79
|
+
- `__pycache__`, `*.pyc`
|
|
80
|
+
- `node_modules`
|
|
81
|
+
- `.venv`, `venv`, `.env`
|
|
82
|
+
- `*.egg-info`, `.pytest_cache`, `.mypy_cache`
|
|
83
|
+
- `dist`, `build`
|
|
84
|
+
- `.idea`, `.vscode`
|
|
85
|
+
- `.DS_Store`
|
|
86
|
+
|
|
87
|
+
## Examples
|
|
88
|
+
|
|
89
|
+
### Basic Tree
|
|
90
|
+
```
|
|
91
|
+
$ filetree
|
|
92
|
+
myproject/
|
|
93
|
+
├── README.md
|
|
94
|
+
├── pyproject.toml
|
|
95
|
+
└── src/
|
|
96
|
+
├── __init__.py
|
|
97
|
+
└── main.py
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Markdown Output
|
|
101
|
+
```
|
|
102
|
+
$ filetree --md
|
|
103
|
+
```
|
|
104
|
+
```
|
|
105
|
+
myproject/
|
|
106
|
+
├── README.md
|
|
107
|
+
├── pyproject.toml
|
|
108
|
+
└── src/
|
|
109
|
+
├── __init__.py
|
|
110
|
+
└── main.py
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### With Stats
|
|
114
|
+
```
|
|
115
|
+
$ filetree --stats --size
|
|
116
|
+
myproject/
|
|
117
|
+
├── README.md (1.2KB)
|
|
118
|
+
├── pyproject.toml (856B)
|
|
119
|
+
└── src/
|
|
120
|
+
├── __init__.py (45B)
|
|
121
|
+
└── main.py (2.3KB)
|
|
122
|
+
|
|
123
|
+
1 directories, 4 files
|
|
124
|
+
Total size: 4.4KB
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### JSON Output
|
|
128
|
+
```
|
|
129
|
+
$ filetree --json
|
|
130
|
+
{
|
|
131
|
+
"root": "/path/to/myproject",
|
|
132
|
+
"tree": [
|
|
133
|
+
"myproject/",
|
|
134
|
+
"├── README.md",
|
|
135
|
+
"├── pyproject.toml",
|
|
136
|
+
"└── src/",
|
|
137
|
+
" ├── __init__.py",
|
|
138
|
+
" └── main.py"
|
|
139
|
+
],
|
|
140
|
+
"stats": {
|
|
141
|
+
"directories": 1,
|
|
142
|
+
"files": 4,
|
|
143
|
+
"total_size": 4501,
|
|
144
|
+
"total_size_human": "4.4KB"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Options
|
|
150
|
+
|
|
151
|
+
| Option | Description |
|
|
152
|
+
|--------|-------------|
|
|
153
|
+
| `-d, --depth N` | Maximum depth (-1 for unlimited) |
|
|
154
|
+
| `-I, --ignore PATTERN` | Patterns to ignore (repeatable) |
|
|
155
|
+
| `--no-default-ignore` | Don't use default ignore patterns |
|
|
156
|
+
| `-a, --all` | Show hidden files/directories |
|
|
157
|
+
| `-D, --dirs-only` | Show only directories |
|
|
158
|
+
| `-s, --size` | Show file sizes |
|
|
159
|
+
| `--stats` | Show summary statistics |
|
|
160
|
+
| `--md, --markdown` | Output as markdown code block |
|
|
161
|
+
| `--json` | Output as JSON |
|
|
162
|
+
| `--version` | Show version |
|
|
163
|
+
|
|
164
|
+
## For AI Agents
|
|
165
|
+
|
|
166
|
+
See [SKILL.md](SKILL.md) for agent-optimized documentation.
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
MIT
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# filetree
|
|
2
|
+
|
|
3
|
+
Generate ASCII or Markdown directory trees with smart filtering.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install filetree-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Basic usage - current directory
|
|
15
|
+
filetree
|
|
16
|
+
|
|
17
|
+
# Specific path with depth limit
|
|
18
|
+
filetree ~/projects -d 2
|
|
19
|
+
|
|
20
|
+
# Markdown output for documentation
|
|
21
|
+
filetree . --md
|
|
22
|
+
|
|
23
|
+
# Ignore additional patterns
|
|
24
|
+
filetree . -I "*.log" -I "tmp"
|
|
25
|
+
|
|
26
|
+
# Show only directories
|
|
27
|
+
filetree . -D
|
|
28
|
+
|
|
29
|
+
# Show hidden files with sizes and stats
|
|
30
|
+
filetree . -a --size --stats
|
|
31
|
+
|
|
32
|
+
# JSON output for programmatic use
|
|
33
|
+
filetree . --json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
- **Smart defaults**: Ignores common cruft (.git, node_modules, __pycache__, etc.)
|
|
39
|
+
- **Depth limiting**: Control how deep the tree goes
|
|
40
|
+
- **Pattern ignoring**: Glob patterns to exclude files/directories
|
|
41
|
+
- **Multiple formats**: ASCII (default), Markdown, or JSON
|
|
42
|
+
- **Size display**: Optional file size annotations
|
|
43
|
+
- **Statistics**: File/directory counts and total size
|
|
44
|
+
- **Hidden files**: Toggle visibility of dotfiles
|
|
45
|
+
|
|
46
|
+
## Default Ignored Patterns
|
|
47
|
+
|
|
48
|
+
These patterns are ignored by default (use `--no-default-ignore` to include them):
|
|
49
|
+
|
|
50
|
+
- `.git`, `.svn`, `.hg`
|
|
51
|
+
- `__pycache__`, `*.pyc`
|
|
52
|
+
- `node_modules`
|
|
53
|
+
- `.venv`, `venv`, `.env`
|
|
54
|
+
- `*.egg-info`, `.pytest_cache`, `.mypy_cache`
|
|
55
|
+
- `dist`, `build`
|
|
56
|
+
- `.idea`, `.vscode`
|
|
57
|
+
- `.DS_Store`
|
|
58
|
+
|
|
59
|
+
## Examples
|
|
60
|
+
|
|
61
|
+
### Basic Tree
|
|
62
|
+
```
|
|
63
|
+
$ filetree
|
|
64
|
+
myproject/
|
|
65
|
+
├── README.md
|
|
66
|
+
├── pyproject.toml
|
|
67
|
+
└── src/
|
|
68
|
+
├── __init__.py
|
|
69
|
+
└── main.py
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Markdown Output
|
|
73
|
+
```
|
|
74
|
+
$ filetree --md
|
|
75
|
+
```
|
|
76
|
+
```
|
|
77
|
+
myproject/
|
|
78
|
+
├── README.md
|
|
79
|
+
├── pyproject.toml
|
|
80
|
+
└── src/
|
|
81
|
+
├── __init__.py
|
|
82
|
+
└── main.py
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### With Stats
|
|
86
|
+
```
|
|
87
|
+
$ filetree --stats --size
|
|
88
|
+
myproject/
|
|
89
|
+
├── README.md (1.2KB)
|
|
90
|
+
├── pyproject.toml (856B)
|
|
91
|
+
└── src/
|
|
92
|
+
├── __init__.py (45B)
|
|
93
|
+
└── main.py (2.3KB)
|
|
94
|
+
|
|
95
|
+
1 directories, 4 files
|
|
96
|
+
Total size: 4.4KB
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### JSON Output
|
|
100
|
+
```
|
|
101
|
+
$ filetree --json
|
|
102
|
+
{
|
|
103
|
+
"root": "/path/to/myproject",
|
|
104
|
+
"tree": [
|
|
105
|
+
"myproject/",
|
|
106
|
+
"├── README.md",
|
|
107
|
+
"├── pyproject.toml",
|
|
108
|
+
"└── src/",
|
|
109
|
+
" ├── __init__.py",
|
|
110
|
+
" └── main.py"
|
|
111
|
+
],
|
|
112
|
+
"stats": {
|
|
113
|
+
"directories": 1,
|
|
114
|
+
"files": 4,
|
|
115
|
+
"total_size": 4501,
|
|
116
|
+
"total_size_human": "4.4KB"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Options
|
|
122
|
+
|
|
123
|
+
| Option | Description |
|
|
124
|
+
|--------|-------------|
|
|
125
|
+
| `-d, --depth N` | Maximum depth (-1 for unlimited) |
|
|
126
|
+
| `-I, --ignore PATTERN` | Patterns to ignore (repeatable) |
|
|
127
|
+
| `--no-default-ignore` | Don't use default ignore patterns |
|
|
128
|
+
| `-a, --all` | Show hidden files/directories |
|
|
129
|
+
| `-D, --dirs-only` | Show only directories |
|
|
130
|
+
| `-s, --size` | Show file sizes |
|
|
131
|
+
| `--stats` | Show summary statistics |
|
|
132
|
+
| `--md, --markdown` | Output as markdown code block |
|
|
133
|
+
| `--json` | Output as JSON |
|
|
134
|
+
| `--version` | Show version |
|
|
135
|
+
|
|
136
|
+
## For AI Agents
|
|
137
|
+
|
|
138
|
+
See [SKILL.md](SKILL.md) for agent-optimized documentation.
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
MIT
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# filetree Skill
|
|
2
|
+
|
|
3
|
+
Generate directory trees in ASCII, Markdown, or JSON format.
|
|
4
|
+
|
|
5
|
+
## Quick Reference
|
|
6
|
+
|
|
7
|
+
| Command | Description |
|
|
8
|
+
|---------|-------------|
|
|
9
|
+
| `filetree [PATH]` | Generate tree for directory |
|
|
10
|
+
| `filetree -d N` | Limit depth to N levels |
|
|
11
|
+
| `filetree -D` | Directories only |
|
|
12
|
+
| `filetree --md` | Markdown code block output |
|
|
13
|
+
| `filetree --json` | JSON output with stats |
|
|
14
|
+
| `filetree --stats` | Show file/dir counts |
|
|
15
|
+
|
|
16
|
+
## Common Patterns
|
|
17
|
+
|
|
18
|
+
### Basic tree with depth limit
|
|
19
|
+
```bash
|
|
20
|
+
filetree . -d 3
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Documentation-ready markdown
|
|
24
|
+
```bash
|
|
25
|
+
filetree ~/project --md > tree.md
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Directories only for large projects
|
|
29
|
+
```bash
|
|
30
|
+
filetree . -D -d 2
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Full analysis with sizes
|
|
34
|
+
```bash
|
|
35
|
+
filetree . -a --size --stats
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Ignore specific patterns
|
|
39
|
+
```bash
|
|
40
|
+
filetree . -I "*.log" -I "tmp" -I "cache"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Machine-readable JSON
|
|
44
|
+
```bash
|
|
45
|
+
filetree . --json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## JSON Output Format
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"root": "/absolute/path",
|
|
53
|
+
"tree": [
|
|
54
|
+
"dirname/",
|
|
55
|
+
"├── file1.txt",
|
|
56
|
+
"└── subdir/",
|
|
57
|
+
" └── file2.txt"
|
|
58
|
+
],
|
|
59
|
+
"stats": {
|
|
60
|
+
"directories": 1,
|
|
61
|
+
"files": 2,
|
|
62
|
+
"total_size": 1234,
|
|
63
|
+
"total_size_human": "1.2KB"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Default Ignored Patterns
|
|
69
|
+
|
|
70
|
+
These are automatically ignored (use `--no-default-ignore` to include):
|
|
71
|
+
- `.git`, `.svn`, `.hg` (version control)
|
|
72
|
+
- `__pycache__`, `*.pyc` (Python cache)
|
|
73
|
+
- `node_modules` (Node.js)
|
|
74
|
+
- `.venv`, `venv` (virtual environments)
|
|
75
|
+
- `dist`, `build` (build outputs)
|
|
76
|
+
- `.idea`, `.vscode` (IDE configs)
|
|
77
|
+
|
|
78
|
+
## Exit Codes
|
|
79
|
+
|
|
80
|
+
| Code | Meaning |
|
|
81
|
+
|------|---------|
|
|
82
|
+
| 0 | Success |
|
|
83
|
+
| 1 | Path not found or not a directory |
|
|
84
|
+
|
|
85
|
+
## Integration Tips
|
|
86
|
+
|
|
87
|
+
1. Use `--json` for programmatic parsing
|
|
88
|
+
2. Use `-D -d 2` for quick project overview
|
|
89
|
+
3. Combine `--md` with documentation generators
|
|
90
|
+
4. Add custom `-I` patterns for project-specific cruft
|
|
91
|
+
|
|
92
|
+
## Installation
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pip install filetree-cli
|
|
96
|
+
```
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""CLI for filetree - directory tree generator."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from fnmatch import fnmatch
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import List, Optional, Set, Tuple
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
|
|
12
|
+
from . import __version__
|
|
13
|
+
|
|
14
|
+
# Default patterns to ignore
|
|
15
|
+
DEFAULT_IGNORES = [
|
|
16
|
+
".git",
|
|
17
|
+
".svn",
|
|
18
|
+
".hg",
|
|
19
|
+
"__pycache__",
|
|
20
|
+
"*.pyc",
|
|
21
|
+
".DS_Store",
|
|
22
|
+
"node_modules",
|
|
23
|
+
".venv",
|
|
24
|
+
"venv",
|
|
25
|
+
".env",
|
|
26
|
+
"*.egg-info",
|
|
27
|
+
".pytest_cache",
|
|
28
|
+
".mypy_cache",
|
|
29
|
+
".ruff_cache",
|
|
30
|
+
".tox",
|
|
31
|
+
"dist",
|
|
32
|
+
"build",
|
|
33
|
+
".idea",
|
|
34
|
+
".vscode",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
# Tree characters
|
|
38
|
+
TREE_BRANCH = "├── "
|
|
39
|
+
TREE_LAST = "└── "
|
|
40
|
+
TREE_PIPE = "│ "
|
|
41
|
+
TREE_SPACE = " "
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def should_ignore(name: str, path: Path, patterns: List[str]) -> bool:
|
|
45
|
+
"""Check if a file/dir should be ignored based on patterns."""
|
|
46
|
+
for pattern in patterns:
|
|
47
|
+
if fnmatch(name, pattern):
|
|
48
|
+
return True
|
|
49
|
+
# Also check full path for patterns with /
|
|
50
|
+
if "/" in pattern and fnmatch(str(path), pattern):
|
|
51
|
+
return True
|
|
52
|
+
return True if name.startswith(".") and ".*" in patterns else False
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_size(path: Path) -> int:
|
|
56
|
+
"""Get file size, 0 for directories."""
|
|
57
|
+
try:
|
|
58
|
+
if path.is_file():
|
|
59
|
+
return path.stat().st_size
|
|
60
|
+
return 0
|
|
61
|
+
except (OSError, PermissionError):
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def format_size(size: int) -> str:
|
|
66
|
+
"""Format bytes to human readable."""
|
|
67
|
+
for unit in ["B", "KB", "MB", "GB", "TB"]:
|
|
68
|
+
if size < 1024:
|
|
69
|
+
return f"{size:.1f}{unit}" if unit != "B" else f"{size}{unit}"
|
|
70
|
+
size /= 1024
|
|
71
|
+
return f"{size:.1f}PB"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def generate_tree(
|
|
75
|
+
root: Path,
|
|
76
|
+
prefix: str = "",
|
|
77
|
+
depth: int = -1,
|
|
78
|
+
current_depth: int = 0,
|
|
79
|
+
ignore_patterns: List[str] = None,
|
|
80
|
+
dirs_only: bool = False,
|
|
81
|
+
show_hidden: bool = False,
|
|
82
|
+
show_size: bool = False,
|
|
83
|
+
stats: dict = None,
|
|
84
|
+
) -> List[str]:
|
|
85
|
+
"""Generate tree lines for a directory."""
|
|
86
|
+
if ignore_patterns is None:
|
|
87
|
+
ignore_patterns = []
|
|
88
|
+
|
|
89
|
+
if stats is None:
|
|
90
|
+
stats = {"dirs": 0, "files": 0, "size": 0}
|
|
91
|
+
|
|
92
|
+
lines = []
|
|
93
|
+
|
|
94
|
+
# Check depth limit
|
|
95
|
+
if depth >= 0 and current_depth >= depth:
|
|
96
|
+
return lines
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
entries = sorted(root.iterdir(), key=lambda p: (not p.is_dir(), p.name.lower()))
|
|
100
|
+
except PermissionError:
|
|
101
|
+
return lines
|
|
102
|
+
|
|
103
|
+
# Filter entries
|
|
104
|
+
filtered = []
|
|
105
|
+
for entry in entries:
|
|
106
|
+
name = entry.name
|
|
107
|
+
|
|
108
|
+
# Skip hidden unless requested
|
|
109
|
+
if not show_hidden and name.startswith("."):
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
# Skip ignored patterns
|
|
113
|
+
skip = False
|
|
114
|
+
for pattern in ignore_patterns:
|
|
115
|
+
if fnmatch(name, pattern):
|
|
116
|
+
skip = True
|
|
117
|
+
break
|
|
118
|
+
if skip:
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
# Skip files if dirs_only
|
|
122
|
+
if dirs_only and entry.is_file():
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
filtered.append(entry)
|
|
126
|
+
|
|
127
|
+
# Generate tree lines
|
|
128
|
+
for i, entry in enumerate(filtered):
|
|
129
|
+
is_last = i == len(filtered) - 1
|
|
130
|
+
connector = TREE_LAST if is_last else TREE_BRANCH
|
|
131
|
+
|
|
132
|
+
# Build the line
|
|
133
|
+
line = prefix + connector + entry.name
|
|
134
|
+
|
|
135
|
+
if entry.is_dir():
|
|
136
|
+
line += "/"
|
|
137
|
+
stats["dirs"] += 1
|
|
138
|
+
else:
|
|
139
|
+
stats["files"] += 1
|
|
140
|
+
size = get_size(entry)
|
|
141
|
+
stats["size"] += size
|
|
142
|
+
if show_size:
|
|
143
|
+
line += f" ({format_size(size)})"
|
|
144
|
+
|
|
145
|
+
lines.append(line)
|
|
146
|
+
|
|
147
|
+
# Recurse into directories
|
|
148
|
+
if entry.is_dir():
|
|
149
|
+
extension = TREE_SPACE if is_last else TREE_PIPE
|
|
150
|
+
lines.extend(
|
|
151
|
+
generate_tree(
|
|
152
|
+
entry,
|
|
153
|
+
prefix=prefix + extension,
|
|
154
|
+
depth=depth,
|
|
155
|
+
current_depth=current_depth + 1,
|
|
156
|
+
ignore_patterns=ignore_patterns,
|
|
157
|
+
dirs_only=dirs_only,
|
|
158
|
+
show_hidden=show_hidden,
|
|
159
|
+
show_size=show_size,
|
|
160
|
+
stats=stats,
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
return lines
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def tree_to_markdown(root_name: str, lines: List[str]) -> str:
|
|
168
|
+
"""Convert tree lines to markdown code block."""
|
|
169
|
+
output = f"```\n{root_name}/\n"
|
|
170
|
+
output += "\n".join(lines)
|
|
171
|
+
output += "\n```"
|
|
172
|
+
return output
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def tree_to_json(root: Path, lines: List[str], stats: dict) -> dict:
|
|
176
|
+
"""Convert tree to JSON structure."""
|
|
177
|
+
return {
|
|
178
|
+
"root": str(root.absolute()),
|
|
179
|
+
"tree": [root.name + "/"] + lines,
|
|
180
|
+
"stats": {
|
|
181
|
+
"directories": stats["dirs"],
|
|
182
|
+
"files": stats["files"],
|
|
183
|
+
"total_size": stats["size"],
|
|
184
|
+
"total_size_human": format_size(stats["size"]),
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@click.command()
|
|
190
|
+
@click.argument("path", default=".", type=click.Path(exists=True))
|
|
191
|
+
@click.option("-d", "--depth", default=-1, type=int, help="Maximum depth (-1 for unlimited)")
|
|
192
|
+
@click.option("-I", "--ignore", "ignores", multiple=True, help="Patterns to ignore (can repeat)")
|
|
193
|
+
@click.option("--no-default-ignore", is_flag=True, help="Don't use default ignore patterns")
|
|
194
|
+
@click.option("-a", "--all", "show_hidden", is_flag=True, help="Show hidden files/dirs")
|
|
195
|
+
@click.option("-D", "--dirs-only", is_flag=True, help="Show only directories")
|
|
196
|
+
@click.option("-s", "--size", "show_size", is_flag=True, help="Show file sizes")
|
|
197
|
+
@click.option("--stats", "show_stats", is_flag=True, help="Show summary statistics")
|
|
198
|
+
@click.option("--md", "--markdown", "markdown", is_flag=True, help="Output as markdown code block")
|
|
199
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
200
|
+
@click.option("--version", is_flag=True, help="Show version")
|
|
201
|
+
def main(
|
|
202
|
+
path: str,
|
|
203
|
+
depth: int,
|
|
204
|
+
ignores: Tuple[str, ...],
|
|
205
|
+
no_default_ignore: bool,
|
|
206
|
+
show_hidden: bool,
|
|
207
|
+
dirs_only: bool,
|
|
208
|
+
show_size: bool,
|
|
209
|
+
show_stats: bool,
|
|
210
|
+
markdown: bool,
|
|
211
|
+
as_json: bool,
|
|
212
|
+
version: bool,
|
|
213
|
+
):
|
|
214
|
+
"""Generate directory tree for PATH.
|
|
215
|
+
|
|
216
|
+
Examples:
|
|
217
|
+
|
|
218
|
+
filetree # Current directory
|
|
219
|
+
filetree ~/projects -d 2 # Max depth 2
|
|
220
|
+
filetree . --md # Markdown output
|
|
221
|
+
filetree . -I "*.log" -I tmp # Ignore patterns
|
|
222
|
+
filetree . -D # Directories only
|
|
223
|
+
filetree . -a --size --stats # Show all with sizes and stats
|
|
224
|
+
"""
|
|
225
|
+
if version:
|
|
226
|
+
click.echo(f"filetree {__version__}")
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
root = Path(path).resolve()
|
|
230
|
+
|
|
231
|
+
if not root.is_dir():
|
|
232
|
+
click.echo(f"Error: {path} is not a directory", err=True)
|
|
233
|
+
sys.exit(1)
|
|
234
|
+
|
|
235
|
+
# Build ignore patterns
|
|
236
|
+
ignore_patterns = []
|
|
237
|
+
if not no_default_ignore:
|
|
238
|
+
ignore_patterns.extend(DEFAULT_IGNORES)
|
|
239
|
+
ignore_patterns.extend(ignores)
|
|
240
|
+
|
|
241
|
+
# Generate tree
|
|
242
|
+
stats = {"dirs": 0, "files": 0, "size": 0}
|
|
243
|
+
lines = generate_tree(
|
|
244
|
+
root,
|
|
245
|
+
depth=depth,
|
|
246
|
+
ignore_patterns=ignore_patterns,
|
|
247
|
+
dirs_only=dirs_only,
|
|
248
|
+
show_hidden=show_hidden,
|
|
249
|
+
show_size=show_size,
|
|
250
|
+
stats=stats,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# Output
|
|
254
|
+
if as_json:
|
|
255
|
+
output = tree_to_json(root, lines, stats)
|
|
256
|
+
click.echo(json.dumps(output, indent=2))
|
|
257
|
+
elif markdown:
|
|
258
|
+
click.echo(tree_to_markdown(root.name, lines))
|
|
259
|
+
else:
|
|
260
|
+
click.echo(f"{root.name}/")
|
|
261
|
+
click.echo("\n".join(lines))
|
|
262
|
+
|
|
263
|
+
if show_stats:
|
|
264
|
+
click.echo()
|
|
265
|
+
click.echo(f"{stats['dirs']} directories, {stats['files']} files")
|
|
266
|
+
if stats["size"] > 0:
|
|
267
|
+
click.echo(f"Total size: {format_size(stats['size'])}")
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
if __name__ == "__main__":
|
|
271
|
+
main()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "filetree-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Generate ASCII or Markdown directory trees with smart filtering"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Marcus", email = "marcus.builds.things@gmail.com" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["directory", "tree", "cli", "file", "structure", "ascii", "markdown"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.8",
|
|
24
|
+
"Programming Language :: Python :: 3.9",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Topic :: Utilities",
|
|
29
|
+
"Topic :: Software Development :: Documentation",
|
|
30
|
+
]
|
|
31
|
+
dependencies = [
|
|
32
|
+
"click>=8.0.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
filetree = "filetree.cli:main"
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://github.com/marcusbuildsthings-droid/filetree"
|
|
40
|
+
Repository = "https://github.com/marcusbuildsthings-droid/filetree"
|
|
41
|
+
Issues = "https://github.com/marcusbuildsthings-droid/filetree/issues"
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.targets.wheel]
|
|
44
|
+
packages = ["filetree"]
|