1use super::*;
2
3pub(crate) fn set_item_string_preserving_decor(slot: &mut Item, new_string: &str) {
12 let mut replacement: Item = value(new_string);
13 if let Some(old_value) = slot.as_value()
14 && let Some(new_value) = replacement.as_value_mut()
15 {
16 *new_value.decor_mut() = old_value.decor().clone();
17 }
18 *slot = replacement;
19}
20
21fn read_root_version(doc: &DocumentMut) -> Result<String, SyncError> {
34 let workspace_version: Option<&str> = doc
35 .get("workspace")
36 .and_then(|workspace: &Item| workspace.get("package"))
37 .and_then(|package: &Item| package.get("version"))
38 .and_then(|version_item: &Item| version_item.as_str());
39 let package_version: Option<&str> = doc
40 .get("package")
41 .and_then(|package: &Item| package.get("version"))
42 .and_then(|version_item: &Item| version_item.as_str());
43 workspace_version
44 .or(package_version)
45 .map(|version: &str| version.to_string())
46 .ok_or_else(|| SyncError::WorkspaceVersionMissing("Cargo.toml".to_string()))
47}
48
49fn read_workspace_members(doc: &DocumentMut) -> Result<Vec<String>, SyncError> {
59 let members: Vec<String> = doc
60 .get("workspace")
61 .and_then(|workspace: &Item| workspace.get("members"))
62 .and_then(|members_item: &Item| members_item.as_array())
63 .ok_or_else(|| SyncError::WorkspaceMembersMissing("Cargo.toml".to_string()))?
64 .iter()
65 .filter_map(|member: &TomlEditValue| member.as_str().map(|s: &str| s.to_string()))
66 .collect();
67 Ok(members)
68}
69
70fn read_member_crate_name(doc: &DocumentMut) -> Result<String, SyncError> {
80 let name: String = doc
81 .get("package")
82 .and_then(|package: &Item| package.get("name"))
83 .and_then(|name_item: &Item| name_item.as_str())
84 .ok_or_else(|| SyncError::MemberNameMissing("Cargo.toml".to_string()))?
85 .to_string();
86 Ok(name)
87}
88
89fn find_dep_alias_for_member_path(deps: &dyn TableLike, member_path: &str) -> Option<String> {
101 for (alias, entry) in deps.iter() {
102 if let Some(path) = entry
103 .get("path")
104 .and_then(|path_item: &Item| path_item.as_str())
105 && path == member_path
106 {
107 return Some(alias.to_string());
108 }
109 }
110 None
111}
112
113fn scan_dep_entry(doc: &DocumentMut, member_path: &str) -> Option<(String, Option<String>)> {
126 let deps: &dyn TableLike = doc
127 .get("workspace")
128 .and_then(|workspace: &Item| workspace.get("dependencies"))
129 .and_then(|deps_item: &Item| deps_item.as_table_like())?;
130 let current_alias: String = find_dep_alias_for_member_path(deps, member_path)?;
131 let existing_version: Option<String> = deps
132 .get(¤t_alias)
133 .and_then(|entry: &Item| entry.get("version"))
134 .and_then(|version_item: &Item| version_item.as_str())
135 .map(|version: &str| version.to_string());
136 Some((current_alias, existing_version))
137}
138
139fn rewrite_entry_version(deps: &mut dyn TableLike, current_alias: &str, workspace_version: &str) {
148 let Some(entry) = deps.get_mut(current_alias) else {
149 return;
150 };
151 match entry.get_mut("version") {
152 Some(version_slot) => set_item_string_preserving_decor(version_slot, workspace_version),
153 None => {
154 if let Some(entry_table) = entry.as_table_like_mut() {
155 entry_table.insert("version", value(workspace_version));
156 }
157 }
158 }
159}
160
161pub async fn execute_sync(manifest_path: &str) -> Result<SyncReport, SyncError> {
195 let path: &Path = Path::new(manifest_path);
196 let content: String = read_to_string(path).await?;
197 let mut doc: DocumentMut = content.parse().map_err(|_| SyncError::ManifestParseError)?;
198 let workspace_version: String = read_root_version(&doc)?;
199 let members: Vec<String> = read_workspace_members(&doc)?;
200 if members.is_empty() {
201 log::info!("sync: no workspace members, nothing to do");
202 return Ok(SyncReport {
203 workspace_version,
204 renamed_entries: Vec::new(),
205 versioned_entries: Vec::new(),
206 file_changed: false,
207 });
208 }
209 let mut renamed_entries: Vec<(String, String)> = Vec::new();
210 let mut versioned_entries: Vec<(String, String)> = Vec::new();
211 let mut needs_rewrite: bool = false;
212 for member_path in &members {
213 let member_manifest_path: PathBuf = path
214 .parent()
215 .unwrap_or_else(|| Path::new("."))
216 .join(member_path)
217 .join("Cargo.toml");
218 if !member_manifest_path.exists() {
219 return Err(SyncError::MemberManifestMissing(
220 member_manifest_path.display().to_string(),
221 ));
222 }
223 let member_content: String = read_to_string(&member_manifest_path).await?;
224 let member_doc: DocumentMut = member_content
225 .parse()
226 .map_err(|_| SyncError::ManifestParseError)?;
227 let canonical_alias: String = read_member_crate_name(&member_doc)?;
228 let (current_alias, existing_version): (String, Option<String>) =
229 match scan_dep_entry(&doc, member_path) {
230 Some(scanned) => scanned,
231 None => {
232 log::info!(
233 "sync: {} -> no [workspace.dependencies] entry, skipping",
234 member_path
235 );
236 continue;
237 }
238 };
239 let alias_needs_rename: bool = current_alias != canonical_alias;
240 let version_needs_rewrite: bool =
241 existing_version.as_deref() != Some(workspace_version.as_str());
242 if !alias_needs_rename && !version_needs_rewrite {
243 continue;
244 }
245 needs_rewrite = true;
246 let deps: &mut dyn TableLike = doc
247 .get_mut("workspace")
248 .and_then(|workspace: &mut Item| workspace.get_mut("dependencies"))
249 .and_then(|deps_item: &mut Item| deps_item.as_table_like_mut())
250 .ok_or(SyncError::ManifestParseError)?;
251 rewrite_entry_version(deps, ¤t_alias, &workspace_version);
252 if alias_needs_rename {
253 if let Some(entry) = deps.remove(¤t_alias) {
254 deps.insert(&canonical_alias, entry);
255 }
256 log::info!(
257 "sync: {} renamed {} -> {}",
258 member_path,
259 current_alias,
260 canonical_alias
261 );
262 renamed_entries.push((current_alias, canonical_alias.clone()));
263 } else {
264 log::info!(
265 "sync: {} -> {} v{}",
266 member_path,
267 canonical_alias,
268 workspace_version
269 );
270 }
271 versioned_entries.push((member_path.clone(), canonical_alias));
272 }
273 let file_changed: bool = needs_rewrite;
274 if file_changed {
275 write(path, doc.to_string()).await?;
276 log::info!(
277 "sync: wrote {} entries to v{}",
278 versioned_entries.len(),
279 workspace_version
280 );
281 } else {
282 log::info!("sync: already in sync: v{}", workspace_version);
283 }
284 Ok(SyncReport {
285 workspace_version,
286 renamed_entries,
287 versioned_entries,
288 file_changed,
289 })
290}