Skip to content

Commit 53c29bb

Browse files
committed
[工具集优化]: 增强哈希计算、图标生成和目录树功能
- 虚拟环境脚本:移除了自动安装依赖的步骤,改为提示用户使用 deactivate 退出虚拟环境 - 哈希计算工具:重构为流式处理,支持大文件计算,增加文本输入模式(--text 选项) - ICNS 生成脚本:增加 macOS 平台检查,确保只在苹果系统上运行 - 目录树工具:增加排除模式(--exclude/--exclude-file)和文件显示选项(--show-files),增强递归显示功能
1 parent 7fd4831 commit 53c29bb

File tree

4 files changed

+146
-43
lines changed

4 files changed

+146
-43
lines changed

activate_venv.sh

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,4 @@ source "$VENV_NAME/bin/activate"
2020
# 提示用户后续操作
2121
echo "虚拟环境已激活,当前 Python 路径: $(which python)"
2222

23-
current_path=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")
24-
pip install --upgrade pip
25-
pip install -r "${current_path}/requirements.txt" -i https://pypi.tuna.tsinghua.edu.cn/simple
23+
echo "你可以使用 'deactivate' 命令退出虚拟环境"

hash/hash.py

Lines changed: 57 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616
import hashlib
1717
import os
1818
import sys
19-
from typing import Dict, List, Callable
19+
from typing import Dict, List, Callable, Union, IO
2020
from utils import Colors
2121

2222

23-
# ---------- 算法表 ----------
23+
# ---------------- 算法表 ----------------
2424
ALGO_MAP: Dict[str, Callable[[], "hashlib._Hash"]] = {}
2525

2626
# 标准库自带
@@ -49,33 +49,49 @@
4949
except ImportError:
5050
pass
5151

52-
53-
# ---------- 工具 ----------
54-
def calc_hash(data: bytes, algo: str) -> str:
55-
"""计算指定算法哈希值,返回十六进制字符串"""
56-
h = ALGO_MAP[algo]()
57-
h.update(data)
58-
return h.hexdigest()
59-
60-
61-
def read_target(target: str) -> bytes:
62-
"""从文件、文本或 stdin 读取二进制数据"""
63-
if target == "-":
52+
# ---------------- 流式核心 ----------------
53+
BUF = 1 << 20
54+
55+
56+
def multi_hash_stream(
57+
stream: Union[bytes, IO[bytes]], algos: List[str], buf_size: int = BUF
58+
) -> Dict[str, str]:
59+
"""一次读取,返回 algo->hex 的映射"""
60+
hashers = {a: ALGO_MAP[a]() for a in algos}
61+
if isinstance(stream, bytes): # 小文本
62+
for h in hashers.values():
63+
h.update(stream)
64+
return {a: h.hexdigest() for a, h in hashers.items()}
65+
# 文件 / stdin
66+
while chunk := stream.read(buf_size):
67+
for h in hashers.values():
68+
h.update(chunk)
69+
return {a: h.hexdigest() for a, h in hashers.items()}
70+
71+
72+
# ---------------- 输入解析 ----------------
73+
def parse_input(args) -> Union[bytes, IO[bytes]]:
74+
if args.target == "-":
6475
print(f"{Colors.OK}从标准输入读取数据{Colors.END}")
65-
return sys.stdin.buffer.read()
66-
if os.path.isfile(target):
67-
print(f"{Colors.OK}读取文件: {target}{Colors.END}")
68-
with open(target, "rb") as f:
69-
return f.read()
76+
return sys.stdin.buffer
77+
if args.text:
78+
print(f"{Colors.OK}按文本内容计算{Colors.END}")
79+
return args.target.encode("utf-8")
80+
if os.path.isfile(args.target):
81+
print(f"{Colors.OK}读取文件: {args.target}{Colors.END}")
82+
return open(args.target, "rb")
7083
print(f"{Colors.OK}按文本内容计算{Colors.END}")
71-
return target.encode("utf-8")
84+
return args.target.encode("utf-8")
7285

7386

