mago_service/source/
mod.rs1use std::path::Path;
2
3use ahash::HashSet;
4use async_walkdir::Error;
5use async_walkdir::Filtering;
6use async_walkdir::WalkDir;
7use futures::StreamExt;
8
9use mago_interner::ThreadedInterner;
10use mago_source::SourceManager;
11
12use crate::source::config::SourceConfiguration;
13
14pub mod config;
15
16#[derive(Debug)]
17pub struct SourceService {
18 interner: ThreadedInterner,
19 configuration: SourceConfiguration,
20}
21
22impl SourceService {
23 pub fn new(interner: ThreadedInterner, configuration: SourceConfiguration) -> Self {
24 Self { interner, configuration }
25 }
26
27 pub async fn load(&self) -> Result<SourceManager, Error> {
35 let SourceConfiguration { root, paths, includes, excludes, extensions } = &self.configuration;
36
37 let mut starting_paths = Vec::new();
38
39 if paths.is_empty() {
40 starting_paths.push((root.clone(), true));
41 } else {
42 for source in paths {
43 starting_paths.push((source.clone(), true));
44 }
45 }
46
47 for include in includes {
48 starting_paths.push((include.clone(), false));
49 }
50
51 if paths.is_empty() && includes.is_empty() {
52 starting_paths.push((root.clone(), true));
53 }
54
55 let excludes_set: HashSet<&String> = excludes.iter().collect();
56 let extensions: HashSet<&String> = extensions.iter().collect();
57
58 let manager = SourceManager::new(self.interner.clone());
59 for (path, user_defined) in starting_paths.into_iter() {
60 let mut entries = WalkDir::new(path)
61 .filter(|entry| async move {
63 if entry.path().starts_with(".") {
64 Filtering::IgnoreDir
65 } else {
66 Filtering::Continue
67 }
68 });
69
70 while let Some(entry) = entries.next().await {
72 let path = entry?.path();
73
74 if is_excluded(&path, &excludes_set) {
75 continue;
76 }
77
78 if path.is_file() && is_accepted_file(&path, &extensions) {
79 let name = match path.strip_prefix(root) {
80 Ok(rel_path) => rel_path.to_path_buf(),
81 Err(_) => path.clone(),
82 };
83
84 let name_str = name.to_string_lossy().to_string();
85
86 manager.insert_path(name_str, path.clone(), user_defined);
87 }
88 }
89 }
90
91 Ok(manager)
92 }
93}
94
95fn is_excluded(path: &Path, excludes: &HashSet<&String>) -> bool {
96 excludes.iter().any(|ex| path.ends_with(ex) || glob_match::glob_match(ex, path.to_string_lossy().as_ref()))
97}
98
99fn is_accepted_file(path: &Path, extensions: &HashSet<&String>) -> bool {
100 if extensions.is_empty() {
101 path.extension().and_then(|s| s.to_str()).map(|ext| ext.eq_ignore_ascii_case("php")).unwrap_or(false)
102 } else {
103 path.extension().and_then(|s| s.to_str()).map(|ext| extensions.contains(&ext.to_string())).unwrap_or(false)
104 }
105}