mimium_lang/utils/
fileloader.rs

1use std::{env, fmt, path::PathBuf};
2
3#[cfg(target_arch = "wasm32")]
4use wasm_bindgen::prelude::*;
5
6#[derive(Debug)]
7pub enum Error {
8    IoError(std::io::Error),
9    FileNotFound(String, PathBuf),
10    UtfConversionError(std::string::FromUtf8Error),
11    PathJoinError(env::JoinPathsError),
12    SelfReference(PathBuf),
13}
14
15impl fmt::Display for Error {
16    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
17        match self {
18            Error::IoError(e) => write!(f, "IoError: {}", e),
19            Error::FileNotFound(e, p) => write!(f, "File {} not found: {}", p.display(), e),
20            Error::UtfConversionError(e) => write!(f, "Failed to convert into UTF: {}", e),
21            Error::PathJoinError(e) => write!(f, "Failed to join path: {}", e),
22            Error::SelfReference(path_buf) => write!(
23                f,
24                "File tried to include itself recusively: {}",
25                path_buf.to_string_lossy()
26            ),
27        }
28    }
29}
30
31impl std::error::Error for Error {}
32
33impl From<std::io::Error> for Error {
34    fn from(e: std::io::Error) -> Self {
35        Error::IoError(e)
36    }
37}
38impl From<std::string::FromUtf8Error> for Error {
39    fn from(e: std::string::FromUtf8Error) -> Self {
40        Error::UtfConversionError(e)
41    }
42}
43impl From<env::JoinPathsError> for Error {
44    fn from(e: env::JoinPathsError) -> Self {
45        Error::PathJoinError(e)
46    }
47}
48fn get_default_library_path() -> Option<PathBuf> {
49    #[cfg(not(target_arch = "wasm32"))]
50    let home = homedir::my_home().ok().flatten();
51    #[cfg(target_arch = "wasm32")]
52    let home: Option<PathBuf> = None;
53    if home.is_none() {
54        log::warn!("default library search path is not available on this platform.");
55        return None;
56    }
57    let p = home.unwrap().join(PathBuf::from(".mimium/lib"));
58    Some(p)
59}
60
61pub fn get_canonical_path(current_file_or_dir: &str, relpath: &str) -> Result<PathBuf, Error> {
62    let parent_dir = get_parent_dir(current_file_or_dir)?;
63    let relpath2 = std::path::PathBuf::from(relpath);
64    let abspath = [parent_dir, relpath2]
65        .into_iter()
66        .collect::<std::path::PathBuf>();
67    if cfg!(target_arch = "wasm32") {
68        //canonicalize is platform-dependent and always returns Err on wasm32
69        Ok(abspath)
70    } else {
71        abspath
72            .canonicalize()
73            .map_err(|e| Error::FileNotFound(e.to_string(), abspath))
74    }
75}
76
77fn get_parent_dir(current_file: &str) -> Result<PathBuf, Error> {
78    let current_filepath = std::path::Path::new(current_file);
79    if current_filepath.is_dir() {
80        Ok(current_filepath.into())
81    } else {
82        #[cfg(not(target_arch = "wasm32"))]
83        let cwd = env::current_dir()?;
84        #[cfg(target_arch = "wasm32")]
85        let cwd = std::path::PathBuf::new();
86        Ok(current_filepath.parent().map_or_else(|| cwd, PathBuf::from))
87    }
88}
89
90/// Used for resolving include.
91/// If the filename is given it searches ~/.mimium/lib first. If not found, tries to find in relative path.
92/// If the relative path is given explicitly, do not find in standard library path.
93pub fn load_mmmlibfile(current_file_or_dir: &str, path: &str) -> Result<(String, PathBuf), Error> {
94    let path = std::path::Path::new(path);
95    let search_default_lib = !(path.is_absolute() || path.starts_with("."));
96    if let (true, Some(stdlibpath)) = (search_default_lib, get_default_library_path()) {
97        let cpath = stdlibpath.join(path).canonicalize();
98        if let Ok(cpath) = cpath
99            && let Ok(content) = load(&cpath.to_string_lossy())
100        {
101            return Ok((content, cpath));
102            // if not found in the stdlib, continue to find in a relative path.
103        }
104    };
105    let cpath = get_canonical_path(current_file_or_dir, &path.to_string_lossy())?;
106    if current_file_or_dir == cpath.to_string_lossy() {
107        return Err(Error::SelfReference(cpath.clone()));
108    }
109    let content = load(&cpath.to_string_lossy())?;
110    Ok((content, cpath))
111}
112
113#[cfg(not(target_arch = "wasm32"))]
114pub fn load(canonical_path: &str) -> Result<String, Error> {
115    // debug_assert!(std::path::Path::new(canonical_path).is_absolute());
116    let content = std::fs::read(canonical_path)
117        .map_err(|e| Error::FileNotFound(e.to_string(), PathBuf::from(canonical_path)))?;
118
119    let content_r = String::from_utf8(content).map_err(Error::from)?;
120    Ok(content_r)
121}
122
123#[cfg(target_arch = "wasm32")]
124pub fn load(canonical_path: &str) -> Result<String, Error> {
125    let content_r = read_file(canonical_path)
126        .map_err(|e| Error::FileNotFound(format!("{:?}", e), canonical_path.into()))?;
127    Ok(content_r)
128}
129
130#[cfg(target_arch = "wasm32")]
131#[wasm_bindgen(module = "/src/utils/fileloader.cjs")]
132extern "C" {
133    #[wasm_bindgen(catch)]
134    fn read_file(path: &str) -> Result<String, JsValue>;
135    #[wasm_bindgen(catch)]
136    pub fn get_env(key: &str) -> Result<String, JsValue>;
137}
138
139#[cfg(all(test, target_arch = "wasm32"))]
140mod test {
141    use super::*;
142    use wasm_bindgen_test::*;
143    fn setup_file() -> (String, String) {
144        let ans = r#"include("error_include_itself.mmm")
145fn dsp(){
146    0.0
147}"#;
148        let file = format!(
149            "{}/../mimium-test/tests/mmm/{}",
150            get_env("TEST_ROOT").expect("TEST_ROOT is not set"),
151            "error_include_itself.mmm"
152        );
153        (ans.to_string(), file)
154    }
155    #[wasm_bindgen_test] //wasm only test
156    fn fileloader_test() {
157        let (ans, file) = setup_file();
158        let res = load(&file).expect("failed to load file");
159        assert_eq!(res, ans);
160    }
161    #[wasm_bindgen_test] //wasm only test
162    fn loadlib_test() {
163        use super::*;
164        let (ans, file) = setup_file();
165        let (res, _path) = load_mmmlibfile("/", &file).expect("failed to load file");
166        assert_eq!(res, ans);
167    }
168    #[wasm_bindgen_test] //wasm only test
169    fn loadlib_test_selfinclude() {
170        use super::*;
171        let (_, file) = setup_file();
172        let err = load_mmmlibfile(&file, &file).expect_err("should be an error");
173
174        assert!(matches!(err, Error::SelfReference(_)));
175    }
176}