dirlens 1.0.1 → 1.0.2
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.
- package/README.md +15 -13
- package/bin/dirlens +0 -0
- package/dirlens.py +31 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
## 出力例
|
|
9
9
|
|
|
10
10
|
```
|
|
11
|
-
Desktop/ (3.74 MB)
|
|
12
|
-
├── EmptyDir/ (0 bytes)
|
|
13
|
-
├── Project/ (712 KB)
|
|
14
|
-
│ ├── assets/ (512 KB)
|
|
15
|
-
│ │ └── images/ (512 KB)
|
|
11
|
+
Desktop/ (2 dirs, 2 files, 3.74 MB)
|
|
12
|
+
├── EmptyDir/ (0 dirs, 0 files, 0 bytes)
|
|
13
|
+
├── Project/ (2 dirs, 1 file, 712 KB)
|
|
14
|
+
│ ├── assets/ (1 dir, 0 files, 512 KB)
|
|
15
|
+
│ │ └── images/ (0 dirs, 1 file, 512 KB)
|
|
16
16
|
│ │ └── logo.png (512 KB)
|
|
17
|
-
│ ├── src/ (80 KB)
|
|
17
|
+
│ ├── src/ (0 dirs, 1 file, 80 KB)
|
|
18
18
|
│ │ └── util.py (80 KB)
|
|
19
19
|
│ └── main.py (120 KB)
|
|
20
20
|
├── archive.zip (3 MB)
|
|
@@ -31,7 +31,8 @@ Desktop/ (3.74 MB)
|
|
|
31
31
|
- **カラー表示** — ディレクトリ・ファイル・シンボリックリンクを色で識別
|
|
32
32
|
- **自動サイズ変換** — bytes / KB / MB / GB / TB
|
|
33
33
|
- **ディレクトリサイズ** — サブディレクトリの合計サイズを自動計算
|
|
34
|
-
-
|
|
34
|
+
- **アイテム数表示** — 各ディレクトリの直下にある dirs / files 数を表示
|
|
35
|
+
- **隠しファイル対応** — `-a` で表示切り替え(アイテム数にも反映)
|
|
35
36
|
- **サイズ順ソート** — `-s` で大きいものから表示
|
|
36
37
|
|
|
37
38
|
---
|
|
@@ -62,16 +63,16 @@ npm uninstall -g dirlens
|
|
|
62
63
|
|
|
63
64
|
### macOS / Linux(スクリプト直接インストール)
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
# 実行権限を付与
|
|
67
|
-
chmod +x dirlens
|
|
66
|
+
GitHubリポジトリから `dirlens.py` をダウンロードして使用します。
|
|
68
67
|
|
|
68
|
+
```bash
|
|
69
69
|
# /usr/local/bin にインストール(どこからでも呼べるようになる)
|
|
70
|
-
sudo
|
|
70
|
+
sudo install -m 755 dirlens.py /usr/local/bin/dirlens
|
|
71
71
|
|
|
72
72
|
# ── または sudo なしでユーザーローカルにインストール ──
|
|
73
73
|
mkdir -p ~/.local/bin
|
|
74
|
-
cp dirlens ~/.local/bin/
|
|
74
|
+
cp dirlens.py ~/.local/bin/dirlens
|
|
75
|
+
chmod +x ~/.local/bin/dirlens
|
|
75
76
|
|
|
76
77
|
# ~/.zshrc(zsh)または ~/.bashrc(bash)に以下を追記:
|
|
77
78
|
export PATH="$HOME/.local/bin:$PATH"
|
|
@@ -89,7 +90,7 @@ dirlens --help
|
|
|
89
90
|
|
|
90
91
|
### Windows(スクリプト直接インストール)
|
|
91
92
|
|
|
92
|
-
1. `dirlens`
|
|
93
|
+
1. `dirlens.py` と `dirlens.bat` を任意のフォルダへ置く
|
|
93
94
|
(例: `C:\Users\ユーザー名\bin\`)
|
|
94
95
|
|
|
95
96
|
2. 同じフォルダに **`dirlens.bat`** を置く(同梱のものを使用):
|
|
@@ -178,3 +179,4 @@ dirlens --no-color > tree.txt
|
|
|
178
179
|
- **シンボリックリンク先のディレクトリ**は展開せず `→` マークで表示
|
|
179
180
|
- 権限がないディレクトリは `[アクセス拒否]` と表示してスキップ
|
|
180
181
|
- 非常に深いディレクトリ(1万階層以上)は `-d` で深さを制限してください
|
|
182
|
+
- **ホームフォルダ(`~/`)やルート(`/`)で実行すると固まる場合があります** — サイズ計算は `-d` の表示制限に関わらず底まで全再帰するため、`~/Library` や iCloud Drive など大容量・ネットワークマウントのディレクトリで時間がかかります。プロジェクトフォルダなど範囲を絞って実行してください
|
package/bin/dirlens
CHANGED
|
File without changes
|
package/dirlens.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
dirlens –
|
|
3
|
+
dirlens – ファイルサイズ+アイテム数付きディレクトリツリー表示ツール
|
|
4
4
|
対応環境: macOS / Linux / Windows (Python 3.8+)
|
|
5
5
|
"""
|
|
6
6
|
|
|
@@ -14,7 +14,6 @@ def _enable_color():
|
|
|
14
14
|
if not hasattr(sys.stdout, "isatty") or not sys.stdout.isatty():
|
|
15
15
|
return False
|
|
16
16
|
if os.name == "nt":
|
|
17
|
-
# Windows: VT100モードを有効にする
|
|
18
17
|
try:
|
|
19
18
|
import ctypes
|
|
20
19
|
kernel32 = ctypes.windll.kernel32
|
|
@@ -22,8 +21,8 @@ def _enable_color():
|
|
|
22
21
|
except Exception:
|
|
23
22
|
pass
|
|
24
23
|
return bool(
|
|
25
|
-
os.environ.get("WT_SESSION")
|
|
26
|
-
or os.environ.get("TERM_PROGRAM")
|
|
24
|
+
os.environ.get("WT_SESSION")
|
|
25
|
+
or os.environ.get("TERM_PROGRAM")
|
|
27
26
|
or os.environ.get("TERM")
|
|
28
27
|
or os.environ.get("ANSICON")
|
|
29
28
|
)
|
|
@@ -40,12 +39,10 @@ GREEN = "\033[32m"
|
|
|
40
39
|
MAGENTA = "\033[35m"
|
|
41
40
|
|
|
42
41
|
def c(text, *codes):
|
|
43
|
-
"""ANSIカラーを適用する。カラー無効時はそのまま返す。"""
|
|
44
42
|
return ("".join(codes) + text + RESET) if USE_COLOR else text
|
|
45
43
|
|
|
46
44
|
# ─── サイズ表示 ───────────────────────────────────────────────
|
|
47
45
|
def fmt_size(n):
|
|
48
|
-
"""バイト数を人が読みやすい文字列に変換する。"""
|
|
49
46
|
if n == 0:
|
|
50
47
|
return "0 bytes"
|
|
51
48
|
for unit, factor in (("TB", 1 << 40), ("GB", 1 << 30), ("MB", 1 << 20), ("KB", 1 << 10)):
|
|
@@ -58,7 +55,6 @@ def fmt_size(n):
|
|
|
58
55
|
_cache = {}
|
|
59
56
|
|
|
60
57
|
def dir_size(path):
|
|
61
|
-
"""ディレクトリ以下の合計バイト数を再帰的に計算する(シンボリックリンクは追わない)。"""
|
|
62
58
|
if path in _cache:
|
|
63
59
|
return _cache[path]
|
|
64
60
|
total = 0
|
|
@@ -77,6 +73,25 @@ def dir_size(path):
|
|
|
77
73
|
_cache[path] = total
|
|
78
74
|
return total
|
|
79
75
|
|
|
76
|
+
# ─── アイテム数カウント ────────────────────────────────────────
|
|
77
|
+
def count_items(path, show_all):
|
|
78
|
+
"""ディレクトリ直下のアイテム数 (num_dirs, num_files) を返す。"""
|
|
79
|
+
try:
|
|
80
|
+
entries = list(os.scandir(path))
|
|
81
|
+
except OSError:
|
|
82
|
+
return (0, 0)
|
|
83
|
+
if not show_all:
|
|
84
|
+
entries = [e for e in entries if not e.name.startswith(".")]
|
|
85
|
+
nd = sum(1 for e in entries if e.is_dir(follow_symlinks=False))
|
|
86
|
+
nf = sum(1 for e in entries if not e.is_dir(follow_symlinks=False))
|
|
87
|
+
return (nd, nf)
|
|
88
|
+
|
|
89
|
+
def fmt_meta(nd, nf, sz):
|
|
90
|
+
"""アイテム数+サイズをまとめて文字列化する。"""
|
|
91
|
+
d_str = f"{nd} {'dir' if nd == 1 else 'dirs'}"
|
|
92
|
+
f_str = f"{nf} {'file' if nf == 1 else 'files'}"
|
|
93
|
+
return f"({d_str}, {f_str}, {fmt_size(sz)})"
|
|
94
|
+
|
|
80
95
|
# ─── ツリー描画 ───────────────────────────────────────────────
|
|
81
96
|
PIPE = "│ "
|
|
82
97
|
FORK = "├── "
|
|
@@ -84,7 +99,6 @@ LAST = "└── "
|
|
|
84
99
|
BLANK = " "
|
|
85
100
|
|
|
86
101
|
def render(path, prefix, depth, max_depth, show_all, by_size, stats):
|
|
87
|
-
"""ディレクトリ内容を再帰的にツリー表示する。"""
|
|
88
102
|
if max_depth is not None and depth >= max_depth:
|
|
89
103
|
return
|
|
90
104
|
|
|
@@ -94,10 +108,7 @@ def render(path, prefix, depth, max_depth, show_all, by_size, stats):
|
|
|
94
108
|
print(f"{prefix}{LAST}{c('[アクセス拒否]', DIM)}")
|
|
95
109
|
return
|
|
96
110
|
|
|
97
|
-
# 隠しファイルのフィルタ(オプション依存)
|
|
98
111
|
entries = [e for e in raw if show_all or not e.name.startswith(".")]
|
|
99
|
-
|
|
100
|
-
# ディレクトリ(シンボリックリンク除く)とそれ以外(ファイル+シンボリックリンク)に分類
|
|
101
112
|
dirs = [e for e in entries if e.is_dir(follow_symlinks=False)]
|
|
102
113
|
files = [e for e in entries if not e.is_dir(follow_symlinks=False)]
|
|
103
114
|
|
|
@@ -107,7 +118,6 @@ def render(path, prefix, depth, max_depth, show_all, by_size, stats):
|
|
|
107
118
|
except OSError:
|
|
108
119
|
return 0
|
|
109
120
|
|
|
110
|
-
# ソート:名前順 or サイズ順
|
|
111
121
|
if by_size:
|
|
112
122
|
dirs.sort(key=lambda e: dir_size(e.path), reverse=True)
|
|
113
123
|
files.sort(key=lambda e: entry_size(e), reverse=True)
|
|
@@ -115,7 +125,6 @@ def render(path, prefix, depth, max_depth, show_all, by_size, stats):
|
|
|
115
125
|
dirs.sort(key=lambda e: e.name.casefold())
|
|
116
126
|
files.sort(key=lambda e: e.name.casefold())
|
|
117
127
|
|
|
118
|
-
# ディレクトリを先に、次にファイル
|
|
119
128
|
combined = dirs + files
|
|
120
129
|
|
|
121
130
|
for i, entry in enumerate(combined):
|
|
@@ -124,11 +133,12 @@ def render(path, prefix, depth, max_depth, show_all, by_size, stats):
|
|
|
124
133
|
cont = BLANK if is_last else PIPE
|
|
125
134
|
|
|
126
135
|
if entry.is_dir(follow_symlinks=False):
|
|
127
|
-
sz
|
|
136
|
+
sz = dir_size(entry.path)
|
|
137
|
+
nd, nf = count_items(entry.path, show_all)
|
|
128
138
|
stats["dirs"] += 1
|
|
129
139
|
name = c(f"{entry.name}/", BOLD, CYAN)
|
|
130
|
-
|
|
131
|
-
print(f"{prefix}{branch}{name} {
|
|
140
|
+
meta = c(fmt_meta(nd, nf, sz), DIM)
|
|
141
|
+
print(f"{prefix}{branch}{name} {meta}")
|
|
132
142
|
render(entry.path, prefix + cont, depth + 1, max_depth, show_all, by_size, stats)
|
|
133
143
|
else:
|
|
134
144
|
sz = entry_size(entry)
|
|
@@ -145,7 +155,7 @@ def main():
|
|
|
145
155
|
|
|
146
156
|
ap = argparse.ArgumentParser(
|
|
147
157
|
prog="dirlens",
|
|
148
|
-
description="
|
|
158
|
+
description="ファイルサイズ+アイテム数付きのディレクトリツリーを表示します",
|
|
149
159
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
150
160
|
epilog=(
|
|
151
161
|
"使用例:\n"
|
|
@@ -175,13 +185,13 @@ def main():
|
|
|
175
185
|
print(f"エラー: '{args.path}' はディレクトリではありません", file=sys.stderr)
|
|
176
186
|
sys.exit(1)
|
|
177
187
|
|
|
178
|
-
# ドライブルート(Windows の C:\ 等)対応
|
|
179
188
|
root_label = target.name if target.name else str(target)
|
|
189
|
+
root_sz = dir_size(str(target))
|
|
190
|
+
nd, nf = count_items(str(target), args.all)
|
|
180
191
|
|
|
181
|
-
root_sz = dir_size(str(target))
|
|
182
192
|
root_name = c(f"{root_label}/", BOLD, BLUE)
|
|
183
|
-
|
|
184
|
-
print(f"{root_name} {
|
|
193
|
+
root_meta = c(fmt_meta(nd, nf, root_sz), DIM)
|
|
194
|
+
print(f"{root_name} {root_meta}")
|
|
185
195
|
|
|
186
196
|
stats = {"files": 0, "dirs": 0}
|
|
187
197
|
render(str(target), "", 0, args.depth, args.all, args.sort_size, stats)
|