fixup_features/
changes.rs

1//! Remember a list of changes to a Cargo.toml file
2
3use anyhow::{anyhow, Result};
4use toml_edit::{Array, Item, Table, Value};
5
6#[derive(Debug, Clone, Default)]
7pub struct Changes {
8    changes: Vec<Change>,
9}
10
11#[derive(Debug, Clone)]
12pub enum Change {
13    AddFeature(String),
14    AddEdge(String, String),
15    AddExternalEdge(String, String),
16    Annotate(String, String),
17}
18
19fn value_is_str(value: &Value, string: &str) -> bool {
20    matches! {
21        value, Value::String(s) if s.value() == string
22    }
23}
24
25impl 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
77impl 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}