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 src: PackagePath,
20 lib: PackagePath,
22 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.0.push(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(".so")]),
72 });
73 }
74
75 Ok(paths)
76 }
77
78 pub fn package_path(&self) -> &PackagePath {
80 &self.src
81 }
82
83 pub fn package_cpath(&self) -> &PackagePath {
85 &self.lib
86 }
87
88 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 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 pub fn path(&self) -> &BinPath {
107 &self.bin
108 }
109
110 pub fn init(&self) -> String {
112 format!("if _VERSION:find('{}') then {LUA_INIT} end", self.version)
113 }
114
115 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 pub fn is_empty(&self) -> bool {
183 self.0.is_empty()
184 }
185 pub fn joined(&self) -> String {
186 env::join_paths(self.0.iter().unique())
187 .expect("Failed to join bin paths.")
188 .to_string_lossy()
189 .to_string()
190 }
191}
192
193impl FromStr for BinPath {
194 type Err = &'static str;
195
196 fn from_str(s: &str) -> Result<Self, Self::Err> {
197 let paths = env::split_paths(s).collect();
198 Ok(BinPath(paths))
199 }
200}
201
202impl Display for BinPath {
203 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204 self.joined().fmt(f)
205 }
206}
207
208#[cfg(test)]
209mod test {
210 use super::*;
211
212 #[test]
213 fn package_path_leading_trailing_delimiters() {
214 let path = PackagePath::from_str(
215 ";;/path/to/some/lib/lua/5.1/?.so;/path/to/another/lib/lua/5.1/?.so;;;",
216 )
217 .unwrap();
218 assert_eq!(
219 path,
220 PackagePath(vec![
221 "/path/to/some/lib/lua/5.1/?.so".into(),
222 "/path/to/another/lib/lua/5.1/?.so".into(),
223 ])
224 );
225 assert_eq!(
226 format!("{}", path),
227 "/path/to/some/lib/lua/5.1/?.so;/path/to/another/lib/lua/5.1/?.so"
228 );
229 }
230}