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