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