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 = 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 pub fn package_path(&self) -> &PackagePath {
72 &self.src
73 }
74
75 pub fn package_cpath(&self) -> &PackagePath {
77 &self.lib
78 }
79
80 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 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 pub fn path(&self) -> &BinPath {
99 &self.bin
100 }
101
102 pub fn init(&self) -> String {
104 format!("if _VERSION:find('{}') then {LUA_INIT} end", self.version)
105 }
106
107 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 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 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}