/ maint / fixup-features / src / changes.rs
changes.rs
 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  }