Skip to main content

changepacks_java/
package.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use changepacks_core::{Language, Package, UpdateType};
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 GradlePackage {
13    name: Option<String>,
14    version: Option<String>,
15    path: PathBuf,
16    relative_path: PathBuf,
17    is_changed: bool,
18    dependencies: HashSet<String>,
19}
20
21impl GradlePackage {
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            name,
31            version,
32            path,
33            relative_path,
34            is_changed: false,
35            dependencies: HashSet::new(),
36        }
37    }
38}
39
40#[async_trait]
41impl Package for GradlePackage {
42    fn name(&self) -> Option<&str> {
43        self.name.as_deref()
44    }
45
46    fn version(&self) -> Option<&str> {
47        self.version.as_deref()
48    }
49
50    fn path(&self) -> &Path {
51        &self.path
52    }
53
54    fn relative_path(&self) -> &Path {
55        &self.relative_path
56    }
57
58    async fn update_version(&mut self, update_type: UpdateType) -> Result<()> {
59        let current_version = self.version.as_deref().unwrap_or("0.0.0");
60        let new_version = next_version(current_version, update_type)?;
61
62        let content = read_to_string(&self.path).await?;
63        let file_name = self
64            .path
65            .file_name()
66            .and_then(|f| f.to_str())
67            .unwrap_or_default();
68        let is_kts = Path::new(file_name)
69            .extension()
70            .is_some_and(|ext| ext.eq_ignore_ascii_case("kts"));
71
72        let updated_content = if is_kts {
73            update_version_in_kts(&content, &new_version)
74        } else {
75            update_version_in_groovy(&content, &new_version)
76        };
77
78        write(&self.path, updated_content).await?;
79        self.version = Some(new_version);
80        Ok(())
81    }
82
83    fn language(&self) -> Language {
84        Language::Java
85    }
86
87    fn set_changed(&mut self, changed: bool) {
88        self.is_changed = changed;
89    }
90
91    fn is_changed(&self) -> bool {
92        self.is_changed
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_package_new() {
122        let package = GradlePackage::new(
123            Some("test-package".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!(package.name(), Some("test-package"));
130        assert_eq!(package.version(), Some("1.0.0"));
131        assert_eq!(package.path(), PathBuf::from("/test/build.gradle.kts"));
132        assert_eq!(
133            package.relative_path(),
134            PathBuf::from("test/build.gradle.kts")
135        );
136        assert_eq!(package.language(), Language::Java);
137        assert!(!package.is_changed());
138        assert_eq!(package.default_publish_command(), "./gradlew publish");
139    }
140
141    #[tokio::test]
142    async fn test_gradle_package_set_changed() {
143        let mut package = GradlePackage::new(
144            Some("test-package".to_string()),
145            Some("1.0.0".to_string()),
146            PathBuf::from("/test/build.gradle.kts"),
147            PathBuf::from("test/build.gradle.kts"),
148        );
149
150        assert!(!package.is_changed());
151        package.set_changed(true);
152        assert!(package.is_changed());
153        package.set_changed(false);
154        assert!(!package.is_changed());
155    }
156
157    #[tokio::test]
158    async fn test_gradle_package_update_version_kts_patch() {
159        let temp_dir = TempDir::new().unwrap();
160        let project_dir = temp_dir.path().join("myproject");
161        fs::create_dir_all(&project_dir).unwrap();
162
163        let build_gradle = project_dir.join("build.gradle.kts");
164        fs::write(
165            &build_gradle,
166            r#"
167plugins {
168    id("java")
169}
170
171group = "com.example"
172version = "1.0.0"
173"#,
174        )
175        .unwrap();
176
177        let mut package = GradlePackage::new(
178            Some("myproject".to_string()),
179            Some("1.0.0".to_string()),
180            build_gradle.clone(),
181            PathBuf::from("myproject/build.gradle.kts"),
182        );
183
184        package.update_version(UpdateType::Patch).await.unwrap();
185
186        let content = read_to_string(&build_gradle).await.unwrap();
187        assert!(content.contains(r#"version = "1.0.1""#));
188
189        temp_dir.close().unwrap();
190    }
191
192    #[tokio::test]
193    async fn test_gradle_package_update_version_kts_minor() {
194        let temp_dir = TempDir::new().unwrap();
195        let project_dir = temp_dir.path().join("myproject");
196        fs::create_dir_all(&project_dir).unwrap();
197
198        let build_gradle = project_dir.join("build.gradle.kts");
199        fs::write(
200            &build_gradle,
201            r#"
202plugins {
203    id("java")
204}
205
206group = "com.example"
207version = "1.0.0"
208"#,
209        )
210        .unwrap();
211
212        let mut package = GradlePackage::new(
213            Some("myproject".to_string()),
214            Some("1.0.0".to_string()),
215            build_gradle.clone(),
216            PathBuf::from("myproject/build.gradle.kts"),
217        );
218
219        package.update_version(UpdateType::Minor).await.unwrap();
220
221        let content = read_to_string(&build_gradle).await.unwrap();
222        assert!(content.contains(r#"version = "1.1.0""#));
223
224        temp_dir.close().unwrap();
225    }
226
227    #[tokio::test]
228    async fn test_gradle_package_update_version_kts_major() {
229        let temp_dir = TempDir::new().unwrap();
230        let project_dir = temp_dir.path().join("myproject");
231        fs::create_dir_all(&project_dir).unwrap();
232
233        let build_gradle = project_dir.join("build.gradle.kts");
234        fs::write(
235            &build_gradle,
236            r#"
237plugins {
238    id("java")
239}
240
241group = "com.example"
242version = "1.0.0"
243"#,
244        )
245        .unwrap();
246
247        let mut package = GradlePackage::new(
248            Some("myproject".to_string()),
249            Some("1.0.0".to_string()),
250            build_gradle.clone(),
251            PathBuf::from("myproject/build.gradle.kts"),
252        );
253
254        package.update_version(UpdateType::Major).await.unwrap();
255
256        let content = read_to_string(&build_gradle).await.unwrap();
257        assert!(content.contains(r#"version = "2.0.0""#));
258
259        temp_dir.close().unwrap();
260    }
261
262    #[tokio::test]
263    async fn test_gradle_package_update_version_groovy() {
264        let temp_dir = TempDir::new().unwrap();
265        let project_dir = temp_dir.path().join("myproject");
266        fs::create_dir_all(&project_dir).unwrap();
267
268        let build_gradle = project_dir.join("build.gradle");
269        fs::write(
270            &build_gradle,
271            r#"
272plugins {
273    id 'java'
274}
275
276group = 'com.example'
277version = '1.0.0'
278"#,
279        )
280        .unwrap();
281
282        let mut package = GradlePackage::new(
283            Some("myproject".to_string()),
284            Some("1.0.0".to_string()),
285            build_gradle.clone(),
286            PathBuf::from("myproject/build.gradle"),
287        );
288
289        package.update_version(UpdateType::Patch).await.unwrap();
290
291        let content = read_to_string(&build_gradle).await.unwrap();
292        assert!(content.contains("version = '1.0.1'"));
293
294        temp_dir.close().unwrap();
295    }
296
297    #[tokio::test]
298    async fn test_gradle_package_update_version_with_fallback() {
299        let temp_dir = TempDir::new().unwrap();
300        let project_dir = temp_dir.path().join("myproject");
301        fs::create_dir_all(&project_dir).unwrap();
302
303        let build_gradle = project_dir.join("build.gradle.kts");
304        fs::write(
305            &build_gradle,
306            r#"
307group = "com.devfive"
308version = project.findProperty("releaseVersion") ?: "1.0.11"
309"#,
310        )
311        .unwrap();
312
313        let mut package = GradlePackage::new(
314            Some("myproject".to_string()),
315            Some("1.0.11".to_string()),
316            build_gradle.clone(),
317            PathBuf::from("myproject/build.gradle.kts"),
318        );
319
320        package.update_version(UpdateType::Patch).await.unwrap();
321
322        let content = read_to_string(&build_gradle).await.unwrap();
323        assert!(content.contains(r#"?: "1.0.12""#));
324
325        temp_dir.close().unwrap();
326    }
327
328    #[test]
329    fn test_gradle_package_dependencies() {
330        let mut package = GradlePackage::new(
331            Some("test-package".to_string()),
332            Some("1.0.0".to_string()),
333            PathBuf::from("/test/build.gradle.kts"),
334            PathBuf::from("test/build.gradle.kts"),
335        );
336
337        // Initially empty
338        assert!(package.dependencies().is_empty());
339
340        // Add dependencies
341        package.add_dependency("core");
342        package.add_dependency("utils");
343
344        let deps = package.dependencies();
345        assert_eq!(deps.len(), 2);
346        assert!(deps.contains("core"));
347        assert!(deps.contains("utils"));
348
349        // Adding duplicate should not increase count
350        package.add_dependency("core");
351        assert_eq!(package.dependencies().len(), 2);
352    }
353
354    #[test]
355    fn test_set_name() {
356        let mut package = GradlePackage::new(
357            None,
358            Some("1.0.0".to_string()),
359            PathBuf::from("/test/build.gradle.kts"),
360            PathBuf::from("build.gradle.kts"),
361        );
362        assert_eq!(package.name(), None);
363        package.set_name("my-project".to_string());
364        assert_eq!(package.name(), Some("my-project"));
365    }
366}