Files
TransPyC/find.py
T

235 lines
8.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
REPL 模式文件内容搜索工具
支持正则或纯文本搜索,默认搜索当前目录,支持指定文件后缀白名单、cd切换目录
新增:工作区 workpath,cd仅改变搜索根目录,输出路径为相对于工作区的相对路径
"""
import os
import re
import sys
# 硬编码排除目录
EXCLUDE_DIRS = {
'.git', '.vs', 'node_modules', '__pycache__', '.idea',
'miniprogram_npm', '_backup_pre_redesign',
}
# 排除的文件扩展名(黑名单)
EXCLUDE_EXTS = {
'.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.bmp',
'.mp3', '.mp4', '.wav', '.avi',
'.zip', '.rar', '.7z', '.tar', '.gz',
'.exe', '.dll', '.so', '.dylib',
'.pyc', '.pyo', '.class', '.o', '.obj',
'.db', '.sqlite', '.vsidx',
}
# 默认脚本所在目录
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
def should_skip(dirpath):
"""判断目录是否应跳过"""
parts = dirpath.replace('\\', '/').split('/')
return any(p in EXCLUDE_DIRS for p in parts)
def search(pattern, root, workspace, use_regex=True, max_results=50, include_exts=None):
"""
搜索文件内容
:param pattern: 搜索文本/正则
:param root: 搜索根目录(cd 会改变此值)
:param workspace: 工作区目录,输出路径将相对于此目录
:param use_regex: 是否启用正则模式
:param max_results: 返回结果上限
:param include_exts: list[str] 文件后缀白名单,如 [".py",".md"];None代表不限制
"""
compiled = None
if use_regex:
try:
compiled = re.compile(pattern, re.IGNORECASE)
except re.error as e:
print(f' 正则语法错误: {e}')
return
count = 0
for dirpath, dirnames, filenames in os.walk(root):
# 就地修改 dirnames 以跳过排除目录
dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS]
for fname in filenames:
ext = os.path.splitext(fname)[1].lower()
# 后缀白名单逻辑:如果设置了白名单,只处理白名单后缀
if include_exts is not None:
if ext not in include_exts:
continue
else:
# 无白名单,走旧黑名单逻辑
if ext in EXCLUDE_EXTS:
continue
fpath = os.path.join(dirpath, fname)
# 输出路径:相对于 workspace,而不是 root
relpath = os.path.relpath(fpath, workspace)
try:
with open(fpath, 'r', encoding='utf-8', errors='ignore') as f:
for lineno, line in enumerate(f, 1):
line_stripped = line.rstrip('\n\r')
hit = False
highlight_line = line_stripped
if use_regex:
match = compiled.search(line_stripped)
if match:
highlight_line = (
line_stripped[:match.start()]
+ f'>>> {match.group()} <<<'
+ line_stripped[match.end():]
)
hit = True
else:
if pattern.lower() in line_stripped.lower():
hit = True
if hit:
print(f' {relpath}:{lineno} {highlight_line}')
count += 1
if count >= max_results:
print(f'\n ... 已达上限 {max_results} 条,停止')
return
except (PermissionError, OSError):
continue
if count == 0:
print(' 未找到匹配内容')
else:
print(f'\n 共 {count} 条匹配')
def main():
print('=== 文件内容搜索工具 ===')
# 初始化:搜索根目录、工作区都等于脚本目录
root = SCRIPT_DIR
workspace = SCRIPT_DIR
print(f'搜索根目录(root): {root}')
print(f'工作区(workspace): {workspace}')
print(f'排除目录: {EXCLUDE_DIRS}')
print()
print('命令:')
print(' /regex <pattern> 正则搜索')
print(' /text <text> 纯文本搜索')
print(' /max <n> 设置结果上限(默认50)')
print(' /dir <path> 设置搜索根目录(绝对路径)')
print(' /workpath <path> 设置工作区(支持相对路径);输出路径相对于工作区')
print(' cd <path> 切换搜索根目录(支持..相对路径,不改变工作区)')
print(' /ext .py,.md,.txt 设置只搜索的后缀,清空:/ext clear')
print(' /help 显示帮助')
print(' /quit 退出')
print()
print('直接输入内容默认使用【纯文本】搜索,需要正则请加 /regex 前缀')
print()
use_regex = False
max_results = 50
include_exts = None # 文件后缀白名单,None=不限制
while True:
try:
raw = input('find> ').strip()
except (EOFError, KeyboardInterrupt):
print('\n再见')
break
if not raw:
continue
if raw == '/quit':
print('再见')
break
if raw == '/help':
print(' /regex <pattern> 正则搜索')
print(' /text <text> 纯文本搜索')
print(' /max <n> 设置结果上限')
print(' /dir <path> 设置搜索根目录(绝对路径)')
print(' /workpath <path> 设置工作区(支持相对路径),输出路径基于工作区')
print(' cd <path> 切换搜索根目录,支持 cd .. 、cd subdir;不改变工作区')
print(' /ext .py,.md,.txt 设置后缀白名单;/ext clear 清除白名单')
print(' /help 显示帮助')
print(' /quit 退出')
print(' 直接输入内容默认纯文本搜索')
continue
if raw.startswith('/regex '):
pattern = raw[7:].strip()
if pattern:
search(pattern, root, workspace, use_regex=True, max_results=max_results, include_exts=include_exts)
continue
if raw.startswith('/text '):
pattern = raw[6:].strip()
if pattern:
search(pattern, root, workspace, use_regex=False, max_results=max_results, include_exts=include_exts)
continue
if raw.startswith('/max '):
try:
max_results = int(raw[5:].strip())
print(f' 结果上限设为 {max_results}')
except ValueError:
print(' 请输入数字')
continue
if raw.startswith('/dir '):
new_dir = raw[5:].strip()
abs_dir = os.path.abspath(new_dir)
if os.path.isdir(abs_dir):
root = abs_dir
print(f' 搜索根目录切换为: {root}')
else:
print(f' 目录不存在: {new_dir}')
continue
# cd:仅修改搜索根目录 root,**不触碰 workspace**
if raw.startswith('cd '):
arg = raw[3:].strip()
new_root = os.path.abspath(os.path.join(root, arg))
if os.path.isdir(new_root):
root = new_root
print(f' cd → {root}')
else:
print(f' cd: 目录不存在 {arg}')
continue
# 新增 /workpath:设置工作区,支持相对路径
if raw.startswith('/workpath '):
arg = raw[10:].strip()
new_ws = os.path.abspath(os.path.join(SCRIPT_DIR, arg))
if os.path.isdir(new_ws):
workspace = new_ws
print(f' 工作区已设置: {workspace}')
else:
print(f' workpath: 目录不存在 {arg}')
continue
if raw.startswith('/ext '):
arg = raw[5:].strip().lower()
if arg == 'clear':
include_exts = None
print(' 已清除后缀白名单,搜索全部非排除文件')
else:
parts = [p.strip() for p in arg.split(',') if p.strip()]
include_exts = parts
print(f' 后缀白名单已设置: {include_exts}')
continue
# 直接输入走纯文本搜索
search(raw, root, workspace, use_regex=False, max_results=max_results, include_exts=include_exts)
if __name__ == '__main__':
main()