rusty_files/search/
query.rs1use 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}