changepacks_java/
workspace.rs1use anyhow::Result;
2use async_trait::async_trait;
3use changepacks_core::{Language, UpdateType, Workspace};
4use changepacks_utils::next_version;
5use std::collections::HashSet;
6use std::path::{Path, PathBuf};
7use tokio::fs::{read_to_string, write};
8
9use crate::{update_version_in_groovy, update_version_in_kts};
10
11#[derive(Debug)]
12pub struct GradleWorkspace {
13 path: PathBuf,
14 relative_path: PathBuf,
15 version: Option<String>,
16 name: Option<String>,
17 is_changed: bool,
18 dependencies: HashSet<String>,
19}
20
21impl GradleWorkspace {
22 #[must_use]
23 pub fn new(
24 name: Option<String>,
25 version: Option<String>,
26 path: PathBuf,
27 relative_path: PathBuf,
28 ) -> Self {
29 Self {
30 path,
31 relative_path,
32 name,
33 version,
34 is_changed: false,
35 dependencies: HashSet::new(),
36 }
37 }
38}
39
40#[async_trait]
41impl Workspace for GradleWorkspace {
42 fn name(&self) -> Option<&str> {
43 self.name.as_deref()
44 }
45
46 fn path(&self) -> &Path {
47 &self.path
48 }
49
50 fn version(&self) -> Option<&str> {
51 self.version.as_deref()
52 }
53
54 async fn update_version(&mut self, update_type: UpdateType) -> Result<()> {
55 let current_version = self.version.as_deref().unwrap_or("0.0.0");
56 let new_version = next_version(current_version, update_type)?;
57
58 let content = read_to_string(&self.path).await?;
59 let file_name = self
60 .path
61 .file_name()
62 .and_then(|f| f.to_str())
63 .unwrap_or_default();
64 let is_kts = Path::new(file_name)
65 .extension()
66 .is_some_and(|ext| ext.eq_ignore_ascii_case("kts"));
67
68 let updated_content = if is_kts {
69 update_version_in_kts(&content, &new_version)
70 } else {
71 update_version_in_groovy(&content, &new_version)
72 };
73
74 write(&self.path, updated_content).await?;
75 self.version = Some(new_version);
76 Ok(())
77 }
78
79 fn language(&self) -> Language {
80 Language::Java
81 }
82
83 fn is_changed(&self) -> bool {
84 self.is_changed
85 }
86
87 fn set_changed(&mut self, changed: bool) {
88 self.is_changed = changed;
89 }
90
91 fn relative_path(&self) -> &Path {
92 &self.relative_path
93 }
94
95 fn default_publish_command(&self) -> String {
96 "./gradlew publish".to_string()
97 }
98
99 fn dependencies(&self) -> &HashSet<String> {
100 &self.dependencies
101 }
102
103 fn add_dependency(&mut self, dependency: &str) {
104 self.dependencies.insert(dependency.to_string());
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use changepacks_core::UpdateType;
112 use std::fs;
113 use tempfile::TempDir;
114 use tokio::fs::read_to_string;
115
116 #[tokio::test]
117 async fn test_gradle_workspace_new() {
118 let workspace = GradleWorkspace::new(
119 Some("test-workspace".to_string()),
120 Some("1.0.0".to_string()),
121 PathBuf::from("/test/build.gradle.kts"),
122 PathBuf::from("test/build.gradle.kts"),
123 );
124
125 assert_eq!(workspace.name(), Some("test-workspace"));
126 assert_eq!(workspace.version(), Some("1.0.0"));
127 assert_eq!(workspace.path(), PathBuf::from("/test/build.gradle.kts"));
128 assert_eq!(
129 workspace.relative_path(),
130 PathBuf::from("test/build.gradle.kts")
131 );
132 assert_eq!(workspace.language(), Language::Java);
133 assert!(!workspace.is_changed());
134 assert_eq!(workspace.default_publish_command(), "./gradlew publish");
135 }
136
137 #[tokio::test]
138 async fn test_gradle_workspace_new_without_name_and_version() {
139 let workspace = GradleWorkspace::new(
140 None,
141 None,
142 PathBuf::from("/test/build.gradle.kts"),
143 PathBuf::from("test/build.gradle.kts"),
144 );
145
146 assert_eq!(workspace.name(), None);
147 assert_eq!(workspace.version(), None);
148 }
149
150 #[tokio::test]
151 async fn test_gradle_workspace_set_changed() {
152 let mut workspace = GradleWorkspace::new(
153 Some("test-workspace".to_string()),
154 Some("1.0.0".to_string()),
155 PathBuf::from("/test/build.gradle.kts"),
156 PathBuf::from("test/build.gradle.kts"),
157 );
158
159 assert!(!workspace.is_changed());
160 workspace.set_changed(true);
161 assert!(workspace.is_changed());
162 workspace.set_changed(false);
163 assert!(!workspace.is_changed());
164 }
165
166 #[tokio::test]
167 async fn test_gradle_workspace_update_version_kts_patch() {
168 let temp_dir = TempDir::new().unwrap();
169 let project_dir = temp_dir.path().join("multiproject");
170 fs::create_dir_all(&project_dir).unwrap();
171
172 let build_gradle = project_dir.join("build.gradle.kts");
173 fs::write(
174 &build_gradle,
175 r#"
176plugins {
177 id("java")
178}
179
180group = "com.example"
181version = "1.0.0"
182"#,
183 )
184 .unwrap();
185
186 let mut workspace = GradleWorkspace::new(
187 Some("multiproject".to_string()),
188 Some("1.0.0".to_string()),
189 build_gradle.clone(),
190 PathBuf::from("multiproject/build.gradle.kts"),
191 );
192
193 workspace.update_version(UpdateType::Patch).await.unwrap();
194
195 let content = read_to_string(&build_gradle).await.unwrap();
196 assert!(content.contains(r#"version = "1.0.1""#));
197
198 temp_dir.close().unwrap();
199 }
200
201 #[tokio::test]
202 async fn test_gradle_workspace_update_version_kts_minor() {
203 let temp_dir = TempDir::new().unwrap();
204 let project_dir = temp_dir.path().join("multiproject");
205 fs::create_dir_all(&project_dir).unwrap();
206
207 let build_gradle = project_dir.join("build.gradle.kts");
208 fs::write(
209 &build_gradle,
210 r#"
211plugins {
212 id("java")
213}
214
215group = "com.example"
216version = "1.0.0"
217"#,
218 )
219 .unwrap();
220
221 let mut workspace = GradleWorkspace::new(
222 Some("multiproject".to_string()),
223 Some("1.0.0".to_string()),
224 build_gradle.clone(),
225 PathBuf::from("multiproject/build.gradle.kts"),
226 );
227
228 workspace.update_version(UpdateType::Minor).await.unwrap();
229
230 let content = read_to_string(&build_gradle).await.unwrap();
231 assert!(content.contains(r#"version = "1.1.0""#));
232
233 temp_dir.close().unwrap();
234 }
235
236 #[tokio::test]
237 async fn test_gradle_workspace_update_version_kts_major() {
238 let temp_dir = TempDir::new().unwrap();
239 let project_dir = temp_dir.path().join("multiproject");
240 fs::create_dir_all(&project_dir).unwrap();
241
242 let build_gradle = project_dir.join("build.gradle.kts");
243 fs::write(
244 &build_gradle,
245 r#"
246plugins {
247 id("java")
248}
249
250group = "com.example"
251version = "1.0.0"
252"#,
253 )
254 .unwrap();
255
256 let mut workspace = GradleWorkspace::new(
257 Some("multiproject".to_string()),
258 Some("1.0.0".to_string()),
259 build_gradle.clone(),
260 PathBuf::from("multiproject/build.gradle.kts"),
261 );
262
263 workspace.update_version(UpdateType::Major).await.unwrap();
264
265 let content = read_to_string(&build_gradle).await.unwrap();
266 assert!(content.contains(r#"version = "2.0.0""#));
267
268 temp_dir.close().unwrap();
269 }
270
271 #[tokio::test]
272 async fn test_gradle_workspace_update_version_groovy() {
273 let temp_dir = TempDir::new().unwrap();
274 let project_dir = temp_dir.path().join("multiproject");
275 fs::create_dir_all(&project_dir).unwrap();
276
277 let build_gradle = project_dir.join("build.gradle");
278 fs::write(
279 &build_gradle,
280 r#"
281plugins {
282 id 'java'
283}
284
285group = 'com.example'
286version = '1.0.0'
287"#,
288 )
289 .unwrap();
290
291 let mut workspace = GradleWorkspace::new(
292 Some("multiproject".to_string()),
293 Some("1.0.0".to_string()),
294 build_gradle.clone(),
295 PathBuf::from("multiproject/build.gradle"),
296 );
297
298 workspace.update_version(UpdateType::Patch).await.unwrap();
299
300 let content = read_to_string(&build_gradle).await.unwrap();
301 assert!(content.contains("version = '1.0.1'"));
302
303 temp_dir.close().unwrap();
304 }
305
306 #[tokio::test]
307 async fn test_gradle_workspace_update_version_without_version() {
308 let temp_dir = TempDir::new().unwrap();
309 let project_dir = temp_dir.path().join("multiproject");
310 fs::create_dir_all(&project_dir).unwrap();
311
312 let build_gradle = project_dir.join("build.gradle.kts");
313 fs::write(
314 &build_gradle,
315 r#"
316plugins {
317 id("java")
318}
319
320group = "com.example"
321version = "0.0.0"
322"#,
323 )
324 .unwrap();
325
326 let mut workspace = GradleWorkspace::new(
327 Some("multiproject".to_string()),
328 None,
329 build_gradle.clone(),
330 PathBuf::from("multiproject/build.gradle.kts"),
331 );
332
333 workspace.update_version(UpdateType::Patch).await.unwrap();
334
335 let content = read_to_string(&build_gradle).await.unwrap();
336 assert!(content.contains(r#"version = "0.0.1""#));
337
338 temp_dir.close().unwrap();
339 }
340
341 #[test]
342 fn test_gradle_workspace_dependencies() {
343 let mut workspace = GradleWorkspace::new(
344 Some("test-workspace".to_string()),
345 Some("1.0.0".to_string()),
346 PathBuf::from("/test/build.gradle.kts"),
347 PathBuf::from("test/build.gradle.kts"),
348 );
349
350 assert!(workspace.dependencies().is_empty());
352
353 workspace.add_dependency("core");
355 workspace.add_dependency("utils");
356
357 let deps = workspace.dependencies();
358 assert_eq!(deps.len(), 2);
359 assert!(deps.contains("core"));
360 assert!(deps.contains("utils"));
361
362 workspace.add_dependency("core");
364 assert_eq!(workspace.dependencies().len(), 2);
365 }
366}