conduit_cli/core/engine/io/
include.rs1use 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}