Skip to main content

lux_lib/
path.rs

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