1use 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 set_name(&mut self, name: String) {
96 self.name = Some(name);
97 }
98
99 #[cfg(windows)]
100 fn default_publish_command(&self) -> String {
101 ".\\gradlew.bat publish".to_string()
102 }
103
104 #[cfg(not(windows))]
105 fn default_publish_command(&self) -> String {
106 "./gradlew publish".to_string()
107 }
108
109 #[cfg(windows)]
113 fn default_dry_run_publish_command(&self) -> Option<String> {
114 Some(".\\gradlew.bat publishToMavenLocal".to_string())
115 }
116
117 #[cfg(not(windows))]
118 fn default_dry_run_publish_command(&self) -> Option<String> {
119 Some("./gradlew publishToMavenLocal".to_string())
120 }
121
122 fn dependencies(&self) -> &HashSet<String> {
123 &self.dependencies
124 }
125
126 fn add_dependency(&mut self, dependency: &str) {
127 self.dependencies.insert(dependency.to_string());
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use changepacks_core::UpdateType;
135 use std::fs;
136 use tempfile::TempDir;
137 use tokio::fs::read_to_string;
138
139 #[tokio::test]
140 async fn test_gradle_workspace_new() {
141 let workspace = GradleWorkspace::new(
142 Some("test-workspace".to_string()),
143 Some("1.0.0".to_string()),
144 PathBuf::from("/test/build.gradle.kts"),
145 PathBuf::from("test/build.gradle.kts"),
146 );
147
148 assert_eq!(workspace.name(), Some("test-workspace"));
149 assert_eq!(workspace.version(), Some("1.0.0"));
150 assert_eq!(workspace.path(), PathBuf::from("/test/build.gradle.kts"));
151 assert_eq!(
152 workspace.relative_path(),
153 PathBuf::from("test/build.gradle.kts")
154 );
155 assert_eq!(workspace.language(), Language::Java);
156 assert!(!workspace.is_changed());
157 #[cfg(windows)]
158 {
159 assert_eq!(
160 workspace.default_publish_command(),
161 ".\\gradlew.bat publish"
162 );
163 assert_eq!(
164 workspace.default_dry_run_publish_command().as_deref(),
165 Some(".\\gradlew.bat publishToMavenLocal")
166 );
167 }
168 #[cfg(not(windows))]
169 {
170 assert_eq!(workspace.default_publish_command(), "./gradlew publish");
171 assert_eq!(
172 workspace.default_dry_run_publish_command().as_deref(),
173 Some("./gradlew publishToMavenLocal")
174 );
175 }
176 }
177
178 #[tokio::test]
179 async fn test_gradle_workspace_new_without_name_and_version() {
180 let workspace = GradleWorkspace::new(
181 None,
182 None,
183 PathBuf::from("/test/build.gradle.kts"),
184 PathBuf::from("test/build.gradle.kts"),
185 );
186
187 assert_eq!(workspace.name(), None);
188 assert_eq!(workspace.version(), None);
189 }
190
191 #[tokio::test]
192 async fn test_gradle_workspace_set_changed() {
193 let mut workspace = GradleWorkspace::new(
194 Some("test-workspace".to_string()),
195 Some("1.0.0".to_string()),
196 PathBuf::from("/test/build.gradle.kts"),
197 PathBuf::from("test/build.gradle.kts"),
198 );
199
200 assert!(!workspace.is_changed());
201 workspace.set_changed(true);
202 assert!(workspace.is_changed());
203 workspace.set_changed(false);
204 assert!(!workspace.is_changed());
205 }
206
207 #[tokio::test]
208 async fn test_gradle_workspace_update_version_kts_patch() {
209 let temp_dir = TempDir::new().unwrap();
210 let project_dir = temp_dir.path().join("multiproject");
211 fs::create_dir_all(&project_dir).unwrap();
212
213 let build_gradle = project_dir.join("build.gradle.kts");
214 fs::write(
215 &build_gradle,
216 r#"
217plugins {
218 id("java")
219}
220
221group = "com.example"
222version = "1.0.0"
223"#,
224 )
225 .unwrap();
226
227 let mut workspace = GradleWorkspace::new(
228 Some("multiproject".to_string()),
229 Some("1.0.0".to_string()),
230 build_gradle.clone(),
231 PathBuf::from("multiproject/build.gradle.kts"),
232 );
233
234 workspace.update_version(UpdateType::Patch).await.unwrap();
235
236 let content = read_to_string(&build_gradle).await.unwrap();
237 assert!(content.contains(r#"version = "1.0.1""#));
238
239 temp_dir.close().unwrap();
240 }
241
242 #[tokio::test]
243 async fn test_gradle_workspace_update_version_kts_minor() {
244 let temp_dir = TempDir::new().unwrap();
245 let project_dir = temp_dir.path().join("multiproject");
246 fs::create_dir_all(&project_dir).unwrap();
247
248 let build_gradle = project_dir.join("build.gradle.kts");
249 fs::write(
250 &build_gradle,
251 r#"
252plugins {
253 id("java")
254}
255
256group = "com.example"
257version = "1.0.0"
258"#,
259 )
260 .unwrap();
261
262 let mut workspace = GradleWorkspace::new(
263 Some("multiproject".to_string()),
264 Some("1.0.0".to_string()),
265 build_gradle.clone(),
266 PathBuf::from("multiproject/build.gradle.kts"),
267 );
268
269 workspace.update_version(UpdateType::Minor).await.unwrap();
270
271 let content = read_to_string(&build_gradle).await.unwrap();
272 assert!(content.contains(r#"version = "1.1.0""#));
273
274 temp_dir.close().unwrap();
275 }
276
277 #[tokio::test]
278 async fn test_gradle_workspace_update_version_kts_major() {
279 let temp_dir = TempDir::new().unwrap();
280 let project_dir = temp_dir.path().join("multiproject");
281 fs::create_dir_all(&project_dir).unwrap();
282
283 let build_gradle = project_dir.join("build.gradle.kts");
284 fs::write(
285 &build_gradle,
286 r#"
287plugins {
288 id("java")
289}
290
291group = "com.example"
292version = "1.0.0"
293"#,
294 )
295 .unwrap();
296
297 let mut workspace = GradleWorkspace::new(
298 Some("multiproject".to_string()),
299 Some("1.0.0".to_string()),
300 build_gradle.clone(),
301 PathBuf::from("multiproject/build.gradle.kts"),
302 );
303
304 workspace.update_version(UpdateType::Major).await.unwrap();
305
306 let content = read_to_string(&build_gradle).await.unwrap();
307 assert!(content.contains(r#"version = "2.0.0""#));
308
309 temp_dir.close().unwrap();
310 }
311
312 #[tokio::test]
313 async fn test_gradle_workspace_update_version_groovy() {
314 let temp_dir = TempDir::new().unwrap();
315 let project_dir = temp_dir.path().join("multiproject");
316 fs::create_dir_all(&project_dir).unwrap();
317
318 let build_gradle = project_dir.join("build.gradle");
319 fs::write(
320 &build_gradle,
321 r#"
322plugins {
323 id 'java'
324}
325
326group = 'com.example'
327version = '1.0.0'
328"#,
329 )
330 .unwrap();
331
332 let mut workspace = GradleWorkspace::new(
333 Some("multiproject".to_string()),
334 Some("1.0.0".to_string()),
335 build_gradle.clone(),
336 PathBuf::from("multiproject/build.gradle"),
337 );
338
339 workspace.update_version(UpdateType::Patch).await.unwrap();
340
341 let content = read_to_string(&build_gradle).await.unwrap();
342 assert!(content.contains("version = '1.0.1'"));
343
344 temp_dir.close().unwrap();
345 }
346
347 #[tokio::test]
348 async fn test_gradle_workspace_update_version_without_version() {
349 let temp_dir = TempDir::new().unwrap();
350 let project_dir = temp_dir.path().join("multiproject");
351 fs::create_dir_all(&project_dir).unwrap();
352
353 let build_gradle = project_dir.join("build.gradle.kts");
354 fs::write(
355 &build_gradle,
356 r#"
357plugins {
358 id("java")
359}
360
361group = "com.example"
362version = "0.0.0"
363"#,
364 )
365 .unwrap();
366
367 let mut workspace = GradleWorkspace::new(
368 Some("multiproject".to_string()),
369 None,
370 build_gradle.clone(),
371 PathBuf::from("multiproject/build.gradle.kts"),
372 );
373
374 workspace.update_version(UpdateType::Patch).await.unwrap();
375
376 let content = read_to_string(&build_gradle).await.unwrap();
377 assert!(content.contains(r#"version = "0.0.1""#));
378
379 temp_dir.close().unwrap();
380 }
381
382 #[test]
383 fn test_gradle_workspace_dependencies() {
384 let mut workspace = GradleWorkspace::new(
385 Some("test-workspace".to_string()),
386 Some("1.0.0".to_string()),
387 PathBuf::from("/test/build.gradle.kts"),
388 PathBuf::from("test/build.gradle.kts"),
389 );
390
391 assert!(workspace.dependencies().is_empty());
393
394 workspace.add_dependency("core");
396 workspace.add_dependency("utils");
397
398 let deps = workspace.dependencies();
399 assert_eq!(deps.len(), 2);
400 assert!(deps.contains("core"));
401 assert!(deps.contains("utils"));
402
403 workspace.add_dependency("core");
405 assert_eq!(workspace.dependencies().len(), 2);
406 }
407
408 #[test]
409 fn test_set_name() {
410 let mut workspace = GradleWorkspace::new(
411 None,
412 Some("1.0.0".to_string()),
413 PathBuf::from("/test/build.gradle.kts"),
414 PathBuf::from("build.gradle.kts"),
415 );
416 assert_eq!(workspace.name(), None);
417 workspace.set_name("my-project".to_string());
418 assert_eq!(workspace.name(), Some("my-project"));
419 }
420}