From 230c326e9fa4f90907dea43bdbc15e510bc88f70 Mon Sep 17 00:00:00 2001 From: David Ali Date: Thu, 29 Jan 2026 01:08:41 +0100 Subject: [PATCH] first commit --- LICENSE | 21 ++++++++ README.md | 101 +++++++++++++++++++++++++++++++++++++ cflat | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 269 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100755 cflat diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2e2989f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Dávid Ali + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7fab34c --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +# cflat (c♭) + +**Make your C++ project fall flat.** + +## What is this? + +`cflat` takes your complex directory structure and exports it into a single text file. It merges your entire C/C++ project into one massive stream of code. + +**Note:** It does not actually delete your folders. It just ignores them in the output. Your architecture is safe. + +## Why? + +Civilized people keep files separate. You, apparently, need them together. + +* **AI Fodder:** Feed your entire codebase to an LLM so it can hallucinate a fix for that segfault you've been ignoring. +* **Code Reviews:** Send a single 50,000-line file to your reviewer. Assert dominance. +* **Grepping:** Search for a string across the project instantly because you still haven't memorized how to use `grep -r`. +* **Spite:** Annoy your lead architect by rendering their carefully crafted folder structure irrelevant. + +## Features + +* **Recursive:** It hunts down source files in every nested folder. Deep sub-directories are no longer safe places to hide bad code. +* **Fake Elegance:** It runs `clang-format` (if installed) so your spaghetti code at least *looks* professional in the export. +* **Hygiene:** It ignores `obj`, `bin`, `.git`, and other binary garbage that nobody wants to read. +* **Self-Preservation:** It automatically adds itself and the output file to `.gitignore`. This prevents you from accidentally committing a 12MB text file. +* **Flexible:** Run it on the current folder, a target folder, or exclude specific shameful directories. + +## Installation + +Trusting random scripts from the internet is a key part of the developer experience. + +```bash +curl -O https://git.alidavid.hu/david/cflat/raw/branch/main/cflat +``` + +Make it executable: + +```bash +chmod +x cflat +``` + +## Usage + +**Default Behavior:** +Run this in your project root to flatten everything. + +```bash +./cflat +``` + +**Target a specific folder:** + +```bash +./cflat ../my-other-project/ +``` + +**The "Denial" option (Exclusions):** +Exclude folders you don't want the world (or the AI) to see. + +```bash +./cflat -e build -e node_modules -e "legacy_code" +``` + +### ⚠️ Wildcards (Important) + +Your shell expands `*` before the script sees it. If you don't use quotes, the script will break. + +```bash +# Good: +./cflat -e "*.tmp" + +# Bad (The shell expands this and confuses the script): +./cflat -e *.tmp +``` + +**Need help?** + +```bash +./cflat -? +``` + +## Output + +The script generates a file named `cflat-export-YYYY-MM-DD.txt`. It contains: + +1. **Project Stats:** How many lines of code you are dealing with. +2. **The Tree:** A visual map of the directory structure. +3. **The Cast:** A list of all classes and structs found. +4. **The Blob:** The full content of every source file, stitched together. + +## Requirements + +* **OS:** Linux or macOS. (Windows users: please use WSL). +* **Shell:** Bash. +* **Optional:** + * `tree`: For structure visualization. + * `clang-format`: For code cleaning. + +## License + +MIT \ No newline at end of file diff --git a/cflat b/cflat new file mode 100755 index 0000000..351aed3 --- /dev/null +++ b/cflat @@ -0,0 +1,147 @@ +#!/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" \ No newline at end of file