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 miette::Diagnostic;
11use path_slash::PathBufExt;
12use pathdiff::diff_paths;
13use serde::{Deserialize, Serialize};
14use std::collections::BTreeMap;
15use std::io;
16use std::path::PathBuf;
17use thiserror::Error;
18use tokio::fs;
19
20#[derive(Error, Debug, Diagnostic)]
21pub enum GenLuaRcError {
22 #[error(transparent)]
23 #[diagnostic(transparent)]
24 Workspace(#[from] WorkspaceError),
25 #[error(transparent)]
26 #[diagnostic(transparent)]
27 WorkspaceTree(#[from] WorkspaceTreeError),
28 #[error("failed to serialize luarc content:\n{0}")]
29 Serialize(String),
30 #[error("failed to deserialize luarc content:\n{0}")]
31 Deserialize(String),
32 #[error("failed to write {0}:\n{1}")]
33 Write(PathBuf, io::Error),
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::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::write(&luarc_path, luarc_content)
115 .await
116 .map_err(|err| GenLuaRcError::Write(luarc_path, err))?;
117
118 Ok(())
119}
120
121fn update_luarc_content(
122 prev_contents: &str,
123 extra_paths: Vec<PathBuf>,
124) -> Result<String, GenLuaRcError> {
125 let mut luarc: LuaRC = serde_json::from_str(prev_contents)
126 .map_err(|err| GenLuaRcError::Deserialize(err.to_string()))?;
127
128 luarc
130 .workspace
131 .library
132 .retain(|path| !path.starts_with(&format!("{LUX_DIR_NAME}/")));
133
134 extra_paths
135 .iter()
136 .map(|path| path.to_slash_lossy().to_string())
137 .for_each(|path_str| luarc.workspace.library.push(path_str));
138
139 serde_json::to_string_pretty(&luarc).map_err(|err| GenLuaRcError::Serialize(err.to_string()))
140}
141
142#[cfg(test)]
143mod tests {
144
145 use super::*;
146
147 #[test]
148 fn test_generate_luarc_with_previous_libraries_parametrized() {
149 let cases = vec![
150 (
151 "Empty existing libraries, adding single lib", r#"{
153 "workspace": {
154 "library": []
155 }
156 }"#,
157 vec![".lux/5.1/my-lib".into()],
158 r#"{
159 "workspace": {
160 "library": [".lux/5.1/my-lib"]
161 }
162 }"#,
163 ),
164 (
165 "Other fields present, adding libs", r#"{
167 "any-other-field": true,
168 "workspace": {
169 "library": []
170 }
171 }"#,
172 vec![".lux/5.1/lib-A".into(), ".lux/5.1/lib-B".into()],
173 r#"{
174 "any-other-field": true,
175 "workspace": {
176 "library": [".lux/5.1/lib-A", ".lux/5.1/lib-B"]
177 }
178 }"#,
179 ),
180 (
181 "Removes not present libs, without removing others", r#"{
183 "workspace": {
184 "library": [".lux/5.1/lib-A", ".lux/5.4/lib-B"]
185 }
186 }"#,
187 vec![".lux/5.1/lib-C".into()],
188 r#"{
189 "workspace": {
190 "library": [".lux/5.1/lib-C"]
191 }
192 }"#,
193 ),
194 ];
195
196 for (description, initial, new_libs, expected) in cases {
197 let content = super::update_luarc_content(initial, new_libs.clone()).unwrap();
198
199 assert_eq!(
200 serde_json::from_str::<LuaRC>(&content).unwrap(),
201 serde_json::from_str::<LuaRC>(expected).unwrap(),
202 "Case failed: {}\nInitial input:\n{}\nNew libs: {:?}",
203 description,
204 initial,
205 &new_libs
206 );
207 }
208 }
209}