Skip to main content

lux_lib/
path.rs

1use crate::{
2    build::utils::c_dylib_extension,
3    lua_version::LuaVersion,
4    tree::{InstallTree, TreeError},
5};
6use itertools::Itertools;
7use miette::Diagnostic;
8use path_slash::PathBufExt;
9use serde::Serialize;
10use std::{env, fmt::Display, path::PathBuf, str::FromStr};
11use thiserror::Error;
12
13const LUA_PATH_SEPARATOR: &str = ";";
14const LUA_INIT: &str = "require('lux').loader()";
15
16#[derive(PartialEq, Eq, Debug, Serialize)]
17pub struct Paths {
18    /// Paths for Lua libraries
19    src: PackagePath,
20    /// Paths for native Lua libraries
21    lib: PackagePath,
22    /// Paths for executables
23    bin: BinPath,
24
25    version: LuaVersion,
26}
27
28#[derive(Debug, Error, Diagnostic)]
29pub enum PathsError {
30    #[error(transparent)]
31    #[diagnostic(transparent)]
32    Tree(#[from] TreeError),
33}
34
35impl Paths {
36    fn default(tree: &impl InstallTree) -> Self {
37        Self {
38            src: <_>::default(),
39            lib: <_>::default(),
40            bin: <_>::default(),
41            version: tree.version().clone(),
42        }
43    }
44
45    pub fn new(tree: &impl InstallTree) -> Result<Self, PathsError> {
46        let mut paths = tree
47            .list()?
48            .values()
49            .flat_map(|packages| {
50                packages
51                    .iter()
52                    .map(|package| tree.installed_rock_layout(package))
53                    .collect_vec()
54            })
55            .try_fold(Self::default(tree), |mut paths, package| {
56                let package = package?;
57                paths.src.0.push(package.src.join("?.lua"));
58                paths.src.0.push(package.src.join("?").join("init.lua"));
59                paths
60                    .lib
61                    .0
62                    .push(package.lib.join(format!("?.{}", c_dylib_extension())));
63                paths.bin.add_path(package.bin);
64                Ok::<Paths, TreeError>(paths)
65            })?;
66
67        if let Some(lib_path) = tree.version().lux_lib_dir() {
68            paths.prepend(&Paths {
69                version: tree.version().clone(),
70                src: <_>::default(),
71                bin: <_>::default(),
72                lib: PackagePath(vec![lib_path.join(format!("?.{}", c_dylib_extension()))]),
73            });
74        }
75
76        Ok(paths)
77    }
78
79    /// Get the `package.path`
80    pub fn package_path(&self) -> &PackagePath {
81        &self.src
82    }
83
84    /// Get the `package.cpath`
85    pub fn package_cpath(&self) -> &PackagePath {
86        &self.lib
87    }
88
89    /// Get the `package.path`, prepended to `LUA_PATH`
90    pub fn package_path_prepended(&self) -> PackagePath {
91        let mut lua_path = PackagePath::from_str(env::var("LUA_PATH").unwrap_or_default().as_str())
92            .unwrap_or_default();
93        lua_path.prepend(self.package_path());
94        lua_path
95    }
96
97    /// Get the `package.cpath`, prepended to `LUA_CPATH`
98    pub fn package_cpath_prepended(&self) -> PackagePath {
99        let mut lua_cpath =
100            PackagePath::from_str(env::var("LUA_CPATH").unwrap_or_default().as_str())
101                .unwrap_or_default();
102        lua_cpath.prepend(self.package_cpath());
103        lua_cpath
104    }
105
106    /// Get the `$PATH`
107    pub fn path(&self) -> &BinPath {
108        &self.bin
109    }
110
111    /// Get `$LUA_INIT`
112    pub fn init(&self) -> String {
113        format!("if _VERSION:find('{}') then {LUA_INIT} end", self.version)
114    }
115
116    /// Get the `$PATH`, prepended to the existing `$PATH` environment.
117    pub fn path_prepended(&self) -> BinPath {
118        let mut path = BinPath::from_env();
119        path.prepend(self.path());
120        path
121    }
122
123    pub fn prepend(&mut self, other: &Self) {
124        self.src.prepend(&other.src);
125        self.lib.prepend(&other.lib);
126        self.bin.prepend(&other.bin);
127    }
128}
129
130#[derive(PartialEq, Eq, Debug, Default, Serialize, Clone)]
131pub struct PackagePath(Vec<PathBuf>);
132
133impl PackagePath {
134    fn prepend(&mut self, other: &Self) {
135        let mut new_vec = other.0.to_owned();
136        new_vec.append(&mut self.0);
137        self.0 = new_vec;
138    }
139    pub fn is_empty(&self) -> bool {
140        self.0.is_empty()
141    }
142    pub fn joined(&self) -> String {
143        self.0
144            .iter()
145            .unique()
146            .map(|path| path.to_slash_lossy())
147            .join(LUA_PATH_SEPARATOR)
148    }
149}
150
151impl FromStr for PackagePath {
152    type Err = &'static str;
153
154    fn from_str(s: &str) -> Result<Self, Self::Err> {
155        let paths = s
156            .trim_start_matches(LUA_PATH_SEPARATOR)
157            .trim_end_matches(LUA_PATH_SEPARATOR)
158            .split(LUA_PATH_SEPARATOR)
159            .map(PathBuf::from)
160            .collect();
161        Ok(PackagePath(paths))
162    }
163}
164
165impl Display for PackagePath {
166    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167        self.joined().fmt(f)
168    }
169}
170
171#[derive(PartialEq, Eq, Debug, Default, Serialize)]
172pub struct BinPath(Vec<PathBuf>);
173
174impl BinPath {
175    pub fn from_env() -> Self {
176        Self::from_str(env::var("PATH").unwrap_or_default().as_str()).unwrap_or_default()
177    }
178    pub fn prepend(&mut self, other: &Self) {
179        let mut new_vec = other.0.to_owned();
180        new_vec.append(&mut self.0);
181        self.0 = new_vec;
182    }
183    /// Adds a `PathBuf` to the path.
184    /// Drops it silently if the `PathBuf` is invalid and cannot be used to create a `PATH` variable
185    /// (e.g. if it contains `PATH` separator characters).
186    pub fn add_path(&mut self, path: PathBuf) {
187        if env::join_paths(vec![&path]).is_ok() {
188            let mut new_vec = Vec::new();
189            new_vec.push(path);
190            new_vec.append(&mut self.0);
191            self.0 = new_vec;
192        }
193    }
194    pub fn is_empty(&self) -> bool {
195        self.0.is_empty()
196    }
197    /// Joins the path to create a PATH expression.
198    /// If a path contains invalid characters (e.g. the PATH separator),
199    /// this returns an empty string.
200    pub fn joined(&self) -> String {
201        env::join_paths(self.0.iter().unique())
202            .unwrap_or_default()
203            .to_string_lossy()
204            .to_string()
205    }
206}
207
208impl FromStr for BinPath {
209    type Err = &'static str;
210
211    fn from_str(s: &str) -> Result<Self, Self::Err> {
212        let paths = env::split_paths(s).collect();
213        Ok(BinPath(paths))
214    }
215}
216
217impl Display for BinPath {
218    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219        self.joined().fmt(f)
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    #[test]
228    fn package_path_leading_trailing_delimiters() {
229        let path = PackagePath::from_str(
230            ";;/path/to/some/lib/lua/5.1/?.so;/path/to/another/lib/lua/5.1/?.so;;;",
231        )
232        .unwrap();
233        assert_eq!(
234            path,
235            PackagePath(vec![
236                "/path/to/some/lib/lua/5.1/?.so".into(),
237                "/path/to/another/lib/lua/5.1/?.so".into(),
238            ])
239        );
240        assert_eq!(
241            format!("{path}"),
242            "/path/to/some/lib/lua/5.1/?.so;/path/to/another/lib/lua/5.1/?.so"
243        );
244    }
245}