microcad_lang/builtin/
file_io.rs

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