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