changepacks_csharp/
package.rs1use std::collections::HashSet;
2use std::path::{Path, PathBuf};
3
4use anyhow::Result;
5use async_trait::async_trait;
6use changepacks_core::{Language, Package, UpdateType};
7use changepacks_utils::next_version;
8use tokio::fs::{read_to_string, write};
9
10use crate::xml_utils::update_version_in_xml;
11
12#[derive(Debug)]
13pub struct CSharpPackage {
14 name: Option<String>,
15 version: Option<String>,
16 path: PathBuf,
17 relative_path: PathBuf,
18 is_changed: bool,
19 dependencies: HashSet<String>,
20}
21
22impl CSharpPackage {
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 CSharpPackage {
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 csproj_raw = read_to_string(&self.path).await?;
63 let has_version = self.version.is_some();
64
65 let updated_content = update_version_in_xml(&csproj_raw, &new_version, has_version)?;
66
67 write(&self.path, updated_content).await?;
68 self.version = Some(new_version);
69 Ok(())
70 }
71
72 fn language(&self) -> Language {
73 Language::CSharp
74 }
75
76 fn is_changed(&self) -> bool {
77 self.is_changed
78 }
79
80 fn set_changed(&mut self, changed: bool) {
81 self.is_changed = changed;
82 }
83
84 fn default_publish_command(&self) -> String {
85 "dotnet pack -c Release && dotnet nuget push".to_string()
86 }
87
88 fn dependencies(&self) -> &HashSet<String> {
89 &self.dependencies
90 }
91
92 fn add_dependency(&mut self, dependency: &str) {
93 self.dependencies.insert(dependency.to_string());
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use std::fs;
101 use tempfile::TempDir;
102
103 #[tokio::test]
104 async fn test_new() {
105 let temp_dir = TempDir::new().unwrap();
106 let csproj_path = temp_dir.path().join("Test.csproj");
107 fs::write(
108 &csproj_path,
109 r#"<Project Sdk="Microsoft.NET.Sdk">
110 <PropertyGroup>
111 <Version>1.0.0</Version>
112 </PropertyGroup>
113</Project>
114"#,
115 )
116 .unwrap();
117
118 let package = CSharpPackage::new(
119 Some("Test".to_string()),
120 Some("1.0.0".to_string()),
121 csproj_path.clone(),
122 PathBuf::from("Test.csproj"),
123 );
124
125 assert_eq!(package.name(), Some("Test"));
126 assert_eq!(package.version(), Some("1.0.0"));
127 assert_eq!(package.path(), csproj_path);
128 assert_eq!(package.relative_path(), PathBuf::from("Test.csproj"));
129 assert!(!package.is_changed());
130 assert_eq!(package.language(), Language::CSharp);
131 assert_eq!(
132 package.default_publish_command(),
133 "dotnet pack -c Release && dotnet nuget push"
134 );
135
136 temp_dir.close().unwrap();
137 }
138
139 #[tokio::test]
140 async fn test_set_changed() {
141 let temp_dir = TempDir::new().unwrap();
142 let csproj_path = temp_dir.path().join("Test.csproj");
143 fs::write(
144 &csproj_path,
145 r#"<Project Sdk="Microsoft.NET.Sdk">
146 <PropertyGroup>
147 <Version>1.0.0</Version>
148 </PropertyGroup>
149</Project>
150"#,
151 )
152 .unwrap();
153
154 let mut package = CSharpPackage::new(
155 Some("Test".to_string()),
156 Some("1.0.0".to_string()),
157 csproj_path.clone(),
158 PathBuf::from("Test.csproj"),
159 );
160
161 assert!(!package.is_changed());
162 package.set_changed(true);
163 assert!(package.is_changed());
164 package.set_changed(false);
165 assert!(!package.is_changed());
166
167 temp_dir.close().unwrap();
168 }
169
170 #[tokio::test]
171 async fn test_update_version_patch() {
172 let temp_dir = TempDir::new().unwrap();
173 let csproj_path = temp_dir.path().join("Test.csproj");
174 fs::write(
175 &csproj_path,
176 r#"<Project Sdk="Microsoft.NET.Sdk">
177 <PropertyGroup>
178 <Version>1.0.0</Version>
179 </PropertyGroup>
180</Project>
181"#,
182 )
183 .unwrap();
184
185 let mut package = CSharpPackage::new(
186 Some("Test".to_string()),
187 Some("1.0.0".to_string()),
188 csproj_path.clone(),
189 PathBuf::from("Test.csproj"),
190 );
191
192 package.update_version(UpdateType::Patch).await.unwrap();
193
194 let content = fs::read_to_string(&csproj_path).unwrap();
195 assert!(content.contains("<Version>1.0.1</Version>"));
196
197 temp_dir.close().unwrap();
198 }
199
200 #[tokio::test]
201 async fn test_update_version_minor() {
202 let temp_dir = TempDir::new().unwrap();
203 let csproj_path = temp_dir.path().join("Test.csproj");
204 fs::write(
205 &csproj_path,
206 r#"<Project Sdk="Microsoft.NET.Sdk">
207 <PropertyGroup>
208 <Version>1.0.0</Version>
209 </PropertyGroup>
210</Project>
211"#,
212 )
213 .unwrap();
214
215 let mut package = CSharpPackage::new(
216 Some("Test".to_string()),
217 Some("1.0.0".to_string()),
218 csproj_path.clone(),
219 PathBuf::from("Test.csproj"),
220 );
221
222 package.update_version(UpdateType::Minor).await.unwrap();
223
224 let content = fs::read_to_string(&csproj_path).unwrap();
225 assert!(content.contains("<Version>1.1.0</Version>"));
226
227 temp_dir.close().unwrap();
228 }
229
230 #[tokio::test]
231 async fn test_update_version_major() {
232 let temp_dir = TempDir::new().unwrap();
233 let csproj_path = temp_dir.path().join("Test.csproj");
234 fs::write(
235 &csproj_path,
236 r#"<Project Sdk="Microsoft.NET.Sdk">
237 <PropertyGroup>
238 <Version>1.0.0</Version>
239 </PropertyGroup>
240</Project>
241"#,
242 )
243 .unwrap();
244
245 let mut package = CSharpPackage::new(
246 Some("Test".to_string()),
247 Some("1.0.0".to_string()),
248 csproj_path.clone(),
249 PathBuf::from("Test.csproj"),
250 );
251
252 package.update_version(UpdateType::Major).await.unwrap();
253
254 let content = fs::read_to_string(&csproj_path).unwrap();
255 assert!(content.contains("<Version>2.0.0</Version>"));
256
257 temp_dir.close().unwrap();
258 }
259
260 #[tokio::test]
261 async fn test_update_version_preserves_other_elements() {
262 let temp_dir = TempDir::new().unwrap();
263 let csproj_path = temp_dir.path().join("Test.csproj");
264 let original_content = r#"<Project Sdk="Microsoft.NET.Sdk">
265 <PropertyGroup>
266 <OutputType>Exe</OutputType>
267 <TargetFramework>net8.0</TargetFramework>
268 <Version>1.0.0</Version>
269 <PackageId>MyPackage</PackageId>
270 </PropertyGroup>
271</Project>
272"#;
273 fs::write(&csproj_path, original_content).unwrap();
274
275 let mut package = CSharpPackage::new(
276 Some("Test".to_string()),
277 Some("1.0.0".to_string()),
278 csproj_path.clone(),
279 PathBuf::from("Test.csproj"),
280 );
281
282 package.update_version(UpdateType::Patch).await.unwrap();
283
284 let content = fs::read_to_string(&csproj_path).unwrap();
285 assert!(content.contains("<Version>1.0.1</Version>"));
286 assert!(content.contains("<OutputType>Exe</OutputType>"));
287 assert!(content.contains("<TargetFramework>net8.0</TargetFramework>"));
288 assert!(content.contains("<PackageId>MyPackage</PackageId>"));
289
290 temp_dir.close().unwrap();
291 }
292
293 #[test]
294 fn test_dependencies() {
295 let mut package = CSharpPackage::new(
296 Some("Test".to_string()),
297 Some("1.0.0".to_string()),
298 PathBuf::from("/test/Test.csproj"),
299 PathBuf::from("test/Test.csproj"),
300 );
301
302 assert!(package.dependencies().is_empty());
304
305 package.add_dependency("Newtonsoft.Json");
307 package.add_dependency("CoreLib");
308
309 let deps = package.dependencies();
310 assert_eq!(deps.len(), 2);
311 assert!(deps.contains("Newtonsoft.Json"));
312 assert!(deps.contains("CoreLib"));
313
314 package.add_dependency("Newtonsoft.Json");
316 assert_eq!(package.dependencies().len(), 2);
317 }
318}