Skip to main content

conduit_cli/core/engine/io/
include.rs

1use crate::core::schemas::include;
2use crate::errors::ConduitResult;
3use ignore::WalkBuilder;
4use serde::{Serialize, de::DeserializeOwned};
5use std::path::{Path, PathBuf};
6use tokio::fs;
7
8pub trait IncludeFile: Serialize + DeserializeOwned + Sync {
9    fn from_patterns(patterns: Vec<String>) -> Self;
10    fn get_patterns(&self) -> &[String];
11
12    fn load<P: AsRef<Path> + Send>(
13        path: P,
14    ) -> impl std::future::Future<Output = ConduitResult<Self>> + Send {
15        async {
16            let content = fs::read_to_string(path).await?;
17            let patterns = content
18                .lines()
19                .map(|l| l.trim().to_string())
20                .filter(|l| !l.is_empty() && !l.starts_with('#'))
21                .collect();
22
23            Ok(Self::from_patterns(patterns))
24        }
25    }
26
27    fn save<P: AsRef<Path> + Send>(
28        &self,
29        path: P,
30    ) -> impl std::future::Future<Output = ConduitResult<()>> + Send {
31        async move {
32            let content = self.get_patterns().join("\n");
33            fs::write(path, content).await?;
34            Ok(())
35        }
36    }
37
38    fn scan<P: AsRef<Path>>(&self, root: P) -> Vec<PathBuf> {
39        let root_path = root.as_ref();
40        let patterns = self.get_patterns();
41
42        if patterns.is_empty() {
43            return Vec::new();
44        }
45
46        let compiled: Vec<_> = patterns
47            .iter()
48            .map(|p| {
49                let p = p.replace('\\', "/");
50                if p.ends_with('/') {
51                    format!("{p}**/*")
52                } else if !p.contains('*') && !p.contains('.') {
53                    format!("{p}/**/*")
54                } else {
55                    p
56                }
57            })
58            .filter_map(|p| glob::Pattern::new(&p).ok())
59            .collect();
60
61        let mut included = Vec::new();
62
63        let walker = WalkBuilder::new(root_path)
64            .hidden(false)
65            .git_ignore(false)
66            .ignore(false)
67            .build();
68
69        for entry in walker.flatten() {
70            let path = entry.path();
71
72            if let Ok(relative_path) = path.strip_prefix(root_path) {
73                let path_str = relative_path.to_string_lossy().replace('\\', "/");
74
75                if path_str.is_empty() || path_str == "." {
76                    continue;
77                }
78
79                if compiled.iter().any(|g| g.matches(&path_str)) {
80                    included.push(path.to_path_buf());
81                }
82            }
83        }
84        included
85    }
86}
87
88impl IncludeFile for include::ConduitInclude {
89    fn from_patterns(patterns: Vec<String>) -> Self {
90        Self { paths: patterns }
91    }
92
93    fn get_patterns(&self) -> &[String] {
94        &self.paths
95    }
96}