rusty_files/search/
query.rs

1use crate::core::error::{Result, SearchError};
2use crate::core::types::{DateFilter, MatchMode, SearchScope, SizeFilter};
3use crate::filters::{parse_relative_date, parse_size};
4
5#[derive(Debug, Clone)]
6pub struct Query {
7    pub pattern: String,
8    pub match_mode: MatchMode,
9    pub scope: SearchScope,
10    pub size_filter: Option<SizeFilter>,
11    pub date_filter: Option<DateFilter>,
12    pub extensions: Vec<String>,
13    pub max_results: Option<usize>,
14}
15
16impl Query {
17    pub fn new(pattern: String) -> Self {
18        Self {
19            pattern,
20            match_mode: MatchMode::CaseInsensitive,
21            scope: SearchScope::Name,
22            size_filter: None,
23            date_filter: None,
24            extensions: Vec::new(),
25            max_results: None,
26        }
27    }
28
29    pub fn with_match_mode(mut self, mode: MatchMode) -> Self {
30        self.match_mode = mode;
31        self
32    }
33
34    pub fn with_scope(mut self, scope: SearchScope) -> Self {
35        self.scope = scope;
36        self
37    }
38
39    pub fn with_size_filter(mut self, filter: SizeFilter) -> Self {
40        self.size_filter = Some(filter);
41        self
42    }
43
44    pub fn with_date_filter(mut self, filter: DateFilter) -> Self {
45        self.date_filter = Some(filter);
46        self
47    }
48
49    pub fn with_extensions(mut self, extensions: Vec<String>) -> Self {
50        self.extensions = extensions;
51        self
52    }
53
54    pub fn with_max_results(mut self, max: usize) -> Self {
55        self.max_results = Some(max);
56        self
57    }
58}
59
60pub struct QueryParser;
61
62impl QueryParser {
63    pub fn parse(input: &str) -> Result<Query> {
64        let mut query = Query::new(String::new());
65        let parts: Vec<&str> = input.split_whitespace().collect();
66
67        let mut pattern_parts = Vec::new();
68        let mut i = 0;
69
70        while i < parts.len() {
71            let part = parts[i];
72
73            if part.contains(':') {
74                let (key, value) = part.split_once(':').unwrap();
75                match key.to_lowercase().as_str() {
76                    "ext" | "extension" => {
77                        query.extensions = value.split(',').map(|s| s.to_string()).collect();
78                    }
79                    "size" => {
80                        query.size_filter = Self::parse_size_filter(value)?;
81                    }
82                    "modified" | "date" => {
83                        query.date_filter = Self::parse_date_filter(value)?;
84                    }
85                    "mode" => {
86                        query.match_mode = Self::parse_match_mode(value)?;
87                    }
88                    "scope" => {
89                        query.scope = Self::parse_scope(value)?;
90                    }
91                    "limit" | "max" => {
92                        if let Ok(max) = value.parse::<usize>() {
93                            query.max_results = Some(max);
94                        }
95                    }
96                    _ => {
97                        pattern_parts.push(part);
98                    }
99                }
100            } else {
101                pattern_parts.push(part);
102            }
103
104            i += 1;
105        }
106
107        query.pattern = pattern_parts.join(" ");
108
109        if query.pattern.is_empty() {
110            return Err(SearchError::InvalidQuery(
111                "Query pattern cannot be empty".to_string(),
112            ));
113        }
114
115        Ok(query)
116    }
117
118    fn parse_size_filter(value: &str) -> Result<Option<SizeFilter>> {
119        if value.starts_with('>') {
120            let size_str = value.trim_start_matches('>');
121            if let Some(size) = parse_size(size_str) {
122                return Ok(Some(SizeFilter::GreaterThan(size)));
123            }
124        } else if value.starts_with('<') {
125            let size_str = value.trim_start_matches('<');
126            if let Some(size) = parse_size(size_str) {
127                return Ok(Some(SizeFilter::LessThan(size)));
128            }
129        } else if value.contains("..") {
130            let parts: Vec<&str> = value.split("..").collect();
131            if parts.len() == 2 {
132                if let (Some(min), Some(max)) = (parse_size(parts[0]), parse_size(parts[1])) {
133                    return Ok(Some(SizeFilter::Range(min, max)));
134                }
135            }
136        } else if let Some(size) = parse_size(value) {
137            return Ok(Some(SizeFilter::Exact(size)));
138        }
139
140        Err(SearchError::InvalidQuery(format!(
141            "Invalid size filter: {}",
142            value
143        )))
144    }
145
146    fn parse_date_filter(value: &str) -> Result<Option<DateFilter>> {
147        if value.starts_with('>') || value.starts_with("after:") {
148            let date_str = value.trim_start_matches('>').trim_start_matches("after:");
149            if let Some(date) = parse_relative_date(date_str) {
150                return Ok(Some(DateFilter::After(date)));
151            }
152        } else if value.starts_with('<') || value.starts_with("before:") {
153            let date_str = value.trim_start_matches('<').trim_start_matches("before:");
154            if let Some(date) = parse_relative_date(date_str) {
155                return Ok(Some(DateFilter::Before(date)));
156            }
157        } else if value.contains("..") {
158            let parts: Vec<&str> = value.split("..").collect();
159            if parts.len() == 2 {
160                if let (Some(start), Some(end)) =
161                    (parse_relative_date(parts[0]), parse_relative_date(parts[1]))
162                {
163                    return Ok(Some(DateFilter::Between(start, end)));
164                }
165            }
166        } else if let Some(date) = parse_relative_date(value) {
167            return Ok(Some(DateFilter::On(date)));
168        }
169
170        Err(SearchError::InvalidQuery(format!(
171            "Invalid date filter: {}",
172            value
173        )))
174    }
175
176    fn parse_match_mode(value: &str) -> Result<MatchMode> {
177        match value.to_lowercase().as_str() {
178            "exact" => Ok(MatchMode::Exact),
179            "case" | "casesensitive" => Ok(MatchMode::Exact),
180            "insensitive" | "caseinsensitive" => Ok(MatchMode::CaseInsensitive),
181            "fuzzy" => Ok(MatchMode::Fuzzy),
182            "regex" => Ok(MatchMode::Regex),
183            "glob" => Ok(MatchMode::Glob),
184            _ => Err(SearchError::InvalidQuery(format!(
185                "Invalid match mode: {}",
186                value
187            ))),
188        }
189    }
190
191    fn parse_scope(value: &str) -> Result<SearchScope> {
192        match value.to_lowercase().as_str() {
193            "name" => Ok(SearchScope::Name),
194            "path" => Ok(SearchScope::Path),
195            "content" => Ok(SearchScope::Content),
196            "all" => Ok(SearchScope::All),
197            _ => Err(SearchError::InvalidQuery(format!(
198                "Invalid search scope: {}",
199                value
200            ))),
201        }
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    #[test]
210    fn test_parse_simple_query() {
211        let query = QueryParser::parse("test.txt").unwrap();
212        assert_eq!(query.pattern, "test.txt");
213        assert_eq!(query.match_mode, MatchMode::CaseInsensitive);
214    }
215
216    #[test]
217    fn test_parse_query_with_extension() {
218        let query = QueryParser::parse("test ext:rs").unwrap();
219        assert_eq!(query.pattern, "test");
220        assert_eq!(query.extensions, vec!["rs"]);
221    }
222
223    #[test]
224    fn test_parse_query_with_size() {
225        let query = QueryParser::parse("test size:>1MB").unwrap();
226        assert_eq!(query.pattern, "test");
227        assert!(query.size_filter.is_some());
228    }
229
230    #[test]
231    fn test_parse_query_with_date() {
232        let query = QueryParser::parse("test modified:today").unwrap();
233        assert_eq!(query.pattern, "test");
234        assert!(query.date_filter.is_some());
235    }
236
237    #[test]
238    fn test_parse_query_with_mode() {
239        let query = QueryParser::parse("test mode:fuzzy").unwrap();
240        assert_eq!(query.pattern, "test");
241        assert_eq!(query.match_mode, MatchMode::Fuzzy);
242    }
243
244    #[test]
245    fn test_parse_complex_query() {
246        let query = QueryParser::parse("test ext:rs,txt size:>100KB modified:today mode:fuzzy").unwrap();
247        assert_eq!(query.pattern, "test");
248        assert_eq!(query.extensions.len(), 2);
249        assert!(query.size_filter.is_some());
250        assert!(query.date_filter.is_some());
251        assert_eq!(query.match_mode, MatchMode::Fuzzy);
252    }
253}