74-
# ---------- 主入口 ----------
87+
# ---------------- 主入口 ----------------
7588
def main(argv: List[str] = None) -> None:
7689
parser = argparse.ArgumentParser(description="计算文件或文本的哈希值")
77-
parser.add_argument("target", nargs="?", help="文件路径、文本或 '-' 表示 stdin")
90+
parser.add_argument("target", nargs="?", help="文件路径、文本或 '-'")
7891
parser.add_argument("-a", "--algo", choices=list(ALGO_MAP), help="仅输出指定算法")
92+
parser.add_argument(
93+
"-t", "--text", action="store_true", help="强制将 TARGET 视为文本"
94+
)
7995
parser.add_argument("-l", "--list", action="store_true", help="列出支持的算法")
8096
args = parser.parse_args(argv)
8197

@@ -88,22 +104,32 @@ def main(argv: List[str] = None) -> None:
88104
return
89105

90106
if args.target is None:
91-
parser.error("缺少参数 target(或改用 -l 查看算法列表)")
107+
parser.error("缺少参数 TARGET(或改用 -l 查看算法列表)")
92108

93109
try:
94-
data = read_target(args.target)
110+
stream = parse_input(args)
95111
except OSError as e:
96-
print(f"{Colors.ERR}读取失败: {e}{Colors.END}", file=sys.stderr)
112+
print(f"{Colors.ERR}打开失败: {e}{Colors.END}", file=sys.stderr)
97113
sys.exit(2)
98114

99-
if args.algo:
100-
print(
101-
f"{Colors.BOLD}{args.algo.upper()}{Colors.END}: {calc_hash(data, args.algo)}"
102-
)
103-
else:
115+
# 要算哪些算法
116+
to_calc = [args.algo] if args.algo else sorted(ALGO_MAP)
117+
118+
# 统一走 multi_hash_stream
119+
if hasattr(stream, "seek"):
120+
stream.seek(0)
121+
results = multi_hash_stream(stream, to_calc)
122+
123+
# 输出
124+
if args.algo: # 单算法
125+
print(f"{Colors.BOLD}{args.algo.upper()}{Colors.END}: " f"{results[args.algo]}")
126+
else: # 多算法
104127
print(f"{Colors.BOLD}哈希值:{Colors.END}")
105128
for k in sorted(ALGO_MAP):
106-
print(f" {k.upper()}: {calc_hash(data, k)}")
129+
print(f" {k.upper()}: {results[k]}")
130+
131+
if hasattr(stream, "close"):
132+
stream.close()
107133

108134

109135
if __name__ == "__main__":

icon-maker/make_icns.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
from PIL import Image
1717
from icon_common import parse_hex_color, square_image
1818

19+
20+
if sys.platform != "darwin": # 非 macOS 立即退出
21+
sys.exit("ERROR: 该脚本只能运行在 macOS 上。")
22+
1923
# icns 所需全部分辨率
2024
ICNS_SIZES = [16, 32, 64, 128, 256, 512, 1024]
2125

tree/tree.py

Lines changed: 84 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
# -*- coding: utf-8 -*-
22
"""
3-
tree.py
4-
递归或非递归打印目录结构,效果类似 Linux 的 tree 命令。
3+
tree.py —— 递归或非递归打印目录树,效果类似 Linux tree 命令。
54
65
用法:
7-
python tree.py /path/to/dir # 仅列一层
8-
python tree.py /path/to/dir -r # 递归子目录
6+
python tree.py /path/to/dir # 仅列目录(默认)
7+
python tree.py /path/to/dir -f # 目录 + 文件
8+
python tree.py /path/to/dir -r -f # 递归且显示文件
9+
python tree.py /path/to/dir -r -e "*.pyc|__pycache__" -f
10+
python tree.py /path/to/dir -r --exclude-file excl.txt -f
911
python tree.py -h
1012
"""
1113

1214
import sys
1315
import argparse
16+
import fnmatch
1417
from pathlib import Path
1518
from typing import List
1619
from utils import Colors
@@ -21,19 +24,46 @@
2124
PREFIX_EMPTY = " "
2225

2326

