diff options
Diffstat (limited to 'vendor/gix-config/src/file/access/read_only.rs')
-rw-r--r-- | vendor/gix-config/src/file/access/read_only.rs | 353 |
1 files changed, 353 insertions, 0 deletions
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() + } +} |