1use super::{BoundSource, Import, ImportType};
2use crate::fun::Name;
3use indexmap::IndexMap;
4use std::{
5 collections::HashSet,
6 path::{Component, Path, PathBuf},
7};
8
9pub type Sources = IndexMap<Name, String>;
10
11pub trait PackageLoader {
13 fn load(&mut self, import: &mut Import) -> Result<Sources, String>;
37}
38
39pub struct DefaultLoader {
41 local_path: PathBuf,
42 loaded: HashSet<Name>,
43 entrypoint: Name,
44}
45
46impl DefaultLoader {
47 pub fn new(local_path: &Path) -> Self {
48 let entrypoint = Name::new(local_path.file_stem().unwrap().to_string_lossy());
49 let local_path = local_path.parent().unwrap().to_path_buf();
50 Self { local_path, loaded: HashSet::new(), entrypoint }
51 }
52
53 fn read_file(&mut self, path: &Path, file_path: &str, src: &mut Sources) -> Result<Option<Name>, String> {
54 let normalized = normalize_path(&PathBuf::from(file_path));
55 let file_path = Name::new(normalized.to_string_lossy());
56
57 if self.entrypoint == file_path {
58 return Err("Can not import the entry point of the program.".to_string());
59 };
60
61 if !self.is_loaded(&file_path) {
62 self.loaded.insert(file_path.clone());
63
64 let path = path.with_extension("bend");
65 let Some(code) = std::fs::read_to_string(path).ok() else { return Ok(None) };
66 src.insert(file_path.clone(), code);
67 }
68
69 Ok(Some(file_path))
70 }
71
72 fn read_file_in_folder(
73 &mut self,
74 full_path: &Path,
75 folder: &str,
76 file_name: &str,
77 src: &mut Sources,
78 ) -> Result<Option<Name>, String> {
79 let full_path = full_path.join(file_name);
80
81 if folder.is_empty() {
82 self.read_file(&full_path, file_name, src)
83 } else {
84 let file_name = &format!("{}/{}", folder, file_name);
85 self.read_file(&full_path, file_name, src)
86 }
87 }
88
89 fn read_path(
90 &mut self,
91 base_path: &Path,
92 path: &Name,
93 imp_type: &ImportType,
94 ) -> Result<Option<(BoundSource, Sources)>, String> {
95 let full_path = base_path.join(path.as_ref());
96 let mut src = IndexMap::new();
97 let (mut file, mut dir) = (None, None);
98
99 if full_path.with_extension("bend").is_file() {
100 file = self.read_file(&full_path, path.as_ref(), &mut src)?;
101 }
102
103 if full_path.is_dir() || path.is_empty() {
104 let mut names = IndexMap::new();
105
106 match imp_type {
107 ImportType::Single(file, _) => {
108 if let Some(name) = self.read_file_in_folder(&full_path, path, file, &mut src)? {
109 names.insert(file.clone(), name);
110 }
111 }
112 ImportType::List(list) => {
113 for (file, _) in list {
114 if let Some(name) = self.read_file_in_folder(&full_path, path, file, &mut src)? {
115 names.insert(file.clone(), name);
116 }
117 }
118 }
119 ImportType::Glob => {
120 for entry in full_path.read_dir().unwrap().flatten() {
121 let file = PathBuf::from(&entry.file_name());
122
123 if let Some("bend") = file.extension().and_then(|f| f.to_str()) {
124 let file = file.file_stem().unwrap().to_string_lossy();
125 if let Some(name) = self.read_file_in_folder(&full_path, path, &file, &mut src)? {
126 names.insert(Name::new(file), name);
127 }
128 }
129 }
130 }
131 }
132
133 if !names.is_empty() {
134 dir = Some(names);
135 }
136 }
137
138 let src = match (file, dir) {
139 (Some(f), None) => Some((BoundSource::File(f), src)),
140 (None, Some(d)) => Some((BoundSource::Dir(d), src)),
141 (Some(f), Some(d)) => Some((BoundSource::Either(f, d), src)),
142 (None, None) => None,
143 };
144
145 Ok(src)
146 }
147
148 fn is_loaded(&self, name: &Name) -> bool {
149 self.loaded.contains(name)
150 }
151}
152
153pub const BEND_PATH: &[&str] = &[""];
154
155impl PackageLoader for DefaultLoader {
156 fn load(&mut self, import: &mut Import) -> Result<Sources, String> {
157 let mut sources = Sources::new();
158
159 let Import { path, imp_type, relative, src } = import;
160
161 let folders = if *relative {
162 vec![self.local_path.clone()]
163 } else {
164 BEND_PATH.iter().map(|p| self.local_path.join(p)).collect()
165 };
166
167 for base in folders {
168 let Some((names, new_pkgs)) = self.read_path(&base, path, imp_type)? else { continue };
169
170 *src = names;
171 sources.extend(new_pkgs);
172 break;
173 }
174
175 if let BoundSource::None = src {
176 return Err(format!("Failed to import '{}' from '{}'", imp_type, path).to_string());
177 }
178
179 Ok(sources)
180 }
181}
182
183pub fn normalize_path(path: &Path) -> PathBuf {
185 let mut components = path.components().peekable();
186 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
187 components.next();
188 PathBuf::from(c.as_os_str())
189 } else {
190 PathBuf::new()
191 };
192
193 for component in components {
194 match component {
195 Component::Prefix(..) => unreachable!(),
196 Component::RootDir => {
197 ret.push(component.as_os_str());
198 }
199 Component::CurDir => {}
200 Component::ParentDir => {
201 ret.pop();
202 }
203 Component::Normal(c) => {
204 ret.push(c);
205 }
206 }
207 }
208 ret
209}