rquickjs_core/loader/
file_resolver.rs

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