diff options
Diffstat (limited to 'vendor/gix-config/src/file/access/mutate.rs')
-rw-r--r-- | vendor/gix-config/src/file/access/mutate.rs | 387 |
1 files changed, 387 insertions, 0 deletions
diff --git a/vendor/gix-config/src/file/access/mutate.rs b/vendor/gix-config/src/file/access/mutate.rs new file mode 100644 index 000000000..e1cfc6e1c --- /dev/null +++ b/vendor/gix-config/src/file/access/mutate.rs @@ -0,0 +1,387 @@ +use std::borrow::Cow; + +use bstr::BStr; +use gix_features::threading::OwnShared; + +use crate::{ + file::{self, rename_section, write::ends_with_newline, MetadataFilter, SectionBodyIdsLut, SectionId, SectionMut}, + lookup, + parse::{section, Event, FrontMatterEvents}, + File, +}; + +/// Mutating low-level access methods. +impl<'event> File<'event> { + /// Returns the last mutable section with a given `name` and optional `subsection_name`, _if it exists_. + pub fn section_mut<'a>( + &'a mut self, + name: impl AsRef<str>, + subsection_name: Option<&BStr>, + ) -> Result<SectionMut<'a, 'event>, lookup::existing::Error> { + let id = self + .section_ids_by_name_and_subname(name.as_ref(), subsection_name)? + .rev() + .next() + .expect("BUG: Section lookup vec was empty"); + let nl = self.detect_newline_style_smallvec(); + Ok(self + .sections + .get_mut(&id) + .expect("BUG: Section did not have id from lookup") + .to_mut(nl)) + } + + /// Returns the last found mutable section with a given `key`, identifying the name and subsection name like `core` or `remote.origin`. + pub fn section_mut_by_key<'a, 'b>( + &'a mut self, + key: impl Into<&'b BStr>, + ) -> Result<SectionMut<'a, 'event>, lookup::existing::Error> { + let key = section::unvalidated::Key::parse(key).ok_or(lookup::existing::Error::KeyMissing)?; + self.section_mut(key.section_name, key.subsection_name) + } + + /// Return the mutable section identified by `id`, or `None` if it didn't exist. + /// + /// Note that `id` is stable across deletions and insertions. + pub fn section_mut_by_id<'a>(&'a mut self, id: SectionId) -> Option<SectionMut<'a, 'event>> { + let nl = self.detect_newline_style_smallvec(); + self.sections.get_mut(&id).map(|s| s.to_mut(nl)) + } + + /// Returns the last mutable section with a given `name` and optional `subsection_name`, _if it exists_, or create a new section. + pub fn section_mut_or_create_new<'a>( + &'a mut self, + name: impl AsRef<str>, + subsection_name: Option<&BStr>, + ) -> Result<SectionMut<'a, 'event>, section::header::Error> { + self.section_mut_or_create_new_filter(name, subsection_name, &mut |_| true) + } + + /// Returns an mutable section with a given `name` and optional `subsection_name`, _if it exists_ **and** passes `filter`, or create + /// a new section. + pub fn section_mut_or_create_new_filter<'a>( + &'a mut self, + name: impl AsRef<str>, + subsection_name: Option<&BStr>, + filter: &mut MetadataFilter, + ) -> Result<SectionMut<'a, 'event>, section::header::Error> { + let name = name.as_ref(); + match self + .section_ids_by_name_and_subname(name.as_ref(), subsection_name) + .ok() + .and_then(|it| { + it.rev().find(|id| { + let s = &self.sections[id]; + filter(s.meta()) + }) + }) { + Some(id) => { + let nl = self.detect_newline_style_smallvec(); + Ok(self + .sections + .get_mut(&id) + .expect("BUG: Section did not have id from lookup") + .to_mut(nl)) + } + None => self.new_section(name.to_owned(), subsection_name.map(|n| Cow::Owned(n.to_owned()))), + } + } + + /// Returns the last found mutable section with a given `name` and optional `subsection_name`, that matches `filter`, _if it exists_. + /// + /// If there are sections matching `section_name` and `subsection_name` but the `filter` rejects all of them, `Ok(None)` + /// is returned. + pub fn section_mut_filter<'a>( + &'a mut self, + name: impl AsRef<str>, + subsection_name: Option<&BStr>, + filter: &mut MetadataFilter, + ) -> Result<Option<file::SectionMut<'a, 'event>>, lookup::existing::Error> { + let id = self + .section_ids_by_name_and_subname(name.as_ref(), subsection_name)? + .rev() + .find(|id| { + let s = &self.sections[id]; + filter(s.meta()) + }); + let nl = self.detect_newline_style_smallvec(); + Ok(id.and_then(move |id| self.sections.get_mut(&id).map(move |s| s.to_mut(nl)))) + } + + /// Like [`section_mut_filter()`][File::section_mut_filter()], but identifies the with a given `key`, + /// like `core` or `remote.origin`. + pub fn section_mut_filter_by_key<'a, 'b>( + &'a mut self, + key: impl Into<&'b BStr>, + filter: &mut MetadataFilter, + ) -> Result<Option<file::SectionMut<'a, 'event>>, lookup::existing::Error> { + let key = section::unvalidated::Key::parse(key).ok_or(lookup::existing::Error::KeyMissing)?; + self.section_mut_filter(key.section_name, key.subsection_name, filter) + } + + /// Adds a new section. If a subsection name was provided, then + /// the generated header will use the modern subsection syntax. + /// Returns a reference to the new section for immediate editing. + /// + /// # Examples + /// + /// Creating a new empty section: + /// + /// ``` + /// # use std::borrow::Cow; + /// # use gix_config::File; + /// # use std::convert::TryFrom; + /// let mut gix_config = gix_config::File::default(); + /// let section = gix_config.new_section("hello", Some(Cow::Borrowed("world".into())))?; + /// let nl = section.newline().to_owned(); + /// assert_eq!(gix_config.to_string(), format!("[hello \"world\"]{nl}")); + /// # Ok::<(), Box<dyn std::error::Error>>(()) + /// ``` + /// + /// Creating a new empty section and adding values to it: + /// + /// ``` + /// # use gix_config::File; + /// # use std::borrow::Cow; + /// # use std::convert::TryFrom; + /// # use bstr::ByteSlice; + /// # use gix_config::parse::section; + /// let mut gix_config = gix_config::File::default(); + /// let mut section = gix_config.new_section("hello", Some(Cow::Borrowed("world".into())))?; + /// section.push(section::Key::try_from("a")?, Some("b".into())); + /// let nl = section.newline().to_owned(); + /// assert_eq!(gix_config.to_string(), format!("[hello \"world\"]{nl}\ta = b{nl}")); + /// let _section = gix_config.new_section("core", None); + /// assert_eq!(gix_config.to_string(), format!("[hello \"world\"]{nl}\ta = b{nl}[core]{nl}")); + /// # Ok::<(), Box<dyn std::error::Error>>(()) + /// ``` + pub fn new_section( + &mut self, + name: impl Into<Cow<'event, str>>, + subsection: impl Into<Option<Cow<'event, BStr>>>, + ) -> Result<SectionMut<'_, 'event>, section::header::Error> { + let id = self.push_section_internal(file::Section::new(name, subsection, OwnShared::clone(&self.meta))?); + let nl = self.detect_newline_style_smallvec(); + let mut section = self.sections.get_mut(&id).expect("each id yields a section").to_mut(nl); + section.push_newline(); + Ok(section) + } + + /// Removes the section with `name` and `subsection_name` , returning it if there was a matching section. + /// If multiple sections have the same name, then the last one is returned. Note that + /// later sections with the same name have precedent over earlier ones. + /// + /// # Examples + /// + /// Creating and removing a section: + /// + /// ``` + /// # use gix_config::File; + /// # use std::convert::TryFrom; + /// let mut gix_config = gix_config::File::try_from( + /// r#"[hello "world"] + /// some-value = 4 + /// "#)?; + /// + /// let section = gix_config.remove_section("hello", Some("world".into())); + /// assert_eq!(gix_config.to_string(), ""); + /// # Ok::<(), Box<dyn std::error::Error>>(()) + /// ``` + /// + /// Precedence example for removing sections with the same name: + /// + /// ``` + /// # use gix_config::File; + /// # use std::convert::TryFrom; + /// let mut gix_config = gix_config::File::try_from( + /// r#"[hello "world"] + /// some-value = 4 + /// [hello "world"] + /// some-value = 5 + /// "#)?; + /// + /// let section = gix_config.remove_section("hello", Some("world".into())); + /// assert_eq!(gix_config.to_string(), "[hello \"world\"]\n some-value = 4\n"); + /// # Ok::<(), Box<dyn std::error::Error>>(()) + /// ``` + pub fn remove_section<'a>( + &mut self, + name: &str, + subsection_name: impl Into<Option<&'a BStr>>, + ) -> Option<file::Section<'event>> { + let id = self + .section_ids_by_name_and_subname(name, subsection_name.into()) + .ok()? + .rev() + .next()?; + self.remove_section_by_id(id) + } + + /// Remove the section identified by `id` if it exists and return it, or return `None` if no such section was present. + /// + /// Note that section ids are unambiguous even in the face of removals and additions of sections. + pub fn remove_section_by_id(&mut self, id: SectionId) -> Option<file::Section<'event>> { + self.section_order + .remove(self.section_order.iter().position(|v| *v == id)?); + let section = self.sections.remove(&id)?; + let lut = self + .section_lookup_tree + .get_mut(§ion.header.name) + .expect("lookup cache still has name to be deleted"); + // NOTE: this leaves empty lists in the data structure which our code now has to deal with. + for entry in lut { + match section.header.subsection_name.as_deref() { + Some(subsection_name) => { + if let SectionBodyIdsLut::NonTerminal(map) = entry { + if let Some(ids) = map.get_mut(subsection_name) { + ids.remove(ids.iter().position(|v| *v == id).expect("present")); + break; + } + } + } + None => { + if let SectionBodyIdsLut::Terminal(ids) = entry { + ids.remove(ids.iter().position(|v| *v == id).expect("present")); + break; + } + } + } + } + Some(section) + } + + /// Removes the section with `name` and `subsection_name` that passed `filter`, returning the removed section + /// if at least one section matched the `filter`. + /// If multiple sections have the same name, then the last one is returned. Note that + /// later sections with the same name have precedent over earlier ones. + pub fn remove_section_filter<'a>( + &mut self, + name: &str, + subsection_name: impl Into<Option<&'a BStr>>, + filter: &mut MetadataFilter, + ) -> Option<file::Section<'event>> { + let id = self + .section_ids_by_name_and_subname(name, subsection_name.into()) + .ok()? + .rev() + .find(|id| filter(self.sections.get(id).expect("each id has a section").meta()))?; + self.section_order.remove( + self.section_order + .iter() + .position(|v| *v == id) + .expect("known section id"), + ); + self.sections.remove(&id) + } + + /// Adds the provided section to the config, returning a mutable reference + /// to it for immediate editing. + /// Note that its meta-data will remain as is. + pub fn push_section( + &mut self, + section: file::Section<'event>, + ) -> Result<SectionMut<'_, 'event>, section::header::Error> { + let id = self.push_section_internal(section); + let nl = self.detect_newline_style_smallvec(); + let section = self.sections.get_mut(&id).expect("each id yields a section").to_mut(nl); + Ok(section) + } + + /// Renames the section with `name` and `subsection_name`, modifying the last matching section + /// to use `new_name` and `new_subsection_name`. + pub fn rename_section<'a>( + &mut self, + name: impl AsRef<str>, + subsection_name: impl Into<Option<&'a BStr>>, + new_name: impl Into<Cow<'event, str>>, + new_subsection_name: impl Into<Option<Cow<'event, BStr>>>, + ) -> Result<(), rename_section::Error> { + let id = self + .section_ids_by_name_and_subname(name.as_ref(), subsection_name.into())? + .rev() + .next() + .expect("list of sections were empty, which violates invariant"); + let section = self.sections.get_mut(&id).expect("known section-id"); + section.header = section::Header::new(new_name, new_subsection_name)?; + Ok(()) + } + + /// Renames the section with `name` and `subsection_name`, modifying the last matching section + /// that also passes `filter` to use `new_name` and `new_subsection_name`. + /// + /// Note that the otherwise unused [`lookup::existing::Error::KeyMissing`] variant is used to indicate + /// that the `filter` rejected all candidates, leading to no section being renamed after all. + pub fn rename_section_filter<'a>( + &mut self, + name: impl AsRef<str>, + subsection_name: impl Into<Option<&'a BStr>>, + new_name: impl Into<Cow<'event, str>>, + new_subsection_name: impl Into<Option<Cow<'event, BStr>>>, + filter: &mut MetadataFilter, + ) -> Result<(), rename_section::Error> { + let id = self + .section_ids_by_name_and_subname(name.as_ref(), subsection_name.into())? + .rev() + .find(|id| filter(self.sections.get(id).expect("each id has a section").meta())) + .ok_or(rename_section::Error::Lookup(lookup::existing::Error::KeyMissing))?; + let section = self.sections.get_mut(&id).expect("known section-id"); + section.header = section::Header::new(new_name, new_subsection_name)?; + Ok(()) + } + + /// Append another File to the end of ourselves, without losing any information. + pub fn append(&mut self, other: Self) -> &mut Self { + self.append_or_insert(other, None) + } + + /// Append another File to the end of ourselves, without losing any information. + pub(crate) fn append_or_insert(&mut self, mut other: Self, mut insert_after: Option<SectionId>) -> &mut Self { + let nl = self.detect_newline_style_smallvec(); + fn extend_and_assure_newline<'a>( + lhs: &mut FrontMatterEvents<'a>, + rhs: FrontMatterEvents<'a>, + nl: &impl AsRef<[u8]>, + ) { + if !ends_with_newline(lhs.as_ref(), nl, true) + && !rhs.first().map_or(true, |e| e.to_bstr_lossy().starts_with(nl.as_ref())) + { + lhs.push(Event::Newline(Cow::Owned(nl.as_ref().into()))) + } + lhs.extend(rhs); + } + #[allow(clippy::unnecessary_lazy_evaluations)] + let our_last_section_before_append = + insert_after.or_else(|| (self.section_id_counter != 0).then(|| SectionId(self.section_id_counter - 1))); + + for id in std::mem::take(&mut other.section_order) { + let section = other.sections.remove(&id).expect("present"); + + let new_id = match insert_after { + Some(id) => { + let new_id = self.insert_section_after(section, id); + insert_after = Some(new_id); + new_id + } + None => self.push_section_internal(section), + }; + + if let Some(post_matter) = other.frontmatter_post_section.remove(&id) { + self.frontmatter_post_section.insert(new_id, post_matter); + } + } + + if other.frontmatter_events.is_empty() { + return self; + } + + match our_last_section_before_append { + Some(last_id) => extend_and_assure_newline( + self.frontmatter_post_section.entry(last_id).or_default(), + other.frontmatter_events, + &nl, + ), + None => extend_and_assure_newline(&mut self.frontmatter_events, other.frontmatter_events, &nl), + } + self + } +} |