microcad_lang/builtin/
import.rs1use std::rc::Rc;
7use miette::{Diagnostic, Report};
8use crate::{builtin::file_io::*, syntax::*, value::*, Id};
9
10use thiserror::Error;
11
12#[derive(Error, Debug, Diagnostic)]
14pub enum ImportError {
15 #[error("IO Error")]
17 IoError(#[from] std::io::Error),
18
19 #[error("File not found: {0}")]
21 FileNotFound(std::path::PathBuf),
22
23 #[error("No importer found for file `{0}`")]
25 NoImporterForFile(std::path::PathBuf),
26
27 #[error("No importer found with id `{0}`")]
29 NoImporterWithId(Id),
30
31 #[error("Multiple importers for file extension: {0:?}")]
33 MultipleImportersForFileExtension(Vec<Id>),
34
35 #[error("{0}")]
37 #[diagnostic(transparent)]
38 CustomError(Report),
39
40 #[error("Value Error")]
42 #[diagnostic(transparent)]
43 ValueError(#[from] ValueError),
44}
45
46pub trait Importer: FileIoInterface {
48 fn parameters(&self) -> ParameterValueList {
50 ParameterValueList::default()
51 }
52
53 fn import(&self, args: &Tuple) -> Result<Value, ImportError>;
55}
56
57#[derive(Default)]
59pub struct ImporterRegistry {
60 io: FileIoRegistry<Rc<dyn Importer + 'static>>,
61 cache: std::collections::HashMap<(String, String), Value>,
62}
63
64impl ImporterRegistry {
65 pub fn insert(mut self, importer: impl Importer + 'static) -> Self {
69 let rc = Rc::new(importer);
70 self.io.insert(rc);
71 self
72 }
73
74 pub fn by_id(&self, id: &Id) -> Result<Rc<dyn Importer>, ImportError> {
76 self.io
77 .by_id(id)
78 .ok_or(ImportError::NoImporterWithId(id.clone()))
79 }
80
81 pub fn by_filename(
83 &self,
84 filename: impl AsRef<std::path::Path>,
85 ) -> Result<Rc<dyn Importer>, ImportError> {
86 let importers = self.io.by_filename(filename.as_ref());
87 match importers.len() {
88 0 => Err(ImportError::NoImporterForFile(std::path::PathBuf::from(
89 filename.as_ref(),
90 ))),
91 1 => Ok(importers.first().expect("One importer").clone()),
92 _ => Err(ImportError::MultipleImportersForFileExtension(
93 importers.iter().map(|importer| importer.id()).collect(),
94 )),
95 }
96 }
97
98 pub(crate) fn get_cached(&self, filename: String, id: String) -> Option<Value> {
99 self.cache.get(&(filename, id)).cloned()
100 }
101
102 pub(crate) fn cache(&mut self, filename: String, id: String, value: Value) {
103 self.cache.insert((filename, id), value);
104 }
105}
106
107pub trait ImporterRegistryAccess {
109 type Error;
111
112 fn import(
114 &mut self,
115 args: &Tuple,
116 search_paths: &[std::path::PathBuf],
117 ) -> Result<Value, Self::Error>;
118}
119
120impl ImporterRegistryAccess for ImporterRegistry {
121 type Error = ImportError;
122
123 fn import(
124 &mut self,
125 args: &Tuple,
126 search_paths: &[std::path::PathBuf],
127 ) -> Result<Value, Self::Error> {
128 let filename: String = args.get("filename");
129
130 match [".".into()] .iter()
132 .chain(search_paths.iter())
133 .map(|path| path.join(&filename))
134 .find(|path| path.exists())
135 {
136 Some(path) => {
137 let mut arg_map = args.clone();
138 let filename = path.to_string_lossy().to_string();
139 arg_map.insert(
140 Identifier::no_ref("filename"),
141 Value::String(filename.clone()),
142 );
143 let id: String = arg_map.get("id");
144
145 if let Some(value) = self.get_cached(filename.clone(), id.clone()) {
147 return Ok(value);
148 }
149
150 let value = if id.is_empty() {
151 self.by_filename(&filename)
152 } else {
153 self.by_id(&id.clone().into())
154 }?
155 .import(&arg_map)?;
156 self.cache(filename, id, value.clone());
157 Ok(value)
158 }
159 None => Err(ImportError::FileNotFound(std::path::PathBuf::from(
160 &filename,
161 ))),
162 }
163 }
164}
165
166#[test]
167fn importer() {
168 struct DummyImporter;
169
170 use crate::{builtin::Importer, parameter};
171 use microcad_core::Integer;
172
173 impl Importer for DummyImporter {
174 fn parameters(&self) -> ParameterValueList {
175 [parameter!(some_arg: Integer = 32)].into_iter().collect()
176 }
177
178 fn import(&self, args: &Tuple) -> Result<Value, ImportError> {
179 let some_arg: Integer = args.get("some_arg");
180 if some_arg == 32 {
181 Ok(Value::Integer(32))
182 } else {
183 Ok(Value::Integer(42))
184 }
185 }
186 }
187
188 impl FileIoInterface for DummyImporter {
189 fn id(&self) -> Id {
190 Id::new("dummy")
191 }
192
193 fn file_extensions(&self) -> Vec<Id> {
194 vec![Id::new("dummy"), Id::new("dmy")]
195 }
196 }
197
198 let registry = ImporterRegistry::default().insert(DummyImporter);
199
200 let by_id = registry.by_id(&"dummy".into()).expect("Dummy importer");
201
202 let mut args = crate::tuple!("(some_arg=32)");
203
204 let value = by_id.import(&args).expect("Value");
205 assert!(matches!(value, Value::Integer(32)));
206
207 let by_filename = registry.by_filename("test.dmy").expect("Filename");
208
209 args.insert(Identifier::no_ref("some_arg"), Value::Integer(42));
210 let value = by_filename.import(&args).expect("Value");
211
212 assert!(matches!(value, Value::Integer(42)));
213}