lux_lib/operations/
gen_luarc.rs1use crate::config::Config;
2use crate::lockfile::LocalPackageLockType;
3use crate::tree::InstallTree;
4use crate::workspace::Workspace;
5use crate::workspace::WorkspaceError;
6use crate::workspace::WorkspaceTreeError;
7use crate::workspace::LUX_DIR_NAME;
8use bon::Builder;
9use itertools::Itertools;
10use path_slash::PathBufExt;
11use pathdiff::diff_paths;
12use serde::{Deserialize, Serialize};
13use std::collections::BTreeMap;
14use std::io;
15use std::path::PathBuf;
16use thiserror::Error;
17use tokio::fs;
18
19#[derive(Error, Debug)]
20pub enum GenLuaRcError {
21 #[error(transparent)]
22 Workspace(#[from] WorkspaceError),
23 #[error(transparent)]
24 WorkspaceTree(#[from] WorkspaceTreeError),
25 #[error("failed to serialize luarc content:\n{0}")]
26 Serialize(String),
27 #[error("failed to deserialize luarc content:\n{0}")]
28 Deserialize(String),
29 #[error("failed to write {0}:\n{1}")]
30 Write(PathBuf, io::Error),
31}
32
33#[derive(Builder)]
34#[builder(start_fn = new, finish_fn(name = _build, vis = ""))]
35pub(crate) struct GenLuaRc<'a> {
36 config: &'a Config,
37 workspace: &'a Workspace,
38}
39
40impl<State> GenLuaRcBuilder<'_, State>
41where
42 State: gen_lua_rc_builder::State + gen_lua_rc_builder::IsComplete,
43{
44 pub async fn generate_luarc(self) -> Result<(), GenLuaRcError> {
45 do_generate_luarc(self._build()).await
46 }
47}
48
49#[derive(Serialize, Deserialize, Default, PartialEq, Debug)]
50#[serde(default)]
51struct LuaRC {
52 #[serde(flatten)] other: BTreeMap<String, serde_json::Value>,
54
55 #[serde(default)]
56 workspace: LuaRcWorkspace,
57}
58
59#[derive(Serialize, Deserialize, Default, PartialEq, Debug)]
60struct LuaRcWorkspace {
61 #[serde(flatten)] other: BTreeMap<String, serde_json::Value>,
63
64 #[serde(default)]
65 library: Vec<String>,
66}
67
68async fn do_generate_luarc(args: GenLuaRc<'_>) -> Result<(), GenLuaRcError> {
69 let config = args.config;
70 if !config.generate_luarc() {
71 return Ok(());
72 }
73 let workspace = args.workspace;
74 let lockfile = workspace.lockfile()?;
75 let luarc_path = workspace.luarc_path();
76
77 let luarc_content = fs::read_to_string(&luarc_path)
79 .await
80 .unwrap_or_else(|_| "{}".into());
81
82 let dependency_tree = workspace.tree(config)?;
83 let dependency_dirs = lockfile
84 .local_pkg_lock(&LocalPackageLockType::Regular)
85 .rocks()
86 .values()
87 .map(|dependency| dependency_tree.installed_rock_layout(dependency))
88 .filter_map(Result::ok)
89 .map(|rock_layout| rock_layout.src)
90 .filter(|dir| dir.is_dir())
91 .filter_map(|dependency_dir| diff_paths(dependency_dir, workspace.root()));
92
93 let test_dependency_tree = workspace.test_tree(config)?;
94 let test_dependency_dirs = lockfile
95 .local_pkg_lock(&LocalPackageLockType::Test)
96 .rocks()
97 .values()
98 .map(|dependency| test_dependency_tree.installed_rock_layout(dependency))
99 .filter_map(Result::ok)
100 .map(|rock_layout| rock_layout.src)
101 .filter(|dir| dir.is_dir())
102 .filter_map(|test_dependency_dir| diff_paths(test_dependency_dir, workspace.root()));
103
104 let library_dirs = dependency_dirs
105 .chain(test_dependency_dirs)
106 .sorted()
107 .collect_vec();
108
109 let luarc_content = update_luarc_content(&luarc_content, library_dirs)?;
110
111 fs::write(&luarc_path, luarc_content)
112 .await
113 .map_err(|err| GenLuaRcError::Write(luarc_path, err))?;
114
115 Ok(())
116}
117
118fn update_luarc_content(
119 prev_contents: &str,
120 extra_paths: Vec<PathBuf>,
121) -> Result<String, GenLuaRcError> {
122 let mut luarc: LuaRC = serde_json::from_str(prev_contents)
123 .map_err(|err| GenLuaRcError::Deserialize(err.to_string()))?;
124
125 luarc
127 .workspace
128 .library
129 .retain(|path| !path.starts_with(&format!("{LUX_DIR_NAME}/")));
130
131 extra_paths
132 .iter()
133 .map(|path| path.to_slash_lossy().to_string())
134 .for_each(|path_str| luarc.workspace.library.push(path_str));
135
136 serde_json::to_string_pretty(&luarc).map_err(|err| GenLuaRcError::Serialize(err.to_string()))
137}
138
139#[cfg(test)]
140mod tests {
141
142 use super::*;
143
144 #[test]
145 fn test_generate_luarc_with_previous_libraries_parametrized() {
146 let cases = vec![
147 (
148 "Empty existing libraries, adding single lib", r#"{
150 "workspace": {
151 "library": []
152 }
153 }"#,
154 vec![".lux/5.1/my-lib".into()],
155 r#"{
156 "workspace": {
157 "library": [".lux/5.1/my-lib"]
158 }
159 }"#,
160 ),
161 (
162 "Other fields present, adding libs", r#"{
164 "any-other-field": true,
165 "workspace": {
166 "library": []
167 }
168 }"#,
169 vec![".lux/5.1/lib-A".into(), ".lux/5.1/lib-B".into()],
170 r#"{
171 "any-other-field": true,
172 "workspace": {
173 "library": [".lux/5.1/lib-A", ".lux/5.1/lib-B"]
174 }
175 }"#,
176 ),
177 (
178 "Removes not present libs, without removing others", r#"{
180 "workspace": {
181 "library": [".lux/5.1/lib-A", ".lux/5.4/lib-B"]
182 }
183 }"#,
184 vec![".lux/5.1/lib-C".into()],
185 r#"{
186 "workspace": {
187 "library": [".lux/5.1/lib-C"]
188 }
189 }"#,
190 ),
191 ];
192
193 for (description, initial, new_libs, expected) in cases {
194 let content = super::update_luarc_content(initial, new_libs.clone()).unwrap();
195
196 assert_eq!(
197 serde_json::from_str::<LuaRC>(&content).unwrap(),
198 serde_json::from_str::<LuaRC>(expected).unwrap(),
199 "Case failed: {}\nInitial input:\n{}\nNew libs: {:?}",
200 description,
201 initial,
202 &new_libs
203 );
204 }
205 }
206}