Skip to main content

distri_filesystem/
search.rs

1use 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/// Grep-based searcher that works on top of FileSystemStore
9#[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    /// Search files using grep library with FileStore interface
20    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        // Recursively search through directories
31        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                    // Recursive search in subdirectory
63                    self.search_directory(&entry_path, matcher, pattern, file_pattern, matches)
64                        .await?;
65                } else {
66                    // Apply file pattern filtering
67                    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                    // Read file and search content using search_reader
79                    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            // Use grep library for content search
120            self.search_with_grep(path, pattern, file_pattern).await?
121        } else {
122            // Just file name search
123            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}