gather_all_code_from_crates/
process_crate_directory.rs

1crate::ix!();
2
3pub fn process_crate_directory(dir: &PathBuf, criteria: &AstFilterCriteria) -> AppResult<String> {
4    if !dir.exists() {
5        return Err(AppError::InvalidInput{reason:ErrorReason::MissingData});
6    }
7
8    let mut combined = String::new();
9    let mut dirs_to_visit = vec![dir.clone()];
10    while let Some(d) = dirs_to_visit.pop() {
11        let entries = fs::read_dir(&d).map_err(|e|AppError::Io{code:e.kind()})?;
12        for entry in entries {
13            let entry = entry.map_err(|e|AppError::Io{code:e.kind()})?;
14            let p = entry.path();
15
16            if !p.starts_with(dir.join("src")) {
17                continue; // only process files in src
18            }
19
20            if p.is_dir() {
21                dirs_to_visit.push(p);
22            } else {
23                if p.extension().and_then(|s|s.to_str()) == Some("rs") {
24                    let relative = match p.strip_prefix(dir) {
25                        Ok(rel) => rel.display().to_string(),
26                        Err(_) => p.display().to_string(),
27                    };
28                    if criteria.excluded_files().iter().any(|ex| ex == &relative) {
29                        continue;
30                    }
31
32                    // If exclude_main_file is true and this is src/main.rs, skip
33                    if *criteria.exclude_main_file() && relative == "src/main.rs" {
34                        continue;
35                    }
36
37                    let snippet = process_file(&p, criteria)?;
38                    if !snippet.trim().is_empty() {
39                        combined.push_str(&snippet);
40                        combined.push('\n');
41                    }
42                }
43            }
44        }
45    }
46
47    Ok(combined)
48}
49
50#[cfg(test)]
51mod process_crate_directory_tests {
52    use super::*;
53
54    #[test]
55    fn test_process_crate_directory_basic() {
56        let tmp_dir = TempDir::new().unwrap();
57        let src_dir = tmp_dir.path().join("src");
58        fs::create_dir(&src_dir).unwrap();
59
60        let lib_path = src_dir.join("lib.rs");
61        let code_lib = r#"pub fn lib_func() {} fn priv_func() {}"#;
62        {
63            let mut f = File::create(&lib_path).unwrap();
64            f.write_all(code_lib.as_bytes()).unwrap();
65        }
66
67        let mod_path = src_dir.join("mod.rs");
68        let code_mod = r#"#[test] fn test_in_mod() {} fn normal_in_mod() {}"#;
69        {
70            let mut f = File::create(&mod_path).unwrap();
71            f.write_all(code_mod.as_bytes()).unwrap();
72        }
73
74        let criteria = AstFilterCriteriaBuilder::default()
75            .include_tests(false)
76            .omit_private(true)
77            .build().unwrap();
78
79        let result = process_crate_directory(&tmp_dir.path().to_path_buf(), &criteria).unwrap();
80        // From lib.rs: omit_private=true leaves only pub fn lib_func
81        // From mod.rs: tests excluded, so test_in_mod gone, normal_in_mod private => gone
82        assert!(result.contains("pub fn lib_func()"));
83        assert!(!result.contains("priv_func"));
84        assert!(!result.contains("test_in_mod"));
85        assert!(!result.contains("normal_in_mod"));
86    }
87
88    #[test]
89    fn test_process_crate_directory_excluded_file() {
90        let tmp_dir = TempDir::new().unwrap();
91        let src_dir = tmp_dir.path().join("src");
92        fs::create_dir(&src_dir).unwrap();
93
94        let keep_path = src_dir.join("keep.rs");
95        let exclude_path = src_dir.join("exclude.rs");
96
97        let code_keep = r#"pub fn keep_me() {}"#;
98        {
99            let mut f = File::create(&keep_path).unwrap();
100            f.write_all(code_keep.as_bytes()).unwrap();
101        }
102
103        let code_exclude = r#"pub fn exclude_me() {}"#;
104        {
105            let mut f = File::create(&exclude_path).unwrap();
106            f.write_all(code_exclude.as_bytes()).unwrap();
107        }
108
109        let criteria = AstFilterCriteriaBuilder::default()
110            .excluded_files(vec!["src/exclude.rs".to_string()])
111            .build().unwrap();
112
113        let result = process_crate_directory(&tmp_dir.path().to_path_buf(), &criteria).unwrap();
114        assert!(result.contains("keep_me"));
115        assert!(!result.contains("exclude_me"));
116    }
117
118    #[test]
119    fn test_process_crate_directory_exclude_main_file() {
120        let tmp_dir = TempDir::new().unwrap();
121        let src_dir = tmp_dir.path().join("src");
122        fs::create_dir(&src_dir).unwrap();
123
124        let main_path = src_dir.join("main.rs");
125        let lib_path = src_dir.join("lib.rs");
126
127        let code_main = r#"fn main() {}"#;
128        {
129            let mut f = File::create(&main_path).unwrap();
130            f.write_all(code_main.as_bytes()).unwrap();
131        }
132
133        let code_lib = r#"pub fn visible() {}"#;
134        {
135            let mut f = File::create(&lib_path).unwrap();
136            f.write_all(code_lib.as_bytes()).unwrap();
137        }
138
139        let criteria = AstFilterCriteriaBuilder::default()
140            .exclude_main_file(true)
141            .build().unwrap();
142
143        info!("criteria: {:#?}", criteria);
144
145        let result = process_crate_directory(&tmp_dir.path().to_path_buf(), &criteria).unwrap();
146
147        info!("result: {:#?}", result);
148
149        // main.rs excluded
150        assert!(result.contains("visible"));
151        assert!(!result.contains("main()"));
152    }
153
154    #[test]
155    fn test_error_handling_invalid_input() {
156        let path = PathBuf::from("non_existent_dir");
157        let criteria = AstFilterCriteriaBuilder::default().build().unwrap();
158        let result = process_crate_directory(&path, &criteria);
159        match result {
160            Err(AppError::InvalidInput { reason: ErrorReason::MissingData }) => (),
161            _ => panic!("Expected MissingData error"),
162        }
163    }
164}