artichoke_backend/extn/core/kernel/
require.rs
1use std::path::{Path, PathBuf};
4
5use artichoke_core::load::{Loaded, Required};
6use bstr::ByteSlice;
7use scolapasta_path::bytes_to_os_str;
8
9use crate::convert::implicitly_convert_to_string;
10use crate::extn::prelude::*;
11use crate::state::parser::Context;
12
13pub fn load(interp: &mut Artichoke, mut filename: Value) -> Result<Loaded, Error> {
14 let filename = unsafe { implicitly_convert_to_string(interp, &mut filename)? };
18 if filename.find_byte(b'\0').is_some() {
19 return Err(ArgumentError::with_message("path name contains null byte").into());
20 }
21 let filename = filename.to_vec();
22 let file = bytes_to_os_str(&filename)?;
23 let path = Path::new(file);
24
25 if let Some(mut context) = interp.resolve_source_path(path)? {
26 for byte in &mut context {
27 if *byte == b'\\' {
28 *byte = b'/';
29 }
30 }
31 let context =
32 Context::new(context).ok_or_else(|| ArgumentError::with_message("path name contains null byte"))?;
33 interp.push_context(context)?;
34 let result = interp.load_source(path);
35 interp.pop_context()?;
36 return result;
37 }
38 let mut message = b"cannot load such file -- ".to_vec();
39 message.extend(filename);
40 Err(LoadError::from(message).into())
41}
42
43pub fn require(interp: &mut Artichoke, mut filename: Value) -> Result<Required, Error> {
44 let filename = unsafe { implicitly_convert_to_string(interp, &mut filename)? };
48 if filename.find_byte(b'\0').is_some() {
49 return Err(ArgumentError::with_message("path name contains null byte").into());
50 }
51 let filename = filename.to_vec();
52 let file = bytes_to_os_str(&filename)?;
53 let path = Path::new(file);
54
55 if let Some(mut context) = interp.resolve_source_path(path)? {
56 for byte in &mut context {
57 if *byte == b'\\' {
58 *byte = b'/';
59 }
60 }
61 let context =
62 Context::new(context).ok_or_else(|| ArgumentError::with_message("path name contains null byte"))?;
63 interp.push_context(context)?;
64 let result = interp.require_source(path);
65 interp.pop_context()?;
66 return result;
67 }
68 let mut message = b"cannot load such file -- ".to_vec();
69 message.extend(filename);
70 Err(LoadError::from(message).into())
71}
72
73pub fn require_relative(interp: &mut Artichoke, mut filename: Value, base: RelativePath) -> Result<Required, Error> {
74 let filename = unsafe { implicitly_convert_to_string(interp, &mut filename)? };
78 if filename.find_byte(b'\0').is_some() {
79 return Err(ArgumentError::with_message("path name contains null byte").into());
80 }
81 let filename = filename.to_vec();
82 let file = bytes_to_os_str(&filename)?;
83 let path = base.join(Path::new(file));
84
85 if let Some(mut context) = interp.resolve_source_path(&path)? {
86 for byte in &mut context {
87 if *byte == b'\\' {
88 *byte = b'/';
89 }
90 }
91 let context =
92 Context::new(context).ok_or_else(|| ArgumentError::with_message("path name contains null byte"))?;
93 interp.push_context(context)?;
94 let result = interp.require_source(&path);
95 interp.pop_context()?;
96 return result;
97 }
98 let mut message = b"cannot load such file -- ".to_vec();
99 message.extend(filename);
100 Err(LoadError::from(message).into())
101}
102
103#[derive(Default, Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
104pub struct RelativePath(PathBuf);
105
106impl From<PathBuf> for RelativePath {
107 fn from(path: PathBuf) -> Self {
108 Self(path)
109 }
110}
111
112impl From<&Path> for RelativePath {
113 fn from(path: &Path) -> Self {
114 Self(path.into())
115 }
116}
117
118impl From<String> for RelativePath {
119 fn from(path: String) -> Self {
120 Self(path.into())
121 }
122}
123
124impl From<&str> for RelativePath {
125 fn from(path: &str) -> Self {
126 Self(path.into())
127 }
128}
129
130impl RelativePath {
131 pub fn join<P: AsRef<Path>>(&self, path: P) -> PathBuf {
132 self.0.join(path.as_ref())
133 }
134
135 pub fn try_from_interp(interp: &mut Artichoke) -> Result<Self, Error> {
136 let context = interp
137 .peek_context()?
138 .ok_or_else(|| Fatal::from("relative require with no context stack"))?;
139 let path = bytes_to_os_str(context.filename())?;
140 let path = Path::new(path);
141 if let Some(base) = path.parent() {
142 Ok(Self::from(base))
143 } else {
144 Ok(Self::from("/"))
145 }
146 }
147}
148
149#[cfg(test)]
150mod test {
151 use bstr::ByteSlice;
152
153 use crate::test::prelude::*;
154
155 #[derive(Debug)]
156 struct MockSourceFile;
157
158 impl File for MockSourceFile {
159 type Artichoke = Artichoke;
160
161 type Error = Error;
162
163 fn require(interp: &mut Artichoke) -> Result<(), Self::Error> {
164 interp.eval(b"@i = 255").unwrap();
165 Ok(())
166 }
167 }
168
169 #[derive(Debug)]
170 struct MockExtensionAndSourceFile;
171
172 impl File for MockExtensionAndSourceFile {
173 type Artichoke = Artichoke;
174
175 type Error = Error;
176
177 fn require(interp: &mut Artichoke) -> Result<(), Self::Error> {
178 interp.eval(b"module Foo; RUST = 7; end").unwrap();
179 Ok(())
180 }
181 }
182
183 #[test]
191 fn functional() {
192 let mut interp = interpreter();
193 interp.def_file_for_type::<_, MockSourceFile>("file.rb").unwrap();
194 let result = interp.eval(b"require 'file'").unwrap();
195 let require_result = result.try_convert_into::<bool>(&interp).unwrap();
196 assert!(require_result);
197 let result = interp.eval(b"@i").unwrap();
198 let i_result = result.try_convert_into::<i64>(&interp).unwrap();
199 assert_eq!(i_result, 255);
200 let result = interp.eval(b"@i = 1000; require 'file'").unwrap();
201 let second_require_result = result.try_convert_into::<bool>(&interp).unwrap();
202 assert!(!second_require_result);
203 let result = interp.eval(b"@i").unwrap();
204 let second_i_result = result.try_convert_into::<i64>(&interp).unwrap();
205 assert_eq!(second_i_result, 1000);
206 let err = interp.eval(b"require 'non-existent-source'").unwrap_err();
207 assert_eq!(
208 b"cannot load such file -- non-existent-source".as_bstr(),
209 err.message().as_ref().as_bstr()
210 );
211 let expected_backtrace = b"(eval):1:in require\n(eval):1".to_vec();
212 let actual_backtrace = bstr::join("\n", err.vm_backtrace(&mut interp).unwrap());
213 assert_eq!(expected_backtrace.as_bstr(), actual_backtrace.as_bstr());
214 }
215
216 #[test]
217 fn absolute_path() {
218 let mut interp = interpreter();
219 let (path, require_code) = if cfg!(windows) {
220 (
221 "c:/artichoke/virtual_root/src/lib/foo/bar/source.rb",
222 &b"require 'c:/artichoke/virtual_root/src/lib/foo/bar/source.rb'"[..],
223 )
224 } else {
225 (
226 "/artichoke/virtual_root/src/lib/foo/bar/source.rb",
227 &b"require '/artichoke/virtual_root/src/lib/foo/bar/source.rb'"[..],
228 )
229 };
230
231 interp.def_rb_source_file(path, &b"# a source file"[..]).unwrap();
232 let result = interp.eval(require_code).unwrap();
233 assert!(result.try_convert_into::<bool>(&interp).unwrap());
234 let result = interp.eval(require_code).unwrap();
235 assert!(!result.try_convert_into::<bool>(&interp).unwrap());
236 }
237
238 #[test]
239 fn relative_with_dotted_path() {
240 let mut interp = interpreter();
241 if cfg!(windows) {
242 interp
243 .def_rb_source_file(
244 "c:/artichoke/virtual_root/src/lib/foo/bar/source.rb",
245 &b"require_relative '../bar.rb'"[..],
246 )
247 .unwrap();
248 interp
249 .def_rb_source_file("c:/artichoke/virtual_root/src/lib/foo/bar.rb", &b"# a source file"[..])
250 .unwrap();
251 let result = interp
252 .eval(b"require 'c:/artichoke/virtual_root/src/lib/foo/bar/source.rb'")
253 .unwrap();
254 assert!(result.try_convert_into::<bool>(&interp).unwrap());
255 let result = interp
256 .eval(b"require 'c:/artichoke/virtual_root/src/lib/foo/bar.rb'")
257 .unwrap();
258 assert!(!result.try_convert_into::<bool>(&interp).unwrap());
259 } else {
260 interp
261 .def_rb_source_file(
262 "/artichoke/virtual_root/src/lib/foo/bar/source.rb",
263 &b"require_relative '../bar.rb'"[..],
264 )
265 .unwrap();
266 interp
267 .def_rb_source_file("/artichoke/virtual_root/src/lib/foo/bar.rb", &b"# a source file"[..])
268 .unwrap();
269 let result = interp
270 .eval(b"require '/artichoke/virtual_root/src/lib/foo/bar/source.rb'")
271 .unwrap();
272 assert!(result.try_convert_into::<bool>(&interp).unwrap());
273 let result = interp
274 .eval(b"require '/artichoke/virtual_root/src/lib/foo/bar.rb'")
275 .unwrap();
276 assert!(!result.try_convert_into::<bool>(&interp).unwrap());
277 };
278 }
279
280 #[test]
281 fn directory_err() {
282 let mut interp = interpreter();
283 let err = interp.eval(b"require '/src'").unwrap_err();
284 assert_eq!(
285 b"cannot load such file -- /src".as_bstr(),
286 err.message().as_ref().as_bstr()
287 );
288 let expected_backtrace = b"(eval):1:in require\n(eval):1".to_vec();
289 let actual_backtrace = bstr::join("\n", err.vm_backtrace(&mut interp).unwrap());
290 assert_eq!(expected_backtrace.as_bstr(), actual_backtrace.as_bstr());
291 }
292
293 #[test]
294 fn path_defined_as_source_then_extension_file() {
295 let mut interp = interpreter();
296 interp
297 .def_rb_source_file("foo.rb", &b"module Foo; RUBY = 3; end"[..])
298 .unwrap();
299 interp
300 .def_file_for_type::<_, MockExtensionAndSourceFile>("foo.rb")
301 .unwrap();
302 let result = interp.eval(b"require 'foo'").unwrap();
303 let result = result.try_convert_into::<bool>(&interp).unwrap();
304 assert!(result, "successfully required foo.rb");
305 let result = interp.eval(b"Foo::RUBY + Foo::RUST").unwrap();
306 let result = result.try_convert_into::<i64>(&interp).unwrap();
307 assert_eq!(result, 10, "defined Ruby and Rust sources from single require");
308 }
309
310 #[test]
311 fn path_defined_as_extension_file_then_source() {
312 let mut interp = interpreter();
313 interp
314 .def_file_for_type::<_, MockExtensionAndSourceFile>("foo.rb")
315 .unwrap();
316 interp
317 .def_rb_source_file("foo.rb", &b"module Foo; RUBY = 3; end"[..])
318 .unwrap();
319 let result = interp.eval(b"require 'foo'").unwrap();
320 let result = result.try_convert_into::<bool>(&interp).unwrap();
321 assert!(result, "successfully required foo.rb");
322 let result = interp.eval(b"Foo::RUBY + Foo::RUST").unwrap();
323 let result = result.try_convert_into::<i64>(&interp).unwrap();
324 assert_eq!(result, 10, "defined Ruby and Rust sources from single require");
325 }
326}