diff options
Diffstat (limited to 'vendor/gix-config/src/file/access')
-rw-r--r-- | vendor/gix-config/src/file/access/comfort.rs | 274 | ||||
-rw-r--r-- | vendor/gix-config/src/file/access/mod.rs | 4 | ||||
-rw-r--r-- | vendor/gix-config/src/file/access/mutate.rs | 387 | ||||
-rw-r--r-- | vendor/gix-config/src/file/access/raw.rs | 536 | ||||
-rw-r--r-- | vendor/gix-config/src/file/access/read_only.rs | 353 |
5 files changed, 1554 insertions, 0 deletions
diff --git a/vendor/gix-config/src/file/access/comfort.rs b/vendor/gix-config/src/file/access/comfort.rs new file mode 100644 index 000000000..b4953c597 --- /dev/null +++ b/vendor/gix-config/src/file/access/comfort.rs @@ -0,0 +1,274 @@ +use std::{borrow::Cow, convert::TryFrom}; + +use bstr::BStr; + +use crate::{file::MetadataFilter, value, File}; + +/// Comfortable API for accessing values +impl<'event> File<'event> { + /// Like [`value()`][File::value()], but returning `None` if the string wasn't found. + /// + /// As strings perform no conversions, this will never fail. + pub fn string( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + ) -> Option<Cow<'_, BStr>> { + self.string_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Like [`string()`][File::string()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn string_by_key<'a>(&self, key: impl Into<&'a BStr>) -> Option<Cow<'_, BStr>> { + self.string_filter_by_key(key, &mut |_| true) + } + + /// Like [`string()`][File::string()], but the section containing the returned value must pass `filter` as well. + pub fn string_filter( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + filter: &mut MetadataFilter, + ) -> Option<Cow<'_, BStr>> { + self.raw_value_filter(section_name, subsection_name, key, filter).ok() + } + + /// Like [`string_filter()`][File::string_filter()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn string_filter_by_key<'a>( + &self, + key: impl Into<&'a BStr>, + filter: &mut MetadataFilter, + ) -> Option<Cow<'_, BStr>> { + let key = crate::parse::key(key)?; + self.raw_value_filter(key.section_name, key.subsection_name, key.value_name, filter) + .ok() + } + + /// Like [`value()`][File::value()], but returning `None` if the path wasn't found. + /// + /// Note that this path is not vetted and should only point to resources which can't be used + /// to pose a security risk. Prefer using [`path_filter()`][File::path_filter()] instead. + /// + /// As paths perform no conversions, this will never fail. + pub fn path( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + ) -> Option<crate::Path<'_>> { + self.path_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Like [`path()`][File::path()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn path_by_key<'a>(&self, key: impl Into<&'a BStr>) -> Option<crate::Path<'_>> { + self.path_filter_by_key(key, &mut |_| true) + } + + /// Like [`path()`][File::path()], but the section containing the returned value must pass `filter` as well. + /// + /// This should be the preferred way of accessing paths as those from untrusted + /// locations can be + /// + /// As paths perform no conversions, this will never fail. + pub fn path_filter( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + filter: &mut MetadataFilter, + ) -> Option<crate::Path<'_>> { + self.raw_value_filter(section_name, subsection_name, key, filter) + .ok() + .map(crate::Path::from) + } + + /// Like [`path_filter()`][File::path_filter()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn path_filter_by_key<'a>( + &self, + key: impl Into<&'a BStr>, + filter: &mut MetadataFilter, + ) -> Option<crate::Path<'_>> { + let key = crate::parse::key(key)?; + self.path_filter(key.section_name, key.subsection_name, key.value_name, filter) + } + + /// Like [`value()`][File::value()], but returning `None` if the boolean value wasn't found. + pub fn boolean( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + ) -> Option<Result<bool, value::Error>> { + self.boolean_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Like [`boolean()`][File::boolean()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn boolean_by_key<'a>(&self, key: impl Into<&'a BStr>) -> Option<Result<bool, value::Error>> { + self.boolean_filter_by_key(key, &mut |_| true) + } + + /// Like [`boolean()`][File::boolean()], but the section containing the returned value must pass `filter` as well. + pub fn boolean_filter( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + filter: &mut MetadataFilter, + ) -> Option<Result<bool, value::Error>> { + let section_name = section_name.as_ref(); + let section_ids = self + .section_ids_by_name_and_subname(section_name, subsection_name) + .ok()?; + let key = key.as_ref(); + for section_id in section_ids.rev() { + let section = self.sections.get(§ion_id).expect("known section id"); + if !filter(section.meta()) { + continue; + } + match section.value_implicit(key) { + Some(Some(v)) => return Some(crate::Boolean::try_from(v).map(|b| b.into())), + Some(None) => return Some(Ok(true)), + None => continue, + } + } + None + } + + /// Like [`boolean_filter()`][File::boolean_filter()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn boolean_filter_by_key<'a>( + &self, + key: impl Into<&'a BStr>, + filter: &mut MetadataFilter, + ) -> Option<Result<bool, value::Error>> { + let key = crate::parse::key(key)?; + self.boolean_filter(key.section_name, key.subsection_name, key.value_name, filter) + } + + /// Like [`value()`][File::value()], but returning an `Option` if the integer wasn't found. + pub fn integer( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + ) -> Option<Result<i64, value::Error>> { + self.integer_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Like [`integer()`][File::integer()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn integer_by_key<'a>(&self, key: impl Into<&'a BStr>) -> Option<Result<i64, value::Error>> { + self.integer_filter_by_key(key, &mut |_| true) + } + + /// Like [`integer()`][File::integer()], but the section containing the returned value must pass `filter` as well. + pub fn integer_filter( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + filter: &mut MetadataFilter, + ) -> Option<Result<i64, value::Error>> { + let int = self.raw_value_filter(section_name, subsection_name, key, filter).ok()?; + Some(crate::Integer::try_from(int.as_ref()).and_then(|b| { + b.to_decimal() + .ok_or_else(|| value::Error::new("Integer overflow", int.into_owned())) + })) + } + + /// Like [`integer_filter()`][File::integer_filter()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn integer_filter_by_key<'a>( + &self, + key: impl Into<&'a BStr>, + filter: &mut MetadataFilter, + ) -> Option<Result<i64, value::Error>> { + let key = crate::parse::key(key)?; + self.integer_filter(key.section_name, key.subsection_name, key.value_name, filter) + } + + /// Similar to [`values(…)`][File::values()] but returning strings if at least one of them was found. + pub fn strings( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + ) -> Option<Vec<Cow<'_, BStr>>> { + self.raw_values(section_name, subsection_name, key).ok() + } + + /// Like [`strings()`][File::strings()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn strings_by_key<'a>(&self, key: impl Into<&'a BStr>) -> Option<Vec<Cow<'_, BStr>>> { + let key = crate::parse::key(key)?; + self.strings(key.section_name, key.subsection_name, key.value_name) + } + + /// Similar to [`strings(…)`][File::strings()], but all values are in sections that passed `filter`. + pub fn strings_filter( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + filter: &mut MetadataFilter, + ) -> Option<Vec<Cow<'_, BStr>>> { + self.raw_values_filter(section_name, subsection_name, key, filter).ok() + } + + /// Like [`strings_filter()`][File::strings_filter()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn strings_filter_by_key<'a>( + &self, + key: impl Into<&'a BStr>, + filter: &mut MetadataFilter, + ) -> Option<Vec<Cow<'_, BStr>>> { + let key = crate::parse::key(key)?; + self.strings_filter(key.section_name, key.subsection_name, key.value_name, filter) + } + + /// Similar to [`values(…)`][File::values()] but returning integers if at least one of them was found + /// and if none of them overflows. + pub fn integers( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + ) -> Option<Result<Vec<i64>, value::Error>> { + self.integers_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Like [`integers()`][File::integers()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn integers_by_key<'a>(&self, key: impl Into<&'a BStr>) -> Option<Result<Vec<i64>, value::Error>> { + self.integers_filter_by_key(key, &mut |_| true) + } + + /// Similar to [`integers(…)`][File::integers()] but all integers are in sections that passed `filter` + /// and that are not overflowing. + pub fn integers_filter( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + filter: &mut MetadataFilter, + ) -> Option<Result<Vec<i64>, value::Error>> { + self.raw_values_filter(section_name, subsection_name, key, filter) + .ok() + .map(|values| { + values + .into_iter() + .map(|v| { + crate::Integer::try_from(v.as_ref()).and_then(|int| { + int.to_decimal() + .ok_or_else(|| value::Error::new("Integer overflow", v.into_owned())) + }) + }) + .collect() + }) + } + + /// Like [`integers_filter()`][File::integers_filter()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn integers_filter_by_key<'a>( + &self, + key: impl Into<&'a BStr>, + filter: &mut MetadataFilter, + ) -> Option<Result<Vec<i64>, value::Error>> { + let key = crate::parse::key(key)?; + self.integers_filter(key.section_name, key.subsection_name, key.value_name, filter) + } +} diff --git a/vendor/gix-config/src/file/access/mod.rs b/vendor/gix-config/src/file/access/mod.rs new file mode 100644 index 000000000..d602b5f8b --- /dev/null +++ b/vendor/gix-config/src/file/access/mod.rs @@ -0,0 +1,4 @@ +mod comfort; +mod mutate; +mod raw; +mod read_only; 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 + } +} diff --git a/vendor/gix-config/src/file/access/raw.rs b/vendor/gix-config/src/file/access/raw.rs new file mode 100644 index 000000000..46f1fb006 --- /dev/null +++ b/vendor/gix-config/src/file/access/raw.rs @@ -0,0 +1,536 @@ +use std::{borrow::Cow, collections::HashMap, convert::TryInto}; + +use bstr::BStr; +use smallvec::ToSmallVec; + +use crate::{ + file::{mutable::multi_value::EntryData, Index, MetadataFilter, MultiValueMut, Size, ValueMut}, + lookup, + parse::{section, Event}, + File, +}; + +/// # Raw value API +/// +/// These functions are the raw value API, returning normalized byte strings. +impl<'event> File<'event> { + /// Returns an uninterpreted value given a section, an optional subsection + /// and key. + /// + /// Consider [`Self::raw_values()`] if you want to get all values of + /// a multivar instead. + pub fn raw_value( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + ) -> Result<Cow<'_, BStr>, lookup::existing::Error> { + self.raw_value_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Returns an uninterpreted value given a section, an optional subsection + /// and key, if it passes the `filter`. + /// + /// Consider [`Self::raw_values()`] if you want to get all values of + /// a multivar instead. + pub fn raw_value_filter( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + filter: &mut MetadataFilter, + ) -> Result<Cow<'_, BStr>, lookup::existing::Error> { + let section_ids = self.section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)?; + let key = key.as_ref(); + for section_id in section_ids.rev() { + let section = self.sections.get(§ion_id).expect("known section id"); + if !filter(section.meta()) { + continue; + } + if let Some(v) = section.value(key) { + return Ok(v); + } + } + + Err(lookup::existing::Error::KeyMissing) + } + + /// Returns a mutable reference to an uninterpreted value given a section, + /// an optional subsection and key. + /// + /// Consider [`Self::raw_values_mut`] if you want to get mutable + /// references to all values of a multivar instead. + pub fn raw_value_mut<'lookup>( + &mut self, + section_name: impl AsRef<str>, + subsection_name: Option<&'lookup BStr>, + key: &'lookup str, + ) -> Result<ValueMut<'_, 'lookup, 'event>, lookup::existing::Error> { + self.raw_value_mut_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Returns a mutable reference to an uninterpreted value given a section, + /// an optional subsection and key, and if it passes `filter`. + /// + /// Consider [`Self::raw_values_mut`] if you want to get mutable + /// references to all values of a multivar instead. + pub fn raw_value_mut_filter<'lookup>( + &mut self, + section_name: impl AsRef<str>, + subsection_name: Option<&'lookup BStr>, + key: &'lookup str, + filter: &mut MetadataFilter, + ) -> Result<ValueMut<'_, 'lookup, 'event>, lookup::existing::Error> { + let mut section_ids = self + .section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)? + .rev(); + let key = section::Key(Cow::<BStr>::Borrowed(key.into())); + + while let Some(section_id) = section_ids.next() { + let mut index = 0; + let mut size = 0; + let mut found_key = false; + let section = self.sections.get(§ion_id).expect("known section id"); + if !filter(section.meta()) { + continue; + } + for (i, event) in section.as_ref().iter().enumerate() { + match event { + Event::SectionKey(event_key) if *event_key == key => { + found_key = true; + index = i; + size = 1; + } + Event::Newline(_) | Event::Whitespace(_) | Event::ValueNotDone(_) if found_key => { + size += 1; + } + Event::ValueDone(_) | Event::Value(_) if found_key => { + found_key = false; + size += 1; + } + Event::KeyValueSeparator if found_key => { + size += 1; + } + _ => {} + } + } + + if size == 0 { + continue; + } + + drop(section_ids); + let nl = self.detect_newline_style().to_smallvec(); + return Ok(ValueMut { + section: self.sections.get_mut(§ion_id).expect("known section-id").to_mut(nl), + key, + index: Index(index), + size: Size(size), + }); + } + + Err(lookup::existing::Error::KeyMissing) + } + + /// Returns all uninterpreted values given a section, an optional subsection + /// ain order of occurrence. + /// + /// The ordering means that the last of the returned values is the one that would be the + /// value used in the single-value case.nd key. + /// + /// # Examples + /// + /// If you have the following config: + /// + /// ```text + /// [core] + /// a = b + /// [core] + /// a = c + /// a = d + /// ``` + /// + /// Attempting to get all values of `a` yields the following: + /// + /// ``` + /// # use gix_config::File; + /// # use std::borrow::Cow; + /// # use std::convert::TryFrom; + /// # use bstr::BStr; + /// # let gix_config = gix_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); + /// assert_eq!( + /// gix_config.raw_values("core", None, "a").unwrap(), + /// vec![ + /// Cow::<BStr>::Borrowed("b".into()), + /// Cow::<BStr>::Borrowed("c".into()), + /// Cow::<BStr>::Borrowed("d".into()), + /// ], + /// ); + /// ``` + /// + /// Consider [`Self::raw_value`] if you want to get the resolved single + /// value for a given key, if your key does not support multi-valued values. + pub fn raw_values( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + ) -> Result<Vec<Cow<'_, BStr>>, lookup::existing::Error> { + self.raw_values_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Returns all uninterpreted values given a section, an optional subsection + /// and key, if the value passes `filter`, in order of occurrence. + /// + /// The ordering means that the last of the returned values is the one that would be the + /// value used in the single-value case. + pub fn raw_values_filter( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + filter: &mut MetadataFilter, + ) -> Result<Vec<Cow<'_, BStr>>, lookup::existing::Error> { + let mut values = Vec::new(); + let section_ids = self.section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)?; + let key = key.as_ref(); + for section_id in section_ids { + let section = self.sections.get(§ion_id).expect("known section id"); + if !filter(section.meta()) { + continue; + } + values.extend(section.values(key)); + } + + if values.is_empty() { + Err(lookup::existing::Error::KeyMissing) + } else { + Ok(values) + } + } + + /// Returns mutable references to all uninterpreted values given a section, + /// an optional subsection and key. + /// + /// # Examples + /// + /// If you have the following config: + /// + /// ```text + /// [core] + /// a = b + /// [core] + /// a = c + /// a = d + /// ``` + /// + /// Attempting to get all values of `a` yields the following: + /// + /// ``` + /// # use gix_config::File; + /// # use std::borrow::Cow; + /// # use std::convert::TryFrom; + /// # use bstr::BStr; + /// # let mut gix_config = gix_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); + /// assert_eq!( + /// gix_config.raw_values("core", None, "a")?, + /// vec![ + /// Cow::<BStr>::Borrowed("b".into()), + /// Cow::<BStr>::Borrowed("c".into()), + /// Cow::<BStr>::Borrowed("d".into()) + /// ] + /// ); + /// + /// gix_config.raw_values_mut("core", None, "a")?.set_all("g"); + /// + /// assert_eq!( + /// gix_config.raw_values("core", None, "a")?, + /// vec![ + /// Cow::<BStr>::Borrowed("g".into()), + /// Cow::<BStr>::Borrowed("g".into()), + /// Cow::<BStr>::Borrowed("g".into()) + /// ], + /// ); + /// # Ok::<(), gix_config::lookup::existing::Error>(()) + /// ``` + /// + /// Consider [`Self::raw_value`] if you want to get the resolved single + /// value for a given key, if your key does not support multi-valued values. + /// + /// Note that this operation is relatively expensive, requiring a full + /// traversal of the config. + pub fn raw_values_mut<'lookup>( + &mut self, + section_name: impl AsRef<str>, + subsection_name: Option<&'lookup BStr>, + key: &'lookup str, + ) -> Result<MultiValueMut<'_, 'lookup, 'event>, lookup::existing::Error> { + self.raw_values_mut_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Returns mutable references to all uninterpreted values given a section, + /// an optional subsection and key, if their sections pass `filter`. + pub fn raw_values_mut_filter<'lookup>( + &mut self, + section_name: impl AsRef<str>, + subsection_name: Option<&'lookup BStr>, + key: &'lookup str, + filter: &mut MetadataFilter, + ) -> Result<MultiValueMut<'_, 'lookup, 'event>, lookup::existing::Error> { + let section_ids = self.section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)?; + let key = section::Key(Cow::<BStr>::Borrowed(key.into())); + + let mut offsets = HashMap::new(); + let mut entries = Vec::new(); + for section_id in section_ids.rev() { + let mut last_boundary = 0; + let mut expect_value = false; + let mut offset_list = Vec::new(); + let mut offset_index = 0; + let section = self.sections.get(§ion_id).expect("known section-id"); + if !filter(section.meta()) { + continue; + } + for (i, event) in section.as_ref().iter().enumerate() { + match event { + Event::SectionKey(event_key) if *event_key == key => { + expect_value = true; + offset_list.push(i - last_boundary); + offset_index += 1; + last_boundary = i; + } + Event::Value(_) | Event::ValueDone(_) if expect_value => { + expect_value = false; + entries.push(EntryData { + section_id, + offset_index, + }); + offset_list.push(i - last_boundary + 1); + offset_index += 1; + last_boundary = i + 1; + } + _ => (), + } + } + offsets.insert(section_id, offset_list); + } + + entries.sort(); + + if entries.is_empty() { + Err(lookup::existing::Error::KeyMissing) + } else { + Ok(MultiValueMut { + section: &mut self.sections, + key, + indices_and_sizes: entries, + offsets, + }) + } + } + + /// Sets a value in a given `section_name`, optional `subsection_name`, and `key`. + /// Note sections named `section_name` and `subsection_name` (if not `None`) + /// must exist for this method to work. + /// + /// # Examples + /// + /// Given the config, + /// + /// ```text + /// [core] + /// a = b + /// [core] + /// a = c + /// a = d + /// ``` + /// + /// Setting a new value to the key `core.a` will yield the following: + /// + /// ``` + /// # use gix_config::File; + /// # use std::borrow::Cow; + /// # use bstr::BStr; + /// # use std::convert::TryFrom; + /// # let mut gix_config = gix_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); + /// gix_config.set_existing_raw_value("core", None, "a", "e")?; + /// assert_eq!(gix_config.raw_value("core", None, "a")?, Cow::<BStr>::Borrowed("e".into())); + /// assert_eq!( + /// gix_config.raw_values("core", None, "a")?, + /// vec![ + /// Cow::<BStr>::Borrowed("b".into()), + /// Cow::<BStr>::Borrowed("c".into()), + /// Cow::<BStr>::Borrowed("e".into()) + /// ], + /// ); + /// # Ok::<(), Box<dyn std::error::Error>>(()) + /// ``` + pub fn set_existing_raw_value<'b>( + &mut self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + new_value: impl Into<&'b BStr>, + ) -> Result<(), lookup::existing::Error> { + self.raw_value_mut(section_name, subsection_name, key.as_ref()) + .map(|mut entry| entry.set(new_value)) + } + + /// Sets a value in a given `section_name`, optional `subsection_name`, and `key`. + /// Creates the section if necessary and the key as well, or overwrites the last existing value otherwise. + /// + /// # Examples + /// + /// Given the config, + /// + /// ```text + /// [core] + /// a = b + /// ``` + /// + /// Setting a new value to the key `core.a` will yield the following: + /// + /// ``` + /// # use gix_config::File; + /// # use std::borrow::Cow; + /// # use bstr::BStr; + /// # use std::convert::TryFrom; + /// # let mut gix_config = gix_config::File::try_from("[core]a=b").unwrap(); + /// let prev = gix_config.set_raw_value("core", None, "a", "e")?; + /// gix_config.set_raw_value("core", None, "b", "f")?; + /// assert_eq!(prev.expect("present").as_ref(), "b"); + /// assert_eq!(gix_config.raw_value("core", None, "a")?, Cow::<BStr>::Borrowed("e".into())); + /// assert_eq!(gix_config.raw_value("core", None, "b")?, Cow::<BStr>::Borrowed("f".into())); + /// # Ok::<(), Box<dyn std::error::Error>>(()) + /// ``` + pub fn set_raw_value<'b, Key, E>( + &mut self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: Key, + new_value: impl Into<&'b BStr>, + ) -> Result<Option<Cow<'event, BStr>>, crate::file::set_raw_value::Error> + where + Key: TryInto<section::Key<'event>, Error = E>, + section::key::Error: From<E>, + { + self.set_raw_value_filter(section_name, subsection_name, key, new_value, &mut |_| true) + } + + /// Similar to [`set_raw_value()`][Self::set_raw_value()], but only sets existing values in sections matching + /// `filter`, creating a new section otherwise. + pub fn set_raw_value_filter<'b, Key, E>( + &mut self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: Key, + new_value: impl Into<&'b BStr>, + filter: &mut MetadataFilter, + ) -> Result<Option<Cow<'event, BStr>>, crate::file::set_raw_value::Error> + where + Key: TryInto<section::Key<'event>, Error = E>, + section::key::Error: From<E>, + { + let mut section = self.section_mut_or_create_new_filter(section_name, subsection_name, filter)?; + Ok(section.set(key.try_into().map_err(section::key::Error::from)?, new_value)) + } + + /// Sets a multivar in a given section, optional subsection, and key value. + /// + /// This internally zips together the new values and the existing values. + /// As a result, if more new values are provided than the current amount of + /// multivars, then the latter values are not applied. If there are less + /// new values than old ones then the remaining old values are unmodified. + /// + /// **Note**: Mutation order is _not_ guaranteed and is non-deterministic. + /// If you need finer control over which values of the multivar are set, + /// consider using [`raw_values_mut()`][Self::raw_values_mut()], which will let you iterate + /// and check over the values instead. This is best used as a convenience + /// function for setting multivars whose values should be treated as an + /// unordered set. + /// + /// # Examples + /// + /// Let us use the follow config for all examples: + /// + /// ```text + /// [core] + /// a = b + /// [core] + /// a = c + /// a = d + /// ``` + /// + /// Setting an equal number of values: + /// + /// ``` + /// # use gix_config::File; + /// # use std::borrow::Cow; + /// # use std::convert::TryFrom; + /// # use bstr::BStr; + /// # let mut gix_config = gix_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); + /// let new_values = vec![ + /// "x", + /// "y", + /// "z", + /// ]; + /// gix_config.set_existing_raw_multi_value("core", None, "a", new_values.into_iter())?; + /// let fetched_config = gix_config.raw_values("core", None, "a")?; + /// assert!(fetched_config.contains(&Cow::<BStr>::Borrowed("x".into()))); + /// assert!(fetched_config.contains(&Cow::<BStr>::Borrowed("y".into()))); + /// assert!(fetched_config.contains(&Cow::<BStr>::Borrowed("z".into()))); + /// # Ok::<(), gix_config::lookup::existing::Error>(()) + /// ``` + /// + /// Setting less than the number of present values sets the first ones found: + /// + /// ``` + /// # use gix_config::File; + /// # use std::borrow::Cow; + /// # use std::convert::TryFrom; + /// # use bstr::BStr; + /// # let mut gix_config = gix_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); + /// let new_values = vec![ + /// "x", + /// "y", + /// ]; + /// gix_config.set_existing_raw_multi_value("core", None, "a", new_values.into_iter())?; + /// let fetched_config = gix_config.raw_values("core", None, "a")?; + /// assert!(fetched_config.contains(&Cow::<BStr>::Borrowed("x".into()))); + /// assert!(fetched_config.contains(&Cow::<BStr>::Borrowed("y".into()))); + /// # Ok::<(), gix_config::lookup::existing::Error>(()) + /// ``` + /// + /// Setting more than the number of present values discards the rest: + /// + /// ``` + /// # use gix_config::File; + /// # use std::borrow::Cow; + /// # use std::convert::TryFrom; + /// # use bstr::BStr; + /// # let mut gix_config = gix_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); + /// let new_values = vec![ + /// "x", + /// "y", + /// "z", + /// "discarded", + /// ]; + /// gix_config.set_existing_raw_multi_value("core", None, "a", new_values)?; + /// assert!(!gix_config.raw_values("core", None, "a")?.contains(&Cow::<BStr>::Borrowed("discarded".into()))); + /// # Ok::<(), gix_config::lookup::existing::Error>(()) + /// ``` + pub fn set_existing_raw_multi_value<'a, Iter, Item>( + &mut self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + new_values: Iter, + ) -> Result<(), lookup::existing::Error> + where + Iter: IntoIterator<Item = Item>, + Item: Into<&'a BStr>, + { + self.raw_values_mut(section_name, subsection_name, key.as_ref()) + .map(|mut v| v.set_values(new_values)) + } +} diff --git a/vendor/gix-config/src/file/access/read_only.rs b/vendor/gix-config/src/file/access/read_only.rs new file mode 100644 index 000000000..5520c6566 --- /dev/null +++ b/vendor/gix-config/src/file/access/read_only.rs @@ -0,0 +1,353 @@ +use std::{borrow::Cow, convert::TryFrom}; + +use bstr::{BStr, ByteSlice}; +use gix_features::threading::OwnShared; +use smallvec::SmallVec; + +use crate::{ + file, + file::{ + write::{extract_newline, platform_newline}, + Metadata, MetadataFilter, SectionId, + }, + lookup, + parse::Event, + File, +}; + +/// Read-only low-level access methods, as it requires generics for converting into +/// custom values defined in this crate like [`Integer`][crate::Integer] and +/// [`Color`][crate::Color]. +impl<'event> File<'event> { + /// Returns an interpreted value given a section, an optional subsection and + /// key. + /// + /// It's recommended to use one of the value types provide dby this crate + /// as they implement the conversion, but this function is flexible and + /// will accept any type that implements [`TryFrom<&BStr>`][std::convert::TryFrom]. + /// + /// Consider [`Self::values`] if you want to get all values of a multivar instead. + /// + /// If a `string` is desired, use the [`string()`][Self::string()] method instead. + /// + /// # Examples + /// + /// ``` + /// # use gix_config::File; + /// # use gix_config::{Integer, Boolean}; + /// # use std::borrow::Cow; + /// # use std::convert::TryFrom; + /// let config = r#" + /// [core] + /// a = 10k + /// c = false + /// "#; + /// let gix_config = gix_config::File::try_from(config)?; + /// // You can either use the turbofish to determine the type... + /// let a_value = gix_config.value::<Integer>("core", None, "a")?; + /// // ... or explicitly declare the type to avoid the turbofish + /// let c_value: Boolean = gix_config.value("core", None, "c")?; + /// # Ok::<(), Box<dyn std::error::Error>>(()) + /// ``` + pub fn value<'a, T: TryFrom<Cow<'a, BStr>>>( + &'a self, + section_name: &str, + subsection_name: Option<&BStr>, + key: &str, + ) -> Result<T, lookup::Error<T::Error>> { + T::try_from(self.raw_value(section_name, subsection_name, key)?).map_err(lookup::Error::FailedConversion) + } + + /// Like [`value()`][File::value()], but returning an `None` if the value wasn't found at `section[.subsection].key` + pub fn try_value<'a, T: TryFrom<Cow<'a, BStr>>>( + &'a self, + section_name: &str, + subsection_name: Option<&BStr>, + key: &str, + ) -> Option<Result<T, T::Error>> { + self.raw_value(section_name, subsection_name, key).ok().map(T::try_from) + } + + /// Returns all interpreted values given a section, an optional subsection + /// and key. + /// + /// It's recommended to use one of the value types provide dby this crate + /// as they implement the conversion, but this function is flexible and + /// will accept any type that implements [`TryFrom<&BStr>`][std::convert::TryFrom]. + /// + /// Consider [`Self::value`] if you want to get a single value + /// (following last-one-wins resolution) instead. + /// + /// To access plain strings, use the [`strings()`][Self::strings()] method instead. + /// + /// # Examples + /// + /// ``` + /// # use gix_config::File; + /// # use gix_config::{Integer, Boolean}; + /// # use std::borrow::Cow; + /// # use std::convert::TryFrom; + /// # use bstr::ByteSlice; + /// let config = r#" + /// [core] + /// a = true + /// c + /// [core] + /// a + /// a = false + /// "#; + /// let gix_config = gix_config::File::try_from(config).unwrap(); + /// // You can either use the turbofish to determine the type... + /// let a_value = gix_config.values::<Boolean>("core", None, "a")?; + /// assert_eq!( + /// a_value, + /// vec![ + /// Boolean(true), + /// Boolean(false), + /// Boolean(false), + /// ] + /// ); + /// // ... or explicitly declare the type to avoid the turbofish + /// let c_value: Vec<Boolean> = gix_config.values("core", None, "c").unwrap(); + /// assert_eq!(c_value, vec![Boolean(false)]); + /// # Ok::<(), Box<dyn std::error::Error>>(()) + /// ``` + /// + /// [`value`]: crate::value + /// [`TryFrom`]: std::convert::TryFrom + pub fn values<'a, T: TryFrom<Cow<'a, BStr>>>( + &'a self, + section_name: &str, + subsection_name: Option<&BStr>, + key: &str, + ) -> Result<Vec<T>, lookup::Error<T::Error>> { + self.raw_values(section_name, subsection_name, key)? + .into_iter() + .map(T::try_from) + .collect::<Result<Vec<_>, _>>() + .map_err(lookup::Error::FailedConversion) + } + + /// Returns the last found immutable section with a given `name` and optional `subsection_name`. + pub fn section( + &self, + name: impl AsRef<str>, + subsection_name: Option<&BStr>, + ) -> Result<&file::Section<'event>, lookup::existing::Error> { + self.section_filter(name, subsection_name, &mut |_| true)? + .ok_or(lookup::existing::Error::SectionMissing) + } + + /// Returns the last found immutable section with a given `key`, identifying the name and subsection name like `core` + /// or `remote.origin`. + pub fn section_by_key<'a>( + &self, + key: impl Into<&'a BStr>, + ) -> Result<&file::Section<'event>, lookup::existing::Error> { + let key = crate::parse::section::unvalidated::Key::parse(key).ok_or(lookup::existing::Error::KeyMissing)?; + self.section(key.section_name, key.subsection_name) + } + + /// Returns the last found immutable section with a given `name` and optional `subsection_name`, that matches `filter`. + /// + /// If there are sections matching `section_name` and `subsection_name` but the `filter` rejects all of them, `Ok(None)` + /// is returned. + pub fn section_filter<'a>( + &'a self, + name: impl AsRef<str>, + subsection_name: Option<&BStr>, + filter: &mut MetadataFilter, + ) -> Result<Option<&'a file::Section<'event>>, lookup::existing::Error> { + Ok(self + .section_ids_by_name_and_subname(name.as_ref(), subsection_name)? + .rev() + .find_map({ + let sections = &self.sections; + move |id| { + let s = §ions[&id]; + filter(s.meta()).then_some(s) + } + })) + } + + /// Like [`section_filter()`][File::section_filter()], but identifies the section with `key` like `core` or `remote.origin`. + pub fn section_filter_by_key<'a, 'b>( + &'a self, + key: impl Into<&'b BStr>, + filter: &mut MetadataFilter, + ) -> Result<Option<&'a file::Section<'event>>, lookup::existing::Error> { + let key = crate::parse::section::unvalidated::Key::parse(key).ok_or(lookup::existing::Error::KeyMissing)?; + self.section_filter(key.section_name, key.subsection_name, filter) + } + + /// Gets all sections that match the provided `name`, ignoring any subsections. + /// + /// # Examples + /// + /// Provided the following config: + /// + /// ```text + /// [core] + /// a = b + /// [core ""] + /// c = d + /// [core "apple"] + /// e = f + /// ``` + /// + /// Calling this method will yield all sections: + /// + /// ``` + /// # use gix_config::File; + /// # use gix_config::{Integer, Boolean}; + /// # use std::borrow::Cow; + /// # use std::convert::TryFrom; + /// let config = r#" + /// [core] + /// a = b + /// [core ""] + /// c = d + /// [core "apple"] + /// e = f + /// "#; + /// let gix_config = gix_config::File::try_from(config)?; + /// assert_eq!(gix_config.sections_by_name("core").map_or(0, |s|s.count()), 3); + /// # Ok::<(), Box<dyn std::error::Error>>(()) + /// ``` + #[must_use] + pub fn sections_by_name<'a>(&'a self, name: &'a str) -> Option<impl Iterator<Item = &file::Section<'event>> + '_> { + self.section_ids_by_name(name).ok().map(move |ids| { + ids.map(move |id| { + self.sections + .get(&id) + .expect("section doesn't have id from from lookup") + }) + }) + } + + /// Similar to [`sections_by_name()`][Self::sections_by_name()], but returns an identifier for this section as well to allow + /// referring to it unambiguously even in the light of deletions. + #[must_use] + pub fn sections_and_ids_by_name<'a>( + &'a self, + name: &'a str, + ) -> Option<impl Iterator<Item = (&file::Section<'event>, SectionId)> + '_> { + self.section_ids_by_name(name).ok().map(move |ids| { + ids.map(move |id| { + ( + self.sections + .get(&id) + .expect("section doesn't have id from from lookup"), + id, + ) + }) + }) + } + + /// Gets all sections that match the provided `name`, ignoring any subsections, and pass the `filter`. + #[must_use] + pub fn sections_by_name_and_filter<'a>( + &'a self, + name: &'a str, + filter: &'a mut MetadataFilter, + ) -> Option<impl Iterator<Item = &file::Section<'event>> + '_> { + self.section_ids_by_name(name).ok().map(move |ids| { + ids.filter_map(move |id| { + let s = self + .sections + .get(&id) + .expect("section doesn't have id from from lookup"); + filter(s.meta()).then_some(s) + }) + }) + } + + /// Returns the number of values in the config, no matter in which section. + /// + /// For example, a config with multiple empty sections will return 0. + /// This ignores any comments. + #[must_use] + pub fn num_values(&self) -> usize { + self.sections.values().map(|section| section.num_values()).sum() + } + + /// Returns if there are no entries in the config. This will return true + /// if there are only empty sections, with whitespace and comments not being considered + /// void. + #[must_use] + pub fn is_void(&self) -> bool { + self.sections.values().all(|s| s.body.is_void()) + } + + /// Return this file's metadata, typically set when it was first created to indicate its origins. + /// + /// It will be used in all newly created sections to identify them. + /// Change it with [`File::set_meta()`]. + pub fn meta(&self) -> &Metadata { + &self.meta + } + + /// Change the origin of this instance to be the given `meta`data. + /// + /// This is useful to control what origin about-to-be-added sections receive. + pub fn set_meta(&mut self, meta: impl Into<OwnShared<Metadata>>) -> &mut Self { + self.meta = meta.into(); + self + } + + /// Similar to [`meta()`][File::meta()], but with shared ownership. + pub fn meta_owned(&self) -> OwnShared<Metadata> { + OwnShared::clone(&self.meta) + } + + /// Return an iterator over all sections, in order of occurrence in the file itself. + pub fn sections(&self) -> impl Iterator<Item = &file::Section<'event>> + '_ { + self.section_order.iter().map(move |id| &self.sections[id]) + } + + /// Return an iterator over all sections and their ids, in order of occurrence in the file itself. + pub fn sections_and_ids(&self) -> impl Iterator<Item = (&file::Section<'event>, SectionId)> + '_ { + self.section_order.iter().map(move |id| (&self.sections[id], *id)) + } + + /// Return an iterator over all sections along with non-section events that are placed right after them, + /// in order of occurrence in the file itself. + /// + /// This allows to reproduce the look of sections perfectly when serializing them with + /// [`write_to()`][file::Section::write_to()]. + pub fn sections_and_postmatter(&self) -> impl Iterator<Item = (&file::Section<'event>, Vec<&Event<'event>>)> { + self.section_order.iter().map(move |id| { + let s = &self.sections[id]; + let pm: Vec<_> = self + .frontmatter_post_section + .get(id) + .map(|events| events.iter().collect()) + .unwrap_or_default(); + (s, pm) + }) + } + + /// Return all events which are in front of the first of our sections, or `None` if there are none. + pub fn frontmatter(&self) -> Option<impl Iterator<Item = &Event<'event>>> { + (!self.frontmatter_events.is_empty()).then(|| self.frontmatter_events.iter()) + } + + /// Return the newline characters that have been detected in this config file or the default ones + /// for the current platform. + /// + /// Note that the first found newline is the one we use in the assumption of consistency. + pub fn detect_newline_style(&self) -> &BStr { + self.frontmatter_events + .iter() + .find_map(extract_newline) + .or_else(|| { + self.sections() + .find_map(|s| s.body.as_ref().iter().find_map(extract_newline)) + }) + .unwrap_or_else(|| platform_newline()) + } + + pub(crate) fn detect_newline_style_smallvec(&self) -> SmallVec<[u8; 2]> { + self.detect_newline_style().as_bytes().into() + } +} |