rquickjs_core/loader/
file_resolver.rs1use crate::{loader::Resolver, Ctx, Error, Result};
2use relative_path::{RelativePath, RelativePathBuf};
3
4#[derive(Debug)]
8pub struct FileResolver {
9 paths: Vec<RelativePathBuf>,
10 patterns: Vec<String>,
11}
12
13impl FileResolver {
14 pub fn add_path<P: Into<RelativePathBuf>>(&mut self, path: P) -> &mut Self {
16 self.paths.push(path.into());
17 self
18 }
19
20 #[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 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 #[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 pub fn add_pattern<P: Into<String>>(&mut self, pattern: P) -> &mut Self {
48 self.patterns.push(pattern.into());
49 self
50 }
51
52 #[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 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 #[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 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 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}