318 lines · 10.1 KB · Rust
RawDownload
use bstr::{BStr, BString, ByteSlice};use smallvec::SmallVec; use crate::{    file,    file::{IntoBStringOpt, Metadata, Section, SectionData, SectionMut, SectionRef},    parse,    parse::{Event, section},}; pub(crate) mod body;pub(crate) use body::BodyData;pub use body::{BodyRef, BodyRefIter};use gix_features::threading::OwnShared; use crate::file::{SectionId, write::platform_newline}; /// Errors related to changing values in a section.pub mod value {    /// The error returned when adding or changing a value in a section.    #[derive(Debug, thiserror::Error)]    #[allow(missing_docs)]    pub enum Error {        #[error(transparent)]        ValueName(#[from] crate::parse::section::value_name::Error),        #[error(transparent)]        Span(#[from] crate::parse::span::Error),    }} impl std::ops::Deref for SectionData {    type Target = BodyData;     fn deref(&self) -> &Self::Target {        &self.body    }} /// A view of a section header whose bytes are owned by the containing [`File`][crate::File].#[derive(Copy, Clone, Debug)]pub struct HeaderRef<'a> {    pub(crate) header: &'a section::HeaderData,    pub(crate) backing: &'a [u8],} impl<'a> HeaderRef<'a> {    /// Return true if this is a header like `[legacy.subsection]`, or false otherwise.    pub fn is_legacy(&self) -> bool {        self.header            .separator            .as_ref()            .is_some_and(|separator| separator.as_slice_in(self.backing) == b".")    }     /// Return the subsection name, if present, i.e. "origin" in `[remote "origin"]`.    pub fn subsection_name(&self) -> Option<&'a BStr> {        self.header            .subsection_name            .as_ref()            .map(|subsection_name| subsection_name.value_in(self.backing))    }     /// Return the name of the header, like "remote" in `[remote "origin"]`.    pub fn name(&self) -> &'a BStr {        self.header.name.as_bstr_in(self.backing)    }     /// Serialize this header view into a `BString` for convenience.    #[must_use]    pub fn to_bstring(&self) -> BString {        let mut buf = Vec::new();        self.header            .write_to_in(self.backing, &mut buf)            .expect("io error impossible");        buf.into()    }} /// Instantiation and conversionimpl<'file> SectionRef<'file> {    pub(crate) fn from_data(data: &'file SectionData, backing: &'file [u8]) -> Self {        SectionRef { data, backing }    }} impl Section {    /// Create an owned section with an empty body.    pub fn new(        name: impl AsRef<str>,        subsection: impl IntoBStringOpt,        meta: impl Into<OwnShared<file::Metadata>>,    ) -> Result<Self, parse::section::header::Error> {        let mut backing = Vec::new();        let data = SectionData::new(name, subsection.into_bstring_opt(), meta, &mut backing)?;        Ok(Section { backing, data })    }     /// Return a read-only view of this section.    pub fn to_ref(&self) -> SectionRef<'_> {        SectionRef::from_data(&self.data, &self.backing)    }     /// Return a mutable view of this section.    pub fn to_mut(&mut self) -> SectionMut<'_> {        let newline = self            .data            .body            .detect_newline_style_in(&self.backing)            .unwrap_or_else(|| platform_newline())            .as_bytes()            .into();        SectionMut::new(&mut self.data, &mut self.backing, None, newline)    }     pub(crate) fn from_data(data: &SectionData, source: &[u8]) -> Self {        let mut backing = Vec::new();        let data = data            .copy_to_backing_in(source, &mut backing)            .expect("copying into an empty buffer cannot exceed the source buffer's span limit");        Section { backing, data }    }     pub(crate) fn into_data(self, target: &mut Vec<u8>) -> Result<SectionData, parse::span::Error> {        self.data.copy_to_backing_in(&self.backing, target)    }} impl SectionData {    /// Create a new section with the given `name` and optional, `subsection`, `meta`-data and an empty body.    pub(crate) fn new(        name: impl AsRef<str>,        subsection: impl Into<Option<BString>>,        meta: impl Into<OwnShared<file::Metadata>>,        backing: &mut Vec<u8>,    ) -> Result<Self, parse::section::header::Error> {        Ok(SectionData {            header: parse::section::HeaderData::new_in(name, subsection, backing)?,            body: Default::default(),            meta: meta.into(),            id: SectionId::default(),        })    }     /// Returns a mutable version of this section for adjustment of values.    pub(crate) fn to_mut<'a>(        &'a mut self,        backing: &'a mut Vec<u8>,        lookup: file::mutable::section::LookupMut<'a>,        newline: SmallVec<[u8; 2]>,    ) -> SectionMut<'a> {        SectionMut::new(self, backing, Some(lookup), newline)    }     pub(crate) fn meta(&self) -> &Metadata {        &self.meta    }     pub(crate) fn copy_to_backing_in(&self, source: &[u8], target: &mut Vec<u8>) -> Result<Self, parse::span::Error> {        Ok(SectionData {            header: self.header.copy_to_backing_in(source, target)?,            body: self.body.copy_to_backing_in(source, target)?,            meta: OwnShared::clone(&self.meta),            id: self.id,        })    }} /// Accessimpl<'file> SectionRef<'file> {    /// Copy this view into a self-contained owned section.    #[must_use]    pub fn to_owned(self) -> Section {        Section::from_data(self.data, self.backing)    }     /// Return our header.    pub fn header(&self) -> HeaderRef<'file> {        HeaderRef {            header: &self.data.header,            backing: self.backing,        }    }     /// Return the unique `id` of the section, for use with the `*_by_id()` family of methods    /// in [`gix_config::File`][crate::File].    pub fn id(&self) -> SectionId {        self.data.id    }     /// Return our body, containing all value names and values.    pub fn body(&self) -> BodyRef<'file> {        BodyRef {            body: &self.data.body,            backing: self.backing,        }    }     pub(crate) fn body_data(&self) -> &'file BodyData {        &self.data.body    }     /// Serialize this type into a `BString` for convenience.    ///    /// Note that `to_string()` can also be used, but might not be lossless.    #[must_use]    pub fn to_bstring(&self) -> BString {        let mut buf = Vec::new();        self.write_to(&mut buf).expect("io error impossible");        buf.into()    }     /// Stream ourselves to the given `out`, in order to reproduce this section mostly losslessly    /// as it was parsed.    pub fn write_to(&self, mut out: &mut dyn std::io::Write) -> std::io::Result<()> {        let nl = self            .body_data()            .detect_newline_style_in(self.backing)            .unwrap_or_else(|| platform_newline());        self.write_to_with_newline(&mut out, nl)    }     pub(crate) fn write_to_with_newline(&self, mut out: &mut dyn std::io::Write, nl: &BStr) -> std::io::Result<()> {        self.data.header.write_to_in(self.backing, &mut *out)?;         if self.body_data().0.is_empty() {            return Ok(());        }         if !self            .body_data()            .as_ref()            .iter()            .take_while(|e| !matches!(e, Event::SectionValueName(_)))            .any(|e| e.to_bstr_lossy_in(self.backing).contains_str(nl))        {            out.write_all(nl)?;        }         let mut saw_newline_after_value = true;        let mut in_key_value_pair = false;        for (idx, event) in self.body_data().as_ref().iter().enumerate() {            match event {                Event::SectionValueName(_) => {                    if !saw_newline_after_value {                        out.write_all(nl)?;                    }                    saw_newline_after_value = false;                    in_key_value_pair = true;                }                Event::Newline(_) if !in_key_value_pair => {                    saw_newline_after_value = true;                }                Event::Value(_) | Event::ValueDone(_) => {                    in_key_value_pair = false;                }                _ => {}            }            event.write_to_in(self.backing, &mut out)?;            if let Event::ValueNotDone(_) = event {                if self                    .body_data()                    .0                    .get(idx + 1)                    .filter(|e| matches!(e, Event::Newline(_)))                    .is_none()                {                    out.write_all(nl)?;                }            }        }        Ok(())    }     /// Return additional information about this sections origin.    pub fn meta(&self) -> &'file Metadata {        &self.data.meta    }     /// Retrieves the last matching value in this section with the given value name, if present.    #[must_use]    pub fn value(&self, value_name: impl AsRef<str>) -> Option<BString> {        self.data            .body            .value_implicit_in(self.backing, value_name.as_ref())            .flatten()    }     /// Retrieves the last matching value in this section, including implicit values.    #[must_use]    pub fn value_implicit(&self, value_name: &str) -> Option<Option<BString>> {        self.data.body.value_implicit_in(self.backing, value_name)    }     /// Retrieves all values that have the provided value name.    #[must_use]    pub fn values(&self, value_name: &str) -> Vec<BString> {        self.data.body.values_in(self.backing, value_name)    }     /// Returns an iterator visiting all value names in order.    pub fn value_names(&self) -> impl Iterator<Item = String> + '_ {        self.data.body.as_ref().iter().filter_map(move |e| match e {            Event::SectionValueName(k) => Some(                k.as_bstr_in(self.backing)                    .to_str()                    .expect("parsed value names are ASCII")                    .to_owned(),            ),            _ => None,        })    }     /// Returns true if the section contains the provided value name.    #[must_use]    pub fn contains_value_name(&self, value_name: &str) -> bool {        self.data.body.contains_value_name_in(self.backing, value_name)    }}