
 File path: ./Cargo.toml 
 
[package]
name = "reatler"
version = "0.3.0"
edition = "2021"
description = "A thing that can bundle files into single one"
author = "Ox4C656F leoyt109@gmail.com"
license = "MIT"

[dependencies]

 File path: ./src/project_type.rs 
 
use std::fmt;

#[derive(Eq, PartialEq)]
pub enum ProjectType {
    Javascript,
    Rust,
    Go,
    C,
    Cpp,
    Python,
    Java,
    Kotlin,
    Swift,
    Php,
    Ruby,
    Shell,
    Dart,
    Haskell,
    Scala,
    Perl,
    R,
    Elixir,
    CSharp,
    FSharp,
    Lua,
}
impl fmt::Display for ProjectType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let name = match self {
            ProjectType::Javascript => "Javascript",
            ProjectType::Rust => "Rust",
            ProjectType::Go => "Go",
            ProjectType::C => "C",
            ProjectType::Cpp => "C++",
            ProjectType::Python => "Python",
            ProjectType::Java => "Java",
            ProjectType::Kotlin => "Kotlin",
            ProjectType::Swift => "Swift",
            ProjectType::Php => "PHP",
            ProjectType::Ruby => "Ruby",
            ProjectType::Shell => "Shell",
            ProjectType::Dart => "Dart",
            ProjectType::Haskell => "Haskell",
            ProjectType::Scala => "Scala",
            ProjectType::Perl => "Perl",
            ProjectType::R => "R",
            ProjectType::Elixir => "Elixir",
            ProjectType::CSharp => "C#",
            ProjectType::FSharp => "F#",
            ProjectType::Lua => "Lua",
        };
        write!(f, "{}", name)
    }
}
impl ProjectType {
    pub fn from(file: &str) -> Vec<Self> {
        let mut types = Vec::new();

        match file {
            // JavaScript/TypeScript specific files
            "package.json" | "yarn.lock" | "pnpm-lock.yaml" | "vite.config.js"
            | "webpack.config.js" => {
                types.push(Self::Javascript);
            }
            // Rust specific files
            "Cargo.toml" | "Cargo.lock" | "build.rs" => {
                types.push(Self::Rust);
            }
            // Go specific files
            "go.mod" | "go.sum" => {
                types.push(Self::Go);
            }
            // C-specific files
            "Makefile" | "config.h" => {
                types.push(Self::C);
            }
            // C++ specific files
            "CMakeLists.txt" | ".clang-format" | ".clang-tidy" => {
                types.push(Self::Cpp);
                types.push(Self::C);
            }
            // Python specific files
            "requirements.txt" | "Pipfile" | "pyproject.toml" | "setup.py" | "tox.ini" => {
                types.push(Self::Python);
            }
            // Java specific files
            "pom.xml" | "build.gradle" | "settings.gradle" => {
                types.push(Self::Java);
            }
            // Kotlin specific files
            "build.gradle.kts" | "settings.gradle.kts" => {
                types.push(Self::Kotlin);
            }
            // Swift specific files
            "Package.swift" | "Info.plist" | ".xcodeproj" | ".xcworkspace" => {
                types.push(Self::Swift);
            }
            // PHP specific files
            "composer.json" | "composer.lock" => {
                types.push(Self::Php);
            }
            // Ruby specific files
            "Gemfile" | "Gemfile.lock" | "Rakefile" => {
                types.push(Self::Ruby);
            }
            // Shell specific files
            ".sh" | ".bashrc" | ".zshrc" | ".profile" => {
                types.push(Self::Shell);
            }
            // Dart specific files
            "pubspec.yaml" | ".packages" => {
                types.push(Self::Dart);
            }
            // Haskell specific files
            "stack.yaml" | "cabal.project" | ".ghci" => {
                types.push(Self::Haskell);
            }
            // Scala specific files
            "build.sbt" => {
                types.push(Self::Scala);
            }
            // Perl specific files
            "Makefile.PL" => {
                types.push(Self::Perl);
            }
            // R specific files
            "DESCRIPTION" | "NAMESPACE" => {
                types.push(Self::R);
            }
            // Elixir specific files
            "mix.exs" => {
                types.push(Self::Elixir);
            }
            // C# specific files
            ".csproj" | ".sln" | "app.config" => {
                types.push(Self::CSharp);
            }
            // F# specific files
            ".fsproj" => {
                types.push(Self::FSharp);
            }
            // Lua specific files
            "init.lua" | ".luacheckrc" => {
                types.push(Self::Lua);
            }
            _ => {}
        }

        types
    }

