ai读项目小工具——文件夹内容复制

文件夹内容复制工具:快速提取项目代码到文本文件

将项目文件复制到txt中,可用来发送个deepseek,gemeni,ChatGPT等ai工具
在这里插入图片描述
下载地址

简介

在软件开发、文档整理、代码分析等场景中,我们经常需要将项目中的源代码或其他文本文件(如配置文件、文档等)提取到一个单独的文本文件中。手动复制粘贴非常繁琐,特别是当项目包含多个子文件夹和大量文件时。“文件夹内容复制工具”就是为了解决这个问题而设计的。

这个小巧而强大的工具可以:

  • 递归复制: 自动遍历指定文件夹及其所有子文件夹。
  • 类型过滤: 只复制常见的文本文件类型(如 .java, .py, .html, .css, .xml, .json, .txt 等),跳过二进制文件。
  • 编码处理: 自动检测文件编码(使用 chardet 库),并尝试使用多种编码(UTF-8, GBK, Latin-1 等)读取,最大限度地避免乱码。
  • 忽略特定目录/文件:
    • Browser 选择: 通过图形界面选择要忽略的特定目录或文件。
    • 正则表达式: 使用正则表达式灵活地匹配要忽略的目录或文件(例如,忽略所有名为 target 的目录,或所有 .log 文件)。
    • 默认忽略: 默认忽略隐藏文件/目录,以及 Java 项目中常见的 target 目录。
  • 自定义输出: 可以将结果保存到您指定的文本文件中,或者使用默认的输出文件名(与源文件夹同名,.txt 扩展名)。
  • 图形界面: 提供直观的图形界面(使用 Tkinter),易于操作。
  • 跨平台: 支持 Windows, macOS, 和 Linux。

使用方法

  1. 下载并运行:

    • 方法一(推荐): 下载已打包好的可执行文件(见下文“下载”部分),直接双击运行。
    • 方法二: 下载源代码,确保您的系统已安装 Python 3 和以下库:
      • tkinter (通常 Python 自带)
      • chardet: pip install chardet
      • pywin32 (仅 Windows): pip install pywin32
        然后运行 folder_copy_gui.py 脚本。
  2. 选择文件夹: 点击“浏览”按钮,选择您要复制内容的源文件夹。

  3. 选择输出文件(可选): 默认情况下,输出文件会保存在与程序相同的目录下,并以源文件夹的名称命名(加上 .txt 扩展名)。如果您想自定义输出文件,请点击“浏览”按钮选择或输入一个文件名。

  4. 忽略设置(可选):

    • 添加目录/文件:
      • 点击“添加目录”按钮,通过文件夹选择对话框选择要忽略的目录。
      • 点击“添加文件”按钮,通过文件选择对话框选择要忽略的文件。
    • 添加目录/文件模式:
      • 点击“添加目录模式”按钮,输入用于匹配目录的正则表达式(例如,.*[/\\]target$ 忽略所有名为 target 的目录)。 注意,在正则表达式中表示路径分隔符,Windows 需要两个反斜杠 \\\\,而 Linux/macOS 使用 /
      • 点击“添加文件模式”按钮,输入用于匹配文件的正则表达式(例如,.*\.log$ 忽略所有 .log 文件)。
    • 移除忽略项: 点击“移除”按钮,在弹出的列表中选择要移除的忽略项。
  5. 开始复制: 点击“开始复制”按钮。程序将开始处理,并在完成后显示提示消息。

示例

假设您有一个 Java 项目文件夹 MyJavaProject,您想复制所有源文件到 MyJavaProject.txt。使用本工具,您只需选择 MyJavaProject 文件夹, 点击“开始复制”即可。默认情况下,target 目录和隐藏文件/目录会被忽略。

已知问题/限制

  • 超大文件: 对于非常大的文件(例如,几个 GB 的日志文件),复制过程可能需要较长时间,甚至可能导致程序崩溃。建议通过正则表达式模式忽略这些文件。
  • 编码检测: 虽然 chardet 库能处理大多数情况,但仍然不能保证 100% 准确地检测所有文件的编码。
  • 文件签名: 由于是通过代码读取文件,无法做到 100% 区分文本文件与二进制文件。
import tkinter as tk
from tkinter import filedialog, messagebox, simpledialog
import os
import chardet
import re
import platform  # 导入 platform 模块

