1use crate::tokens::TokenizerKind;
2use globset::GlobMatcher;
3use std::path::PathBuf;
4
5#[derive(Debug, Clone)]
6pub struct Config {
7 pub path: PathBuf,
8 pub include_extensions: Option<Vec<String>>,
9 pub exclude_extensions: Option<Vec<String>>,
10 pub match_patterns: Option<Vec<GlobMatcher>>,
11 pub output_file: Option<PathBuf>,
12 pub dry_run: bool,
13 pub stats_only: bool,
14 pub gitignore_path: Option<PathBuf>,
15 pub max_file_size: u64,
16 pub compress: bool,
17 pub full_match_patterns: Option<Vec<GlobMatcher>>,
18 pub token_budget: Option<usize>,
19 pub tokenizer: TokenizerKind,
20}
21
22impl Default for Config {
23 fn default() -> Self {
24 Self {
25 path: PathBuf::from("."),
26 include_extensions: None,
27 exclude_extensions: None,
28 match_patterns: None,
29 output_file: None,
30 dry_run: false,
31 stats_only: false,
32 gitignore_path: None,
33 max_file_size: 1024 * 1024, compress: false,
35 full_match_patterns: None,
36 token_budget: None,
37 tokenizer: TokenizerKind::default(),
38 }
39 }
40}
41
42impl Config {
43 pub fn should_include_extension(&self, ext: &str) -> bool {
44 if let Some(ref include) = self.include_extensions {
46 if !include.iter().any(|e| e.eq_ignore_ascii_case(ext)) {
47 return false;
48 }
49 }
50
51 if let Some(ref exclude) = self.exclude_extensions {
53 if exclude.iter().any(|e| e.eq_ignore_ascii_case(ext)) {
54 return false;
55 }
56 }
57
58 true
59 }
60
61 pub fn should_include_by_match(&self, file_name: &str) -> bool {
64 match &self.match_patterns {
65 Some(patterns) => patterns.iter().any(|m| m.is_match(file_name)),
66 None => true,
67 }
68 }
69
70 pub fn is_full_match(&self, file_name: &str) -> bool {
73 match &self.full_match_patterns {
74 Some(patterns) => patterns.iter().any(|m| m.is_match(file_name)),
75 None => false,
76 }
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use globset::Glob;
84
85 #[test]
86 fn test_include_only() {
87 let config = Config {
88 include_extensions: Some(vec!["rs".to_string(), "toml".to_string()]),
89 ..Default::default()
90 };
91
92 assert!(config.should_include_extension("rs"));
93 assert!(config.should_include_extension("toml"));
94 assert!(!config.should_include_extension("json"));
95 }
96
97 #[test]
98 fn test_exclude_only() {
99 let config = Config {
100 exclude_extensions: Some(vec!["test".to_string(), "json".to_string()]),
101 ..Default::default()
102 };
103
104 assert!(config.should_include_extension("rs"));
105 assert!(!config.should_include_extension("test"));
106 assert!(!config.should_include_extension("json"));
107 }
108
109 #[test]
110 fn test_include_and_exclude() {
111 let config = Config {
112 include_extensions: Some(vec!["rs".to_string(), "toml".to_string()]),
113 exclude_extensions: Some(vec!["toml".to_string()]),
114 ..Default::default()
115 };
116
117 assert!(config.should_include_extension("rs"));
118 assert!(!config.should_include_extension("toml")); assert!(!config.should_include_extension("json"));
120 }
121
122 #[test]
123 fn test_match_no_patterns() {
124 let config = Config::default();
125 assert!(config.should_include_by_match("anything.rs"));
126 }
127
128 #[test]
129 fn test_match_single_pattern() {
130 let config = Config {
131 match_patterns: Some(vec![Glob::new("*_test.go").unwrap().compile_matcher()]),
132 ..Default::default()
133 };
134
135 assert!(config.should_include_by_match("user_test.go"));
136 assert!(config.should_include_by_match("auth_test.go"));
137 assert!(!config.should_include_by_match("main.go"));
138 assert!(!config.should_include_by_match("test.rs"));
139 }
140
141 #[test]
142 fn test_match_multiple_patterns() {
143 let config = Config {
144 match_patterns: Some(vec![
145 Glob::new("*_test.go").unwrap().compile_matcher(),
146 Glob::new("*.spec.js").unwrap().compile_matcher(),
147 ]),
148 ..Default::default()
149 };
150
151 assert!(config.should_include_by_match("user_test.go"));
152 assert!(config.should_include_by_match("button.spec.js"));
153 assert!(!config.should_include_by_match("main.go"));
154 }
155}