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