    pub fn get_files(&self) -> Vec<&str> {
        match self {
            Self::Javascript => vec![
                ".ts",
                ".js",
                ".jsx",
                ".tsx",
                ".json",
                "package.json",
                "yarn.lock",
                "pnpm-lock.yaml",
                "vite.config.js",
                "webpack.config.js",
                ".eslintrc",
                ".prettierrc",
                ".babelrc",
            ],
            Self::Rust => vec![
                ".rs",
                "Cargo.toml",
                "Cargo.lock",
                "build.rs",
                ".rustfmt.toml",
                ".clippy.toml",
            ],
            Self::Go => vec![
                ".go",
                "go.mod",
                "go.sum",
                ".golangci.yaml",
                "Makefile",
                "Dockerfile",
            ],
            Self::C => vec![".c", ".h", ".o", "Makefile", "config.h", "CMakeLists.txt"],
            Self::Cpp => vec![
                ".cpp",
                ".hpp",
                ".hxx",
                ".cxx",
                ".cc",
                ".o",
                "Makefile",
                "CMakeLists.txt",
                ".clang-format",
                ".clang-tidy",
            ],
            Self::Python => vec![
                ".py",              // Python source files
                "requirements.txt", // Dependency management
                "Pipfile",          // Pipenv dependency management
                "pyproject.toml",   // PEP 518 configuration
                "setup.py",         // Package configuration
                ".pylintrc",        // Pylint configuration
                "tox.ini",          // Testing framework configuration
                "Dockerfile",       // Docker build configuration
            ],
            Self::Java => vec![
                ".java",           // Java source files
                "pom.xml",         // Maven project descriptor
                "build.gradle",    // Gradle project descriptor
                "settings.gradle", // Gradle settings
                ".classpath",      // Eclipse project configuration
                ".project",        // Eclipse project configuration
            ],
            Self::Kotlin => vec![
                ".kt",                 // Kotlin source files
                ".kts",                // Kotlin scripts
                "build.gradle.kts",    // Gradle Kotlin DSL
                "settings.gradle.kts", // Gradle settings Kotlin DSL
            ],
            Self::Swift => vec![
                ".swift",        // Swift source files
                "Package.swift", // Swift package manager manifest
                "Info.plist",    // iOS/macOS app metadata
                ".xcodeproj",    // Xcode project file
                ".xcworkspace",  // Xcode workspace
            ],
            Self::Php => vec![
                ".php",          // PHP source files
                "composer.json", // Composer dependency manager
                "composer.lock", // Composer lock file
                ".env",          // Environment configuration
            ],
            Self::Ruby => vec![
                ".rb",          // Ruby source files
                "Gemfile",      // Ruby dependency management
                "Gemfile.lock", // Lock file for dependencies
                "Rakefile",     // Rake build scripts
            ],
            Self::Shell => vec![
                ".sh",      // Shell scripts
                ".bashrc",  // Bash configuration
                ".zshrc",   // Zsh configuration
                ".profile", // User profile configuration
            ],
            Self::Dart => vec![
                ".dart",        // Dart source files
                "pubspec.yaml", // Dart package manager manifest
                ".packages",    // Dart package resolution
            ],
            Self::Haskell => vec![
                ".hs",           // Haskell source files
                "stack.yaml",    // Stack configuration
                "cabal.project", // Cabal configuration
                ".ghci",         // GHC interactive environment
            ],
            Self::Scala => vec![
                ".scala",    // Scala source files
                "build.sbt", // SBT build file
                ".sc",       // Scala scripts
            ],
            Self::Perl => vec![
                ".pl",         // Perl source files
                ".pm",         // Perl modules
                "Makefile.PL", // Perl makefile
            ],
            Self::R => vec![
                ".R",          // R source files
                "DESCRIPTION", // R package description
                "NAMESPACE",   // R package namespace
            ],
            Self::Elixir => vec![
                ".ex",     // Elixir source files
                ".exs",    // Elixir scripts
                "mix.exs", // Mix build configuration
            ],
            Self::CSharp => vec![
                ".cs",        // C# source files
                ".csproj",    // C# project configuration
                ".sln",       // Solution files
                "app.config", // Application configuration
            ],
            Self::FSharp => vec![
                ".fs",     // F# source files
                ".fsproj", // F# project configuration
            ],
            Self::Lua => vec![
                ".lua",        // Lua source files
                "init.lua",    // Lua entry point
                ".luacheckrc", // Lua lint configuration
            ],
        }
    }
}

 File path: ./src/reatler.rs 
 
