distri_filesystem/
search.rs1use crate::{FileSystemStore, SearchMatch, SearchResult};
2use anyhow::{Context, Result};
3use async_trait::async_trait;
4use grep::regex::RegexMatcher;
5use grep::searcher::{sinks::UTF8, Searcher};
6use std::sync::Arc;
7
8#[derive(Debug)]
10pub struct FileSystemGrepSearcher {
11 file_store: Arc<FileSystemStore>,
12}
13
14impl FileSystemGrepSearcher {
15 pub fn new(file_store: Arc<FileSystemStore>) -> Self {
16 Self { file_store }
17 }
18
19 async fn search_with_grep(
21 &self,
22 base_path: &str,
23 content_pattern: &str,
24 file_pattern: Option<&str>,
25 ) -> Result<Vec<SearchMatch>> {
26 let mut matches = Vec::new();
27 let matcher = RegexMatcher::new(content_pattern)
28 .with_context(|| format!("Invalid regex pattern: {}", content_pattern))?;
29
30 self.search_directory(
32 base_path,
33 &matcher,
34 content_pattern,
35 file_pattern,
36 &mut matches,
37 )
38 .await?;
39
40 Ok(matches)
41 }
42
43 fn search_directory<'a>(
44 &'a self,
45 dir_path: &'a str,
46 matcher: &'a RegexMatcher,
47 pattern: &'a str,
48 file_pattern: Option<&'a str>,
49 matches: &'a mut Vec<SearchMatch>,
50 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send + 'a>> {
51 Box::pin(async move {
52 let listing = self.file_store.list(dir_path).await?;
53
54 for entry in &listing.entries {
55 let entry_path = if dir_path.is_empty() {
56 entry.name.clone()
57 } else {
58 format!("{}/{}", dir_path, entry.name)
59 };
60
61 if entry.is_dir {
62 self.search_directory(&entry_path, matcher, pattern, file_pattern, matches)
64 .await?;
65 } else {
66 if let Some(fp) = file_pattern {
68 if fp.starts_with("*.") {
69 let extension = &fp[2..];
70 if !entry.name.ends_with(&format!(".{}", extension)) {
71 continue;
72 }
73 } else if !entry.name.contains(fp) {
74 continue;
75 }
76 }
77
78 if let Ok(read_result) = self
80 .file_store
81 .read(&entry_path, crate::ReadParams::default())
82 .await
83 {
84 let mut searcher = Searcher::new();
85 let content_reader = std::io::Cursor::new(read_result.content.as_bytes());
86
87 let path_clone = entry_path.clone();
88 let _result = searcher.search_reader(
89 matcher,
90 content_reader,
91 UTF8(|line_num, line_content| {
92 matches.push(SearchMatch {
93 file_path: path_clone.clone(),
94 line_number: Some(line_num),
95 line_content: line_content.trim_end().to_string(),
96 match_text: pattern.to_string(),
97 });
98 Ok(true)
99 }),
100 );
101 }
102 }
103 }
104
105 Ok(())
106 })
107 }
108}
109
110#[async_trait]
111impl crate::GrepSearcher for FileSystemGrepSearcher {
112 async fn search(
113 &self,
114 path: &str,
115 content_pattern: Option<&str>,
116 file_pattern: Option<&str>,
117 ) -> Result<SearchResult> {
118 let matches = if let Some(pattern) = content_pattern {
119 self.search_with_grep(path, pattern, file_pattern).await?
121 } else {
122 let listing = self.file_store.list(path).await?;
124 let mut matches = Vec::new();
125
126 for entry in &listing.entries {
127 if let Some(pattern) = file_pattern {
128 if entry.name.contains(pattern) {
129 let file_path = if path.is_empty() {
130 entry.name.clone()
131 } else {
132 format!("{}/{}", path, entry.name)
133 };
134
135 matches.push(SearchMatch {
136 file_path,
137 line_number: None,
138 line_content: String::new(),
139 match_text: pattern.to_string(),
140 });
141 }
142 }
143 }
144
145 matches
146 };
147
148 Ok(SearchResult {
149 path: path.to_string(),
150 matches,
151 })
152 }
153}