24-
def tree(dir_path: Path, prefix: str = "", recursive: bool = False) -> None:
25-
"""打印目录树,recursive 控制是否进入子目录"""
27+
def tree(
28+
dir_path: Path,
29+
prefix: str = "",
30+
recursive: bool = False,
31+
exclude: List[str] = None,
32+
show_files: bool = False,
33+
) -> None:
34+
"""打印目录树"""
35+
exclude = exclude or []
36+
37+
def _is_excluded(p: Path) -> bool:
38+
abs_path = str(p.absolute())
39+
for pat in exclude:
40+
if pat in abs_path or fnmatch.fnmatch(abs_path, pat):
41+
return True
42+
return False
43+
2644
entries: List[Path] = sorted(
2745
dir_path.iterdir(), key=lambda p: (p.is_file(), p.name.lower())
2846
)
2947
for idx, path in enumerate(entries):
48+
if _is_excluded(path):
49+
continue
50+
# 新增:默认不显示文件
51+
if path.is_file() and not show_files:
52+
continue
53+
3054
is_last = idx == len(entries) - 1
3155
connector = PREFIX_LAST if is_last else PREFIX_MIDDLE
3256
if path.is_dir():
3357
print(f"{prefix}{connector}{Colors.BOLD}{path.name}{Colors.END}")
34-
if recursive: # 仅递归时继续深入
58+
if recursive:
3559
extension = PREFIX_EMPTY if is_last else PREFIX_CONT
36-
tree(path, prefix + extension, recursive=True)
60+
tree(
61+
path,
62+
prefix + extension,
63+
recursive=True,
64+
exclude=exclude,
65+
show_files=show_files,
66+
)
3767
else:
3868
print(f"{prefix}{connector}{path.name}")
3969

@@ -44,16 +74,61 @@ def main(argv: List[str] = None) -> None:
4474
parser.add_argument(
4575
"-r", "--recursive", action="store_true", help="递归子目录(默认不递归)"
4676
)
77+
parser.add_argument(
78+
"-f", "--show-files", action="store_true", help="同时显示文件(默认仅目录)"
79+
)
80+
parser.add_argument(
81+
"-e",
82+
"--exclude",
83+
nargs="*",
84+
action="append",
85+
default=[],
86+
help='排除模式,可重复,竖线分隔,例:-e "*.pyc|__pycache__"',
87+
)
88+
parser.add_argument(
89+
"-E",
90+
"--exclude-file",
91+
type=Path,
92+
help="从文本文件读取排除模式,一行一个,# 开头或空行忽略",
93+
)
4794
args = parser.parse_args(argv)
4895

96+
# 收集排除模式
97+
cmd_patterns = [
98+
pat.strip()
99+
for group in args.exclude
100+
for item in group
101+
for pat in item.split("|")
102+
if pat.strip()
103+
]
104+
file_patterns = []
105+
if args.exclude_file:
106+
if not args.exclude_file.is_file():
107+
print(
108+
f"{Colors.ERR}排除文件不存在: {args.exclude_file}{Colors.END}",
109+
file=sys.stderr,
110+
)
111+
sys.exit(2)
112+
file_patterns = [
113+
line.strip()
114+
for line in args.exclude_file.read_text(encoding="utf-8").splitlines()
115+
if line.strip() and not line.startswith("#")
116+
]
117+
exclude_patterns = cmd_patterns + file_patterns
118+
49119
root = Path(args.directory).expanduser().resolve()
50120
if not root.is_dir():
51121
print(f"{Colors.ERR}错误: {root} 不是有效目录{Colors.END}", file=sys.stderr)
52122
sys.exit(2)
53123

54124
print(f"{Colors.OK}{root}{Colors.END}")
55125
try:
56-
tree(root, recursive=args.recursive)
126+
tree(
127+
root,
128+
recursive=args.recursive,
129+
exclude=exclude_patterns,
130+
show_files=args.show_files,
131+
)
57132
except KeyboardInterrupt:
58133
sys.exit(130)
59134
except Exception as e:

0 commit comments

Comments
 (0)