/
perminevma
/
frontend
Обзор
Документация
Войти
/
perminevma
/
frontend
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
dump_code.py
179 строк
7 KB
ivgrd
+svetlaya_tema
28 авг 2025, 22:16
28 авг 2025, 22:16
3494e37
Код
Авторство
О чём код?
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import os import sys import argparse from pathlib import Path DEFAULT_OUTPUT = "project_dump.txt" EXCLUDE_DIRS = { ".git", ".hg", ".svn", "__pycache__", ".mypy_cache", ".pytest_cache", "node_modules", ".pnpm-store", ".yarn", ".yarn/cache", ".next", ".nuxt", ".svelte-kit", "out", "build", "dist", ".parcel-cache", ".turbo", ".vite", ".cache", "coverage", ".gradle", "target", ".idea", ".vscode", ".venv", "venv", "env" } EXCLUDE_FILES = {".DS_Store", "Thumbs.db"} NAME_ALWAYS_INCLUDE = { "Dockerfile", "dockerfile", "Makefile", "makefile", "Procfile", "tsconfig.json", "jsconfig.json", "package.json", "package-lock.json", "pnpm-lock.yaml", "yarn.lock", "pyproject.toml", "poetry.lock", "requirements.txt", "docker-compose.yml", "docker-compose.yaml", ".gitignore", ".gitattributes" } def is_binary(path: Path, sample_size: int = 4096) -> bool: try: with path.open("rb") as f: chunk = f.read(sample_size) if b"\x00" in chunk: return True chunk.decode("utf-8") return False except Exception: return True def iter_files(root: Path, follow_symlinks: bool = False): for dirpath, dirnames, filenames in os.walk(root, followlinks=follow_symlinks): # фильтруем каталоги dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS] for fn in filenames: if fn in EXCLUDE_FILES: continue yield Path(dirpath) / fn def render_tree(root: Path) -> str: """Похоже на вывод `tree`, но без внешних зависимостей.""" lines = [] root = root.resolve() for dirpath, dirnames, filenames in os.walk(root): # не лезем в мусорные папки dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS] rel = Path(dirpath).relative_to(root) indent = " " * (0 if str(rel) == "." else len(rel.parts)) if str(rel) != ".": lines.append(f"{indent}{rel.name}/") # показываем только имена (без мусора) for name in sorted(filenames): if name in EXCLUDE_FILES: continue lines.append(f"{indent} {name}") return "\n".join([f"{root.name}/"] + lines) def should_include_text_file(p: Path, include_env: bool, include_locks: bool) -> bool: name = p.name if name in NAME_ALWAYS_INCLUDE: if name in {"yarn.lock", "pnpm-lock.yaml", "package-lock.json", "poetry.lock"}: return include_locks return True if name.startswith(".env"): return include_env # Остальные — берём всё, что похоже на текст (фильтр по бинарности ниже) return True def dump_project( root_dir: Path, output_file: Path, include_env: bool = False, include_locks: bool = False, max_file_bytes: int = 2_000_000, follow_symlinks: bool = False ): root_dir = root_dir.resolve() files_written = 0 bytes_written = 0 with output_file.open("w", encoding="utf-8") as out: out.write(f"# PROJECT DUMP\n# root: {root_dir}\n\n") out.write("# PROJECT TREE\n") out.write("```\n") out.write(render_tree(root_dir)) out.write("\n```\n\n") out.write("# FILES\n") for path in iter_files(root_dir, follow_symlinks=follow_symlinks): # сам дамп не включаем if path.resolve() == output_file.resolve(): continue # пропускаем мусор по правилам if not should_include_text_file(path, include_env, include_locks): continue # бинарники — в топку if is_binary(path): continue try: size = path.stat().st_size rel = path.relative_to(root_dir) if size > max_file_bytes: out.write(f"{'='*80}\n# Файл: {rel}\n") out.write(f"# Пропущен: {size} > лимита {max_file_bytes} байт\n") out.write(f"{'='*80}\n\n") continue with path.open("r", encoding="utf-8", errors="replace") as f: content = f.read() out.write(f"{'='*80}\n# Файл: {rel}\n{'='*80}\n\n") out.write(content) if not content.endswith("\n"): out.write("\n") out.write("\n") files_written += 1 bytes_written += len(content.encode("utf-8", errors="ignore")) except Exception as e: rel = path.relative_to(root_dir) out.write(f"{'='*80}\n# Файл: {rel}\n{'='*80}\n\n") out.write(f"<<Не удалось прочитать файл: {e}>>\n\n") print(f"[OK] {output_file.name} создан в {output_file.parent}") print(f" Файлов: {files_written}, ~{bytes_written} байт текста") def main(): parser = argparse.ArgumentParser( description="Собрать дерево проекта и содержимое всех текстовых файлов (бэк+фронт) в один TXT." ) # ВАЖНО: по умолчанию корень = папка, где лежит этот скрипт default_root = Path(__file__).resolve().parent parser.add_argument("--root", default=str(default_root), help="Корень проекта. По умолчанию: папка, где лежит скрипт.") parser.add_argument("--output", default=DEFAULT_OUTPUT, help=f"Имя выходного файла (по умолчанию {DEFAULT_OUTPUT}).") parser.add_argument("--include-env", action="store_true", help="Включать .env* (ОСТОРОЖНО: секреты).") parser.add_argument("--include-locks", action="store_true", help="Включать lock-файлы (может раздуть дамп).") parser.add_argument("--max-file-bytes", type=int, default=2_000_000, help="Максимальный размер одного файла (байт).") parser.add_argument("--follow-symlinks", action="store_true", help="Следовать по symlink (обычно не нужно).") args = parser.parse_args() root_dir = Path(args.root).resolve() output_file = (root_dir / args.output).resolve() dump_project( root_dir=root_dir, output_file=output_file, include_env=args.include_env, include_locks=args.include_locks, max_file_bytes=args.max_file_bytes, follow_symlinks=args.follow_symlinks ) if __name__ == "__main__": main()