Skip to main content

microcad_lang/builtin/
file_io.rs

1// Copyright © 2025-2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Builtin FileIoInterface
5
6use std::rc::Rc;
7
8use microcad_core::hash::HashMap;
9
10use crate::Id;
11
12/// The [`FileIoInterface`] defines an interface for file import and export.
13pub trait FileIoInterface {
14    /// Return the id for this IO interface.
15    fn id(&self) -> Id;
16
17    /// Return file extensions this IO interface supports.
18    fn file_extensions(&self) -> Vec<Id> {
19        vec![self.id()]
20    }
21}
22
23/// Registry to store file IO handlers by id and by file extension.
24pub(crate) struct FileIoRegistry<T> {
25    /// File IO by ID.
26    by_id: HashMap<Id, T>,
27    /// File IO by file extension.
28    by_file_extension: HashMap<Id, Vec<T>>,
29}
30
31impl<T> Default for FileIoRegistry<T> {
32    fn default() -> Self {
33        Self {
34            by_id: HashMap::default(),
35            by_file_extension: HashMap::default(),
36        }
37    }
38}
39
40impl<T: FileIoInterface + ?Sized> FileIoRegistry<Rc<T>> {
41    /// Add new importer to the registry.
42    ///
43    /// TODO Error handling.
44    pub fn insert(&mut self, rc: Rc<T>) {
45        let id = rc.id();
46        assert!(!id.is_empty());
47
48        if self.by_id.contains_key(&id) {
49            panic!("Importer already exists");
50        }
51
52        self.by_id.insert(id, rc.clone());
53
54        let extensions = rc.file_extensions();
55        for ext in extensions {
56            if !ext.is_empty() && self.by_file_extension.contains_key(&ext) {
57                self.by_file_extension
58                    .get_mut(&ext)
59                    .expect("Exporter list")
60                    .push(rc.clone());
61            } else {
62                self.by_file_extension.insert(ext, vec![rc.clone()]);
63            }
64        }
65    }
66
67    /// Get file IO by filename.
68    pub fn by_filename(&self, filename: impl AsRef<std::path::Path>) -> Vec<Rc<T>> {
69        let ext: Id = filename
70            .as_ref()
71            .extension()
72            .unwrap_or_default()
73            .to_str()
74            .unwrap_or_default()
75            .into();
76
77        self.by_file_extension
78            .get(&ext)
79            .cloned()
80            .unwrap_or_default()
81    }
82
83    /// Get file IO by id.
84    pub fn by_id(&self, id: &Id) -> Option<Rc<T>> {
85        self.by_id.get(id).cloned()
86    }
87}