#!/usr/bin/env bash # projdump.sh — dump project structure and source code into text files set -euo pipefail usage() { cat >&2 < Создаёт два файла: -tree.txt — дерево проекта (через tree) -code.txt — содержимое всех исходных файлов Игнорирует типичные артефакты сборки, кэши, IDE-файлы и зависимости. Параметры: -h, --help Показать эту справку EOF } if [[ $# -eq 0 ]] || [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then usage exit 0 fi TARGET_DIR="$1" if [[ ! -d "$TARGET_DIR" ]]; then echo "Ошибка: директория '$TARGET_DIR' не существует." >&2 exit 1 fi # Привести к абсолютному пути для надёжности TARGET_DIR="$(cd "$TARGET_DIR" && pwd)" # Имя базовой директории BASENAME=$(basename "$TARGET_DIR") # Проверка зависимостей if ! command -v tree >/dev/null 2>&1; then echo "Ошибка: требуется утилита 'tree'. Установите её (например, apt install tree)." >&2 exit 1 fi # Расширенный список игнорируемых паттернов # Охватывает: Python, Node.js, Rust, Go, C/C++ (CMake, build dirs), Java, .NET, CI, OS, etc. IGNORE_LIST=( "third_party" "3rd_party" "3rdparty" ".git" ".svn" ".hg" "node_modules" "__pycache__" "*.pyc" ".venv" "venv" "env" ".env" ".idea" ".vscode" ".vs" ".DS_Store" "Thumbs.db" "dist" "build" "out" "target" ".pytest_cache" ".mypy_cache" ".next" ".nuxt" ".output" ".svelte-kit" "coverage" ".cache" ".parcel-cache" ".eslintcache" ".yarn" "yarn-error.log" "package-lock.json" "yarn.lock" "pnpm-lock.yaml" "Cargo.lock" "go.sum" ".gradle" "gradle" ".mvn" "mvnw" "mvnw.cmd" "*.swp" "*.swo" ".stack-work" "_build" "deps" ".cargo" ".rustup" ".clangd" ".ccls-cache" "CMakeFiles" "CMakeCache.txt" "CTestTestfile.cmake" "cmake_install.cmake" "*.dSYM" "*.ilk" "*.pdb" "*.obj" "*.o" "*.so" "*.dylib" "*.dll" "*.exe" "*.bin" "*.hex" "*.elf" "tags" "TAGS" "GPATH" "GTAGS" "GRTAGS" "GSYMS" ".github" ".gitlab" ".circleci" ".travis.yml" "Jenkinsfile" "Dockerfile*" "docker-compose*.yml" "*.log" "logs" "tmp" "temp" ".terraform" ".tfstate" ".tfstate.backup" "*.tfvars" ) # === Подготовка аргументов для find === FIND_IGNORE_ARGS=() for pat in "${IGNORE_LIST[@]}"; do # Для шаблонов с /* или * внутри — используем -name или -path соответственно if [[ "$pat" == */* ]]; then FIND_IGNORE_ARGS+=(-o -path "*/$pat") else FIND_IGNORE_ARGS+=(-o -name "$pat") fi done # Строим команду find: пропускаем игнорируемые, печатаем остальные файлы # \( -false ... \) гарантирует корректную логику без начального -o FIND_CMD=(find "$TARGET_DIR" \( -false "${FIND_IGNORE_ARGS[@]}" \) -prune -o -type f -print0) # === Паттерны для tree (только имена, без путей; tree использует glob-паттерны через |) === TREE_IGNORE_PATTERNS="" for i in "${!IGNORE_LIST[@]}"; do pat="${IGNORE_LIST[i]}" # tree не поддерживает пути — только имена файлов/директорий # поэтому берём только basename шаблона, если он содержит / if [[ "$pat" == */* ]]; then # Например, ".github/workflows" → игнорируем только ".github" base_pat="${pat%%/*}" else base_pat="$pat" fi # Удаляем звёздочки для tree? Нет — tree поддерживает * в -I if [[ -z "$TREE_IGNORE_PATTERNS" ]]; then TREE_IGNORE_PATTERNS="$base_pat" else TREE_IGNORE_PATTERNS="$TREE_IGNORE_PATTERNS|$base_pat" fi done # === Сбор кода === { while IFS= read -r -d '' file; do echo "========================================" echo "// FILE: $file" echo "========================================" cat "$file" echo -e "\n\n" done < <("${FIND_CMD[@]}") } > "${BASENAME}-code.txt" # === Построение дерева === tree -I "$TREE_IGNORE_PATTERNS" "$TARGET_DIR" > "${BASENAME}-tree.txt" echo "Готово:" echo " Дерево: ${BASENAME}-tree.txt" echo " Код: ${BASENAME}-code.txt"