use crate::{
    choice::{self, get_scan_type, ScanType},
    dir::{scan_dir, ScanParams},
    project_type::ProjectType,
};
use std::{
    fs::File,
    io::{Read, Write},
    process::exit,
};

pub fn parse_gitignore() -> Vec<String> {
    let mut ignore = Vec::new();
    let mut file = match File::open(".gitignore") {
        Ok(file) => file,
        Err(_) => return ignore,
    };
    let mut buf = String::new();
    match file.read_to_string(&mut buf) {
        Ok(_) => (),
        Err(_) => return ignore,
    }

    for line in buf.lines() {
        if !line.is_empty() {
            ignore.push(line.to_string());
        }
    }
    ignore
}

pub fn parse_args(args: &[String]) -> &str {
    match args.get(1) {
        Some(str) => str,
        None => "./",
    }
}

pub fn run(args: &[String]) {
    let dir = parse_args(args);
    let is_manual = matches!(get_scan_type(args), ScanType::Manual);

    let params = if is_manual {
        get_scan_params_manual()
    } else {
        match get_scan_params_auto(dir) {
            Some(p) => p,
            None => {
                println!("Type autodiscovery failed, fallback to manual selection");
                get_scan_params_manual()
            }
        }
    };

    let file_names = match scan_dir(dir, params, true) {
        Ok(files) => files,
        Err(e) => {
            eprintln!("Error while scanning files:  {}", e);
            exit(0);
        }
    };
    for file in &file_names {
        println!("+{}", file)
    }
    add_files(&file_names).expect("Unable to write files");
}

fn add_files(file_names: &Vec<String>) -> Result<(), std::io::Error> {
    let mut output_file = File::create("output.txt").expect("Error occured with output file");

    for file_name in file_names {
        if append_file_to_output(file_name, &mut output_file).is_err() {
            println!("Unable to write {} to output", file_name)
        }
    }
    Ok(())
}

fn append_file_to_output(file_name: &str, output_file: &mut File) -> Result<(), std::io::Error> {
    let mut buf = String::new();
    let mut source_file = File::open(file_name).expect("Can't open file for reading");
    source_file
        .read_to_string(&mut buf)
        .expect("Can't read file to string");

    let file_name_line = format!("\n File path: {} \n \n", file_name);
    output_file
        .write_all(file_name_line.as_bytes())
        .expect("Can't write filename to output");
    output_file
        .write_all(buf.as_bytes())
        .expect("Can't write file to output");
    Ok(())
}
fn get_scan_params_manual() -> ScanParams {
    let include = choice::get_types();
    let mut ignore = choice::get_ignore();
    let gitignore = parse_gitignore();
    ignore.extend(gitignore);
    ScanParams { include, ignore }
}
fn get_scan_params_auto(dir: &str) -> Option<ScanParams> {
    let mut params = ScanParams::default();
    let gitignore = parse_gitignore();

    let tmp_params = ScanParams {
        include: vec!["".into()],
        ignore: gitignore.clone(),
    };

    let files = scan_dir(dir, tmp_params, false).unwrap_or_default();
    let types = get_project_types(&files)?;

    params.ignore.extend(gitignore);
    params.include.extend(
        types
            .iter()
            .flat_map(|project_type| project_type.get_files())
            .map(|p_t| p_t.to_string()),
    );
    Some(params)
}

fn get_project_types(files: &Vec<String>) -> Option<Vec<ProjectType>> {
    let mut types: Vec<ProjectType> = Vec::new();
    for file in files {
        match file.split("/").last() {
            Some(striped) => types.extend(ProjectType::from(striped)),
            None => println!("Bad shit happned with {}", file),
        }
    }

    types.dedup();

    if types.is_empty() {
        return None;
    }
    println!(
        "A {} project found!",
        types
            .iter()
            .map(|t| t.to_string())
            .collect::<Vec<String>>()
            .join(", ")
    );
    Some(types)
}

 File path: ./src/choice.rs 
 
