Sebastian Thiel[object #177] fix docs2fd23ed5 years agoHistory
1957 lines · 65.9 KB · Rust
RawDownload
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.