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 }