Skip to main content

changepacks_java/
workspace.rs

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 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        // Initially empty
351        assert!(workspace.dependencies().is_empty());
352
353        // Add dependencies
354        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        // Adding duplicate should not increase count
363        workspace.add_dependency("core");
364        assert_eq!(workspace.dependencies().len(), 2);
365    }
366}