def 复制文件夹内容到txt(文件夹路径, txt文件路径, ignored_items):
    """
    复制文件夹内容,支持以下忽略项,合并显示:
    - Browser 选择的目录 (B:)
    - 正则表达式匹配的目录 (RD:)
    - Browser 选择的文件 (F:)
    - 正则表达式匹配的文件 (RF:)
    - 隐藏文件/目录 (H:) - 默认启用
    """
    TEXT_EXTENSIONS = [
        '.txt', '.java', '.py', '.c', '.cpp', '.h', '.hpp', '.cs', '.js', '.ts', '.jsx', '.tsx', '.go', '.rs',
        '.swift', '.kt', '.scala', '.groovy', '.php', '.rb', '.pl', '.lua', '.m', '.mm', '.vb', '.vbs', '.bas',
        '.fs', '.f90', '.f', '.for', '.pas', '.inc', '.dpr', '.lpr', '.pp', '.ml', '.mli', '.hs', '.lhs',
        '.clj', '.cljs', '.edn', '.scm', '.ss', '.rkt', '.r', '.rmd', '.asm', '.s',
        '.html', '.htm', '.css', '.scss', '.less', '.sass', '.vue', '.svelte', '.ejs', '.pug', '.haml',
        '.xml', '.json', '.yaml', '.yml', '.md', '.rst', '.toml', '.csv', '.tsv',
        '.sql', '.ddl', '.dml',
        '.sh', '.bash', '.bat', '.ps1', '.cmd',
        '.cfg', '.ini', '.conf', '.config', '.properties', '.env', '.env.local',
        '.gitignore', '.editorconfig', '.prettierrc', '.eslintrc', '.babelrc',
        '.dockerfile', '.docker-compose.yml',
        '.mak', '.cmake', '.pro', '.sln', '.vcproj', '.csproj', '.vcxproj', '.makefile', '.am', '.in',
        'pom.xml', 'build.gradle', 'package.json', 'yarn.lock', 'composer.json', 'Gemfile', 'Pipfile',
        'requirements.txt', 'Cargo.toml',
        '.tex', '.bib', '.sty', '.cls',
        '.log', '.awk', '.sed', '.graphql',
    ]

    try:
        with open(txt文件路径, 'w', encoding='utf-8') as outfile:
            for root, dirs, files in os.walk(文件夹路径):
                # 过滤目录
                dirs[:] = [d for d in dirs if not should_ignore_dir(root, d, ignored_items)]

                for filename in files:
                    filepath = os.path.join(root, filename)
                    # 过滤文件
                    if should_ignore_file(filepath, ignored_items) or is_hidden(filepath):  # 添加 is_hidden 检查
                        print(f"跳过文件(忽略/隐藏): {filepath}")
                        continue

                    if os.path.splitext(filename)[1].lower() in TEXT_EXTENSIONS:
                        try:
                            with open(filepath, 'rb') as f:
                                rawdata = f.read()
                                result = chardet.detect(rawdata)
                                encoding = result['encoding']
                                confidence = result['confidence']

                            if encoding is None:
                                print(f"警告: 无法确定文件 {filepath} 的编码。跳过。")
                                continue
                            if confidence < 0.7:
                                print(f"警告: 文件 {filepath} 编码检测可信度较低 ({confidence:.2f})")

                            with open(filepath, 'r', encoding=encoding, errors='replace') as infile:
                                content = infile.read()
                                outfile.write(f"=== 文件: {filepath} (编码: {encoding}) ===\n")
                                outfile.write(content)
                                outfile.write("\n\n")
                        except Exception as e:
                            print(f"错误: 无法读取文件 {filepath}: {e}")
                    else:
                        print(f"跳过文件(可能不是文本): {filepath}")

        messagebox.showinfo("完成", f"已复制到 '{txt文件路径}'")

    except Exception as e:
        messagebox.showerror("错误", str(e))



def should_ignore_dir(root, dir_name, ignored_items):
    abs_dir_path = os.path.abspath(os.path.join(root, dir_name))
    for item in ignored_items:
        if item.startswith("B:"):
            if abs_dir_path == item[2:]:
                return True
        elif item.startswith("RD:"):
            if re.match(item[3:], os.path.join(root, dir_name)):
                return True
        elif item.startswith("H:"):  # 检查隐藏目录
            if is_hidden(os.path.join(root,dir_name)):
                return True
    return False


