dirlens 1.0.2 → 1.0.4
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 +39 -12
- package/dirlens.py +220 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ Desktop/ (2 dirs, 2 files, 3.74 MB)
|
|
|
21
21
|
└── readme.txt (50 KB)
|
|
22
22
|
|
|
23
23
|
5 ディレクトリ, 5 ファイル
|
|
24
|
+
.py ×2 .txt ×1 .zip ×1 .png ×1
|
|
24
25
|
```
|
|
25
26
|
|
|
26
27
|
---
|
|
@@ -32,7 +33,12 @@ Desktop/ (2 dirs, 2 files, 3.74 MB)
|
|
|
32
33
|
- **自動サイズ変換** — bytes / KB / MB / GB / TB
|
|
33
34
|
- **ディレクトリサイズ** — サブディレクトリの合計サイズを自動計算
|
|
34
35
|
- **アイテム数表示** — 各ディレクトリの直下にある dirs / files 数を表示
|
|
35
|
-
-
|
|
36
|
+
- **拡張子統計** — ツリー全体のファイル種別を集計してサマリーに表示
|
|
37
|
+
- **`.gitignore` 対応** — `-g` で `node_modules/` などを自動除外
|
|
38
|
+
- **最終更新日時** — `--date` で各ファイル・ディレクトリの更新日時を相対表示
|
|
39
|
+
- **拡張子フィルタ** — `-t py` など指定した拡張子のみ表示
|
|
40
|
+
- **Markdown出力** — `-m` でコードブロック形式に出力、AIチャットにそのままペースト可
|
|
41
|
+
- **隠しファイル対応** — `-a` で表示切り替え(アイテム数・統計にも反映)
|
|
36
42
|
- **サイズ順ソート** — `-s` で大きいものから表示
|
|
37
43
|
|
|
38
44
|
---
|
|
@@ -137,27 +143,47 @@ dirlens -a
|
|
|
137
143
|
# サイズの大きい順に並べる
|
|
138
144
|
dirlens -s
|
|
139
145
|
|
|
146
|
+
# .gitignore に記載されたファイル・ディレクトリを除外
|
|
147
|
+
dirlens -g
|
|
148
|
+
|
|
149
|
+
# 最終更新日時を相対表示(例: 3日前、2時間前)
|
|
150
|
+
dirlens --date
|
|
151
|
+
|
|
152
|
+
# 指定した拡張子のファイルのみ表示
|
|
153
|
+
dirlens -t py
|
|
154
|
+
dirlens -t md
|
|
155
|
+
|
|
156
|
+
# Markdown コードブロック形式で出力(AI チャットへのペースト用)
|
|
157
|
+
dirlens -m
|
|
158
|
+
|
|
140
159
|
# カラーなし(パイプ・ファイル書き出し向け)
|
|
141
160
|
dirlens --no-color
|
|
142
161
|
|
|
143
|
-
# 組み合わせ例:
|
|
144
|
-
dirlens
|
|
162
|
+
# 組み合わせ例:gitignore 除外 + Python のみ + 日付表示
|
|
163
|
+
dirlens -g -t py --date
|
|
164
|
+
|
|
165
|
+
# AI に貼り付けやすい形式で出力
|
|
166
|
+
dirlens -g -m
|
|
145
167
|
|
|
146
168
|
# テキストファイルに書き出す
|
|
147
|
-
dirlens --no-color >
|
|
169
|
+
dirlens --no-color > dirlens.txt
|
|
148
170
|
```
|
|
149
171
|
|
|
150
172
|
---
|
|
151
173
|
|
|
152
174
|
## オプション一覧
|
|
153
175
|
|
|
154
|
-
| オプション | 省略形
|
|
155
|
-
|
|
156
|
-
| `path` | —
|
|
157
|
-
| `--depth N` | `-d N`
|
|
158
|
-
| `--all` | `-a`
|
|
159
|
-
| `--sort-size` | `-s`
|
|
160
|
-
| `--
|
|
176
|
+
| オプション | 省略形 | 説明 |
|
|
177
|
+
|------------------|----------|-------------------------------------------------|
|
|
178
|
+
| `path` | — | 対象ディレクトリ(省略時はカレント) |
|
|
179
|
+
| `--depth N` | `-d N` | 表示する最大の深さ |
|
|
180
|
+
| `--all` | `-a` | 隠しファイル・ディレクトリも表示 |
|
|
181
|
+
| `--sort-size` | `-s` | サイズが大きい順に並べる |
|
|
182
|
+
| `--gitignore` | `-g` | `.gitignore` に記載されたファイルを除外(サブディレクトリも対応) |
|
|
183
|
+
| `--date` | — | 最終更新日時を相対表示(例: 3日前) |
|
|
184
|
+
| `--type EXT` | `-t EXT` | 指定した拡張子のファイルのみ表示(例: `-t py`) |
|
|
185
|
+
| `--markdown` | `-m` | Markdown コードブロック形式で出力(カラー自動無効) |
|
|
186
|
+
| `--no-color` | — | カラー表示を無効化(リダイレクト時に推奨) |
|
|
161
187
|
|
|
162
188
|
---
|
|
163
189
|
|
|
@@ -175,8 +201,9 @@ dirlens --no-color > tree.txt
|
|
|
175
201
|
|
|
176
202
|
## 仕様・注意事項
|
|
177
203
|
|
|
178
|
-
- ディレクトリのサイズは
|
|
204
|
+
- ディレクトリのサイズは **全サブファイルの合計**(隠しファイルを含む、`.gitignore` 対象も含む)
|
|
179
205
|
- **シンボリックリンク先のディレクトリ**は展開せず `→` マークで表示
|
|
180
206
|
- 権限がないディレクトリは `[アクセス拒否]` と表示してスキップ
|
|
181
207
|
- 非常に深いディレクトリ(1万階層以上)は `-d` で深さを制限してください
|
|
182
208
|
- **ホームフォルダ(`~/`)やルート(`/`)で実行すると固まる場合があります** — サイズ計算は `-d` の表示制限に関わらず底まで全再帰するため、`~/Library` や iCloud Drive など大容量・ネットワークマウントのディレクトリで時間がかかります。プロジェクトフォルダなど範囲を絞って実行してください
|
|
209
|
+
- **`-g` の否定パターン(`!` から始まる行)は現時点で非対応**です
|
package/dirlens.py
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
dirlens –
|
|
3
|
+
dirlens – ファイルサイズ付きディレクトリツリー表示ツール
|
|
4
4
|
対応環境: macOS / Linux / Windows (Python 3.8+)
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
8
|
import sys
|
|
9
9
|
import argparse
|
|
10
|
+
import fnmatch
|
|
11
|
+
import datetime
|
|
10
12
|
from pathlib import Path
|
|
11
13
|
|
|
12
14
|
# ─── カラー設定 ──────────────────────────────────────────────
|
|
@@ -41,6 +43,7 @@ MAGENTA = "\033[35m"
|
|
|
41
43
|
def c(text, *codes):
|
|
42
44
|
return ("".join(codes) + text + RESET) if USE_COLOR else text
|
|
43
45
|
|
|
46
|
+
|
|
44
47
|
# ─── サイズ表示 ───────────────────────────────────────────────
|
|
45
48
|
def fmt_size(n):
|
|
46
49
|
if n == 0:
|
|
@@ -51,6 +54,68 @@ def fmt_size(n):
|
|
|
51
54
|
return f"{s} {unit}"
|
|
52
55
|
return f"{n} {'byte' if n == 1 else 'bytes'}"
|
|
53
56
|
|
|
57
|
+
|
|
58
|
+
# ─── アイテム数表示 ────────────────────────────────────────────
|
|
59
|
+
def fmt_count(nd, nf):
|
|
60
|
+
d = f"{nd} {'dir' if nd == 1 else 'dirs'}"
|
|
61
|
+
f_str = f"{nf} {'file' if nf == 1 else 'files'}"
|
|
62
|
+
return f"{d}, {f_str}"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ─── 日時表示 ─────────────────────────────────────────────────
|
|
66
|
+
def fmt_date(mtime):
|
|
67
|
+
sec = int((datetime.datetime.now() -
|
|
68
|
+
datetime.datetime.fromtimestamp(mtime)).total_seconds())
|
|
69
|
+
if sec < 60: return "今"
|
|
70
|
+
if sec < 3600: return f"{sec // 60}分前"
|
|
71
|
+
if sec < 86400: return f"{sec // 3600}時間前"
|
|
72
|
+
days = sec // 86400
|
|
73
|
+
if days == 1: return "昨日"
|
|
74
|
+
if days < 7: return f"{days}日前"
|
|
75
|
+
if days < 30: return f"{days // 7}週間前"
|
|
76
|
+
if days < 365: return f"{days // 30}ヶ月前"
|
|
77
|
+
return f"{days // 365}年前"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ─── .gitignore サポート ──────────────────────────────────────
|
|
81
|
+
def load_gitignore(directory):
|
|
82
|
+
"""指定ディレクトリの .gitignore パターンを読み込む。"""
|
|
83
|
+
patterns = []
|
|
84
|
+
path = os.path.join(directory, ".gitignore")
|
|
85
|
+
if os.path.isfile(path):
|
|
86
|
+
try:
|
|
87
|
+
with open(path, encoding="utf-8", errors="ignore") as f:
|
|
88
|
+
for line in f:
|
|
89
|
+
line = line.strip()
|
|
90
|
+
if line and not line.startswith("#"):
|
|
91
|
+
patterns.append(line)
|
|
92
|
+
except OSError:
|
|
93
|
+
pass
|
|
94
|
+
return patterns
|
|
95
|
+
|
|
96
|
+
def is_ignored(name, rel_path, is_dir, patterns):
|
|
97
|
+
"""gitignore パターンにマッチするか判定する(簡易実装)。"""
|
|
98
|
+
rel = rel_path.replace("\\", "/")
|
|
99
|
+
for pat in patterns:
|
|
100
|
+
if pat.startswith("!"):
|
|
101
|
+
continue
|
|
102
|
+
dir_only = pat.endswith("/")
|
|
103
|
+
p = pat.rstrip("/")
|
|
104
|
+
if dir_only and not is_dir:
|
|
105
|
+
continue
|
|
106
|
+
if p.startswith("/"):
|
|
107
|
+
if fnmatch.fnmatch(rel, p.lstrip("/")):
|
|
108
|
+
return True
|
|
109
|
+
else:
|
|
110
|
+
if fnmatch.fnmatch(name, p):
|
|
111
|
+
return True
|
|
112
|
+
if fnmatch.fnmatch(rel, p):
|
|
113
|
+
return True
|
|
114
|
+
if fnmatch.fnmatch(rel, "*/" + p):
|
|
115
|
+
return True
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
|
|
54
119
|
# ─── ディレクトリサイズ(キャッシュ付き) ─────────────────────
|
|
55
120
|
_cache = {}
|
|
56
121
|
|
|
@@ -73,24 +138,42 @@ def dir_size(path):
|
|
|
73
138
|
_cache[path] = total
|
|
74
139
|
return total
|
|
75
140
|
|
|
141
|
+
|
|
76
142
|
# ─── アイテム数カウント ────────────────────────────────────────
|
|
77
|
-
def
|
|
78
|
-
"""ディレクトリ直下のアイテム数 (num_dirs, num_files) を返す。"""
|
|
143
|
+
def count_entries(path, show_all, active_pats, root, type_ext, use_gitignore=False):
|
|
79
144
|
try:
|
|
80
|
-
|
|
145
|
+
raw = list(os.scandir(path))
|
|
81
146
|
except OSError:
|
|
82
|
-
return
|
|
83
|
-
if not
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
147
|
+
return 0, 0
|
|
148
|
+
entries = [e for e in raw if show_all or not e.name.startswith(".")]
|
|
149
|
+
|
|
150
|
+
# count_entries の対象ディレクトリ自身の .gitignore も反映する
|
|
151
|
+
local_pats = active_pats
|
|
152
|
+
if use_gitignore:
|
|
153
|
+
local = load_gitignore(path)
|
|
154
|
+
if local:
|
|
155
|
+
rel_dir = os.path.relpath(path, root).replace("\\", "/")
|
|
156
|
+
adjusted = []
|
|
157
|
+
for pat in local:
|
|
158
|
+
if not pat.startswith("!") and pat.startswith("/"):
|
|
159
|
+
adjusted.append("/" + rel_dir + pat)
|
|
160
|
+
else:
|
|
161
|
+
adjusted.append(pat)
|
|
162
|
+
local_pats = active_pats + adjusted
|
|
163
|
+
|
|
164
|
+
if local_pats:
|
|
165
|
+
entries = [e for e in entries
|
|
166
|
+
if not is_ignored(e.name,
|
|
167
|
+
os.path.relpath(e.path, root),
|
|
168
|
+
e.is_dir(follow_symlinks=False),
|
|
169
|
+
local_pats)]
|
|
170
|
+
nd = sum(1 for e in entries if e.is_dir(follow_symlinks=False))
|
|
171
|
+
files = [e for e in entries if not e.is_dir(follow_symlinks=False)]
|
|
172
|
+
if type_ext:
|
|
173
|
+
files = [f for f in files
|
|
174
|
+
if os.path.splitext(f.name)[1].lower() == type_ext]
|
|
175
|
+
return nd, len(files)
|
|
176
|
+
|
|
94
177
|
|
|
95
178
|
# ─── ツリー描画 ───────────────────────────────────────────────
|
|
96
179
|
PIPE = "│ "
|
|
@@ -98,10 +181,32 @@ FORK = "├── "
|
|
|
98
181
|
LAST = "└── "
|
|
99
182
|
BLANK = " "
|
|
100
183
|
|
|
101
|
-
def render(path, prefix, depth,
|
|
184
|
+
def render(path, prefix, depth, opts, stats, active_pats):
|
|
185
|
+
"""
|
|
186
|
+
active_pats: 現在の階層で有効な gitignore パターンの累積リスト。
|
|
187
|
+
サブディレクトリに入るたびにローカルの .gitignore を読み込んで追記する。
|
|
188
|
+
リストは新規作成して渡すため、兄弟ディレクトリには影響しない。
|
|
189
|
+
"""
|
|
190
|
+
max_depth, show_all, by_size, show_date, use_gitignore, root, type_ext = opts
|
|
191
|
+
|
|
102
192
|
if max_depth is not None and depth >= max_depth:
|
|
103
193
|
return
|
|
104
194
|
|
|
195
|
+
# このディレクトリの .gitignore を読んでパターンを積み重ねる(ルートは main で読み済み)
|
|
196
|
+
if use_gitignore and depth > 0:
|
|
197
|
+
local = load_gitignore(path)
|
|
198
|
+
if local:
|
|
199
|
+
# アンカーパターン(/xxx)をルートからの相対パスに変換する
|
|
200
|
+
# 例: src/.gitignore の /build → /src/build
|
|
201
|
+
rel_dir = os.path.relpath(path, root).replace("\\", "/")
|
|
202
|
+
adjusted = []
|
|
203
|
+
for pat in local:
|
|
204
|
+
if not pat.startswith("!") and pat.startswith("/"):
|
|
205
|
+
adjusted.append("/" + rel_dir + pat)
|
|
206
|
+
else:
|
|
207
|
+
adjusted.append(pat)
|
|
208
|
+
active_pats = active_pats + adjusted # 新しいリストを作成(親に影響しない)
|
|
209
|
+
|
|
105
210
|
try:
|
|
106
211
|
raw = list(os.scandir(path))
|
|
107
212
|
except PermissionError:
|
|
@@ -109,18 +214,32 @@ def render(path, prefix, depth, max_depth, show_all, by_size, stats):
|
|
|
109
214
|
return
|
|
110
215
|
|
|
111
216
|
entries = [e for e in raw if show_all or not e.name.startswith(".")]
|
|
217
|
+
|
|
218
|
+
if active_pats:
|
|
219
|
+
entries = [e for e in entries
|
|
220
|
+
if not is_ignored(e.name,
|
|
221
|
+
os.path.relpath(e.path, root),
|
|
222
|
+
e.is_dir(follow_symlinks=False),
|
|
223
|
+
active_pats)]
|
|
224
|
+
|
|
112
225
|
dirs = [e for e in entries if e.is_dir(follow_symlinks=False)]
|
|
113
226
|
files = [e for e in entries if not e.is_dir(follow_symlinks=False)]
|
|
114
227
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
228
|
+
if type_ext:
|
|
229
|
+
files = [f for f in files
|
|
230
|
+
if os.path.splitext(f.name)[1].lower() == type_ext]
|
|
231
|
+
|
|
232
|
+
def esz(e):
|
|
233
|
+
try: return e.stat(follow_symlinks=True).st_size
|
|
234
|
+
except OSError: return 0
|
|
235
|
+
|
|
236
|
+
def emtime(e):
|
|
237
|
+
try: return e.stat(follow_symlinks=True).st_mtime
|
|
238
|
+
except OSError: return 0
|
|
120
239
|
|
|
121
240
|
if by_size:
|
|
122
241
|
dirs.sort(key=lambda e: dir_size(e.path), reverse=True)
|
|
123
|
-
files.sort(key=lambda e:
|
|
242
|
+
files.sort(key=lambda e: esz(e), reverse=True)
|
|
124
243
|
else:
|
|
125
244
|
dirs.sort(key=lambda e: e.name.casefold())
|
|
126
245
|
files.sort(key=lambda e: e.name.casefold())
|
|
@@ -133,20 +252,41 @@ def render(path, prefix, depth, max_depth, show_all, by_size, stats):
|
|
|
133
252
|
cont = BLANK if is_last else PIPE
|
|
134
253
|
|
|
135
254
|
if entry.is_dir(follow_symlinks=False):
|
|
136
|
-
sz
|
|
137
|
-
nd, nf
|
|
255
|
+
sz = dir_size(entry.path)
|
|
256
|
+
nd, nf = count_entries(entry.path, show_all, active_pats, root, type_ext, use_gitignore)
|
|
138
257
|
stats["dirs"] += 1
|
|
258
|
+
|
|
259
|
+
parts = [fmt_count(nd, nf), fmt_size(sz)]
|
|
260
|
+
if show_date:
|
|
261
|
+
try:
|
|
262
|
+
parts.append(fmt_date(entry.stat(follow_symlinks=False).st_mtime))
|
|
263
|
+
except OSError:
|
|
264
|
+
pass
|
|
265
|
+
|
|
139
266
|
name = c(f"{entry.name}/", BOLD, CYAN)
|
|
140
|
-
meta = c(
|
|
267
|
+
meta = c(f"({', '.join(parts)})", DIM)
|
|
141
268
|
print(f"{prefix}{branch}{name} {meta}")
|
|
142
|
-
render(entry.path, prefix + cont, depth + 1,
|
|
269
|
+
render(entry.path, prefix + cont, depth + 1, opts, stats, active_pats)
|
|
270
|
+
|
|
143
271
|
else:
|
|
144
|
-
sz =
|
|
272
|
+
sz = esz(entry)
|
|
145
273
|
sym = " →" if entry.is_symlink() else ""
|
|
146
274
|
stats["files"] += 1
|
|
275
|
+
|
|
276
|
+
ext = os.path.splitext(entry.name)[1].lower()
|
|
277
|
+
key = ext if ext else "(no ext)"
|
|
278
|
+
stats["extensions"][key] = stats["extensions"].get(key, 0) + 1
|
|
279
|
+
|
|
280
|
+
parts = [fmt_size(sz)]
|
|
281
|
+
if show_date:
|
|
282
|
+
mt = emtime(entry)
|
|
283
|
+
if mt:
|
|
284
|
+
parts.append(fmt_date(mt))
|
|
285
|
+
|
|
147
286
|
name = c(f"{entry.name}{sym}", MAGENTA if entry.is_symlink() else GREEN)
|
|
148
|
-
|
|
149
|
-
print(f"{prefix}{branch}{name} {
|
|
287
|
+
meta = c(f"({', '.join(parts)})", DIM)
|
|
288
|
+
print(f"{prefix}{branch}{name} {meta}")
|
|
289
|
+
|
|
150
290
|
|
|
151
291
|
# ─── エントリポイント ─────────────────────────────────────────
|
|
152
292
|
def main():
|
|
@@ -155,7 +295,7 @@ def main():
|
|
|
155
295
|
|
|
156
296
|
ap = argparse.ArgumentParser(
|
|
157
297
|
prog="dirlens",
|
|
158
|
-
description="
|
|
298
|
+
description="ファイルサイズ付きのディレクトリツリーを表示します",
|
|
159
299
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
160
300
|
epilog=(
|
|
161
301
|
"使用例:\n"
|
|
@@ -164,17 +304,26 @@ def main():
|
|
|
164
304
|
" dirlens -d 2 深さ 2 階層まで表示\n"
|
|
165
305
|
" dirlens -a 隠しファイル (.xxx) も表示\n"
|
|
166
306
|
" dirlens -s サイズの大きい順に表示\n"
|
|
307
|
+
" dirlens -g .gitignore のファイルを除外\n"
|
|
308
|
+
" dirlens --date 最終更新日時を表示\n"
|
|
309
|
+
" dirlens -t py .py ファイルのみ表示\n"
|
|
310
|
+
" dirlens -m Markdown コードブロックで出力\n"
|
|
167
311
|
" dirlens --no-color カラーなしで表示"
|
|
312
|
+
" dirlens > dirlens.txt dirlens.txtに書き出す"
|
|
168
313
|
),
|
|
169
314
|
)
|
|
170
315
|
ap.add_argument("path", nargs="?", default=".", help="対象ディレクトリ(省略時はカレント)")
|
|
171
316
|
ap.add_argument("-d", "--depth", type=int, metavar="N", help="表示する最大の深さ")
|
|
172
317
|
ap.add_argument("-a", "--all", action="store_true", help="隠しファイルも表示する")
|
|
173
318
|
ap.add_argument("-s", "--sort-size", action="store_true", help="サイズが大きい順に並べる")
|
|
319
|
+
ap.add_argument("-g", "--gitignore", action="store_true", help=".gitignore のファイルを除外する(サブディレクトリも対応)")
|
|
320
|
+
ap.add_argument("--date", action="store_true", help="最終更新日時を表示する")
|
|
321
|
+
ap.add_argument("-t", "--type", metavar="EXT", help="指定した拡張子のみ表示 (例: py, md)")
|
|
322
|
+
ap.add_argument("-m", "--markdown", action="store_true", help="Markdown コードブロックで出力")
|
|
174
323
|
ap.add_argument("--no-color", action="store_true", help="カラー表示を無効化する")
|
|
175
324
|
args = ap.parse_args()
|
|
176
325
|
|
|
177
|
-
if args.no_color:
|
|
326
|
+
if args.no_color or args.markdown:
|
|
178
327
|
USE_COLOR = False
|
|
179
328
|
|
|
180
329
|
target = Path(args.path).resolve()
|
|
@@ -185,19 +334,51 @@ def main():
|
|
|
185
334
|
print(f"エラー: '{args.path}' はディレクトリではありません", file=sys.stderr)
|
|
186
335
|
sys.exit(1)
|
|
187
336
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
337
|
+
# ルートの .gitignore を起点として読み込む
|
|
338
|
+
active_pats = load_gitignore(str(target)) if args.gitignore else []
|
|
339
|
+
type_ext = ("." + args.type.lstrip(".")).lower() if args.type else None
|
|
340
|
+
opts = (args.depth, args.all, args.sort_size, args.date,
|
|
341
|
+
args.gitignore, str(target), type_ext)
|
|
342
|
+
|
|
343
|
+
if args.markdown:
|
|
344
|
+
print("```")
|
|
345
|
+
|
|
346
|
+
# ルートを表示
|
|
347
|
+
root_sz = dir_size(str(target))
|
|
348
|
+
root_nd, root_nf = count_entries(str(target), args.all, active_pats, str(target), type_ext, args.gitignore)
|
|
349
|
+
root_label = target.name if target.name else str(target)
|
|
350
|
+
|
|
351
|
+
parts = [fmt_count(root_nd, root_nf), fmt_size(root_sz)]
|
|
352
|
+
if args.date:
|
|
353
|
+
try:
|
|
354
|
+
parts.append(fmt_date(target.stat().st_mtime))
|
|
355
|
+
except OSError:
|
|
356
|
+
pass
|
|
191
357
|
|
|
192
358
|
root_name = c(f"{root_label}/", BOLD, BLUE)
|
|
193
|
-
root_meta = c(
|
|
359
|
+
root_meta = c(f"({', '.join(parts)})", DIM)
|
|
194
360
|
print(f"{root_name} {root_meta}")
|
|
195
361
|
|
|
196
|
-
stats = {"files": 0, "dirs": 0}
|
|
197
|
-
render(str(target), "", 0,
|
|
362
|
+
stats = {"files": 0, "dirs": 0, "extensions": {}}
|
|
363
|
+
render(str(target), "", 0, opts, stats, active_pats)
|
|
198
364
|
|
|
365
|
+
# ─ サマリー ──────────────────────────────────────────────
|
|
199
366
|
print()
|
|
200
|
-
|
|
367
|
+
summary = f" {stats['dirs']} ディレクトリ, {stats['files']} ファイル"
|
|
368
|
+
if args.gitignore:
|
|
369
|
+
summary += " (.gitignore 適用済み)"
|
|
370
|
+
if type_ext:
|
|
371
|
+
summary += f" (フィルタ: {type_ext})"
|
|
372
|
+
print(c(summary, DIM))
|
|
373
|
+
|
|
374
|
+
if stats["extensions"]:
|
|
375
|
+
sorted_exts = sorted(stats["extensions"].items(), key=lambda x: -x[1])
|
|
376
|
+
ext_line = " " + " ".join(f"{ext} ×{n}" for ext, n in sorted_exts[:8])
|
|
377
|
+
print(c(ext_line, DIM))
|
|
378
|
+
|
|
379
|
+
if args.markdown:
|
|
380
|
+
print("```")
|
|
381
|
+
|
|
201
382
|
|
|
202
383
|
if __name__ == "__main__":
|
|
203
384
|
main()
|