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 src: PackagePath,
20 lib: PackagePath,
22 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 pub fn package_path(&self) -> &PackagePath {
81 &self.src
82 }
83
84 pub fn package_cpath(&self) -> &PackagePath {
86 &self.lib
87 }
88
89 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 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 pub fn path(&self) -> &BinPath {
108 &self.bin
109 }
110
111 pub fn init(&self) -> String {
113 format!("if _VERSION:find('{}') then {LUA_INIT} end", self.version)
114 }
115
116 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 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 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}