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.layout_for(dependency).src)
91 .filter(|dir| dir.is_dir())
92 .filter_map(|dependency_dir| diff_paths(dependency_dir, workspace.root()));
93
94 let test_dependency_tree = workspace.test_tree(config)?;
95 let test_dependency_dirs = lockfile
96 .local_pkg_lock(&LocalPackageLockType::Test)
97 .rocks()
98 .values()
99 .map(|dependency| test_dependency_tree.layout_for(dependency).src)
100 .filter(|dir| dir.is_dir())
101 .filter_map(|test_dependency_dir| diff_paths(test_dependency_dir, workspace.root()));
102
103 let library_dirs = dependency_dirs
104 .chain(test_dependency_dirs)
105 .sorted()
106 .collect_vec();
107
108 let luarc_content = update_luarc_content(&luarc_content, library_dirs)?;
109
110 fs::tokio::write(&luarc_path, luarc_content).await?;
111
112 Ok(())
113}
114
115fn update_luarc_content(
116 prev_contents: &str,
117 extra_paths: Vec<PathBuf>,
118) -> Result<String, GenLuaRcError> {
119 let mut luarc: LuaRC = serde_json::from_str(prev_contents)
120 .map_err(|err| GenLuaRcError::Deserialize(err.to_string()))?;
121
122 luarc
124 .workspace
125 .library
126 .retain(|path| !path.starts_with(&format!("{LUX_DIR_NAME}/")));
127
128 extra_paths
129 .iter()
130 .map(|path| path.to_slash_lossy().to_string())
131 .for_each(|path_str| luarc.workspace.library.push(path_str));
132
133 serde_json::to_string_pretty(&luarc).map_err(|err| GenLuaRcError::Serialize(err.to_string()))
134}
135
136#[cfg(test)]
137mod tests {
138
139 use super::*;
140
141 #[test]
142 fn test_generate_luarc_with_previous_libraries_parametrized() {
143 let cases = vec![
144 (
145 "Empty existing libraries, adding single lib", r#"{
147 "workspace": {
148 "library": []
149 }
150 }"#,
151 vec![".lux/5.1/my-lib".into()],
152 r#"{
153 "workspace": {
154 "library": [".lux/5.1/my-lib"]
155 }
156 }"#,
157 ),
158 (
159 "Other fields present, adding libs", r#"{
161 "any-other-field": true,
162 "workspace": {
163 "library": []
164 }
165 }"#,
166 vec![".lux/5.1/lib-A".into(), ".lux/5.1/lib-B".into()],
167 r#"{
168 "any-other-field": true,
169 "workspace": {
170 "library": [".lux/5.1/lib-A", ".lux/5.1/lib-B"]
171 }
172 }"#,
173 ),
174 (
175 "Removes not present libs, without removing others", r#"{
177 "workspace": {
178 "library": [".lux/5.1/lib-A", ".lux/5.4/lib-B"]
179 }
180 }"#,
181 vec![".lux/5.1/lib-C".into()],
182 r#"{
183 "workspace": {
184 "library": [".lux/5.1/lib-C"]
185 }
186 }"#,
187 ),
188 ];
189
190 for (description, initial, new_libs, expected) in cases {
191 let content = super::update_luarc_content(initial, new_libs.clone()).unwrap();
192
193 assert_eq!(
194 serde_json::from_str::<LuaRC>(&content).unwrap(),
195 serde_json::from_str::<LuaRC>(expected).unwrap(),
196 "Case failed: {}\nInitial input:\n{}\nNew libs: {:?}",
197 description,
198 initial,
199 new_libs
200 );
201 }
202 }
203}