use std::{ borrow::Cow, collections::{HashMap, VecDeque}, convert::TryFrom, fmt::Display, path::Path,}; use crate::{ file::{ error::{GitConfigError, GitConfigFromEnvError}, section::{MutableSection, SectionBody}, value::{EntryData, MutableMultiValue, MutableValue}, Index, Size, }, parser::{ parse_from_bytes, parse_from_path, parse_from_str, Error, Event, Key, ParsedSectionHeader, Parser, ParserOrIoError, SectionHeaderName, },}; /// The section ID is a monotonically increasing ID used to refer to sections./// This value does not imply any ordering between sections, as new sections/// with higher section IDs may be in between lower ID sections.////// We need to use a section id because `git-config` permits sections with/// identical names. As a result, we can't simply use the section name as a key/// in a map.////// This id guaranteed to be unique, but not guaranteed to be compact. In other/// words, it's possible that a section may have an ID of 3 but the next section/// has an ID of 5.#[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)]pub(super) struct SectionId(usize); /// Internal data structure for the section id lookup tree used by/// [`GitConfig`]. Note that order in Vec matters as it represents the order/// of section ids with the matched section and name, and is used for precedence/// management.#[derive(PartialEq, Eq, Clone, Debug)]pub(super) enum LookupTreeNode<'a> { Terminal(Vec<SectionId>), NonTerminal(HashMap<Cow<'a, str>, Vec<SectionId>>),} /// High level `git-config` reader and writer.////// This is the full-featured implementation that can deserialize, serialize,/// and edit `git-config` files without loss of whitespace or comments. As a/// result, it's lot more complex than it's read-only variant,/// [`ResolvedGitConfig`] that exposes a [`HashMap`]-like interface. Users that/// only need to read `git-config` files should use that instead.////// Internally, this uses various acceleration data structures to improve/// performance of the typical usage behavior of many lookups and relatively/// fewer insertions.////// # Multivar behavior////// `git` is flexible enough to allow users to set a key multiple times in/// any number of identically named sections. When this is the case, the key/// is known as a "multivar". In this case, `get_raw_value` follows the/// "last one wins" approach that `git-config` internally uses for multivar/// resolution.////// Concretely, the following config has a multivar, `a`, with the values/// of `b`, `c`, and `d`, while `e` is a single variable with the value/// `f g h`.////// ```text/// [core]/// a = b/// a = c/// [core]/// a = d/// e = f g h/// ```////// Calling methods that fetch or set only one value (such as [`get_raw_value`])/// key `a` with the above config will fetch `d` or replace `d`, since the last/// valid config key/value pair is `a = d`:////// ```/// # use git_config::file::GitConfig;/// # use std::borrow::Cow;/// # use std::convert::TryFrom;/// # let git_config = GitConfig::try_from("[core]a=b\n[core]\na=c\na=d").unwrap();/// assert_eq!(git_config.get_raw_value("core", None, "a"), Ok(Cow::Borrowed("d".as_bytes())));/// ```////// Consider the `multi` variants of the methods instead, if you want to work/// with all values instead.////// [`ResolvedGitConfig`]: crate::file::ResolvedGitConfig/// [`get_raw_value`]: Self::get_raw_value#[derive(PartialEq, Eq, Clone, Debug, Default)]pub struct GitConfig<'event> { /// The list of events that occur before an actual section. Since a /// `git-config` file prohibits global values, this vec is limited to only /// comment, newline, and whitespace events. frontmatter_events: SectionBody<'event>, /// Section name and subsection name to section id lookup tree. This is /// effectively a n-tree (opposed to a binary tree) that can have a height /// of at most three (including an implicit root node). pub(super) section_lookup_tree: HashMap<SectionHeaderName<'event>, Vec<LookupTreeNode<'event>>>, /// SectionId to section mapping. The value of this HashMap contains actual /// events. /// /// This indirection with the SectionId as the key is critical to flexibly /// supporting `git-config` sections, as duplicated keys are permitted. pub(super) sections: HashMap<SectionId, SectionBody<'event>>, section_headers: HashMap<SectionId, ParsedSectionHeader<'event>>, /// Internal monotonically increasing counter for section ids. section_id_counter: usize, /// Section order for output ordering. section_order: VecDeque<SectionId>,} impl<'event> GitConfig<'event> { /// Constructs an empty `git-config` file. #[inline] #[must_use] pub fn new() -> Self { Self::default() } /// Constructs a `git-config` file from the provided path. /// /// # Errors /// /// Returns an error if there was an IO error or if the file wasn't a valid /// git-config file. #[inline] pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, ParserOrIoError<'static>> { parse_from_path(path).map(Self::from) } /// Generates a config from the environment variables. This is neither /// zero-copy nor zero-alloc. See [`git-config`'s documentation] on /// environment variable for more information. /// /// # Errors /// /// Returns an error if `GIT_CONFIG_COUNT` set and is not a number, or if /// there was an invalid key value pair. /// /// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT pub fn from_env() -> Result<Option<Self>, GitConfigFromEnvError> { use std::env; let count: usize = match env::var("GIT_CONFIG_COUNT") { Ok(v) => v.parse().map_err(|_| GitConfigFromEnvError::ParseError(v))?, Err(_) => return Ok(None), }; let mut config = Self::new(); for i in 0..count { let key = env::var(format!("GIT_CONFIG_KEY_{}", i)).map_err(|_| GitConfigFromEnvError::InvalidKeyId(i))?; let value = env::var(format!("GIT_CONFIG_VALUE_{}", i)).map_err(|_| GitConfigFromEnvError::InvalidValueId(i))?; if let Some((section, maybe_subsection)) = key.split_once('.') { let (subsection, key) = if let Some((subsection, key)) = maybe_subsection.rsplit_once('.') { (Some(subsection), key) } else { (None, maybe_subsection) }; let mut section = if let Ok(section) = config.section_mut(section, subsection) { section } else { // Need to have config own the section and subsection names // else they get dropped at the end of the loop. config.new_section( section.to_string(), subsection.map(|subsection| Cow::Owned(subsection.to_string())), ) }; section.push( Cow::<str>::Owned(key.to_string()).into(), Cow::Owned(value.into_bytes()), ); } else { return Err(GitConfigFromEnvError::InvalidKeyValue(i, key.to_string())); } } // This occurs when `GIT_CONFIG_COUNT` is set to zero. if config.is_empty() { Ok(None) } else { Ok(Some(config)) } } /// Returns an interpreted value given a section, an optional subsection and /// key. /// /// It's recommended to use one of the values in the [`values`] module as /// the conversion is already implemented, but this function is flexible and /// will accept any type that implements [`TryFrom<&[u8]>`][`TryFrom`]. /// /// Consider [`Self::multi_value`] if you want to get all values of a /// multivar instead. /// /// # Examples /// /// ``` /// # use git_config::file::{GitConfig, GitConfigError}; /// # use git_config::values::{Integer, Value, Boolean}; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; /// let config = r#" /// [core] /// a = 10k /// c /// "#; /// let git_config = GitConfig::try_from(config)?; /// // You can either use the turbofish to determine the type... /// let a_value = git_config.value::<Integer>("core", None, "a")?; /// // ... or explicitly declare the type to avoid the turbofish /// let c_value: Boolean = git_config.value("core", None, "c")?; /// # Ok::<(), Box<dyn std::error::Error>>(()) /// ``` /// /// # Errors /// /// This function will return an error if the key is not in the requested /// section and subsection, if the section and subsection do not exist, or /// if there was an issue converting the type into the requested variant. /// /// [`values`]: crate::values /// [`TryFrom`]: std::convert::TryFrom #[inline] pub fn value<'lookup, T: TryFrom<Cow<'event, [u8]>>>( &'event self, section_name: &'lookup str, subsection_name: Option<&'lookup str>, key: &'lookup str, ) -> Result<T, GitConfigError<'lookup>> { T::try_from(self.get_raw_value(section_name, subsection_name, key)?) .map_err(|_| GitConfigError::FailedConversion) } /// Returns all interpreted values given a section, an optional subsection /// and key. /// /// It's recommended to use one of the values in the [`values`] module as /// the conversion is already implemented, but this function is flexible and /// will accept any type that implements [`TryFrom<&[u8]>`][`TryFrom`]. /// /// Consider [`Self::value`] if you want to get a single value /// (following last-one-wins resolution) instead. /// /// # Examples /// /// ``` /// # use git_config::file::{GitConfig, GitConfigError}; /// # use git_config::values::{Integer, Value, Boolean, TrueVariant}; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; /// let config = r#" /// [core] /// a = true /// c = g /// [core] /// a /// a = false /// "#; /// let git_config = GitConfig::try_from(config).unwrap(); /// // You can either use the turbofish to determine the type... /// let a_value = git_config.multi_value::<Boolean>("core", None, "a")?; /// assert_eq!( /// a_value, /// vec![ /// Boolean::True(TrueVariant::Explicit(Cow::Borrowed("true"))), /// Boolean::True(TrueVariant::Implicit), /// Boolean::False(Cow::Borrowed("false")), /// ] /// ); /// // ... or explicitly declare the type to avoid the turbofish /// let c_value: Vec<Value> = git_config.multi_value("core", None, "c")?; /// assert_eq!(c_value, vec![Value::Other(Cow::Borrowed(b"g"))]); /// # Ok::<(), GitConfigError>(()) /// ``` /// /// # Errors /// /// This function will return an error if the key is not in the requested /// section and subsection, if the section and subsection do not exist, or /// if there was an issue converting the type into the requested variant. /// /// [`values`]: crate::values /// [`TryFrom`]: std::convert::TryFrom #[inline] pub fn multi_value<'lookup, T: TryFrom<Cow<'event, [u8]>>>( &'event self, section_name: &'lookup str, subsection_name: Option<&'lookup str>, key: &'lookup str, ) -> Result<Vec<T>, GitConfigError<'lookup>> { self.get_raw_multi_value(section_name, subsection_name, key)? .into_iter() .map(T::try_from) .collect::<Result<Vec<_>, _>>() .map_err(|_| GitConfigError::FailedConversion) } /// Returns an immutable section reference. /// /// # Errors /// /// This function will return an error if the section and optional /// subsection do not exist. pub fn section<'lookup>( &mut self, section_name: &'lookup str, subsection_name: Option<&'lookup str>, ) -> Result<&SectionBody<'event>, GitConfigError<'lookup>> { let section_ids = self.get_section_ids_by_name_and_subname(section_name, subsection_name)?; let id = section_ids .last() .expect("Section lookup vec was empty, internal invariant violated"); Ok(self .sections .get(id) .expect("Section did not have id from lookup, internal invariant violated")) } /// Returns an mutable section reference. /// /// # Errors /// /// This function will return an error if the section and optional /// subsection do not exist. pub fn section_mut<'lookup>( &mut self, section_name: &'lookup str, subsection_name: Option<&'lookup str>, ) -> Result<MutableSection<'_, 'event>, GitConfigError<'lookup>> { let section_ids = self.get_section_ids_by_name_and_subname(section_name, subsection_name)?; let id = section_ids .last() .expect("Section lookup vec was empty, internal invariant violated"); Ok(MutableSection::new(self.sections.get_mut(id).expect( "Section did not have id from lookup, internal invariant violated", ))) } /// 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 git_config::file::{GitConfig, GitConfigError}; /// # use git_config::values::{Integer, Value, Boolean, TrueVariant}; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; /// let config = r#" /// [core] /// a = b /// [core ""] /// c = d /// [core "apple"] /// e = f /// "#; /// let git_config = GitConfig::try_from(config).unwrap(); /// assert_eq!(git_config.sections_by_name("core").len(), 3); /// ``` #[must_use] pub fn sections_by_name<'lookup>(&self, section_name: &'lookup str) -> Vec<&SectionBody<'event>> { self.get_section_ids_by_name(section_name) .unwrap_or_default() .into_iter() .map(|id| { self.sections .get(&id) .expect("section doesn't have id from from lookup") }) .collect() } /// Adds a new section to config. 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 git_config::file::{GitConfig, GitConfigError}; /// # use std::convert::TryFrom; /// let mut git_config = GitConfig::new(); /// let _section = git_config.new_section("hello", Some("world".into())); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n"); /// ``` /// /// Creating a new empty section and adding values to it: /// /// ``` /// # use git_config::file::{GitConfig, GitConfigError}; /// # use std::convert::TryFrom; /// let mut git_config = GitConfig::new(); /// let mut section = git_config.new_section("hello", Some("world".into())); /// section.push("a".into(), "b".as_bytes().into()); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n"); /// let _section = git_config.new_section("core", None); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n[core]\n"); /// ``` pub fn new_section( &mut self, section_name: impl Into<Cow<'event, str>>, subsection_name: impl Into<Option<Cow<'event, str>>>, ) -> MutableSection<'_, 'event> { let mut section = self.push_section(section_name, subsection_name, SectionBody::new()); section.push_newline(); section } /// Removes the section, returning the events it had, if any. 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 git_config::file::{GitConfig, GitConfigError}; /// # use std::convert::TryFrom; /// let mut git_config = GitConfig::try_from( /// r#"[hello "world"] /// some-value = 4 /// "#).unwrap(); /// /// let events = git_config.remove_section("hello", Some("world".into())); /// assert_eq!(git_config.to_string(), ""); /// ``` /// /// Precedence example for removing sections with the same name: /// /// ``` /// # use git_config::file::{GitConfig, GitConfigError}; /// # use std::convert::TryFrom; /// let mut git_config = GitConfig::try_from( /// r#"[hello "world"] /// some-value = 4 /// [hello "world"] /// some-value = 5 /// "#).unwrap(); /// /// let events = git_config.remove_section("hello", Some("world".into())); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n some-value = 4\n"); /// ``` pub fn remove_section<'lookup>( &mut self, section_name: &'lookup str, subsection_name: impl Into<Option<&'lookup str>>, ) -> Option<SectionBody> { let id = self .get_section_ids_by_name_and_subname(section_name, subsection_name.into()) .ok()? .pop()?; self.section_order.remove( self.section_order .iter() .position(|v| *v == id) .expect("Section order does not contain section that we were trying to remove"), ); self.sections.remove(&id) } /// Adds the provided section to the config, returning a mutable reference /// to it. pub fn push_section( &mut self, section_name: impl Into<Cow<'event, str>>, subsection_name: impl Into<Option<Cow<'event, str>>>, section: SectionBody<'event>, ) -> MutableSection<'_, 'event> { let subsection_name = subsection_name.into(); if subsection_name.is_some() { self.push_section_internal( ParsedSectionHeader { name: SectionHeaderName(section_name.into()), separator: Some(" ".into()),Lines 1–500 of 1957. Load more
use std::{ borrow::Cow, collections::{HashMap, VecDeque}, convert::TryFrom, fmt::Display, path::Path, }; use crate::{ file::{ error::{GitConfigError, GitConfigFromEnvError}, section::{MutableSection, SectionBody}, value::{EntryData, MutableMultiValue, MutableValue}, Index, Size, }, parser::{ parse_from_bytes, parse_from_path, parse_from_str, Error, Event, Key, ParsedSectionHeader, Parser, ParserOrIoError, SectionHeaderName, }, }; /// The section ID is a monotonically increasing ID used to refer to sections. /// This value does not imply any ordering between sections, as new sections /// with higher section IDs may be in between lower ID sections. /// /// We need to use a section id because `git-config` permits sections with /// identical names. As a result, we can't simply use the section name as a key /// in a map. /// /// This id guaranteed to be unique, but not guaranteed to be compact. In other /// words, it's possible that a section may have an ID of 3 but the next section /// has an ID of 5. #[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)] pub(super) struct SectionId(usize); /// Internal data structure for the section id lookup tree used by /// [`GitConfig`]. Note that order in Vec matters as it represents the order /// of section ids with the matched section and name, and is used for precedence /// management. #[derive(PartialEq, Eq, Clone, Debug)] pub(super) enum LookupTreeNode<'a> { Terminal(Vec<SectionId>), NonTerminal(HashMap<Cow<'a, str>, Vec<SectionId>>), } /// High level `git-config` reader and writer. /// /// This is the full-featured implementation that can deserialize, serialize, /// and edit `git-config` files without loss of whitespace or comments. As a /// result, it's lot more complex than it's read-only variant, /// [`ResolvedGitConfig`] that exposes a [`HashMap`]-like interface. Users that /// only need to read `git-config` files should use that instead. /// /// Internally, this uses various acceleration data structures to improve /// performance of the typical usage behavior of many lookups and relatively /// fewer insertions. /// /// # Multivar behavior /// /// `git` is flexible enough to allow users to set a key multiple times in /// any number of identically named sections. When this is the case, the key /// is known as a "multivar". In this case, `get_raw_value` follows the /// "last one wins" approach that `git-config` internally uses for multivar /// resolution. /// /// Concretely, the following config has a multivar, `a`, with the values /// of `b`, `c`, and `d`, while `e` is a single variable with the value /// `f g h`. /// /// ```text /// [core] /// a = b /// a = c /// [core] /// a = d /// e = f g h /// ``` /// /// Calling methods that fetch or set only one value (such as [`get_raw_value`]) /// key `a` with the above config will fetch `d` or replace `d`, since the last /// valid config key/value pair is `a = d`: /// /// ``` /// # use git_config::file::GitConfig; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; /// # let git_config = GitConfig::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); /// assert_eq!(git_config.get_raw_value("core", None, "a"), Ok(Cow::Borrowed("d".as_bytes()))); /// ``` /// /// Consider the `multi` variants of the methods instead, if you want to work /// with all values instead. /// /// [`ResolvedGitConfig`]: crate::file::ResolvedGitConfig /// [`get_raw_value`]: Self::get_raw_value #[derive(PartialEq, Eq, Clone, Debug, Default)] pub struct GitConfig<'event> { /// The list of events that occur before an actual section. Since a /// `git-config` file prohibits global values, this vec is limited to only /// comment, newline, and whitespace events. frontmatter_events: SectionBody<'event>, /// Section name and subsection name to section id lookup tree. This is /// effectively a n-tree (opposed to a binary tree) that can have a height /// of at most three (including an implicit root node). pub(super) section_lookup_tree: HashMap<SectionHeaderName<'event>, Vec<LookupTreeNode<'event>>>, /// SectionId to section mapping. The value of this HashMap contains actual /// events. /// /// This indirection with the SectionId as the key is critical to flexibly /// supporting `git-config` sections, as duplicated keys are permitted. pub(super) sections: HashMap<SectionId, SectionBody<'event>>, section_headers: HashMap<SectionId, ParsedSectionHeader<'event>>, /// Internal monotonically increasing counter for section ids. section_id_counter: usize, /// Section order for output ordering. section_order: VecDeque<SectionId>, } impl<'event> GitConfig<'event> { /// Constructs an empty `git-config` file. #[inline] #[must_use] pub fn new() -> Self { Self::default() } /// Constructs a `git-config` file from the provided path. /// /// # Errors /// /// Returns an error if there was an IO error or if the file wasn't a valid /// git-config file. #[inline] pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, ParserOrIoError<'static>> { parse_from_path(path).map(Self::from) } /// Generates a config from the environment variables. This is neither /// zero-copy nor zero-alloc. See [`git-config`'s documentation] on /// environment variable for more information. /// /// # Errors /// /// Returns an error if `GIT_CONFIG_COUNT` set and is not a number, or if /// there was an invalid key value pair. /// /// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT pub fn from_env() -> Result<Option<Self>, GitConfigFromEnvError> { use std::env; let count: usize = match env::var("GIT_CONFIG_COUNT") { Ok(v) => v.parse().map_err(|_| GitConfigFromEnvError::ParseError(v))?, Err(_) => return Ok(None), }; let mut config = Self::new(); for i in 0..count { let key = env::var(format!("GIT_CONFIG_KEY_{}", i)).map_err(|_| GitConfigFromEnvError::InvalidKeyId(i))?; let value = env::var(format!("GIT_CONFIG_VALUE_{}", i)).map_err(|_| GitConfigFromEnvError::InvalidValueId(i))?; if let Some((section, maybe_subsection)) = key.split_once('.') { let (subsection, key) = if let Some((subsection, key)) = maybe_subsection.rsplit_once('.') { (Some(subsection), key) } else { (None, maybe_subsection) }; let mut section = if let Ok(section) = config.section_mut(section, subsection) { section } else { // Need to have config own the section and subsection names // else they get dropped at the end of the loop. config.new_section( section.to_string(), subsection.map(|subsection| Cow::Owned(subsection.to_string())), ) }; section.push( Cow::<str>::Owned(key.to_string()).into(), Cow::Owned(value.into_bytes()), ); } else { return Err(GitConfigFromEnvError::InvalidKeyValue(i, key.to_string())); } } // This occurs when `GIT_CONFIG_COUNT` is set to zero. if config.is_empty() { Ok(None) } else { Ok(Some(config)) } } /// Returns an interpreted value given a section, an optional subsection and /// key. /// /// It's recommended to use one of the values in the [`values`] module as /// the conversion is already implemented, but this function is flexible and /// will accept any type that implements [`TryFrom<&[u8]>`][`TryFrom`]. /// /// Consider [`Self::multi_value`] if you want to get all values of a /// multivar instead. /// /// # Examples /// /// ``` /// # use git_config::file::{GitConfig, GitConfigError}; /// # use git_config::values::{Integer, Value, Boolean}; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; /// let config = r#" /// [core] /// a = 10k /// c /// "#; /// let git_config = GitConfig::try_from(config)?; /// // You can either use the turbofish to determine the type... /// let a_value = git_config.value::<Integer>("core", None, "a")?; /// // ... or explicitly declare the type to avoid the turbofish /// let c_value: Boolean = git_config.value("core", None, "c")?; /// # Ok::<(), Box<dyn std::error::Error>>(()) /// ``` /// /// # Errors /// /// This function will return an error if the key is not in the requested /// section and subsection, if the section and subsection do not exist, or /// if there was an issue converting the type into the requested variant. /// /// [`values`]: crate::values /// [`TryFrom`]: std::convert::TryFrom #[inline] pub fn value<'lookup, T: TryFrom<Cow<'event, [u8]>>>( &'event self, section_name: &'lookup str, subsection_name: Option<&'lookup str>, key: &'lookup str, ) -> Result<T, GitConfigError<'lookup>> { T::try_from(self.get_raw_value(section_name, subsection_name, key)?) .map_err(|_| GitConfigError::FailedConversion) } /// Returns all interpreted values given a section, an optional subsection /// and key. /// /// It's recommended to use one of the values in the [`values`] module as /// the conversion is already implemented, but this function is flexible and /// will accept any type that implements [`TryFrom<&[u8]>`][`TryFrom`]. /// /// Consider [`Self::value`] if you want to get a single value /// (following last-one-wins resolution) instead. /// /// # Examples /// /// ``` /// # use git_config::file::{GitConfig, GitConfigError}; /// # use git_config::values::{Integer, Value, Boolean, TrueVariant}; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; /// let config = r#" /// [core] /// a = true /// c = g /// [core] /// a /// a = false /// "#; /// let git_config = GitConfig::try_from(config).unwrap(); /// // You can either use the turbofish to determine the type... /// let a_value = git_config.multi_value::<Boolean>("core", None, "a")?; /// assert_eq!( /// a_value, /// vec![ /// Boolean::True(TrueVariant::Explicit(Cow::Borrowed("true"))), /// Boolean::True(TrueVariant::Implicit), /// Boolean::False(Cow::Borrowed("false")), /// ] /// ); /// // ... or explicitly declare the type to avoid the turbofish /// let c_value: Vec<Value> = git_config.multi_value("core", None, "c")?; /// assert_eq!(c_value, vec![Value::Other(Cow::Borrowed(b"g"))]); /// # Ok::<(), GitConfigError>(()) /// ``` /// /// # Errors /// /// This function will return an error if the key is not in the requested /// section and subsection, if the section and subsection do not exist, or /// if there was an issue converting the type into the requested variant. /// /// [`values`]: crate::values /// [`TryFrom`]: std::convert::TryFrom #[inline] pub fn multi_value<'lookup, T: TryFrom<Cow<'event, [u8]>>>( &'event self, section_name: &'lookup str, subsection_name: Option<&'lookup str>, key: &'lookup str, ) -> Result<Vec<T>, GitConfigError<'lookup>> { self.get_raw_multi_value(section_name, subsection_name, key)? .into_iter() .map(T::try_from) .collect::<Result<Vec<_>, _>>() .map_err(|_| GitConfigError::FailedConversion) } /// Returns an immutable section reference. /// /// # Errors /// /// This function will return an error if the section and optional /// subsection do not exist. pub fn section<'lookup>( &mut self, section_name: &'lookup str, subsection_name: Option<&'lookup str>, ) -> Result<&SectionBody<'event>, GitConfigError<'lookup>> { let section_ids = self.get_section_ids_by_name_and_subname(section_name, subsection_name)?; let id = section_ids .last() .expect("Section lookup vec was empty, internal invariant violated"); Ok(self .sections .get(id) .expect("Section did not have id from lookup, internal invariant violated")) } /// Returns an mutable section reference. /// /// # Errors /// /// This function will return an error if the section and optional /// subsection do not exist. pub fn section_mut<'lookup>( &mut self, section_name: &'lookup str, subsection_name: Option<&'lookup str>, ) -> Result<MutableSection<'_, 'event>, GitConfigError<'lookup>> { let section_ids = self.get_section_ids_by_name_and_subname(section_name, subsection_name)?; let id = section_ids .last() .expect("Section lookup vec was empty, internal invariant violated"); Ok(MutableSection::new(self.sections.get_mut(id).expect( "Section did not have id from lookup, internal invariant violated", ))) } /// 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 git_config::file::{GitConfig, GitConfigError}; /// # use git_config::values::{Integer, Value, Boolean, TrueVariant}; /// # use std::borrow::Cow; /// # use std::convert::TryFrom; /// let config = r#" /// [core] /// a = b /// [core ""] /// c = d /// [core "apple"] /// e = f /// "#; /// let git_config = GitConfig::try_from(config).unwrap(); /// assert_eq!(git_config.sections_by_name("core").len(), 3); /// ``` #[must_use] pub fn sections_by_name<'lookup>(&self, section_name: &'lookup str) -> Vec<&SectionBody<'event>> { self.get_section_ids_by_name(section_name) .unwrap_or_default() .into_iter() .map(|id| { self.sections .get(&id) .expect("section doesn't have id from from lookup") }) .collect() } /// Adds a new section to config. 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 git_config::file::{GitConfig, GitConfigError}; /// # use std::convert::TryFrom; /// let mut git_config = GitConfig::new(); /// let _section = git_config.new_section("hello", Some("world".into())); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n"); /// ``` /// /// Creating a new empty section and adding values to it: /// /// ``` /// # use git_config::file::{GitConfig, GitConfigError}; /// # use std::convert::TryFrom; /// let mut git_config = GitConfig::new(); /// let mut section = git_config.new_section("hello", Some("world".into())); /// section.push("a".into(), "b".as_bytes().into()); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n"); /// let _section = git_config.new_section("core", None); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n a=b\n[core]\n"); /// ``` pub fn new_section( &mut self, section_name: impl Into<Cow<'event, str>>, subsection_name: impl Into<Option<Cow<'event, str>>>, ) -> MutableSection<'_, 'event> { let mut section = self.push_section(section_name, subsection_name, SectionBody::new()); section.push_newline(); section } /// Removes the section, returning the events it had, if any. 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 git_config::file::{GitConfig, GitConfigError}; /// # use std::convert::TryFrom; /// let mut git_config = GitConfig::try_from( /// r#"[hello "world"] /// some-value = 4 /// "#).unwrap(); /// /// let events = git_config.remove_section("hello", Some("world".into())); /// assert_eq!(git_config.to_string(), ""); /// ``` /// /// Precedence example for removing sections with the same name: /// /// ``` /// # use git_config::file::{GitConfig, GitConfigError}; /// # use std::convert::TryFrom; /// let mut git_config = GitConfig::try_from( /// r#"[hello "world"] /// some-value = 4 /// [hello "world"] /// some-value = 5 /// "#).unwrap(); /// /// let events = git_config.remove_section("hello", Some("world".into())); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n some-value = 4\n"); /// ``` pub fn remove_section<'lookup>( &mut self, section_name: &'lookup str, subsection_name: impl Into<Option<&'lookup str>>, ) -> Option<SectionBody> { let id = self .get_section_ids_by_name_and_subname(section_name, subsection_name.into()) .ok()? .pop()?; self.section_order.remove( self.section_order .iter() .position(|v| *v == id) .expect("Section order does not contain section that we were trying to remove"), ); self.sections.remove(&id) } /// Adds the provided section to the config, returning a mutable reference /// to it. pub fn push_section( &mut self, section_name: impl Into<Cow<'event, str>>, subsection_name: impl Into<Option<Cow<'event, str>>>, section: SectionBody<'event>, ) -> MutableSection<'_, 'event> { let subsection_name = subsection_name.into(); if subsection_name.is_some() { self.push_section_internal( ParsedSectionHeader { name: SectionHeaderName(section_name.into()), separator: Some(" ".into()),