standard_version/
gradle.rs1use crate::version_file::{VersionFile, VersionFileError};
9
10#[derive(Debug, Clone, Copy)]
12pub struct GradleVersionFile;
13
14impl VersionFile for GradleVersionFile {
15 fn name(&self) -> &str {
16 "gradle.properties"
17 }
18
19 fn filenames(&self) -> &[&str] {
20 &["gradle.properties"]
21 }
22
23 fn detect(&self, content: &str) -> bool {
24 content
25 .lines()
26 .any(|line| line.starts_with("VERSION_NAME="))
27 }
28
29 fn read_version(&self, content: &str) -> Option<String> {
30 for line in content.lines() {
31 if let Some(value) = line.strip_prefix("VERSION_NAME=") {
32 let trimmed = value.trim();
33 if !trimmed.is_empty() {
34 return Some(trimmed.to_string());
35 }
36 }
37 }
38 None
39 }
40
41 fn write_version(&self, content: &str, new_version: &str) -> Result<String, VersionFileError> {
42 let mut result = String::new();
43 let mut replaced = false;
44
45 for line in content.lines() {
46 if !replaced && line.starts_with("VERSION_NAME=") {
47 result.push_str(&format!("VERSION_NAME={new_version}"));
48 result.push('\n');
49 replaced = true;
50 continue;
51 }
52
53 if let Some(old_code) = line.strip_prefix("VERSION_CODE=") {
54 let old_code = old_code.trim();
55 let code_num: u64 = old_code.parse().unwrap_or(0);
56 result.push_str(&format!("VERSION_CODE={}", code_num + 1));
57 result.push('\n');
58 continue;
59 }
60
61 result.push_str(line);
62 result.push('\n');
63 }
64
65 if !replaced {
66 return Err(VersionFileError::NoVersionField);
67 }
68
69 if !content.ends_with('\n') && result.ends_with('\n') {
71 result.pop();
72 }
73
74 Ok(result)
75 }
76
77 fn extra_info(&self, old_content: &str, new_content: &str) -> Option<String> {
78 let old_code = extract_version_code(old_content);
79 let new_code = extract_version_code(new_content);
80
81 match (old_code, new_code) {
82 (Some(old), Some(new)) => Some(format!("VERSION_CODE: {old} \u{2192} {new}")),
83 _ => None,
84 }
85 }
86}
87
88fn extract_version_code(content: &str) -> Option<u64> {
90 for line in content.lines() {
91 if let Some(value) = line.strip_prefix("VERSION_CODE=") {
92 return value.trim().parse().ok();
93 }
94 }
95 None
96}
97
98#[cfg(test)]
103mod tests {
104 use super::*;
105
106 const BASIC_GRADLE: &str = "\
107VERSION_NAME=1.2.3
108VERSION_CODE=42
109org.gradle.jvmargs=-Xmx2048m
110";
111
112 const GRADLE_NO_CODE: &str = "\
113VERSION_NAME=1.2.3
114org.gradle.jvmargs=-Xmx2048m
115";
116
117 #[test]
120 fn detect_positive() {
121 assert!(GradleVersionFile.detect(BASIC_GRADLE));
122 }
123
124 #[test]
125 fn detect_negative() {
126 let content = "org.gradle.jvmargs=-Xmx2048m\n";
127 assert!(!GradleVersionFile.detect(content));
128 }
129
130 #[test]
131 fn detect_negative_version_name_in_comment() {
132 let content = "# VERSION_NAME=1.0.0\norg.gradle.jvmargs=-Xmx2048m\n";
133 assert!(!GradleVersionFile.detect(content));
134 }
135
136 #[test]
139 fn read_version_basic() {
140 assert_eq!(
141 GradleVersionFile.read_version(BASIC_GRADLE),
142 Some("1.2.3".to_string()),
143 );
144 }
145
146 #[test]
147 fn read_version_missing() {
148 let content = "org.gradle.jvmargs=-Xmx2048m\n";
149 assert_eq!(GradleVersionFile.read_version(content), None);
150 }
151
152 #[test]
155 fn write_version_updates_name_and_code() {
156 let result = GradleVersionFile
157 .write_version(BASIC_GRADLE, "2.0.0")
158 .unwrap();
159 assert!(result.contains("VERSION_NAME=2.0.0"));
160 assert!(result.contains("VERSION_CODE=43"));
161 assert!(result.contains("org.gradle.jvmargs=-Xmx2048m"));
162 }
163
164 #[test]
165 fn write_version_no_code_stays_without() {
166 let result = GradleVersionFile
167 .write_version(GRADLE_NO_CODE, "2.0.0")
168 .unwrap();
169 assert!(result.contains("VERSION_NAME=2.0.0"));
170 assert!(!result.contains("VERSION_CODE"));
171 }
172
173 #[test]
174 fn write_version_no_field_returns_error() {
175 let content = "org.gradle.jvmargs=-Xmx2048m\n";
176 let err = GradleVersionFile.write_version(content, "1.0.0");
177 assert!(err.is_err());
178 }
179
180 #[test]
181 fn write_version_preserves_no_trailing_newline() {
182 let content = "VERSION_NAME=0.1.0";
183 let result = GradleVersionFile.write_version(content, "0.2.0").unwrap();
184 assert!(!result.ends_with('\n'));
185 assert!(result.contains("VERSION_NAME=0.2.0"));
186 }
187
188 #[test]
191 fn extra_info_reports_version_code_change() {
192 let old = BASIC_GRADLE;
193 let new_content = GradleVersionFile.write_version(old, "2.0.0").unwrap();
194 let info = GradleVersionFile.extra_info(old, &new_content);
195 assert_eq!(info, Some("VERSION_CODE: 42 \u{2192} 43".to_string()));
196 }
197
198 #[test]
199 fn extra_info_none_when_no_version_code() {
200 let old = GRADLE_NO_CODE;
201 let new_content = GradleVersionFile.write_version(old, "2.0.0").unwrap();
202 let info = GradleVersionFile.extra_info(old, &new_content);
203 assert_eq!(info, None);
204 }
205
206 #[test]
209 fn integration_roundtrip() {
210 let dir = tempfile::tempdir().unwrap();
211 let path = dir.path().join("gradle.properties");
212 std::fs::write(&path, BASIC_GRADLE).unwrap();
213
214 let content = std::fs::read_to_string(&path).unwrap();
215 assert!(GradleVersionFile.detect(&content));
216 assert_eq!(
217 GradleVersionFile.read_version(&content),
218 Some("1.2.3".to_string()),
219 );
220
221 let updated = GradleVersionFile.write_version(&content, "3.0.0").unwrap();
222 std::fs::write(&path, &updated).unwrap();
223
224 let final_content = std::fs::read_to_string(&path).unwrap();
225 assert_eq!(
226 GradleVersionFile.read_version(&final_content),
227 Some("3.0.0".to_string()),
228 );
229 assert!(final_content.contains("VERSION_CODE=43"));
230 }
231
232 #[test]
233 fn integration_roundtrip_no_code() {
234 let dir = tempfile::tempdir().unwrap();
235 let path = dir.path().join("gradle.properties");
236 std::fs::write(&path, GRADLE_NO_CODE).unwrap();
237
238 let content = std::fs::read_to_string(&path).unwrap();
239 let updated = GradleVersionFile.write_version(&content, "3.0.0").unwrap();
240 std::fs::write(&path, &updated).unwrap();
241
242 let final_content = std::fs::read_to_string(&path).unwrap();
243 assert_eq!(
244 GradleVersionFile.read_version(&final_content),
245 Some("3.0.0".to_string()),
246 );
247 assert!(!final_content.contains("VERSION_CODE"));
248 }
249}