1use crate::utils::error::CodeDigestError;
4use crate::utils::file_ext::FileType;
5use anyhow::Result;
6use glob::Pattern;
7use ignore::{Walk, WalkBuilder};
8use rayon::prelude::*;
9use std::path::{Path, PathBuf};
10use std::sync::Arc;
11
12#[derive(Debug, Clone)]
25pub struct CompiledPriority {
26 pub matcher: Pattern,
28 pub weight: f32,
30 pub original_pattern: String,
32}
33
34impl CompiledPriority {
35 pub fn new(pattern: &str, weight: f32) -> Result<Self, glob::PatternError> {
37 let matcher = Pattern::new(pattern)?;
38 Ok(Self { matcher, weight, original_pattern: pattern.to_string() })
39 }
40
41 pub fn try_from_config_priority(
43 priority: &crate::config::Priority,
44 ) -> Result<Self, glob::PatternError> {
45 Self::new(&priority.pattern, priority.weight)
46 }
47}
48
49#[derive(Debug, Clone)]
51pub struct WalkOptions {
52 pub max_file_size: Option<usize>,
54 pub follow_links: bool,
56 pub include_hidden: bool,
58 pub parallel: bool,
60 pub ignore_file: String,
62 pub ignore_patterns: Vec<String>,
64 pub include_patterns: Vec<String>,
66 pub custom_priorities: Vec<CompiledPriority>,
68}
69
70impl WalkOptions {
71 pub fn from_config(config: &crate::cli::Config) -> Result<Self> {
73 let mut custom_priorities = Vec::new();
75 for priority in &config.custom_priorities {
76 match CompiledPriority::try_from_config_priority(priority) {
77 Ok(compiled) => custom_priorities.push(compiled),
78 Err(e) => {
79 return Err(CodeDigestError::ConfigError(format!(
80 "Invalid glob pattern '{}' in custom priorities: {}",
81 priority.pattern, e
82 ))
83 .into());
84 }
85 }
86 }
87
88 Ok(WalkOptions {
89 max_file_size: Some(10 * 1024 * 1024), follow_links: false,
91 include_hidden: false,
92 parallel: true,
93 ignore_file: ".digestignore".to_string(),
94 ignore_patterns: vec![],
95 include_patterns: vec![],
96 custom_priorities,
97 })
98 }
99}
100
101impl Default for WalkOptions {
102 fn default() -> Self {
103 WalkOptions {
104 max_file_size: Some(10 * 1024 * 1024), follow_links: false,
106 include_hidden: false,
107 parallel: true,
108 ignore_file: ".digestignore".to_string(),
109 ignore_patterns: vec![],
110 include_patterns: vec![],
111 custom_priorities: vec![],
112 }
113 }
114}
115
116#[derive(Debug, Clone)]
118pub struct FileInfo {
119 pub path: PathBuf,
121 pub relative_path: PathBuf,
123 pub size: u64,
125 pub file_type: FileType,
127 pub priority: f32,
129}
130
131impl FileInfo {
132 pub fn file_type_display(&self) -> &'static str {
134 use crate::utils::file_ext::FileType;
135 match self.file_type {
136 FileType::Rust => "Rust",
137 FileType::Python => "Python",
138 FileType::JavaScript => "JavaScript",
139 FileType::TypeScript => "TypeScript",
140 FileType::Go => "Go",
141 FileType::Java => "Java",
142 FileType::Cpp => "C++",
143 FileType::C => "C",
144 FileType::CSharp => "C#",
145 FileType::Ruby => "Ruby",
146 FileType::Php => "PHP",
147 FileType::Swift => "Swift",
148 FileType::Kotlin => "Kotlin",
149 FileType::Scala => "Scala",
150 FileType::Haskell => "Haskell",
151 FileType::Markdown => "Markdown",
152 FileType::Json => "JSON",
153 FileType::Yaml => "YAML",
154 FileType::Toml => "TOML",
155 FileType::Xml => "XML",
156 FileType::Html => "HTML",
157 FileType::Css => "CSS",
158 FileType::Text => "Text",
159 FileType::Other => "Other",
160 }
161 }
162}
163
164pub fn walk_directory(root: &Path, options: WalkOptions) -> Result<Vec<FileInfo>> {
166 if !root.exists() {
167 return Err(CodeDigestError::InvalidPath(format!(
168 "Directory does not exist: {}",
169 root.display()
170 ))
171 .into());
172 }
173
174 if !root.is_dir() {
175 return Err(CodeDigestError::InvalidPath(format!(
176 "Path is not a directory: {}",
177 root.display()
178 ))
179 .into());
180 }
181
182 let root = root.canonicalize()?;
183 let walker = build_walker(&root, &options);
184
185 if options.parallel {
186 walk_parallel(walker, &root, &options)
187 } else {
188 walk_sequential(walker, &root, &options)
189 }
190}
191
192fn build_walker(root: &Path, options: &WalkOptions) -> Walk {
194 let mut builder = WalkBuilder::new(root);
195
196 builder
198 .follow_links(options.follow_links)
199 .hidden(!options.include_hidden)
200 .git_ignore(true)
201 .git_global(true)
202 .git_exclude(true)
203 .ignore(true)
204 .parents(true)
205 .add_custom_ignore_filename(&options.ignore_file);
206
207 for pattern in &options.ignore_patterns {
209 let _ = builder.add_ignore(pattern);
210 }
211
212 for pattern in &options.include_patterns {
214 let _ = builder.add_ignore(format!("!{pattern}"));
215 }
216
217 builder.build()
218}
219
220fn walk_sequential(walker: Walk, root: &Path, options: &WalkOptions) -> Result<Vec<FileInfo>> {
222 let mut files = Vec::new();
223
224 for entry in walker {
225 let entry = entry?;
226 let path = entry.path();
227
228 if path.is_dir() {
230 continue;
231 }
232
233 if let Some(file_info) = process_file(path, root, options)? {
235 files.push(file_info);
236 }
237 }
238
239 Ok(files)
240}
241
242fn walk_parallel(walker: Walk, root: &Path, options: &WalkOptions) -> Result<Vec<FileInfo>> {
244 use itertools::Itertools;
245
246 let root = Arc::new(root.to_path_buf());
247 let options = Arc::new(options.clone());
248
249 let entries: Vec<_> = walker.filter_map(|e| e.ok()).filter(|e| !e.path().is_dir()).collect();
251
252 let results: Vec<Result<Option<FileInfo>, CodeDigestError>> = entries
254 .into_par_iter()
255 .map(|entry| {
256 let path = entry.path();
257 match process_file(path, &root, &options) {
258 Ok(file_info) => Ok(file_info),
259 Err(e) => Err(CodeDigestError::FileProcessingError {
260 path: path.display().to_string(),
261 error: e.to_string(),
262 }),
263 }
264 })
265 .collect();
266
267 let (successes, errors): (Vec<_>, Vec<_>) = results.into_iter().partition_result();
269
270 if !errors.is_empty() {
272 eprintln!("Warning: {} files could not be processed:", errors.len());
273 for error in &errors {
274 eprintln!(" {error}");
275 }
276 }
277
278 let files: Vec<FileInfo> = successes.into_iter().flatten().collect();
280 Ok(files)
281}
282
283fn process_file(path: &Path, root: &Path, options: &WalkOptions) -> Result<Option<FileInfo>> {
285 let metadata = match std::fs::metadata(path) {
287 Ok(meta) => meta,
288 Err(_) => return Ok(None), };
290
291 let size = metadata.len();
292
293 if let Some(max_size) = options.max_file_size {
295 if size > max_size as u64 {
296 return Ok(None);
297 }
298 }
299
300 let relative_path = path.strip_prefix(root).unwrap_or(path).to_path_buf();
302
303 let file_type = FileType::from_path(path);
305
306 let priority = calculate_priority(&file_type, &relative_path, &options.custom_priorities);
308
309 Ok(Some(FileInfo { path: path.to_path_buf(), relative_path, size, file_type, priority }))
310}
311
312fn calculate_priority(
314 file_type: &FileType,
315 relative_path: &Path,
316 custom_priorities: &[CompiledPriority],
317) -> f32 {
318 let base_score = calculate_base_priority(file_type, relative_path);
320
321 for priority in custom_priorities {
323 if priority.matcher.matches_path(relative_path) {
324 return base_score + priority.weight;
325 }
326 }
327
328 base_score
330}
331
332fn calculate_base_priority(file_type: &FileType, relative_path: &Path) -> f32 {
334 let mut score: f32 = match file_type {
335 FileType::Rust => 1.0,
336 FileType::Python => 0.9,
337 FileType::JavaScript => 0.9,
338 FileType::TypeScript => 0.95,
339 FileType::Go => 0.9,
340 FileType::Java => 0.85,
341 FileType::Cpp => 0.85,
342 FileType::C => 0.8,
343 FileType::CSharp => 0.85,
344 FileType::Ruby => 0.8,
345 FileType::Php => 0.75,
346 FileType::Swift => 0.85,
347 FileType::Kotlin => 0.85,
348 FileType::Scala => 0.8,
349 FileType::Haskell => 0.75,
350 FileType::Markdown => 0.6,
351 FileType::Json => 0.5,
352 FileType::Yaml => 0.5,
353 FileType::Toml => 0.5,
354 FileType::Xml => 0.4,
355 FileType::Html => 0.4,
356 FileType::Css => 0.4,
357 FileType::Text => 0.3,
358 FileType::Other => 0.2,
359 };
360
361 let path_str = relative_path.to_string_lossy().to_lowercase();
363 if path_str.contains("main") || path_str.contains("index") {
364 score *= 1.5;
365 }
366 if path_str.contains("lib") || path_str.contains("src") {
367 score *= 1.2;
368 }
369 if path_str.contains("test") || path_str.contains("spec") {
370 score *= 0.8;
371 }
372 if path_str.contains("example") || path_str.contains("sample") {
373 score *= 0.7;
374 }
375
376 if relative_path.parent().is_none() || relative_path.parent() == Some(Path::new("")) {
378 match file_type {
379 FileType::Toml | FileType::Yaml | FileType::Json => score *= 1.3,
380 _ => {}
381 }
382 }
383
384 score.min(2.0) }
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390 use std::fs::{self, File};
391 use tempfile::TempDir;
392
393 #[test]
394 fn test_walk_directory_basic() {
395 let temp_dir = TempDir::new().unwrap();
396 let root = temp_dir.path();
397
398 File::create(root.join("main.rs")).unwrap();
400 File::create(root.join("lib.rs")).unwrap();
401 fs::create_dir(root.join("src")).unwrap();
402 File::create(root.join("src/utils.rs")).unwrap();
403
404 let options = WalkOptions::default();
405 let files = walk_directory(root, options).unwrap();
406
407 assert_eq!(files.len(), 3);
408 assert!(files.iter().any(|f| f.relative_path == PathBuf::from("main.rs")));
409 assert!(files.iter().any(|f| f.relative_path == PathBuf::from("lib.rs")));
410 assert!(files.iter().any(|f| f.relative_path == PathBuf::from("src/utils.rs")));
411 }
412
413 #[test]
414 fn test_walk_with_digestignore() {
415 let temp_dir = TempDir::new().unwrap();
416 let root = temp_dir.path();
417
418 File::create(root.join("main.rs")).unwrap();
420 File::create(root.join("ignored.rs")).unwrap();
421
422 fs::write(root.join(".digestignore"), "ignored.rs").unwrap();
424
425 let options = WalkOptions::default();
426 let files = walk_directory(root, options).unwrap();
427
428 assert_eq!(files.len(), 1);
429 assert_eq!(files[0].relative_path, PathBuf::from("main.rs"));
430 }
431
432 #[test]
433 fn test_priority_calculation() {
434 let rust_priority = calculate_priority(&FileType::Rust, Path::new("src/main.rs"), &[]);
435 let test_priority = calculate_priority(&FileType::Rust, Path::new("tests/test.rs"), &[]);
436 let doc_priority = calculate_priority(&FileType::Markdown, Path::new("README.md"), &[]);
437
438 assert!(rust_priority > doc_priority);
439 assert!(rust_priority > test_priority);
440 }
441
442 #[test]
443 fn test_file_size_limit() {
444 let temp_dir = TempDir::new().unwrap();
445 let root = temp_dir.path();
446
447 let large_file = root.join("large.txt");
449 let data = vec![0u8; 1024 * 1024]; fs::write(&large_file, &data).unwrap();
451
452 File::create(root.join("small.txt")).unwrap();
454
455 let options = WalkOptions {
456 max_file_size: Some(512 * 1024), ..Default::default()
458 };
459
460 let files = walk_directory(root, options).unwrap();
461
462 assert_eq!(files.len(), 1);
463 assert_eq!(files[0].relative_path, PathBuf::from("small.txt"));
464 }
465
466 #[test]
467 fn test_walk_empty_directory() {
468 let temp_dir = TempDir::new().unwrap();
469 let root = temp_dir.path();
470
471 let options = WalkOptions::default();
472 let files = walk_directory(root, options).unwrap();
473
474 assert_eq!(files.len(), 0);
475 }
476
477 #[test]
478 fn test_walk_options_from_config() {
479 use crate::cli::Config;
480 use tempfile::TempDir;
481
482 let temp_dir = TempDir::new().unwrap();
483 let config = Config {
484 prompt: None,
485 paths: Some(vec![temp_dir.path().to_path_buf()]),
486 output_file: None,
487 max_tokens: None,
488 llm_tool: crate::cli::LlmTool::default(),
489 quiet: false,
490 verbose: false,
491 config: None,
492 progress: false,
493 repo: None,
494 read_stdin: false,
495 copy: false,
496 enhanced_context: false,
497 custom_priorities: vec![],
498 };
499
500 let options = WalkOptions::from_config(&config).unwrap();
501
502 assert_eq!(options.max_file_size, Some(10 * 1024 * 1024));
503 assert!(!options.follow_links);
504 assert!(!options.include_hidden);
505 assert!(options.parallel);
506 assert_eq!(options.ignore_file, ".digestignore");
507 }
508
509 #[test]
510 fn test_walk_with_custom_options() {
511 let temp_dir = TempDir::new().unwrap();
512 let root = temp_dir.path();
513
514 File::create(root.join("main.rs")).unwrap();
516 File::create(root.join("test.rs")).unwrap();
517 File::create(root.join("readme.md")).unwrap();
518
519 let options =
520 WalkOptions { ignore_patterns: vec!["*.md".to_string()], ..Default::default() };
521
522 let files = walk_directory(root, options).unwrap();
523
524 assert!(files.len() >= 2);
526 assert!(files.iter().any(|f| f.relative_path == PathBuf::from("main.rs")));
527 assert!(files.iter().any(|f| f.relative_path == PathBuf::from("test.rs")));
528 }
529
530 #[test]
531 fn test_walk_with_include_patterns() {
532 let temp_dir = TempDir::new().unwrap();
533 let root = temp_dir.path();
534
535 File::create(root.join("main.rs")).unwrap();
537 File::create(root.join("lib.rs")).unwrap();
538 File::create(root.join("README.md")).unwrap();
539
540 let options =
541 WalkOptions { include_patterns: vec!["*.rs".to_string()], ..Default::default() };
542
543 let files = walk_directory(root, options).unwrap();
544
545 assert!(files.len() >= 2);
547 assert!(files.iter().any(|f| f.relative_path == PathBuf::from("main.rs")));
548 assert!(files.iter().any(|f| f.relative_path == PathBuf::from("lib.rs")));
549 }
550
551 #[test]
552 fn test_walk_subdirectories() {
553 let temp_dir = TempDir::new().unwrap();
554 let root = temp_dir.path();
555
556 fs::create_dir(root.join("src")).unwrap();
558 fs::create_dir(root.join("src").join("utils")).unwrap();
559 File::create(root.join("main.rs")).unwrap();
560 File::create(root.join("src").join("lib.rs")).unwrap();
561 File::create(root.join("src").join("utils").join("helpers.rs")).unwrap();
562
563 let options = WalkOptions::default();
564 let files = walk_directory(root, options).unwrap();
565
566 assert_eq!(files.len(), 3);
567 assert!(files.iter().any(|f| f.relative_path == PathBuf::from("main.rs")));
568 assert!(files.iter().any(|f| f.relative_path == PathBuf::from("src/lib.rs")));
569 assert!(files.iter().any(|f| f.relative_path == PathBuf::from("src/utils/helpers.rs")));
570 }
571
572 #[test]
573 fn test_priority_edge_cases() {
574 let main_priority = calculate_priority(&FileType::Rust, Path::new("main.rs"), &[]);
576 let lib_priority = calculate_priority(&FileType::Rust, Path::new("lib.rs"), &[]);
577 let nested_main_priority =
578 calculate_priority(&FileType::Rust, Path::new("src/main.rs"), &[]);
579
580 assert!(main_priority > lib_priority);
581 assert!(nested_main_priority > lib_priority);
582
583 let toml_priority = calculate_priority(&FileType::Toml, Path::new("Cargo.toml"), &[]);
585 let nested_toml_priority =
586 calculate_priority(&FileType::Toml, Path::new("config/app.toml"), &[]);
587
588 assert!(toml_priority > nested_toml_priority);
589 }
590
591 #[test]
594 fn test_custom_priority_no_match_returns_base_priority() {
595 let custom_priorities = [CompiledPriority::new("docs/*.md", 5.0).unwrap()];
598
599 let priority =
601 calculate_priority(&FileType::Rust, Path::new("src/main.rs"), &custom_priorities);
602
603 let expected_base = calculate_priority(&FileType::Rust, Path::new("src/main.rs"), &[]);
605 assert_eq!(priority, expected_base);
606 }
607
608 #[test]
609 fn test_custom_priority_single_match_adds_weight() {
610 let custom_priorities = [CompiledPriority::new("src/core/mod.rs", 10.0).unwrap()];
612
613 let priority =
615 calculate_priority(&FileType::Rust, Path::new("src/core/mod.rs"), &custom_priorities);
616
617 let base_priority = calculate_priority(&FileType::Rust, Path::new("src/core/mod.rs"), &[]);
619 let expected = base_priority + 10.0;
620 assert_eq!(priority, expected);
621 }
622
623 #[test]
624 fn test_custom_priority_glob_pattern_match() {
625 let custom_priorities = [CompiledPriority::new("src/**/*.rs", 2.5).unwrap()];
627
628 let priority = calculate_priority(
630 &FileType::Rust,
631 Path::new("src/api/handlers.rs"),
632 &custom_priorities,
633 );
634
635 let base_priority =
637 calculate_priority(&FileType::Rust, Path::new("src/api/handlers.rs"), &[]);
638 let expected = base_priority + 2.5;
639 assert_eq!(priority, expected);
640 }
641
642 #[test]
643 fn test_custom_priority_negative_weight() {
644 let custom_priorities = [CompiledPriority::new("tests/*", -0.5).unwrap()];
646
647 let priority = calculate_priority(
649 &FileType::Rust,
650 Path::new("tests/test_utils.rs"),
651 &custom_priorities,
652 );
653
654 let base_priority =
656 calculate_priority(&FileType::Rust, Path::new("tests/test_utils.rs"), &[]);
657 let expected = base_priority - 0.5;
658 assert_eq!(priority, expected);
659 }
660
661 #[test]
662 fn test_custom_priority_first_match_wins() {
663 let custom_priorities = [
665 CompiledPriority::new("src/**/*.rs", 5.0).unwrap(),
666 CompiledPriority::new("src/main.rs", 100.0).unwrap(),
667 ];
668
669 let priority =
671 calculate_priority(&FileType::Rust, Path::new("src/main.rs"), &custom_priorities);
672
673 let base_priority = calculate_priority(&FileType::Rust, Path::new("src/main.rs"), &[]);
675 let expected = base_priority + 5.0;
676 assert_eq!(priority, expected);
677 }
678
679 #[test]
680 fn test_custom_priority_zero_weight() {
681 let custom_priorities = [CompiledPriority::new("*.rs", 0.0).unwrap()];
683
684 let priority =
686 calculate_priority(&FileType::Rust, Path::new("src/main.rs"), &custom_priorities);
687
688 let base_priority = calculate_priority(&FileType::Rust, Path::new("src/main.rs"), &[]);
690 assert_eq!(priority, base_priority);
691 }
692
693 #[test]
694 fn test_custom_priority_empty_list() {
695 let custom_priorities: &[CompiledPriority] = &[];
697
698 let priority =
700 calculate_priority(&FileType::Rust, Path::new("src/main.rs"), custom_priorities);
701
702 let expected_base = calculate_priority(&FileType::Rust, Path::new("src/main.rs"), &[]);
704 assert_eq!(priority, expected_base);
705 }
706
707 #[test]
710 fn test_config_to_walker_data_flow() {
711 use crate::config::{ConfigFile, Priority};
712 use std::fs::{self, File};
713 use tempfile::TempDir;
714
715 let temp_dir = TempDir::new().unwrap();
717 let root = temp_dir.path();
718
719 File::create(root.join("high_priority.rs")).unwrap();
721 File::create(root.join("normal.txt")).unwrap();
722 fs::create_dir(root.join("logs")).unwrap();
723 File::create(root.join("logs/app.log")).unwrap();
724
725 let config_file = ConfigFile {
727 priorities: vec![
728 Priority { pattern: "*.rs".to_string(), weight: 10.0 },
729 Priority { pattern: "logs/*.log".to_string(), weight: -5.0 },
730 ],
731 ..Default::default()
732 };
733
734 let mut config = crate::cli::Config {
736 prompt: None,
737 paths: Some(vec![root.to_path_buf()]),
738 repo: None,
739 read_stdin: false,
740 output_file: None,
741 max_tokens: None,
742 llm_tool: crate::cli::LlmTool::default(),
743 quiet: false,
744 verbose: false,
745 config: None,
746 progress: false,
747 copy: false,
748 enhanced_context: false,
749 custom_priorities: vec![],
750 };
751 config_file.apply_to_cli_config(&mut config);
752
753 let walk_options = WalkOptions::from_config(&config).unwrap();
755
756 let files = walk_directory(root, walk_options).unwrap();
758
759 let rs_file = files
761 .iter()
762 .find(|f| f.relative_path.to_string_lossy().contains("high_priority.rs"))
763 .unwrap();
764 let log_file =
765 files.iter().find(|f| f.relative_path.to_string_lossy().contains("app.log")).unwrap();
766 let txt_file = files
767 .iter()
768 .find(|f| f.relative_path.to_string_lossy().contains("normal.txt"))
769 .unwrap();
770
771 let base_rs = calculate_base_priority(&rs_file.file_type, &rs_file.relative_path);
773 let base_txt = calculate_base_priority(&txt_file.file_type, &txt_file.relative_path);
774 let base_log = calculate_base_priority(&log_file.file_type, &log_file.relative_path);
775
776 assert_eq!(rs_file.priority, base_rs + 10.0);
778
779 assert_eq!(log_file.priority, base_log - 5.0);
781
782 assert_eq!(txt_file.priority, base_txt);
784 }
785
786 #[test]
787 fn test_invalid_glob_pattern_in_config() {
788 use crate::config::{ConfigFile, Priority};
789 use tempfile::TempDir;
790
791 let temp_dir = TempDir::new().unwrap();
792
793 let config_file = ConfigFile {
795 priorities: vec![Priority { pattern: "[invalid_glob".to_string(), weight: 5.0 }],
796 ..Default::default()
797 };
798
799 let mut config = crate::cli::Config {
800 prompt: None,
801 paths: Some(vec![temp_dir.path().to_path_buf()]),
802 repo: None,
803 read_stdin: false,
804 output_file: None,
805 max_tokens: None,
806 llm_tool: crate::cli::LlmTool::default(),
807 quiet: false,
808 verbose: false,
809 config: None,
810 progress: false,
811 copy: false,
812 enhanced_context: false,
813 custom_priorities: vec![],
814 };
815 config_file.apply_to_cli_config(&mut config);
816
817 let result = WalkOptions::from_config(&config);
819 assert!(result.is_err());
820
821 let error_msg = result.unwrap_err().to_string();
823 assert!(error_msg.contains("invalid_glob") || error_msg.contains("Invalid"));
824 }
825
826 #[test]
827 fn test_empty_custom_priorities_config() {
828 use crate::config::ConfigFile;
829 use tempfile::TempDir;
830
831 let temp_dir = TempDir::new().unwrap();
832
833 let config_file = ConfigFile {
835 priorities: vec![], ..Default::default()
837 };
838
839 let mut config = crate::cli::Config {
840 prompt: None,
841 paths: Some(vec![temp_dir.path().to_path_buf()]),
842 repo: None,
843 read_stdin: false,
844 output_file: None,
845 max_tokens: None,
846 llm_tool: crate::cli::LlmTool::default(),
847 quiet: false,
848 verbose: false,
849 config: None,
850 progress: false,
851 copy: false,
852 enhanced_context: false,
853 custom_priorities: vec![],
854 };
855 config_file.apply_to_cli_config(&mut config);
856
857 let walk_options = WalkOptions::from_config(&config).unwrap();
859
860 assert!(walk_directory(temp_dir.path(), walk_options).is_ok());
863 }
864
865 #[test]
866 fn test_empty_pattern_in_config() {
867 use crate::config::{ConfigFile, Priority};
868 use tempfile::TempDir;
869
870 let temp_dir = TempDir::new().unwrap();
871
872 let config_file = ConfigFile {
874 priorities: vec![Priority { pattern: "".to_string(), weight: 5.0 }],
875 ..Default::default()
876 };
877
878 let mut config = crate::cli::Config {
879 prompt: None,
880 paths: Some(vec![temp_dir.path().to_path_buf()]),
881 repo: None,
882 read_stdin: false,
883 output_file: None,
884 max_tokens: None,
885 llm_tool: crate::cli::LlmTool::default(),
886 quiet: false,
887 verbose: false,
888 config: None,
889 progress: false,
890 copy: false,
891 enhanced_context: false,
892 custom_priorities: vec![],
893 };
894 config_file.apply_to_cli_config(&mut config);
895
896 let result = WalkOptions::from_config(&config);
898 assert!(result.is_ok());
899
900 let walk_options = result.unwrap();
902 assert_eq!(walk_options.custom_priorities.len(), 1);
903 }
904
905 #[test]
906 fn test_extreme_weights_in_config() {
907 use crate::config::{ConfigFile, Priority};
908 use tempfile::TempDir;
909
910 let temp_dir = TempDir::new().unwrap();
911
912 let config_file = ConfigFile {
914 priorities: vec![
915 Priority { pattern: "*.rs".to_string(), weight: f32::MAX },
916 Priority { pattern: "*.txt".to_string(), weight: f32::MIN },
917 Priority { pattern: "*.md".to_string(), weight: f32::INFINITY },
918 Priority { pattern: "*.log".to_string(), weight: f32::NEG_INFINITY },
919 ],
920 ..Default::default()
921 };
922
923 let mut config = crate::cli::Config {
924 prompt: None,
925 paths: Some(vec![temp_dir.path().to_path_buf()]),
926 repo: None,
927 read_stdin: false,
928 output_file: None,
929 max_tokens: None,
930 llm_tool: crate::cli::LlmTool::default(),
931 quiet: false,
932 verbose: false,
933 config: None,
934 progress: false,
935 copy: false,
936 enhanced_context: false,
937 custom_priorities: vec![],
938 };
939 config_file.apply_to_cli_config(&mut config);
940
941 let result = WalkOptions::from_config(&config);
943 assert!(result.is_ok());
944
945 let walk_options = result.unwrap();
946 assert_eq!(walk_options.custom_priorities.len(), 4);
947 }
948
949 #[test]
950 fn test_file_info_file_type_display() {
951 let file_info = FileInfo {
952 path: PathBuf::from("test.rs"),
953 relative_path: PathBuf::from("test.rs"),
954 size: 1000,
955 file_type: FileType::Rust,
956 priority: 1.0,
957 };
958
959 assert_eq!(file_info.file_type_display(), "Rust");
960
961 let file_info_md = FileInfo {
962 path: PathBuf::from("README.md"),
963 relative_path: PathBuf::from("README.md"),
964 size: 500,
965 file_type: FileType::Markdown,
966 priority: 0.6,
967 };
968
969 assert_eq!(file_info_md.file_type_display(), "Markdown");
970 }
971}