rquickjs_core/loader/
file_resolver.rs1use 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#[derive(Debug)]
16pub struct FileResolver {
17 paths: Vec<RelativePathBuf>,
18 patterns: Vec<String>,
19}
20
21impl FileResolver {
22 pub fn add_path<P: Into<RelativePathBuf>>(&mut self, path: P) -> &mut Self {
24 self.paths.push(path.into());
25 self
26 }
27
28 #[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 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 #[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 pub fn add_pattern<P: Into<String>>(&mut self, pattern: P) -> &mut Self {
56 self.patterns.push(pattern.into());
57 self
58 }
59
60 #[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 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 #[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 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 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}