def should_ignore_file(filepath, ignored_items):
    abs_file_path = os.path.abspath(filepath)
    for item in ignored_items:
        if item.startswith("F:"):
            if abs_file_path == item[2:]:
                return True
        elif item.startswith("RF:"):
            if re.match(item[3:], filepath):
                return True
    return False

def is_hidden(path):
    """
    检查文件或目录是否隐藏。
    """
    try:
        if platform.system() == "Windows":
            # Windows: 检查隐藏属性
            import win32api, win32con
            attrs = win32api.GetFileAttributes(path)
            return attrs & win32con.FILE_ATTRIBUTE_HIDDEN
        else:
            # Unix-like: 检查是否以 . 开头
            return os.path.basename(path).startswith(".")
    except Exception:
        return False # 出现异常,当作非隐藏处理

def browse_folder():
    folder_selected = filedialog.askdirectory()
    folder_path.set(folder_selected)
    if folder_selected:
        update_default_output_file()

def browse_output_file():
    file_selected = filedialog.asksaveasfilename(defaultextension=".txt", filetypes=[("Text files", "*.txt"), ("All files", "*.*")])
    if file_selected:
        output_file_path.set(file_selected)

def update_default_output_file():
    folder = folder_path.get()
    if folder:
        default_output = os.path.join(os.getcwd(), os.path.basename(folder) + ".txt")
        output_file_path.set(default_output)

def add_ignore_dir():
    if not folder_path.get():
        messagebox.showwarning("提示", "请先选择要复制的文件夹")
        return
    dir_to_ignore = filedialog.askdirectory(initialdir=folder_path.get())
    if dir_to_ignore:
        abs_path = os.path.abspath(dir_to_ignore)
        ignored_items.add(f"B:{abs_path}")
        update_ignored_items_display()

def add_ignore_file():
    if not folder_path.get():
        messagebox.showwarning("提示", "请先选择要复制的文件夹")
        return
    file_to_ignore = filedialog.askopenfilename(initialdir=folder_path.get())
    if file_to_ignore:
        abs_path = os.path.abspath(file_to_ignore)
        ignored_items.add(f"F:{abs_path}")
        update_ignored_items_display()

def add_ignore_dir_pattern():
    pattern = simpledialog.askstring("添加忽略目录模式", "请输入正则表达式:")
    if pattern:
        try:
            re.compile(pattern)
            ignored_items.add(f"RD:{pattern}")
            update_ignored_items_display()
        except re.error:
            messagebox.showerror("错误", "无效的正则表达式。")

def add_ignore_file_pattern():
    pattern = simpledialog.askstring("添加忽略文件模式", "请输入正则表达式:")
    if pattern:
        try:
            re.compile(pattern)
            ignored_items.add(f"RF:{pattern}")
            update_ignored_items_display()
        except re.error:
            messagebox.showerror("错误", "无效的正则表达式。")

def remove_ignore_item():
    if not ignored_items:
        messagebox.showinfo("提示", "没有可移除的忽略项。")
        return

    dialog = tk.Toplevel(root)
    dialog.title("选择要移除的项")
    dialog.transient(root)
    dialog.grab_set()

    listbox = tk.Listbox(dialog, selectmode=tk.SINGLE, width=60, height=min(10, len(ignored_items)))
    # 构建显示文本和原始 item 的映射
    display_to_item = {}
    for item in sorted(ignored_items):
        display_item = item.replace("B:", "Browser Dir: ").replace("RD:", "Regex Dir: ").replace("F:", "Browser File: ").replace("RF:", "Regex File: ").replace("H:", "Hidden: ")
        listbox.insert(tk.END, display_item)
        display_to_item[display_item] = item  # 建立映射

    listbox.pack(padx=10, pady=10)

    def do_remove():
        selected_index = listbox.curselection()
        if selected_index:
            selected_item_display = listbox.get(selected_index[0])
            # 使用映射找到原始的 item
            selected_item = display_to_item[selected_item_display]

            ignored_items.remove(selected_item)
            update_ignored_items_display()
            dialog.destroy()
        else:
            messagebox.showinfo("提示", "请先选择要移除的项。")

    remove_button = tk.Button(dialog, text="移除", command=do_remove)
    remove_button.pack(pady=5)
    dialog.wait_window(dialog)



