147 lines
4.9 KiB
Bash
Executable File
147 lines
4.9 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# --- cflat (c♭) ---
|
|
# Flattens C/C++ projects into a single text file.
|
|
# https://git.alidavid.hu/david/cflat
|
|
|
|
VERSION="1.0.0"
|
|
SCRIPT_NAME=$(basename "$0")
|
|
|
|
# 1. HELP FUNCTION
|
|
function show_help {
|
|
echo "cflat (c♭) v$VERSION"
|
|
echo "Flattens C/C++ projects into a single text file."
|
|
echo "https://git.alidavid.hu/david/cflat"
|
|
echo ""
|
|
echo "Usage: ./$SCRIPT_NAME [DIRECTORY] [-e PATTERN]..."
|
|
echo ""
|
|
echo "Options:"
|
|
echo " -e, --exclude Exclude exact path relative to the target directory."
|
|
echo " 'aaa.a' -> Excludes ./aaa.a (Target root only)"
|
|
echo " 'src/lib' -> Excludes ./src/lib (and its contents)"
|
|
echo " '*.txt' -> Excludes ./*.txt (Target root only)"
|
|
echo " '*/*.txt' -> Excludes ./*/*.txt (Recursive)"
|
|
echo " -h, --help, -? Show this message"
|
|
exit 0
|
|
}
|
|
|
|
# 2. ARGUMENT PARSING
|
|
TARGET_DIR="."
|
|
EXCLUDE_ARGS=()
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-h|--help|-\?) show_help ;;
|
|
-e|--exclude)
|
|
if [[ -n "$2" && "$2" != -* ]]; then
|
|
# Clean trailing slash from pattern to match find output safely
|
|
CLEAN_PAT="${2%/}"
|
|
EXCLUDE_ARGS+=("$CLEAN_PAT")
|
|
shift 2
|
|
else
|
|
echo "Error: --exclude requires an argument." >&2; exit 1
|
|
fi
|
|
;;
|
|
-*) echo "Error: Unknown option $1" >&2; exit 1 ;;
|
|
*) TARGET_DIR="$1"; shift ;;
|
|
esac
|
|
done
|
|
|
|
# Clean target directory (remove trailing slash)
|
|
TARGET_DIR=${TARGET_DIR%/}
|
|
|
|
# 3. BUILD FIND COMMAND
|
|
# We anchor every exclusion to the TARGET_DIR.
|
|
# This ensures that exclusions are strict (e.g., 'aaa.a' only matches the root file).
|
|
|
|
# Default ignores (Hidden files, obj, bin, build, and our own output)
|
|
BASE_IGNORES=(
|
|
-path '*/.*' -o
|
|
-path '*/obj' -o
|
|
-path '*/bin' -o
|
|
-path '*/build' -o
|
|
-name "cflat-*.txt" -o
|
|
-name "$SCRIPT_NAME"
|
|
)
|
|
|
|
# Build User Excludes
|
|
USER_PRUNE_ARGS=()
|
|
if [[ ${#EXCLUDE_ARGS[@]} -gt 0 ]]; then
|
|
for pattern in "${EXCLUDE_ARGS[@]}"; do
|
|
# Construct the strict path match anchored to TARGET_DIR
|
|
# e.g., if target is "." and pattern is "file", we match "./file"
|
|
MATCH_PATH="$TARGET_DIR/$pattern"
|
|
|
|
USER_PRUNE_ARGS+=("-path" "$MATCH_PATH" "-o")
|
|
done
|
|
fi
|
|
|
|
# Combine logic: ( UserExcludes OR BaseIgnores ) -prune
|
|
FINAL_PRUNE_ARGS=("(" "${USER_PRUNE_ARGS[@]}" "${BASE_IGNORES[@]}" ")" "-prune")
|
|
|
|
# 4. CONFIGURATION
|
|
TIMESTAMP=$(date +"%Y-%m-%dT%H-%M-%S")
|
|
OUTPUT="cflat-export-$TIMESTAMP.txt"
|
|
MAX_SIZE_KB=100
|
|
HAS_CLANG=$(command -v clang-format)
|
|
|
|
# 5. HEADER
|
|
echo "Generating export for: $TARGET_DIR"
|
|
echo "C-FLAT EXPORT" > "$OUTPUT"
|
|
echo "Generated on: $(date)" >> "$OUTPUT"
|
|
echo "----------------------------------------" >> "$OUTPUT"
|
|
|
|
# 6. STATISTICS
|
|
echo "PROJECT STATISTICS:" >> "$OUTPUT"
|
|
find "$TARGET_DIR" "${FINAL_PRUNE_ARGS[@]}" -o -type f \( -name "*.cpp" -o -name "*.hpp" -o -name "*.c" -o -name "*.h" \) -print0 | xargs -0 -r wc -l | tail -n 1 >> "$OUTPUT"
|
|
|
|
# 7. KEY DEFINITIONS
|
|
echo -e "\nKEY DEFINITIONS FOUND:" >> "$OUTPUT"
|
|
find "$TARGET_DIR" "${FINAL_PRUNE_ARGS[@]}" -o -type f -exec grep -nE "class [A-Za-z0-9_]+|struct [A-Za-z0-9_]+" {} + >> "$OUTPUT"
|
|
|
|
# 8. PROJECT STRUCTURE
|
|
echo -e "\nPROJECT STRUCTURE:" >> "$OUTPUT"
|
|
# Print all non-pruned paths for the tree visualization
|
|
find "$TARGET_DIR" "${FINAL_PRUNE_ARGS[@]}" -o -print | sort | sed -e "s;[^/]*/;|____;g;s;____|; |;g" >> "$OUTPUT"
|
|
|
|
echo -e "\n--- FILE CONTENTS ---\n" >> "$OUTPUT"
|
|
|
|
# 9. CONTENT EXPORT
|
|
find "$TARGET_DIR" "${FINAL_PRUNE_ARGS[@]}" -o -type f -print | while read -r file; do
|
|
|
|
if [[ "$file" =~ \.(cpp|hpp|h|c|cc|cxx)$ ]]; then
|
|
echo "--- START OF SOURCE FILE: $file ---" >> "$OUTPUT"
|
|
if [ -n "$HAS_CLANG" ]; then
|
|
clang-format --style=LLVM "$file" >> "$OUTPUT"
|
|
else
|
|
cat "$file" >> "$OUTPUT"
|
|
fi
|
|
echo -e "\n--- END OF SOURCE FILE: $file ---\n" >> "$OUTPUT"
|
|
|
|
elif [[ "$file" == *"Makefile"* ]] || [[ "$file" =~ \.(txt|py|sh|md|cmake)$ ]]; then
|
|
echo "--- START OF CONFIG/SCRIPT: $file ---" >> "$OUTPUT"
|
|
cat "$file" >> "$OUTPUT"
|
|
echo -e "\n--- END OF CONFIG/SCRIPT: $file ---\n" >> "$OUTPUT"
|
|
|
|
elif grep -qI . "$file"; then
|
|
FILE_SIZE=$(du -k "$file" | cut -f1)
|
|
if [ "$FILE_SIZE" -lt "$MAX_SIZE_KB" ]; then
|
|
echo "--- START OF ASSET FILE: $file ---" >> "$OUTPUT"
|
|
cat "$file" >> "$OUTPUT"
|
|
echo -e "\n--- END OF ASSET FILE: $file ---\n" >> "$OUTPUT"
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# 10. AUTO-GITIGNORE
|
|
GITIGNORE="$TARGET_DIR/.gitignore"
|
|
if [ -f "$GITIGNORE" ]; then
|
|
if ! grep -qxF "$SCRIPT_NAME" "$GITIGNORE"; then
|
|
echo -e "\n# C-Flat ignores\n$SCRIPT_NAME" >> "$GITIGNORE"
|
|
fi
|
|
if ! grep -qF "cflat-*.txt" "$GITIGNORE"; then
|
|
echo "cflat-*.txt" >> "$GITIGNORE"
|
|
fi
|
|
fi
|
|
|
|
echo "Done: Output saved to $OUTPUT" |