mezzaluna_load_path/
ruby_core.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
use std::path::Path;

/// A Ruby load path builder that prepares paths for in-memory Ruby Core and
/// Ruby Standard Library sources.
///
/// Artichoke embeds Ruby sources and native extensions into an in-memory file
/// system. This in-memory file system is addressed at a virtual mount point,
/// which must be located within the VM `$LOAD_PATH` so they may be loaded by
/// the [require] subsystem.
///
/// Like the site directories in MRI, these paths are the lowest priority load
/// paths and should appear at the end of the `$LOAD_PATH`.
///
/// Paths earlier in the sequence returned from [`load_path`] have higher
/// priority.
///
/// ```no_run
/// use mezzaluna_load_path::RubyCore;
///
/// let core_loader = RubyCore::new();
/// // Load path contains 2 entries: one for Ruby Core sources and one for
/// // Ruby Standard Library sources.
/// assert_eq!(core_loader.load_path().len(), 2);
/// ```
///
/// [require]: https://ruby-doc.org/core-3.1.2/Kernel.html#method-i-require
/// [`load_path`]: Self::load_path
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub struct RubyCore {
    _private: (),
}

impl RubyCore {
    /// Create a new load path builder that reads from the `RUBYLIB` environment
    /// variable.
    ///
    /// The `RUBYLIB` environment variable is read only once at the time this
    /// method is called. The resolved load path is immutable.
    ///
    /// This method returns [`None`] if there are errors resolving the
    /// `RUBYLIB` environment variable, if the `RUBYLIB` environment variable is
    /// not set, or if the given `RUBYLIB` environment variable only contains
    /// empty paths.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use mezzaluna_load_path::RubyCore;
    ///
    /// let loader = RubyCore::new();
    /// ```
    #[inline]
    #[must_use]
    pub const fn new() -> Self {
        Self { _private: () }
    }

    /// Return a reference to the load path for sources in the Ruby Core
    /// library.
    ///
    /// Features in Ruby Core have the lowest priority, so the returned path
    /// should appear last in `$LOAD_PATH`.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::path::Path;
    /// use mezzaluna_load_path::RubyCore;
    ///
    /// let loader = RubyCore::new();
    ///
    /// if cfg!(windows) {
    ///     assert_eq!(
    ///         loader.core_load_path(),
    ///         Path::new("c:/artichoke/virtual_root/site/core/lib"),
    ///     );
    /// } else {
    ///     assert_eq!(
    ///         loader.core_load_path(),
    ///         Path::new("/artichoke/virtual_root/site/core/lib"),
    ///     );
    /// }
    /// ```
    #[inline]
    #[must_use]
    pub fn core_load_path(self) -> &'static Path {
        if cfg!(windows) {
            Path::new("c:/artichoke/virtual_root/site/core/lib")
        } else {
            Path::new("/artichoke/virtual_root/site/core/lib")
        }
    }

    /// Return a reference to the load path for sources in the Ruby Standard
    /// Library.
    ///
    /// Features in Ruby Standard Library have low priority, so the returned
    /// path should appear second to last in `$LOAD_PATH` (only ahead of the
    /// [core load path]).
    ///
    /// # Examples
    ///
    /// ```
    /// use std::path::Path;
    /// use mezzaluna_load_path::RubyCore;
    ///
    /// let loader = RubyCore::new();
    ///
    /// if cfg!(windows) {
    ///     assert_eq!(
    ///         loader.stdlib_load_path(),
    ///         Path::new("c:/artichoke/virtual_root/site/stdlib/lib"),
    ///     );
    /// } else {
    ///     assert_eq!(
    ///         loader.stdlib_load_path(),
    ///         Path::new("/artichoke/virtual_root/site/stdlib/lib"),
    ///     );
    /// }
    /// ```
    ///
    /// [core load path]: Self::core_load_path
    #[inline]
    #[must_use]
    pub fn stdlib_load_path(self) -> &'static Path {
        if cfg!(windows) {
            Path::new("c:/artichoke/virtual_root/site/stdlib/lib")
        } else {
            Path::new("/artichoke/virtual_root/site/stdlib/lib")
        }
    }

    /// Return a reference to the paths in `$LOAD_PATH` parsed by this builder.
    ///
    /// Because the site paths have the lowest priority when loading
    /// features, the returned paths should appear last in `$LOAD_PATH`.
    ///
    /// # Examples
    ///
    /// ```
    /// use mezzaluna_load_path::RubyCore;
    ///
    /// let loader = RubyCore::new();
    /// assert_eq!(
    ///     loader.load_path(),
    ///     [loader.stdlib_load_path(), loader.core_load_path()],
    /// );
    /// ```
    #[inline]
    #[must_use]
    pub fn load_path(self) -> [&'static Path; 2] {
        [self.stdlib_load_path(), self.core_load_path()]
    }
}

#[cfg(test)]
mod tests {
    use std::path::Path;

    use super::*;

    #[test]
    #[allow(dead_code)]
    fn test_apis_are_const() {
        const LOADER: RubyCore = RubyCore::new();
    }

    #[test]
    fn test_load_path_has_two_entries() {
        let loader = RubyCore::new();
        assert_eq!(loader.load_path().len(), 2);
    }

    #[test]
    fn test_load_path_has_stdlib_first() {
        let loader = RubyCore::new();
        assert_eq!(loader.load_path().first().copied().unwrap(), loader.stdlib_load_path());
    }

    #[test]
    fn test_load_path_has_core_last() {
        let loader = RubyCore::new();
        assert_eq!(loader.load_path().last().copied().unwrap(), loader.core_load_path());
    }

    #[test]
    fn test_all_paths_are_non_empty() {
        let loader = RubyCore::new();
        assert!(!loader.core_load_path().as_os_str().is_empty());
        assert!(!loader.stdlib_load_path().as_os_str().is_empty());
        assert!(loader.load_path().iter().copied().all(|p| !p.as_os_str().is_empty()));
    }

    #[test]
    fn test_all_paths_are_absolute() {
        let loader = RubyCore::new();
        assert!(loader.core_load_path().is_absolute());
        assert!(loader.stdlib_load_path().is_absolute());
        assert!(loader.load_path().iter().copied().all(Path::is_absolute));
    }
}