rquickjs_core/loader/
file_resolver.rs

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