Skip to main content

cargo_resolvediff/
toml_edit.rs

1// Copyright (C) 2026 by GiGa infosystems
2
3//! Utilities for editing `Cargo.toml` manifests
4
5use color_eyre::Result;
6use std::fs;
7use std::path::{Path, PathBuf};
8use toml_edit::{DocumentMut, Item};
9
10/// A mutable TOML file with capabilities to:
11/// * Write it back to the filesystem
12/// * Roll back to the previously committed version
13/// * Commit to the current version
14pub struct MutableTomlFile {
15    dirty: bool,
16    path: PathBuf,
17    previous_contents: String,
18    document: DocumentMut,
19}
20
21impl MutableTomlFile {
22    pub fn open(path: impl Into<PathBuf>) -> Result<Self> {
23        let path = path.into();
24        let contents = fs::read_to_string(&path)?;
25        let document = contents.parse::<DocumentMut>()?;
26        Ok(MutableTomlFile {
27            dirty: false,
28            path,
29            previous_contents: contents,
30            document,
31        })
32    }
33
34    pub fn path(&self) -> &Path {
35        &self.path
36    }
37
38    pub fn document(&self) -> &toml_edit::DocumentMut {
39        &self.document
40    }
41
42    pub fn document_mut(&mut self) -> &mut toml_edit::DocumentMut {
43        self.dirty = true;
44        &mut self.document
45    }
46
47    fn write_back_inner(&self, data: &str) -> Result<()> {
48        let tmp_path = self.path.with_file_name(".Cargo.toml.update");
49        fs::write(&tmp_path, data)?;
50        fs::rename(&tmp_path, &self.path)?;
51        Ok(())
52    }
53
54    /// Write the TOML file back to the underlying file
55    pub fn write_back(&mut self) -> Result<()> {
56        if self.dirty {
57            self.write_back_inner(&self.document.to_string())?;
58            self.dirty = false;
59        }
60
61        Ok(())
62    }
63
64    /// Roll all changes back to the last commit point (or initial opening of this file)
65    pub fn roll_back(&mut self) -> Result<()> {
66        self.document = self.previous_contents.parse()?;
67        self.write_back_inner(&self.previous_contents)?;
68        self.dirty = false;
69        Ok(())
70    }
71
72    /// Commit to the current version. This cannot error out if it has been written back already.
73    pub fn commit(&mut self) -> Result<()> {
74        self.write_back()?;
75        self.previous_contents = self.document.to_string();
76        Ok(())
77    }
78}
79
80/// Utility to follow paths of string keys in a TOML file.
81///
82/// This is used to access stored version requirements.
83pub trait TomlPathLookup {
84    fn path_lookup(&self, path: impl IntoIterator<Item: AsRef<str>>) -> Option<&Item>;
85    fn path_lookup_mut(&mut self, path: impl IntoIterator<Item: AsRef<str>>) -> Option<&mut Item>;
86}
87
88impl TomlPathLookup for toml_edit::Item {
89    fn path_lookup(&self, path: impl IntoIterator<Item: AsRef<str>>) -> Option<&Item> {
90        let mut item = self;
91        for i in path {
92            item = item.get(i.as_ref())?;
93        }
94
95        Some(item)
96    }
97
98    fn path_lookup_mut(&mut self, path: impl IntoIterator<Item: AsRef<str>>) -> Option<&mut Item> {
99        let mut item = self;
100        for i in path {
101            item = item.get_mut(i.as_ref())?;
102        }
103
104        Some(item)
105    }
106}
107
108impl TomlPathLookup for MutableTomlFile {
109    fn path_lookup(&self, path: impl IntoIterator<Item: AsRef<str>>) -> Option<&Item> {
110        self.document().as_item().path_lookup(path)
111    }
112
113    fn path_lookup_mut(&mut self, path: impl IntoIterator<Item: AsRef<str>>) -> Option<&mut Item> {
114        self.document_mut().as_item_mut().path_lookup_mut(path)
115    }
116}