def update_ignored_items_display():
    ignore_listbox.config(state=tk.NORMAL)
    ignore_listbox.delete("1.0", tk.END)
    display_items = [item.replace("B:", "Browser Dir: ").replace("RD:", "Regex Dir: ").replace("F:", "Browser File: ").replace("RF:", "Regex File: ").replace("H:","Hidden: ")
                     for item in sorted(ignored_items)]
    ignore_listbox.insert("1.0", "\n".join(display_items))
    ignore_listbox.config(state=tk.DISABLED)

def start_copy():
    folder = folder_path.get()
    output_file = output_file_path.get()
    if not folder or not output_file:
        messagebox.showerror("错误", "请选择文件夹和输出文件")
        return
    复制文件夹内容到txt(folder, output_file, ignored_items)

# --- GUI 部分 ---
root = tk.Tk()
root.title("文件夹内容复制工具")
root.geometry("680x450")
root.minsize(600, 400)

root.columnconfigure(1, weight=1)
root.rowconfigure(3, weight=1)

# 文件夹选择
folder_path = tk.StringVar()
folder_label = tk.Label(root, text="选择文件夹:")
folder_label.grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
folder_entry = tk.Entry(root, textvariable=folder_path, width=40)
folder_entry.grid(row=0, column=1, padx=5, pady=5, sticky=tk.EW)
browse_button = tk.Button(root, text="浏览", command=browse_folder)
browse_button.grid(row=0, column=2, padx=5, pady=5, sticky=tk.E)

# 输出文件选择
output_file_path = tk.StringVar()
output_file_label = tk.Label(root, text="输出文件:")
output_file_label.grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
output_file_entry = tk.Entry(root, textvariable=output_file_path, width=40)
output_file_entry.grid(row=1, column=1, padx=5, pady=5, sticky=tk.EW)
browse_output_button = tk.Button(root, text="浏览", command=browse_output_file)
browse_output_button.grid(row=1, column=2, padx=5, pady=5, sticky=tk.E)

# 忽略项
ignored_items = {"RD:.*/target$", "H:"}  # 统一存储, 默认忽略 target 和隐藏文件/目录
ignore_label = tk.Label(root, text="忽略:")
ignore_label.grid(row=2, column=0, padx=5, pady=5, sticky=tk.NW)

ignore_listbox = tk.Text(root, width=50, height=8, relief="sunken")
ignore_listbox.grid(row=3, column=1, padx=5, pady=5, sticky=tk.NSEW)
ignore_listbox.config(state=tk.DISABLED)

scrollbar = tk.Scrollbar(root, command=ignore_listbox.yview)
scrollbar.grid(row=3, column=2, sticky=tk.NS)
ignore_listbox["yscrollcommand"] = scrollbar.set

# 添加/移除按钮 (在同一个 Frame 中)
button_frame = tk.Frame(root)
button_frame.grid(row=4, column=1, padx=5, pady=5, sticky=tk.E)

add_ignore_dir_button = tk.Button(button_frame, text="添加目录", command=add_ignore_dir)
add_ignore_dir_button.pack(side=tk.LEFT, padx=2)

add_ignore_file_button = tk.Button(button_frame, text="添加文件", command=add_ignore_file)
add_ignore_file_button.pack(side=tk.LEFT, padx=2)

add_ignore_dir_pattern_button = tk.Button(button_frame, text="添加目录模式", command=add_ignore_dir_pattern)
add_ignore_dir_pattern_button.pack(side=tk.LEFT, padx=2)

add_ignore_file_pattern_button = tk.Button(button_frame, text="添加文件模式", command=add_ignore_file_pattern)
add_ignore_file_pattern_button.pack(side=tk.LEFT, padx=2)

remove_ignore_item_button = tk.Button(button_frame, text="移除", command=remove_ignore_item)
remove_ignore_item_button.pack(side=tk.LEFT, padx=2)

# 开始复制按钮
start_button = tk.Button(root, text="开始复制", command=start_copy, bg="#4CAF50", fg="white", relief=tk.FLAT)
start_button.grid(row=5, column=0, columnspan=3, padx=10, pady=15, sticky=tk.EW)


update_default_output_file()
update_ignored_items_display()

root.mainloop()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

+720

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值