Skip to main content

rquickjs_core/loader/
file_resolver.rs

1use crate::{
2    loader::{ImportAttributes, Resolver},
3    Ctx, Error, Result,
4};
5use alloc::{
6    string::{String, ToString as _},
7    vec,
8    vec::Vec,
9};
10use relative_path::{RelativePath, RelativePathBuf};
11
12/// The file module resolver
13///
14/// This resolver can be used as the nested backing resolver in user-defined resolvers.
15#[derive(Debug)]
16pub struct FileResolver {
17    paths: Vec<RelativePathBuf>,
18    patterns: Vec<String>,
19}
20
21impl FileResolver {
22    /// Add search path for modules
23    pub fn add_path<P: Into<RelativePathBuf>>(&mut self, path: P) -> &mut Self {
24        self.paths.push(path.into());
25        self
26    }
27
28    /// Add search path for modules
29    #[must_use]
30    pub fn with_path<P: Into<RelativePathBuf>>(mut self, path: P) -> Self {
31        self.add_path(path);
32        self
33    }
34
35    /// Add search paths for modules
36    pub fn add_paths<I: IntoIterator<Item = P>, P: Into<RelativePathBuf>>(
37        &mut self,
38        paths: I,
39    ) -> &mut Self {
40        self.paths.extend(paths.into_iter().map(|path| path.into()));
41        self
42    }
43
44    /// Add search paths for modules
45    #[must_use]
46    pub fn with_paths<I: IntoIterator<Item = P>, P: Into<RelativePathBuf>>(
47        mut self,
48        paths: I,
49    ) -> Self {
50        self.add_paths(paths);
51        self
52    }
53
54    /// Add module file pattern
55    pub fn add_pattern<P: Into<String>>(&mut self, pattern: P) -> &mut Self {
56        self.patterns.push(pattern.into());
57        self
58    }
59
60    /// Add module file pattern
61    #[must_use]
62    pub fn with_pattern<P: Into<String>>(mut self, pattern: P) -> Self {
63        self.add_pattern(pattern);
64        self
65    }
66
67    /// Add support for native modules
68    pub fn add_native(&mut self) -> &mut Self {
69        #[cfg(target_family = "windows")]
70        self.add_pattern("{}.dll");
71
72        #[cfg(target_vendor = "apple")]
73        self.add_pattern("{}.dylib").add_pattern("lib{}.dylib");
74
75        #[cfg(target_family = "unix")]
76        self.add_pattern("{}.so").add_pattern("lib{}.so");
77
78        self
79    }
80
81    /// Add support for native modules
82    #[must_use]
83    pub fn with_native(mut self) -> Self {
84        self.add_native();
85        self
86    }
87
88    fn try_patterns(&self, path: &RelativePath) -> Option<RelativePathBuf> {
89        if let Some(extension) = &path.extension() {
90            if !is_file(path) {
91                return None;
92            }
93            // check for known extensions
94            self.patterns
95                .iter()
96                .find(|pattern| {
97                    let path = RelativePath::new(pattern);
98                    if let Some(known_extension) = &path.extension() {
99                        known_extension == extension
100                    } else {
101                        false
102                    }
103                })
104                .map(|_| path.to_relative_path_buf())
105        } else {
106            // try with known patterns
107            self.patterns.iter().find_map(|pattern| {
108                let name = pattern.replace("{}", path.file_name()?);
109                let file = path.with_file_name(name);
110                if is_file(&file) {
111                    Some(file)
112                } else {
113                    None
114                }
115            })
116        }
117    }
118}
119
120impl Default for FileResolver {
121    fn default() -> Self {
122        Self {
123            paths: vec![],
124            patterns: vec!["{}.js".into()],
125        }
126    }
127}
128
129impl Resolver for FileResolver {
130    fn resolve<'js>(
131        &mut self,
132        _ctx: &Ctx<'js>,
133        base: &str,
134        name: &str,
135        _attributes: Option<ImportAttributes<'js>>,
136    ) -> Result<String> {
137        let path = if !name.starts_with('.') {
138            self.paths.iter().find_map(|path| {
139                let path = path.join_normalized(name);
140                self.try_patterns(&path)
141            })
142        } else {
143            let path = RelativePath::new(base);
144            let path = if let Some(dir) = path.parent() {
145                dir.join_normalized(name)
146            } else {
147                name.into()
148            };
149            self.try_patterns(&path)
150        }
151        .ok_or_else(|| Error::new_resolving(base, name))?;
152
153        Ok(path.to_string())
154    }
155}
156
157fn is_file<P: AsRef<RelativePath>>(path: P) -> bool {
158    path.as_ref().to_path(".").is_file()
159}