summaryrefslogtreecommitdiffstats
path: root/vendor/gix-config/src/file/access/mutate.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix-config/src/file/access/mutate.rs')
-rw-r--r--vendor/gix-config/src/file/access/mutate.rs387
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(&section.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
+ }
+}