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