use std::{io::Write, process::exit};

#[derive(Default)]
pub enum ScanType {
    Manual,
    #[default]
    Auto,
}
pub fn get_types() -> Vec<String> {
    print!("\nWhich file formats to include? (example: rs toml json) ");
    std::io::stdout().flush().expect("Could not flush stdin");
    let mut buf = String::new();
    std::io::stdin()
        .read_line(&mut buf)
        .expect("Unable to read from stdin");

    if buf.trim().is_empty() {
        println!("No file formats specified, exiting...");
        exit(1)
    }
    let buf: Vec<String> = buf
        .split(' ')
        .map(|val| val.trim().to_string())
        .map(|val| {
            if val.starts_with('.') {
                val
            } else {
                format!(".{}", val)
            }
        })
        .collect();

    buf
}

pub fn get_ignore() -> Vec<String> {
    print!("\nWhich files/directories to ignore? (example: target dist .d.ts) ");
    println!("Note: .gitignore parsing enabled");
    std::io::stdout().flush().expect("Could not flush stdin");
    let mut buf = String::new();
    std::io::stdin()
        .read_line(&mut buf)
        .expect("Unable to read from stdin");

    if buf.trim().is_empty() {
        println!("No files or directories specified");
        return vec![".git".into()];
    }
    let mut buf: Vec<String> = buf.split(' ').map(|val| val.trim().to_string()).collect();
    buf.push(".git".into());
    buf
}

pub fn get_scan_type(args: &[String]) -> ScanType {
    if args.iter().any(|v| v == "--manual") {
        ScanType::Manual
    } else {
        ScanType::Auto
    }
}

 File path: ./src/dir.rs 
 
use std::{fs::read_dir, path::Path};
#[derive(Default)]
pub struct ScanParams {
    pub ignore: Vec<String>,
    pub include: Vec<String>,
}
/// Scans a directory for files that match the include and ignore parameters
pub fn scan_dir(
    dirname: &str,
    params: ScanParams,
    recursive: bool,
) -> std::io::Result<Vec<String>> {
    let mut files: Vec<String> = Vec::new();
    dir_helper(dirname, &mut files, &params, recursive)?;
    Ok(files)
}

fn dir_helper(
    dirname: &str,
    files: &mut Vec<String>,
    params: &ScanParams,
    recursive: bool,
) -> std::io::Result<()> {
    let ScanParams { include, ignore } = params;
    for entry in read_dir(dirname)? {
        let entry = entry?;
        let path = entry.path();
        let is_dir = path.is_dir();
        let path_str = path.to_string_lossy().to_string();

        if is_ignored(&path_str, ignore) {
            continue;
        }

        if is_dir && recursive {
            dir_helper(&path_str, files, params, true)?;
        } else if is_included(&path_str, include) {
            files.push(path_str);
        }
    }

    Ok(())
}

/// Normalize a gitignore pattern to handle different path formats
fn normalize_pattern(pattern: &str) -> String {
    let pattern = pattern.trim();
    let pattern = pattern.strip_prefix("./").unwrap_or(pattern);
    let pattern = pattern.strip_prefix('/').unwrap_or(pattern);
    pattern.to_string()
}

/// Checks if a file should be ignored based on gitignore patterns
fn is_ignored(path: &str, ignored: &[String]) -> bool {
    let path = Path::new(path);
    let path_str = path.to_string_lossy();

    let file_name = path
        .file_name()
        .map(|s| s.to_string_lossy())
        .unwrap_or_default();

    for pattern in ignored {
        let normalized_pattern = normalize_pattern(pattern);

        if path_str.contains(&normalized_pattern) {
            return true;
        }

        if file_name.contains(&normalized_pattern) {
            return true;
        }
    }

    false
}
/// Updated is_included to match against the full path
fn is_included(path: &str, included: &[String]) -> bool {
    let path = Path::new(path);
    if let Some(file_name) = path.file_name() {
        let file_name = file_name.to_string_lossy();
        included
            .iter()
            .any(|included| file_name.ends_with(included))
    } else {
        false
    }
}

 File path: ./src/main.rs 
 
mod choice;
mod dir;
mod project_type;
mod reatler;
fn main() {
    let args = std::env::args().collect::<Vec<String>>();
    reatler::run(&args);
}

 File path: ./Cargo.lock 
 
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

[[package]]
name = "reatler"
version = "0.3.0"
