Maintain/Build/
TomlEdit.rs

1//=============================================================================//
2// File Path: Element/Maintain/Source/Build/TomlEdit.rs
3//=============================================================================//
4// Module: TomlEdit
5//
6// Brief Description: Implements TOML file editing for Cargo.toml.
7//
8// RESPONSIBILITIES:
9// ================
10//
11// Primary:
12// - Dynamically modify name fields in Cargo.toml files
13// - Update package.name, package.default-run, lib.name, and bin.name
14// - Preserve the original structure and formatting
15//
16// Secondary:
17// - Provide detailed logging of changes made
18// - Return whether any modifications occurred
19//
20// ARCHITECTURAL ROLE:
21// ===================
22//
23// Position:
24// - Infrastructure/File manipulation layer
25// - TOML editing functionality
26//
27// Dependencies (What this module requires):
28// - External crates: toml_edit, std (fs, log)
29// - Internal modules: Error::BuildError
30// - Traits implemented: None
31//
32// Dependents (What depends on this module):
33// - Build orchestration functions
34// - Process function
35//
36// IMPLEMENTATION DETAILS:
37// =======================
38//
39// Design Patterns:
40// - Builder pattern (via toml_edit)
41// - Functional pattern
42//
43// Performance Considerations:
44// - Complexity: O(n) - parsing and modifying based on file size
45// - Memory usage patterns: In-memory document manipulation
46// - Hot path optimizations: None needed
47//
48// Thread Safety:
49// - Thread-safe: No (not designed for concurrent access to files)
50// - Synchronization mechanisms used: None
51// - Interior mutability considerations: None
52//
53// Error Handling:
54// - Error types returned: BuildError (Edit type)
55// - Recovery strategies: Propagate error up; Guard restores original file
56//
57// EXAMPLES:
58// =========
59//
60// Example 1: Product name change
61/// ```rust
62/// use crate::Maintain::Source::Build::TomlEdit;
63/// let cargo_path = PathBuf::from("Cargo.toml");
64/// let old_name = "Mountain";
65/// let new_name = "Debug_NodeEnvironment_Mountain";
66/// let modified = TomlEdit(&cargo_path, old_name, new_name)?;
67/// if modified {
68/// println!("Cargo.toml was updated");
69/// }
70/// ```
71//
72// Example 2: No change needed
73/// ```rust
74/// use crate::Maintain::Source::Build::TomlEdit;
75/// let modified = TomlEdit(&cargo_path, "Mountain", "Mountain")?;
76/// // modified = false, no changes made
77/// ```
78//
79//=============================================================================//
80// IMPLEMENTATION
81//=============================================================================//
82
83use crate::Build::Error::Error as BuildError;
84
85use log::{debug, info, warn};
86use std::{fs, path::Path};
87use toml_edit::{DocumentMut as TomlDocument, Item as TomlItem, Value as TomlValue};
88
89/// Dynamically modifies specific name fields within a `Cargo.toml` file.
90///
91/// This function searches for and updates the following fields if they match
92/// the old name:
93/// - `package.name` - The package name identifier
94/// - `package.default-run` - The default binary to run
95/// - `lib.name` - The library name
96/// - `bin.name` - The binary name (first occurrence)
97///
98/// The function preserves the existing file structure and ensures that only
99/// matching names are updated, preventing unintended modifications.
100///
101/// # Parameters
102///
103/// * `File` - Path to the Cargo.toml file to modify
104/// * `Old` - The current name to search for and replace
105/// * `Current` - The new name to set
106///
107/// # Returns
108///
109/// Returns a `Result<bool>` indicating:
110/// - `Ok(true)` - The file was modified and saved
111/// - `Ok(false)` - No changes were made (either no matches or old == current)
112/// - `Err(BuildError)` - An error occurred during modification
113///
114/// # Errors
115///
116/// * `BuildError::Io` - If the file cannot be read or written
117/// * `BuildError::Edit` - If the TOML document cannot be parsed or modified
118///
119/// # Behavior
120///
121/// - If `Old` equals `Current`, returns `Ok(false)` without modifying the file
122/// - Only updates fields that exactly match the old name
123/// - Updates are atomic: the file is written only if at least one change occurs
124/// - Logs all changes made at INFO level
125///
126/// # Example
127///
128/// ```no_run
129/// use crate::Maintain::Source::Build::TomlEdit;
130/// let path = PathBuf::from("Cargo.toml");
131/// let modified = TomlEdit(&path, "Mountain", "Debug_Mountain")?;
132/// if modified {
133/// println!("Successfully updated Cargo.toml");
134/// }
135/// ```
136pub fn TomlEdit(File: &Path, Old: &str, Current: &str) -> Result<bool, BuildError> {
137    debug!(target: "Build::Toml", "Attempting to modify TOML file: {}", File.display());
138
139    let Data = fs::read_to_string(File)?;
140
141    if Old == Current {
142        info!(
143            target: "Build::Toml",
144            "Old name '{}' is the same as current name '{}'. No changes needed for {}.",
145            Old,
146            Current,
147            File.display()
148        );
149
150        return Ok(false);
151    }
152
153    debug!(target: "Build::Toml", "Old name: '{}', Current name: '{}'", Old, Current);
154
155    let mut Parsed: TomlDocument = Data.parse()?;
156
157    let mut PackageChange = false;
158
159    let mut LibraryChange = false;
160
161    let mut BinaryChange = false;
162
163    let mut DefaultChange = false;
164
165    // Update package.name
166    if let Some(PackageTable) = Parsed.get_mut("package").and_then(|Item| Item.as_table_mut()) {
167        if let Some(NameItem) = PackageTable.get_mut("name") {
168            if NameItem.as_str() == Some(Old) {
169                *NameItem = TomlItem::Value(TomlValue::String(toml_edit::Formatted::new(Current.to_string())));
170
171                PackageChange = true;
172
173                debug!(target: "Build::Toml", "Changed package.name");
174            }
175        }
176
177        // Update package.default-run
178        if let Some(RunItem) = PackageTable.get_mut("default-run") {
179            if RunItem.as_str() == Some(Old) {
180                *RunItem = TomlItem::Value(TomlValue::String(toml_edit::Formatted::new(Current.to_string())));
181
182                DefaultChange = true;
183
184                debug!(target: "Build::Toml", "Changed package.default-run");
185            }
186        }
187    }
188
189    // Update lib.name
190    if let Some(LibraryTable) = Parsed.get_mut("lib").and_then(|Item| Item.as_table_mut()) {
191        if let Some(NameItem) = LibraryTable.get_mut("name") {
192            if NameItem.as_str() == Some(Old) {
193                *NameItem = TomlItem::Value(TomlValue::String(toml_edit::Formatted::new(Current.to_string())));
194
195                LibraryChange = true;
196
197                debug!(target: "Build::Toml", "Changed lib.name");
198            }
199        }
200    }
201
202    // Update bin.name (first occurrence)
203    if let Some(BinArray) = Parsed.get_mut("bin").and_then(|Item| Item.as_array_of_tables_mut()) {
204        for Table in BinArray.iter_mut() {
205            if let Some(NameItem) = Table.get_mut("name") {
206                if NameItem.as_str() == Some(Old) {
207                    *NameItem = TomlItem::Value(TomlValue::String(toml_edit::Formatted::new(Current.to_string())));
208
209                    BinaryChange = true;
210
211                    debug!(target: "Build::Toml", "Changed a bin.name entry to '{}'", Current);
212
213                    break;
214                }
215            }
216        }
217    }
218
219    // Write the file if any changes were made
220    if PackageChange || LibraryChange || BinaryChange || DefaultChange {
221        let Output = Parsed.to_string();
222
223        fs::write(File, Output)?;
224
225        let mut ModifiedItems = Vec::new();
226
227        if PackageChange {
228            ModifiedItems.push("package.name");
229        }
230
231        if DefaultChange {
232            ModifiedItems.push("package.default-run");
233        }
234
235        if LibraryChange {
236            ModifiedItems.push("lib.name");
237        }
238
239        if BinaryChange {
240            ModifiedItems.push("bin.name");
241        }
242
243        info!(
244            target: "Build::Toml",
245            "Temporarily changed {} in {} to: {}",
246            ModifiedItems.join(", "),
247            File.display(),
248            Current
249        );
250
251        Ok(true)
252    } else {
253        warn!(
254            target: "Build::Toml",
255            "Name '{}' not found in relevant sections of {}. No changes made to file.",
256            Old,
257            File.display()
258        );
259
260        Ok(false)
261    }
262}