dirlens 1.0.0 → 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 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
- - **隠しファイル対応**`-a` で表示切り替え
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
- ```bash
66
- # 実行権限を付与
67
- chmod +x dirlens
66
+ GitHubリポジトリから `dirlens.py` をダウンロードして使用します。
68
67
 
68
+ ```bash
69
69
  # /usr/local/bin にインストール(どこからでも呼べるようになる)
70
- sudo cp dirlens /usr/local/bin/
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` **`dirlens.py`** に改名して任意のフォルダへ置く
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 ADDED
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ dirlens – ファイルサイズ+アイテム数付きディレクトリツリー表示ツール
4
+ 対応環境: macOS / Linux / Windows (Python 3.8+)
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import argparse
10
+ from pathlib import Path
11
+
12
+ # ─── カラー設定 ──────────────────────────────────────────────
13
+ def _enable_color():
14
+ if not hasattr(sys.stdout, "isatty") or not sys.stdout.isatty():
15
+ return False
16
+ if os.name == "nt":
17
+ try:
18
+ import ctypes
19
+ kernel32 = ctypes.windll.kernel32
20
+ kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
21
+ except Exception:
22
+ pass
23
+ return bool(
24
+ os.environ.get("WT_SESSION")
25
+ or os.environ.get("TERM_PROGRAM")
26
+ or os.environ.get("TERM")
27
+ or os.environ.get("ANSICON")
28
+ )
29
+ return True
30
+
31
+ USE_COLOR = _enable_color()
32
+
33
+ RESET = "\033[0m"
34
+ BOLD = "\033[1m"
35
+ DIM = "\033[2m"
36
+ BLUE = "\033[34m"
37
+ CYAN = "\033[36m"
38
+ GREEN = "\033[32m"
39
+ MAGENTA = "\033[35m"
40
+
41
+ def c(text, *codes):
42
+ return ("".join(codes) + text + RESET) if USE_COLOR else text
43
+
44
+ # ─── サイズ表示 ───────────────────────────────────────────────
45
+ def fmt_size(n):
46
+ if n == 0:
47
+ return "0 bytes"
48
+ for unit, factor in (("TB", 1 << 40), ("GB", 1 << 30), ("MB", 1 << 20), ("KB", 1 << 10)):
49
+ if n >= factor:
50
+ s = f"{n / factor:.2f}".rstrip("0").rstrip(".")
51
+ return f"{s} {unit}"
52
+ return f"{n} {'byte' if n == 1 else 'bytes'}"
53
+
54
+ # ─── ディレクトリサイズ(キャッシュ付き) ─────────────────────
55
+ _cache = {}
56
+
57
+ def dir_size(path):
58
+ if path in _cache:
59
+ return _cache[path]
60
+ total = 0
61
+ try:
62
+ with os.scandir(path) as it:
63
+ for e in it:
64
+ try:
65
+ if e.is_file(follow_symlinks=False):
66
+ total += e.stat(follow_symlinks=False).st_size
67
+ elif e.is_dir(follow_symlinks=False):
68
+ total += dir_size(e.path)
69
+ except OSError:
70
+ pass
71
+ except OSError:
72
+ pass
73
+ _cache[path] = total
74
+ return total
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
+
95
+ # ─── ツリー描画 ───────────────────────────────────────────────
96
+ PIPE = "│ "
97
+ FORK = "├── "
98
+ LAST = "└── "
99
+ BLANK = " "
100
+
101
+ def render(path, prefix, depth, max_depth, show_all, by_size, stats):
102
+ if max_depth is not None and depth >= max_depth:
103
+ return
104
+
105
+ try:
106
+ raw = list(os.scandir(path))
107
+ except PermissionError:
108
+ print(f"{prefix}{LAST}{c('[アクセス拒否]', DIM)}")
109
+ return
110
+
111
+ entries = [e for e in raw if show_all or not e.name.startswith(".")]
112
+ dirs = [e for e in entries if e.is_dir(follow_symlinks=False)]
113
+ files = [e for e in entries if not e.is_dir(follow_symlinks=False)]
114
+
115
+ def entry_size(e):
116
+ try:
117
+ return e.stat(follow_symlinks=True).st_size
118
+ except OSError:
119
+ return 0
120
+
121
+ if by_size:
122
+ dirs.sort(key=lambda e: dir_size(e.path), reverse=True)
123
+ files.sort(key=lambda e: entry_size(e), reverse=True)
124
+ else:
125
+ dirs.sort(key=lambda e: e.name.casefold())
126
+ files.sort(key=lambda e: e.name.casefold())
127
+
128
+ combined = dirs + files
129
+
130
+ for i, entry in enumerate(combined):
131
+ is_last = (i == len(combined) - 1)
132
+ branch = LAST if is_last else FORK
133
+ cont = BLANK if is_last else PIPE
134
+
135
+ if entry.is_dir(follow_symlinks=False):
136
+ sz = dir_size(entry.path)
137
+ nd, nf = count_items(entry.path, show_all)
138
+ stats["dirs"] += 1
139
+ name = c(f"{entry.name}/", BOLD, CYAN)
140
+ meta = c(fmt_meta(nd, nf, sz), DIM)
141
+ print(f"{prefix}{branch}{name} {meta}")
142
+ render(entry.path, prefix + cont, depth + 1, max_depth, show_all, by_size, stats)
143
+ else:
144
+ sz = entry_size(entry)
145
+ sym = " →" if entry.is_symlink() else ""
146
+ stats["files"] += 1
147
+ name = c(f"{entry.name}{sym}", MAGENTA if entry.is_symlink() else GREEN)
148
+ size = c(f"({fmt_size(sz)})", DIM)
149
+ print(f"{prefix}{branch}{name} {size}")
150
+
151
+ # ─── エントリポイント ─────────────────────────────────────────
152
+ def main():
153
+ global USE_COLOR
154
+ sys.setrecursionlimit(10_000)
155
+
156
+ ap = argparse.ArgumentParser(
157
+ prog="dirlens",
158
+ description="ファイルサイズ+アイテム数付きのディレクトリツリーを表示します",
159
+ formatter_class=argparse.RawDescriptionHelpFormatter,
160
+ epilog=(
161
+ "使用例:\n"
162
+ " dirlens カレントディレクトリを表示\n"
163
+ " dirlens ~/Desktop 指定したディレクトリを表示\n"
164
+ " dirlens -d 2 深さ 2 階層まで表示\n"
165
+ " dirlens -a 隠しファイル (.xxx) も表示\n"
166
+ " dirlens -s サイズの大きい順に表示\n"
167
+ " dirlens --no-color カラーなしで表示"
168
+ ),
169
+ )
170
+ ap.add_argument("path", nargs="?", default=".", help="対象ディレクトリ(省略時はカレント)")
171
+ ap.add_argument("-d", "--depth", type=int, metavar="N", help="表示する最大の深さ")
172
+ ap.add_argument("-a", "--all", action="store_true", help="隠しファイルも表示する")
173
+ ap.add_argument("-s", "--sort-size", action="store_true", help="サイズが大きい順に並べる")
174
+ ap.add_argument("--no-color", action="store_true", help="カラー表示を無効化する")
175
+ args = ap.parse_args()
176
+
177
+ if args.no_color:
178
+ USE_COLOR = False
179
+
180
+ target = Path(args.path).resolve()
181
+ if not target.exists():
182
+ print(f"エラー: '{args.path}' が見つかりません", file=sys.stderr)
183
+ sys.exit(1)
184
+ if not target.is_dir():
185
+ print(f"エラー: '{args.path}' はディレクトリではありません", file=sys.stderr)
186
+ sys.exit(1)
187
+
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)
191
+
192
+ root_name = c(f"{root_label}/", BOLD, BLUE)
193
+ root_meta = c(fmt_meta(nd, nf, root_sz), DIM)
194
+ print(f"{root_name} {root_meta}")
195
+
196
+ stats = {"files": 0, "dirs": 0}
197
+ render(str(target), "", 0, args.depth, args.all, args.sort_size, stats)
198
+
199
+ print()
200
+ print(c(f" {stats['dirs']} ディレクトリ, {stats['files']} ファイル", DIM))
201
+
202
+ if __name__ == "__main__":
203
+ main()
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "dirlens",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Directory tree viewer with file sizes / ファイルサイズ付きディレクトリツリー表示ツール",
5
5
  "bin": {
6
6
  "dirlens": "./bin/dirlens"
7
7
  },
8
8
  "files": [
9
9
  "bin/",
10
- "dirlens",
10
+ "dirlens.py",
11
11
  "dirlens.bat",
12
12
  "README.md"
13
13
  ],
@@ -24,4 +24,4 @@
24
24
  "type": "git",
25
25
  "url": "https://github.com/igarinpiano/dirlens.git"
26
26
  }
27
- }
27
+ }