artichoke_core/
load.rs

1//! Load Ruby and Rust sources into the VM.
2
3use alloc::borrow::Cow;
4use alloc::vec::Vec;
5
6#[cfg(feature = "std")]
7type Path = std::path::Path;
8#[cfg(not(feature = "std"))]
9type Path = str;
10
11use crate::file::File;
12
13/// The side effect from a call to [`Kernel#require`].
14///
15/// In Ruby, `require` is stateful. All required sources are tracked in a global
16/// interpreter state accessible as `$"` and `$LOADED_FEATURES`.
17///
18/// The first time a file is required, it is parsed and executed by the
19/// interpreter. If the file executes without raising an error, the file is
20/// successfully required and Rust callers can expect a [`Required::Success`]
21/// variant. Files that are successfully required are added to the interpreter's
22/// set of loaded features.
23///
24/// If the file raises an exception as it is required, Rust callers can expect
25/// an `Err` variant. The file is not added to the set of loaded features.
26///
27/// If the file has previously been required such that [`Required::Success`] has
28/// been returned, all subsequent calls to require the file will return
29/// [`Required::AlreadyRequired`].
30///
31/// See the documentation of [`require_source`] for more details.
32///
33/// [`Kernel#require`]: https://ruby-doc.org/core-3.1.2/Kernel.html#method-i-require
34/// [`require_source`]: LoadSources::require_source
35#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
36pub enum Required {
37    /// [`Kernel#require`] succeeded at requiring the file.
38    ///
39    /// If this variant is returned, this is the first time the given file has
40    /// been required in the interpreter.
41    ///
42    /// This variant has value `true` when converting to a Boolean as returned
43    /// by `Kernel#require`.
44    ///
45    /// [`Kernel#require`]: https://ruby-doc.org/core-3.1.2/Kernel.html#method-i-require
46    Success,
47    /// [`Kernel#require`] did not require the file because it has already been
48    /// required.
49    ///
50    /// If this variant is returned, this is at least the second time the given
51    /// file has been required. Interpreters guarantee that files are only
52    /// required once. To load a source multiple times, see [`load_source`] and
53    /// [`Kernel#load`].
54    ///
55    /// This variant has value `false` when converting to a Boolean as returned
56    /// by `Kernel#require`.
57    ///
58    /// [`Kernel#require`]: https://ruby-doc.org/core-3.1.2/Kernel.html#method-i-require
59    /// [`load_source`]: LoadSources::load_source
60    /// [`Kernel#load`]: https://ruby-doc.org/core-3.1.2/Kernel.html#method-i-load
61    AlreadyRequired,
62}
63
64impl From<Required> for bool {
65    /// Convert a [`Required`] enum into a [`bool`] as returned by
66    /// [`Kernel#require`].
67    ///
68    /// [`Kernel#require`]: https://ruby-doc.org/core-3.1.2/Kernel.html#method-i-require
69    fn from(req: Required) -> Self {
70        match req {
71            Required::Success => true,
72            Required::AlreadyRequired => false,
73        }
74    }
75}
76
77/// The side effect from a call to [`Kernel#load`].
78///
79/// In Ruby, `load` is stateless. All sources passed to `load` are loaded for
80/// every method call.
81///
82/// Each time a file is loaded, it is parsed and executed by the
83/// interpreter. If the file executes without raising an error, the file is
84/// successfully loaded and Rust callers can expect a [`Loaded::Success`]
85/// variant.
86///
87/// If the file raises an exception as it is required, Rust callers can expect
88/// an `Err` variant. The file is not added to the set of loaded features.
89///
90/// See the documentation of [`load_source`] for more details.
91///
92/// [`Kernel#load`]: https://ruby-doc.org/core-3.1.2/Kernel.html#method-i-load
93/// [`load_source`]: LoadSources::load_source
94#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
95pub enum Loaded {
96    /// [`Kernel#load`] succeeded at loading the file.
97    ///
98    /// This variant has value `true` when converting to a Boolean as returned
99    /// by `Kernel#load`.
100    ///
101    /// [`Kernel#load`]: https://ruby-doc.org/core-3.1.2/Kernel.html#method-i-load
102    Success,
103}
104
105impl From<Loaded> for bool {
106    /// Convert a [`Loaded`] enum into a [`bool`] as returned by
107    /// [`Kernel#load`].
108    ///
109    /// [`Kernel#load`]: https://ruby-doc.org/core-3.1.2/Kernel.html#method-i-load
110    fn from(loaded: Loaded) -> Self {
111        let Loaded::Success = loaded;
112        true
113    }
114}
115
116/// Load Ruby sources and Rust extensions into an interpreter.
117pub trait LoadSources {
118    /// Concrete type for interpreter.
119    type Artichoke;
120
121    /// Concrete type for errors returned from file system IO.
122    type Error;
123
124    /// Concrete type for errors returned by `File::require`.
125    type Exception;
126
127    /// Add a Rust extension hook to the virtual file system. A stub Ruby file is
128    /// added to the file system and [`File::require`] will dynamically define
129    /// Ruby items when invoked via `Kernel#require`.
130    ///
131    /// If `path` is a relative path, the Ruby source is added to the
132    /// file system relative to `RUBY_LOAD_PATH`. If the path is absolute, the
133    /// file is placed directly on the file system. Ancestor directories are
134    /// created automatically.
135    ///
136    /// # Errors
137    ///
138    /// If the underlying file system is inaccessible, an error is returned.
139    ///
140    /// If writes to the underlying file system fail, an error is returned.
141    fn def_file_for_type<P, T>(&mut self, path: P) -> Result<(), Self::Error>
142    where
143        P: AsRef<Path>,
144        T: File<Artichoke = Self::Artichoke, Error = Self::Exception>;
145
146    /// Add a Ruby source to the virtual file system.
147    ///
148    /// If `path` is a relative path, the Ruby source is added to the
149    /// file system relative to `RUBY_LOAD_PATH`. If the path is absolute, the
150    /// file is placed directly on the file system. Ancestor directories are
151    /// created automatically.
152    ///
153    /// # Errors
154    ///
155    /// If the underlying file system is inaccessible, an error is returned.
156    ///
157    /// If writes to the underlying file system fail, an error is returned.
158    fn def_rb_source_file<P, T>(&mut self, path: P, contents: T) -> Result<(), Self::Error>
159    where
160        P: AsRef<Path>,
161        T: Into<Cow<'static, [u8]>>;
162
163    /// Test for a source file at a path and return the absolute path of the
164    /// resolved file.
165    ///
166    /// Query the underlying virtual file system to check if `path` points to a
167    /// source file.
168    ///
169    /// This function returns [`None`] if `path` does not exist in the virtual
170    /// file system.
171    ///
172    /// # Errors
173    ///
174    /// If the underlying file system is inaccessible, an error is returned.
175    fn resolve_source_path<P>(&self, path: P) -> Result<Option<Vec<u8>>, Self::Error>
176    where
177        P: AsRef<Path>;
178
179    /// Test for a source file at a path.
180    ///
181    /// Query the underlying virtual file system to check if `path` points to a
182    /// source file.
183    ///
184    /// This function returns `false` if `path` does not exist in the virtual
185    /// file system.
186    ///
187    /// # Errors
188    ///
189    /// If the underlying file system is inaccessible, an error is returned.
190    fn source_is_file<P>(&self, path: P) -> Result<bool, Self::Error>
191    where
192        P: AsRef<Path>;
193
194    /// Load source located at the given path.
195    ///
196    /// Query the underlying virtual file system for a source file and load it
197    /// onto the interpreter. This loads files with the following steps:
198    ///
199    /// 1. Retrieve and execute the extension hook, if any.
200    /// 2. Read file contents and [`eval`](crate::eval::Eval) them.
201    ///
202    /// If this function returns without error, the feature specified by `path`
203    /// is loaded, but is not added to `$LOADED_FEATURES`. This function is
204    /// equivalent to `Kernel#load`.
205    ///
206    /// # Errors
207    ///
208    /// If the underlying file system is inaccessible, an error is returned.
209    ///
210    /// If reads to the underlying file system fail, an error is returned.
211    ///
212    /// If `path` does not point to a source file, an error is returned.
213    ///
214    /// If the source file at `path` has no contents, an error is returned.
215    fn load_source<P>(&mut self, path: P) -> Result<Loaded, Self::Error>
216    where
217        P: AsRef<Path>;
218
219    /// Require source located at the given path.
220    ///
221    /// Query the underlying virtual file system for a source file and require it
222    /// onto the interpreter. This requires files with the following steps:
223    ///
224    /// 1. Retrieve and execute the extension hook, if any.
225    /// 2. Read file contents and [`eval`](crate::eval::Eval) them.
226    /// 3. Mark file as required and add to `$LOADED_FEATURES`.
227    ///
228    /// If this function returns without error, the feature specified by `path`
229    /// is loaded and added to `$LOADED_FEATURES`. This function is equivalent
230    /// to `Kernel#require`.
231    ///
232    /// Implementations should ensure that this method returns
233    /// [`Ok(Required::Success)`][success] at most once. Subsequent `Ok(_)`
234    /// return values should include [`Required::AlreadyRequired`]. See the
235    /// documentation of [`Required`] for more details.
236    ///
237    /// # Errors
238    ///
239    /// If the underlying file system is inaccessible, an error is returned.
240    ///
241    /// If reads to the underlying file system fail, an error is returned.
242    ///
243    /// If `path` does not point to a source file, an error is returned.
244    ///
245    /// If the source file at `path` has no contents, an error is returned.
246    ///
247    /// [success]: Required::Success
248    fn require_source<P>(&mut self, path: P) -> Result<Required, Self::Error>
249    where
250        P: AsRef<Path>;
251
252    /// Retrieve file contents for a source file.
253    ///
254    /// Query the underlying virtual file system for the file contents of the
255    /// source file at `path`.
256    ///
257    /// # Errors
258    ///
259    /// If the underlying file system is inaccessible, an error is returned.
260    ///
261    /// If reads to the underlying file system fail, an error is returned.
262    ///
263    /// If `path` does not point to a source file, an error is returned.
264    fn read_source_file_contents<P>(&self, path: P) -> Result<Cow<'_, [u8]>, Self::Error>
265    where
266        P: AsRef<Path>;
267}