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