|
| 1 | +//! Dependency entry building functions |
| 2 | +
|
| 3 | +use crate::cargo::unify_types::UnifiedDep; |
| 4 | +use toml_edit::{InlineTable, Item, Value}; |
| 5 | + |
| 6 | +use super::fields::build_feature_array; |
| 7 | + |
| 8 | +/// Build a dependency entry for [workspace.dependencies] |
| 9 | +/// |
| 10 | +/// Returns: |
| 11 | +/// - `Item::Value(Value::String)` for simple case (version only) |
| 12 | +/// - `Item::Value(Value::InlineTable)` for complex case (features, path, etc.) |
| 13 | +/// |
| 14 | +/// # Examples |
| 15 | +/// |
| 16 | +/// ```ignore |
| 17 | +/// let dep = UnifiedDep { |
| 18 | +/// name: "serde".to_string(), |
| 19 | +/// version_req: "1.0".parse().unwrap(), |
| 20 | +/// features: vec![], |
| 21 | +/// default_features: true, |
| 22 | +/// used_by: vec!["crate1".to_string()], |
| 23 | +/// target: None, |
| 24 | +/// path: None, |
| 25 | +/// }; |
| 26 | +/// let entry = build_dep_entry(&dep); |
| 27 | +/// // Result: Item::Value(Value::String("1.0")) |
| 28 | +/// ``` |
| 29 | +pub fn build_dep_entry(dep: &UnifiedDep) -> Item { |
| 30 | + // Simple case: just version, no features, defaults enabled, no path |
| 31 | + if dep.features.is_empty() && dep.default_features && dep.path.is_none() { |
| 32 | + let mut value = Value::from(dep.version_req.to_string()); |
| 33 | + value.decor_mut().set_suffix(" #unified"); |
| 34 | + return Item::Value(value); |
| 35 | + } |
| 36 | + |
| 37 | + // Complex case: use inline table |
| 38 | + let mut table = InlineTable::new(); |
| 39 | + |
| 40 | + // Add path if present (for workspace member deps) |
| 41 | + if let Some(ref path) = dep.path { |
| 42 | + table.insert("path", Value::from(path.display().to_string())); |
| 43 | + // Also include version for publishable workspace members |
| 44 | + // This allows `cargo publish` to work while using paths for local dev |
| 45 | + if dep.version_req.to_string() != "*" { |
| 46 | + table.insert("version", Value::from(dep.version_req.to_string())); |
| 47 | + } |
| 48 | + } else { |
| 49 | + table.insert("version", Value::from(dep.version_req.to_string())); |
| 50 | + } |
| 51 | + |
| 52 | + // Add default-features flag if false |
| 53 | + if !dep.default_features { |
| 54 | + table.insert("default-features", Value::from(false)); |
| 55 | + } |
| 56 | + |
| 57 | + // Add features if any |
| 58 | + if !dep.features.is_empty() { |
| 59 | + table.insert("features", build_feature_array(&dep.features)); |
| 60 | + } |
| 61 | + |
| 62 | + // Add #unified comment marker |
| 63 | + let mut value = Value::InlineTable(table); |
| 64 | + value.decor_mut().set_suffix(" #unified"); |
| 65 | + |
| 66 | + Item::Value(value) |
| 67 | +} |
| 68 | + |
| 69 | +/// Build a workspace-inherited dependency entry |
| 70 | +/// |
| 71 | +/// Used in member manifests to reference [workspace.dependencies]. |
| 72 | +/// |
| 73 | +/// # Arguments |
| 74 | +/// |
| 75 | +/// * `local_features` - Additional features to enable locally (beyond workspace features) |
| 76 | +/// * `is_optional` - Whether the dependency is optional |
| 77 | +/// |
| 78 | +/// # Returns |
| 79 | +/// |
| 80 | +/// `{ workspace = true }` with optional fields and `#unified` comment marker |
| 81 | +pub fn build_workspace_dep_entry(local_features: Option<Vec<String>>, is_optional: bool) -> Item { |
| 82 | + let mut table = InlineTable::new(); |
| 83 | + table.insert("workspace", Value::from(true)); |
| 84 | + |
| 85 | + // Add local features if any |
| 86 | + if let Some(features) = local_features |
| 87 | + && !features.is_empty() |
| 88 | + { |
| 89 | + table.insert("features", build_feature_array(&features)); |
| 90 | + } |
| 91 | + |
| 92 | + // Add optional if needed |
| 93 | + if is_optional { |
| 94 | + table.insert("optional", Value::from(true)); |
| 95 | + } |
| 96 | + |
| 97 | + // Add #unified comment marker to track what was modified by unify |
| 98 | + let mut value = Value::InlineTable(table); |
| 99 | + value.decor_mut().set_suffix(" #unified"); |
| 100 | + |
| 101 | + Item::Value(value) |
| 102 | +} |
| 103 | + |
| 104 | +/// Build a transitive dependency entry for pinning |
| 105 | +/// |
| 106 | +/// Used for workspace-hack replacement (dev-dependencies with workspace = true and features). |
| 107 | +/// |
| 108 | +/// # Arguments |
| 109 | +/// |
| 110 | +/// * `features` - Features to enable for the transitive dependency |
| 111 | +pub fn build_transitive_entry(features: &[String]) -> Item { |
| 112 | + let mut table = InlineTable::new(); |
| 113 | + table.insert("workspace", Value::from(true)); |
| 114 | + |
| 115 | + if !features.is_empty() { |
| 116 | + table.insert("features", build_feature_array(features)); |
| 117 | + } |
| 118 | + |
| 119 | + // Add #unified comment marker |
| 120 | + let mut value = Value::InlineTable(table); |
| 121 | + value.decor_mut().set_suffix(" #unified"); |
| 122 | + |
| 123 | + Item::Value(value) |
| 124 | +} |
| 125 | + |
| 126 | +/// Build a versioned dependency entry for [workspace.dependencies] |
| 127 | +/// |
| 128 | +/// Used when adding transitive dependencies to workspace.dependencies. |
| 129 | +/// Creates entries like: `dep = { version = "1.0", features = ["foo"] }` |
| 130 | +/// |
| 131 | +/// # Arguments |
| 132 | +/// |
| 133 | +/// * `version` - The semver version to use |
| 134 | +/// * `features` - Features to enable |
| 135 | +pub fn build_versioned_dep_entry(version: &semver::Version, features: &[String]) -> Item { |
| 136 | + // Simple case: just version, no features |
| 137 | + if features.is_empty() { |
| 138 | + let mut value = Value::from(format!("^{}", version)); |
| 139 | + value.decor_mut().set_suffix(" #unified"); |
| 140 | + return Item::Value(value); |
| 141 | + } |
| 142 | + |
| 143 | + // Complex case: version + features |
| 144 | + let mut table = InlineTable::new(); |
| 145 | + table.insert("version", Value::from(format!("^{}", version))); |
| 146 | + table.insert("features", build_feature_array(features)); |
| 147 | + |
| 148 | + // Add #unified comment marker |
| 149 | + let mut value = Value::InlineTable(table); |
| 150 | + value.decor_mut().set_suffix(" #unified"); |
| 151 | + |
| 152 | + Item::Value(value) |
| 153 | +} |
| 154 | + |
| 155 | +#[cfg(test)] |
| 156 | +mod tests { |
| 157 | + use super::*; |
| 158 | + use crate::cargo::unify_types::UnifiedDep; |
| 159 | + use std::path::PathBuf; |
| 160 | + |
| 161 | + use super::super::fields::{extract_features, extract_path, has_default_features, has_path}; |
| 162 | + use super::super::transform::{extract_version, is_inline_table_dep, is_optional, is_simple_string_dep}; |
| 163 | + use super::super::workspace_ref::is_workspace_dep; |
| 164 | + |
| 165 | + fn create_test_dep(name: &str, version: &str) -> UnifiedDep { |
| 166 | + UnifiedDep { |
| 167 | + name: name.to_string(), |
| 168 | + version_req: version.parse().unwrap(), |
| 169 | + features: vec![], |
| 170 | + default_features: true, |
| 171 | + used_by: vec!["test-crate".to_string()], |
| 172 | + target: None, |
| 173 | + path: None, |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + #[test] |
| 178 | + fn test_build_dep_entry_simple() { |
| 179 | + let dep = create_test_dep("serde", "1.0"); |
| 180 | + let entry = build_dep_entry(&dep); |
| 181 | + assert!(is_simple_string_dep(&entry)); |
| 182 | + // Version requirement parsing adds the caret operator |
| 183 | + assert_eq!(extract_version(&entry).unwrap(), "^1.0"); |
| 184 | + } |
| 185 | + |
| 186 | + #[test] |
| 187 | + fn test_build_dep_entry_with_features() { |
| 188 | + let mut dep = create_test_dep("serde", "1.0"); |
| 189 | + dep.features = vec!["derive".to_string()]; |
| 190 | + |
| 191 | + let entry = build_dep_entry(&dep); |
| 192 | + assert!(is_inline_table_dep(&entry)); |
| 193 | + let features = extract_features(&entry).unwrap(); |
| 194 | + assert_eq!(features, vec!["derive"]); |
| 195 | + } |
| 196 | + |
| 197 | + #[test] |
| 198 | + fn test_build_dep_entry_with_path() { |
| 199 | + let mut dep = create_test_dep("local-crate", "0.1.0"); |
| 200 | + dep.path = Some(PathBuf::from("../local-crate")); |
| 201 | + |
| 202 | + let entry = build_dep_entry(&dep); |
| 203 | + assert!(is_inline_table_dep(&entry)); |
| 204 | + assert!(has_path(&entry)); |
| 205 | + assert_eq!(extract_path(&entry).unwrap(), "../local-crate"); |
| 206 | + } |
| 207 | + |
| 208 | + #[test] |
| 209 | + fn test_build_dep_entry_no_default_features() { |
| 210 | + let mut dep = create_test_dep("tokio", "1.0"); |
| 211 | + dep.default_features = false; |
| 212 | + |
| 213 | + let entry = build_dep_entry(&dep); |
| 214 | + assert!(is_inline_table_dep(&entry)); |
| 215 | + assert!(!has_default_features(&entry)); |
| 216 | + } |
| 217 | + |
| 218 | + #[test] |
| 219 | + fn test_build_workspace_dep_entry_simple() { |
| 220 | + let entry = build_workspace_dep_entry(None, false); |
| 221 | + assert!(is_workspace_dep(&entry)); |
| 222 | + assert!(is_inline_table_dep(&entry)); |
| 223 | + } |
| 224 | + |
| 225 | + #[test] |
| 226 | + fn test_build_workspace_dep_entry_with_features() { |
| 227 | + let entry = build_workspace_dep_entry(Some(vec!["extra".to_string()]), false); |
| 228 | + assert!(is_workspace_dep(&entry)); |
| 229 | + let features = extract_features(&entry).unwrap(); |
| 230 | + assert_eq!(features, vec!["extra"]); |
| 231 | + } |
| 232 | + |
| 233 | + #[test] |
| 234 | + fn test_build_workspace_dep_entry_optional() { |
| 235 | + let entry = build_workspace_dep_entry(None, true); |
| 236 | + assert!(is_workspace_dep(&entry)); |
| 237 | + assert!(is_optional(&entry)); |
| 238 | + } |
| 239 | + |
| 240 | + #[test] |
| 241 | + fn test_build_transitive_entry() { |
| 242 | + let features = vec!["feature1".to_string(), "feature2".to_string()]; |
| 243 | + let entry = build_transitive_entry(&features); |
| 244 | + assert!(is_workspace_dep(&entry)); |
| 245 | + let extracted = extract_features(&entry).unwrap(); |
| 246 | + assert_eq!(extracted, features); |
| 247 | + } |
| 248 | +} |
0 commit comments