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