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};
pub mod value {
#[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
}
}
#[derive(Copy, Clone, Debug)]
pub struct HeaderRef<'a> {
pub(crate) header: &'a section::HeaderData,
pub(crate) backing: &'a [u8],
}
impl<'a> HeaderRef<'a> {
pub fn is_legacy(&self) -> bool {
self.header
.separator
.as_ref()
.is_some_and(|separator| separator.as_slice_in(self.backing) == b".")
}
pub fn subsection_name(&self) -> Option<&'a BStr> {
self.header
.subsection_name
.as_ref()
.map(|subsection_name| subsection_name.value_in(self.backing))
}
pub fn name(&self) -> &'a BStr {
self.header.name.as_bstr_in(self.backing)
}
#[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()
}
}
impl<'file> SectionRef<'file> {
pub(crate) fn from_data(data: &'file SectionData, backing: &'file [u8]) -> Self {
SectionRef { data, backing }
}
}
impl Section {
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 })
}
pub fn to_ref(&self) -> SectionRef<'_> {
SectionRef::from_data(&self.data, &self.backing)
}
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 {
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(),
})
}
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,
})
}
}
impl<'file> SectionRef<'file> {
#[must_use]
pub fn to_owned(self) -> Section {
Section::from_data(self.data, self.backing)
}
pub fn header(&self) -> HeaderRef<'file> {
HeaderRef {
header: &self.data.header,
backing: self.backing,
}
}
pub fn id(&self) -> SectionId {
self.data.id
}
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
}
#[must_use]
pub fn to_bstring(&self) -> BString {
let mut buf = Vec::new();
self.write_to(&mut buf).expect("io error impossible");
buf.into()
}
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(())
}
pub fn meta(&self) -> &'file Metadata {
&self.data.meta
}
#[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()
}
#[must_use]
pub fn value_implicit(&self, value_name: &str) -> Option<Option<BString>> {
self.data.body.value_implicit_in(self.backing, value_name)
}
#[must_use]
pub fn values(&self, value_name: &str) -> Vec<BString> {
self.data.body.values_in(self.backing, value_name)
}
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,
})
}
#[must_use]
pub fn contains_value_name(&self, value_name: &str) -> bool {
self.data.body.contains_value_name_in(self.backing, value_name)
}
}