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 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_package_new() {
118        let package = GradlePackage::new(
119            Some("test-package".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!(package.name(), Some("test-package"));
126        assert_eq!(package.version(), Some("1.0.0"));
127        assert_eq!(package.path(), PathBuf::from("/test/build.gradle.kts"));
128        assert_eq!(
129            package.relative_path(),
130            PathBuf::from("test/build.gradle.kts")
131        );
132        assert_eq!(package.language(), Language::Java);
133        assert!(!package.is_changed());
134        assert_eq!(package.default_publish_command(), "./gradlew publish");
135    }
136
137    #[tokio::test]
138    async fn test_gradle_package_set_changed() {
139        let mut package = GradlePackage::new(
140            Some("test-package".to_string()),
141            Some("1.0.0".to_string()),
142            PathBuf::from("/test/build.gradle.kts"),
143            PathBuf::from("test/build.gradle.kts"),
144        );
145
146        assert!(!package.is_changed());
147        package.set_changed(true);
148        assert!(package.is_changed());
149        package.set_changed(false);
150        assert!(!package.is_changed());
151    }
152
153    #[tokio::test]
154    async fn test_gradle_package_update_version_kts_patch() {
155        let temp_dir = TempDir::new().unwrap();
156        let project_dir = temp_dir.path().join("myproject");
157        fs::create_dir_all(&project_dir).unwrap();
158
159        let build_gradle = project_dir.join("build.gradle.kts");
160        fs::write(
161            &build_gradle,
162            r#"
163plugins {
164    id("java")
165}
166
167group = "com.example"
168version = "1.0.0"
169"#,
170        )
171        .unwrap();
172
173        let mut package = GradlePackage::new(
174            Some("myproject".to_string()),
175            Some("1.0.0".to_string()),
176            build_gradle.clone(),
177            PathBuf::from("myproject/build.gradle.kts"),
178        );
179
180        package.update_version(UpdateType::Patch).await.unwrap();
181
182        let content = read_to_string(&build_gradle).await.unwrap();
183        assert!(content.contains(r#"version = "1.0.1""#));
184
185        temp_dir.close().unwrap();
186    }
187
188    #[tokio::test]
189    async fn test_gradle_package_update_version_kts_minor() {
190        let temp_dir = TempDir::new().unwrap();
191        let project_dir = temp_dir.path().join("myproject");
192        fs::create_dir_all(&project_dir).unwrap();
193
194        let build_gradle = project_dir.join("build.gradle.kts");
195        fs::write(
196            &build_gradle,
197            r#"
198plugins {
199    id("java")
200}
201
202group = "com.example"
203version = "1.0.0"
204"#,
205        )
206        .unwrap();
207
208        let mut package = GradlePackage::new(
209            Some("myproject".to_string()),
210            Some("1.0.0".to_string()),
211            build_gradle.clone(),
212            PathBuf::from("myproject/build.gradle.kts"),
213        );
214
215        package.update_version(UpdateType::Minor).await.unwrap();
216
217        let content = read_to_string(&build_gradle).await.unwrap();
218        assert!(content.contains(r#"version = "1.1.0""#));
219
220        temp_dir.close().unwrap();
221    }
222
223    #[tokio::test]
224    async fn test_gradle_package_update_version_kts_major() {
225        let temp_dir = TempDir::new().unwrap();
226        let project_dir = temp_dir.path().join("myproject");
227        fs::create_dir_all(&project_dir).unwrap();
228
229        let build_gradle = project_dir.join("build.gradle.kts");
230        fs::write(
231            &build_gradle,
232            r#"
233plugins {
234    id("java")
235}
236
237group = "com.example"
238version = "1.0.0"
239"#,
240        )
241        .unwrap();
242
243        let mut package = GradlePackage::new(
244            Some("myproject".to_string()),
245            Some("1.0.0".to_string()),
246            build_gradle.clone(),
247            PathBuf::from("myproject/build.gradle.kts"),
248        );
249
250        package.update_version(UpdateType::Major).await.unwrap();
251
252        let content = read_to_string(&build_gradle).await.unwrap();
253        assert!(content.contains(r#"version = "2.0.0""#));
254
255        temp_dir.close().unwrap();
256    }
257
258    #[tokio::test]
259    async fn test_gradle_package_update_version_groovy() {
260        let temp_dir = TempDir::new().unwrap();
261        let project_dir = temp_dir.path().join("myproject");
262        fs::create_dir_all(&project_dir).unwrap();
263
264        let build_gradle = project_dir.join("build.gradle");
265        fs::write(
266            &build_gradle,
267            r#"
268plugins {
269    id 'java'
270}
271
272group = 'com.example'
273version = '1.0.0'
274"#,
275        )
276        .unwrap();
277
278        let mut package = GradlePackage::new(
279            Some("myproject".to_string()),
280            Some("1.0.0".to_string()),
281            build_gradle.clone(),
282            PathBuf::from("myproject/build.gradle"),
283        );
284
285        package.update_version(UpdateType::Patch).await.unwrap();
286
287        let content = read_to_string(&build_gradle).await.unwrap();
288        assert!(content.contains("version = '1.0.1'"));
289
290        temp_dir.close().unwrap();
291    }
292
293    #[tokio::test]
294    async fn test_gradle_package_update_version_with_fallback() {
295        let temp_dir = TempDir::new().unwrap();
296        let project_dir = temp_dir.path().join("myproject");
297        fs::create_dir_all(&project_dir).unwrap();
298
299        let build_gradle = project_dir.join("build.gradle.kts");
300        fs::write(
301            &build_gradle,
302            r#"
303group = "com.devfive"
304version = project.findProperty("releaseVersion") ?: "1.0.11"
305"#,
306        )
307        .unwrap();
308
309        let mut package = GradlePackage::new(
310            Some("myproject".to_string()),
311            Some("1.0.11".to_string()),
312            build_gradle.clone(),
313            PathBuf::from("myproject/build.gradle.kts"),
314        );
315
316        package.update_version(UpdateType::Patch).await.unwrap();
317
318        let content = read_to_string(&build_gradle).await.unwrap();
319        assert!(content.contains(r#"?: "1.0.12""#));
320
321        temp_dir.close().unwrap();
322    }
323
324    #[test]
325    fn test_gradle_package_dependencies() {
326        let mut package = GradlePackage::new(
327            Some("test-package".to_string()),
328            Some("1.0.0".to_string()),
329            PathBuf::from("/test/build.gradle.kts"),
330            PathBuf::from("test/build.gradle.kts"),
331        );
332
333        // Initially empty
334        assert!(package.dependencies().is_empty());
335
336        // Add dependencies
337        package.add_dependency("core");
338        package.add_dependency("utils");
339
340        let deps = package.dependencies();
341        assert_eq!(deps.len(), 2);
342        assert!(deps.contains("core"));
343        assert!(deps.contains("utils"));
344
345        // Adding duplicate should not increase count
346        package.add_dependency("core");
347        assert_eq!(package.dependencies().len(), 2);
348    }
349}