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