306 lines
9.9 KiB
Python
306 lines
9.9 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
项目代码导出工具
|
||
用于将整个电商项目的代码导出到文本文件中
|
||
"""
|
||
|
||
import os
|
||
import datetime
|
||
from pathlib import Path
|
||
|
||
|
||
class CodeExporter:
|
||
def __init__(self, project_root=None):
|
||
"""
|
||
初始化代码导出器
|
||
:param project_root: 项目根目录,默认为当前目录
|
||
"""
|
||
self.project_root = Path(project_root) if project_root else Path('.')
|
||
self.output_file = None
|
||
|
||
# 需要导出的文件扩展名
|
||
self.include_extensions = {
|
||
'.py', '.html', '.css', '.js', '.sql', '.txt', '.md',
|
||
'.yml', '.yaml', '.json', '.xml', '.ini', '.cfg'
|
||
}
|
||
|
||
# 需要排除的目录
|
||
self.exclude_dirs = {
|
||
'venv', '.venv', 'env', '.env', '__pycache__', '.git',
|
||
'.idea', '.vscode', 'node_modules', 'logs', 'temp', 'tmp',
|
||
'.pytest_cache', '.coverage', 'htmlcov', 'dist', 'build'
|
||
}
|
||
|
||
# 需要排除的文件
|
||
self.exclude_files = {
|
||
'.DS_Store', 'Thumbs.db', '.gitignore', '*.pyc', '*.pyo',
|
||
'*.log', '*.tmp', '*.bak', '*.swp', '*.swo'
|
||
}
|
||
|
||
# 特殊处理的文件(即使没有扩展名也要包含)
|
||
self.special_files = {
|
||
'Dockerfile', 'requirements.txt', 'README', 'LICENSE',
|
||
'Makefile', 'Procfile', '.dockerignore'
|
||
}
|
||
|
||
def should_include_file(self, file_path):
|
||
"""
|
||
判断文件是否应该被包含在导出中
|
||
:param file_path: 文件路径
|
||
:return: bool
|
||
"""
|
||
file_name = file_path.name
|
||
file_suffix = file_path.suffix.lower()
|
||
|
||
# 检查特殊文件
|
||
if file_name in self.special_files:
|
||
return True
|
||
|
||
# 检查扩展名
|
||
if file_suffix in self.include_extensions:
|
||
return True
|
||
|
||
return False
|
||
|
||
def should_exclude_dir(self, dir_path):
|
||
"""
|
||
判断目录是否应该被排除
|
||
:param dir_path: 目录路径
|
||
:return: bool
|
||
"""
|
||
dir_name = dir_path.name
|
||
return dir_name in self.exclude_dirs or dir_name.startswith('.')
|
||
|
||
def get_file_info(self, file_path):
|
||
"""
|
||
获取文件信息
|
||
:param file_path: 文件路径
|
||
:return: dict
|
||
"""
|
||
try:
|
||
stat = file_path.stat()
|
||
return {
|
||
'size': stat.st_size,
|
||
'modified': datetime.datetime.fromtimestamp(stat.st_mtime),
|
||
'relative_path': file_path.relative_to(self.project_root)
|
||
}
|
||
except Exception as e:
|
||
return {
|
||
'size': 0,
|
||
'modified': datetime.datetime.now(),
|
||
'relative_path': file_path.relative_to(self.project_root),
|
||
'error': str(e)
|
||
}
|
||
|
||
def read_file_content(self, file_path):
|
||
"""
|
||
读取文件内容
|
||
:param file_path: 文件路径
|
||
:return: str
|
||
"""
|
||
try:
|
||
# 尝试用UTF-8编码读取
|
||
with open(file_path, 'r', encoding='utf-8') as f:
|
||
return f.read()
|
||
except UnicodeDecodeError:
|
||
try:
|
||
# 如果UTF-8失败,尝试GBK编码
|
||
with open(file_path, 'r', encoding='gbk') as f:
|
||
return f.read()
|
||
except UnicodeDecodeError:
|
||
try:
|
||
# 如果还是失败,尝试latin-1编码
|
||
with open(file_path, 'r', encoding='latin-1') as f:
|
||
return f.read()
|
||
except Exception as e:
|
||
return f"[无法读取文件内容: {str(e)}]"
|
||
except Exception as e:
|
||
return f"[读取文件时发生错误: {str(e)}]"
|
||
|
||
def scan_project(self):
|
||
"""
|
||
扫描项目目录,获取所有需要导出的文件
|
||
:return: list
|
||
"""
|
||
files_to_export = []
|
||
|
||
for root, dirs, files in os.walk(self.project_root):
|
||
root_path = Path(root)
|
||
|
||
# 过滤掉需要排除的目录
|
||
dirs[:] = [d for d in dirs if not self.should_exclude_dir(root_path / d)]
|
||
|
||
for file in files:
|
||
file_path = root_path / file
|
||
|
||
if self.should_include_file(file_path):
|
||
file_info = self.get_file_info(file_path)
|
||
files_to_export.append({
|
||
'path': file_path,
|
||
'info': file_info
|
||
})
|
||
|
||
# 按相对路径排序
|
||
files_to_export.sort(key=lambda x: str(x['info']['relative_path']))
|
||
return files_to_export
|
||
|
||
def export_to_file(self, output_filename=None):
|
||
"""
|
||
导出代码到文件
|
||
:param output_filename: 输出文件名
|
||
"""
|
||
if not output_filename:
|
||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||
output_filename = f"project_code_export_{timestamp}.txt"
|
||
|
||
self.output_file = output_filename
|
||
files_to_export = self.scan_project()
|
||
|
||
print(f"开始导出项目代码...")
|
||
print(f"项目根目录: {self.project_root.absolute()}")
|
||
print(f"找到 {len(files_to_export)} 个文件需要导出")
|
||
print(f"输出文件: {output_filename}")
|
||
|
||
with open(output_filename, 'w', encoding='utf-8') as output:
|
||
# 写入文件头
|
||
self.write_header(output, files_to_export)
|
||
|
||
# 写入每个文件的内容
|
||
for i, file_data in enumerate(files_to_export, 1):
|
||
file_path = file_data['path']
|
||
file_info = file_data['info']
|
||
|
||
print(f"正在处理 ({i}/{len(files_to_export)}): {file_info['relative_path']}")
|
||
|
||
self.write_file_section(output, file_path, file_info)
|
||
|
||
# 写入文件尾
|
||
self.write_footer(output)
|
||
|
||
print(f"\n✅ 导出完成!")
|
||
print(f"输出文件: {output_filename}")
|
||
print(f"文件大小: {os.path.getsize(output_filename) / 1024:.2f} KB")
|
||
|
||
def write_header(self, output, files_to_export):
|
||
"""
|
||
写入文件头部信息
|
||
"""
|
||
output.write("=" * 80 + "\n")
|
||
output.write("项目代码导出文件\n")
|
||
output.write("=" * 80 + "\n")
|
||
output.write(f"项目名称: 基于Python的线上电商系统\n")
|
||
output.write(f"导出时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||
output.write(f"项目路径: {self.project_root.absolute()}\n")
|
||
output.write(f"文件总数: {len(files_to_export)}\n")
|
||
output.write("=" * 80 + "\n\n")
|
||
|
||
# 写入文件目录
|
||
output.write("📁 文件目录:\n")
|
||
output.write("-" * 50 + "\n")
|
||
for file_data in files_to_export:
|
||
file_info = file_data['info']
|
||
size_kb = file_info['size'] / 1024 if file_info['size'] > 0 else 0
|
||
output.write(f"{file_info['relative_path']} ({size_kb:.1f} KB)\n")
|
||
output.write("\n" + "=" * 80 + "\n\n")
|
||
|
||
def write_file_section(self, output, file_path, file_info):
|
||
"""
|
||
写入单个文件的内容
|
||
"""
|
||
relative_path = file_info['relative_path']
|
||
|
||
# 文件分隔符
|
||
output.write("🔸" + "=" * 78 + "\n")
|
||
output.write(f"📄 文件: {relative_path}\n")
|
||
output.write(f"📊 大小: {file_info['size']} bytes ({file_info['size'] / 1024:.2f} KB)\n")
|
||
output.write(f"🕒 修改时间: {file_info['modified'].strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||
|
||
if 'error' in file_info:
|
||
output.write(f"⚠️ 错误: {file_info['error']}\n")
|
||
|
||
output.write("🔸" + "=" * 78 + "\n\n")
|
||
|
||
# 文件内容
|
||
content = self.read_file_content(file_path)
|
||
output.write(content)
|
||
|
||
# 确保文件结尾有换行
|
||
if not content.endswith('\n'):
|
||
output.write('\n')
|
||
|
||
output.write("\n\n")
|
||
|
||
def write_footer(self, output):
|
||
"""
|
||
写入文件尾部信息
|
||
"""
|
||
output.write("=" * 80 + "\n")
|
||
output.write("导出完成\n")
|
||
output.write(f"导出时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||
output.write("=" * 80 + "\n")
|
||
|
||
def export_summary(self):
|
||
"""
|
||
导出项目摘要信息
|
||
"""
|
||
files_to_export = self.scan_project()
|
||
|
||
# 按文件类型统计
|
||
type_stats = {}
|
||
total_size = 0
|
||
|
||
for file_data in files_to_export:
|
||
file_path = file_data['path']
|
||
file_info = file_data['info']
|
||
|
||
ext = file_path.suffix.lower() or '无扩展名'
|
||
if ext not in type_stats:
|
||
type_stats[ext] = {'count': 0, 'size': 0}
|
||
|
||
type_stats[ext]['count'] += 1
|
||
type_stats[ext]['size'] += file_info['size']
|
||
total_size += file_info['size']
|
||
|
||
print("\n📊 项目统计信息:")
|
||
print("-" * 50)
|
||
print(f"总文件数: {len(files_to_export)}")
|
||
print(f"总大小: {total_size / 1024:.2f} KB")
|
||
print("\n📋 文件类型统计:")
|
||
|
||
for ext, stats in sorted(type_stats.items(), key=lambda x: x[1]['count'], reverse=True):
|
||
print(f"{ext:>10}: {stats['count']:>3} 个文件, {stats['size'] / 1024:>6.1f} KB")
|
||
|
||
|
||
def main():
|
||
"""
|
||
主函数
|
||
"""
|
||
print("🚀 项目代码导出工具")
|
||
print("=" * 50)
|
||
|
||
# 创建导出器
|
||
exporter = CodeExporter()
|
||
|
||
# 显示项目摘要
|
||
exporter.export_summary()
|
||
|
||
# 询问是否继续导出
|
||
print("\n" + "=" * 50)
|
||
choice = input("是否继续导出完整代码到文件? (y/n): ").lower().strip()
|
||
|
||
if choice in ['y', 'yes', '是']:
|
||
# 询问输出文件名
|
||
output_name = input("请输入输出文件名 (直接回车使用默认名称): ").strip()
|
||
if not output_name:
|
||
output_name = None
|
||
|
||
# 开始导出
|
||
exporter.export_to_file(output_name)
|
||
else:
|
||
print("取消导出。")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|