1
//! Remember a list of changes to a Cargo.toml file
2

            
3
use anyhow::{anyhow, Result};
4
use toml_edit::{Array, Item, Table, Value};
5

            
6
#[derive(Debug, Clone, Default)]
7
pub struct Changes {
8
    changes: Vec<Change>,
9
}
10

            
11
#[derive(Debug, Clone)]
12
pub enum Change {
13
    AddFeature(String),
14
    AddEdge(String, String),
15
    AddExternalEdge(String, String),
16
    Annotate(String, String),
17
}
18

            
19
fn value_is_str(value: &Value, string: &str) -> bool {
20
    matches! {
21
        value, Value::String(s) if s.value() == string
22
    }
23
}
24

            
25
impl Change {
26
    fn apply(&self, features: &mut Table) -> Result<()> {
27
        match self {
28
            Change::AddFeature(feature_name) => match features.get(feature_name) {
29
                Some(_) => {} // nothing to do.
30
                None => {
31
                    assert!(!feature_name.contains('/'), "/ in {feature_name}");
32
                    features.insert(feature_name, Item::Value(Value::Array(Array::new())));
33
                }
34
            },
35
            Change::AddEdge(from, to) => {
36
                // Make sure "to" is there.
37
                Change::AddFeature(to.to_string()).apply(features)?;
38
                Change::AddExternalEdge(from.to_string(), to.to_string()).apply(features)?;
39
            }
40
            Change::AddExternalEdge(from, to) => {
41
                // Make sure "from" is there.
42
                Change::AddFeature(from.to_string()).apply(features)?;
43
                assert!(!from.contains('/'), "/ in {from}");
44
                let array = features
45
                    .get_mut(from)
46
                    .expect("but we just tried to add {from}!")
47
                    .as_array_mut()
48
                    .ok_or_else(|| anyhow!("features.{from} wasn't an array!"))?;
49
                if !array.iter().any(|val| value_is_str(val, to)) {
50
                    array.push(to);
51
                }
52
            }
53
            Change::Annotate(feature_name, annotation) => {
54
                if features.get(feature_name).is_none() {
55
                    return Err(anyhow!(
56
                        "no such feature as {feature_name} to annotate with {annotation}"
57
                    ));
58
                }
59
                let mut key = features.key_mut(feature_name).expect("key not found!?");
60
                let decor = key.leaf_decor_mut();
61
                let prefix = match decor.prefix() {
62
                    Some(r) => r.as_str().expect("prefix not a string"), // (We can't proceed if the prefix decor is not a string.)
63
                    None => "",
64
                };
65
                if !prefix.contains(annotation) {
66
                    let mut new_prefix: String = prefix.to_string();
67
                    new_prefix.push('\n');
68
                    new_prefix.push_str(annotation);
69
                    decor.set_prefix(new_prefix);
70
                }
71
            }
72
        }
73
        Ok(())
74
    }
75
}
76

            
77
impl Changes {
78
    pub fn push(&mut self, change: Change) {
79
        self.changes.push(change);
80
    }
81
    pub fn drop_annotations(&mut self) {
82
        self.changes
83
            .retain(|change| !matches!(change, Change::Annotate(_, _)));
84
    }
85
    pub fn apply(&self, features: &mut Table) -> Result<()> {
86
        self.changes
87
            .iter()
88
            .try_for_each(|change| change.apply(features))
89
    }
90
}