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 conversion impl<'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, subsection: impl IntoBStringOpt, meta: impl Into>, ) -> Result { 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) -> Result { 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, subsection: impl Into>, meta: impl Into>, backing: &mut Vec, ) -> Result { 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, 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) -> Result { 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, }) } } /// Access impl<'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) -> Option { 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> { 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 { 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 + '_ { 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) } }