flatten_rust/
lib.rs

1//! High-performance codebase flattening library
2//!
3//! This library provides functionality for flattening codebases into
4//! markdown format with intelligent exclusion patterns based on gitignore templates.
5
6pub mod config;
7pub mod exclusions;
8
9use std::collections::HashSet;
10use std::fs;
11use std::path::Path;
12
13/// Configuration for codebase flattening
14///
15/// Controls which files and folders are processed and how they're displayed
16pub struct FlattenConfig {
17    /// Folders to skip during processing
18    pub skip_folders: HashSet<String>,
19    /// File extensions to skip
20    pub skip_extensions: HashSet<String>,
21    /// Whether to show skipped items in the output
22    pub show_skipped: bool,
23    /// Maximum file size to process in bytes
24    pub max_file_size: u64,
25    /// Whether to include hidden files and folders
26    pub include_hidden: bool,
27    /// Maximum directory traversal depth
28    pub max_depth: usize,
29}
30
31impl Default for FlattenConfig {
32    fn default() -> Self {
33        Self {
34            skip_folders: HashSet::new(),
35            skip_extensions: HashSet::new(),
36            show_skipped: false,
37            max_file_size: 104857600, // 100MB
38            include_hidden: false,
39            max_depth: usize::MAX,
40        }
41    }
42}
43
44impl FlattenConfig {
45    /// Creates a new FlattenConfig with default settings
46    ///
47    /// # Examples
48    /// ```
49    /// use flatten_rust::FlattenConfig;
50    /// 
51    /// let config = FlattenConfig::new();
52    /// ```
53    pub fn new() -> Self {
54        Self::default()
55    }
56
57    pub fn should_skip_path(&self, path: &Path) -> bool {
58        if let Some(name) = path.file_name()
59            && let Some(name_str) = name.to_str() {
60            // Skip hidden files unless explicitly included
61            if !self.include_hidden && name_str.starts_with('.') {
62                return true;
63            }
64            return self.skip_folders.contains(name_str);
65        }
66        false
67    }
68
69    pub fn should_skip_file(&self, path: &Path) -> bool {
70        if let Some(extension) = path.extension()
71            && let Some(ext_str) = extension.to_str() {
72            return self.skip_extensions.contains(ext_str);
73        }
74        false
75    }
76}
77
78pub fn create_test_structure() -> anyhow::Result<tempfile::TempDir> {
79    let temp_dir = tempfile::TempDir::new()?;
80
81    // Create basic structure
82    fs::create_dir_all(temp_dir.path().join("src"))?;
83    fs::create_dir_all(temp_dir.path().join("tests"))?;
84    fs::create_dir_all(temp_dir.path().join("node_modules"))?;
85
86    // Create files
87    fs::write(
88        temp_dir.path().join("src/main.rs"),
89        "fn main() { println!(\"Hello\"); }",
90    )?;
91    fs::write(
92        temp_dir.path().join("src/lib.rs"),
93        "pub fn hello() { \"world\" }",
94    )?;
95    fs::write(
96        temp_dir.path().join("tests/integration.rs"),
97        "#[test] fn test_integration() {}",
98    )?;
99    fs::write(temp_dir.path().join("README.md"), "# Test Project")?;
100    fs::write(
101        temp_dir.path().join("Cargo.toml"),
102        "[package]\nname = \"test\"\nversion = \"0.1.0\"",
103    )?;
104
105    // Create a binary file
106    fs::write(temp_dir.path().join("test.bin"), b"\x00\x01\x02\x03\x04")?;
107
108    Ok(temp_dir)
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use std::fs;
115
116    #[test]
117    fn test_config_skip_folders() {
118        let mut config = FlattenConfig::new();
119        config.skip_folders.insert("node_modules".to_string());
120        config.skip_folders.insert(".git".to_string());
121
122        assert!(config.should_skip_path(Path::new("/project/node_modules")));
123        assert!(config.should_skip_path(Path::new("/project/.git")));
124        assert!(!config.should_skip_path(Path::new("/project/src")));
125    }
126
127    #[test]
128    fn test_config_skip_extensions() {
129        let mut config = FlattenConfig::new();
130        config.skip_extensions.insert("exe".to_string());
131        config.skip_extensions.insert("bin".to_string());
132
133        assert!(config.should_skip_file(Path::new("/project/test.exe")));
134        assert!(config.should_skip_file(Path::new("/project/test.bin")));
135        assert!(!config.should_skip_file(Path::new("/project/test.rs")));
136    }
137
138    #[test]
139    fn test_create_test_structure() {
140        let temp_dir = create_test_structure().unwrap();
141
142        assert!(temp_dir.path().join("src").exists());
143        assert!(temp_dir.path().join("tests").exists());
144        assert!(temp_dir.path().join("node_modules").exists());
145        assert!(temp_dir.path().join("src/main.rs").exists());
146        assert!(temp_dir.path().join("README.md").exists());
147    }
148
149    #[test]
150    fn test_file_content_reading() {
151        let temp_dir = create_test_structure().unwrap();
152        let main_rs_path = temp_dir.path().join("src/main.rs");
153
154        let content = fs::read_to_string(&main_rs_path).unwrap();
155        assert!(content.contains("fn main()"));
156    }
157}